From a5254e0329d1f6acf40ed1345a88c1dc2cb4aba7 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Sat, 5 Feb 2022 18:31:17 +0900 Subject: [PATCH 1/7] Add support for scope analysis of ` + + diff --git a/test/fixtures/integrations/script-setup/.eslintrc.json b/test/fixtures/integrations/script-setup/.eslintrc.json new file mode 100644 index 00000000..e08039ec --- /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" + }, + "rules": { + "no-unused-vars": "error" + } +} 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-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..3de694fa --- /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..d6e2dc47 --- /dev/null +++ b/test/fixtures/integrations/script-setup/output.json @@ -0,0 +1,67 @@ +[ + { + "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..6f9d1aac --- /dev/null +++ b/test/integrations.js @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// 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, ""), + 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..c818bfbf 100644 --- a/typings/eslint-scope/index.d.ts +++ b/typings/eslint-scope/index.d.ts @@ -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 - isRead(): boolean - isReadOnly(): boolean - isReadWrite(): boolean - isStatic(): boolean - isWrite(): boolean - isWriteOnly(): boolean + public isRead(): boolean + public isReadOnly(): boolean + public isReadWrite(): boolean + public isStatic(): boolean + public isWrite(): boolean + public isWriteOnly(): boolean + + // For typescript-eslint + public isTypeReference: boolean + public isValueReference: boolean } export declare const analyze: ( From 87fed641db6666af0f65b2cc07b73f6bdaf4b9f7 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Mon, 7 Feb 2022 14:41:27 +0900 Subject: [PATCH 3/7] fix for win test --- test/integrations.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integrations.js b/test/integrations.js index 6f9d1aac..1f230e95 100644 --- a/test/integrations.js +++ b/test/integrations.js @@ -76,7 +76,9 @@ describe("Integration tests", () => { .filter((res) => res.messages.length) .map((res) => { return { - filePath: res.filePath.replace(cwd, ""), + filePath: res.filePath + .replace(cwd, "") + .replace(/\\/gu, "/"), messages: res.messages.map((msg) => { return { ruleId: msg.ruleId, From fef3413544897e092dfa412dccf5d9a37ebc0cf4 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Mon, 7 Feb 2022 16:06:40 +0900 Subject: [PATCH 4/7] Support for Compiler Macro --- .../script-setup-scope-analyzer.ts | 155 ++++++++++++++---- .../.eslintrc.json | 5 +- .../consistent-type-imports/.eslintrc.json | 5 + .../valid.vue} | 0 .../no-undef/.eslintrc.json | 5 + .../no-undef/invalid/with-defaults.vue | 13 ++ .../no-undef/valid/with-defaults.vue | 11 ++ .../output.json | 18 +- .../integrations/script-setup/.eslintrc.json | 4 +- .../script-setup/no-undef/.eslintrc.json | 5 + .../no-undef/invalid/define-expose.vue | 13 ++ .../invalid/define-props-and-emits.vue | 9 + .../no-undef/valid/define-expose.vue | 11 ++ .../define-props-and-emits-with-import.vue | 9 + .../no-undef/valid/define-props-and-emits.vue | 8 + .../no-unused-vars/.eslintrc.json | 5 + .../invalid}/component-names.vue | 0 .../invalid}/css-v-bind.vue | 0 .../invalid}/invalid-scope.vue | 2 +- .../invalid}/sample.vue | 0 .../invalid}/with-v-for.vue | 0 .../invalid}/without-script-setup.vue | 0 .../valid}/component-is.vue | 0 .../valid}/component-names1.vue | 0 .../valid}/component-names2.vue | 0 .../valid}/css-v-bind.vue | 0 .../valid}/directive.vue | 0 .../valid}/kebab-case-component.vue | 0 .../valid}/mustash.vue | 0 .../valid}/ns-component.vue | 0 .../valid}/ref.vue | 0 .../valid}/sample.vue | 0 .../valid}/top-level-await.vue | 0 .../integrations/script-setup/output.json | 37 ++++- typings/eslint-scope/index.d.ts | 14 +- 35 files changed, 276 insertions(+), 53 deletions(-) create mode 100644 test/fixtures/integrations/script-setup-with-typescript-eslint/consistent-type-imports/.eslintrc.json rename test/fixtures/integrations/script-setup-with-typescript-eslint/{valid/consistent-type-imports.vue => consistent-type-imports/valid.vue} (100%) create mode 100644 test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/.eslintrc.json create mode 100644 test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/invalid/with-defaults.vue create mode 100644 test/fixtures/integrations/script-setup-with-typescript-eslint/no-undef/valid/with-defaults.vue create mode 100644 test/fixtures/integrations/script-setup/no-undef/.eslintrc.json create mode 100644 test/fixtures/integrations/script-setup/no-undef/invalid/define-expose.vue create mode 100644 test/fixtures/integrations/script-setup/no-undef/invalid/define-props-and-emits.vue create mode 100644 test/fixtures/integrations/script-setup/no-undef/valid/define-expose.vue create mode 100644 test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits-with-import.vue create mode 100644 test/fixtures/integrations/script-setup/no-undef/valid/define-props-and-emits.vue create mode 100644 test/fixtures/integrations/script-setup/no-unused-vars/.eslintrc.json rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/component-names.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/css-v-bind.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/invalid-scope.vue (87%) rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/sample.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/with-v-for.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-invalid => no-unused-vars/invalid}/without-script-setup.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/component-is.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/component-names1.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/component-names2.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/css-v-bind.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/directive.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/kebab-case-component.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/mustash.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/ns-component.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/ref.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/sample.vue (100%) rename test/fixtures/integrations/script-setup/{no-unused-vars-valid => no-unused-vars/valid}/top-level-await.vue (100%) diff --git a/src/script-setup/script-setup-scope-analyzer.ts b/src/script-setup/script-setup-scope-analyzer.ts index 7d274148..fae35af7 100644 --- a/src/script-setup/script-setup-scope-analyzer.ts +++ b/src/script-setup/script-setup-scope-analyzer.ts @@ -1,4 +1,4 @@ -import * as escopeTypes from "eslint-scope" +import type * as escopeTypes from "eslint-scope" import type { ParserOptions } from "../common/parser-options" import type { VAttribute, @@ -9,6 +9,7 @@ import type { VText, } from "../ast" import { traverseNodes } from "../ast" +import { getEslintScope } from "../common/eslint-scope" const BUILTIN_COMPONENTS = new Set([ "template", @@ -76,40 +77,86 @@ const SVG_TAGS = const NATIVE_TAGS = new Set([...HTML_TAGS.split(","), ...SVG_TAGS.split(",")]) +const COMPILER_MACROS_AT_ROOT = new Set([ + "defineProps", + "defineEmits", + "defineExpose", + "withDefaults", +]) + +/** + * `casing.camelCase()` converts the beginning to lowercase, + * but does not convert the case of the beginning character when converting with Vue3. + * @see https://github.com/vuejs/vue-next/blob/48de8a42b7fed7a03f7f1ff5d53d6a704252cafe/packages/shared/src/index.ts#L109 + */ +function camelize(str: string) { + return str.replace(/-(\w)/gu, (_, c) => (c ? c.toUpperCase() : "")) +} + +function capitalize(str: string) { + return str[0].toUpperCase() + str.slice(1) +} + +/** + * Analyze ` 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 index fe51488c..1a7e639e 100644 --- a/test/fixtures/integrations/script-setup-with-typescript-eslint/output.json +++ b/test/fixtures/integrations/script-setup-with-typescript-eslint/output.json @@ -1 +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/.eslintrc.json b/test/fixtures/integrations/script-setup/.eslintrc.json index e08039ec..7815413c 100644 --- a/test/fixtures/integrations/script-setup/.eslintrc.json +++ b/test/fixtures/integrations/script-setup/.eslintrc.json @@ -5,7 +5,7 @@ "ecmaVersion": 2022, "sourceType": "module" }, - "rules": { - "no-unused-vars": "error" + "env": { + "browser": true } } 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/component-names.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/component-names.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/css-v-bind.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/css-v-bind.vue 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 similarity index 87% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/invalid-scope.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/invalid-scope.vue index 3de694fa..2943f78c 100644 --- 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 @@ -1,5 +1,5 @@ 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/sample.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/sample.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/with-v-for.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/with-v-for.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-invalid/without-script-setup.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/invalid/without-script-setup.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/component-is.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/component-is.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/component-names1.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names1.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/component-names2.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/component-names2.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/css-v-bind.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/css-v-bind.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/directive.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/directive.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/kebab-case-component.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/kebab-case-component.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/mustash.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/mustash.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/ns-component.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/ns-component.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/ref.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/ref.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/sample.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/sample.vue 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 similarity index 100% rename from test/fixtures/integrations/script-setup/no-unused-vars-valid/top-level-await.vue rename to test/fixtures/integrations/script-setup/no-unused-vars/valid/top-level-await.vue diff --git a/test/fixtures/integrations/script-setup/output.json b/test/fixtures/integrations/script-setup/output.json index d6e2dc47..c8c96350 100644 --- a/test/fixtures/integrations/script-setup/output.json +++ b/test/fixtures/integrations/script-setup/output.json @@ -1,6 +1,31 @@ [ { - "filePath": "/no-unused-vars-invalid/component-names.vue", + "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", @@ -10,7 +35,7 @@ ] }, { - "filePath": "/no-unused-vars-invalid/css-v-bind.vue", + "filePath": "/no-unused-vars/invalid/css-v-bind.vue", "messages": [ { "ruleId": "no-unused-vars", @@ -20,7 +45,7 @@ ] }, { - "filePath": "/no-unused-vars-invalid/invalid-scope.vue", + "filePath": "/no-unused-vars/invalid/invalid-scope.vue", "messages": [ { "ruleId": "no-unused-vars", @@ -30,7 +55,7 @@ ] }, { - "filePath": "/no-unused-vars-invalid/sample.vue", + "filePath": "/no-unused-vars/invalid/sample.vue", "messages": [ { "ruleId": "no-unused-vars", @@ -45,7 +70,7 @@ ] }, { - "filePath": "/no-unused-vars-invalid/with-v-for.vue", + "filePath": "/no-unused-vars/invalid/with-v-for.vue", "messages": [ { "ruleId": "no-unused-vars", @@ -55,7 +80,7 @@ ] }, { - "filePath": "/no-unused-vars-invalid/without-script-setup.vue", + "filePath": "/no-unused-vars/invalid/without-script-setup.vue", "messages": [ { "ruleId": "no-unused-vars", diff --git a/typings/eslint-scope/index.d.ts b/typings/eslint-scope/index.d.ts index c818bfbf..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 { From 943bc322bbce59e77c3c63604bc3212d8ed5a387 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Mon, 7 Feb 2022 16:22:38 +0900 Subject: [PATCH 5/7] rename --- src/index.ts | 2 +- .../{script-setup-scope-analyzer.ts => scope-analyzer.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/script-setup/{script-setup-scope-analyzer.ts => scope-analyzer.ts} (100%) diff --git a/src/index.ts b/src/index.ts index 3f820078..9bf1eb43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import { } from "./common/ast-utils" import { parseStyleElements } from "./style" import { analyzeScope } from "./script/scope-analyzer" -import { analyzeScriptSetupScope } from "./script-setup/script-setup-scope-analyzer" +import { analyzeScriptSetupScope } from "./script-setup/scope-analyzer" const STARTS_WITH_LT = /^\s* Date: Tue, 8 Feb 2022 09:05:07 +0900 Subject: [PATCH 6/7] refactor --- src/script-setup/scope-analyzer.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/script-setup/scope-analyzer.ts b/src/script-setup/scope-analyzer.ts index fae35af7..b32f2e1b 100644 --- a/src/script-setup/scope-analyzer.ts +++ b/src/script-setup/scope-analyzer.ts @@ -261,12 +261,12 @@ function analyzeUsedInTemplateVariables( } // Analyze CSS v-bind() - for (const style of df.children - .filter(isVElement) - .filter((e) => e.name === "style")) { - for (const node of style.children) { - if (node.type === "VExpressionContainer") { - processVExpressionContainer(node) + for (const child of df.children) { + if (child.type === "VElement" && child.name === "style") { + for (const node of child.children) { + if (node.type === "VExpressionContainer") { + processVExpressionContainer(node) + } } } } @@ -284,6 +284,7 @@ function analyzeCompilerMacrosVariables( return } const usedCompilerMacros = new Map() + const newThrough: escopeTypes.Reference[] = [] for (const reference of globalScope.through) { if (COMPILER_MACROS_AT_ROOT.has(reference.identifier.name)) { if ( @@ -298,8 +299,11 @@ function analyzeCompilerMacrosVariables( reference, ]) } + // This reference is removed from `Scope#through`. + continue } } + newThrough.push(reference) } for (const [name, references] of usedCompilerMacros) { @@ -315,12 +319,5 @@ function analyzeCompilerMacrosVariables( } } - globalScope.through = globalScope.through.filter((reference) => { - const list = usedCompilerMacros.get(reference.identifier.name) - if (list && list.includes(reference)) { - // This reference is removed from `Scope#through`. - return false - } - return true - }) + globalScope.through = newThrough } From 1c5d5ce0210ad47b2f4dd7c5071482dde74b088b Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 8 Feb 2022 09:11:06 +0900 Subject: [PATCH 7/7] refactor --- src/script-setup/scope-analyzer.ts | 51 +++++++++++------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/script-setup/scope-analyzer.ts b/src/script-setup/scope-analyzer.ts index b32f2e1b..0d2518e9 100644 --- a/src/script-setup/scope-analyzer.ts +++ b/src/script-setup/scope-analyzer.ts @@ -6,7 +6,6 @@ import type { VDocumentFragment, VElement, VExpressionContainer, - VText, } from "../ast" import { traverseNodes } from "../ast" import { getEslintScope } from "../common/eslint-scope" @@ -117,15 +116,6 @@ export function analyzeScriptSetupScope( analyzeCompilerMacrosVariables(scopeManager) } -/** - * Checks whether the given node is VElement. - */ -function isVElement( - node: VElement | VExpressionContainer | VText, -): node is VElement { - return node.type === "VElement" -} - function extractVariables(scopeManager: escopeTypes.ScopeManager) { const scriptVariables = new Map() const globalScope = scopeManager.globalScope @@ -283,7 +273,24 @@ function analyzeCompilerMacrosVariables( if (!globalScope) { return } - const usedCompilerMacros = new Map() + const compilerMacroVariables = new Map() + + function addCompilerMacroVariable(reference: escopeTypes.Reference) { + const name = reference.identifier.name + let variable = compilerMacroVariables.get(name) + if (!variable) { + variable = new (getEslintScope().Variable)() + variable.name = name + variable.scope = globalScope + globalScope.variables.push(variable) + globalScope.set.set(name, variable) + compilerMacroVariables.set(name, variable) + } + // Links the variable and the reference. + reference.resolved = variable + variable.references.push(reference) + } + const newThrough: escopeTypes.Reference[] = [] for (const reference of globalScope.through) { if (COMPILER_MACROS_AT_ROOT.has(reference.identifier.name)) { @@ -291,14 +298,7 @@ function analyzeCompilerMacrosVariables( reference.from.type === "global" || reference.from.type === "module" ) { - const list = usedCompilerMacros.get(reference.identifier.name) - if (list) { - list.push(reference) - } else { - usedCompilerMacros.set(reference.identifier.name, [ - reference, - ]) - } + addCompilerMacroVariable(reference) // This reference is removed from `Scope#through`. continue } @@ -306,18 +306,5 @@ function analyzeCompilerMacrosVariables( newThrough.push(reference) } - for (const [name, references] of usedCompilerMacros) { - const variable = new (getEslintScope().Variable)() - variable.name = name - variable.scope = globalScope - globalScope.variables.push(variable) - globalScope.set.set(name, variable) - for (const reference of references) { - // Links the variable and the reference. - reference.resolved = variable - variable.references.push(reference) - } - } - globalScope.through = newThrough }