no-template-shadow.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /**
  2. * @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope.
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @typedef {import('../utils').GroupName} GroupName
  9. */
  10. /** @type {GroupName[]} */
  11. const GROUP_NAMES = [
  12. 'props',
  13. 'computed',
  14. 'data',
  15. 'asyncData',
  16. 'methods',
  17. 'setup'
  18. ]
  19. /**
  20. * @param {RuleContext} context
  21. * @param {string} variableName
  22. */
  23. function isAllowedVarName(context, variableName) {
  24. if (context.options[0] && context.options[0].allow) {
  25. return context.options[0].allow.includes(variableName)
  26. }
  27. return false
  28. }
  29. module.exports = {
  30. meta: {
  31. type: 'suggestion',
  32. docs: {
  33. description:
  34. 'disallow variable declarations from shadowing variables declared in the outer scope',
  35. categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
  36. url: 'https://eslint.vuejs.org/rules/no-template-shadow.html'
  37. },
  38. fixable: null,
  39. schema: [
  40. {
  41. type: 'object',
  42. properties: {
  43. allow: {
  44. type: 'array',
  45. items: {
  46. type: 'string'
  47. },
  48. uniqueItems: true
  49. }
  50. },
  51. additionalProperties: false
  52. }
  53. ],
  54. messages: {
  55. alreadyDeclaredInUpperScope:
  56. "Variable '{{name}}' is already declared in the upper scope."
  57. }
  58. },
  59. /** @param {RuleContext} context */
  60. create(context) {
  61. /** @type {Set<string>} */
  62. const jsVars = new Set()
  63. /**
  64. * @typedef {object} ScopeStack
  65. * @property {ScopeStack | null} parent
  66. * @property {Identifier[]} nodes
  67. */
  68. /** @type {ScopeStack | null} */
  69. let scopeStack = null
  70. return utils.compositingVisitors(
  71. utils.isScriptSetup(context)
  72. ? {
  73. Program() {
  74. const globalScope =
  75. context.getSourceCode().scopeManager.globalScope
  76. if (!globalScope) {
  77. return
  78. }
  79. for (const variable of globalScope.variables) {
  80. if (variable.defs.length > 0) {
  81. jsVars.add(variable.name)
  82. }
  83. }
  84. const moduleScope = globalScope.childScopes.find(
  85. (scope) => scope.type === 'module'
  86. )
  87. if (!moduleScope) {
  88. return
  89. }
  90. for (const variable of moduleScope.variables) {
  91. if (variable.defs.length > 0) {
  92. jsVars.add(variable.name)
  93. }
  94. }
  95. }
  96. }
  97. : {},
  98. utils.defineScriptSetupVisitor(context, {
  99. onDefinePropsEnter(_node, props) {
  100. for (const prop of props) {
  101. if (prop.propName) {
  102. jsVars.add(prop.propName)
  103. }
  104. }
  105. }
  106. }),
  107. utils.executeOnVue(context, (obj) => {
  108. const properties = utils.iterateProperties(obj, new Set(GROUP_NAMES))
  109. for (const node of properties) {
  110. jsVars.add(node.name)
  111. }
  112. }),
  113. utils.defineTemplateBodyVisitor(context, {
  114. /** @param {VElement} node */
  115. VElement(node) {
  116. scopeStack = {
  117. parent: scopeStack,
  118. nodes: scopeStack ? [...scopeStack.nodes] : []
  119. }
  120. for (const variable of node.variables) {
  121. const varNode = variable.id
  122. const name = varNode.name
  123. if (isAllowedVarName(context, name)) {
  124. continue
  125. }
  126. if (
  127. scopeStack.nodes.some((node) => node.name === name) ||
  128. jsVars.has(name)
  129. ) {
  130. context.report({
  131. node: varNode,
  132. loc: varNode.loc,
  133. messageId: 'alreadyDeclaredInUpperScope',
  134. data: {
  135. name
  136. }
  137. })
  138. } else {
  139. scopeStack.nodes.push(varNode)
  140. }
  141. }
  142. },
  143. 'VElement:exit'() {
  144. scopeStack = scopeStack && scopeStack.parent
  145. }
  146. })
  147. )
  148. }
  149. }