Files
flights_web/eslint.config.js
gnezim 5c47498472 Allow non-null assertions in tests; refactor two production hotspots to drop them
- 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.
2026-04-20 08:24:01 +03:00

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",
},
},
];