no-template-shadow.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  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. module.exports = {
  20. meta: {
  21. type: 'suggestion',
  22. docs: {
  23. description:
  24. 'disallow variable declarations from shadowing variables declared in the outer scope',
  25. categories: ['vue3-strongly-recommended', 'strongly-recommended'],
  26. url: 'https://eslint.vuejs.org/rules/no-template-shadow.html'
  27. },
  28. fixable: null,
  29. schema: [],
  30. messages: {
  31. alreadyDeclaredInUpperScope:
  32. "Variable '{{name}}' is already declared in the upper scope."
  33. }
  34. },
  35. /** @param {RuleContext} context */
  36. create(context) {
  37. /** @type {Set<string>} */
  38. const jsVars = new Set()
  39. /**
  40. * @typedef {object} ScopeStack
  41. * @property {ScopeStack | null} parent
  42. * @property {Identifier[]} nodes
  43. */
  44. /** @type {ScopeStack | null} */
  45. let scopeStack = null
  46. return utils.compositingVisitors(
  47. utils.isScriptSetup(context)
  48. ? {
  49. Program() {
  50. const globalScope =
  51. context.getSourceCode().scopeManager.globalScope
  52. if (!globalScope) {
  53. return
  54. }
  55. for (const variable of globalScope.variables) {
  56. if (variable.defs.length > 0) {
  57. jsVars.add(variable.name)
  58. }
  59. }
  60. const moduleScope = globalScope.childScopes.find(
  61. (scope) => scope.type === 'module'
  62. )
  63. if (!moduleScope) {
  64. return
  65. }
  66. for (const variable of moduleScope.variables) {
  67. if (variable.defs.length > 0) {
  68. jsVars.add(variable.name)
  69. }
  70. }
  71. }
  72. }
  73. : {},
  74. utils.defineScriptSetupVisitor(context, {
  75. onDefinePropsEnter(_node, props) {
  76. for (const prop of props) {
  77. if (prop.propName) {
  78. jsVars.add(prop.propName)
  79. }
  80. }
  81. }
  82. }),
  83. utils.executeOnVue(context, (obj) => {
  84. const properties = utils.iterateProperties(obj, new Set(GROUP_NAMES))
  85. for (const node of properties) {
  86. jsVars.add(node.name)
  87. }
  88. }),
  89. utils.defineTemplateBodyVisitor(context, {
  90. /** @param {VElement} node */
  91. VElement(node) {
  92. scopeStack = {
  93. parent: scopeStack,
  94. nodes: scopeStack ? [...scopeStack.nodes] : []
  95. }
  96. for (const variable of node.variables) {
  97. const varNode = variable.id
  98. const name = varNode.name
  99. if (
  100. scopeStack.nodes.some((node) => node.name === name) ||
  101. jsVars.has(name)
  102. ) {
  103. context.report({
  104. node: varNode,
  105. loc: varNode.loc,
  106. messageId: 'alreadyDeclaredInUpperScope',
  107. data: {
  108. name
  109. }
  110. })
  111. } else {
  112. scopeStack.nodes.push(varNode)
  113. }
  114. }
  115. },
  116. 'VElement:exit'() {
  117. scopeStack = scopeStack && scopeStack.parent
  118. }
  119. })
  120. )
  121. }
  122. }