From e47b73fb8eea2cb990a5e0f290514d7bbabdfbd7 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Fri, 4 Jul 2025 23:26:04 +0800 Subject: [PATCH 01/32] feat: move `parser-options` to `vitest` --- package.json | 1 + test/{parser-options.js => parser-options.test.ts} | 12 +++++------- vitest.config.ts | 7 +++++++ 3 files changed, 13 insertions(+), 7 deletions(-) rename test/{parser-options.js => parser-options.test.ts} (91%) create mode 100644 vitest.config.ts diff --git a/package.json b/package.json index 6358665..5f7b279 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "rollup-plugin-sourcemaps": "^0.6.3", "ts-node": "^10.9.2", "typescript": "~5.7.3", + "vitest": "^3.2.4", "wait-on": "^6.0.0", "warun": "^1.0.0" }, diff --git a/test/parser-options.js b/test/parser-options.test.ts similarity index 91% rename from test/parser-options.js rename to test/parser-options.test.ts index 3c7e4e1..12d82ef 100644 --- a/test/parser-options.js +++ b/test/parser-options.test.ts @@ -2,12 +2,10 @@ * @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 { Linter } from "eslint" describe("parserOptions", () => { describe("parser", () => { @@ -33,7 +31,7 @@ describe("parserOptions", () => { it("false then skip parsing '` - const config = { + const config: Linter.Config = { files: ["*.vue"], plugins: { vue: plugin, @@ -57,7 +55,7 @@ describe("parserOptions", () => { it("Fail in ` - const config = { + const config: Linter.Config = { files: ["*.vue"], plugins: { vue: plugin, diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..b4b0888 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig} from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/parser-options.test.ts'] + } +}) \ No newline at end of file From 1cdfa2f9ee5d8367e6deb7eb5d47a3a62e7c1f70 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 00:13:02 +0800 Subject: [PATCH 02/32] feat: move `crlf` to `vitest` --- test/{crlf.js => crlf.test.ts} | 21 +++++++++++---------- vitest.config.ts | 10 +++++----- 2 files changed, 16 insertions(+), 15 deletions(-) rename test/{crlf.js => crlf.test.ts} (73%) 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 d35ce26..c2ecb46 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 +889,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 +924,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 +944,7 @@ export default {} }) }) -function buildPlugins(rule) { +function buildPlugins(rule: Rule.RuleModule) { return { test: { rules: { diff --git a/vitest.config.ts b/vitest.config.ts index d1299b0..266ce87 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "vitest/config" export default defineConfig({ test: { include: [ - "test/{parser-options,crlf,define-document-visitor,define-custom-blocks-visitor,parser-options-project,document-fragment,tokens,variables-references,ast}.test.ts", + "test/{parser-options,crlf,define-document-visitor,define-custom-blocks-visitor,parser-options-project,document-fragment,tokens,variables-references,ast,index}.test.ts", ], }, }) From 8099d38238e000ccf1d410d9797b681addbc86bc Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 22:25:12 +0800 Subject: [PATCH 16/32] refactor: move all test to `vitest` --- package.json | 6 +-- src/index.ts | 2 +- test/define-custom-blocks-visitor.test.ts | 1 + .../{integrations.js => integrations.test.ts} | 50 ++++++++--------- .../{eslint-compat.js => eslint-compat.ts} | 53 +++++++------------ test/parser-options-project.test.ts | 1 + vitest.config.ts | 6 +-- 7 files changed, 54 insertions(+), 65 deletions(-) rename test/{integrations.js => integrations.test.ts} (71%) rename test/lib/{eslint-compat.js => eslint-compat.ts} (67%) diff --git a/package.json b/package.json index 3587777..d0988bf 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", "@types/debug": "^4.1.7", - "@types/espree": "^10.1.0", "@types/estree": "^1.0.0", "@types/fs-extra": "^11.0.4", "@types/mocha": "^9.0.0", @@ -73,9 +72,8 @@ "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": "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", diff --git a/src/index.ts b/src/index.ts index 13cb530..45d43d6 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/test/define-custom-blocks-visitor.test.ts b/test/define-custom-blocks-visitor.test.ts index 0da7e5c..cd83bf2 100644 --- a/test/define-custom-blocks-visitor.test.ts +++ b/test/define-custom-blocks-visitor.test.ts @@ -13,6 +13,7 @@ import { assert, describe, it } from "vitest" import { Linter } from "eslint" import { builtinRules } from "eslint/use-at-your-own-risk" import jsonParser from "jsonc-eslint-parser" +// @ts-expect-error -- ignore import * as espree from "espree" import * as parser from "../src" import type { Program } from "estree" diff --git a/test/integrations.js b/test/integrations.test.ts similarity index 71% rename from test/integrations.js rename to test/integrations.test.ts index 88e9c50..65dbfbb 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 "path" +import fs from "fs-extra" +import cp from "child_process" +import eslintCompat from "./lib/eslint-compat" +import * as 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, @@ -72,22 +78,18 @@ describe("Integration tests", () => { function normalizeReport(report, option = {}) { return report .filter((res) => res.messages.length) - .map((res) => { - return { - filePath: res.filePath - .replace(cwd, "") - .replace(/\\/gu, "/"), - messages: res.messages.map((msg) => { - return { - ruleId: msg.ruleId, - line: msg.line, - ...(option.withoutMessage - ? {} - : { message: msg.message }), - } - }), - } - }) + .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 67% rename from test/lib/eslint-compat.js rename to test/lib/eslint-compat.ts index 9077ec5..2f29fdb 100644 --- a/test/lib/eslint-compat.js +++ b/test/lib/eslint-compat.ts @@ -1,11 +1,6 @@ -"use strict" +import type { ESLint } from "eslint" -/** - * @typedef {import('eslint')} eslint - */ - -/** @param {eslint} eslint */ -module.exports = function compat(eslint) { +export default function compat(eslint: any) { return { ESLint: eslint.ESLint || getESLintClassForV6(eslint), RuleTester: eslint.RuleTester, @@ -13,15 +8,16 @@ module.exports = function compat(eslint) { } } -/** @returns {typeof eslint.ESLint} */ -function getESLintClassForV6(eslint) { +function getESLintClassForV6(eslint: any): ESLint { class ESLintForV6 { + public engine + 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: ESLint.Options) { const { overrideConfig: { plugins, @@ -40,8 +36,7 @@ function getESLintClassForV6(eslint) { plugins: pluginsMap, ...otherOptions } = options || {} - /** @type {eslint.CLIEngine.Options} */ - const newOptions = { + const newOptions: CLIEngine.Options = { fix: Boolean(fix), reportUnusedDisableDirectives: reportUnusedDisableDirectives ? reportUnusedDisableDirectives !== "off" @@ -64,7 +59,7 @@ function getESLintClassForV6(eslint) { } return o }, - /** @type {NonNullable} */ {}, + {} satisfies NonNullable, ) : undefined, ...overrideConfig, @@ -76,41 +71,33 @@ function getESLintClassForV6(eslint) { } } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - async lintText(...params) { + async lintText( + ...params: Parameters + ): ReturnType { const result = this.engine.executeOnText( params[0], - params[1].filePath, + params[1]!.filePath, ) return result.results } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - async lintFiles(...params) { + async lintFiles( + ...params: Parameters + ): ReturnType { const result = this.engine.executeOnFiles( Array.isArray(params[0]) ? params[0] : [params[0]], ) return result.results } - /** - * @param {Parameters} params - * @returns {ReturnType} - */ - static async outputFixes(...params) { + static async outputFixes( + ...params: Parameters + ): ReturnType { return 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.test.ts b/test/parser-options-project.test.ts index 257f4de..77c8c51 100644 --- a/test/parser-options-project.test.ts +++ b/test/parser-options-project.test.ts @@ -1,5 +1,6 @@ import { describe, it, assert } from "vitest" import { parseForESLint } from "../src" +// @ts-expect-error -- ignore import * as espree from "espree" import type { Linter } from "eslint" diff --git a/vitest.config.ts b/vitest.config.ts index 266ce87..4aca483 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,8 +2,8 @@ import { defineConfig } from "vitest/config" export default defineConfig({ test: { - include: [ - "test/{parser-options,crlf,define-document-visitor,define-custom-blocks-visitor,parser-options-project,document-fragment,tokens,variables-references,ast,index}.test.ts", - ], + reporters: "dot", + include: ["test/*.test.ts"], + teardownTimeout: 60000, }, }) From f3f67de3b4cf8da3a079522101f0d066bbf8b3bb Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 22:53:05 +0800 Subject: [PATCH 17/32] chore: enhance type definition --- test/integrations.test.ts | 8 ++++++-- test/lib/eslint-compat.ts | 33 +++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/test/integrations.test.ts b/test/integrations.test.ts index 65dbfbb..e860487 100644 --- a/test/integrations.test.ts +++ b/test/integrations.test.ts @@ -22,6 +22,7 @@ const FIXTURE_DIR = path.join(__dirname, "fixtures/integrations") describe("Integration tests", () => { beforeAll(async () => { + // @ts-expect-error -- ignore await import("ts-node/register") }) for (const target of fs.readdirSync(FIXTURE_DIR)) { @@ -75,8 +76,11 @@ 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) => ({ filePath: res.filePath diff --git a/test/lib/eslint-compat.ts b/test/lib/eslint-compat.ts index 2f29fdb..ae96362 100644 --- a/test/lib/eslint-compat.ts +++ b/test/lib/eslint-compat.ts @@ -1,6 +1,10 @@ -import type { ESLint } from "eslint" +import type { ESLint, Linter, RuleTester } from "eslint" -export default function compat(eslint: any) { +export default function compat(eslint: any): { + ESLint: typeof ESLint + RuleTester: typeof RuleTester + Linter: typeof Linter +} { return { ESLint: eslint.ESLint || getESLintClassForV6(eslint), RuleTester: eslint.RuleTester, @@ -8,16 +12,17 @@ export default function compat(eslint: any) { } } -function getESLintClassForV6(eslint: any): 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 } // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility - constructor(options: ESLint.Options) { + constructor(options: any) { const { overrideConfig: { plugins, @@ -29,14 +34,14 @@ function getESLintClassForV6(eslint: any): ESLint { plugins: [], globals: {}, rules: {}, - }, + } as any, overrideConfigFile, fix, reportUnusedDisableDirectives, plugins: pluginsMap, ...otherOptions } = options || {} - const newOptions: CLIEngine.Options = { + const newOptions = { fix: Boolean(fix), reportUnusedDisableDirectives: reportUnusedDisableDirectives ? reportUnusedDisableDirectives !== "off" @@ -59,7 +64,7 @@ function getESLintClassForV6(eslint: any): ESLint { } return o }, - {} satisfies NonNullable, + {} as Record, ) : undefined, ...overrideConfig, @@ -71,29 +76,33 @@ function getESLintClassForV6(eslint: any): ESLint { } } + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility async lintText( ...params: Parameters ): ReturnType { - const result = this.engine.executeOnText( + const result = await this.engine.executeOnText( params[0], params[1]!.filePath, ) return result.results } + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility async lintFiles( ...params: Parameters ): ReturnType { - const result = this.engine.executeOnFiles( + const result = await this.engine.executeOnFiles( Array.isArray(params[0]) ? params[0] : [params[0]], ) return result.results } + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility static async outputFixes( - ...params: Parameters - ): ReturnType { - return eslint.CLIEngine.outputFixes({ + ...params: Parameters + ): ReturnType { + // eslint-disable-next-line no-return-await + return await eslint.CLIEngine.outputFixes({ results: params[0], }) } From 3cd2989a882e3b44f248d2552891de36a7788374 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 22:58:20 +0800 Subject: [PATCH 18/32] chore: uninstall mocha --- package.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d0988bf..7f5b9a7 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@types/debug": "^4.1.7", "@types/estree": "^1.0.0", "@types/fs-extra": "^11.0.4", - "@types/mocha": "^9.0.0", "@types/node": "^18.8.4", "@types/semver": "^7.3.12", "@typescript-eslint/eslint-plugin": "^8.22.0", @@ -48,7 +47,6 @@ "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", @@ -67,12 +65,11 @@ "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.*", + "clean": "rimraf .temp index.*", "coverage": "opener ./coverage/lcov-report/index.html", "lint": "eslint src test package.json", "pretest": "run-s build lint", - "test": "npm run -s test:mocha", - "test:vitest": "vitest", + "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", @@ -82,7 +79,6 @@ "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" }, From 94d42efbdbc0b3c6157d58037f8ffa2023f9458f Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 23:07:32 +0800 Subject: [PATCH 19/32] test: update --- package.json | 4 +++- vitest.config.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7f5b9a7..977a007 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "@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", @@ -66,7 +68,7 @@ "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 .temp index.*", - "coverage": "opener ./coverage/lcov-report/index.html", + "coverage": "vitest --ui", "lint": "eslint src test package.json", "pretest": "run-s build lint", "test": "vitest", diff --git a/vitest.config.ts b/vitest.config.ts index 4aca483..867408c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,11 @@ import { defineConfig } from "vitest/config" export default defineConfig({ test: { reporters: "dot", - include: ["test/*.test.ts"], + include: ["test/**/*.test.ts"], teardownTimeout: 60000, + coverage: { + enabled: true, + include: ["src"], + }, }, }) From 736756749fa79de33177c7abb90a5d15d16f4837 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 23:15:18 +0800 Subject: [PATCH 20/32] chore: update --- .gitignore | 2 -- eslint.config.mjs | 2 -- package.json | 5 +---- test/index.test.ts | 2 ++ test/parser-options.test.ts | 5 +++-- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 8bf60fc..f46f6af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -/.nyc_output /.temp -/coverage node_modules /test/temp /index.* diff --git a/eslint.config.mjs b/eslint.config.mjs index 5e81622..635c3b4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,9 +22,7 @@ const compat = new FlatCompat({ export default [ { ignores: [ - ".nyc_output", ".temp", - "coverage", "**/node_modules", "src/html/util/alternative-cr.ts", "src/html/util/attribute-names.ts", diff --git a/package.json b/package.json index 977a007..933d18e 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,6 @@ "fs-extra": "^10.0.0", "jsonc-eslint-parser": "^2.0.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", @@ -81,8 +79,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: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/test/index.test.ts b/test/index.test.ts index 9f108bf..98fd6df 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -11,6 +11,7 @@ import type { Rule } from "eslint" import path from "node:path" import { describe, it, assert, beforeEach, afterEach } from "vitest" +// @ts-expect-error -- ignore import * as tsParser from "@typescript-eslint/parser" import fs from "fs-extra" import * as eslint from "eslint" @@ -49,6 +50,7 @@ const BABEL_PARSER_OPTIONS = { describe("Basic tests", async () => { const ESLint = await eslint.loadESLint({ useFlatConfig: true }) const Linter = class extends eslint.Linter { + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility constructor() { super({ configType: "flat" }) } diff --git a/test/parser-options.test.ts b/test/parser-options.test.ts index 12d82ef..37aa60a 100644 --- a/test/parser-options.test.ts +++ b/test/parser-options.test.ts @@ -5,13 +5,14 @@ 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) { From 7aa76c652bf85214e48ebe425fad5558e5f289eb Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 23:31:53 +0800 Subject: [PATCH 21/32] chore: update ci --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1d1e9d..09e612c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -58,7 +58,7 @@ jobs: - 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 +82,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 From dd1335a1d17e284eb0c89d330c71ce6acca09d7f Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 23:39:45 +0800 Subject: [PATCH 22/32] fix: update something maybe wrong --- test/ast.test.ts | 68 ++++++++++------------- test/define-custom-blocks-visitor.test.ts | 3 +- test/test-utils.ts | 1 + 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/test/ast.test.ts b/test/ast.test.ts index df31018..b402aa9 100644 --- a/test/ast.test.ts +++ b/test/ast.test.ts @@ -71,28 +71,23 @@ function getTree(source: string, parserOptions: any) { }, }), } - const result = linter.verify( - source, - { - files: ["**"], - plugins: { - test: { - rules: { - maketree, - }, + const result = linter.verify(source, { + files: ["**"], + plugins: { + test: { + rules: { + maketree, }, }, - languageOptions: { - parser, - ecmaVersion: parserOptions.ecmaVersion ?? "latest", - sourceType: parserOptions.sourceType ?? "module", - 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 @@ -122,7 +117,7 @@ function validateParent(source: string, parserOptions: any) { ruleContext.sourceCode.parserServices.defineTemplateBodyVisitor({ "*"(node: Node) { if (stack.length >= 1) { - const parent = stack.at(-1) + const parent = stack.at(-1)! assert( node.parent === parent, `The parent of ${nodeToString( @@ -141,28 +136,23 @@ function validateParent(source: string, parserOptions: any) { }, }), } - 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, - }, - rules: { "test/validateparent": "error" }, }, - undefined, - true, - ) + languageOptions: { + parser, + ecmaVersion: parserOptions.ecmaVersion ?? "latest", + sourceType: parserOptions.sourceType ?? "module", + parserOptions, + }, + rules: { "test/validateparent": "error" }, + }) assert.deepStrictEqual(result, []) } diff --git a/test/define-custom-blocks-visitor.test.ts b/test/define-custom-blocks-visitor.test.ts index cd83bf2..97c7298 100644 --- a/test/define-custom-blocks-visitor.test.ts +++ b/test/define-custom-blocks-visitor.test.ts @@ -461,7 +461,8 @@ describe("parserServices.defineCustomBlocksVisitor tests", () => { const linter = createLinter() const config = getConfig( (lang, block) => - (lang === "json" && lang === "json5") || + lang === "json" || + lang === "json5" || (!lang && block.name === "i18n"), ) diff --git a/test/test-utils.ts b/test/test-utils.ts index 993851f..7048f34 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -144,6 +144,7 @@ export function analyze( key !== "tokens" && key !== "trailingComments" && typeof value === "object" && + // @ts-expect-error -- ignore (typeof value.type === "string" || Array.isArray(value)) ) } From f558d6a155e290ed75c3e8db4bc1187110aac319 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sat, 5 Jul 2025 23:59:29 +0800 Subject: [PATCH 23/32] chore: update, try fix ci --- .gitignore | 1 + eslint.config.mjs | 2 +- package.json | 2 +- tsconfig.json | 2 +- tsconfig.test.json | 7 +++++++ vitest.config.ts | 1 - 6 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 tsconfig.test.json diff --git a/.gitignore b/.gitignore index f46f6af..1afb3f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.temp node_modules +/coverage /test/temp /index.* /npm-debug.log diff --git a/eslint.config.mjs b/eslint.config.mjs index 635c3b4..f44e79b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -62,7 +62,7 @@ export default [ }, loggerFn: false, - project: "tsconfig.json", + project: ["tsconfig.json", "tsconfig.test.json"], }, }, diff --git a/package.json b/package.json index 933d18e..2f33bb9 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "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 .temp index.*", - "coverage": "vitest --ui", + "coverage": "vitest --coverage --ui", "lint": "eslint src test package.json", "pretest": "run-s build lint", "test": "vitest", diff --git a/tsconfig.json b/tsconfig.json index 5fe9fd6..c5dc0e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,5 +37,5 @@ "skipLibCheck": true }, - "include": ["src/**/*.ts", "test/**/*.ts", "vitest.config.ts"] + "include": ["src/**/*.ts"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..e507e28 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "moduleResolution": "Bundler" + }, + "include": ["test/**/*.ts", "vitest.config.ts"] +} diff --git a/vitest.config.ts b/vitest.config.ts index 867408c..c00bfcd 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,7 +6,6 @@ export default defineConfig({ include: ["test/**/*.test.ts"], teardownTimeout: 60000, coverage: { - enabled: true, include: ["src"], }, }, From 6c1b3a9c780b6023b3b814f111064bace5d7a5ec Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:15:36 +0800 Subject: [PATCH 24/32] chore: up --- package.json | 1 - test/define-custom-blocks-visitor.test.ts | 7 +++---- test/document-fragment.test.ts | 4 ++-- test/index.test.ts | 5 ++--- test/integrations.test.ts | 7 +++---- test/parser-options-project.test.ts | 1 - 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 2f33bb9..852636c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@eslint/js": "^9.19.0", "@types/debug": "^4.1.7", "@types/estree": "^1.0.0", - "@types/fs-extra": "^11.0.4", "@types/node": "^18.8.4", "@types/semver": "^7.3.12", "@typescript-eslint/eslint-plugin": "^8.22.0", diff --git a/test/define-custom-blocks-visitor.test.ts b/test/define-custom-blocks-visitor.test.ts index 97c7298..f57ddf1 100644 --- a/test/define-custom-blocks-visitor.test.ts +++ b/test/define-custom-blocks-visitor.test.ts @@ -7,13 +7,12 @@ //------------------------------------------------------------------------------ import type { ESLint, Rule } from "eslint" -import type { VElement } from "../src/ast" +import type { Location, VElement } from "../src/ast" import type { CustomBlockContext } from "../src/sfc/custom-block" import { assert, describe, it } from "vitest" import { Linter } from "eslint" import { builtinRules } from "eslint/use-at-your-own-risk" import jsonParser from "jsonc-eslint-parser" -// @ts-expect-error -- ignore import * as espree from "espree" import * as parser from "../src" import type { Program } from "estree" @@ -55,7 +54,7 @@ const noParsingErrorRule: Rule.RuleModule = { create(context) { const parseError = context.getSourceCode().parserServices.parseError if (parseError) { - let loc = undefined + let loc: Location | undefined = undefined if ("column" in parseError && "lineNumber" in parseError) { loc = { line: parseError.lineNumber, @@ -79,7 +78,7 @@ const noParsingErrorRule2: Rule.RuleModule = { create(context: any) { const parseError = context.parserServices.parseError if (parseError) { - let loc = undefined + let loc: Location | undefined = undefined if ("column" in parseError && "lineNumber" in parseError) { loc = { line: parseError.lineNumber, diff --git a/test/document-fragment.test.ts b/test/document-fragment.test.ts index 7988e6f..d3f8a74 100644 --- a/test/document-fragment.test.ts +++ b/test/document-fragment.test.ts @@ -3,8 +3,8 @@ //------------------------------------------------------------------------------ import type { VDocumentFragment } from "../src/ast" -import fs from "fs" -import path from "path" +import fs from "node:fs" +import path from "node:path" import { describe, it, assert } from "vitest" import * as parser from "../src" import { replacer } from "./test-utils" diff --git a/test/index.test.ts b/test/index.test.ts index 98fd6df..852ec52 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -11,10 +11,9 @@ import type { Rule } from "eslint" import path from "node:path" import { describe, it, assert, beforeEach, afterEach } from "vitest" -// @ts-expect-error -- ignore -import * as tsParser from "@typescript-eslint/parser" +import tsParser from "@typescript-eslint/parser" import fs from "fs-extra" -import * as eslint from "eslint" +import eslint from "eslint" import { parse, parseForESLint } from "../src" import * as parser from "../src" import type { Node, VAttribute, VElement, VText } from "../src/ast" diff --git a/test/integrations.test.ts b/test/integrations.test.ts index e860487..651e661 100644 --- a/test/integrations.test.ts +++ b/test/integrations.test.ts @@ -3,11 +3,11 @@ //------------------------------------------------------------------------------ import { assert, beforeAll, describe, it } from "vitest" -import path from "path" -import fs from "fs-extra" +import path from "node:path" +import fs from "node:fs" import cp from "child_process" import eslintCompat from "./lib/eslint-compat" -import * as ESLintRaw from "eslint" +import ESLintRaw from "eslint" //------------------------------------------------------------------------------ // Helpers @@ -22,7 +22,6 @@ const FIXTURE_DIR = path.join(__dirname, "fixtures/integrations") describe("Integration tests", () => { beforeAll(async () => { - // @ts-expect-error -- ignore await import("ts-node/register") }) for (const target of fs.readdirSync(FIXTURE_DIR)) { diff --git a/test/parser-options-project.test.ts b/test/parser-options-project.test.ts index 77c8c51..257f4de 100644 --- a/test/parser-options-project.test.ts +++ b/test/parser-options-project.test.ts @@ -1,6 +1,5 @@ import { describe, it, assert } from "vitest" import { parseForESLint } from "../src" -// @ts-expect-error -- ignore import * as espree from "espree" import type { Linter } from "eslint" From a27eccc1c9a3de75a5a29318bac266f39712aa92 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:36:25 +0800 Subject: [PATCH 25/32] ci: try fix --- .github/workflows/CI.yml | 2 -- package.json | 4 ++-- scripts/ci-install-eslint.js | 3 +++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 09e612c..6bb0a76 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -55,8 +55,6 @@ 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 test-for-old-eslint: diff --git a/package.json b/package.json index 852636c..df18afc 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,9 @@ "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 .temp index.*", - "coverage": "vitest --coverage --ui", + "coverage": "vitest --coverage --ui", "lint": "eslint src test package.json", - "pretest": "run-s build lint", + "pretest": "npm run lint", "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", diff --git a/scripts/ci-install-eslint.js b/scripts/ci-install-eslint.js index c362669..289481f 100644 --- a/scripts/ci-install-eslint.js +++ b/scripts/ci-install-eslint.js @@ -1,6 +1,7 @@ "use strict" const { spawn } = require("child_process") +const semver = require("semver") function cd(path) { console.log("$ cd %s", path) @@ -30,6 +31,8 @@ function sh(command) { // Install ESLint of the requested version await sh(`npm install eslint@${requestedVersionSpec} -f`) + if (semver.compare(requestedVersionSpec, "9.0.0")) + await sh(`npm install @types/eslint -f`) // Install ESLint submodule of the requested version // const installedVersion = require("eslint/package.json").version From 5a219168a39d195a87643739cba4dd743bd7bb64 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:38:40 +0800 Subject: [PATCH 26/32] chore: up --- scripts/ci-install-eslint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci-install-eslint.js b/scripts/ci-install-eslint.js index 289481f..ab180f3 100644 --- a/scripts/ci-install-eslint.js +++ b/scripts/ci-install-eslint.js @@ -31,7 +31,7 @@ function sh(command) { // Install ESLint of the requested version await sh(`npm install eslint@${requestedVersionSpec} -f`) - if (semver.compare(requestedVersionSpec, "9.0.0")) + if (semver.compare(requestedVersion, "9.0.0")) await sh(`npm install @types/eslint -f`) // Install ESLint submodule of the requested version From 50e1e69541804f6b6d8b5faf35a15006bfe7f6e0 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:41:40 +0800 Subject: [PATCH 27/32] ci: fix --- scripts/ci-install-eslint.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/ci-install-eslint.js b/scripts/ci-install-eslint.js index ab180f3..fe2f96c 100644 --- a/scripts/ci-install-eslint.js +++ b/scripts/ci-install-eslint.js @@ -1,7 +1,6 @@ "use strict" const { spawn } = require("child_process") -const semver = require("semver") function cd(path) { console.log("$ cd %s", path) @@ -31,8 +30,8 @@ function sh(command) { // Install ESLint of the requested version await sh(`npm install eslint@${requestedVersionSpec} -f`) - if (semver.compare(requestedVersion, "9.0.0")) - await sh(`npm install @types/eslint -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 From 8acc52a9c9bd997fe25b23707010062870cda237 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:45:35 +0800 Subject: [PATCH 28/32] chore: use vite^6 for node18 test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df18afc..b325e75 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "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" @@ -67,7 +68,6 @@ "clean": "rimraf .temp index.*", "coverage": "vitest --coverage --ui", "lint": "eslint src test package.json", - "pretest": "npm run lint", "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", From 52150d8d04a64a2ec58938f7af0d418fc93b47d5 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 00:47:37 +0800 Subject: [PATCH 29/32] chore: update timeout config --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index c00bfcd..f0cdca4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ test: { reporters: "dot", include: ["test/**/*.test.ts"], - teardownTimeout: 60000, + testTimeout: 60000, coverage: { include: ["src"], }, From 2131857218c9282eb052ba4c0e896c9544cde334 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 01:02:37 +0800 Subject: [PATCH 30/32] chore: update --- eslint.config.mjs | 1 + tsconfig.json | 3 ++- tsconfig.test.json | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index f44e79b..423872c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,7 @@ export default [ ignores: [ ".temp", "**/node_modules", + "coverage", "src/html/util/alternative-cr.ts", "src/html/util/attribute-names.ts", "src/html/util/entities.ts", diff --git a/tsconfig.json b/tsconfig.json index c5dc0e4..677d8fe 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 index e507e28..a5e080f 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,6 +1,11 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "composite": true, + "paths": { + "*": ["typings/*"] + }, + "module": "esnext", "moduleResolution": "Bundler" }, "include": ["test/**/*.ts", "vitest.config.ts"] From 11147906e194e36f6d29a7df83642ee87bcae85f Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Sun, 6 Jul 2025 01:12:43 +0800 Subject: [PATCH 31/32] chore: flat diff --- .gitignore | 2 +- eslint.config.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1afb3f2..387f51a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /.temp -node_modules /coverage +node_modules /test/temp /index.* /npm-debug.log diff --git a/eslint.config.mjs b/eslint.config.mjs index 423872c..a46acd5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,8 +23,8 @@ export default [ { ignores: [ ".temp", - "**/node_modules", "coverage", + "**/node_modules", "src/html/util/alternative-cr.ts", "src/html/util/attribute-names.ts", "src/html/util/entities.ts", From a327579a7c8043fc454c1b5c5137ef3417a5a12e Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Tue, 8 Jul 2025 08:51:38 +0800 Subject: [PATCH 32/32] test: fix incorrect `fallback` --- test/test-utils.ts | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/test/test-utils.ts b/test/test-utils.ts index 7048f34..f530518 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -8,6 +8,7 @@ import type { 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. @@ -128,24 +129,4 @@ export function analyze( }) return result - - function getFallbackKeys(node: any) { - return Object.keys(node).filter(fallbackKeysFilter, node) - } - - function fallbackKeysFilter(key: string) { - const value = null - return ( - key !== "comments" && - key !== "leadingComments" && - key !== "loc" && - key !== "parent" && - key !== "range" && - key !== "tokens" && - key !== "trailingComments" && - typeof value === "object" && - // @ts-expect-error -- ignore - (typeof value.type === "string" || Array.isArray(value)) - ) - } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy