diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1d1e9de..6bb0a761 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -55,10 +55,8 @@ jobs: run: npm install -f - name: Install ESLint v${{ matrix.eslint }} run: node scripts/ci-install-eslint ${{ matrix.eslint }} - - name: Build - run: npm run -s build - name: Test - run: npm run -s test:mocha + run: npm run -s test test-for-old-eslint: name: Test strategy: @@ -82,4 +80,4 @@ jobs: - name: Install ESLint v${{ matrix.eslint }} run: node scripts/ci-install-eslint ${{ matrix.eslint }} - name: Test - run: npm run -s test:debug + run: npm run -s test diff --git a/.gitignore b/.gitignore index 8bf60fc5..387f51ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/.nyc_output /.temp /coverage node_modules diff --git a/eslint.config.mjs b/eslint.config.mjs index 5e81622f..a46acd53 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,7 +22,6 @@ const compat = new FlatCompat({ export default [ { ignores: [ - ".nyc_output", ".temp", "coverage", "**/node_modules", @@ -64,7 +63,7 @@ export default [ }, loggerFn: false, - project: "tsconfig.json", + project: ["tsconfig.json", "tsconfig.test.json"], }, }, diff --git a/package.json b/package.json index 63586655..b325e750 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,12 @@ "@eslint/js": "^9.19.0", "@types/debug": "^4.1.7", "@types/estree": "^1.0.0", - "@types/mocha": "^9.0.0", "@types/node": "^18.8.4", "@types/semver": "^7.3.12", "@typescript-eslint/eslint-plugin": "^8.22.0", "@typescript-eslint/parser": "^8.22.0", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", "chokidar": "^3.5.2", "cross-spawn": "^7.0.3", "dts-bundle": "^0.7.3", @@ -47,10 +48,7 @@ "eslint-plugin-unicorn": "^57.0.0", "fs-extra": "^10.0.0", "jsonc-eslint-parser": "^2.0.3", - "mocha": "^9.1.3", "npm-run-all": "^4.1.5", - "nyc": "^15.1.0", - "opener": "^1.5.2", "prettier": "^3.4.2", "rimraf": "^3.0.2", "rollup": "^2.60.0", @@ -59,20 +57,19 @@ "rollup-plugin-sourcemaps": "^0.6.3", "ts-node": "^10.9.2", "typescript": "~5.7.3", + "vite": "^6.3.5", + "vitest": "^3.2.4", "wait-on": "^6.0.0", "warun": "^1.0.0" }, "scripts": { "prebuild": "npm run -s clean", "build": "tsc --module es2015 && rollup -c -o index.js && dts-bundle --name vue-eslint-parser --main .temp/index.d.ts --out ../index.d.ts", - "clean": "rimraf .nyc_output .temp coverage index.*", - "coverage": "opener ./coverage/lcov-report/index.html", + "clean": "rimraf .temp index.*", + "coverage": "vitest --coverage --ui", "lint": "eslint src test package.json", - "pretest": "run-s build lint", - "test": "npm run -s test:mocha", - "test:mocha": "mocha --require ts-node/register \"test/*.js\" --reporter dot --timeout 60000", - "test:cover": "nyc mocha \"test/*.js\" --reporter dot --timeout 60000", - "test:debug": "mocha --require ts-node/register/transpile-only \"test/*.js\" --reporter dot --timeout 60000", + "test": "vitest", + "test:cover": "vitest --coverage", "update-fixtures": "ts-node --transpile-only scripts/update-fixtures-ast.js && ts-node --transpile-only scripts/update-fixtures-document-fragment.js", "preversion": "npm test", "version": "npm run -s build", @@ -81,9 +78,7 @@ "watch": "run-p watch:*", "watch:tsc": "tsc --module es2015 --watch", "watch:rollup": "wait-on .temp/index.js && rollup -c -o index.js --watch", - "watch:test": "wait-on index.js && warun index.js \"test/*.js\" \"test/fixtures/ast/*/*.json\" \"test/fixtures/*\" --debounce 1000 --no-initial -- nyc mocha \"test/*.js\" --reporter dot --timeout 10000", - "watch:update-ast": "wait-on index.js && warun index.js \"test/fixtures/ast/*/*.vue\" -- ts-node scripts/update-fixtures-ast.js", - "watch:coverage-report": "wait-on coverage/lcov-report/index.html && opener coverage/lcov-report/index.html" + "watch:update-ast": "wait-on index.js && warun index.js \"test/fixtures/ast/*/*.vue\" -- ts-node scripts/update-fixtures-ast.js" }, "repository": { "type": "git", diff --git a/scripts/ci-install-eslint.js b/scripts/ci-install-eslint.js index c3626692..fe2f96c3 100644 --- a/scripts/ci-install-eslint.js +++ b/scripts/ci-install-eslint.js @@ -30,6 +30,8 @@ function sh(command) { // Install ESLint of the requested version await sh(`npm install eslint@${requestedVersionSpec} -f`) + if (Number(requestedVersion) < 9) + await sh(`npm install @types/eslint -D -f`) // Install ESLint submodule of the requested version // const installedVersion = require("eslint/package.json").version diff --git a/src/ast/nodes.ts b/src/ast/nodes.ts index d652e4e0..c24e3727 100644 --- a/src/ast/nodes.ts +++ b/src/ast/nodes.ts @@ -8,6 +8,7 @@ import type { ParseError } from "./errors" import type { HasLocation } from "./locations" import type { Token } from "./tokens" import type { TSESTree } from "@typescript-eslint/utils" +import type { ParserServices } from "../parser-services" //------------------------------------------------------------------------------ // Common @@ -70,7 +71,7 @@ export type ESLintNode = */ export interface ESLintExtendedProgram { ast: ESLintProgram - services?: {} + services?: ParserServices visitorKeys?: { [type: string]: string[] } scopeManager?: ScopeManager } diff --git a/src/index.ts b/src/index.ts index 13cb5300..45d43d64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,7 +84,7 @@ export function parseForESLint( * @param options The parser options. * @returns The parsing result. */ -export function parse(code: string, options: any): AST.ESLintProgram { +export function parse(code: string, options?: any): AST.ESLintProgram { return parseForESLint(code, options).ast } diff --git a/src/parser-services.ts b/src/parser-services.ts index 3b830c41..b84faa43 100644 --- a/src/parser-services.ts +++ b/src/parser-services.ts @@ -83,7 +83,7 @@ export interface ParserServices { | ((lang: string | null, customBlock: VElement) => boolean) create: CustomBlockVisitorFactory }, - scriptVisitor: { [key: string]: (...args: any) => void }, + scriptVisitor?: { [key: string]: (...args: any) => void }, ): { [key: string]: (...args: any) => void } /** @@ -262,7 +262,7 @@ export function define( | ((lang: string | null, customBlock: VElement) => boolean) create: CustomBlockVisitorFactory }, - scriptVisitor: { [key: string]: (...args: any) => void }, + scriptVisitor?: { [key: string]: (...args: any) => void }, ): { [key: string]: (...args: any) => void } { if (scriptVisitor == null) { scriptVisitor = {} //eslint-disable-line no-param-reassign diff --git a/test/ast.js b/test/ast.test.ts similarity index 74% rename from test/ast.js rename to test/ast.test.ts index 0331042e..b402aa91 100644 --- a/test/ast.js +++ b/test/ast.test.ts @@ -3,27 +3,29 @@ * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ -"use strict" //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert") -const fs = require("fs") -const path = require("path") -const parser = require("../src") -const eslint = require("eslint") -const semver = require("semver") -const { scopeToJSON, analyze, replacer, getAllTokens } = require("./test-utils") +import type { Rule } from "eslint" +import type { Node } from "../src/ast" +import type { ParserOptions } from "../src/common/parser-options" +import fs from "node:fs" +import path from "node:path" +import { describe, it, assert } from "vitest" +import { Linter } from "eslint" +import semver from "semver" +import * as parser from "../src" +import { scopeToJSON, analyze, replacer, getAllTokens } from "./test-utils" //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const Linter = eslint.Linter +// eslint-disable-next-line no-undef const ROOT = path.join(__dirname, "fixtures/ast") const TARGETS = fs.readdirSync(ROOT) -const PARSER_OPTIONS = { +const PARSER_OPTIONS: ParserOptions = { comment: true, ecmaVersion: "latest", sourceType: "module", @@ -33,22 +35,28 @@ const PARSER_OPTIONS = { eslintScopeManager: true, } +type TreeNode = { + type?: string + text?: string + children: TreeNode[] +} + /** * Create simple tree. - * @param {string} source The source code. - * @param {object} parserOptions The parser options. - * @returns {object} Simple tree. + * @param source The source code. + * @param parserOptions The parser options. + * @returns Simple tree. */ -function getTree(source, parserOptions) { +function getTree(source: string, parserOptions: any) { const linter = new Linter({ configType: "flat" }) - const stack = [] - const root = { children: [] } - let current = root + const stack: TreeNode[] = [] + const root: TreeNode = { children: [] } + let current: TreeNode = root - const maketree = { + const maketree: Rule.RuleModule = { create: (ruleContext) => ruleContext.sourceCode.parserServices.defineTemplateBodyVisitor({ - "*"(node) { + "*"(node: Node) { stack.push(current) current.children.push( (current = { @@ -59,32 +67,27 @@ function getTree(source, parserOptions) { ) }, "*:exit"() { - current = stack.pop() + current = stack.pop()! }, }), } - const result = linter.verify( - source, - { - files: ["**"], - plugins: { - test: { - rules: { - maketree, - }, + const result = linter.verify(source, { + files: ["**"], + plugins: { + test: { + rules: { + maketree, }, }, - languageOptions: { - parser: parser, - ecmaVersion: parserOptions.ecmaVersion ?? "latest", - sourceType: parserOptions.sourceType ?? "module", - parserOptions: parserOptions, - }, - rules: { "test/maketree": "error" }, }, - undefined, - true, - ) + languageOptions: { + parser, + ecmaVersion: parserOptions.ecmaVersion ?? "latest", + sourceType: parserOptions.sourceType ?? "module", + parserOptions, + }, + rules: { "test/maketree": "error" }, + }) assert.deepStrictEqual(result, []) return root.children @@ -92,30 +95,29 @@ function getTree(source, parserOptions) { /** * Convert a given node to string. - * @param {Node} node The node to make string expression. - * @param {string} source The source code. - * @returns {string} The string expression of the node. + * @param node The node to make string expression. + * @param source The source code. + * @returns The string expression of the node. */ -function nodeToString(node, source) { +function nodeToString(node: Node, source: string): string { return node ? `${node.type}[${source.slice(...node.range)}]` : "undefined" } /** * Validate the parent property of every node. - * @param {string} source The source code. - * @param {object} parserOptions The parser options. - * @returns {void} + * @param source The source code. + * @param parserOptions The parser options. */ -function validateParent(source, parserOptions) { +function validateParent(source: string, parserOptions: any) { const linter = new Linter({ configType: "flat" }) - const stack = [] + const stack: Node[] = [] - const validateparent = { + const validateparent: Rule.RuleModule = { create: (ruleContext) => ruleContext.sourceCode.parserServices.defineTemplateBodyVisitor({ - "*"(node) { + "*"(node: Node) { if (stack.length >= 1) { - const parent = stack.at(-1) + const parent = stack.at(-1)! assert( node.parent === parent, `The parent of ${nodeToString( @@ -124,7 +126,7 @@ function validateParent(source, parserOptions) { )} should be ${nodeToString( parent, source, - )}, but got ${nodeToString(node.parent, source)}`, + )}, but got ${nodeToString(node.parent!, source)}`, ) } stack.push(node) @@ -134,28 +136,23 @@ function validateParent(source, parserOptions) { }, }), } - const result = linter.verify( - source, - { - files: ["**"], - plugins: { - test: { - rules: { - validateparent, - }, + const result = linter.verify(source, { + files: ["**"], + plugins: { + test: { + rules: { + validateparent, }, }, - languageOptions: { - parser, - ecmaVersion: parserOptions.ecmaVersion ?? "latest", - sourceType: parserOptions.sourceType ?? "module", - parserOptions: parserOptions, - }, - rules: { "test/validateparent": "error" }, }, - undefined, - true, - ) + languageOptions: { + parser, + ecmaVersion: parserOptions.ecmaVersion ?? "latest", + sourceType: parserOptions.sourceType ?? "module", + parserOptions, + }, + rules: { "test/validateparent": "error" }, + }) assert.deepStrictEqual(result, []) } @@ -173,8 +170,13 @@ describe("Template AST", () => { const requirementsPath = path.join(ROOT, `${name}/requirements.json`) const servicesPath = path.join(ROOT, `${name}/services.json`) const source = fs.readFileSync(sourcePath, "utf8") - const parserOptions = optionsPath ? require(optionsPath) : {} - const requirements = fs.existsSync(requirementsPath) + + const parserOptions: ParserOptions = optionsPath + ? require(optionsPath) // eslint-disable-line @typescript-eslint/no-require-imports + : {} + const requirements: Record = fs.existsSync( + requirementsPath, + ) ? JSON.parse(fs.readFileSync(requirementsPath, "utf8")) : {} const services = fs.existsSync(servicesPath) @@ -185,12 +187,13 @@ describe("Template AST", () => { Object.entries(parserOptions.templateTokenizer).map( ([key, value]) => [ key, - path.resolve(__dirname, "../", value), + // eslint-disable-next-line no-undef + path.resolve(__dirname, "../", value as string), ], ), ) } - const options = { + const options: ParserOptions = { filePath: sourcePath, ...PARSER_OPTIONS, ...parserOptions, @@ -201,7 +204,7 @@ describe("Template AST", () => { const version = pkgName === "node" ? process.version - : require(`${pkgName}/package.json`).version + : require(`${pkgName}/package.json`).version // eslint-disable-line @typescript-eslint/no-require-imports return !semver.satisfies(version, pkgVersion) }) ) { @@ -252,7 +255,8 @@ describe("Template AST", () => { }) it("should have correct location.", () => { - const lines = source.match(/[^\r\n]*(?:\r?\n|$)/gu) ?? [] + const lines: string[] = + source.match(/[^\r\n]*(?:\r?\n|$)/gu) ?? [] lines.push(String.fromCodePoint(0)) for (const token of getAllTokens(actual.ast)) { const line0 = token.loc.start.line - 1 @@ -318,7 +322,7 @@ describe("Template AST", () => { if (services) { it("should have correct services.", () => { assert.deepStrictEqual( - Object.keys(actual.services).sort(), + Object.keys(actual.services!).sort(), services, ) }) diff --git a/test/crlf.js b/test/crlf.test.ts similarity index 73% rename from test/crlf.js rename to test/crlf.test.ts index d35ce260..c2ecb46d 100644 --- a/test/crlf.js +++ b/test/crlf.test.ts @@ -1,9 +1,10 @@ -const assert = require("assert") -const parser = require("../src") +import type { VElement, VText } from "../src/ast" +import { describe, it, assert } from "vitest" +import { parseForESLint } from "../src" describe("About CRLF tests", () => { it("should not contain CR in `" - const config = { + const config: eslint.Linter.Config = { languageOptions: { parser, parserOptions: { @@ -882,7 +890,7 @@ describe("Basic tests", async () => { it("should notify 1 no-undef error", () => { const code = "" - const config = { + const config: eslint.Linter.Config = { languageOptions: { parser, }, @@ -917,7 +925,7 @@ export default {} ` const result = parseForESLint(code, { sourceType: "module" }) - const comments = result.ast.comments + const comments = result.ast.comments! // Should have 2 comments assert.strictEqual(comments.length, 2) @@ -937,7 +945,7 @@ export default {} }) }) -function buildPlugins(rule) { +function buildPlugins(rule: Rule.RuleModule) { return { test: { rules: { diff --git a/test/integrations.js b/test/integrations.test.ts similarity index 67% rename from test/integrations.js rename to test/integrations.test.ts index 88e9c501..651e6616 100644 --- a/test/integrations.js +++ b/test/integrations.test.ts @@ -2,16 +2,18 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert") -const path = require("path") -const fs = require("fs-extra") -const cp = require("child_process") -const eslintCompat = require("./lib/eslint-compat") +import { assert, beforeAll, describe, it } from "vitest" +import path from "node:path" +import fs from "node:fs" +import cp from "child_process" +import eslintCompat from "./lib/eslint-compat" +import ESLintRaw from "eslint" //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ +// eslint-disable-next-line no-undef const FIXTURE_DIR = path.join(__dirname, "fixtures/integrations") //------------------------------------------------------------------------------ @@ -19,15 +21,19 @@ const FIXTURE_DIR = path.join(__dirname, "fixtures/integrations") //------------------------------------------------------------------------------ describe("Integration tests", () => { + beforeAll(async () => { + await import("ts-node/register") + }) for (const target of fs.readdirSync(FIXTURE_DIR)) { it(target, async () => { - let ESLint = eslintCompat(require("eslint")).ESLint + let ESLint = eslintCompat(ESLintRaw).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( + // eslint-disable-next-line @typescript-eslint/no-require-imports require( path.join( FIXTURE_DIR, @@ -46,7 +52,7 @@ describe("Integration tests", () => { }) const report = await cli.lintFiles(["**/*.vue"]) - const outputPath = path.join(FIXTURE_DIR, target, `output.json`) + const outputPath = path.join(FIXTURE_DIR, target, "output.json") const expected = JSON.parse(fs.readFileSync(outputPath, "utf8")) try { assert.deepStrictEqual( @@ -59,7 +65,7 @@ describe("Integration tests", () => { const actualPath = path.join( FIXTURE_DIR, target, - `_actual.json`, + "_actual.json", ) fs.writeFileSync( actualPath, @@ -69,25 +75,24 @@ describe("Integration tests", () => { throw e } - function normalizeReport(report, option = {}) { - return report + function normalizeReport( + result: ESLintRaw.ESLint.LintResult[], + option: { withoutMessage?: boolean } = {}, + ) { + return result .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 }), - } - }), - } - }) + .map((res) => ({ + filePath: res.filePath + .replace(cwd, "") + .replace(/\\/gu, "/"), + messages: res.messages.map((msg) => ({ + ruleId: msg.ruleId, + line: msg.line, + ...(option.withoutMessage + ? {} + : { message: msg.message }), + })), + })) .sort((a, b) => a.filePath < b.filePath ? -1 diff --git a/test/lib/eslint-compat.js b/test/lib/eslint-compat.ts similarity index 60% rename from test/lib/eslint-compat.js rename to test/lib/eslint-compat.ts index 9077ec55..ae96362f 100644 --- a/test/lib/eslint-compat.js +++ b/test/lib/eslint-compat.ts @@ -1,11 +1,10 @@ -"use strict" +import type { ESLint, Linter, RuleTester } from "eslint" -/** - * @typedef {import('eslint')} eslint - */ - -/** @param {eslint} eslint */ -module.exports = function compat(eslint) { +export default function compat(eslint: any): { + ESLint: typeof ESLint + RuleTester: typeof RuleTester + Linter: typeof Linter +} { return { ESLint: eslint.ESLint || getESLintClassForV6(eslint), RuleTester: eslint.RuleTester, @@ -13,15 +12,17 @@ module.exports = function compat(eslint) { } } -/** @returns {typeof eslint.ESLint} */ -function getESLintClassForV6(eslint) { +function getESLintClassForV6(eslint: any): typeof ESLint { class ESLintForV6 { + public engine + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility static get version() { return eslint.CLIEngine.version } - /** @param {eslint.ESLint.Options} options */ - constructor(options) { + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + constructor(options: any) { const { overrideConfig: { plugins, @@ -33,14 +34,13 @@ function getESLintClassForV6(eslint) { plugins: [], globals: {}, rules: {}, - }, + } as any, overrideConfigFile, fix, reportUnusedDisableDirectives, plugins: pluginsMap, ...otherOptions } = options || {} - /** @type {eslint.CLIEngine.Options} */ const newOptions = { fix: Boolean(fix), reportUnusedDisableDirectives: reportUnusedDisableDirectives @@ -64,7 +64,7 @@ function getESLintClassForV6(eslint) { } return o }, - /** @type {NonNullable} */ {}, + {} as Record, ) : undefined, ...overrideConfig, @@ -76,41 +76,37 @@ function getESLintClassForV6(eslint) { } } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - async lintText(...params) { - const result = this.engine.executeOnText( + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + async lintText( + ...params: Parameters + ): ReturnType { + const result = await this.engine.executeOnText( params[0], - params[1].filePath, + params[1]!.filePath, ) return result.results } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - async lintFiles(...params) { - const result = this.engine.executeOnFiles( + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + async lintFiles( + ...params: Parameters + ): ReturnType { + const result = await this.engine.executeOnFiles( Array.isArray(params[0]) ? params[0] : [params[0]], ) return result.results } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - static async outputFixes(...params) { - return eslint.CLIEngine.outputFixes({ + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + static async outputFixes( + ...params: Parameters + ): ReturnType { + // eslint-disable-next-line no-return-await + return await eslint.CLIEngine.outputFixes({ results: params[0], }) } } - /** @type {typeof eslint.ESLint} */ - const eslintClass = /** @type {any} */ ESLintForV6 - return eslintClass + return ESLintForV6 as any } diff --git a/test/parser-options-project.js b/test/parser-options-project.test.ts similarity index 93% rename from test/parser-options-project.js rename to test/parser-options-project.test.ts index 1fc3b12a..257f4ded 100644 --- a/test/parser-options-project.js +++ b/test/parser-options-project.test.ts @@ -1,8 +1,7 @@ -"use strict" - -const assert = require("assert") -const { parseForESLint } = require("../src") -const espree = require("espree") +import { describe, it, assert } from "vitest" +import { parseForESLint } from "../src" +import * as espree from "espree" +import type { Linter } from "eslint" describe("use `project: undefined` when parsing template script-let", () => { it("should be the project option is defined only once in Simple SFC.", () => { @@ -39,7 +38,7 @@ describe("use `project: undefined` when parsing template script-let", () => { ast: espree.parse(code, options), } }, - }, + } satisfies Linter.Parser, }, ) assert.strictEqual(projectCount, 1) @@ -83,7 +82,7 @@ describe("use `project: undefined` when parsing template script-let", () => { ast: espree.parse(code, options), } }, - }, + } satisfies Linter.Parser, }, ) assert.strictEqual(projectCount, 1) @@ -126,7 +125,7 @@ describe("use `project: undefined` when parsing template script-let", () => { ast: espree.parse(code, options), } }, - }, + } satisfies Linter.Parser, }, ) assert.strictEqual(projectCount, 1) diff --git a/test/parser-options.js b/test/parser-options.test.ts similarity index 86% rename from test/parser-options.js rename to test/parser-options.test.ts index 3c7e4e1b..37aa60a9 100644 --- a/test/parser-options.js +++ b/test/parser-options.test.ts @@ -2,18 +2,17 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" -const assert = require("assert") -const { parseForESLint } = require("../src") -const eslint = require("eslint") -const Linter = eslint.Linter +import { describe, it, assert } from "vitest" +import { parseForESLint } from "../src" +import type { ESLint } from "eslint" +import { Linter } from "eslint" describe("parserOptions", () => { describe("parser", () => { const linter = new Linter({ configType: "flat" }) - const parser = { parseForESLint } - const plugin = { + const parser: Linter.Parser = { parseForESLint } + const plugin: ESLint.Plugin = { rules: { "template-test": { create(context) { @@ -33,7 +32,7 @@ describe("parserOptions", () => { it("false then skip parsing '` - const config = { + const config: Linter.Config = { files: ["*.vue"], plugins: { vue: plugin, @@ -57,7 +56,7 @@ describe("parserOptions", () => { it("Fail in ` - const config = { + const config: Linter.Config = { files: ["*.vue"], plugins: { vue: plugin, diff --git a/test/test-utils.js b/test/test-utils.ts similarity index 68% rename from test/test-utils.js rename to test/test-utils.ts index 8ea0b760..f5305181 100644 --- a/test/test-utils.js +++ b/test/test-utils.ts @@ -1,14 +1,22 @@ -const escope = require("eslint-scope") - -module.exports = { replacer, getAllTokens, scopeToJSON, analyze } +import type { Identifier, Node } from "estree" +import type { + Scope, + ScopeManager, + Variable, + VariableDefinition, +} from "eslint-scope" +import type { ESLintProgram, Token } from "../src/ast" +import type { ParserOptions } from "../src/common/parser-options" +import * as escope from "eslint-scope" +import { getFallbackKeys } from "../src/ast" /** * Remove `parent` properties from the given AST. - * @param {string} key The key. - * @param {any} value The value of the key. - * @returns {any} The value of the key to output. + * @param key The key. + * @param value The value of the key. + * @returns The value of the key to output. */ -function replacer(key, value) { +export function replacer(key: string, value: any): any { if (key === "parent") { return undefined } @@ -25,10 +33,10 @@ function replacer(key, value) { /** * Get all tokens of the given AST. - * @param {ASTNode} ast The root node of AST. - * @returns {Token[]} Tokens. + * @param ast The root node of AST. + * @returns Tokens. */ -function getAllTokens(ast) { +export function getAllTokens(ast: ESLintProgram): Token[] { const tokenArrays = [ast.tokens, ast.comments] if (ast.templateBody != null) { tokenArrays.push(ast.templateBody.tokens, ast.templateBody.comments) @@ -36,10 +44,10 @@ function getAllTokens(ast) { return Array.prototype.concat.apply([], tokenArrays) } -function scopeToJSON(scopeManager) { +export function scopeToJSON(scopeManager: ScopeManager) { return JSON.stringify(normalizeScope(scopeManager.globalScope), replacer, 4) - function normalizeScope(scope) { + function normalizeScope(scope: Scope): any { return { type: scope.type, variables: scope.variables.map(normalizeVar), @@ -49,7 +57,7 @@ function scopeToJSON(scopeManager) { } } - function normalizeVar(v) { + function normalizeVar(v: Variable) { return { name: v.name, identifiers: v.identifiers.map(normalizeId), @@ -58,7 +66,7 @@ function scopeToJSON(scopeManager) { } } - function normalizeReference(reference) { + function normalizeReference(reference: any) { return { identifier: normalizeId(reference.identifier), from: reference.from.type, @@ -75,7 +83,7 @@ function scopeToJSON(scopeManager) { } } - function normalizeDef(def) { + function normalizeDef(def: VariableDefinition) { return { type: def.type, node: normalizeDefNode(def.node), @@ -83,7 +91,7 @@ function scopeToJSON(scopeManager) { } } - function normalizeId(identifier) { + function normalizeId(identifier: Identifier | null) { return ( identifier && { type: identifier.type, @@ -93,7 +101,7 @@ function scopeToJSON(scopeManager) { ) } - function normalizeDefNode(node) { + function normalizeDefNode(node: Node) { return { type: node.type, loc: node.loc, @@ -104,7 +112,10 @@ function scopeToJSON(scopeManager) { /** * Analyze scope */ -function analyze(ast, parserOptions) { +export function analyze( + ast: ESLintProgram, + parserOptions: ParserOptions, +): ScopeManager { const ecmaVersion = parserOptions.ecmaVersion ?? 2022 const ecmaFeatures = parserOptions.ecmaFeatures ?? {} const sourceType = parserOptions.sourceType ?? "script" @@ -118,23 +129,4 @@ function analyze(ast, parserOptions) { }) return result - - function getFallbackKeys(node) { - return Object.keys(node).filter(fallbackKeysFilter, node) - } - - function fallbackKeysFilter(key) { - const value = null - return ( - key !== "comments" && - key !== "leadingComments" && - key !== "loc" && - key !== "parent" && - key !== "range" && - key !== "tokens" && - key !== "trailingComments" && - typeof value === "object" && - (typeof value.type === "string" || Array.isArray(value)) - ) - } } diff --git a/test/tokens.js b/test/tokens.test.ts similarity index 70% rename from test/tokens.js rename to test/tokens.test.ts index de370298..b7133625 100644 --- a/test/tokens.js +++ b/test/tokens.test.ts @@ -3,14 +3,20 @@ * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ -"use strict" //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert") -const parse = require("../src").parseForESLint +import type { + ESLintProgram, + Token, + VElement, + VExpressionContainer, +} from "../src/ast" +import type TokenStore from "../src/external/token-store" +import { assert, beforeAll, describe, it } from "vitest" +import { parseForESLint as parse } from "../src" //------------------------------------------------------------------------------ // Helpers @@ -26,10 +32,10 @@ const PARSER_OPTIONS = { /** * Get the value of the given node. - * @param {ASTNode} token The node to get value. - * @returns {string} The value of the node. + * @param token The node to get value. + * @returns The value of the node. */ -function toValue(token) { +function toValue(token: Token): string { if (token.type === "HTMLAssociation") { return "=" } @@ -48,18 +54,18 @@ describe("services.getTemplateBodyTokenStore", () => {
{{ message /*comment3*/ }}
` - let ast = null - let tokens = null + let ast: ESLintProgram | null = null + let tokens: TokenStore | null = null - before(() => { + beforeAll(() => { const result = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }) ast = result.ast - tokens = result.services.getTemplateBodyTokenStore() + tokens = result.services!.getTemplateBodyTokenStore() }) describe("ast.templateBody", () => { it("should return all tokens (except comments) in the template.", () => { - const actual = tokens.getTokens(ast.templateBody).map(toValue) + const actual = tokens!.getTokens(ast!.templateBody!).map(toValue) assert.deepStrictEqual(actual, [ "template", @@ -94,8 +100,8 @@ describe("services.getTemplateBodyTokenStore", () => { }) it("should return all tokens (include comments) in the template if you give {includeComments: true} option.", () => { - const actual = tokens - .getTokens(ast.templateBody, { includeComments: true }) + const actual = tokens! + .getTokens(ast!.templateBody!, { includeComments: true }) .map(toValue) assert.deepStrictEqual(actual, [ @@ -137,8 +143,8 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[0] (VText)", () => { it("should return a text token.", () => { - const node = ast.templateBody.children[0] - const actual = tokens.getTokens(node).map(toValue) + const node = ast!.templateBody!.children[0] + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["\n "]) }) @@ -146,8 +152,8 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2] (VElement)", () => { it("should return all tokens in the element.", () => { - const node = ast.templateBody.children[2] - const actual = tokens.getTokens(node).map(toValue) + const node = ast!.templateBody!.children[2] + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, [ "div", @@ -177,8 +183,8 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag (VStartTag)", () => { it("should return all tokens in the tag.", () => { - const node = ast.templateBody.children[2].startTag - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, [ "div", @@ -203,8 +209,9 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[0] (VAttribute)", () => { it("should return all tokens in the attribute.", () => { - const node = ast.templateBody.children[2].startTag.attributes[0] - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + .attributes[0] + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["a", "=", "b"]) }) @@ -212,8 +219,9 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[0].key (VIdentifier)", () => { it("should return the identifier token.", () => { - const node = ast.templateBody.children[2].startTag.attributes[0].key - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + .attributes[0].key + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["a"]) }) @@ -221,9 +229,9 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[0].value (VAttributeValue)", () => { it("should return the value token.", () => { - const node = - ast.templateBody.children[2].startTag.attributes[0].value - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + .attributes[0].value! + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["b"]) }) @@ -231,8 +239,9 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[1].key (VDirectiveKey)", () => { it("should return the identifier token.", () => { - const node = ast.templateBody.children[2].startTag.attributes[1].key - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + .attributes[1].key + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["v-show"]) }) @@ -240,9 +249,9 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[1].value (VExpressionContainer)", () => { it("should return all tokens in the value.", () => { - const node = - ast.templateBody.children[2].startTag.attributes[1].value - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).startTag + .attributes[1].value! + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, [ '"', @@ -260,10 +269,11 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].startTag.attributes[1].value.expression (BinaryExpression)", () => { it("should return all tokens in the expression.", () => { - const node = - ast.templateBody.children[2].startTag.attributes[1].value - .expression - const actual = tokens.getTokens(node).map(toValue) + const node = ( + (ast!.templateBody!.children[2] as VElement).startTag + .attributes[1].value as VExpressionContainer + ).expression! + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, [ "c", @@ -279,8 +289,8 @@ describe("services.getTemplateBodyTokenStore", () => { describe("ast.templateBody.children[2].endTag (VEndTag)", () => { it("should return all tokens in the tag.", () => { - const node = ast.templateBody.children[2].endTag - const actual = tokens.getTokens(node).map(toValue) + const node = (ast!.templateBody!.children[2] as VElement).endTag! + const actual = tokens!.getTokens(node).map(toValue) assert.deepStrictEqual(actual, ["div", ">"]) }) @@ -289,15 +299,13 @@ describe("services.getTemplateBodyTokenStore", () => { describe("TokenStore#get{Range,Loc}()", () => { it("should return loc and range.", () => { const { - templateBody: { - children: [node], - tokens: [token], - }, - } = ast - assert.equal(typeof tokens.getRange(node)[0], "number") - assert.equal(typeof tokens.getRange(token)[1], "number") - assert.equal(typeof tokens.getLoc(node).start.line, "number") - assert.equal(typeof tokens.getLoc(node).end.column, "number") + children: [node], + tokens: [token], + } = ast!.templateBody! + assert.equal(typeof tokens!.getRange(node)[0], "number") + assert.equal(typeof tokens!.getRange(token)[1], "number") + assert.equal(typeof tokens!.getLoc(node).start.line, "number") + assert.equal(typeof tokens!.getLoc(node).end.column, "number") }) }) }) diff --git a/test/variables-references.js b/test/variables-references.test.ts similarity index 53% rename from test/variables-references.js rename to test/variables-references.test.ts index 4e6a590b..e0a9e765 100644 --- a/test/variables-references.js +++ b/test/variables-references.test.ts @@ -3,14 +3,26 @@ * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ -"use strict" //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert") -const parse = require("../src").parseForESLint +import type { + ESLintBinaryExpression, + ESLintCallExpression, + ESLintExpressionStatement, + ESLintProgram, + Reference, + Variable, + VDirective, + VElement, + VExpressionContainer, + VForExpression, + VOnExpression, +} from "../src/ast" +import { describe, it, assert, beforeAll } from "vitest" +import { parseForESLint as parse } from "../src" //------------------------------------------------------------------------------ // Helpers @@ -31,65 +43,82 @@ const PARSER_OPTIONS = { describe("[references] expression containers", () => { describe("in directives", () => { const code = '' - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should have references", () => { - const directive = - ast.templateBody.children[0].startTag.attributes[0] + const element = ast.templateBody!.children[0] as VElement + const directive = element.startTag.attributes[0] as VDirective assert(directive.key.type === "VDirectiveKey") - assert(directive.value.references != null) + assert(directive.value!.references != null) assert( - directive.value.references[0].id === - directive.value.expression.left, + directive.value!.references[0].id === + (directive.value!.expression as ESLintBinaryExpression) + .left, ) assert( - directive.value.references[1].id === - directive.value.expression.right, + directive.value!.references[1].id === + (directive.value!.expression as ESLintBinaryExpression) + .right, ) }) }) describe("in text", () => { const code = "" - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should have references", () => { - const container = ast.templateBody.children[0] + const container = ast.templateBody! + .children[0] as VExpressionContainer assert(container.type === "VExpressionContainer") assert(container.references != null) - assert(container.references[0].id === container.expression.left) - assert(container.references[1].id === container.expression.right) + assert( + container.references[0].id === + (container.expression as ESLintBinaryExpression).left, + ) + assert( + container.references[1].id === + (container.expression as ESLintBinaryExpression).right, + ) }) }) describe("in v-on directive", () => { const code = '' - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should not include $event references.", () => { - const directive = - ast.templateBody.children[0].startTag.attributes[0] + const element = ast.templateBody!.children[0] as VElement + const directive = element.startTag.attributes[0] as VDirective assert(directive.key.type === "VDirectiveKey") assert(directive.key.name.name === "on") - assert(directive.value.references.length === 1) + assert(directive.value!.references.length === 1) assert( - directive.value.references[0].id === - directive.value.expression.body[0].expression.callee, + directive.value!.references[0].id === + ( + ( + (directive.value!.expression as VOnExpression) + .body[0] as ESLintExpressionStatement + ).expression as ESLintCallExpression + ).callee, ) }) }) @@ -98,68 +127,61 @@ describe("[references] expression containers", () => { describe("[variables] elements", () => { describe("which have v-for directive", () => { const code = '' - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should have references", () => { - const element = ast.templateBody.children[0] - const directive = element.startTag.attributes[0] + const element = ast.templateBody!.children[0] as VElement + const directive = element.startTag.attributes[0] as VDirective + const vForExpression = directive.value!.expression as VForExpression assert(element.type === "VElement") assert(element.variables.length === 1) - assert( - element.variables[0].id === directive.value.expression.left[0], - ) - assert(directive.value.references.length === 1) - assert( - directive.value.references[0].id === - directive.value.expression.right, - ) + assert(element.variables[0].id === vForExpression.left[0]) + assert(directive.value!.references.length === 1) + assert(directive.value!.references[0].id === vForExpression.right) }) }) describe("which have v-for directive (with index)", () => { const code = '' - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should have references", () => { - const element = ast.templateBody.children[0] - const directive = element.startTag.attributes[0] + const element = ast.templateBody!.children[0] as VElement + const directive = element.startTag.attributes[0] as VDirective + const vForExpression = directive.value!.expression as VForExpression assert(element.type === "VElement") assert(element.variables.length === 2) - assert( - element.variables[0].id === directive.value.expression.left[0], - ) - assert( - element.variables[1].id === directive.value.expression.left[1], - ) - assert(directive.value.references.length === 1) - assert( - directive.value.references[0].id === - directive.value.expression.right, - ) + assert(element.variables[0].id === vForExpression.left[0]) + assert(element.variables[1].id === vForExpression.left[1]) + assert(directive.value!.references.length === 1) + assert(directive.value!.references[0].id === vForExpression.right) }) }) describe("which have scope attribute", () => { const code = '' - let ast = null + // @ts-expect-error init in beforeAll + let ast: ESLintProgram = null - before(() => { + beforeAll(() => { ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast }) it("should have variables", () => { - const element = ast.templateBody.children[0] - const attribute = element.startTag.attributes[0] + const element = ast.templateBody!.children[0] as VElement + const attribute = element.startTag.attributes[0] as VDirective assert(element.type === "VElement") assert(element.variables.length === 1) @@ -167,10 +189,10 @@ describe("[variables] elements", () => { assert(element.variables[0].id.range[0] === 27) assert(element.variables[0].id.range[1] === 28) assert(element.variables[0].kind === "scope") - assert(attribute.value.type === "VExpressionContainer") - assert(attribute.value.expression.type === "VSlotScopeExpression") - assert(attribute.value.expression.params[0].type === "Identifier") - assert(attribute.value.expression.params[0].name === "a") + assert(attribute.value!.type === "VExpressionContainer") + assert(attribute.value!.expression!.type === "VSlotScopeExpression") + assert(attribute.value!.expression.params[0].type === "Identifier") + assert(attribute.value!.expression.params[0].name === "a") }) }) }) @@ -178,25 +200,38 @@ describe("[variables] elements", () => { describe("Variables of v-for and references", () => { const code = '' - let variables = null - let vForReferences = null - let vBindKeyReferences = null - let mustacheReferences1 = null - let mustacheReferences2 = null - let mustacheReferences3 = null - - before(() => { + // @ts-expect-error init in beforeAll + let variables: Variable[] = null + // @ts-expect-error init in beforeAll + let vForReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let vBindKeyReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences1: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences2: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences3: Reference[] = null + + beforeAll(() => { const ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast - variables = ast.templateBody.children[0].variables - vForReferences = - ast.templateBody.children[0].startTag.attributes[0].value.references - vBindKeyReferences = - ast.templateBody.children[0].startTag.attributes[1].value.references - mustacheReferences1 = - ast.templateBody.children[0].children[0].references - mustacheReferences2 = - ast.templateBody.children[0].children[1].children[0].references - mustacheReferences3 = ast.templateBody.children[1].references + const firstChild = ast.templateBody!.children[0] as VElement + const secondChild = ast.templateBody! + .children[1] as VExpressionContainer + variables = firstChild.variables + vForReferences = ( + firstChild.startTag.attributes[0].value as VExpressionContainer + ).references + vBindKeyReferences = ( + firstChild.startTag.attributes[1].value as VExpressionContainer + ).references + mustacheReferences1 = (firstChild.children[0] as VExpressionContainer) + .references + mustacheReferences2 = ( + (firstChild.children[1] as VElement) + .children[0] as VExpressionContainer + ).references + mustacheReferences3 = secondChild.references }) it("should have relationship each other", () => { @@ -221,14 +256,14 @@ describe("Variables of v-for and references", () => { it("`Variable#references` should be non-enumerable", () => { for (const variable of variables) { assert( - Object.getOwnPropertyDescriptor(variable, "references") + Object.getOwnPropertyDescriptor(variable, "references")! .enumerable === false, ) } }) it("`Reference#variable` should be non-enumerable", () => { - for (const reference of [].concat( + for (const reference of ([] as Reference[]).concat( vForReferences, vBindKeyReferences, mustacheReferences1, @@ -236,7 +271,7 @@ describe("Variables of v-for and references", () => { mustacheReferences3, )) { assert( - Object.getOwnPropertyDescriptor(reference, "variable") + Object.getOwnPropertyDescriptor(reference, "variable")! .enumerable === false, ) } @@ -246,22 +281,33 @@ describe("Variables of v-for and references", () => { describe("Variables of template-scope and references", () => { const code = '' - let variables = null - let vBindKeyReferences = null - let mustacheReferences1 = null - let mustacheReferences2 = null - let mustacheReferences3 = null - - before(() => { + // @ts-expect-error init in beforeAll + let variables: Variable[] = null + // @ts-expect-error init in beforeAll + let vBindKeyReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences1: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences2: Reference[] = null + // @ts-expect-error init in beforeAll + let mustacheReferences3: Reference[] = null + + beforeAll(() => { const ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast - variables = ast.templateBody.children[0].variables - vBindKeyReferences = - ast.templateBody.children[0].startTag.attributes[1].value.references - mustacheReferences1 = - ast.templateBody.children[0].children[0].references - mustacheReferences2 = - ast.templateBody.children[0].children[1].children[0].references - mustacheReferences3 = ast.templateBody.children[1].references + const element = ast.templateBody!.children[0] as VElement + const secondElement = ast.templateBody! + .children[1] as VExpressionContainer + + variables = element.variables + vBindKeyReferences = (element.startTag.attributes[1] as VDirective) + .value!.references + mustacheReferences1 = (element.children[0] as VExpressionContainer) + .references + mustacheReferences2 = ( + (element.children[1] as VElement) + .children[0] as VExpressionContainer + ).references + mustacheReferences3 = secondElement.references }) it("should have relationship each other", () => { @@ -284,21 +330,21 @@ describe("Variables of template-scope and references", () => { it("`Variable#references` should be non-enumerable", () => { for (const variable of variables) { assert( - Object.getOwnPropertyDescriptor(variable, "references") + Object.getOwnPropertyDescriptor(variable, "references")! .enumerable === false, ) } }) it("`Reference#variable` should be non-enumerable", () => { - for (const reference of [].concat( + for (const reference of ([] as Reference[]).concat( vBindKeyReferences, mustacheReferences1, mustacheReferences2, mustacheReferences3, )) { assert( - Object.getOwnPropertyDescriptor(reference, "variable") + Object.getOwnPropertyDescriptor(reference, "variable")! .enumerable === false, ) } @@ -307,18 +353,24 @@ describe("Variables of template-scope and references", () => { describe("Variables of v-for and references of dynamic arguments", () => { const code = '' - let variables = null - let vForReferences = null - let vBindKeyReferences = null - - before(() => { + // @ts-expect-error init in beforeAll + let variables: Variable[] = null + // @ts-expect-error init in beforeAll + let vForReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let vBindKeyReferences: Reference[] = null + + beforeAll(() => { const ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast - variables = ast.templateBody.children[0].variables - vForReferences = - ast.templateBody.children[0].startTag.attributes[0].value.references - vBindKeyReferences = - ast.templateBody.children[0].startTag.attributes[1].key.argument - .references + const element = ast.templateBody!.children[0] as VElement + + variables = element.variables + vForReferences = (element.startTag.attributes[0] as VDirective).value! + .references + vBindKeyReferences = ( + (element.startTag.attributes[1] as VDirective).key + .argument as VExpressionContainer + ).references }) it("should have relationship each other", () => { @@ -334,17 +386,22 @@ describe("Variables of v-for and references of dynamic arguments", () => { describe("Variables of v-for and references of v-bind same-name shorthand", () => { const code = '' - let variables = null - let vForReferences = null - let vBindReferences = null - - before(() => { + // @ts-expect-error init in beforeAll + let variables: Variable[] = null + // @ts-expect-error init in beforeAll + let vForReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let vBindReferences: Reference[] = null + + beforeAll(() => { const ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast - variables = ast.templateBody.children[0].variables - vForReferences = - ast.templateBody.children[0].startTag.attributes[0].value.references - vBindReferences = - ast.templateBody.children[0].startTag.attributes[1].value.references + const element = ast.templateBody!.children[0] as VElement + + variables = element.variables + vForReferences = (element.startTag.attributes[0] as VDirective).value! + .references + vBindReferences = (element.startTag.attributes[1] as VDirective).value! + .references }) it("should have relationship each other", () => { @@ -360,17 +417,22 @@ describe("Variables of v-for and references of v-bind same-name shorthand", () = describe("Variables of v-for and references of v-bind same-name shorthand with kebab-case", () => { const code = '' - let variables = null - let vForReferences = null - let vBindReferences = null - - before(() => { + // @ts-expect-error init in beforeAll + let variables: Variable[] = null + // @ts-expect-error init in beforeAll + let vForReferences: Reference[] = null + // @ts-expect-error init in beforeAll + let vBindReferences: Reference[] = null + + beforeAll(() => { const ast = parse(code, { filePath: "test.vue", ...PARSER_OPTIONS }).ast - variables = ast.templateBody.children[0].variables - vForReferences = - ast.templateBody.children[0].startTag.attributes[0].value.references - vBindReferences = - ast.templateBody.children[0].startTag.attributes[1].value.references + const element = ast.templateBody!.children[0] as VElement + + variables = element.variables + vForReferences = (element.startTag.attributes[0] as VDirective).value! + .references + vBindReferences = (element.startTag.attributes[1] as VDirective).value! + .references }) it("should have relationship each other", () => { diff --git a/tsconfig.json b/tsconfig.json index c5dc0e4c..677d8fec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,5 +37,6 @@ "skipLibCheck": true }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts"], + "references": [{ "path": "./tsconfig.test.json" }] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..a5e080f7 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "paths": { + "*": ["typings/*"] + }, + "module": "esnext", + "moduleResolution": "Bundler" + }, + "include": ["test/**/*.ts", "vitest.config.ts"] +} diff --git a/typings/eslint-scope/index.d.ts b/typings/eslint-scope/index.d.ts index 6d14afcf..1469f501 100644 --- a/typings/eslint-scope/index.d.ts +++ b/typings/eslint-scope/index.d.ts @@ -14,7 +14,7 @@ export interface AnalysisOptions { impliedStrict?: boolean fallback?: string | Function sourceType?: "script" | "module" - ecmaVersion?: number + ecmaVersion?: number | "latest" childVisitorKeys?: VisitorKeys } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..f0cdca4d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + reporters: "dot", + include: ["test/**/*.test.ts"], + testTimeout: 60000, + coverage: { + include: ["src"], + }, + }, +})