5c47498472
- eslint.config.js: disable no-non-null-assertion for *.test.ts/tsx and tests/** (fixture-driven tests routinely use arr[0]! after a length check — signal there is low). - closestFlight.ts: replace flights.legs[0]! / flights[flights.length-1]! with explicit null checks. - FlightDetailsAccordion.tsx: refactor transition + meal/service branches to use local consts narrowed by a preceding truthy check, dropping the `leg.transition!.registration!` patterns. Warning count: 190 → 65. Remaining warnings are pre-existing production-code non-null assertions spread across the codebase.
193 lines
6.4 KiB
JavaScript
193 lines
6.4 KiB
JavaScript
// ESLint v9 flat config. Mirrors the rule set from
|
|
// docs/superpowers/plans/2026-04-14-phase-1a1-skeleton.md (Task 4).
|
|
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import unusedImports from "eslint-plugin-unused-imports";
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
|
|
export default [
|
|
{
|
|
ignores: [
|
|
"dist/**",
|
|
"node_modules/**",
|
|
"ClientApp/**",
|
|
"wwwroot/**",
|
|
"**/*.cjs",
|
|
"pnpm-lock.yaml",
|
|
],
|
|
},
|
|
js.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
languageOptions: {
|
|
parser: tseslint.parser,
|
|
parserOptions: {
|
|
ecmaVersion: 2023,
|
|
sourceType: "module",
|
|
project: "./tsconfig.json",
|
|
ecmaFeatures: { jsx: true },
|
|
},
|
|
},
|
|
plugins: {
|
|
"unused-imports": unusedImports,
|
|
},
|
|
rules: {
|
|
"@typescript-eslint/no-unused-vars": "off",
|
|
"unused-imports/no-unused-imports": "error",
|
|
"unused-imports/no-unused-vars": [
|
|
"warn",
|
|
{
|
|
vars: "all",
|
|
varsIgnorePattern: "^_",
|
|
args: "after-used",
|
|
argsIgnorePattern: "^_",
|
|
},
|
|
],
|
|
"@typescript-eslint/consistent-type-imports": [
|
|
"error",
|
|
{ prefer: "type-imports" },
|
|
],
|
|
"@typescript-eslint/no-explicit-any": "warn",
|
|
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
},
|
|
},
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
plugins: {
|
|
boundaries,
|
|
},
|
|
settings: {
|
|
"import/resolver": {
|
|
node: {
|
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
},
|
|
},
|
|
"boundaries/elements": [
|
|
{ type: "routes", pattern: "src/routes/*" },
|
|
{ type: "mf", pattern: "src/mf/*" },
|
|
{ type: "features", pattern: "src/features/*", capture: ["feature"] },
|
|
{ type: "ui", pattern: "src/ui/*" },
|
|
{ type: "shared", pattern: "src/shared/*" },
|
|
{ type: "observability", pattern: "src/observability/*" },
|
|
{ type: "i18n", pattern: "src/i18n/*" },
|
|
{ type: "env", pattern: "src/env/*" },
|
|
],
|
|
},
|
|
rules: {
|
|
// Design spec §1.2 layered dependency direction:
|
|
// features/ cannot import routes/ or mf/
|
|
// ui/ cannot import features/
|
|
// shared/ cannot import features/, routes/, mf/, observability/
|
|
// observability/ cannot import features/, routes/, mf/
|
|
"boundaries/element-types": [
|
|
"error",
|
|
{
|
|
default: "allow",
|
|
rules: [
|
|
{
|
|
from: "features",
|
|
disallow: ["routes", "mf"],
|
|
message: "Features must not import from routes/ or mf/. Use the HostContract or a shared module instead.",
|
|
},
|
|
{
|
|
from: "ui",
|
|
disallow: ["features", "routes", "mf"],
|
|
message: "UI layer must not import from features/, routes/, or mf/. UI is consumed by features, not the other way around.",
|
|
},
|
|
{
|
|
from: "shared",
|
|
disallow: ["features", "routes", "mf", "observability"],
|
|
message: "Shared modules must not import from features/, routes/, mf/, or observability/.",
|
|
},
|
|
{
|
|
from: "observability",
|
|
disallow: ["features", "routes", "mf"],
|
|
message: "Observability modules must not import from features/, routes/, or mf/.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// --- Restricted imports (master plan §1A-3) ---
|
|
// OTel SDK internals + react-i18next: merged into one block so ESLint 9
|
|
// flat config doesn't override one rule with the other.
|
|
// otel.ts is allowed to import OTel SDK; provider.tsx is allowed to import react-i18next.
|
|
// Neither file needs the other's exemption, so combining ignores is safe.
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
ignores: ["src/observability/metrics/otel.ts", "src/observability/metrics/otel.test.ts", "src/i18n/provider.tsx"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
paths: [
|
|
{
|
|
name: "@opentelemetry/sdk-metrics",
|
|
message: "Import from @opentelemetry/api or use getMeter() from @/observability/metrics/otel instead. Direct SDK access is restricted to src/observability/metrics/otel.ts.",
|
|
},
|
|
{
|
|
name: "@opentelemetry/sdk-node",
|
|
message: "Import from @opentelemetry/api or use getMeter()/getTracer() from @/observability/metrics/otel instead. Direct SDK access is restricted to src/observability/metrics/otel.ts.",
|
|
},
|
|
{
|
|
name: "react-i18next",
|
|
message: "Import useTranslation from @/i18n/provider instead. Direct react-i18next imports are restricted to src/i18n/provider.tsx.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// @microsoft/signalr: forbidden in files that run during SSR.
|
|
// SSR-bundle = routes/ and server/ directories. Features + shared/hooks are client-side.
|
|
{
|
|
files: ["src/routes/**/*.{ts,tsx}", "src/server/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
paths: [
|
|
{
|
|
name: "@microsoft/signalr",
|
|
message: "SignalR must not be imported in SSR-bundle files (routes/, server/). Use dynamic import in a useEffect or a client-only wrapper.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// window.localStorage / window.sessionStorage: only src/shared/storage.ts
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
ignores: ["src/shared/storage.ts", "src/shared/storage.test.ts"],
|
|
rules: {
|
|
"no-restricted-globals": [
|
|
"error",
|
|
{
|
|
name: "localStorage",
|
|
message: "Use the storage module from @/shared/storage instead. Direct localStorage access is restricted to src/shared/storage.ts.",
|
|
},
|
|
{
|
|
name: "sessionStorage",
|
|
message: "Use the storage module from @/shared/storage instead. Direct sessionStorage access is restricted to src/shared/storage.ts.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// Test files get a looser ruleset: non-null assertions are idiomatic
|
|
// when fixtures guarantee presence (`arr[0]!` after an explicit length
|
|
// check). Production code keeps the warning; tests don't.
|
|
{
|
|
files: [
|
|
"src/**/*.test.{ts,tsx}",
|
|
"tests/**/*.{ts,tsx}",
|
|
],
|
|
rules: {
|
|
"@typescript-eslint/no-non-null-assertion": "off",
|
|
},
|
|
},
|
|
];
|