lanekeep 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/index.d.ts +67 -9
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -89,6 +89,7 @@ actual rule.
89
89
  lanekeep check # the whole project
90
90
  lanekeep check --staged # only what is about to be committed
91
91
  lanekeep check --since main # only what changed against a ref
92
+ lanekeep check --file src/a.ts # exactly these files, repeatable, no git involved
92
93
  lanekeep check --watch # re-check on every change, until Ctrl-C
93
94
  lanekeep check --fix # apply the safe fixes, report what is left
94
95
  lanekeep check --profile # per rule: where the time went, and what it looked at
package/index.d.ts CHANGED
@@ -10,11 +10,12 @@
10
10
  * The world is the single source of truth for every member the renderer emits straight from it.
11
11
  * Three members deviate from the world on purpose, and all three are QuickJS-shaped: `today` is
12
12
  * omitted from `RuleContext` because QuickJS exposes it as a conditional property rather than a
13
- * callable, a shape this renderer cannot state honestly from the world; `facts` is added to
14
- * `RuleContext` because QuickJS hands a per-file rule `facts` that the world declares only on
15
- * `reduce-context`; and `types` is added to `RuleContext` because `ctx.types` — the bounded
13
+ * callable, a shape this renderer cannot state honestly from the world; `types` is added
14
+ * to `RuleContext` because `ctx.types` the bounded
16
15
  * type oracle — is QuickJS-only and has no presence in `world.wit` at all: a component rule
17
- * cannot declare `requires`, so there is nothing for the world to say about it. Nothing else is
16
+ * cannot declare `requires`, so there is nothing for the world to say about it; and `flow` is
17
+ * added to `RuleContext` for the same reason — `ctx.flow`, the taint-analysis completeness
18
+ * surface, is QuickJS-only too and has no presence in `world.wit` either. Nothing else is
18
19
  * added or omitted by hand.
19
20
  */
20
21
 
@@ -136,7 +137,13 @@ export interface Gates {
136
137
  fileNotContains?: string[]
137
138
  }
138
139
 
139
- /** A replacement a rule offers for a violation. */
140
+ /**
141
+ * A replacement a rule offers for a violation.
142
+ *
143
+ * A per-file offer alone: a fix names a node, and the reduce phase consumes facts and the
144
+ * file list and nothing else — there is no parse tree there for `node` to name, which is why
145
+ * a reduce report's options carry a message and never a fix.
146
+ */
140
147
  export interface Fix {
141
148
  /** The node whose text is replaced. */
142
149
  node: Node
@@ -160,6 +167,20 @@ export interface ReportOptions {
160
167
  fix?: Fix
161
168
  }
162
169
 
170
+ /** Options for a single reduce report. */
171
+ export interface ReduceReportOptions {
172
+ /** Overrides the card's `message` for this one violation. */
173
+ message?: string
174
+ /**
175
+ * Never present. A fix replaces a node's text, and the reduce phase has no parse tree —
176
+ * `node` has nothing to name, and both hosts throw on a supplied fix rather than drop it.
177
+ * Typed `never` rather than left absent so offering one fails to compile even for an
178
+ * object built before the call, which a fresh literal's excess-property check alone would
179
+ * admit.
180
+ */
181
+ fix?: never
182
+ }
183
+
163
184
  /**
164
185
  * A fact a rule emits for the reduce phase.
165
186
  *
@@ -355,6 +376,18 @@ export interface TypeApi {
355
376
  complete(): boolean
356
377
  }
357
378
 
379
+ /**
380
+ * The taint-analysis completeness surface, present on `ctx.flow` for a rule that declares
381
+ * `flow`. Answers whether the analysis saw through every construct in the file being checked
382
+ * — so a rule can say "I could not verify this file" rather than nothing.
383
+ */
384
+ export interface FlowApi {
385
+ /** `true` iff the analysis dropped no construct in this file (`dropped === 0`). */
386
+ complete(): boolean
387
+ /** How many constructs the analysis could not see through in this file. */
388
+ readonly dropped: number
389
+ }
390
+
358
391
  /** A rule's RuleContext surface. */
359
392
  export interface RuleContext {
360
393
  readonly filePath: string
@@ -381,8 +414,6 @@ export interface RuleContext {
381
414
  emitFact(fact: Fact): void
382
415
  loc(n: Node): NodeLocation | undefined
383
416
  report(at: Node, message?: string | ReportOptions): void
384
- /** Facts emitted so far, optionally filtered by `kind`. */
385
- facts(kind?: string): EmittedFact[]
386
417
  /**
387
418
  * The type oracle, present only for a rule that declared `requires: ['types']`.
388
419
  * Bounded by a fixed depth rather than by file: it follows an import into the
@@ -396,6 +427,11 @@ export interface RuleContext {
396
427
  * deliberate.
397
428
  */
398
429
  types: TypeApi
430
+ /**
431
+ * The taint-analysis completeness surface, present only in the flow phase
432
+ * of a rule that declares `flow` (inside `checkFlow` / `checkFile`).
433
+ */
434
+ flow: FlowApi
399
435
  }
400
436
 
401
437
  /** A violation the reduce phase reports, which has no node to point at. */
@@ -412,7 +448,8 @@ export interface ReduceLocation {
412
448
  export interface ReduceContext {
413
449
  readonly files: string[]
414
450
  facts(kind?: string): EmittedFact[]
415
- report(at: ReduceLocation, message?: string | ReportOptions): void
451
+ /** Takes a message; a supplied `fix` throws — there is no node to attach one to. */
452
+ report(at: ReduceLocation, message?: string | ReduceReportOptions): void
416
453
  }
417
454
 
418
455
  /** Queries whose named captures drive the taint analysis. */
@@ -445,8 +482,18 @@ export type ObligationSpec = {
445
482
  /**
446
483
  * `'function'` — every path out of the enclosing function, `return`/`throw` included.
447
484
  * `'block'` — every path out of the block the acquire is in.
485
+ * `'module'` — a matching-`@key` release must exist somewhere in the file.
486
+ * `'class'` — a matching-`@key` release must exist in the same class.
487
+ * `'component'` — a matching-`@key` release must exist in the same React function component.
448
488
  */
449
- scope: 'function' | 'block'
489
+ scope: 'function' | 'block' | 'module' | 'class' | 'component'
490
+ /**
491
+ * How a `@key` correlates an acquire with a release. `'text'` (default) — exact captured
492
+ * text. `'binding'` — shared value origin (follows copies/wrappers/ternaries); requires
493
+ * `@key`. Note: `'binding'` does not correlate distinct per-frame parameters — use `'text'`
494
+ * for the sibling-callback pattern.
495
+ */
496
+ keyBy?: 'text' | 'binding'
450
497
  }
451
498
 
452
499
  /** An acquire some path leaves undischarged, passed to `checkObligation`. */
@@ -456,6 +503,11 @@ export type UnmetObligation = {
456
503
  readonly exit: Node
457
504
  /** Whether any path *did* discharge it: partial coverage reads differently to none. */
458
505
  readonly partial: boolean
506
+ /**
507
+ * The acquire's `@key` capture, when the rule's acquire/release queries bind one — so a
508
+ * report can name the value. Absent for an un-keyed obligation.
509
+ */
510
+ readonly key?: Node
459
511
  }
460
512
 
461
513
  /** A rule, as `defineRule` takes it. */
@@ -539,6 +591,12 @@ export interface Rule {
539
591
  obligation?: ObligationSpec
540
592
  /** Called once per value left with an unmet obligation at the end of its scope. */
541
593
  checkObligation?(ctx: RuleContext, unmet: UnmetObligation): void
594
+ /**
595
+ * Called once per file the rule's flow phase examined, including files with no flow.
596
+ * Read `ctx.flow.complete()` / `ctx.flow.dropped` and report at `ctx.root` when the taint
597
+ * analysis could not see through every construct. Requires `flow`.
598
+ */
599
+ checkFile?(ctx: RuleContext): void
542
600
  }
543
601
 
544
602
  /** A lanekeep configuration, as `defineConfig` takes it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lanekeep",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Deterministic, AST-based architectural conformance checking",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -23,10 +23,10 @@
23
23
  "node": ">=18"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@lanekeep/darwin-arm64": "0.9.0",
27
- "@lanekeep/linux-arm64": "0.9.0",
28
- "@lanekeep/linux-x64": "0.9.0",
29
- "@lanekeep/win32-x64": "0.9.0"
26
+ "@lanekeep/darwin-arm64": "0.11.0",
27
+ "@lanekeep/linux-arm64": "0.11.0",
28
+ "@lanekeep/linux-x64": "0.11.0",
29
+ "@lanekeep/win32-x64": "0.11.0"
30
30
  },
31
31
  "main": "index.js",
32
32
  "types": "index.d.ts",