diff --git a/lib/rules/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js index b8858e1f3..d9f46972a 100644 --- a/lib/rules/jsx-uses-vars.js +++ b/lib/rules/jsx-uses-vars.js @@ -30,6 +30,8 @@ SOFTWARE. */ 'use strict' +const utils = require('../utils') + module.exports = { // eslint-disable-next-line eslint-plugin/prefer-message-ids meta: { @@ -63,7 +65,7 @@ module.exports = { return } - context.markVariableAsUsed(name) + utils.markVariableAsUsed(context, name, node) } } } diff --git a/lib/rules/script-setup-uses-vars.js b/lib/rules/script-setup-uses-vars.js index 05ccc9653..703d0cb9a 100644 --- a/lib/rules/script-setup-uses-vars.js +++ b/lib/rules/script-setup-uses-vars.js @@ -39,9 +39,10 @@ module.exports = { if (!utils.isScriptSetup(context)) { return {} } + const sourceCode = context.getSourceCode() /** @type {Set} */ const scriptVariableNames = new Set() - const globalScope = context.getSourceCode().scopeManager.globalScope + const globalScope = sourceCode.scopeManager.globalScope if (globalScope) { for (const variable of globalScope.variables) { scriptVariableNames.add(variable.name) @@ -54,23 +55,28 @@ module.exports = { } } + /** @param {string} name */ + function markVariableAsUsed(name) { + utils.markVariableAsUsed(context, name, sourceCode.ast) + } + /** * @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) + markVariableAsUsed(name) return true } const camelName = camelize(name) if (scriptVariableNames.has(camelName)) { - context.markVariableAsUsed(camelName) + markVariableAsUsed(camelName) return true } const pascalName = casing.capitalize(camelName) if (scriptVariableNames.has(pascalName)) { - context.markVariableAsUsed(pascalName) + markVariableAsUsed(pascalName) return true } return false @@ -83,7 +89,7 @@ module.exports = { for (const ref of node.references.filter( (ref) => ref.variable == null )) { - context.markVariableAsUsed(ref.id.name) + markVariableAsUsed(ref.id.name) } }, VElement(node) { @@ -115,7 +121,7 @@ module.exports = { /** @param {VAttribute} node */ 'VAttribute[directive=false]'(node) { if (node.key.name === 'ref' && node.value) { - context.markVariableAsUsed(node.value.value) + markVariableAsUsed(node.value.value) } } }, @@ -124,7 +130,7 @@ module.exports = { const styleVars = getStyleVariablesContext(context) if (styleVars) { for (const ref of styleVars.references) { - context.markVariableAsUsed(ref.id.name) + markVariableAsUsed(ref.id.name) } } } diff --git a/lib/utils/index.js b/lib/utils/index.js index 838d14f6d..9127fa95e 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -245,10 +245,12 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { tokenStore ) + /** @type {WeakMap} */ const containerScopes = new WeakMap() /** * @param {ASTNode} node + * @returns {import('eslint').Scope.ScopeManager|null} */ function getContainerScope(node) { const exprContainer = getVExpressionContainer(node) @@ -260,9 +262,11 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { return cache } const programNode = eslintSourceCode.ast - const parserOptions = context.parserOptions || {} + const parserOptions = + context.languageOptions?.parserOptions ?? context.parserOptions ?? {} const ecmaFeatures = parserOptions.ecmaFeatures || {} - const ecmaVersion = parserOptions.ecmaVersion || 2020 + const ecmaVersion = + context.languageOptions?.ecmaVersion ?? parserOptions.ecmaVersion ?? 2020 const sourceType = programNode.sourceType try { const eslintScope = createRequire(require.resolve('eslint'))( @@ -297,7 +301,6 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { getSourceCode() { return sourceCode }, - // @ts-expect-error -- Added in ESLint v8.40 get sourceCode() { return sourceCode }, @@ -310,11 +313,11 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { */ function getDeclaredVariables(node) { const scope = getContainerScope(node) - if (scope) { - return scope.getDeclaredVariables(node) - } - - return context.getDeclaredVariables(node) + return ( + scope?.getDeclaredVariables?.(node) ?? + context.getDeclaredVariables?.(node) ?? + [] + ) } } @@ -1939,6 +1942,10 @@ module.exports = { withinTypeNode, findVariableByIdentifier, getScope, + /** + * Marks a variable with the given name in the current scope as used. This affects the no-unused-vars rule. + */ + markVariableAsUsed, /** * Checks whether the given node is in export default. * @param {ASTNode} node @@ -2562,6 +2569,26 @@ function isTypeScriptFile(path) { return path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.mts') } +// ------------------------------------------------------------------------------ +// ESLint Helpers +// ------------------------------------------------------------------------------ +/** + * Marks a variable with the given name in the current scope as used. This affects the no-unused-vars rule. + * @param {RuleContext} context + * @param {string} name + * @param {ASTNode} node The node to get the current scope. + */ +function markVariableAsUsed(context, name, node) { + const sourceCode = context.getSourceCode() + if (sourceCode.markVariableAsUsed) { + sourceCode.markVariableAsUsed(name, node) + } else { + // This function does not use the given node, but the currently visited node. + // If we need to determine the scope of a given node, we need to implement it yourself. + context.markVariableAsUsed?.(name) + } +} + // ------------------------------------------------------------------------------ // Vue Helpers // ------------------------------------------------------------------------------ diff --git a/tests/lib/rules/jsx-uses-vars.js b/tests/lib/rules/jsx-uses-vars.js index 3a2c7e82b..c0c39ccd6 100644 --- a/tests/lib/rules/jsx-uses-vars.js +++ b/tests/lib/rules/jsx-uses-vars.js @@ -24,6 +24,23 @@ const ruleTester = new RuleTester({ const linter = ruleTester.linter || eslint.linter linter.defineRule('jsx-uses-vars', rule) +ruleTester.run('jsx-uses-vars', rule, { + // Visually check that there are no warnings in the console. + valid: [ + ` + import SomeComponent from './SomeComponent.jsx'; + export default { + render () { + return ( + + ) + }, + }; + ` + ], + invalid: [] +}) + describe('jsx-uses-vars', () => { ruleTester.run('no-unused-vars', ruleNoUnusedVars, { valid: [ diff --git a/tests/lib/rules/script-setup-uses-vars.js b/tests/lib/rules/script-setup-uses-vars.js index 9d20f5748..93f20d551 100644 --- a/tests/lib/rules/script-setup-uses-vars.js +++ b/tests/lib/rules/script-setup-uses-vars.js @@ -23,6 +23,21 @@ const ruleTester = new RuleTester({ const linter = ruleTester.linter || eslint.linter linter.defineRule('script-setup-uses-vars', rule) +ruleTester.run('script-setup-uses-vars', rule, { + // Visually check that there are no warnings in the console. + valid: [ + ` + + + + ` + ], + invalid: [] +}) describe('script-setup-uses-vars', () => { ruleTester.run('no-unused-vars', ruleNoUnusedVars, { valid: [ diff --git a/typings/eslint/index.d.ts b/typings/eslint/index.d.ts index 7d85f665f..a11b29f11 100644 --- a/typings/eslint/index.d.ts +++ b/typings/eslint/index.d.ts @@ -18,7 +18,8 @@ export namespace Scope { scopes: Scope[] globalScope: Scope | null acquire(node: VAST.ESNode | VAST.Program, inner?: boolean): Scope | null - getDeclaredVariables(node: VAST.ESNode): Variable[] + /** @since ESLint v8.38.0 */ + getDeclaredVariables?(node: VAST.ESNode): Variable[] } interface Scope { type: @@ -230,6 +231,11 @@ export class SourceCode /*extends ESLintSourceCode*/ { getCommentsBefore(nodeOrToken: VNODE.HasLocation): VNODE.Comment[] getCommentsAfter(nodeOrToken: VNODE.HasLocation): VNODE.Comment[] getCommentsInside(node: VNODE.HasLocation): VNODE.Comment[] + + /** @since ESLint v8.39.0 */ + markVariableAsUsed?(name: string, node?: VNODE.HasLocation): void + /** @since ESLint v8.37.0 */ + getScope?(node: VNODE.HasLocation): Scope.Scope } export namespace SourceCode { interface Config { @@ -317,21 +323,35 @@ export namespace Rule { id: string options: ESLintRule.RuleContext['options'] settings: { [name: string]: any } - parserPath: string - parserOptions: any - parserServices: parserServices.ParserServices - - getAncestors(): VAST.ESNode[] - - getDeclaredVariables(node: VAST.ESNode): Scope.Variable[] + /** @deprecated removed in ESLint v10? */ + parserPath?: string + /** @deprecated removed in ESLint v10? */ + parserOptions?: ESLintLinter.ParserOptions + /** For flat config */ + languageOptions?: ESLintLinter.FlatConfig['languageOptions'] + /** @deprecated removed in ESLint v9 */ + parserServices?: parserServices.ParserServices + + /** @deprecated removed in ESLint v9 */ + getAncestors?(): VAST.ESNode[] + /** @deprecated removed in ESLint v9 */ + getDeclaredVariables?(node: VAST.ESNode): Scope.Variable[] getFilename(): string - getScope(): Scope.Scope + /** @since ESLint v8.40.0 */ + filename?: string + /** @deprecated removed in ESLint v9 */ + getScope?(): Scope.Scope getSourceCode(): SourceCode - markVariableAsUsed(name: string): boolean + /** @since ESLint v8.40.0 */ + sourceCode?: SourceCode + /** @deprecated removed in ESLint v9 */ + markVariableAsUsed?(name: string): boolean report(descriptor: ReportDescriptor): void // eslint@6 does not have this method. getCwd?: () => string + /** @since ESLint v8.40.0 */ + cwd?: string } type ReportDescriptor =