UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

143 lines (136 loc) 4.85 kB
/** * @author Yosuke Ota * See LICENSE file in root directory for full license. */ 'use strict' const { getStyleVariablesContext } = require('../utils/style-variables') // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ const utils = require('../utils') const casing = require('../utils/casing') // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { meta: { type: 'problem', docs: { description: 'prevent `<script setup>` variables used in `<template>` to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description categories: ['base'], url: 'https://eslint.vuejs.org/rules/script-setup-uses-vars.html' }, schema: [] }, /** * @param {RuleContext} context - The rule context. * @returns {RuleListener} AST event handlers. */ create(context) { if (!utils.isScriptSetup(context)) { return {} } /** @type {Set<string>} */ const scriptVariableNames = new Set() const globalScope = context.getSourceCode().scopeManager.globalScope if (globalScope) { for (const variable of globalScope.variables) { scriptVariableNames.add(variable.name) } const moduleScope = globalScope.childScopes.find( (scope) => scope.type === 'module' ) for (const variable of (moduleScope && moduleScope.variables) || []) { scriptVariableNames.add(variable.name) } } /** * `casing.camelCase()` converts the beginning to lowercase, * but does not convert the case of the beginning character when converting with Vue3. * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/shared/src/index.ts#L116 * @param {string} str */ function camelize(str) { return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) } /** * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L333 * @param {string} name */ function markSetupReferenceVariableAsUsed(name) { if (scriptVariableNames.has(name)) { context.markVariableAsUsed(name) return true } const camelName = camelize(name) if (scriptVariableNames.has(camelName)) { context.markVariableAsUsed(camelName) return true } const pascalName = casing.capitalize(camelName) if (scriptVariableNames.has(pascalName)) { context.markVariableAsUsed(pascalName) return true } return false } return utils.defineTemplateBodyVisitor( context, { VExpressionContainer(node) { for (const ref of node.references.filter( (ref) => ref.variable == null )) { context.markVariableAsUsed(ref.id.name) } }, VElement(node) { if ( (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || (node.rawName === node.name && (utils.isHtmlWellKnownElementName(node.rawName) || utils.isSvgWellKnownElementName(node.rawName))) || utils.isBuiltInComponentName(node.rawName) ) { return } if (!markSetupReferenceVariableAsUsed(node.rawName)) { // Check namespace // https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L304 const dotIndex = node.rawName.indexOf('.') if (dotIndex > 0) { markSetupReferenceVariableAsUsed(node.rawName.slice(0, dotIndex)) } } }, /** @param {VDirective} node */ 'VAttribute[directive=true]'(node) { if (utils.isBuiltInDirectiveName(node.key.name.name)) { return } markSetupReferenceVariableAsUsed(`v-${node.key.name.rawName}`) }, /** @param {VAttribute} node */ 'VAttribute[directive=false]'(node) { if (node.key.name === 'ref' && node.value) { context.markVariableAsUsed(node.value.value) } } }, { Program() { const styleVars = getStyleVariablesContext(context) if (styleVars) { for (const ref of styleVars.references) { context.markVariableAsUsed(ref.id.name) } } } }, { templateBodyTriggerSelector: 'Program' } ) } }