no-unused-refs.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /**
  2. * @fileoverview Disallow unused refs.
  3. * @author Yosuke Ota
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. // ------------------------------------------------------------------------------
  11. // Helpers
  12. // ------------------------------------------------------------------------------
  13. /**
  14. * Extract names from references objects.
  15. * @param {VReference[]} references
  16. */
  17. function getReferences(references) {
  18. return references.filter((ref) => ref.variable == null).map((ref) => ref.id)
  19. }
  20. // ------------------------------------------------------------------------------
  21. // Rule Definition
  22. // ------------------------------------------------------------------------------
  23. module.exports = {
  24. meta: {
  25. type: 'suggestion',
  26. docs: {
  27. description: 'disallow unused refs',
  28. categories: undefined,
  29. url: 'https://eslint.vuejs.org/rules/no-unused-refs.html'
  30. },
  31. fixable: null,
  32. schema: [],
  33. messages: {
  34. unused: "'{{name}}' is defined as ref, but never used."
  35. }
  36. },
  37. /** @param {RuleContext} context */
  38. create(context) {
  39. /** @type {Set<string>} */
  40. const usedRefs = new Set()
  41. /** @type {VLiteral[]} */
  42. const defineRefs = []
  43. let hasUnknown = false
  44. /**
  45. * Report all unused refs.
  46. */
  47. function reportUnusedRefs() {
  48. for (const defineRef of defineRefs) {
  49. if (usedRefs.has(defineRef.value)) {
  50. continue
  51. }
  52. context.report({
  53. node: defineRef,
  54. messageId: 'unused',
  55. data: {
  56. name: defineRef.value
  57. }
  58. })
  59. }
  60. }
  61. /**
  62. * Extract the use ref names for ObjectPattern.
  63. * @param {ObjectPattern} node
  64. * @returns {void}
  65. */
  66. function extractUsedForObjectPattern(node) {
  67. for (const prop of node.properties) {
  68. if (prop.type === 'Property') {
  69. const name = utils.getStaticPropertyName(prop)
  70. if (name) {
  71. usedRefs.add(name)
  72. } else {
  73. hasUnknown = true
  74. return
  75. }
  76. } else {
  77. hasUnknown = true
  78. return
  79. }
  80. }
  81. }
  82. /**
  83. * Extract the use ref names.
  84. * @param {Identifier | MemberExpression} refsNode
  85. * @returns {void}
  86. */
  87. function extractUsedForPattern(refsNode) {
  88. /** @type {Identifier | MemberExpression | ChainExpression} */
  89. let node = refsNode
  90. while (node.parent.type === 'ChainExpression') {
  91. node = node.parent
  92. }
  93. const parent = node.parent
  94. if (parent.type === 'AssignmentExpression') {
  95. if (parent.right === node) {
  96. if (parent.left.type === 'ObjectPattern') {
  97. // `({foo} = $refs)`
  98. extractUsedForObjectPattern(parent.left)
  99. } else if (parent.left.type === 'Identifier') {
  100. // `foo = $refs`
  101. hasUnknown = true
  102. }
  103. }
  104. } else if (parent.type === 'VariableDeclarator') {
  105. if (parent.init === node) {
  106. if (parent.id.type === 'ObjectPattern') {
  107. // `const {foo} = $refs`
  108. extractUsedForObjectPattern(parent.id)
  109. } else if (parent.id.type === 'Identifier') {
  110. // `const foo = $refs`
  111. hasUnknown = true
  112. }
  113. }
  114. } else if (parent.type === 'MemberExpression') {
  115. if (parent.object === node) {
  116. // `$refs.foo`
  117. const name = utils.getStaticPropertyName(parent)
  118. if (name) {
  119. usedRefs.add(name)
  120. } else {
  121. hasUnknown = true
  122. }
  123. }
  124. } else if (parent.type === 'CallExpression') {
  125. const argIndex = parent.arguments.indexOf(node)
  126. if (argIndex > -1) {
  127. // `foo($refs)`
  128. hasUnknown = true
  129. }
  130. } else if (
  131. parent.type === 'ForInStatement' ||
  132. parent.type === 'ReturnStatement'
  133. ) {
  134. hasUnknown = true
  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 (id.parent.type === 'MemberExpression') {
  221. if (id.parent.property === id) {
  222. // `this.$refs.foo`
  223. refsNode = id.parent
  224. }
  225. }
  226. extractUsedForPattern(refsNode)
  227. }
  228. }
  229. )
  230. )
  231. }
  232. }