From 805e7bfdf41b65d1a6eb2f063b6367d6470ec22f Mon Sep 17 00:00:00 2001 From: murashit Date: Wed, 15 Jul 2026 15:08:32 +0900 Subject: [PATCH] test: simplify grit policy fixtures --- biome.jsonc | 1 - docs/development.md | 2 +- knip.json | 2 +- .../invalid/src/app-server/protocol/escape.ts | 4 - .../grit-policy/invalid/src/domain/root.ts | 4 - .../src/features/chat/app-server/escape.ts | 3 - .../mappers/thread-stream/turn-items.ts | 3 - .../src/features/chat/application/escape.ts | 5 - .../features/chat/application/state/escape.ts | 1 - .../invalid/src/features/chat/host/escape.ts | 3 - .../invalid/src/features/chat/panel/escape.ts | 3 - .../src/features/chat/presentation/escape.ts | 6 - .../invalid/src/features/chat/ui/escape.tsx | 10 - .../src/features/selection-rewrite/escape.ts | 4 - .../invalid/src/features/threads/escape.tsx | 1 - .../src/features/threads/workflows/escape.ts | 3 - .../grit-policy/invalid/src/plugin-runtime.ts | 5 - .../invalid/src/settings/escape.ts | 7 - .../invalid/src/shared/runtime/escape.ts | 7 - .../grit-policy/invalid/src/styles/escape.css | 4 - .../invalid/src/workspace/escape.ts | 3 - .../valid/src/app-server/protocol/safe.ts | 3 - .../valid/src/app-server/services/safe.ts | 3 - .../valid/src/domain/example/safe.ts | 1 - .../mappers/thread-stream/turn-items.ts | 3 - .../features/chat/application/state/safe.ts | 3 - .../valid/src/features/chat/host/safe.ts | 1 - .../valid/src/features/chat/panel/safe.ts | 1 - .../src/features/chat/presentation/safe.ts | 1 - .../valid/src/features/chat/ui/safe.ts | 3 - .../valid/src/features/chat/ui/safe.tsx | 3 - .../src/features/selection-rewrite/safe.ts | 1 - .../src/features/threads/workflows/safe.ts | 1 - .../grit-policy/valid/src/plugin-runtime.ts | 3 - .../grit-policy/valid/src/settings/safe.ts | 1 - .../valid/src/shared/runtime/safe.ts | 3 - .../grit-policy/valid/src/styles/safe.css | 4 - .../grit-policy/valid/src/workspace/safe.ts | 1 - tests/scripts/grit-policy.test.mjs | 296 ++++++++++++++---- tsconfig.json | 3 +- 40 files changed, 233 insertions(+), 183 deletions(-) delete mode 100644 tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/domain/root.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx delete mode 100644 tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/settings/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts delete mode 100644 tests/fixtures/grit-policy/invalid/src/styles/escape.css delete mode 100644 tests/fixtures/grit-policy/invalid/src/workspace/escape.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/domain/example/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx delete mode 100644 tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/plugin-runtime.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/settings/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts delete mode 100644 tests/fixtures/grit-policy/valid/src/styles/safe.css delete mode 100644 tests/fixtures/grit-policy/valid/src/workspace/safe.ts diff --git a/biome.jsonc b/biome.jsonc index 4740abe1..2cbf643d 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -195,7 +195,6 @@ "scripts/**/*.mjs", "src/**/*.{css,ts,tsx}", "tests/**/*.{mjs,ts,tsx}", - "!tests/fixtures/grit-policy", "!src/generated" ] }, diff --git a/docs/development.md b/docs/development.md index 0c09d909..27cce460 100644 --- a/docs/development.md +++ b/docs/development.md @@ -31,7 +31,7 @@ Use focused scripts while iterating, but run `npm run check` before handoff. CI Keep rule suppressions local and include the Obsidian-specific reason when a native Obsidian UI pattern intentionally diverges from a generic browser rule. -Grit policy fixtures protect matcher semantics rather than diagnostic wording, source locations, or the exact shape of Biome configuration. Change the checked-in valid and invalid fixtures only when a policy's accepted or rejected source shape intentionally changes. +Grit policy cases protect matcher semantics rather than diagnostic wording, source locations, or the exact shape of Biome configuration. Keep one valid and invalid source case for each policy, and change a case only when that policy's accepted or rejected source shape intentionally changes. ## Generated and Loaded Files diff --git a/knip.json b/knip.json index 016d3c6e..804bc894 100644 --- a/knip.json +++ b/knip.json @@ -1,6 +1,6 @@ { "entry": ["src/main.ts", "scripts/**/*.mjs", "*.config.{ts,mjs}", "tests/**/*.test.{ts,tsx}"], "project": ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.mjs", "*.config.{ts,mjs}"], - "ignore": ["src/generated/**", "tests/fixtures/grit-policy/**"], + "ignore": ["src/generated/**"], "ignoreBinaries": ["codex", "taskkill"] } diff --git a/tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts b/tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts deleted file mode 100644 index 38ca5c88..00000000 --- a/tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Client } from "../connection/client"; -import type { Root } from "../root"; - -export type Escape = Client | Root; diff --git a/tests/fixtures/grit-policy/invalid/src/domain/root.ts b/tests/fixtures/grit-policy/invalid/src/domain/root.ts deleted file mode 100644 index b6ccfea3..00000000 --- a/tests/fixtures/grit-policy/invalid/src/domain/root.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "obsidian"; - -export { value } from "./owner"; -export * from "./barrel"; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts deleted file mode 100644 index f4e53493..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Panel } from "../panel/toolbar-actions"; - -export type Escape = Panel; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts deleted file mode 100644 index bfff9090..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Catalog } from "../../../../../../app-server/protocol/catalog"; - -export type Escape = Catalog; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts deleted file mode 100644 index 03a2c366..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Protocol } from "../../../../app-server/protocol/catalog"; -import type { Host } from "../host/contracts"; -import type { Workspace } from "../../../../workspace/panel-coordinator"; - -export type Escape = Protocol | Host | Workspace; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts deleted file mode 100644 index ead6245b..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts +++ /dev/null @@ -1 +0,0 @@ -export const checkedAt = Date.now(); diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts deleted file mode 100644 index 930a9c6e..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { View } from "../presentation/thread-stream/view-model"; - -export type Escape = View; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts deleted file mode 100644 index 30cf1524..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Runtime } from "../host/session-runtime"; - -export type Escape = Runtime; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts deleted file mode 100644 index a98f9bc0..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { signal } from "@preact/signals"; -import type { State } from "../application/state/store"; -import type { ReadModel } from "../panel/shell-read-model"; - -export const escaped = signal(1); -export type Escape = State | ReadModel | Signal; diff --git a/tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx b/tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx deleted file mode 100644 index c51c25bf..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { State } from "../application/state/store"; -import { addDomEventListener } from "../../../../shared/dom/events.dom"; -import { renderPreactRoot } from "../../../../shared/dom/preact-root.dom"; - -export function Escape(props: { state: State }): JSX.Element { - document.body.append(document.createElement("div")); - void addDomEventListener; - void renderPreactRoot; - return ; -} diff --git a/tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts deleted file mode 100644 index 2a5f1533..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { Connection } from "../../app-server/connection/client"; -import type { Workspace } from "../../workspace/panel-coordinator"; - -export type Escape = Connection | Workspace; diff --git a/tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx b/tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx deleted file mode 100644 index 335c63fb..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx +++ /dev/null @@ -1 +0,0 @@ -export const misplaced = 1; diff --git a/tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts b/tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts deleted file mode 100644 index af873c2f..00000000 --- a/tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Client } from "../../../app-server/connection/client"; - -export type Escape = Client; diff --git a/tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts b/tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts deleted file mode 100644 index 2615035e..00000000 --- a/tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts +++ /dev/null @@ -1,5 +0,0 @@ -class Runner { - constructor(readonly stop: () => void) {} -} - -export const runner = new Runner(() => runner.stop()); diff --git a/tests/fixtures/grit-policy/invalid/src/settings/escape.ts b/tests/fixtures/grit-policy/invalid/src/settings/escape.ts deleted file mode 100644 index eb4439d9..00000000 --- a/tests/fixtures/grit-policy/invalid/src/settings/escape.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Generated } from "../generated/app-server/types"; - -export async function read(client: { request(method: string): Promise }): Promise { - await client.request("config/read"); -} - -export type Escape = Generated; diff --git a/tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts b/tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts deleted file mode 100644 index 7ef5b9d5..00000000 --- a/tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Feature } from "../../features/escape"; - -export function first(iterator: Iterator): T { - return iterator.next().value as T; -} - -export type EscapedFeature = Feature; diff --git a/tests/fixtures/grit-policy/invalid/src/styles/escape.css b/tests/fixtures/grit-policy/invalid/src/styles/escape.css deleted file mode 100644 index bb69d90d..00000000 --- a/tests/fixtures/grit-policy/invalid/src/styles/escape.css +++ /dev/null @@ -1,4 +0,0 @@ -#codex-panel-fixture * { - color: #fff; - padding: 12px; -} diff --git a/tests/fixtures/grit-policy/invalid/src/workspace/escape.ts b/tests/fixtures/grit-policy/invalid/src/workspace/escape.ts deleted file mode 100644 index 43c347a8..00000000 --- a/tests/fixtures/grit-policy/invalid/src/workspace/escape.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { ChatState } from "../features/chat/application/state/store"; - -export type Escape = ChatState; diff --git a/tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts b/tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts deleted file mode 100644 index bf4dc05e..00000000 --- a/tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ProtocolValue { - id: string; -} diff --git a/tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts b/tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts deleted file mode 100644 index bbbd6988..00000000 --- a/tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function serviceValue(): string { - return "safe"; -} diff --git a/tests/fixtures/grit-policy/valid/src/domain/example/safe.ts b/tests/fixtures/grit-policy/valid/src/domain/example/safe.ts deleted file mode 100644 index a910fe21..00000000 --- a/tests/fixtures/grit-policy/valid/src/domain/example/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type DomainValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts b/tests/fixtures/grit-policy/valid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts deleted file mode 100644 index 8a9739cf..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/app-server/mappers/thread-stream/turn-items.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function convert(value: string): string { - return value; -} diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts b/tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts deleted file mode 100644 index 08dea1a3..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ChatStateValue { - ready: boolean; -} diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts b/tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts deleted file mode 100644 index e2efd365..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type HostValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts b/tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts deleted file mode 100644 index 9fe715ce..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type PanelValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts b/tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts deleted file mode 100644 index 260abe13..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type PresentationValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts b/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts deleted file mode 100644 index 200f0388..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function label(value: string): string { - return value.trim(); -} diff --git a/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx b/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx deleted file mode 100644 index 67b32ece..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function SafeView(): JSX.Element { - return Safe; -} diff --git a/tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts b/tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts deleted file mode 100644 index fd8d56a1..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type RewriteValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts b/tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts deleted file mode 100644 index 3cc38f8a..00000000 --- a/tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type WorkflowValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/plugin-runtime.ts b/tests/fixtures/grit-policy/valid/src/plugin-runtime.ts deleted file mode 100644 index 5b422cc2..00000000 --- a/tests/fixtures/grit-policy/valid/src/plugin-runtime.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function createRuntime(): object { - return {}; -} diff --git a/tests/fixtures/grit-policy/valid/src/settings/safe.ts b/tests/fixtures/grit-policy/valid/src/settings/safe.ts deleted file mode 100644 index cf1efa51..00000000 --- a/tests/fixtures/grit-policy/valid/src/settings/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type SettingsValue = string; diff --git a/tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts b/tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts deleted file mode 100644 index 5aca6fd9..00000000 --- a/tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function identity(value: T): T { - return value; -} diff --git a/tests/fixtures/grit-policy/valid/src/styles/safe.css b/tests/fixtures/grit-policy/valid/src/styles/safe.css deleted file mode 100644 index 084dd1ce..00000000 --- a/tests/fixtures/grit-policy/valid/src/styles/safe.css +++ /dev/null @@ -1,4 +0,0 @@ -.codex-panel-fixture { - color: var(--text-normal); - padding: var(--size-4-2); -} diff --git a/tests/fixtures/grit-policy/valid/src/workspace/safe.ts b/tests/fixtures/grit-policy/valid/src/workspace/safe.ts deleted file mode 100644 index 2f25d67c..00000000 --- a/tests/fixtures/grit-policy/valid/src/workspace/safe.ts +++ /dev/null @@ -1 +0,0 @@ -export type WorkspaceValue = string; diff --git a/tests/scripts/grit-policy.test.mjs b/tests/scripts/grit-policy.test.mjs index 76d42117..a2e22cba 100644 --- a/tests/scripts/grit-policy.test.mjs +++ b/tests/scripts/grit-policy.test.mjs @@ -1,13 +1,198 @@ import { spawnSync } from "node:child_process"; -import { cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; const repoRoot = process.cwd(); const biomeBin = path.join(repoRoot, "node_modules", ".bin", "biome"); -const fixtureRoot = path.join(repoRoot, "tests", "fixtures", "grit-policy"); const workspaces = new Set(); +let projectPluginsPromise; + +const policyCases = [ + policyCase("no-handwritten-reexports.grit", "src/example.ts", 'export { value } from "./owner";', "const value = 1;\nexport { value };"), + policyCase("no-responsibility-root-module-files.grit", "src/domain/escape.ts", "export const value = 1;", "export const value = 1;", { + validPath: "src/domain/example/safe.ts", + }), + policyCase( + "no-misplaced-tsx.grit", + "src/features/threads/escape.tsx", + "export const value = 1;", + "export function Safe(): JSX.Element { return ; }", + { + validPath: "src/features/chat/ui/safe.tsx", + }, + ), + policyCase( + "no-self-referential-initializer-callback.grit", + "src/example.ts", + "const runner = new Runner(() => runner.stop());", + "let runner: Runner;\nrunner = new Runner(() => runner.stop());", + ), + policyCase( + "no-unsafe-iterator-value.grit", + "src/example.ts", + "export const value = iterator.next().value;", + "const result = iterator.next();\nexport const value = result.done ? undefined : result.value;", + ), + policyCase( + "no-uncontrolled-preact-form-state.grit", + "src/features/chat/ui/form.tsx", + 'export function Form(): JSX.Element { return ; }', + 'export function Form(): JSX.Element { return ; }', + ), + policyCase( + "no-domain-outer-layer-imports.grit", + "src/domain/example/value.ts", + 'import type { App } from "obsidian";', + "export type Value = string;", + ), + policyCase( + "no-lower-level-feature-imports.grit", + "src/shared/runtime/escape.ts", + 'import type { Feature } from "../../features/escape";', + "export type Value = string;", + ), + policyCase( + "no-app-server-subfolder-root-imports.grit", + "src/app-server/protocol/escape.ts", + 'import type { Root } from "../root";', + 'import type { Client } from "../connection/client";', + ), + policyCase( + "no-app-server-connection-boundary-imports.grit", + "src/app-server/protocol/escape.ts", + 'import type { Client } from "../connection/client";', + "export type Value = string;", + ), + policyCase( + "no-app-server-protocol-boundary-imports.grit", + "src/settings/escape.ts", + 'import type { Catalog } from "../app-server/protocol/catalog";', + 'import type { Catalog } from "../app-server/services/catalog";', + ), + policyCase( + "no-chat-turn-item-non-turn-protocol-imports.grit", + "src/features/chat/app-server/mappers/thread-stream/turn-items.ts", + 'import type { Catalog } from "../../../../../../app-server/protocol/catalog";', + 'import type { Turn } from "../../../../../../app-server/protocol/turn";', + ), + policyCase( + "no-generated-app-server-boundary-imports.grit", + "src/settings/escape.ts", + 'import type { Generated } from "../generated/app-server/types";', + "export type Value = string;", + ), + policyCase( + "no-app-server-direct-rpcs.grit", + "src/settings/escape.ts", + 'export async function read(client) { await client.request("config/read"); }', + "export async function read(service) { await service.readConfig(); }", + ), + policyCase( + "no-chat-workspace-boundary-imports.grit", + "src/features/chat/host/escape.ts", + 'import type { Workspace } from "../../../workspace/panel-coordinator";', + "export type Value = string;", + ), + policyCase( + "no-feature-workspace-boundary-imports.grit", + "src/features/selection-rewrite/escape.ts", + 'import type { Workspace } from "../../workspace/panel-coordinator";', + "export type Value = string;", + ), + policyCase( + "no-workspace-chat-internal-imports.grit", + "src/workspace/escape.ts", + 'import type { State } from "../features/chat/application/state/store";', + 'import type { Host } from "../features/chat/host/contracts";', + ), + policyCase( + "no-thread-workflow-app-server-imports.grit", + "src/features/threads/workflows/escape.ts", + 'import type { Client } from "../../../app-server/connection/client";', + 'import type { Transport } from "./ports";', + ), + policyCase( + "no-chat-application-outer-layer-imports.grit", + "src/features/chat/application/escape.ts", + 'import type { Host } from "../host/contracts";', + 'import type { Item } from "../domain/thread-stream/items";', + ), + policyCase( + "no-chat-app-server-outer-layer-imports.grit", + "src/features/chat/app-server/escape.ts", + 'import type { Panel } from "../panel/toolbar-actions";', + 'import type { Store } from "../application/state/store";', + ), + policyCase( + "no-chat-host-rendering-layer-imports.grit", + "src/features/chat/host/escape.ts", + 'import type { View } from "../presentation/thread-stream/view-model";', + 'import type { Store } from "../application/state/store";', + ), + policyCase( + "no-chat-panel-runtime-boundary-imports.grit", + "src/features/chat/panel/escape.ts", + 'import type { Host } from "../host/contracts";', + 'import type { View } from "../presentation/thread-stream/view-model";', + ), + policyCase( + "no-chat-presentation-outer-layer-imports.grit", + "src/features/chat/presentation/escape.ts", + 'import type { Store } from "../application/state/store";', + 'import type { Item } from "../domain/thread-stream/items";', + ), + policyCase( + "no-chat-ui-outer-layer-imports.grit", + "src/features/chat/ui/escape.ts", + 'import type { Store } from "../application/state/store";', + 'import type { View } from "../presentation/thread-stream/view-model";', + ), + policyCase( + "no-preact-signal-imports.grit", + "src/settings/escape.ts", + 'import { signal } from "@preact/signals";', + "export const value = 1;", + ), + policyCase( + "no-chat-shell-read-model-imports.grit", + "src/features/chat/presentation/escape.ts", + 'import type { ReadModel } from "../panel/shell-read-model";', + "export type Value = string;", + ), + policyCase( + "no-chat-signal-type-references.grit", + "src/features/chat/presentation/escape.ts", + "export type Value = Signal;", + "export type Value = string;", + ), + policyCase( + "no-state-module-side-effects.grit", + "src/features/chat/application/state/escape.ts", + "export const now = Date.now();", + "export const now = 1;", + ), + policyCase( + "no-implicit-dom-bridges.grit", + "src/features/chat/ui/escape.ts", + 'export const element = document.createElement("div");', + "export const value = 1;", + ), + policyCase( + "no-dom-events-imports.grit", + "src/features/chat/ui/escape.ts", + 'import { addDomEventListener } from "../../../shared/dom/events.dom";', + "export const value = 1;", + ), + policyCase( + "no-preact-root-imports.grit", + "src/features/chat/ui/escape.ts", + 'import { renderPreactRoot } from "../../../shared/dom/preact-root.dom";', + "export const value = 1;", + ), + policyCase("no-restricted-css-policy.grit", "src/styles/escape.css", ".escape { color: #fff; }", ".safe { color: var(--text-normal); }"), +]; afterEach(async () => { await Promise.all([...workspaces].map((workspace) => rm(workspace, { recursive: true, force: true }))); @@ -15,48 +200,53 @@ afterEach(async () => { }); describe("Biome Grit policies", () => { - it("wires every checked-in policy once with a non-empty scope", async () => { - const config = parseJsonc(await readFile(path.join(repoRoot, "biome.jsonc"), "utf8")); - const configured = config.plugins.map((plugin) => ({ - path: typeof plugin === "string" ? plugin : plugin.path, - includes: typeof plugin === "string" ? [] : plugin.includes, - })); - const checkedIn = await gritPolicyPaths(path.join(repoRoot, "scripts", "grit")); + it("keeps one semantic case for every configured policy", async () => { + const configured = (await projectPlugins()).map((plugin) => path.basename(pluginPath(plugin))).sort(); + const covered = policyCases.map((testCase) => testCase.plugin).sort(); - expect(new Set(configured.map((plugin) => plugin.path)).size).toBe(configured.length); - expect(configured.every((plugin) => plugin.includes.length > 0)).toBe(true); - expect(configured.map((plugin) => plugin.path.replace(/^\.\//, "")).sort()).toEqual(checkedIn); + expect(new Set(configured).size).toBe(configured.length); + expect(new Set(covered).size).toBe(covered.length); + expect(covered).toEqual(configured); }); - it("rejects the checked-in invalid fixture for every retained matcher", async () => { - const config = parseJsonc(await readFile(path.join(repoRoot, "biome.jsonc"), "utf8")); - for (const plugin of config.plugins) { - const workspace = await fixtureWorkspace(); - await writePluginConfig(workspace, plugin); - const result = biomeLint(workspace, await fixtureSourcePaths(workspace)); - expect(result.status, `${pluginPath(plugin)}\n${result.output}`).toBe(1); - expect(result.pluginErrors, `${pluginPath(plugin)}\n${result.output}`).toBeGreaterThan(0); - } - }, 30_000); + it.each(policyCases)("$plugin rejects its invalid source", async (testCase) => { + const result = await lintPolicyCase(testCase, "invalid"); - it("accepts the checked-in valid fixture for every retained matcher", async () => { - const config = parseJsonc(await readFile(path.join(repoRoot, "biome.jsonc"), "utf8")); - for (const plugin of config.plugins) { - const workspace = await fixtureWorkspace("valid"); - await writePluginConfig(workspace, plugin); - const result = biomeLint(workspace, await fixtureSourcePaths(workspace)); - expect(result.status, `${pluginPath(plugin)}\n${result.output}`).toBe(0); - expect(result.pluginErrors, pluginPath(plugin)).toBe(0); - } - }, 30_000); + expect(result.status, result.output).toBe(1); + expect(result.pluginErrors, result.output).toBeGreaterThan(0); + }); + + it.each(policyCases)("$plugin accepts its valid source", async (testCase) => { + const result = await lintPolicyCase(testCase, "valid"); + + expect(result.status, result.output).toBe(0); + expect(result.pluginErrors, result.output).toBe(0); + }); }); -async function fixtureWorkspace(kind = "invalid") { - const parent = await mkdtemp(path.join(tmpdir(), "codex-panel-grit-policy-")); - const workspace = path.join(parent, "fixture"); - workspaces.add(parent); - await cp(path.join(fixtureRoot, kind), workspace, { recursive: true }); - return workspace; +function policyCase(plugin, invalidPath, invalidSource, validSource, options = {}) { + return { + plugin, + invalid: { path: invalidPath, source: invalidSource }, + valid: { path: options.validPath ?? invalidPath, source: validSource }, + }; +} + +async function lintPolicyCase(testCase, variant) { + const plugin = (await projectPlugins()).find((candidate) => path.basename(pluginPath(candidate)) === testCase.plugin); + if (!plugin) throw new Error(`Missing configured Grit policy: ${testCase.plugin}`); + const workspace = await mkdtemp(path.join(tmpdir(), "codex-panel-grit-policy-")); + workspaces.add(workspace); + const fixture = testCase[variant]; + const fixturePath = path.join(workspace, fixture.path); + await mkdir(path.dirname(fixturePath), { recursive: true }); + await Promise.all([writeFile(fixturePath, fixture.source), writePluginConfig(workspace, plugin)]); + return biomeLint(workspace, fixture.path); +} + +async function projectPlugins() { + projectPluginsPromise ??= readFile(path.join(repoRoot, "biome.jsonc"), "utf8").then((source) => parseJsonc(source).plugins); + return projectPluginsPromise; } async function writePluginConfig(workspace, plugin) { @@ -68,8 +258,8 @@ async function writePluginConfig(workspace, plugin) { await writeFile(path.join(workspace, "biome.json"), JSON.stringify(config)); } -function biomeLint(workspace, targets) { - const result = spawnSync(biomeBin, ["lint", ...targets, "--config-path", workspace, "--reporter=json", "--max-diagnostics=none"], { +function biomeLint(workspace, target) { + const result = spawnSync(biomeBin, ["lint", target, "--config-path", workspace, "--reporter=json", "--max-diagnostics=none"], { cwd: workspace, encoding: "utf8", }); @@ -81,36 +271,10 @@ function biomeLint(workspace, targets) { }; } -async function fixtureSourcePaths(workspace, directory = "src") { - const { readdir } = await import("node:fs/promises"); - const entries = await readdir(path.join(workspace, directory), { withFileTypes: true }); - const paths = await Promise.all( - entries.map(async (entry) => { - const relativePath = path.join(directory, entry.name); - return entry.isDirectory() ? fixtureSourcePaths(workspace, relativePath) : [relativePath]; - }), - ); - return paths.flat(); -} - function pluginPath(plugin) { return typeof plugin === "string" ? plugin : plugin.path; } -async function gritPolicyPaths(directory) { - const { readdir } = await import("node:fs/promises"); - const entries = await readdir(directory, { withFileTypes: true }); - const paths = await Promise.all( - entries.map(async (entry) => { - const entryPath = path.join(directory, entry.name); - if (entry.isDirectory()) return gritPolicyPaths(entryPath); - if (entry.isFile() && entry.name.endsWith(".grit")) return [path.relative(repoRoot, entryPath).split(path.sep).join("/")]; - return []; - }), - ); - return paths.flat().sort(); -} - function parseJsonc(source) { return JSON.parse(source.replace(/^\s*\/\/.*$/gm, "")); } diff --git a/tsconfig.json b/tsconfig.json index f91155b4..c3ba3181 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,5 @@ "types": ["node", "vitest"], "lib": ["ES2022", "DOM"] }, - "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx", "vitest.config.ts"], - "exclude": ["tests/fixtures/grit-policy/**"] + "include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx", "vitest.config.ts"] }