import react from "@eslint-react/eslint-plugin"; import stylistic from "@stylistic/eslint-plugin"; import query from "@tanstack/eslint-plugin-query"; import vitest from "@vitest/eslint-plugin"; import { ESLint } from "eslint"; import { importX } from "eslint-plugin-import-x"; import jestDom from "eslint-plugin-jest-dom"; import noUnsanitized from "eslint-plugin-no-unsanitized"; import perfectionist from "eslint-plugin-perfectionist"; import hooks from "eslint-plugin-react-hooks"; import refresh from "eslint-plugin-react-refresh"; import regexp from "eslint-plugin-regexp"; import security from "eslint-plugin-security"; import sonarjs from "eslint-plugin-sonarjs"; import storybook from "eslint-plugin-storybook"; import testingLibrary from "eslint-plugin-testing-library"; import unicorn from "eslint-plugin-unicorn"; import assert from "node:assert/strict"; import { globSync } from "node:fs"; import { test } from "node:test"; import tseslint from "typescript-eslint"; test("ESLint enforces the frontend profile and excludes generated artifacts", async () => { const eslint = new ESLint(); for (const [filePath, code, rule] of [ [ "src/shared/api/problem-details.ts", "export function read(value: string) { return value?.trim(); }", "@typescript-eslint/no-unnecessary-condition", ], [ "src/shared/api/problem-details.ts", "export function read(value?: string) { return value!.trim(); }", "@typescript-eslint/no-non-null-assertion", ], [ "src/shared/api/problem-details.ts", "import { ReactNode } from \"react\"; export type Content = ReactNode;", "@typescript-eslint/consistent-type-imports", ], [ "src/shared/api/problem-details.ts", "export function run() { Promise.resolve(); }", "@typescript-eslint/no-floating-promises", ], [ "packages/ui/src/EntryCard/EntryCard.tsx", "import { useState } from \"react\"; export function Example({ active }: { active: boolean }) { if (active) useState(0); return null; }", "react-hooks/rules-of-hooks", ], [ "src/shared/api/problem-details.ts", "export const values = Array(3).fill({});", "unicorn/no-array-fill-with-reference-type", ], [ "src/shared/api/problem-details.ts", "export default 1;", "import-x/no-default-export", ], [ "src/shared/api/problem-details.ts", "new Error(\"forgot to throw\");", "sonarjs/no-unthrown-error", ], [ "src/shared/api/problem-details.ts", "export const pattern = /[aab]/;", "regexp/no-dupe-characters-character-class", ], [ "src/shared/api/problem-details.ts", "export type Status = \"z\" | \"a\";", "perfectionist/sort-union-types", ], [ "src/shared/api/problem-details.ts", "//missing space\nexport const ready = true;", "@stylistic/spaced-comment", ], [ "src/shared/api/problem-details.ts", "export function pattern(input: string) { return new RegExp(input); }", "security/detect-non-literal-regexp", ], [ "scripts/check-openapi.mjs", "import { exec } from \"node:child_process\"; exec(process.argv[2]);", "security/detect-child-process", ], [ "src/shared/api/problem-details.ts", "export function renderHtml(element: HTMLElement, html: string) { element.innerHTML = html; }", "no-unsanitized/property", ], [ "src/shared/api/problem-details.ts", "export function renderHtml(element: HTMLElement, html: string) { element.insertAdjacentHTML(\"beforeend\", html); }", "no-unsanitized/method", ], [ "packages/ui/src/EntryCard/EntryCard.test.tsx", "import { expect } from \"vitest\"; expect(document.activeElement).toBe(document.body);", "jest-dom/prefer-focus", ], [ "src/shared/api/client.test.ts", "import { it } from \"vitest\"; it.only(\"focused\", () => {});", "vitest/no-focused-tests", ], [ "packages/ui/src/EntryCard/EntryCard.test.tsx", "import { screen } from \"@testing-library/react\"; screen.findByText(\"ready\");", "testing-library/await-async-queries", ], [ "packages/ui/src/EntryCard/EntryCard.tsx", "import { QueryClient } from \"@tanstack/react-query\"; export function Example() { const client = new QueryClient(); return
{String(client)}
; }", "@tanstack/query/stable-query-client", ], ]) { const [result] = await eslint.lintText(code, { filePath }); assert.equal(result.fatalErrorCount, 0, filePath); assert.ok( result.messages.some(message => message.ruleId === rule), rule, ); } for (const file of [ "src/shared/api/generated/schema.d.ts", "packages/ui/dist/index.js", "storybook-static/index.js", "coverage/index.js", ]) assert.equal(await eslint.isPathIgnored(file), true, file); const story = await eslint.calculateConfigForFile( "src/shared/ui/Button.stories.tsx", ); assert.equal(story.rules["import-x/no-default-export"][0], 0); assert.ok(story.rules["storybook/default-exports"][0] > 0); assert.equal(story.rules["react-refresh/only-export-components"], undefined); }); test("recommended plugin rules retain the explicit null-contract exception", async () => { const eslint = new ESLint(); const source = "src/shared/api/problem-details.ts"; const component = "packages/ui/src/EntryCard/EntryCard.tsx"; const unit = "packages/ui/src/EntryCard/EntryCard.test.tsx"; for (const [preset, filePath] of [ [tseslint.configs.recommendedTypeChecked, source], [[importX.flatConfigs.recommended, importX.flatConfigs.typescript], source], [unicorn.configs.recommended, source], [sonarjs.configs.recommended, source], [regexp.configs.recommended, source], [security.configs.recommended, source], [noUnsanitized.configs.recommended, source], [perfectionist.configs["recommended-natural"], source], [stylistic.configs.recommended, component], [react.configs["recommended-type-checked"], component], [hooks.configs.flat.recommended, component], [refresh.configs.vite, component], [query.configs["flat/recommended"], component], [vitest.configs.recommended, unit], [testingLibrary.configs["flat/react"], unit], [jestDom.configs["flat/recommended"], unit], ...storybook.configs["flat/recommended"] .filter(config => config.rules) .map(config => [ config, config.name.endsWith("main-rules") ? ".storybook/main.ts" : "src/shared/ui/Button.stories.tsx", ]), ]) { const configs = [preset].flat(); const expected = Object.assign( {}, ...configs.map(config => config.rules), ); const config = await eslint.calculateConfigForFile(filePath); const actual = new Map(Object.entries(config.rules)); for (const [rule, setting] of Object.entries(expected)) { const severity = Array.isArray(setting) ? setting[0] : setting; if (severity === "off" || severity === 0 || !rule.includes("/")) continue; if (rule === "unicorn/no-null") { assert.equal(actual.get(rule)?.[0], 0); continue; } assert.ok(actual.get(rule)?.[0] > 0, `${filePath}: ${rule}`); } } }); test("ESLint formatting is idempotent and preserves TSX generic syntax", async () => { const eslint = new ESLint({ fix: true }); const filePath = "packages/ui/src/EntryCard/EntryCard.tsx"; const [fixed] = await eslint.lintText( "//identity\nconst identity = (value: T): T => value;\nexport {identity};", { filePath }, ); assert.equal(fixed.fatalErrorCount, 0); assert.equal(fixed.errorCount, 0); assert.ok(fixed.output); const [again] = await eslint.lintText(fixed.output, { filePath }); assert.equal(again.errorCount, 0); assert.equal(again.output, undefined); }); test("ESLint formatting preserves spaces around JSX text and interpolations", async () => { const eslint = new ESLint({ fix: true }); const [fixed] = await eslint.lintText( "const Example = () => {\nconst value = 'value';\nreturn Amount: {value} / {value};\n};\nexport { Example };", { filePath: "packages/ui/src/EntryCard/EntryCard.tsx" }, ); assert.equal(fixed.errorCount, 0); assert.ok(fixed.output?.includes("Amount: {value} / {value}")); }); // Single-use codes must be caught too; fixture literals remain independent wire assertions. test("machine codes require enum members without banning ordinary text or Prisma selects", async () => { const eslint = new ESLint(); const filePath = "src/shared/api/problem-details.ts"; for (const code of [ "export function check(code: string) { return code === \"one-off-code\"; }", "export const result = `MOEX_UNAVAILABLE`;", "export const result = { code: \"one-off-code\" };", "export const result = { \"errorCode\": \"one-off-code\" };", "export const result = { code: `dynamic-${Date.now()}` };", "export const result = \"MOEX_UNAVAILABLE\";", "export const result = [\"INSTRUMENT_ID_TYPE_FIGI\"];", "export const result = { OPERATION_TYPE_COUPON: \"income\" };", ]) { const [result] = await eslint.lintText(code, { filePath }); assert.equal(result.fatalErrorCount, 0, code); assert.ok( result.messages.some(({ ruleId }) => ruleId === "no-restricted-syntax"), code, ); } for (const code of [ "enum Code { Unavailable = \"MOEX_UNAVAILABLE\" } export const result = { code: Code.Unavailable };", "export const select = { code: true, errorCode: true };", "export const label = \"Please try again\"; export const key = \"JWT_SECRET\";", ]) { const [result] = await eslint.lintText(code, { filePath }); assert.equal(result.fatalErrorCount, 0, code); assert.ok( result.messages.every(({ ruleId }) => ruleId !== "no-restricted-syntax"), code, ); } const [fixture] = await eslint.lintText( "export const result = { code: \"MOEX_UNAVAILABLE\" };", { filePath: "src/shared/api/client.test.ts" }, ); assert.ok( fixture.messages.every(({ ruleId }) => ruleId !== "no-restricted-syntax"), ); const [comparison] = await eslint.lintText( "enum Code { Ready = \"ready\" } export function check(code: Code) { return code === \"ready\"; }", { filePath }, ); assert.ok( comparison.messages.some( ({ ruleId }) => ruleId === "@typescript-eslint/no-unsafe-enum-comparison", ), ); }); test("readability enforces syntax without banning VM and schema patterns", async () => { const eslint = new ESLint(); const component = "src/entities/bank-card/ui/BankCardForm/BankCardForm.tsx"; const model = "src/entities/income/api/useIncome.ts"; const invalid = [ [component, "const Example = () => ;", "frontend/readability"], [component, "const Example = () =>