diff --git a/web/eslint-plugins/README.md b/web/eslint-plugins/README.md index 5b7fde1..0499615 100644 --- a/web/eslint-plugins/README.md +++ b/web/eslint-plugins/README.md @@ -132,10 +132,11 @@ hct-авторство. фактическому пути, поэтому относительным импортом правило не обойти. - Старое: `routes/v1/**`, `lib/v1/**`. -- Новое: `routes/**` без `v1/**`, `lib/components/**`, `lib/registry/**`, - `lib/catalog.ts`, `lib/categories.ts`, `lib/tool-icons.ts`, - `lib/registry-schema.ts`, `lib/registry-schema.test.ts`. -- Общее (разрешено обоим): `core/`, `i18n/`, `theme`, `assets/`, корневой `lib`. +- Новое: `routes/**` без `v1/**`; корневой `lib` целиком (`lib/**`) без + `lib/v1/**` и без общих пакетов. +- Общее (разрешено обоим) — только служебные пакеты: `core/`, `theme`. + v1 не высовывается: из `lib/v1/**` наружу разрешены импорты лишь в + общие пакеты и в собственный форк i18n (`lib/v1/i18n/**`). - Плагин **конфигурируем** (опции `old`/`new` + `root`/`alias` в `eslint.config.js`): перенос старых файлов в папки `old/` — это правка glob-паттернов в настройке, а не код правила. diff --git a/web/eslint-plugins/__fixtures__/src/lib/v1/i18n/t.ts b/web/eslint-plugins/__fixtures__/src/lib/v1/i18n/t.ts new file mode 100644 index 0000000..3b5add1 --- /dev/null +++ b/web/eslint-plugins/__fixtures__/src/lib/v1/i18n/t.ts @@ -0,0 +1 @@ +// Fixture stub for lint-rule tests: content is ignored, must exist for import resolution. \ No newline at end of file diff --git a/web/eslint-plugins/__tests__/no-mixed-imports.test.ts b/web/eslint-plugins/__tests__/no-mixed-imports.test.ts index ac8be68..1dc5571 100644 --- a/web/eslint-plugins/__tests__/no-mixed-imports.test.ts +++ b/web/eslint-plugins/__tests__/no-mixed-imports.test.ts @@ -7,8 +7,8 @@ // // Side map (mirrors the defaults in no-mixed-imports.js): // old -> routes/v1/**, lib/v1/** -// new -> routes/** (minus v1), lib/components/**, lib/registry/** + a few -// shared-> everything else: core/, i18n/, theme, ... +// new -> routes/** (minus v1), lib/** (minus lib/v1/** and shared) +// shared-> everything else: core/, theme import { describe, expect, it } from "vitest"; import noMixedImports from "../isolation/no-mixed-imports.js"; import { asRuleModule, verifyInFixtures, type FlatConfig } from "./helpers.js"; @@ -80,15 +80,27 @@ describe("isolation/no-mixed-imports", () => { expectClean("lib/v1/old.ts", 'import { e } from "../core/errors";'); }); - it("allows old -> shared (i18n and theme via $lib)", () => { - expectClean("routes/v1/+layout.svelte", 'import { t } from "$lib/i18n/t";'); + it("flags old -> root i18n ($lib/i18n is the new branch now)", () => { + expectViolation( + "routes/v1/+layout.svelte", + 'import { t } from "$lib/i18n/t";', + "old", + "new", + ); + }); + + it("allows old -> own i18n copy and theme via $lib", () => { + expectClean( + "routes/v1/+layout.svelte", + 'import { t } from "$lib/v1/i18n/t";', + ); expectClean( "routes/v1/+layout.svelte", 'import { g } from "$lib/theme.svelte";', ); }); - it("allows new -> shared and new -> new", () => { + it("allows new -> new (root i18n and components)", () => { expectClean("routes/+page.svelte", 'import { t } from "$lib/i18n/t";'); expectClean( "routes/+page.svelte", @@ -96,11 +108,26 @@ describe("isolation/no-mixed-imports", () => { ); }); - it("leaves shared files unrestricted in both directions", () => { - expectClean("lib/i18n/t.ts", 'import { o } from "$lib/v1/old";'); + it("flags new i18n importing old and leaves core shared", () => { + expectViolation( + "lib/i18n/t.ts", + 'import { o } from "$lib/v1/old";', + "new", + "old", + ); expectClean( "lib/core/errors.ts", 'import { c } from "$lib/components/CheckerCanvas.svelte";', ); }); + + it("keeps the old i18n copy on the old side", () => { + expectClean("lib/v1/i18n/t.ts", 'import { o } from "$lib/v1/old";'); + expectViolation( + "lib/v1/i18n/t.ts", + 'import { c } from "$lib/components/CheckerCanvas.svelte";', + "old", + "new", + ); + }); }); diff --git a/web/eslint-plugins/isolation/no-mixed-imports.js b/web/eslint-plugins/isolation/no-mixed-imports.js index 13d55b9..d6e48ff 100644 --- a/web/eslint-plugins/isolation/no-mixed-imports.js +++ b/web/eslint-plugins/isolation/no-mixed-imports.js @@ -5,7 +5,7 @@ * `$lib/...` alias and relative `./` / `../` paths), classifies the source * file and each import target by their actual location, and reports whenever an * "old" file imports a "new" one or vice versa. Files that match neither side - * are "shared" (core/, i18n/, theme, assets, tests, ...) and can import freely. + * are "shared" (core/, theme, assets, tests, ...) and can import freely. * * Options (object, all optional): * - root: path from lint cwd to the source dir (default "src"); @@ -17,21 +17,19 @@ import fs from "node:fs"; import path from "node:path"; -// NEW declared as an allowlist: `lib/**` descends into v1/ and the shared dirs -// (core/, i18n/, assets/, theme), so matching by prefix would misclassify them. -// Negations (`!`) carve old paths out of `routes/**`. +// NEW declared as an allowlist: `lib/**` descends into v1/ and the shared +// packages (core/, theme), so negations carve them back out; `routes/**` +// needs `!routes/v1/**` for the old branch. Last-match-wins +// (see matchesGlobList). const DEFAULT_OLD = ["routes/v1/**", "lib/v1/**"]; const DEFAULT_NEW = [ "routes/**", "!routes/v1/**", - "lib/components/**", - "lib/registry/**", - "lib/catalog.ts", - "lib/categories.ts", - "lib/tool-icons.ts", - "lib/registry-schema.ts", - "lib/registry-schema.test.ts", + "lib/**", + "!lib/v1/**", + "!lib/core/**", + "!lib/theme.svelte.ts", ]; /** Escape everything except glob metacharacters, then handle **, *, ?. */ diff --git a/web/eslint.config.js b/web/eslint.config.js index 9b46c31..450c3c9 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -82,6 +82,7 @@ export default tseslint.config( "eslint-plugins/__tests__/helpers.ts", "eslint-plugins/__tests__/no-mixed-imports.test.ts", "eslint-plugins/__fixtures__/src/lib/v1/old.ts", + "eslint-plugins/__fixtures__/src/lib/v1/i18n/t.ts", "eslint-plugins/__fixtures__/src/lib/core/errors.ts", "eslint-plugins/__fixtures__/src/lib/i18n/t.ts", "eslint-plugins/__fixtures__/src/lib/theme.svelte.ts", @@ -197,7 +198,7 @@ export default tseslint.config( // Полная взаимная изоляция веток (см. plan-composite-params, Фаза 5). // Кастомный плагин isolation/no-mixed-imports резолвит импорты по реальному // пути (и $lib, и относительные) и ругается на old→new / new→old. - // Общее (core/, i18n/, theme) разрешено обоим. + // Общее (core/, theme) разрешено обоим. { files: ["**/*.{ts,svelte}"], plugins: { diff --git a/web/src/lib/i18n/dict.ts b/web/src/lib/i18n/dict.ts index dda97f3..382ddc3 100644 --- a/web/src/lib/i18n/dict.ts +++ b/web/src/lib/i18n/dict.ts @@ -1,4 +1,4 @@ -import type { CategoryId } from "../v1/categories"; +import type { CategoryId } from "../categories"; export const LOCALES = ["ru", "en"] as const; export type Locale = (typeof LOCALES)[number]; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index fbd4b81..f0a3709 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -1,6 +1,5 @@ - +
diff --git a/web/src/routes/v1/+layout.svelte b/web/src/routes/v1/+layout.svelte index 0837a2a..eda0668 100644 --- a/web/src/routes/v1/+layout.svelte +++ b/web/src/routes/v1/+layout.svelte @@ -1,6 +1,5 @@ - +
diff --git a/web/src/lib/assets/favicon.svg b/web/static/favicon.svg similarity index 100% rename from web/src/lib/assets/favicon.svg rename to web/static/favicon.svg