diff --git a/eslint.config.mjs b/eslint.config.mjs index b6011ae7..ee74eafa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,13 @@ - import tsParser from "@typescript-eslint/parser"; import tsPlugin from "@typescript-eslint/eslint-plugin"; -import playwrightPlugin from "eslint-plugin-playwright"; -import prettierPlugin from "eslint-plugin-prettier"; let localOverride = {}; try { - localOverride = (await import("./.eslint.local.config.js")).default; - console.log("✅ Loaded local ESLint override config."); + localOverride = (await import("./eslint.local.config.js")).default; + console.log("✅ Loaded local ESLint override config from root."); } catch (err) { - console.log("ℹ️ No local override config found."); + // No root override } export default [ @@ -34,16 +31,14 @@ export default [ }, plugins: { "@typescript-eslint": tsPlugin, - playwright: playwrightPlugin, - prettier: prettierPlugin, }, rules: { - "no-unused-vars": "error", - "playwright/no-wait-for-timeout": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-explicit-any": "off", "no-console": "off", - "prettier/prettier": "error", + ...(localOverride?.rules || {}), }, }, -]; +]; \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index a4058020..a89b501c 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -565,9 +565,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", - "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "dev": true, "license": "MIT", "dependencies": { @@ -1001,17 +1001,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1197,21 +1186,6 @@ "dev": true, "license": "MIT" }, - "node_modules/contextify": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/contextify/-/contextify-0.1.15.tgz", - "integrity": "sha512-NpM4b6u5Mzig1/ux3ReVv42L/og3WcKKvkmTWxZpIjhZ/S23BViWZD/7hds9LGNzEL3W9ItfoZ+p6eRhCQMH6Q==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "bindings": "^1.2.1", - "nan": "^2.1.0" - }, - "engines": { - "node": ">=0.10.11" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1480,9 +1454,9 @@ } }, "node_modules/eslint-plugin-playwright": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", - "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.8.3.tgz", + "integrity": "sha512-h87JPFHkz8a6oPhn8GRGGhSQoAJjx0AkOv1jME6NoMk2FpEsfvfJJNaQDxLSqSALkCr0IJXPGTnp6SIRVu5Nqg==", "dev": true, "license": "MIT", "workspaces": [ @@ -1495,13 +1469,19 @@ "node": ">=16.6.0" }, "peerDependencies": { - "eslint": ">=8.40.0" + "eslint": ">=8.40.0", + "eslint-plugin-jest": ">=25" + }, + "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + } } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.0.tgz", + "integrity": "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1803,14 +1783,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2541,14 +2513,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nan": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", - "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package-lock.json b/package-lock.json index 6a34e41f..0d0d1c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,12 @@ "@mermaid-js/mermaid-cli": "10.6.1", "@octokit/core": "^5.0.0", "@playwright/test": "^1.52.0", - "@types/node": "^24.0.7", + "@types/node": "^24.0.3", "@typescript-eslint/eslint-plugin": "^8.35.0", "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.0.0", - "eslint-plugin-playwright": "^2.2.0", - "eslint-plugin-prettier": "^5.5.1", + "eslint-plugin-playwright": "^1.8.3", + "eslint-plugin-prettier": "^5.5.0", "jq": "^1.6.0", "marked": "15.0.12", "prettier": "^3.3.2" @@ -584,9 +584,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", - "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "dev": true, "license": "MIT", "dependencies": { @@ -1499,9 +1499,9 @@ } }, "node_modules/eslint-plugin-playwright": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", - "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.8.3.tgz", + "integrity": "sha512-h87JPFHkz8a6oPhn8GRGGhSQoAJjx0AkOv1jME6NoMk2FpEsfvfJJNaQDxLSqSALkCr0IJXPGTnp6SIRVu5Nqg==", "dev": true, "license": "MIT", "workspaces": [ @@ -1514,13 +1514,19 @@ "node": ">=16.6.0" }, "peerDependencies": { - "eslint": ">=8.40.0" + "eslint": ">=8.40.0", + "eslint-plugin-jest": ">=25" + }, + "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + } } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", - "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.0.tgz", + "integrity": "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7172a2b3..c100de5c 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,10 @@ "@mermaid-js/mermaid-cli": "10.6.1", "@octokit/core": "^5.0.0", "@playwright/test": "^1.52.0", - "@types/node": "^24.0.7", + "@types/node": "^24.0.3", "@typescript-eslint/eslint-plugin": "^8.35.0", "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.0.0", - "eslint-plugin-playwright": "^2.2.0", - "eslint-plugin-prettier": "^5.5.1", "jq": "^1.6.0", "marked": "15.0.12", "prettier": "^3.3.2" diff --git a/tests/eslint-config.test.ts b/tests/eslint-config.test.ts deleted file mode 100644 index e05e6277..00000000 --- a/tests/eslint-config.test.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { execSync } from "node:child_process"; -import fs from "node:fs/promises"; -import path from "node:path"; - -interface ESLintMessage { - ruleId: string | null; - severity: number; - message: string; - line: number; - column: number; - nodeType?: string; - endLine?: number; - endColumn?: number; - fix?: unknown; -} - -interface ESLintResult { - filePath: string; - messages: ESLintMessage[]; - errorCount: number; - warningCount: number; - fixableErrorCount: number; - fixableWarningCount: number; - source?: string; -} - -function runESLint(filePath: string): ESLintResult[] { - try { - const output = execSync(`npx eslint ${filePath} -f json`, { - encoding: "utf-8", - stdio: "pipe", - }); - const jsonStart = output.indexOf("["); - if (jsonStart === -1) throw new Error("No JSON array found in ESLint output"); - return JSON.parse(output.slice(jsonStart)); - } catch (err: any) { - const output = err.stdout?.toString() || err.stderr?.toString() || ""; - const jsonStart = output.indexOf("["); - if (jsonStart !== -1) { - try { - return JSON.parse(output.slice(jsonStart)); - } catch { - console.error("Failed to parse ESLint output JSON"); - } - } - console.error("ESLint failed and no JSON output could be parsed"); - return []; - } -} - -const localConfigPath = path.join("tests", "eslint.local.config.mjs"); -const backupPath = path.join("tests", "eslint.local.config.backup.mjs"); - -const testCases: { - name: string; - code: string; - expectedRule: string | null; -}[] = [ - { - name: "unused variable (no-unused-vars)", - code: `const unused = 42;`, - expectedRule: "no-unused-vars", - }, - // { - // name: "use of any type (@typescript-eslint/no-explicit-any)", - // code: `function test(value: any) { return value; }`, - // expectedRule: "@typescript-eslint/no-explicit-any", - // }, - // { - // name: "use of console (no-console)", - // code: `console.log("Hello");`, - // expectedRule: "no-console", - // }, - { - name: "prettier formatting issue (prettier/prettier)", - code: `const x = 1;`, - expectedRule: "prettier/prettier", - }, - { - name: "clean code (no errors)", - code: `function add(a: number, b: number) {\n return a + b;\n}\n`, - expectedRule: null, - }, -]; - -test.describe("ESLint config validation (ESM)", () => { - for (const { name, code, expectedRule } of testCases) { - test(name, async () => { - // Temporarily rename local override file if present inside tests folder - let renamed = false; - try { - await fs.rename(localConfigPath, backupPath); - renamed = true; - } catch {} - - const fileName = `lint-demo-${name.replace(/[^a-z0-9\-]/gi, "-")}.ts`; - const filePath = path.join("tests", fileName); - - await fs.mkdir("tests", { recursive: true }); - await fs.writeFile(filePath, code); - - const results = runESLint(filePath); - const ruleIds: string[] = []; - - for (const fileResult of results) { - if (Array.isArray(fileResult.messages)) { - for (const message of fileResult.messages) { - if (typeof message.ruleId === "string") { - ruleIds.push(message.ruleId); - } - } - } - } - - console.log(`▶ Rule IDs for ${fileName}:`, ruleIds); - - if (expectedRule) { - expect(ruleIds).toContain(expectedRule); - } else { - const ignored = ["prettier/prettier", "no-unused-vars"]; - const actualRelevant = ruleIds.filter((id) => !ignored.includes(id)); - expect(actualRelevant.length).toBe(0); - } - - await fs.unlink(filePath); - - // Restore the override file if it was renamed - if (renamed) { - await fs.rename(backupPath, localConfigPath); - } - }); - } - - test("should not crash if tests/eslint.local.config.mjs is missing", async () => { - const filePath = "tests/lint-no-override.ts"; - - await fs.mkdir("tests", { recursive: true }); - await fs.writeFile(filePath, `const test = 1;`); - - let renamed = false; - try { - await fs.rename(localConfigPath, backupPath); - renamed = true; - } catch {} - - const results = runESLint(filePath); - const ruleIds: string[] = []; - - for (const fileResult of results) { - if (fileResult.messages) { - for (const msg of fileResult.messages) { - if (msg.ruleId) { - ruleIds.push(msg.ruleId); - } - } - } - } - - console.log(`▶ Rule IDs for ${filePath}:`, ruleIds); - - expect(Array.isArray(ruleIds)).toBe(true); - - await fs.unlink(filePath); - - if (renamed) { - await fs.rename(backupPath, localConfigPath); - } - }); - - test("should apply local override config correctly", async () => { - const configPath = "eslint.config.mjs"; - const filePath = "tests/lint-override-applied.mjs"; - const backupConfigPath = "eslint.config,mjs.backup"; - - await fs.mkdir("tests", { recursive: true }); - - // Backup eslint.config.js if it exists - let backedUp = false; - try { - await fs.access(configPath); - await fs.rename(configPath, backupConfigPath); - backedUp = true; - console.log("⚠️ Backed up original eslint.config.mjs"); - } catch {} - - try { - // Step 1: Write override file inside tests folder - await fs.writeFile( - localConfigPath, - `export default { - rules: { - 'no-console': 'off' - } -};` - ); - - // Step 2: Write the test ESLint config that uses the override - await fs.writeFile( - configPath, - ` -import tsParser from "@typescript-eslint/parser"; -import tsPlugin from "@typescript-eslint/eslint-plugin"; -import playwrightPlugin from "eslint-plugin-playwright"; -import prettierPlugin from "eslint-plugin-prettier"; - -let localOverride = {}; - -try { - localOverride = (await import("./tests/.eslint.local.config.js")).default; - console.log("✅ Loaded local ESLint override config."); -} catch (err) { - console.log("ℹ️ No local override config found."); -} - -export default [ - { - ignores: [], - }, - { - files: ["**/*.ts"], - languageOptions: { - ecmaVersion: 2021, - sourceType: "module", - parser: tsParser, - }, - plugins: { - "@typescript-eslint": tsPlugin, - playwright: playwrightPlugin, - prettier: prettierPlugin, - }, - rules: { - "no-console": "error", - "prettier/prettier": "error", - ...(localOverride?.rules || {}) - }, - }, -]; -` - ); - - // Step 3: Write test file that triggers no-console - await fs.writeFile(filePath, `console.log("This should pass");`); - - // Run ESLint normally (your runESLint function uses default config) - const results = runESLint(filePath); - const ruleIds: string[] = []; - - for (const fileResult of results) { - if (fileResult.messages) { - for (const msg of fileResult.messages) { - if (msg.ruleId) { - ruleIds.push(msg.ruleId); - } - } - } - } - - console.log(`▶ Rule IDs for override test:`, ruleIds); - - expect(ruleIds).not.toContain("no-console"); - - // Clean up test files - await fs.unlink(filePath); - await fs.unlink(localConfigPath); - await fs.unlink(configPath); - } finally { - // Restore original eslint.config.js if backed up - if (backedUp) { - await fs.rename(backupConfigPath, configPath); - console.log("✅ Restored original eslint.config.js"); - } - } - }); - - test("should fallback gracefully if local override config is broken", async () => { - const filePath = "tests/lint-broken-override.ts"; - - await fs.mkdir("tests", { recursive: true }); - - // Write invalid override inside tests folder - await fs.writeFile( - localConfigPath, - `export default { - rules: { - 'no-console': 'off', -}; // syntax error` - ); - - await fs.writeFile(filePath, `const test = 1;`); - - const results = runESLint(filePath); - const ruleIds: string[] = []; - - for (const fileResult of results) { - if (fileResult.messages) { - for (const msg of fileResult.messages) { - if (msg.ruleId) { - ruleIds.push(msg.ruleId); - } - } - } - } - - console.log(`▶ Rule IDs with broken override:`, ruleIds); - - expect(Array.isArray(ruleIds)).toBe(true); - - await fs.unlink(filePath); - await fs.unlink(localConfigPath); - }); -}); \ No newline at end of file