no-template-shadow.js 3.3 KB

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