diff --git a/.circleci/config.yml b/.circleci/config.yml index c10b2139e..b80032ae8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,8 @@ workflows: jobs: - node-v8 - node-v10 + - eslint-v7 + - eslint-v8 - node-v12 - node-v14 - lint @@ -54,6 +56,42 @@ jobs: <<: *node-base docker: - image: node:10 + eslint-v7: + docker: + - image: node:10 + steps: + - run: + name: Versions + command: npm version + - checkout + - run: + name: Install eslint@7 + command: | + npm install --save-exact eslint@7 + - run: + name: Install dependencies + command: npm install + - run: + name: Test + command: npm test + eslint-v8: + docker: + - image: node:14 + steps: + - run: + name: Versions + command: npm version + - checkout + - run: + name: Install eslint@8 + command: | + npm install --save-exact eslint@^8.0.0-0 + - run: + name: Install dependencies + command: npm install + - run: + name: Test + command: npm test node-v12: <<: *node-base docker: diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index a056c912b..39e44d9ab 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -25,6 +25,8 @@ yarn add -D eslint eslint-plugin-vue - ESLint v6.2.0 and above - Node.js v8.10.0 and above +We have started supporting ESLint v8.0.0 beta, but note that beta support will be dropped once the stable version is released. + ::: ## :book: Usage @@ -93,13 +95,13 @@ If you installed [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/ ### How to use a custom parser? -If you want to use custom parsers such as [babel-eslint](https://www.npmjs.com/package/babel-eslint) or [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), you have to use the `parserOptions.parser` option instead of the `parser` option. Because this plugin requires [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser) to parse `.vue` files, this plugin doesn't work if you overwrite the `parser` option. +If you want to use custom parsers such as [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) or [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), you have to use the `parserOptions.parser` option instead of the `parser` option. Because this plugin requires [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser) to parse `.vue` files, this plugin doesn't work if you overwrite the `parser` option. ```diff -- "parser": "babel-eslint", +- "parser": "@typescript-eslint/parser", + "parser": "vue-eslint-parser", "parserOptions": { -+ "parser": "babel-eslint", ++ "parser": "@typescript-eslint/parser", "sourceType": "module" } ``` @@ -238,13 +240,13 @@ Make sure you have one of the following settings in your **.eslintrc**: - `"extends": ["plugin:vue/vue3-recommended"]` - `"extends": ["plugin:vue/base"]` -If you already use another parser (e.g. `"parser": "babel-eslint"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration: +If you already use another parser (e.g. `"parser": "@typescript-eslint/parser"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration: ```diff -- "parser": "babel-eslint", +- "parser": "@typescript-eslint/parser", + "parser": "vue-eslint-parser", "parserOptions": { -+ "parser": "babel-eslint", ++ "parser": "@typescript-eslint/parser", "ecmaVersion": 2020, "sourceType": "module" } @@ -331,7 +333,7 @@ Note that you cannot use angle-bracket type assertion style (`var x = bar;` - Turning off the rule in the ESLint configuration file does not ignore the warning. - Using the `` comment does not suppress warnings. - Duplicate warnings are displayed. -- Used `babel-eslint`, but the template still show `vue/no-parsing-error` warnings. +- Used `@babel/eslint-parser`, but the template still show `vue/no-parsing-error` warnings. You need to turn off Vetur's template validation by adding `vetur.validation.template: false` to your `.vscode/settings.json`. @@ -398,20 +400,18 @@ module.exports = { However, note that the AST generated by `espree` v8+ may not work well with some rules of `ESLint` v7.x. - #### Other Problems diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index 5d80b83bb..a4d0dbcfd 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -1331,7 +1331,7 @@ module.exports.defineVisitor = function create( setOffset([fromToken, nameToken], 1, exportToken) } } else { - // maybe babel-eslint + // maybe babel parser } } }, diff --git a/lib/utils/index.js b/lib/utils/index.js index e309dc579..dfd5610d0 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -50,7 +50,7 @@ const VUE3_BUILTIN_COMPONENT_NAMES = new Set( ) const path = require('path') const vueEslintParser = require('vue-eslint-parser') -const traverseNodes = vueEslintParser.AST.traverseNodes +const { traverseNodes, getFallbackKeys } = vueEslintParser.AST const { findVariable } = require('eslint-utils') const { getComponentPropsFromTypeDefine, @@ -155,11 +155,77 @@ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { } }) + const containerScopes = new WeakMap() + + /** + * @param {ASTNode} node + */ + function getContainerScope(node) { + const exprContainer = getVExpressionContainer(node) + if (!exprContainer) { + return null + } + const cache = containerScopes.get(exprContainer) + if (cache) { + return cache + } + const programNode = eslintSourceCode.ast + const parserOptions = context.parserOptions || {} + const ecmaFeatures = parserOptions.ecmaFeatures || {} + const ecmaVersion = parserOptions.ecmaVersion || 2020 + const sourceType = programNode.sourceType + try { + const eslintScope = createRequire(require.resolve('eslint'))( + 'eslint-scope' + ) + const expStmt = new Proxy(exprContainer, { + get(_object, key) { + if (key === 'type') { + return 'ExpressionStatement' + } + // @ts-expect-error + return exprContainer[key] + } + }) + const scopeProgram = new Proxy(programNode, { + get(_object, key) { + if (key === 'body') { + return [expStmt] + } + // @ts-expect-error + return programNode[key] + } + }) + const scope = eslintScope.analyze(scopeProgram, { + ignoreEval: true, + nodejsScope: false, + impliedStrict: ecmaFeatures.impliedStrict, + ecmaVersion, + sourceType, + fallback: getFallbackKeys + }) + containerScopes.set(exprContainer, scope) + return scope + } catch (e) { + // ignore + // console.log(e) + } + + return null + } return { // @ts-expect-error __proto__: context, getSourceCode() { return sourceCode + }, + getDeclaredVariables(node) { + const scope = getContainerScope(node) + if (scope) { + return scope.getDeclaredVariables(node) + } + + return context.getDeclaredVariables(node) } } } @@ -1806,6 +1872,36 @@ function isDef(v) { return v != null } +// ------------------------------------------------------------------------------ +// Nodejs Helpers +// ------------------------------------------------------------------------------ +/** + * @param {String} filename + */ +function createRequire(filename) { + const Module = require('module') + const moduleCreateRequire = + // Added in v12.2.0 + Module.createRequire || + // Added in v10.12.0, but deprecated in v12.2.0. + Module.createRequireFromPath || + // Polyfill - This is not executed on the tests on node@>=10. + /** + * @param {string} filename + */ + function (filename) { + const mod = new Module(filename) + + mod.filename = filename + // @ts-ignore + mod.paths = Module._nodeModulePaths(path.dirname(filename)) + // @ts-ignore + mod._compile('module.exports = require;', filename) + return mod.exports + } + return moduleCreateRequire(filename) +} + // ------------------------------------------------------------------------------ // Rule Helpers // ------------------------------------------------------------------------------ @@ -2121,6 +2217,19 @@ function getStringLiteralValue(node, stringOnly) { } return null } +/** + * Gets the VExpressionContainer of a given node. + * @param {ASTNode} node - The node to get. + * @return {VExpressionContainer|null} + */ +function getVExpressionContainer(node) { + /** @type {ASTNode | null} */ + let n = node + while (n && n.type !== 'VExpressionContainer') { + n = n.parent + } + return n +} // ------------------------------------------------------------------------------ // Vue Helpers diff --git a/package.json b/package.json index 9c5ff64ef..4ba55e5c2 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "node": ">=8.10" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0" + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0-0" }, "dependencies": { "eslint-utils": "^2.1.0", @@ -66,7 +66,6 @@ "@types/semver": "^7.2.0", "@typescript-eslint/parser": "^4.28.0", "@vuepress/plugin-pwa": "^1.4.1", - "babel-eslint": "^10.1.0", "env-cmd": "^10.1.0", "eslint": "^7.0.0", "eslint-config-prettier": "^6.11.0", diff --git a/tests/eslint-compat.js b/tests/eslint-compat.js index 5b846bdfe..8da19ae3f 100644 --- a/tests/eslint-compat.js +++ b/tests/eslint-compat.js @@ -16,24 +16,39 @@ function getESLintClassForV6() { /** @param {eslint.ESLint.Options} options */ constructor(options) { const { - overrideConfig: { plugins, globals, ...overrideConfig }, + overrideConfig: { plugins, globals, rules, ...overrideConfig } = { + plugins: [], + globals: {}, + rules: {} + }, fix, reportUnusedDisableDirectives, plugins: pluginsMap, ...otherOptions - } = options - this.engine = new eslint.CLIEngine({ + } = options || {} + /** @type {eslint.CLIEngine.Options} */ + const newOptions = { fix: Boolean(fix), + reportUnusedDisableDirectives: reportUnusedDisableDirectives + ? reportUnusedDisableDirectives !== 'off' + : undefined, + ...otherOptions, + globals: globals ? Object.keys(globals).filter((n) => globals[n]) : undefined, - ...otherOptions, - ...overrideConfig, plugins: plugins || [], - reportUnusedDisableDirectives: reportUnusedDisableDirectives - ? reportUnusedDisableDirectives !== 'off' - : undefined - }) + rules: rules + ? Object.entries(rules).reduce((o, [ruleId, opt]) => { + if (opt) { + o[ruleId] = opt + } + return o + }, /** @type {NonNullable}*/ ({})) + : undefined, + ...overrideConfig + } + this.engine = new eslint.CLIEngine(newOptions) for (const [name, plugin] of Object.entries(pluginsMap || {})) { this.engine.addPlugin(name, plugin) diff --git a/tests/lib/rules-without-vue-eslint-parser.js b/tests/lib/rules-without-vue-eslint-parser.js index b7c585134..a0c8a80a0 100644 --- a/tests/lib/rules-without-vue-eslint-parser.js +++ b/tests/lib/rules-without-vue-eslint-parser.js @@ -5,7 +5,6 @@ 'use strict' const Linter = require('eslint').Linter -const parser = require('babel-eslint') const rules = require('../..').rules const assert = require('assert') @@ -18,13 +17,11 @@ describe("Don't crash even if without vue-eslint-parser.", () => { it(ruleId, () => { const linter = new Linter() const config = { - parser: 'babel-eslint', - parserOptions: { ecmaVersion: 2015 }, + parserOptions: { ecmaVersion: 2015, ecmaFeatures: { jsx: true } }, rules: { [ruleId]: 'error' } } - linter.defineParser('babel-eslint', parser) linter.defineRule(ruleId, rules[key]) const resultVue = linter.verifyAndFix(code, config, 'test.vue') for (const { message } of resultVue.messages) { diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js index 31d5b6beb..2470f63f1 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -52,6 +52,20 @@ tester.run('camelcase', rule, { line: 4 } ] + }, + { + code: ` + `, + errors: [ + { + message: "Identifier 'my_pref' is not in camel case.", + line: 4 + } + ] } ] }) diff --git a/tests/lib/rules/jsx-uses-vars.js b/tests/lib/rules/jsx-uses-vars.js index ea00e836c..f8b700bd6 100644 --- a/tests/lib/rules/jsx-uses-vars.js +++ b/tests/lib/rules/jsx-uses-vars.js @@ -10,7 +10,9 @@ const eslint = require('eslint') const rule = require('../../../lib/rules/jsx-uses-vars') -const ruleNoUnusedVars = require('eslint/lib/rules/no-unused-vars') +const ruleNoUnusedVars = new (require('eslint').Linter)() + .getRules() + .get('no-unused-vars') const RuleTester = eslint.RuleTester const ruleTester = new RuleTester({ diff --git a/tests/lib/rules/script-setup-uses-vars.js b/tests/lib/rules/script-setup-uses-vars.js index 3cf9cde0c..f8287c0c7 100644 --- a/tests/lib/rules/script-setup-uses-vars.js +++ b/tests/lib/rules/script-setup-uses-vars.js @@ -11,7 +11,9 @@ const eslint = require('eslint') const rule = require('../../../lib/rules/script-setup-uses-vars') -const ruleNoUnusedVars = require('eslint/lib/rules/no-unused-vars') +const ruleNoUnusedVars = new (require('eslint').Linter)() + .getRules() + .get('no-unused-vars') const RuleTester = eslint.RuleTester const ruleTester = new RuleTester({ diff --git a/typings/eslint/index.d.ts b/typings/eslint/index.d.ts index fdbca8503..0a1cd36c4 100644 --- a/typings/eslint/index.d.ts +++ b/typings/eslint/index.d.ts @@ -123,9 +123,6 @@ export class SourceCode /*extends ESLintSourceCode*/ { ): string getLines(): string[] getAllComments(): VNODE.Comment[] - getComments( - node: VAST.ESNode - ): { leading: VNODE.Comment[]; trailing: VNODE.Comment[] } getJSDocComment(node: VAST.ESNode): AST.Token | null getNodeByRangeIndex(index: number): VAST.ESNode | VAST.JSXNode isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean