From 62b698660ae1192214f434c9a705de43aee7b3ed Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 15 Mar 2022 20:39:55 +0900 Subject: [PATCH 1/6] Add support for scope analysis of ` + + diff --git a/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/.eslintrc.json b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/.eslintrc.json new file mode 100644 index 00000000..f21f62ca --- /dev/null +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-undef": "error" + } +} diff --git a/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/invalid/with-defaults.vue b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/invalid/with-defaults.vue new file mode 100644 index 00000000..7496058b --- /dev/null +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/invalid/with-defaults.vue @@ -0,0 +1,13 @@ + diff --git a/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/valid/with-defaults.vue b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/valid/with-defaults.vue new file mode 100644 index 00000000..6e2bd254 --- /dev/null +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/valid/with-defaults.vue @@ -0,0 +1,11 @@ + diff --git a/test/fixtures/integrations/script-setup-with-typescript-eslint/output.json b/test/fixtures/integrations/script-setup-with-typescript-eslint/output.json new file mode 100644 index 00000000..1a7e639e --- /dev/null +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/output.json @@ -0,0 +1,17 @@ +[ + { + "filePath": "/no-undef/invalid/with-defaults.vue", + "messages": [ + { + "ruleId": "no-undef", + "line": 8, + "message": "'withDefaults' is not defined." + }, + { + "ruleId": "no-undef", + "line": 8, + "message": "'defineProps' is not defined." + } + ] + } +] diff --git a/test/fixtures/integrations/script-setup-with-typescript-eslint/package.json b/test/fixtures/integrations/script-setup-with-typescript-eslint/package.json new file mode 100644 index 00000000..c158dd6f --- /dev/null +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/package.json @@ -0,0 +1,7 @@ +{ + "devDependencies": { + "eslint": "^8.8.0", + "@typescript-eslint/parser": "^5.10.2", + "@typescript-eslint/eslint-plugin": "^5.10.2" + } +} diff --git a/test/fixtures/integrations/script-setup/.eslintrc.json b/test/fixtures/integrations/script-setup/.eslintrc.json new file mode 100644 index 00000000..7815413c --- /dev/null +++ b/test/fixtures/integrations/script-setup/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "root": true, + "parser": "../../../../src/index.ts", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "env": { + "browser": true + } +} diff --git a/test/fixtures/integrations/script-setup/.npmrc b/test/fixtures/integrations/script-setup/.npmrc new file mode 100644 index 00000000..c1ca392f --- /dev/null +++ b/test/fixtures/integrations/script-setup/.npmrc @@ -0,0 +1 @@ +package-lock = false diff --git a/test/fixtures/integrations/script-setup/no-undef/.eslintrc.json b/test/fixtures/integrations/script-setup/no-undef/.eslintrc.json new file mode 100644 index 00000000..f21f62ca --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-undef": "error" + } +} diff --git a/test/fixtures/integrations/script-setup/no-undef/invalid/define-expose.vue b/test/fixtures/integrations/script-setup/no-undef/invalid/define-expose.vue new file mode 100644 index 00000000..aa6d6193 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/invalid/define-expose.vue @@ -0,0 +1,13 @@ + diff --git a/test/fixtures/integrations/script-setup/no-undef/invalid/define-props-and-emits.vue b/test/fixtures/integrations/script-setup/no-undef/invalid/define-props-and-emits.vue new file mode 100644 index 00000000..c94cc8dc --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/invalid/define-props-and-emits.vue @@ -0,0 +1,9 @@ + diff --git a/test/fixtures/integrations/script-setup/no-undef/valid/define-expose.vue b/test/fixtures/integrations/script-setup/no-undef/valid/define-expose.vue new file mode 100644 index 00000000..9a34c335 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/valid/define-expose.vue @@ -0,0 +1,11 @@ + diff --git a/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits-with-import.vue b/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits-with-import.vue new file mode 100644 index 00000000..526aa2a7 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits-with-import.vue @@ -0,0 +1,9 @@ + diff --git a/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits.vue b/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits.vue new file mode 100644 index 00000000..d436665f --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits.vue @@ -0,0 +1,8 @@ + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/.eslintrc.json b/test/fixtures/integrations/script-setup/no-unused-vars/.eslintrc.json new file mode 100644 index 00000000..da817b4c --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-unused-vars": "error" + } +} diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/component-names.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/component-names.vue new file mode 100644 index 00000000..63792e58 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/component-names.vue @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/css-v-bind.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/css-v-bind.vue new file mode 100644 index 00000000..43f25387 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/css-v-bind.vue @@ -0,0 +1,12 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/invalid-scope.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/invalid-scope.vue new file mode 100644 index 00000000..2943f78c --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/invalid-scope.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/sample.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/sample.vue new file mode 100644 index 00000000..92024d49 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/sample.vue @@ -0,0 +1,22 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/with-v-for.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/with-v-for.vue new file mode 100644 index 00000000..213f418b --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/with-v-for.vue @@ -0,0 +1,8 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/invalid/without-script-setup.vue b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/without-script-setup.vue new file mode 100644 index 00000000..bb67fbb1 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/invalid/without-script-setup.vue @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-is.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-is.vue new file mode 100644 index 00000000..a08343d2 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-is.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names1.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names1.vue new file mode 100644 index 00000000..26196fd1 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names1.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names2.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names2.vue new file mode 100644 index 00000000..a75da9e9 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names2.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/css-v-bind.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/css-v-bind.vue new file mode 100644 index 00000000..822b035c --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/css-v-bind.vue @@ -0,0 +1,11 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/directive.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/directive.vue new file mode 100644 index 00000000..bff5a052 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/directive.vue @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/kebab-case-component.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/kebab-case-component.vue new file mode 100644 index 00000000..b4d26cd4 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/kebab-case-component.vue @@ -0,0 +1,10 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/mustash.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/mustash.vue new file mode 100644 index 00000000..c7c27a17 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/mustash.vue @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/ns-component.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/ns-component.vue new file mode 100644 index 00000000..84fcdcac --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/ns-component.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/ref.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/ref.vue new file mode 100644 index 00000000..b2a58120 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/ref.vue @@ -0,0 +1,8 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/sample.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/sample.vue new file mode 100644 index 00000000..7c5b2f53 --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/sample.vue @@ -0,0 +1,16 @@ + + + diff --git a/test/fixtures/integrations/script-setup/no-unused-vars/valid/top-level-await.vue b/test/fixtures/integrations/script-setup/no-unused-vars/valid/top-level-await.vue new file mode 100644 index 00000000..be6f803a --- /dev/null +++ b/test/fixtures/integrations/script-setup/no-unused-vars/valid/top-level-await.vue @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/integrations/script-setup/output.json b/test/fixtures/integrations/script-setup/output.json new file mode 100644 index 00000000..c8c96350 --- /dev/null +++ b/test/fixtures/integrations/script-setup/output.json @@ -0,0 +1,92 @@ +[ + { + "filePath": "/no-undef/invalid/define-expose.vue", + "messages": [ + { + "ruleId": "no-undef", + "line": 8, + "message": "'defineExpose' is not defined." + } + ] + }, + { + "filePath": "/no-undef/invalid/define-props-and-emits.vue", + "messages": [ + { + "ruleId": "no-undef", + "line": 3, + "message": "'defineProps' is not defined." + }, + { + "ruleId": "no-undef", + "line": 7, + "message": "'defineEmits' is not defined." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/component-names.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 2, + "message": "'camelCase' is defined but never used." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/css-v-bind.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 2, + "message": "'color' is assigned a value but never used." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/invalid-scope.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 3, + "message": "'msg' is assigned a value but never used." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/sample.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 4, + "message": "'Bar' is defined but never used." + }, + { + "ruleId": "no-unused-vars", + "line": 17, + "message": "'baz' is assigned a value but never used." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/with-v-for.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 2, + "message": "'i' is assigned a value but never used." + } + ] + }, + { + "filePath": "/no-unused-vars/invalid/without-script-setup.vue", + "messages": [ + { + "ruleId": "no-unused-vars", + "line": 2, + "message": "'msg' is assigned a value but never used." + } + ] + } +] diff --git a/test/fixtures/integrations/script-setup/package.json b/test/fixtures/integrations/script-setup/package.json new file mode 100644 index 00000000..75604bef --- /dev/null +++ b/test/fixtures/integrations/script-setup/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "eslint": "^8.8.0" + } +} diff --git a/test/integrations.js b/test/integrations.js new file mode 100644 index 00000000..1f230e95 --- /dev/null +++ b/test/integrations.js @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert") +const path = require("path") +const fs = require("fs-extra") +const cp = require("child_process") +const semver = require("semver") +const eslintCompat = require("./lib/eslint-compat") + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const FIXTURE_DIR = path.join(__dirname, "fixtures/integrations") + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("Integration tests", () => { + if (!semver.gte(process.version, "14.0.0")) { + return + } + for (const target of fs.readdirSync(FIXTURE_DIR)) { + it(target, async () => { + let ESLint = eslintCompat(require("eslint")).ESLint + if (fs.existsSync(path.join(FIXTURE_DIR, target, "package.json"))) { + const originalCwd = process.cwd() + try { + process.chdir(path.join(FIXTURE_DIR, target)) + cp.execSync("npm i", { stdio: "inherit" }) + ESLint = eslintCompat( + require(path.join( + FIXTURE_DIR, + target, + "node_modules/eslint", + )), + ).ESLint + } finally { + process.chdir(originalCwd) + } + } + const cwd = path.join(FIXTURE_DIR, target) + const cli = new ESLint({ + cwd, + }) + const report = await cli.lintFiles(["**/*.vue"]) + + const outputPath = path.join(FIXTURE_DIR, target, `output.json`) + const expected = JSON.parse(fs.readFileSync(outputPath, "utf8")) + try { + assert.deepStrictEqual( + normalizeReport(report, { withoutMessage: true }), + normalizeReport(expected, { + withoutMessage: true, + }), + ) + } catch (e) { + const actualPath = path.join( + FIXTURE_DIR, + target, + `_actual.json`, + ) + fs.writeFileSync( + actualPath, + JSON.stringify(normalizeReport(report), null, 4), + "utf8", + ) + throw e + } + + function normalizeReport(report, option = {}) { + return report + .filter((res) => res.messages.length) + .map((res) => { + return { + filePath: res.filePath + .replace(cwd, "") + .replace(/\\/gu, "/"), + messages: res.messages.map((msg) => { + return { + ruleId: msg.ruleId, + line: msg.line, + ...(option.withoutMessage + ? {} + : { message: msg.message }), + } + }), + } + }) + .sort((a, b) => + a.filePath < b.filePath + ? -1 + : a.filePath < b.filePath + ? 1 + : 0, + ) + } + }) + } +}) diff --git a/typings/eslint-scope/index.d.ts b/typings/eslint-scope/index.d.ts index 61228932..c3ae32b5 100644 --- a/typings/eslint-scope/index.d.ts +++ b/typings/eslint-scope/index.d.ts @@ -43,13 +43,13 @@ export interface Scope { variableScope: Scope } -export interface Variable { - defs: VariableDefinition[] - identifiers: estree.Identifier[] - name: string - references: Reference[] - scope: Scope - stack: boolean +export class Variable { + public defs: VariableDefinition[] + public identifiers: estree.Identifier[] + public name: string + public references: Reference[] + public scope: Scope + public stack: boolean } export interface VariableDefinition { @@ -59,20 +59,24 @@ export interface VariableDefinition { parent?: estree.Node } -export interface Reference { - from: Scope - identifier: estree.Identifier - partial: boolean - resolved: Variable | null - tainted: boolean - writeExpr: estree.Expression +export class Reference { + public from: Scope + public identifier: estree.Identifier + public partial: boolean + public resolved: Variable | null + public tainted: boolean + public writeExpr: estree.Expression + + public isRead(): boolean + public isReadOnly(): boolean + public isReadWrite(): boolean + public isStatic(): boolean + public isWrite(): boolean + public isWriteOnly(): boolean - isRead(): boolean - isReadOnly(): boolean - isReadWrite(): boolean - isStatic(): boolean - isWrite(): boolean - isWriteOnly(): boolean + // For typescript-eslint + public isTypeReference: boolean + public isValueReference: boolean } export declare const analyze: ( From 9448a785020d9635ab80eaa70b952206183d8638 Mon Sep 17 00:00:00 2001 From: rash Date: Thu, 14 Apr 2022 08:16:05 +0200 Subject: [PATCH 2/6] Custom template tokenizers (#148) * add support for custom template tokenizers * fix types * Make existing tests pass * merge tokens into existing tokenizer * add ast test * Write documentation for custom template tokenizers * forward tokenizer controls to custom tokenizer * document attributes used to control tokenizer * refactor template text tokenizing into separate method. * fix mock tokenizer token ranges * guard against empty text nodes * don't parse mustaches when template lang isn't html * test if tokenizer gets unprocessed mustaches * don't call the custom tokinzer on root text nodes if we are already processing a custom template lang * don't have empty tokens in custom tokenizer * add disclaimer for templateTokenizer option * prevent nested template parsing by checking if template is top level instead of maintaining a flag --- README.md | 23 + ...implementing-custom-template-tokenizers.md | 70 ++ src/common/parser-options.ts | 2 + src/html/parser.ts | 68 +- src/index.ts | 3 +- test/ast.js | 10 + .../ast/custom-template-tokenizer/ast.json | 884 ++++++++++++++++++ .../custom-tokenizer.js | 153 +++ .../parser-options.json | 5 + .../ast/custom-template-tokenizer/source.vue | 3 + .../token-ranges.json | 25 + .../ast/custom-template-tokenizer/tree.json | 107 +++ 12 files changed, 1350 insertions(+), 3 deletions(-) create mode 100644 docs/implementing-custom-template-tokenizers.md create mode 100644 test/fixtures/ast/custom-template-tokenizer/ast.json create mode 100644 test/fixtures/ast/custom-template-tokenizer/custom-tokenizer.js create mode 100644 test/fixtures/ast/custom-template-tokenizer/parser-options.json create mode 100644 test/fixtures/ast/custom-template-tokenizer/source.vue create mode 100644 test/fixtures/ast/custom-template-tokenizer/token-ranges.json create mode 100644 test/fixtures/ast/custom-template-tokenizer/tree.json diff --git a/README.md b/README.md index 62f128eb..9ad0f7e1 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,29 @@ If set to `true`, to parse expressions in `v-bind` CSS functions inside `