script-setup-uses-vars.js 4.7 KB

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