no-unused-refs.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /**
  2. * @fileoverview Disallow unused refs.
  3. * @author Yosuke Ota
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * Extract names from references objects.
  9. * @param {VReference[]} references
  10. */
  11. function getReferences(references) {
  12. return references.filter((ref) => ref.variable == null).map((ref) => ref.id)
  13. }
  14. module.exports = {
  15. meta: {
  16. type: 'suggestion',
  17. docs: {
  18. description: 'disallow unused refs',
  19. categories: undefined,
  20. url: 'https://eslint.vuejs.org/rules/no-unused-refs.html'
  21. },
  22. fixable: null,
  23. schema: [],
  24. messages: {
  25. unused: "'{{name}}' is defined as ref, but never used."
  26. }
  27. },
  28. /** @param {RuleContext} context */
  29. create(context) {
  30. /** @type {Set<string>} */
  31. const usedRefs = new Set()
  32. /** @type {VLiteral[]} */
  33. const defineRefs = []
  34. let hasUnknown = false
  35. /**
  36. * Report all unused refs.
  37. */
  38. function reportUnusedRefs() {
  39. for (const defineRef of defineRefs) {
  40. if (usedRefs.has(defineRef.value)) {
  41. continue
  42. }
  43. context.report({
  44. node: defineRef,
  45. messageId: 'unused',
  46. data: {
  47. name: defineRef.value
  48. }
  49. })
  50. }
  51. }
  52. /**
  53. * Extract the use ref names for ObjectPattern.
  54. * @param {ObjectPattern} node
  55. * @returns {void}
  56. */
  57. function extractUsedForObjectPattern(node) {
  58. for (const prop of node.properties) {
  59. if (prop.type === 'Property') {
  60. const name = utils.getStaticPropertyName(prop)
  61. if (name) {
  62. usedRefs.add(name)
  63. } else {
  64. hasUnknown = true
  65. return
  66. }
  67. } else {
  68. hasUnknown = true
  69. return
  70. }
  71. }
  72. }
  73. /**
  74. * Extract the use ref names.
  75. * @param {Identifier | MemberExpression} refsNode
  76. * @returns {void}
  77. */
  78. function extractUsedForPattern(refsNode) {
  79. /** @type {Identifier | MemberExpression | ChainExpression} */
  80. let node = refsNode
  81. while (node.parent.type === 'ChainExpression') {
  82. node = node.parent
  83. }
  84. const parent = node.parent
  85. switch (parent.type) {
  86. case 'AssignmentExpression': {
  87. if (parent.right === node) {
  88. if (parent.left.type === 'ObjectPattern') {
  89. // `({foo} = $refs)`
  90. extractUsedForObjectPattern(parent.left)
  91. } else if (parent.left.type === 'Identifier') {
  92. // `foo = $refs`
  93. hasUnknown = true
  94. }
  95. }
  96. break
  97. }
  98. case 'VariableDeclarator': {
  99. if (parent.init === node) {
  100. if (parent.id.type === 'ObjectPattern') {
  101. // `const {foo} = $refs`
  102. extractUsedForObjectPattern(parent.id)
  103. } else if (parent.id.type === 'Identifier') {
  104. // `const foo = $refs`
  105. hasUnknown = true
  106. }
  107. }
  108. break
  109. }
  110. case 'MemberExpression': {
  111. if (parent.object === node) {
  112. // `$refs.foo`
  113. const name = utils.getStaticPropertyName(parent)
  114. if (name) {
  115. usedRefs.add(name)
  116. } else {
  117. hasUnknown = true
  118. }
  119. }
  120. break
  121. }
  122. case 'CallExpression': {
  123. const argIndex = parent.arguments.indexOf(node)
  124. if (argIndex !== -1) {
  125. // `foo($refs)`
  126. hasUnknown = true
  127. }
  128. break
  129. }
  130. case 'ForInStatement':
  131. case 'ReturnStatement': {
  132. hasUnknown = true
  133. break
  134. }
  135. }
  136. }
  137. return utils.defineTemplateBodyVisitor(
  138. context,
  139. {
  140. /**
  141. * @param {VExpressionContainer} node
  142. */
  143. VExpressionContainer(node) {
  144. if (hasUnknown) {
  145. return
  146. }
  147. for (const id of getReferences(node.references)) {
  148. if (id.name !== '$refs') {
  149. continue
  150. }
  151. extractUsedForPattern(id)
  152. }
  153. },
  154. /**
  155. * @param {VAttribute} node
  156. */
  157. 'VAttribute[directive=false]'(node) {
  158. if (hasUnknown) {
  159. return
  160. }
  161. if (node.key.name === 'ref' && node.value != null) {
  162. defineRefs.push(node.value)
  163. }
  164. },
  165. "VElement[parent.type!='VElement']:exit"() {
  166. if (hasUnknown) {
  167. return
  168. }
  169. reportUnusedRefs()
  170. }
  171. },
  172. utils.compositingVisitors(
  173. utils.isScriptSetup(context)
  174. ? {
  175. Program() {
  176. const globalScope =
  177. context.getSourceCode().scopeManager.globalScope
  178. if (!globalScope) {
  179. return
  180. }
  181. for (const variable of globalScope.variables) {
  182. if (variable.defs.length > 0) {
  183. usedRefs.add(variable.name)
  184. }
  185. }
  186. const moduleScope = globalScope.childScopes.find(
  187. (scope) => scope.type === 'module'
  188. )
  189. if (!moduleScope) {
  190. return
  191. }
  192. for (const variable of moduleScope.variables) {
  193. if (variable.defs.length > 0) {
  194. usedRefs.add(variable.name)
  195. }
  196. }
  197. }
  198. }
  199. : {},
  200. utils.defineVueVisitor(context, {
  201. onVueObjectEnter(node) {
  202. for (const prop of utils.iterateProperties(
  203. node,
  204. new Set(['setup'])
  205. )) {
  206. usedRefs.add(prop.name)
  207. }
  208. }
  209. }),
  210. {
  211. Identifier(id) {
  212. if (hasUnknown) {
  213. return
  214. }
  215. if (id.name !== '$refs') {
  216. return
  217. }
  218. /** @type {Identifier | MemberExpression} */
  219. let refsNode = id
  220. if (
  221. id.parent.type === 'MemberExpression' &&
  222. id.parent.property === id
  223. ) {
  224. // `this.$refs.foo`
  225. refsNode = id.parent
  226. }
  227. extractUsedForPattern(refsNode)
  228. },
  229. CallExpression(callExpression) {
  230. const firstArgument = callExpression.arguments[0]
  231. if (
  232. callExpression.callee.name !== 'useTemplateRef' ||
  233. !firstArgument
  234. ) {
  235. return
  236. }
  237. const name = utils.getStringLiteralValue(firstArgument)
  238. if (name !== null) {
  239. usedRefs.add(name)
  240. }
  241. }
  242. }
  243. )
  244. )
  245. }
  246. }