From 77af4b4c43b5afbe2b58f2d67e117597c24a451d Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Sat, 2 Dec 2023 00:01:10 +0900 Subject: [PATCH 01/17] Improve compatibility with ESLint v9 (take2) (#2338) --- lib/rules/jsx-uses-vars.js | 4 ++- lib/rules/script-setup-uses-vars.js | 20 +++++++---- lib/utils/index.js | 43 ++++++++++++++++++----- tests/lib/rules/jsx-uses-vars.js | 17 +++++++++ tests/lib/rules/script-setup-uses-vars.js | 15 ++++++++ typings/eslint/index.d.ts | 40 +++++++++++++++------ 6 files changed, 113 insertions(+), 26 deletions(-) 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 = From 7cf5c517cd80b7e28a37ae52b262b4169cf78473 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Wed, 27 Dec 2023 07:58:11 +0100 Subject: [PATCH 02/17] Document non-fixable rules correctly (#2343) --- docs/rules/index.md | 4 ++-- docs/rules/no-boolean-default.md | 4 +--- docs/rules/no-deprecated-v-is.md | 3 +-- eslint-internal-rules/no-invalid-meta-docs-categories.js | 1 + eslint.config.js | 4 ++++ lib/rules/html-indent.js | 1 + lib/rules/no-boolean-default.js | 2 +- lib/rules/no-deprecated-scope-attribute.js | 1 + lib/rules/no-deprecated-slot-attribute.js | 1 + lib/rules/no-deprecated-slot-scope-attribute.js | 1 + lib/rules/no-deprecated-v-is.js | 2 +- lib/rules/no-unsupported-features.js | 1 + lib/rules/padding-line-between-blocks.js | 1 + lib/rules/padding-line-between-tags.js | 1 + lib/rules/script-indent.js | 1 + lib/rules/v-if-else-key.js | 1 + 16 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/rules/index.md b/docs/rules/index.md index 71a05d42e..afc8deb98 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -62,7 +62,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: | :three::hammer: | | [vue/no-deprecated-slot-scope-attribute](./no-deprecated-slot-scope-attribute.md) | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: | :three::hammer: | | [vue/no-deprecated-v-bind-sync](./no-deprecated-v-bind-sync.md) | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: | :three::warning: | -| [vue/no-deprecated-v-is](./no-deprecated-v-is.md) | disallow deprecated `v-is` directive (in Vue.js 3.1.0+) | :wrench: | :three::hammer: | +| [vue/no-deprecated-v-is](./no-deprecated-v-is.md) | disallow deprecated `v-is` directive (in Vue.js 3.1.0+) | | :three::hammer: | | [vue/no-deprecated-v-on-native-modifier](./no-deprecated-v-on-native-modifier.md) | disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-v-on-number-modifiers](./no-deprecated-v-on-number-modifiers.md) | disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+) | :wrench: | :three::warning: | | [vue/no-deprecated-vue-config-keycodes](./no-deprecated-vue-config-keycodes.md) | disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+) | | :three::warning: | @@ -225,7 +225,7 @@ For example: | [vue/new-line-between-multi-line-property](./new-line-between-multi-line-property.md) | enforce new lines between multi-line properties in Vue components | :wrench: | :lipstick: | | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: | | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `` }, - { - filename: 'test.vue', - code: ` - ` - }, // computed properties { @@ -629,26 +622,6 @@ tester.run('no-unused-components', rule, { } ] }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: 'The "Foo" component has been registered but not used.', - line: 8 - } - ] - }, // computed properties { diff --git a/tests/lib/rules/v-bind-style.js b/tests/lib/rules/v-bind-style.js index 0cb67f62f..5b4e88ab2 100644 --- a/tests/lib/rules/v-bind-style.js +++ b/tests/lib/rules/v-bind-style.js @@ -106,6 +106,21 @@ tester.run('v-bind-style', rule, { output: '', options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] + }, + // v-bind same-name shorthand (Vue 3.4+) + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand'], + errors: ["Unexpected 'v-bind' before ':'."] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['longform'], + errors: ["Expected 'v-bind' before ':'."] } ] }) diff --git a/tests/lib/rules/valid-v-bind.js b/tests/lib/rules/valid-v-bind.js index 05c3d2b03..2259f9129 100644 --- a/tests/lib/rules/valid-v-bind.js +++ b/tests/lib/rules/valid-v-bind.js @@ -71,6 +71,15 @@ tester.run('valid-v-bind', rule, { filename: 'test.vue', code: "" }, + // v-bind same-name shorthand (Vue 3.4+) + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, // parsing error { filename: 'parsing-error.vue', @@ -88,11 +97,6 @@ tester.run('valid-v-bind', rule, { code: '', errors: ["'v-bind' directives require an attribute value."] }, - { - filename: 'test.vue', - code: '', - errors: ["'v-bind' directives require an attribute value."] - }, { filename: 'test.vue', code: "", From 26fc85e928a46c617e52875164e58bde1bead696 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Tue, 9 Jan 2024 13:10:59 +0100 Subject: [PATCH 14/17] Remove Vue 3 syntax status notice from docs (#2358) --- docs/index.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/index.md b/docs/index.md index f9b1e49f1..3fd2b6d66 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,11 +14,6 @@ This plugin allows us to check the `