214 lines
7.4 KiB
JavaScript
214 lines
7.4 KiB
JavaScript
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
import globals from "globals";
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
export default tseslint.config(
|
|
{
|
|
ignores: [
|
|
"dist/**",
|
|
"node_modules/**",
|
|
"coverage/**",
|
|
"experiments/**",
|
|
"instance/**",
|
|
"scripts/serve-demo.mjs",
|
|
"public/sw.js",
|
|
"share/**",
|
|
],
|
|
},
|
|
js.configs.recommended,
|
|
...tseslint.configs.strictTypeChecked,
|
|
...tseslint.configs.stylisticTypeChecked,
|
|
{
|
|
languageOptions: {
|
|
parserOptions: {
|
|
projectService: {
|
|
allowDefaultProject: [
|
|
"eslint.config.js",
|
|
"vitest.config.ts",
|
|
"vite.config.ts",
|
|
"public/sw.js",
|
|
],
|
|
},
|
|
tsconfigRootDir: import.meta.dirname,
|
|
},
|
|
globals: {
|
|
...globals.browser,
|
|
...globals.node,
|
|
},
|
|
},
|
|
plugins: {
|
|
boundaries,
|
|
},
|
|
settings: {
|
|
"boundaries/elements": [
|
|
{ type: "platform", pattern: "src/platform/**" },
|
|
{ type: "storage", pattern: "src/storage/**" },
|
|
{ type: "data", pattern: "src/data/**" },
|
|
{ type: "sync-verifier", pattern: "src/sync/verifier/**" },
|
|
{ type: "sync-transport", pattern: "src/sync/transport/**" },
|
|
{ type: "sync", pattern: "src/sync/**" },
|
|
{ type: "domain", pattern: "src/domain/**" },
|
|
{ type: "ui", pattern: "src/ui/**" },
|
|
{ type: "app", pattern: "src/app/**" },
|
|
{ type: "emergency-baseline", pattern: "src/emergency-baseline/**" },
|
|
{ type: "tests", pattern: "tests/**" },
|
|
{ type: "scripts", pattern: "scripts/**" },
|
|
{ type: "pipeline", pattern: "pipeline/**" },
|
|
],
|
|
"boundaries/ignore": ["**/*.test.ts", "**/*.spec.ts"],
|
|
},
|
|
rules: {
|
|
// Architectural invariants — dependency rules B-1 … B-7
|
|
// B-1: ui never touches platform/storage/sync/transport/fetch/idb/caches
|
|
// Implemented via boundaries + no-restricted-globals where applicable
|
|
"boundaries/element-types": [
|
|
"error",
|
|
{
|
|
default: "disallow",
|
|
rules: [
|
|
// platform: low-level — may not import domain/ui/sync
|
|
{ from: "platform", allow: [] },
|
|
// storage: may only import platform
|
|
{ from: "storage", allow: ["platform"] },
|
|
// data: may import platform only
|
|
{ from: "data", allow: ["platform"] },
|
|
// sync-verifier: may import platform (hash) but not data/domain/ui/sync
|
|
{ from: "sync-verifier", allow: ["platform"] },
|
|
// sync-transport: byte pipe — may not import data/domain/ui
|
|
{ from: "sync-transport", allow: [] },
|
|
// sync: orchestration — may import data + sync sublayers + platform (via verifier)
|
|
{ from: "sync", allow: ["data", "sync-verifier", "sync-transport", "platform"] },
|
|
// domain: may import data + clock (domain/clock) — clock is domain, so allow domain internal
|
|
{ from: "domain", allow: ["data", "domain", "emergency-baseline"] },
|
|
// ui: may import domain + app helpers, but NOT platform/storage/sync
|
|
{ from: "ui", allow: ["domain", "emergency-baseline"] },
|
|
// app: composition root may import via public APIs — allow all for Stage 1, will tighten later
|
|
{
|
|
from: "app",
|
|
allow: [
|
|
"platform",
|
|
"storage",
|
|
"data",
|
|
"sync",
|
|
"sync-verifier",
|
|
"sync-transport",
|
|
"domain",
|
|
"ui",
|
|
"emergency-baseline",
|
|
],
|
|
},
|
|
// emergency-baseline: generated, no deps
|
|
{ from: "emergency-baseline", allow: [] },
|
|
// tests/scripts may import anything for validation
|
|
{
|
|
from: "tests",
|
|
allow: [
|
|
"platform",
|
|
"storage",
|
|
"data",
|
|
"sync",
|
|
"sync-verifier",
|
|
"sync-transport",
|
|
"domain",
|
|
"ui",
|
|
"app",
|
|
"emergency-baseline",
|
|
],
|
|
},
|
|
{
|
|
from: "scripts",
|
|
allow: [
|
|
"platform",
|
|
"storage",
|
|
"data",
|
|
"sync",
|
|
"sync-verifier",
|
|
"sync-transport",
|
|
"domain",
|
|
"ui",
|
|
"app",
|
|
"emergency-baseline",
|
|
],
|
|
},
|
|
{
|
|
from: "pipeline",
|
|
allow: ["platform", "storage", "data", "domain", "emergency-baseline", "pipeline"],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
// Explicit forbidden globals in ui (B-1 core)
|
|
"no-restricted-globals": [
|
|
"error",
|
|
{
|
|
name: "indexedDB",
|
|
message: "B-1: ui must not touch indexedDB — use data/ read APIs only.",
|
|
},
|
|
{
|
|
name: "caches",
|
|
message: "B-1: ui must not touch caches — use platform/cache only via data/sync.",
|
|
},
|
|
],
|
|
// General strictness
|
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
"@typescript-eslint/consistent-type-imports": "error",
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/no-unnecessary-condition": "warn",
|
|
},
|
|
},
|
|
{
|
|
// B-7: no innerHTML with festival content — forbid assignment to innerHTML in src
|
|
files: ["src/**/*.ts"],
|
|
rules: {
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
selector: "AssignmentExpression[left.property.name='innerHTML']",
|
|
message:
|
|
"B-7: Do not render festival content via innerHTML of raw strings — use ui/render safe DOM (C-22).",
|
|
},
|
|
{
|
|
selector: "CallExpression[callee.property.name='insertAdjacentHTML']",
|
|
message:
|
|
"B-7: Do not use insertAdjacentHTML with festival content — use ui/render safe DOM (C-22).",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
// platform/idb and cache/sw are the ONLY places allowed to touch indexedDB/caches (B-1 — ui forbidden)
|
|
files: ["src/platform/**/*.ts"],
|
|
rules: {
|
|
"no-restricted-globals": "off",
|
|
},
|
|
},
|
|
{
|
|
// pipeline is build-time Node code — relax some strict app rules
|
|
files: ["pipeline/**/*.ts"],
|
|
rules: {
|
|
"no-restricted-globals": "off",
|
|
"no-restricted-syntax": "off",
|
|
"@typescript-eslint/restrict-template-expressions": "off",
|
|
"@typescript-eslint/no-unnecessary-type-assertion": "off",
|
|
"@typescript-eslint/no-unnecessary-condition": "off",
|
|
"@typescript-eslint/no-empty-object-type": "off",
|
|
"@typescript-eslint/consistent-type-imports": "off",
|
|
"@typescript-eslint/no-require-imports": "off",
|
|
"@typescript-eslint/array-type": "off",
|
|
"@typescript-eslint/no-non-null-assertion": "off",
|
|
"@typescript-eslint/no-unnecessary-type-conversion": "off",
|
|
"prefer-const": "off",
|
|
},
|
|
},
|
|
{
|
|
// Allow explicit forbidden-globals only where justified; tests may use them
|
|
files: ["tests/**/*.ts", "scripts/**/*.ts"],
|
|
rules: {
|
|
"no-restricted-globals": "off",
|
|
"no-restricted-syntax": "off",
|
|
},
|
|
},
|
|
);
|