Skip to content

Commit

Permalink
Add support for ESLint v8 (beta). (#1610)
Browse files Browse the repository at this point in the history
* Add support for ESLint v8.

* fix CI

* update

* update

* update

* update

* fix

* update

* update doc
  • Loading branch information
ota-meshi committed Aug 27, 2021
1 parent 80ca3ea commit c86cc88
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 33 deletions.
38 changes: 38 additions & 0 deletions .circleci/config.yml
Expand Up @@ -4,6 +4,8 @@ workflows:
jobs:
- node-v8
- node-v10
- eslint-v7
- eslint-v8
- node-v12
- node-v14
- lint
Expand Down Expand Up @@ -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:
Expand Down
22 changes: 11 additions & 11 deletions docs/user-guide/README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
```
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -331,7 +333,7 @@ Note that you cannot use angle-bracket type assertion style (`var x = <foo>bar;`
- Turning off the rule in the ESLint configuration file does not ignore the warning.
- Using the `<!-- eslint-disable -->` 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`.

Expand Down Expand Up @@ -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.

<!--
##### Using ESLint >= v8.x

You need to specify `2022` for `parserOptions.ecmaVersion`.
You need to specify `2022` or `"latest"` for `parserOptions.ecmaVersion`.

```js
module.exports = {
parserOptions: {
ecmaVersion: 2022,
ecmaVersion: 'latest',
sourceType: 'module'
},
}
```
-->

#### Other Problems

Expand Down
2 changes: 1 addition & 1 deletion lib/utils/indent-common.js
Expand Up @@ -1331,7 +1331,7 @@ module.exports.defineVisitor = function create(
setOffset([fromToken, nameToken], 1, exportToken)
}
} else {
// maybe babel-eslint
// maybe babel parser
}
}
},
Expand Down
111 changes: 110 additions & 1 deletion lib/utils/index.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
33 changes: 24 additions & 9 deletions tests/eslint-compat.js
Expand Up @@ -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<eslint.CLIEngine.Options["rules"]>}*/ ({}))
: undefined,
...overrideConfig
}
this.engine = new eslint.CLIEngine(newOptions)

for (const [name, plugin] of Object.entries(pluginsMap || {})) {
this.engine.addPlugin(name, plugin)
Expand Down
5 changes: 1 addition & 4 deletions tests/lib/rules-without-vue-eslint-parser.js
Expand Up @@ -5,7 +5,6 @@
'use strict'

const Linter = require('eslint').Linter
const parser = require('babel-eslint')
const rules = require('../..').rules
const assert = require('assert')

Expand All @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions tests/lib/rules/camelcase.js
Expand Up @@ -52,6 +52,20 @@ tester.run('camelcase', rule, {
line: 4
}
]
},
{
code: `
<template>
<div @click="
const { my_pref } = $event
" />
</template>`,
errors: [
{
message: "Identifier 'my_pref' is not in camel case.",
line: 4
}
]
}
]
})

0 comments on commit c86cc88

Please sign in to comment.