no-unused-properties.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. /**
  2. * @fileoverview Disallow unused properties, data and computed properties.
  3. * @author Learning Equality
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const eslintUtils = require('eslint-utils')
  11. /**
  12. * @typedef {import('../utils').ComponentPropertyData} ComponentPropertyData
  13. * @typedef {import('../utils').VueObjectData} VueObjectData
  14. */
  15. /**
  16. * @typedef {object} TemplatePropertiesContainer
  17. * @property {Set<string>} usedNames
  18. * @property {Set<string>} refNames
  19. * @typedef {object} VueComponentPropertiesContainer
  20. * @property {ComponentPropertyData[]} properties
  21. * @property {Set<string>} usedNames
  22. * @property {boolean} unknown
  23. * @property {Set<string>} usedPropsNames
  24. * @property {boolean} unknownProps
  25. * @typedef { { node: FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, index: number } } CallIdAndParamIndex
  26. * @typedef { { usedNames: UsedNames, unknown: boolean } } UsedProperties
  27. * @typedef { (context: RuleContext) => UsedProps } UsedPropsTracker
  28. */
  29. // ------------------------------------------------------------------------------
  30. // Constants
  31. // ------------------------------------------------------------------------------
  32. const GROUP_PROPERTY = 'props'
  33. const GROUP_DATA = 'data'
  34. const GROUP_COMPUTED_PROPERTY = 'computed'
  35. const GROUP_METHODS = 'methods'
  36. const GROUP_SETUP = 'setup'
  37. const GROUP_WATCHER = 'watch'
  38. const PROPERTY_LABEL = {
  39. props: 'property',
  40. data: 'data',
  41. computed: 'computed property',
  42. methods: 'method',
  43. setup: 'property returned from `setup()`',
  44. watch: 'watch'
  45. }
  46. // ------------------------------------------------------------------------------
  47. // Helpers
  48. // ------------------------------------------------------------------------------
  49. /**
  50. * Find the variable of a given name.
  51. * @param {RuleContext} context The rule context
  52. * @param {Identifier} node The variable name to find.
  53. * @returns {Variable|null} The found variable or null.
  54. */
  55. function findVariable(context, node) {
  56. return eslintUtils.findVariable(getScope(context, node), node)
  57. }
  58. /**
  59. * Gets the scope for the current node
  60. * @param {RuleContext} context The rule context
  61. * @param {ESNode} currentNode The node to get the scope of
  62. * @returns { import('eslint').Scope.Scope } The scope information for this node
  63. */
  64. function getScope(context, currentNode) {
  65. // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
  66. const inner = currentNode.type !== 'Program'
  67. const scopeManager = context.getSourceCode().scopeManager
  68. /** @type {ESNode | null} */
  69. let node = currentNode
  70. for (; node; node = /** @type {ESNode | null} */ (node.parent)) {
  71. const scope = scopeManager.acquire(node, inner)
  72. if (scope) {
  73. if (scope.type === 'function-expression-name') {
  74. return scope.childScopes[0]
  75. }
  76. return scope
  77. }
  78. }
  79. return scopeManager.scopes[0]
  80. }
  81. /**
  82. * Extract names from references objects.
  83. * @param {VReference[]} references
  84. */
  85. function getReferencesNames(references) {
  86. return references
  87. .filter((ref) => ref.variable == null)
  88. .map((ref) => ref.id.name)
  89. }
  90. class UsedNames {
  91. constructor() {
  92. /** @type {Map<string, UsedPropsTracker[]>} */
  93. this.map = new Map()
  94. }
  95. /**
  96. * @returns {IterableIterator<string>}
  97. */
  98. names() {
  99. return this.map.keys()
  100. }
  101. /**
  102. * @param {string} name
  103. * @returns {UsedPropsTracker[]}
  104. */
  105. get(name) {
  106. return this.map.get(name) || []
  107. }
  108. /**
  109. * @param {string} name
  110. * @param {UsedPropsTracker} tracker
  111. */
  112. add(name, tracker) {
  113. const list = this.map.get(name)
  114. if (list) {
  115. list.push(tracker)
  116. } else {
  117. this.map.set(name, [tracker])
  118. }
  119. }
  120. /**
  121. * @param {UsedNames} other
  122. */
  123. addAll(other) {
  124. other.map.forEach((trackers, name) => {
  125. const list = this.map.get(name)
  126. if (list) {
  127. list.push(...trackers)
  128. } else {
  129. this.map.set(name, trackers)
  130. }
  131. })
  132. }
  133. }
  134. /**
  135. * @param {ObjectPattern} node
  136. * @returns {UsedProperties}
  137. */
  138. function extractObjectPatternProperties(node) {
  139. const usedNames = new UsedNames()
  140. for (const prop of node.properties) {
  141. if (prop.type === 'Property') {
  142. const name = utils.getStaticPropertyName(prop)
  143. if (name) {
  144. usedNames.add(name, getObjectPatternPropertyPatternTracker(prop.value))
  145. } else {
  146. // If cannot trace name, everything is used!
  147. return {
  148. usedNames,
  149. unknown: true
  150. }
  151. }
  152. } else {
  153. // If use RestElement, everything is used!
  154. return {
  155. usedNames,
  156. unknown: true
  157. }
  158. }
  159. }
  160. return {
  161. usedNames,
  162. unknown: false
  163. }
  164. }
  165. /**
  166. * @param {Pattern} pattern
  167. * @returns {UsedPropsTracker}
  168. */
  169. function getObjectPatternPropertyPatternTracker(pattern) {
  170. if (pattern.type === 'ObjectPattern') {
  171. return () => {
  172. const result = new UsedProps()
  173. const { usedNames, unknown } = extractObjectPatternProperties(pattern)
  174. result.usedNames.addAll(usedNames)
  175. result.unknown = unknown
  176. return result
  177. }
  178. }
  179. if (pattern.type === 'Identifier') {
  180. return (context) => {
  181. const result = new UsedProps()
  182. const variable = findVariable(context, pattern)
  183. if (!variable) {
  184. return result
  185. }
  186. for (const reference of variable.references) {
  187. const id = reference.identifier
  188. const { usedNames, unknown, calls } = extractPatternOrThisProperties(
  189. id,
  190. context
  191. )
  192. result.usedNames.addAll(usedNames)
  193. result.unknown = result.unknown || unknown
  194. result.calls.push(...calls)
  195. }
  196. return result
  197. }
  198. } else if (pattern.type === 'AssignmentPattern') {
  199. return getObjectPatternPropertyPatternTracker(pattern.left)
  200. }
  201. return () => {
  202. const result = new UsedProps()
  203. result.unknown = true
  204. return result
  205. }
  206. }
  207. /**
  208. * @param {Identifier | MemberExpression | ChainExpression | ThisExpression} node
  209. * @param {RuleContext} context
  210. * @returns {UsedProps}
  211. */
  212. function extractPatternOrThisProperties(node, context) {
  213. const result = new UsedProps()
  214. const parent = node.parent
  215. if (parent.type === 'AssignmentExpression') {
  216. if (parent.right === node && parent.left.type === 'ObjectPattern') {
  217. // `({foo} = arg)`
  218. const { usedNames, unknown } = extractObjectPatternProperties(parent.left)
  219. result.usedNames.addAll(usedNames)
  220. result.unknown = result.unknown || unknown
  221. }
  222. return result
  223. } else if (parent.type === 'VariableDeclarator') {
  224. if (parent.init === node) {
  225. if (parent.id.type === 'ObjectPattern') {
  226. // `const {foo} = arg`
  227. const { usedNames, unknown } = extractObjectPatternProperties(parent.id)
  228. result.usedNames.addAll(usedNames)
  229. result.unknown = result.unknown || unknown
  230. } else if (parent.id.type === 'Identifier') {
  231. // `const foo = arg`
  232. const variable = findVariable(context, parent.id)
  233. if (!variable) {
  234. return result
  235. }
  236. for (const reference of variable.references) {
  237. const id = reference.identifier
  238. const { usedNames, unknown, calls } = extractPatternOrThisProperties(
  239. id,
  240. context
  241. )
  242. result.usedNames.addAll(usedNames)
  243. result.unknown = result.unknown || unknown
  244. result.calls.push(...calls)
  245. }
  246. }
  247. }
  248. return result
  249. } else if (parent.type === 'MemberExpression') {
  250. if (parent.object === node) {
  251. // `arg.foo`
  252. const name = utils.getStaticPropertyName(parent)
  253. if (name) {
  254. result.usedNames.add(name, () =>
  255. extractPatternOrThisProperties(parent, context)
  256. )
  257. } else {
  258. result.unknown = true
  259. }
  260. }
  261. return result
  262. } else if (parent.type === 'CallExpression') {
  263. const argIndex = parent.arguments.indexOf(node)
  264. if (argIndex > -1 && parent.callee.type === 'Identifier') {
  265. // `foo(arg)`
  266. const calleeVariable = findVariable(context, parent.callee)
  267. if (!calleeVariable) {
  268. return result
  269. }
  270. if (calleeVariable.defs.length === 1) {
  271. const def = calleeVariable.defs[0]
  272. if (
  273. def.type === 'Variable' &&
  274. def.parent.kind === 'const' &&
  275. def.node.init &&
  276. (def.node.init.type === 'FunctionExpression' ||
  277. def.node.init.type === 'ArrowFunctionExpression')
  278. ) {
  279. result.calls.push({
  280. node: def.node.init,
  281. index: argIndex
  282. })
  283. } else if (def.node.type === 'FunctionDeclaration') {
  284. result.calls.push({
  285. node: def.node,
  286. index: argIndex
  287. })
  288. }
  289. }
  290. }
  291. } else if (parent.type === 'ChainExpression') {
  292. const { usedNames, unknown, calls } = extractPatternOrThisProperties(
  293. parent,
  294. context
  295. )
  296. result.usedNames.addAll(usedNames)
  297. result.unknown = result.unknown || unknown
  298. result.calls.push(...calls)
  299. }
  300. return result
  301. }
  302. /**
  303. * Collects the property names used.
  304. */
  305. class UsedProps {
  306. constructor() {
  307. this.usedNames = new UsedNames()
  308. /** @type {CallIdAndParamIndex[]} */
  309. this.calls = []
  310. this.unknown = false
  311. }
  312. }
  313. /**
  314. * Collects the property names used for one parameter of the function.
  315. */
  316. class ParamUsedProps extends UsedProps {
  317. /**
  318. * @param {Pattern} paramNode
  319. * @param {RuleContext} context
  320. */
  321. constructor(paramNode, context) {
  322. super()
  323. while (paramNode.type === 'AssignmentPattern') {
  324. paramNode = paramNode.left
  325. }
  326. if (paramNode.type === 'RestElement' || paramNode.type === 'ArrayPattern') {
  327. // cannot check
  328. return
  329. }
  330. if (paramNode.type === 'ObjectPattern') {
  331. const { usedNames, unknown } = extractObjectPatternProperties(paramNode)
  332. this.usedNames.addAll(usedNames)
  333. this.unknown = this.unknown || unknown
  334. return
  335. }
  336. if (paramNode.type !== 'Identifier') {
  337. return
  338. }
  339. const variable = findVariable(context, paramNode)
  340. if (!variable) {
  341. return
  342. }
  343. for (const reference of variable.references) {
  344. const id = reference.identifier
  345. const { usedNames, unknown, calls } = extractPatternOrThisProperties(
  346. id,
  347. context
  348. )
  349. this.usedNames.addAll(usedNames)
  350. this.unknown = this.unknown || unknown
  351. this.calls.push(...calls)
  352. }
  353. }
  354. }
  355. /**
  356. * Collects the property names used for parameters of the function.
  357. */
  358. class ParamsUsedProps {
  359. /**
  360. * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
  361. * @param {RuleContext} context
  362. */
  363. constructor(node, context) {
  364. this.node = node
  365. this.context = context
  366. /** @type {ParamUsedProps[]} */
  367. this.params = []
  368. }
  369. /**
  370. * @param {number} index
  371. * @returns {ParamUsedProps | null}
  372. */
  373. getParam(index) {
  374. const param = this.params[index]
  375. if (param != null) {
  376. return param
  377. }
  378. if (this.node.params[index]) {
  379. return (this.params[index] = new ParamUsedProps(
  380. this.node.params[index],
  381. this.context
  382. ))
  383. }
  384. return null
  385. }
  386. }
  387. // ------------------------------------------------------------------------------
  388. // Rule Definition
  389. // ------------------------------------------------------------------------------
  390. module.exports = {
  391. meta: {
  392. type: 'suggestion',
  393. docs: {
  394. description: 'disallow unused properties',
  395. categories: undefined,
  396. url: 'https://eslint.vuejs.org/rules/no-unused-properties.html'
  397. },
  398. fixable: null,
  399. schema: [
  400. {
  401. type: 'object',
  402. properties: {
  403. groups: {
  404. type: 'array',
  405. items: {
  406. enum: [
  407. GROUP_PROPERTY,
  408. GROUP_DATA,
  409. GROUP_COMPUTED_PROPERTY,
  410. GROUP_METHODS,
  411. GROUP_SETUP
  412. ]
  413. },
  414. additionalItems: false,
  415. uniqueItems: true
  416. }
  417. },
  418. additionalProperties: false
  419. }
  420. ],
  421. messages: {
  422. unused: "'{{name}}' of {{group}} found, but never used."
  423. }
  424. },
  425. /** @param {RuleContext} context */
  426. create(context) {
  427. const options = context.options[0] || {}
  428. const groups = new Set(options.groups || [GROUP_PROPERTY])
  429. /** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, ParamsUsedProps>} */
  430. const paramsUsedPropsMap = new Map()
  431. /** @type {TemplatePropertiesContainer} */
  432. const templatePropertiesContainer = {
  433. usedNames: new Set(),
  434. refNames: new Set()
  435. }
  436. /** @type {Map<ASTNode, VueComponentPropertiesContainer>} */
  437. const vueComponentPropertiesContainerMap = new Map()
  438. /**
  439. * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} node
  440. * @returns {ParamsUsedProps}
  441. */
  442. function getParamsUsedProps(node) {
  443. let usedProps = paramsUsedPropsMap.get(node)
  444. if (!usedProps) {
  445. usedProps = new ParamsUsedProps(node, context)
  446. paramsUsedPropsMap.set(node, usedProps)
  447. }
  448. return usedProps
  449. }
  450. /**
  451. * @param {ASTNode} node
  452. * @returns {VueComponentPropertiesContainer}
  453. */
  454. function getVueComponentPropertiesContainer(node) {
  455. let container = vueComponentPropertiesContainerMap.get(node)
  456. if (!container) {
  457. container = {
  458. properties: [],
  459. usedNames: new Set(),
  460. usedPropsNames: new Set(),
  461. unknown: false,
  462. unknownProps: false
  463. }
  464. vueComponentPropertiesContainerMap.set(node, container)
  465. }
  466. return container
  467. }
  468. /**
  469. * Report all unused properties.
  470. */
  471. function reportUnusedProperties() {
  472. for (const container of vueComponentPropertiesContainerMap.values()) {
  473. if (container.unknown) {
  474. // unknown
  475. continue
  476. }
  477. for (const property of container.properties) {
  478. if (
  479. container.usedNames.has(property.name) ||
  480. templatePropertiesContainer.usedNames.has(property.name)
  481. ) {
  482. // used
  483. continue
  484. }
  485. if (
  486. property.groupName === 'props' &&
  487. (container.unknownProps ||
  488. container.usedPropsNames.has(property.name))
  489. ) {
  490. // used props
  491. continue
  492. }
  493. if (
  494. property.groupName === 'setup' &&
  495. templatePropertiesContainer.refNames.has(property.name)
  496. ) {
  497. // used template refs
  498. continue
  499. }
  500. context.report({
  501. node: property.node,
  502. messageId: 'unused',
  503. data: {
  504. group: PROPERTY_LABEL[property.groupName],
  505. name: property.name
  506. }
  507. })
  508. }
  509. }
  510. }
  511. /**
  512. * @param {UsedProps} usedProps
  513. * @param {Map<ASTNode,Set<number>>} already
  514. * @returns {IterableIterator<UsedProps>}
  515. */
  516. function* iterateUsedProps(usedProps, already = new Map()) {
  517. yield usedProps
  518. for (const call of usedProps.calls) {
  519. let alreadyIndexes = already.get(call.node)
  520. if (!alreadyIndexes) {
  521. alreadyIndexes = new Set()
  522. already.set(call.node, alreadyIndexes)
  523. }
  524. if (alreadyIndexes.has(call.index)) {
  525. continue
  526. }
  527. alreadyIndexes.add(call.index)
  528. const paramsUsedProps = getParamsUsedProps(call.node)
  529. const paramUsedProps = paramsUsedProps.getParam(call.index)
  530. if (!paramUsedProps) {
  531. continue
  532. }
  533. yield paramUsedProps
  534. yield* iterateUsedProps(paramUsedProps, already)
  535. }
  536. }
  537. /**
  538. * @param {VueComponentPropertiesContainer} container
  539. * @param {UsedProps} baseUseProps
  540. */
  541. function processParamPropsUsed(container, baseUseProps) {
  542. for (const { usedNames, unknown } of iterateUsedProps(baseUseProps)) {
  543. if (unknown) {
  544. container.unknownProps = true
  545. return
  546. }
  547. for (const name of usedNames.names()) {
  548. container.usedPropsNames.add(name)
  549. }
  550. }
  551. }
  552. /**
  553. * @param {VueComponentPropertiesContainer} container
  554. * @param {UsedProps} baseUseProps
  555. */
  556. function processUsed(container, baseUseProps) {
  557. for (const { usedNames, unknown } of iterateUsedProps(baseUseProps)) {
  558. if (unknown) {
  559. container.unknown = true
  560. return
  561. }
  562. for (const name of usedNames.names()) {
  563. container.usedNames.add(name)
  564. }
  565. }
  566. }
  567. /**
  568. * @param {Expression} node
  569. * @returns {Property|null}
  570. */
  571. function getParentProperty(node) {
  572. if (
  573. !node.parent ||
  574. node.parent.type !== 'Property' ||
  575. node.parent.value !== node
  576. ) {
  577. return null
  578. }
  579. const property = node.parent
  580. if (!property.parent || property.parent.type !== 'ObjectExpression') {
  581. return null
  582. }
  583. return /** @type {Property} */ (property)
  584. }
  585. const scriptVisitor = Object.assign(
  586. {},
  587. utils.defineVueVisitor(context, {
  588. onVueObjectEnter(node) {
  589. const container = getVueComponentPropertiesContainer(node)
  590. const watcherUsedProperties = new Set()
  591. for (const watcher of utils.iterateProperties(
  592. node,
  593. new Set([GROUP_WATCHER])
  594. )) {
  595. // Process `watch: { foo /* <- this */ () {} }`
  596. let path
  597. for (const seg of watcher.name.split('.')) {
  598. path = path ? `${path}.${seg}` : seg
  599. watcherUsedProperties.add(path)
  600. }
  601. // Process `watch: { x: 'foo' /* <- this */ }`
  602. if (watcher.type === 'object') {
  603. const property = watcher.property
  604. if (property.kind === 'init') {
  605. for (const handlerValueNode of utils.iterateWatchHandlerValues(
  606. property
  607. )) {
  608. if (
  609. handlerValueNode.type === 'Literal' ||
  610. handlerValueNode.type === 'TemplateLiteral'
  611. ) {
  612. const name = utils.getStringLiteralValue(handlerValueNode)
  613. if (name != null) {
  614. watcherUsedProperties.add(name)
  615. }
  616. }
  617. }
  618. }
  619. }
  620. }
  621. for (const prop of utils.iterateProperties(node, groups)) {
  622. if (watcherUsedProperties.has(prop.name)) {
  623. continue
  624. }
  625. container.properties.push(prop)
  626. }
  627. },
  628. /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
  629. 'ObjectExpression > Property > :function[params.length>0]'(
  630. node,
  631. vueData
  632. ) {
  633. const property = getParentProperty(node)
  634. if (!property) {
  635. return
  636. }
  637. if (property.parent === vueData.node) {
  638. if (utils.getStaticPropertyName(property) !== 'data') {
  639. return
  640. }
  641. // check { data: (vm) => vm.prop }
  642. } else {
  643. const parentProperty = getParentProperty(property.parent)
  644. if (!parentProperty) {
  645. return
  646. }
  647. if (parentProperty.parent === vueData.node) {
  648. if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
  649. return
  650. }
  651. // check { computed: { foo: (vm) => vm.prop } }
  652. } else {
  653. const parentParentProperty = getParentProperty(
  654. parentProperty.parent
  655. )
  656. if (!parentParentProperty) {
  657. return
  658. }
  659. if (parentParentProperty.parent === vueData.node) {
  660. if (
  661. utils.getStaticPropertyName(parentParentProperty) !==
  662. 'computed' ||
  663. utils.getStaticPropertyName(property) !== 'get'
  664. ) {
  665. return
  666. }
  667. // check { computed: { foo: { get: () => vm.prop } } }
  668. } else {
  669. return
  670. }
  671. }
  672. }
  673. const paramsUsedProps = getParamsUsedProps(node)
  674. const usedProps = /** @type {ParamUsedProps} */ (paramsUsedProps.getParam(
  675. 0
  676. ))
  677. processUsed(
  678. getVueComponentPropertiesContainer(vueData.node),
  679. usedProps
  680. )
  681. },
  682. onSetupFunctionEnter(node, vueData) {
  683. const container = getVueComponentPropertiesContainer(vueData.node)
  684. if (node.params[0]) {
  685. const paramsUsedProps = getParamsUsedProps(node)
  686. const paramUsedProps = /** @type {ParamUsedProps} */ (paramsUsedProps.getParam(
  687. 0
  688. ))
  689. processParamPropsUsed(container, paramUsedProps)
  690. }
  691. },
  692. onRenderFunctionEnter(node, vueData) {
  693. const container = getVueComponentPropertiesContainer(vueData.node)
  694. if (node.params[0]) {
  695. // for Vue 3.x render
  696. const paramsUsedProps = getParamsUsedProps(node)
  697. const paramUsedProps = /** @type {ParamUsedProps} */ (paramsUsedProps.getParam(
  698. 0
  699. ))
  700. processParamPropsUsed(container, paramUsedProps)
  701. if (container.unknownProps) {
  702. return
  703. }
  704. }
  705. if (vueData.functional && node.params[1]) {
  706. // for Vue 2.x render & functional
  707. const paramsUsedProps = getParamsUsedProps(node)
  708. const paramUsedProps = /** @type {ParamUsedProps} */ (paramsUsedProps.getParam(
  709. 1
  710. ))
  711. for (const { usedNames, unknown } of iterateUsedProps(
  712. paramUsedProps
  713. )) {
  714. if (unknown) {
  715. container.unknownProps = true
  716. return
  717. }
  718. for (const usedPropsTracker of usedNames.get('props')) {
  719. const propUsedProps = usedPropsTracker(context)
  720. processParamPropsUsed(container, propUsedProps)
  721. if (container.unknownProps) {
  722. return
  723. }
  724. }
  725. }
  726. }
  727. },
  728. /**
  729. * @param {ThisExpression | Identifier} node
  730. * @param {VueObjectData} vueData
  731. */
  732. 'ThisExpression, Identifier'(node, vueData) {
  733. if (!utils.isThis(node, context)) {
  734. return
  735. }
  736. const container = getVueComponentPropertiesContainer(vueData.node)
  737. const usedProps = extractPatternOrThisProperties(node, context)
  738. processUsed(container, usedProps)
  739. }
  740. }),
  741. {
  742. /** @param {Program} node */
  743. 'Program:exit'(node) {
  744. if (!node.templateBody) {
  745. reportUnusedProperties()
  746. }
  747. }
  748. }
  749. )
  750. const templateVisitor = {
  751. /**
  752. * @param {VExpressionContainer} node
  753. */
  754. VExpressionContainer(node) {
  755. for (const name of getReferencesNames(node.references)) {
  756. templatePropertiesContainer.usedNames.add(name)
  757. }
  758. },
  759. /**
  760. * @param {VAttribute} node
  761. */
  762. 'VAttribute[directive=false]'(node) {
  763. if (node.key.name === 'ref' && node.value != null) {
  764. templatePropertiesContainer.refNames.add(node.value.value)
  765. }
  766. },
  767. "VElement[parent.type!='VElement']:exit"() {
  768. reportUnusedProperties()
  769. }
  770. }
  771. return utils.defineTemplateBodyVisitor(
  772. context,
  773. templateVisitor,
  774. scriptVisitor
  775. )
  776. }
  777. }