script-setup-uses-vars.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { getStyleVariablesContext } = require('../utils/style-variables')
  7. const utils = require('../utils')
  8. const casing = require('../utils/casing')
  9. /**
  10. * `casing.camelCase()` converts the beginning to lowercase,
  11. * but does not convert the case of the beginning character when converting with Vue3.
  12. * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/shared/src/index.ts#L116
  13. * @param {string} str
  14. */
  15. function camelize(str) {
  16. return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
  17. }
  18. module.exports = {
  19. // eslint-disable-next-line eslint-plugin/prefer-message-ids
  20. meta: {
  21. type: 'problem',
  22. docs: {
  23. description:
  24. 'prevent `<script setup>` variables used in `<template>` to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
  25. categories: undefined,
  26. url: 'https://eslint.vuejs.org/rules/script-setup-uses-vars.html'
  27. },
  28. deprecated: true,
  29. schema: []
  30. },
  31. /**
  32. * @param {RuleContext} context - The rule context.
  33. * @returns {RuleListener} AST event handlers.
  34. */
  35. create(context) {
  36. if (!utils.isScriptSetup(context)) {
  37. return {}
  38. }
  39. const sourceCode = context.getSourceCode()
  40. /** @type {Set<string>} */
  41. const scriptVariableNames = new Set()
  42. const globalScope = sourceCode.scopeManager.globalScope
  43. if (globalScope) {
  44. for (const variable of globalScope.variables) {
  45. scriptVariableNames.add(variable.name)
  46. }
  47. const moduleScope = globalScope.childScopes.find(
  48. (scope) => scope.type === 'module'
  49. )
  50. for (const variable of (moduleScope && moduleScope.variables) || []) {
  51. scriptVariableNames.add(variable.name)
  52. }
  53. }
  54. /** @param {string} name */
  55. function markVariableAsUsed(name) {
  56. utils.markVariableAsUsed(context, name, sourceCode.ast)
  57. }
  58. /**
  59. * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L333
  60. * @param {string} name
  61. */
  62. function markSetupReferenceVariableAsUsed(name) {
  63. if (scriptVariableNames.has(name)) {
  64. markVariableAsUsed(name)
  65. return true
  66. }
  67. const camelName = camelize(name)
  68. if (scriptVariableNames.has(camelName)) {
  69. markVariableAsUsed(camelName)
  70. return true
  71. }
  72. const pascalName = casing.capitalize(camelName)
  73. if (scriptVariableNames.has(pascalName)) {
  74. markVariableAsUsed(pascalName)
  75. return true
  76. }
  77. return false
  78. }
  79. return utils.defineTemplateBodyVisitor(
  80. context,
  81. {
  82. VExpressionContainer(node) {
  83. for (const ref of node.references.filter(
  84. (ref) => ref.variable == null
  85. )) {
  86. markVariableAsUsed(ref.id.name)
  87. }
  88. },
  89. VElement(node) {
  90. if (
  91. (!utils.isHtmlElementNode(node) &&
  92. !utils.isSvgElementNode(node) &&
  93. !utils.isMathElementNode(node)) ||
  94. (node.rawName === node.name &&
  95. (utils.isHtmlWellKnownElementName(node.rawName) ||
  96. utils.isSvgWellKnownElementName(node.rawName) ||
  97. utils.isMathWellKnownElementName(node.rawName))) ||
  98. utils.isBuiltInComponentName(node.rawName)
  99. ) {
  100. return
  101. }
  102. if (!markSetupReferenceVariableAsUsed(node.rawName)) {
  103. // Check namespace
  104. // https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L304
  105. const dotIndex = node.rawName.indexOf('.')
  106. if (dotIndex > 0) {
  107. markSetupReferenceVariableAsUsed(node.rawName.slice(0, dotIndex))
  108. }
  109. }
  110. },
  111. /** @param {VDirective} node */
  112. 'VAttribute[directive=true]'(node) {
  113. if (utils.isBuiltInDirectiveName(node.key.name.name)) {
  114. return
  115. }
  116. markSetupReferenceVariableAsUsed(`v-${node.key.name.rawName}`)
  117. },
  118. /** @param {VAttribute} node */
  119. 'VAttribute[directive=false]'(node) {
  120. if (node.key.name === 'ref' && node.value) {
  121. markVariableAsUsed(node.value.value)
  122. }
  123. }
  124. },
  125. {
  126. Program() {
  127. const styleVars = getStyleVariablesContext(context)
  128. if (styleVars) {
  129. for (const ref of styleVars.references) {
  130. markVariableAsUsed(ref.id.name)
  131. }
  132. }
  133. }
  134. },
  135. {
  136. templateBodyTriggerSelector: 'Program'
  137. }
  138. )
  139. }
  140. }