diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..7b519c7 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,195 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["import"], + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn", + // style/restriction off: opt-in, contradictory grab-bags. Wanted rules enabled individually below. + "style": "off", + "restriction": "off" + }, + "env": { + "browser": true, + "es2021": true + }, + "ignorePatterns": [ + "node_modules", + "dist", + "build", + ".svelte-kit", + ".vercel", + "*.config.js", + "*.config.ts" + ], + "rules": { + "no-console": "warn", + "no-debugger": "error", + "no-alert": "warn", + + // no-cycle resolves $-aliases via tsconfig auto-discovery (no resolver config in oxlint) + "import/no-cycle": "error", + "import/no-duplicates": "warn", + "import/no-unassigned-import": "off", // CSS/side-effect imports are intentional + + "no-sequences": "error", + "no-underscore-dangle": "off", + "no-shadow": "warn", + "no-implicit-coercion": "warn", + "no-await-in-loop": "warn", + "no-return-assign": "warn", + "no-new": "warn", + "no-unneeded-ternary": "warn" + }, + // FSD boundaries. oxlint has no zone rule, so layer/segment direction is enforced + // with no-restricted-imports patterns scoped per glob. Layer order (high->low): + // app(exempt top shell) > routes > widgets > features > entities > shared. + // A layer bans imports from itself (cross-slice via alias) and every layer above. + // Overrides are LAST-WINS, not merged: a file matching two overrides keeps only the + // last rule config. So the domain override (below) is a self-contained superset, and + // the test/story override (last) fully disables boundary checks for those files. + "overrides": [ + // shared = lowest layer: imports nothing above it + { + "files": ["src/shared/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": [ + "$app", + "$app/*", + "$routes", + "$routes/*", + "$widgets", + "$widgets/*", + "$features", + "$features/*", + "$entities", + "$entities/*" + ], + "message": "FSD layer violation: `shared` is the lowest layer and may not import from any layer above it." + } + ] + }] + } + }, + // entities: import shared only; no other entity via alias; interior ui<-only-ui + { + "files": ["src/entities/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": ["$app", "$app/*", "$routes", "$routes/*", "$widgets", "$widgets/*", "$features", "$features/*"], + "message": "FSD layer violation: `entities` may only import from `shared`." + }, + { + "group": ["$entities", "$entities/*"], + "message": "FSD cross-slice violation: do not import another entity via its alias. Use relative imports inside your own slice; invert the dependency through a higher layer for cross-slice needs." + }, + { + "group": ["../ui", "../ui/*", "../../ui/*"], + "message": "FSD segment violation: only `ui` may import `ui`. Interior direction is ui -> model -> domain." + } + ] + }] + } + }, + // features: import entities/shared only; no other feature via alias + { + "files": ["src/features/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": ["$app", "$app/*", "$routes", "$routes/*", "$widgets", "$widgets/*"], + "message": "FSD layer violation: `features` may only import from `entities` and `shared`." + }, + { + "group": ["$features", "$features/*"], + "message": "FSD cross-slice violation: do not import another feature via its alias. Invert the dependency through a higher layer (widget/route)." + }, + { + "group": ["../ui", "../ui/*", "../../ui/*"], + "message": "FSD segment violation: only `ui` may import `ui`. Interior direction is ui -> model -> domain." + } + ] + }] + } + }, + // widgets: import features/entities/shared only; no other widget via alias + { + "files": ["src/widgets/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": ["$app", "$app/*", "$routes", "$routes/*"], + "message": "FSD layer violation: `widgets` may only import from `features`, `entities`, and `shared`." + }, + { + "group": ["$widgets", "$widgets/*"], + "message": "FSD cross-slice violation: do not import another widget via its alias. Invert the dependency through the route layer." + }, + { + "group": ["../ui", "../ui/*", "../../ui/*"], + "message": "FSD segment violation: only `ui` may import `ui`. Interior direction is ui -> model -> domain." + } + ] + }] + } + }, + // routes: top of the FSD list, imports any layer below; only app is above it + { + "files": ["src/routes/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { "group": ["$app", "$app/*"], "message": "FSD layer violation: `routes` may not import from `app`." } + ] + }] + } + }, + // domain (FSD+): pure logic. Imports NO layer (not even shared) and no sibling + // model/ui segment. Superset: wins over the layer override above for these files. + { + "files": ["src/**/domain/**"], + "rules": { + "no-restricted-imports": ["error", { + "patterns": [ + { + "group": [ + "$app", + "$app/*", + "$routes", + "$routes/*", + "$widgets", + "$widgets/*", + "$features", + "$features/*", + "$entities", + "$entities/*", + "$shared", + "$shared/*" + ], + "message": "FSD+ domain isolation: `domain` is pure business logic and may not import any layer (including `shared`). Allowed: relative imports within `domain` and framework-agnostic npm packages." + }, + { + "group": ["../model", "../model/*", "../../model/*", "../ui", "../ui/*", "../../ui/*"], + "message": "FSD+ domain isolation: `domain` may not import sibling `model` or `ui` segments. Dependency flows ui -> model -> domain, never back." + } + ] + }] + } + }, + // tests/stories/fixtures legitimately cross-import (e.g. $entities/Font/testing). + // Must be LAST so last-wins disables boundary checks for them. + { + "files": ["**/*.test.ts", "**/*.spec.ts", "**/*.stories.svelte", "src/**/testing/**"], + "rules": { + "no-restricted-imports": "off" + } + } + ] +} diff --git a/oxlint.json b/oxlint.json deleted file mode 100644 index ab5c55e..0000000 --- a/oxlint.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "plugins": ["import"], - "categories": { - "correctness": "error", - "suspicious": "warn", - "perf": "warn", - "style": "warn", - "restriction": "error" - }, - "env": { - "browser": true, - "es2021": true - }, - "ignore": [ - "node_modules", - "dist", - "build", - ".svelte-kit", - ".vercel", - "*.config.js", - "*.config.ts" - ], - "rules": { - "no-console": "off", - "no-debugger": "error", - "no-alert": "warn", - "import/no-cycle": "error" - } -}