123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787 |
- /**
- * @author Yosuke Ota
- * @copyright 2021 Yosuke Ota. All rights reserved.
- * See LICENSE file in root directory for full license.
- */
- 'use strict'
- const utils = require('./index')
- const eslintUtils = require('@eslint-community/eslint-utils')
- /**
- * @typedef {import('./style-variables').StyleVariablesContext} StyleVariablesContext
- */
- /**
- * @typedef {object} IHasPropertyOption
- * @property {boolean} [unknownCallAsAny]
- */
- /**
- * @typedef {object} NestPropertyNodeForExpression
- * @property {'expression'} type
- * @property {MemberExpression} node
- *
- * @typedef {object} NestPropertyNodeForPattern
- * @property {'pattern'} type
- * @property {MemberExpression | Identifier | ObjectPattern | ArrayPattern} node
- *
- * @typedef {NestPropertyNodeForExpression | NestPropertyNodeForPattern} NestPropertyNode
- */
- /**
- * @typedef {object} IPropertyReferences
- * @property { (name: string, option?: IHasPropertyOption) => boolean } hasProperty
- * @property { () => Map<string, {nodes:ASTNode[]}> } allProperties Get all properties.
- * @property { (name: string) => IPropertyReferences } getNest Get the nesting property references.
- * @property { (name: string) => Iterable<NestPropertyNode> } getNestNodes Get the nesting property nodes.
- */
- /** @type {IPropertyReferences} */
- const ANY = {
- hasProperty: () => true,
- allProperties: () => new Map(),
- getNest: () => ANY,
- getNestNodes: () => []
- }
- /** @type {IPropertyReferences} */
- const NEVER = {
- hasProperty: () => false,
- allProperties: () => new Map(),
- getNest: () => NEVER,
- getNestNodes: () => []
- }
- /**
- * @param {RuleContext} context
- * @param {Identifier} id
- * @returns {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | null}
- */
- function findFunction(context, id) {
- const calleeVariable = utils.findVariableByIdentifier(context, id)
- if (!calleeVariable) {
- return null
- }
- if (calleeVariable.defs.length === 1) {
- const def = calleeVariable.defs[0]
- if (def.node.type === 'FunctionDeclaration') {
- return def.node
- }
- if (
- def.type === 'Variable' &&
- def.parent.kind === 'const' &&
- def.node.init
- ) {
- if (
- def.node.init.type === 'FunctionExpression' ||
- def.node.init.type === 'ArrowFunctionExpression'
- ) {
- return def.node.init
- }
- if (def.node.init.type === 'Identifier') {
- return findFunction(context, def.node.init)
- }
- }
- }
- return null
- }
- module.exports = {
- definePropertyReferenceExtractor,
- mergePropertyReferences
- }
- /**
- * @param {RuleContext} context The rule context.
- */
- function definePropertyReferenceExtractor(
- context,
- { unknownMemberAsUnreferenced = false, returnAsUnreferenced = false } = {}
- ) {
- /** @type {Map<Expression, IPropertyReferences>} */
- const cacheForExpression = new Map()
- /** @type {Map<Pattern, IPropertyReferences>} */
- const cacheForPattern = new Map()
- /** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, Map<number, IPropertyReferences>>} */
- const cacheForFunction = new Map()
- /** @type {{ toRefNodes: Set<ESNode>, toRefsNodes: Set<ESNode>} | null} */
- let toRefSet = null
- let isFunctionalTemplate = false
- const templateBody = context.getSourceCode().ast.templateBody
- if (templateBody) {
- isFunctionalTemplate = utils.hasAttribute(templateBody, 'functional')
- }
- function getToRefSet() {
- if (toRefSet) {
- return toRefSet
- }
- const tracker = new eslintUtils.ReferenceTracker(
- context.getSourceCode().scopeManager.scopes[0]
- )
- const toRefNodes = new Set()
- for (const { node } of utils.iterateReferencesTraceMap(tracker, {
- [eslintUtils.ReferenceTracker.ESM]: true,
- toRef: {
- [eslintUtils.ReferenceTracker.CALL]: true
- }
- })) {
- toRefNodes.add(node)
- }
- const toRefsNodes = new Set()
- for (const { node } of utils.iterateReferencesTraceMap(tracker, {
- [eslintUtils.ReferenceTracker.ESM]: true,
- toRefs: {
- [eslintUtils.ReferenceTracker.CALL]: true
- }
- })) {
- toRefsNodes.add(node)
- }
- return (toRefSet = { toRefNodes, toRefsNodes })
- }
- /**
- * Collects the property references for member expr.
- * @implements {IPropertyReferences}
- */
- class PropertyReferencesForMember {
- /**
- *
- * @param {MemberExpression} node
- * @param {string} name
- * @param {boolean} withInTemplate
- */
- constructor(node, name, withInTemplate) {
- this.node = node
- this.name = name
- this.withInTemplate = withInTemplate
- }
- /**
- * @param {string} name
- */
- hasProperty(name) {
- return name === this.name
- }
- allProperties() {
- return new Map([[this.name, { nodes: [this.node.property] }]])
- }
- /**
- * @param {string} name
- * @returns {IPropertyReferences}
- */
- getNest(name) {
- return name === this.name
- ? extractFromExpression(this.node, this.withInTemplate)
- : NEVER
- }
- /**
- * @param {string} name
- * @returns {Iterable<NestPropertyNodeForExpression>}
- */
- *getNestNodes(name) {
- if (name === this.name) {
- yield {
- type: 'expression',
- node: this.node
- }
- }
- }
- }
- /**
- * Collects the property references for object.
- * @implements {IPropertyReferences}
- */
- class PropertyReferencesForObject {
- constructor() {
- /** @type {Record<string, AssignmentProperty[]>} */
- this.properties = Object.create(null)
- }
- /**
- * @param {string} name
- */
- hasProperty(name) {
- return Boolean(this.properties[name])
- }
- allProperties() {
- const result = new Map()
- for (const [name, nodes] of Object.entries(this.properties)) {
- result.set(name, { nodes: nodes.map((node) => node.key) })
- }
- return result
- }
- /**
- * @param {string} name
- * @returns {IPropertyReferences}
- */
- getNest(name) {
- const properties = this.properties[name]
- return properties
- ? mergePropertyReferences(
- properties.map((property) => getNestFromPattern(property.value))
- )
- : NEVER
- }
- /**
- * @param {string} name
- * @returns {Iterable<NestPropertyNodeForPattern>}
- */
- *getNestNodes(name) {
- const properties = this.properties[name]
- if (!properties) {
- return
- }
- const values = properties.map((property) => property.value)
- let node
- while ((node = values.shift())) {
- if (
- node.type === 'Identifier' ||
- node.type === 'MemberExpression' ||
- node.type === 'ObjectPattern' ||
- node.type === 'ArrayPattern'
- ) {
- yield {
- type: 'pattern',
- node
- }
- } else if (node.type === 'AssignmentPattern') {
- values.unshift(node.left)
- }
- }
- return properties ? properties.map((p) => p.value) : []
- }
- }
- /**
- * @param {Pattern} pattern
- * @returns {IPropertyReferences}
- */
- function getNestFromPattern(pattern) {
- if (pattern.type === 'ObjectPattern') {
- return extractFromObjectPattern(pattern)
- }
- if (pattern.type === 'Identifier') {
- return extractFromIdentifier(pattern)
- } else if (pattern.type === 'AssignmentPattern') {
- return getNestFromPattern(pattern.left)
- }
- return ANY
- }
- /**
- * Extract the property references from Expression.
- * @param {Identifier | MemberExpression | ChainExpression | ThisExpression | CallExpression} node
- * @param {boolean} withInTemplate
- * @returns {IPropertyReferences}
- */
- function extractFromExpression(node, withInTemplate) {
- const ref = cacheForExpression.get(node)
- if (ref) {
- return ref
- }
- cacheForExpression.set(node, ANY)
- const result = extractWithoutCache()
- cacheForExpression.set(node, result)
- return result
- function extractWithoutCache() {
- const parent = node.parent
- switch (parent.type) {
- case 'AssignmentExpression': {
- // `({foo} = arg)`
- return !withInTemplate &&
- parent.right === node &&
- parent.operator === '='
- ? extractFromPattern(parent.left)
- : NEVER
- }
- case 'VariableDeclarator': {
- // `const {foo} = arg`
- // `const foo = arg`
- return !withInTemplate && parent.init === node
- ? extractFromPattern(parent.id)
- : NEVER
- }
- case 'MemberExpression': {
- if (parent.object === node) {
- // `arg.foo`
- const name = utils.getStaticPropertyName(parent)
- if (
- name === '$props' &&
- parent.parent.type === 'MemberExpression'
- ) {
- // `$props.arg`
- const propName = utils.getStaticPropertyName(parent.parent)
- if (!propName) return unknownMemberAsUnreferenced ? NEVER : ANY
- return new PropertyReferencesForMember(
- parent.parent,
- propName,
- withInTemplate
- )
- } else if (name) {
- return new PropertyReferencesForMember(
- parent,
- name,
- withInTemplate
- )
- } else {
- return unknownMemberAsUnreferenced ? NEVER : ANY
- }
- }
- return NEVER
- }
- case 'CallExpression': {
- const argIndex = parent.arguments.indexOf(node)
- // `foo(arg)`
- return !withInTemplate && argIndex !== -1
- ? extractFromCall(parent, argIndex)
- : NEVER
- }
- case 'ChainExpression': {
- return extractFromExpression(parent, withInTemplate)
- }
- case 'ArrowFunctionExpression':
- case 'VExpressionContainer':
- case 'Property':
- case 'ArrayExpression': {
- return maybeExternalUsed(parent) ? ANY : NEVER
- }
- case 'ReturnStatement': {
- if (returnAsUnreferenced) {
- return NEVER
- } else {
- return maybeExternalUsed(parent) ? ANY : NEVER
- }
- }
- }
- return NEVER
- }
- /**
- * @param {ASTNode} parentTarget
- * @returns {boolean}
- */
- function maybeExternalUsed(parentTarget) {
- if (
- parentTarget.type === 'ReturnStatement' ||
- parentTarget.type === 'VExpressionContainer'
- ) {
- return true
- }
- if (parentTarget.type === 'ArrayExpression') {
- return maybeExternalUsed(parentTarget.parent)
- }
- if (parentTarget.type === 'Property') {
- return maybeExternalUsed(parentTarget.parent.parent)
- }
- if (parentTarget.type === 'ArrowFunctionExpression') {
- return parentTarget.body === node
- }
- return false
- }
- }
- /**
- * Extract the property references from one parameter of the function.
- * @param {Pattern} node
- * @returns {IPropertyReferences}
- */
- function extractFromPattern(node) {
- const ref = cacheForPattern.get(node)
- if (ref) {
- return ref
- }
- cacheForPattern.set(node, ANY)
- const result = extractWithoutCache()
- cacheForPattern.set(node, result)
- return result
- function extractWithoutCache() {
- while (node.type === 'AssignmentPattern') {
- node = node.left
- }
- if (node.type === 'RestElement' || node.type === 'ArrayPattern') {
- // cannot check
- return NEVER
- }
- if (node.type === 'ObjectPattern') {
- return extractFromObjectPattern(node)
- }
- if (node.type === 'Identifier') {
- return extractFromIdentifier(node)
- }
- return NEVER
- }
- }
- /**
- * Extract the property references from ObjectPattern.
- * @param {ObjectPattern} node
- * @returns {IPropertyReferences}
- */
- function extractFromObjectPattern(node) {
- const refs = new PropertyReferencesForObject()
- for (const prop of node.properties) {
- if (prop.type === 'Property') {
- const name = utils.getStaticPropertyName(prop)
- if (name) {
- const list = refs.properties[name] || (refs.properties[name] = [])
- list.push(prop)
- } else {
- // If cannot trace name, everything is used!
- return ANY
- }
- } else {
- // If use RestElement, everything is used!
- return ANY
- }
- }
- return refs
- }
- /**
- * Extract the property references from id.
- * @param {Identifier} node
- * @returns {IPropertyReferences}
- */
- function extractFromIdentifier(node) {
- const variable = utils.findVariableByIdentifier(context, node)
- if (!variable) {
- return NEVER
- }
- return mergePropertyReferences(
- variable.references.map((reference) => {
- const id = reference.identifier
- return extractFromExpression(id, false)
- })
- )
- }
- /**
- * Extract the property references from call.
- * @param {CallExpression} node
- * @param {number} argIndex
- * @returns {IPropertyReferences}
- */
- function extractFromCall(node, argIndex) {
- if (node.callee.type !== 'Identifier') {
- return {
- hasProperty(_name, options) {
- return Boolean(options && options.unknownCallAsAny)
- },
- allProperties: () => new Map(),
- getNest: () => ANY,
- getNestNodes: () => []
- }
- }
- const fnNode = findFunction(context, node.callee)
- if (!fnNode) {
- if (argIndex === 0) {
- if (getToRefSet().toRefNodes.has(node)) {
- return extractFromToRef(node)
- } else if (getToRefSet().toRefsNodes.has(node)) {
- return extractFromToRefs(node)
- }
- }
- return {
- hasProperty(_name, options) {
- return Boolean(options && options.unknownCallAsAny)
- },
- allProperties: () => new Map(),
- getNest: () => ANY,
- getNestNodes: () => []
- }
- }
- return extractFromFunctionParam(fnNode, argIndex)
- }
- /**
- * Extract the property references from function param.
- * @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node
- * @param {number} argIndex
- * @returns {IPropertyReferences}
- */
- function extractFromFunctionParam(node, argIndex) {
- let cacheForIndexes = cacheForFunction.get(node)
- if (!cacheForIndexes) {
- cacheForIndexes = new Map()
- cacheForFunction.set(node, cacheForIndexes)
- }
- const ref = cacheForIndexes.get(argIndex)
- if (ref) {
- return ref
- }
- cacheForIndexes.set(argIndex, NEVER)
- const arg = node.params[argIndex]
- if (!arg) {
- return NEVER
- }
- const result = extractFromPattern(arg)
- cacheForIndexes.set(argIndex, result)
- return result
- }
- /**
- * Extract the property references from path.
- * @param {string} pathString
- * @param {Identifier | Literal | TemplateLiteral} node
- * @returns {IPropertyReferences}
- */
- function extractFromPath(pathString, node) {
- return extractFromSegments(pathString.split('.'))
- /**
- * @param {string[]} segments
- * @returns {IPropertyReferences}
- */
- function extractFromSegments(segments) {
- if (segments.length === 0) {
- return ANY
- }
- const segmentName = segments[0]
- return {
- hasProperty: (name) => name === segmentName,
- allProperties: () => new Map([[segmentName, { nodes: [node] }]]),
- getNest: (name) =>
- name === segmentName ? extractFromSegments(segments.slice(1)) : NEVER,
- getNestNodes: () => []
- }
- }
- }
- /**
- * Extract the property references from name literal.
- * @param {Expression} node
- * @returns {IPropertyReferences}
- */
- function extractFromNameLiteral(node) {
- const referenceName =
- node.type === 'Literal' || node.type === 'TemplateLiteral'
- ? utils.getStringLiteralValue(node)
- : null
- return referenceName
- ? {
- hasProperty: (name) => name === referenceName,
- allProperties: () => new Map([[referenceName, { nodes: [node] }]]),
- getNest: (name) => (name === referenceName ? ANY : NEVER),
- getNestNodes: () => []
- }
- : NEVER
- }
- /**
- * Extract the property references from name.
- * @param {string} referenceName
- * @param {Expression|SpreadElement} nameNode
- * @param { () => IPropertyReferences } [getNest]
- * @returns {IPropertyReferences}
- */
- function extractFromName(referenceName, nameNode, getNest) {
- return {
- hasProperty: (name) => name === referenceName,
- allProperties: () => new Map([[referenceName, { nodes: [nameNode] }]]),
- getNest: (name) =>
- name === referenceName ? (getNest ? getNest() : ANY) : NEVER,
- getNestNodes: () => []
- }
- }
- /**
- * Extract the property references from toRef call.
- * @param {CallExpression} node
- * @returns {IPropertyReferences}
- */
- function extractFromToRef(node) {
- const nameNode = node.arguments[1]
- const refName =
- nameNode &&
- (nameNode.type === 'Literal' || nameNode.type === 'TemplateLiteral')
- ? utils.getStringLiteralValue(nameNode)
- : null
- if (!refName) {
- // unknown name
- return ANY
- }
- return extractFromName(refName, nameNode, () =>
- extractFromExpression(node, false).getNest('value')
- )
- }
- /**
- * Extract the property references from toRefs call.
- * @param {CallExpression} node
- * @returns {IPropertyReferences}
- */
- function extractFromToRefs(node) {
- const base = extractFromExpression(node, false)
- return {
- hasProperty: (name, option) => base.hasProperty(name, option),
- allProperties: () => base.allProperties(),
- getNest: (name) => base.getNest(name).getNest('value'),
- getNestNodes: (name) => base.getNest(name).getNestNodes('value')
- }
- }
- /**
- * Extract the property references from VExpressionContainer.
- * @param {VExpressionContainer} node
- * @param {object} [options]
- * @param {boolean} [options.ignoreGlobals]
- * @returns {IPropertyReferences}
- */
- function extractFromVExpressionContainer(node, options) {
- const ignoreGlobals = options && options.ignoreGlobals
- /** @type { (name:string)=>boolean } */
- let ignoreRef = () => false
- if (ignoreGlobals) {
- const globalScope =
- context.getSourceCode().scopeManager.globalScope ||
- context.getSourceCode().scopeManager.scopes[0]
- ignoreRef = (name) => globalScope.set.has(name)
- }
- /** @type {IPropertyReferences[]} */
- const references = []
- for (const id of node.references
- .filter((ref) => ref.variable == null)
- .map((ref) => ref.id)) {
- if (ignoreRef(id.name)) {
- continue
- }
- if (isFunctionalTemplate) {
- if (id.name === 'props') {
- references.push(extractFromExpression(id, true))
- }
- } else {
- const referenceId =
- id.name === '$props' &&
- id.parent.type === 'MemberExpression' &&
- id.parent.property.type === 'Identifier'
- ? id.parent.property
- : id
- references.push(
- extractFromName(referenceId.name, referenceId, () =>
- extractFromExpression(referenceId, true)
- )
- )
- }
- }
- return mergePropertyReferences(references)
- }
- /**
- * Extract the property references from StyleVariablesContext.
- * @param {StyleVariablesContext} ctx
- * @returns {IPropertyReferences}
- */
- function extractFromStyleVariablesContext(ctx) {
- const references = []
- for (const { id } of ctx.references) {
- references.push(
- extractFromName(id.name, id, () => extractFromExpression(id, true))
- )
- }
- return mergePropertyReferences(references)
- }
- return {
- extractFromExpression,
- extractFromPattern,
- extractFromFunctionParam,
- extractFromPath,
- extractFromName,
- extractFromNameLiteral,
- extractFromVExpressionContainer,
- extractFromStyleVariablesContext
- }
- }
- /**
- * @param {IPropertyReferences[]} references
- * @returns {IPropertyReferences}
- */
- function mergePropertyReferences(references) {
- if (references.length === 0) {
- return NEVER
- }
- if (references.length === 1) {
- return references[0]
- }
- return new PropertyReferencesForMerge(references)
- }
- /**
- * Collects the property references for merge.
- * @implements {IPropertyReferences}
- */
- class PropertyReferencesForMerge {
- /**
- * @param {IPropertyReferences[]} references
- */
- constructor(references) {
- this.references = references
- }
- /**
- * @param {string} name
- * @param {IHasPropertyOption} [option]
- */
- hasProperty(name, option) {
- return this.references.some((ref) => ref.hasProperty(name, option))
- }
- allProperties() {
- const result = new Map()
- for (const reference of this.references) {
- for (const [name, { nodes }] of reference.allProperties()) {
- const r = result.get(name)
- if (r) {
- r.nodes = [...new Set([...r.nodes, ...nodes])]
- } else {
- result.set(name, { nodes: [...nodes] })
- }
- }
- }
- return result
- }
- /**
- * @param {string} name
- * @returns {IPropertyReferences}
- */
- getNest(name) {
- /** @type {IPropertyReferences[]} */
- const nest = []
- for (const ref of this.references) {
- if (ref.hasProperty(name)) {
- nest.push(ref.getNest(name))
- }
- }
- return mergePropertyReferences(nest)
- }
- /**
- * @param {string} name
- * @returns {Iterable<NestPropertyNode>}
- */
- *getNestNodes(name) {
- for (const ref of this.references) {
- if (ref.hasProperty(name)) {
- yield* ref.getNestNodes(name)
- }
- }
- }
- }
|