From 743d51093c20a3a72542c822379f87f7cc2093f6 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:29:36 +0100 Subject: [PATCH] docs: explain custom web rule facts --- concepts/analyzers/typescript-sveltekit.mdx | 67 ++++++++++++++++++++- reference/rule-context.mdx | 66 ++++++++++++++++++-- 2 files changed, 125 insertions(+), 8 deletions(-) diff --git a/concepts/analyzers/typescript-sveltekit.mdx b/concepts/analyzers/typescript-sveltekit.mdx index 71ed2ca..34be10b 100644 --- a/concepts/analyzers/typescript-sveltekit.mdx +++ b/concepts/analyzers/typescript-sveltekit.mdx @@ -105,8 +105,71 @@ also reads literal SvelteKit `kit.alias` values. When a required alias is dynami Fensu fails with a configuration diagnostic rather than guessing. A missing generated `.svelte-kit/tsconfig.json` is accepted on a fresh checkout. -Python custom rules and `fensu map` remain Python analyzer features. Web targets -use the shipped native `typescript` or `sveltekit` catalogue. +## Custom web rules + +Python-authored custom rules can extend a TypeScript or Svelte target without +changing its CLI, configuration, selectors, or policy model. Declare analyzer +applicability on the normal `@rule` decorator: + +```python +from fensu import AnalyzerId, Family, Fault, File, RuleContext, rule + + +@rule( + code="XWEB001", + family=Family.CUSTOM, + slug="resolved-import-boundary", + message="Import crosses the configured boundary", + analyzers=(AnalyzerId.TYPESCRIPT,), + cacheable=True, +) +def resolved_import_boundary(*, file: File, ctx: RuleContext) -> list[Fault]: + facts = ctx.web.file(file) + if facts is None: + return [] + return [ + ctx.fault_at(location=edge.location) + for edge in ctx.graph.imports(file) + if edge.target is not None and edge.target.role == "_state" + ] +``` + +Configure it like any other custom rule: + +```toml +[targets.web] +analyzer = "typescript" +roots = ["src"] +tests = ["tests"] +tooling = [] +rule_paths = ["rules/web.py"] +select = ["FPTS", "XWEB001"] +``` + +`ctx.web` exposes immutable Fensu-owned facts for TypeScript, JavaScript, and +Svelte sources. These include source kinds, resolved import targets, functions, +classes, models, bindings, calls, resources, exports, source locations, and stable +text handles. `ctx.graph` exposes the same resolved imports through the common +architecture graph, including `tsconfig.json`, `jsconfig.json`, `$lib`, and +literal SvelteKit alias resolution. + +Svelte files add `SvelteFileFacts` for component markup, route identity, script +contexts, and runes. Svelte state modules such as `session.svelte.ts` are marked +separately and expose only recognized top-level initializer runes; nested calls +are not promoted to module-owned state. + +File rules run once per selected source. A typed `Project` callback runs once for +the target and can query `ctx.web.files` and `ctx.project.tree`. Use focused +`ctx.web.file(...)`, tree, or graph queries when unrelated edits should preserve a +cached result. Every query records its actual answer for cache replay. + +Web parsing and built-in rules always remain native and Node-free. Fensu starts a +bounded Python host only when configured custom sources and an active `X` selector +require hosted resolution. Native-only, unselected, and custom selections whose +selector prefixes are wholly covered by ignore prefixes remain Python-free. Actual +rule applicability is resolved inside the host. Custom diagnostics then pass +through the normal warnings, ignores, exceptions, rendering, and exit-code +handling. `fensu map` remains a Python analyzer feature. ## Gradual adoption diff --git a/reference/rule-context.mdx b/reference/rule-context.mdx index 6326de0..59d9bd4 100644 --- a/reference/rule-context.mdx +++ b/reference/rule-context.mdx @@ -100,9 +100,11 @@ unsupported path objects. `FilePosition` includes `path`, `analyzer`, `source_ki ## `ctx.graph` -For Python targets, an immutable architecture graph resolved from static imports -and authoritative discovered modules. Unresolved imports remain explicit and -dynamic imports are not represented. +For Python, TypeScript, and Svelte targets, an immutable architecture graph +resolved from static imports and authoritative discovered modules. Unresolved +imports remain explicit and dynamic imports are not represented. Web graph +resolution reuses the native analyzer's static TypeScript, JavaScript, SvelteKit, +and configured alias resolution. | Call | Returns | Purpose | | --- | --- | --- | @@ -139,6 +141,38 @@ Each broad or focused call records its answer for subject-aware cache replay. Us focused `file()` or `crate()` lookup when unrelated source or Cargo changes should not invalidate a cached result. +## `ctx.web` + +For TypeScript and Svelte targets, a versioned immutable fact view transferred from +the native analyzers. It contains no parser-library nodes and does not require +Node. + +| Call | Returns | Purpose | +| --- | --- | --- | +| `files` | `tuple[WebFileFacts, ...]` | All discovered direct, support, excluded, and generated web source facts. | +| `file(file_or_path)` | `WebFileFacts \| None` | One focused file lookup by `File` or `ProjectPath`. | + +`WebFileFacts` exposes its analyzer, `WebSourceKind`, `WebSourcePurpose`, module, +source root, scope, authored source, optional parse error, and immutable imports, +functions, classes, models, bindings, calls, resources, re-exports, and export +counts. `WebImportFact.target` is a `File` only when the native analyzer proves a +project-local destination. Use `ctx.graph` for resolved dependency and dependent +queries. + +`syntax_handles` contains stable Fensu-owned `WebSyntaxHandle` byte ranges for +imports, declarations, calls, resources, re-exports, Svelte scripts, and runes. +Pass one to `WebFileFacts.text(handle)` to retrieve its authored text without +receiving an analyzer AST. + +On Svelte targets, `WebFileFacts.svelte` is a `SvelteFileFacts` extension when the +file is a component or state module. It records script contexts, recognized +top-level runes, component markup, route identity, and state-module identity. +TypeScript-only targets do not require this extension. + +Broad `files` and focused `file()` calls record their exact answers for +subject-aware cache replay. Tree and graph calls made during the same invocation +record their own query-specific dependencies. + ## `ctx.text`, `ctx.syntax`, `ctx.relations` Source text, opaque syntax identities, and syntax relationships for the current @@ -160,9 +194,11 @@ The zone protocols are `FactAnalysis`, `ProjectAnalysis`, `TextAnalysis`, `SyntaxAnalysis`, and `RelationAnalysis`, all exported from `fensu`. The table above describes the Python analysis zones. Serialized Rust rules use -`ctx.rust`, `ctx.project.tree`, `ctx.source`, and stable locations. `ctx.graph`, -`ctx.syntax`, and `ctx.relations` are unavailable because parser implementation -objects are not part of the Rust authoring contract. +`ctx.rust`, `ctx.project.tree`, `ctx.source`, and stable locations. Serialized web +rules use `ctx.web`, `ctx.graph`, `ctx.project.tree`, `ctx.source`, and +`WebSyntaxHandle`. The generic `ctx.syntax` and `ctx.relations` zones are +unavailable because parser implementation objects are not part of native-analyzer +authoring contracts. ## Fault construction @@ -328,6 +364,24 @@ are frozen dataclasses; inspect exact fields with your editor's type information | `RustItemFact` | Function, struct, enum, trait, implementation or module declaration. | | `RustUseFact` | Authored `use` path and explicit resolution evidence. | +**Web facts** + +| Model | Describes | +| --- | --- | +| `WebWorkspaceFacts` | Versioned TypeScript or Svelte target facts. | +| `WebFileFacts` | One discovered web source, its semantic facts, source, and parse result. | +| `WebImportFact` | Authored import bindings, flags, location, and optional resolved target. | +| `WebFunctionFact` | Function ownership, annotations, return type, and structural metrics. | +| `WebClassFact` | Class identity, export status, and error-class classification. | +| `WebModelFact` | Interface or type-literal model shape and readonly evidence. | +| `WebBindingFact` | Top-level binding and optional initializer call. | +| `WebCallFact` | Named call, lexical function owner, ancestors, and cleanup evidence. | +| `WebResourceFact` | Statically recognized resource family, binding, and call ancestry. | +| `WebSyntaxHandle` | Fensu-owned kind, name, location, and stable source byte range. | +| `SvelteFileFacts` | Component, route, state-module, script, and rune extensions. | +| `SvelteScriptFact` | One module or instance script block. | +| `SvelteRuneFact` | One recognized module-owned rune and source location. | + **Test facts** | Model | Describes |