如何在esint中干净地组合Vue 3和Typescript



因此,基本上,以下是我正在尝试(但迄今为止失败(实现的目标:

我希望esint对我的.vue文件和.ts文件进行lint处理,对两者使用相同的TypeScript规则(我的每个组件都使用<script lang="ts" setup>(,并将我的tsconfig.json作为两者的基本TS配置。

现在我发现的困难是这个。据我所知,当你为esint添加typescript插件时,它需要明确地告诉你的tsconfig.json在哪里

module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
};

但问题是,我不能将typescript解析器设置为主解析器。因为vue解析器需要成为主要的解析器。

所以我把它设置成这样:

module.exports = {
env: {
node: true,
},
root: true,
ignorePatterns: ["/.vscode/**/*", "/dist/**/*", "/public/**/*"],
extends: [
"eslint:recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:vue/vue3-recommended",
"@vue/eslint-config-typescript",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:json/recommended",
"plugin:prettier/recommended",
],
plugins: ["@typescript-eslint"],
parser: "vue-eslint-parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
vueFeatures: {
filter: false,
styleCSSVariableInjection: true,
},
parser: {
// Script parser for `<script>`
js: "espree",
// Script parser for `<script lang="ts">`
ts: "@typescript-eslint/parser",
// Script parser for vue directives (e.g. `v-if=` or `:attribute=`)
// and vue interpolations (e.g. `{{variable}}`).
// If not specified, the parser determined by `<script lang ="...">` is used.
// "<template>": "typescript-estree",
},
},
settings: {
"import/resolver": {
typescript: true,
node: true,
},
},
globals: {
$: "readonly",
$$: "readonly",
$ref: "readonly",
$computed: "readonly",
$shallowRef: "readonly",
$customRef: "readonly",
$toRef: "readonly",
},
}

但正如你所看到的,我没有地方配置tsconfig.json的位置。现在我遇到了一些问题。一个是,在我的tsconfig.json中,我配置了一个路径别名:"paths": { "@/*": ["src/*"] },但esint现在无法识别。在我的tsconfig.json中还有一些其他配置,esint现在还没有意识到,所以我收到的警告和错误比我应该得到的要多。

那么,如何正确地配置esint以干净地使用vue和typescript呢?

对于任何感兴趣的人来说,我最终还是成功了。但与此同时,我也让我的.eslintrc.js比它本身需要的复杂得多。但我现在太懒了,无法简化它,所以我只把整个东西粘贴在这里:

// .eslintrc.js
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
const isProd = process.env.NODE_ENV === "production"
const runInProd = config => !isProd ? "off" : config
const rules = {
eslint: {
"no-debugger": runInProd("warn"),
"dot-location": ["error", "property"],
"eqeqeq": ["error", "smart"],
"max-len": [
"error",
{
code: 90,
tabWidth: 2,
ignoreUrls: true,
ignoreStrings: true,
ignoreComments: true,
ignoreRegExpLiterals: true,
ignoreTrailingComments: true,
ignorePattern: 'class="|<path',
},
],
"no-tabs": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "warn",
"no-unused-expressions": ["error", {
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
enforceForJSX: true,
}],
"no-unused-vars": ["warn", {
args: "all",
vars: "local",
caughtErrors: "all",
ignoreRestSiblings: true,
varsIgnorePattern: "^_",
argsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
}],
"prefer-template": "warn",
},
vue: {
"attribute-hyphenation": [
"warn",
"always",
{
ignore: [
"DEF",
"nameSpaceName",
"mapDEFToID",
"contentType",
"showStat",
"showLog",
],
},
],
"block-lang": [
"warn",
{
script: {
lang: "ts",
},
},
],
"block-tag-newline": "error",
"comma-spacing": "warn",
"component-api-style": ["error", ["script-setup"]],
"component-name-in-template-casing": ["error", "PascalCase"],
"define-macros-order": "warn",
"dot-location": ["error", "property"],
"html-closing-bracket-newline": [
"error",
{
singleline: "never",
multiline: "always",
},
],
"html-indent": ["error", 2, { baseIndent: 0 }],
"html-self-closing": [
"error",
{
html: {
void: "any",
},
},
],
"max-attributes-per-line": [
"error",
{
singleline: {
max: 2,
},
multiline: {
max: 2,
},
},
],
get "max-len"() {
return rules.eslint["max-len"]
},
"multi-word-component-names": "off",
"no-setup-props-destructure": "off",
"no-template-shadow": "off",
"no-template-target-blank": "error",
"no-v-html": "off",
"object-shorthand": "warn",
"padding-line-between-blocks": "warn",
"prefer-template": "warn",
"require-default-prop": "off",
"script-indent": ["error", 2],
},
import: {
"export": "error",
"extensions": "off",
"first": "error",
"no-self-import": "error",
"no-unresolved": "error",
"no-useless-path-segments": [
"error",
{
noUselessIndex: true,
},
],
"order": [
"error",
{
groups: [
"type",
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
],
pathGroups: [
{
pattern: "@/*",
group: "internal",
},
],
},
],
"no-cycle": runInProd("error"),
"no-deprecated": runInProd("warn"),
"no-unused-modules": runInProd("error"),
"no-named-as-default": runInProd("error"),
},
typescript: {
"ban-types": [
"error",
{
types: {
"Function": false,
"{}": false,
},
extendDefaults: true,
},
],
"brace-style": "warn",
"comma-dangle": ["warn", "always-multiline"],
"comma-spacing": "warn",
"consistent-type-exports": runInProd("warn"),
"consistent-type-imports": runInProd("warn"),
"dot-notation": "off",
"func-call-spacing": "warn",
"indent": "off",
"keyword-spacing": "warn",
"lines-between-class-members": "warn",
"member-delimiter-style": [
"warn",
{
multiline: {
delimiter: "none",
},
singleline: {
requireLast: false,
},
},
],
"no-confusing-void-expression": "off",
"no-empty-function": [
"warn",
{
allow: ["arrowFunctions"],
},
],
"no-explicit-any": "off",
"no-extra-parens": "warn",
"no-floating-promises": "off",
"no-misused-promises": [
"error",
{
checksVoidReturn: false,
},
],
"no-non-null-assertion": "off",
"no-throw-literal": "error",
"no-redeclare": [
"warn",
{
ignoreDeclarationMerge: true,
},
],
"no-redundant-type-constituents": runInProd("warn"),
"no-unnecessary-boolean-literal-compare": runInProd([
"warn",
{
allowComparingNullableBooleansToTrue: false,
},
]),
"no-unnecessary-condition": "off",
"no-unsafe-argument": "off",
"no-unsafe-assignment": "off",
"no-unsafe-call": "off",
"no-unsafe-member-access": "off",
"no-unsafe-return": "off",
get "no-unused-expressions"() {
return rules.eslint["no-unused-expressions"]
},
get "no-unused-vars"() {
return rules.eslint["no-unused-vars"]
},
"padding-line-between-statements": [
"warn",
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
{ "blankLine": "any", "prev": ["singleline-const", "singleline-let", "singleline-var"], "next": ["singleline-const", "singleline-let", "singleline-var"] },
{ "blankLine": "always", "prev": "multiline-block-like", "next": "*" },
{ "blankLine": "any", "prev": "expression", "next": "expression" },
{ "blankLine": "always", "prev": "multiline-expression", "next": "*" },
{ "blankLine": "always", "prev": ["interface", "type"], "next": "*" },
{ "blankLine": "always", "prev": "break", "next": "*" },
{ "blankLine": "always", "prev": "throw", "next": "*" },
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": "return", "next": "*" },
],
"prefer-optional-chain": "error",
"prefer-readonly": "off",
"prefer-reduce-type-parameter": "off",
"prefer-return-this-type": runInProd("warn"),
"prefer-string-starts-ends-with": runInProd("warn"),
"prefer-ts-expect-error": "warn",
"promise-function-async": runInProd("warn"),
"quotes": [
"error",
"double",
{
avoidEscape: true,
allowTemplateLiterals: false,
},
],
"require-await": "off",
"restrict-template-expressions": "off",
"return-await": "warn",
"semi": [
"error",
"never",
{
beforeStatementContinuationChars: "always",
},
],
"sort-type-union-intersection-members": "error",
"space-before-blocks": "warn",
"space-before-function-paren": [
"warn",
{
anonymous: "never",
named: "never",
asyncArrow: "always",
},
],
"space-infix-ops": "warn",
"switch-exhaustiveness-check": runInProd("error"),
"type-annotation-spacing": "warn",
"unbound-method": runInProd("error"),
"unified-signatures": "warn",
},
}
rules["import"] = Object.keys(rules["import"]).reduce(
(obj, key) => ({
...obj,
[`import/${key}`]: rules["import"][key],
}),
{}
)
rules["vue"] = Object.keys(rules["vue"]).reduce(
(obj, key) => ({
...obj,
[key]: "off",
[`vue/${key}`]: rules["vue"][key],
}),
{}
)
rules["typescript"] = Object.keys(rules["typescript"]).reduce(
(obj, key) => ({
...obj,
[key]: "off",
[`@typescript-eslint/${key}`]: rules["typescript"][key],
}),
{}
)
const eslint = {
root: true,
env: {
es2021: true,
browser: true,
},
extends: [
"eslint:recommended",
],
plugins: [
"import",
"unused-imports",
],
rules: {
...rules["eslint"],
...rules["import"],
},
settings: {
"import/ignore": [/.vue$/],
"import/extensions": [".js", ".jsx", ".ts", ".tsx", ".vue"],
"import/parsers": {
"espree": [".js", ".jsx"],
},
"import/resolvers": {
"alias": [["@/", "./src/"]],
}
},
globals: {
$: "readonly",
$$: "readonly",
$ref: "readonly",
$computed: "readonly",
$shallowRef: "readonly",
$customRef: "readonly",
$toRef: "readonly",
x3dom: "readonly",
X3dMouseEvent: "readonly",
},
}
const typescript = {
files: ["*.ts", "*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: "./tsconfig.json",
},
extends: [
...eslint.extends,
"@vue/eslint-config-typescript/recommended",
...(isProd ? ["plugin:@typescript-eslint/recommended-requiring-type-checking"] : []),
"plugin:import/typescript",
],
plugins: [...eslint.plugins, "@typescript-eslint"],
settings: {
...eslint.settings,
"import/parsers": {
...eslint.settings["import/parsers"],
"@typescript-eslint/parser": [".ts", ".tsx"],
},
"import/resolver": {
typescript: {
alwaysTryTypes: true,
project: `${__dirname}/tsconfig.json`,
},
},
},
rules: {
...eslint.rules,
...rules["typescript"],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": rules["eslint"]["no-unused-vars"],
},
}
const vue = {
files: ["*.vue"],
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
tsconfigRootDir: __dirname,
project: "./tsconfig.json",
},
extends: [
"plugin:vue/vue3-recommended",
...typescript.extends,
],
plugins: [...typescript.plugins, "vue"],
settings: {
...typescript.settings,
"import/parsers": {
...typescript.settings["import/parsers"],
"vue-eslint-parser": [".vue"],
},
"import/resolver": {
...typescript.settings["import/resolver"],
vue: {
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
vueFeatures: {
filter: false,
styleCSSVariableInjection: true,
},
parser: {
// Script parser for `<script>`
js: "espree",
// Script parser for `<script lang="ts">`
ts: "@typescript-eslint/parser",
// Script parser for vue directives (e.g. `v-if=` or `:attribute=`)
// and vue interpolations (e.g. `{{variable}}`).
// If not specified, the parser determined by `<script lang ="...">` is used.
// "<template>": "typescript-estree",
},
},
},
},
},
rules: {
...typescript.rules,
...rules["vue"],
"eol-last": "off",
"linebreak-style": "off",
"max-lines": "off",
"unicode-bom": "off",
},
}
module.exports = {
...eslint,
overrides: [
{ ...typescript },
{ ...vue },
],
}

哦,这些是我安装的与esint相关的开发依赖项。有些我可能根本没用。我仍在计划找出我没有使用的,可以删除的。但无论如何,以下是粘贴的整个列表:

{
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.4",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"@typescript-eslint/typescript-estree": "^5.36.1",
"@volar/vue-typescript": "^0.40.5",
"@vue/eslint-config-typescript": "^11.0.0",
"eslint": "^8.23.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^3.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"eslint-plugin-vue": "^9.3.0",
"espree": "^9.3.3",
"typescript": "^4.8.2",
"vue-eslint-parser": "^9.0.3",
}
}

祝你好运:-(

最新更新