@nifrajs/cli 4.0.2 → 5.0.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.
- package/README.md +3 -0
- package/dist/agent-files.d.ts +107 -0
- package/dist/agent-files.d.ts.map +1 -0
- package/dist/agent-files.js +162 -0
- package/dist/agent-files.js.map +1 -0
- package/dist/app-files.d.ts +6 -0
- package/dist/app-files.d.ts.map +1 -1
- package/dist/app-files.js +27 -2
- package/dist/app-files.js.map +1 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +7 -2
- package/dist/assure-hydration.js.map +1 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +13 -3
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check-diagnostics.d.ts +20 -2
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +54 -7
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +75 -22
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +1475 -310
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +2 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +192 -65
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +112 -5
- package/dist/cli.js.map +1 -1
- package/dist/client-module.d.ts +18 -0
- package/dist/client-module.d.ts.map +1 -0
- package/dist/client-module.js +49 -0
- package/dist/client-module.js.map +1 -0
- package/dist/command-catalog.d.ts +16 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +133 -29
- package/dist/command-catalog.js.map +1 -1
- package/dist/component-check.d.ts +13 -0
- package/dist/component-check.d.ts.map +1 -0
- package/dist/component-check.js +59 -0
- package/dist/component-check.js.map +1 -0
- package/dist/contract-proof.d.ts +1 -1
- package/dist/contract-proof.d.ts.map +1 -1
- package/dist/contract-proof.js +2 -24
- package/dist/contract-proof.js.map +1 -1
- package/dist/contracts.d.ts +19 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +50 -3
- package/dist/contracts.js.map +1 -1
- package/dist/db-tool.d.ts.map +1 -1
- package/dist/db-tool.js +4 -3
- package/dist/db-tool.js.map +1 -1
- package/dist/dev-bun-config.d.ts +5 -1
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +44 -11
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +9 -8
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +28 -24
- package/dist/diff-tool.js.map +1 -1
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +23 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +6 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +6 -0
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +21 -2
- package/dist/fix-recipes.js.map +1 -1
- package/dist/hand-built-manifest.d.ts +27 -0
- package/dist/hand-built-manifest.d.ts.map +1 -0
- package/dist/hand-built-manifest.js +124 -0
- package/dist/hand-built-manifest.js.map +1 -0
- package/dist/hooks-report.d.ts +23 -0
- package/dist/hooks-report.d.ts.map +1 -0
- package/dist/hooks-report.js +104 -0
- package/dist/hooks-report.js.map +1 -0
- package/dist/init-agents.d.ts +2 -2
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +17 -11
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/nifra-project.d.ts +41 -0
- package/dist/internal/nifra-project.d.ts.map +1 -0
- package/dist/internal/nifra-project.js +183 -0
- package/dist/internal/nifra-project.js.map +1 -0
- package/dist/internal/project-specifiers.d.ts +16 -0
- package/dist/internal/project-specifiers.d.ts.map +1 -0
- package/dist/internal/project-specifiers.js +396 -0
- package/dist/internal/project-specifiers.js.map +1 -0
- package/dist/internal/script-kind.d.ts.map +1 -1
- package/dist/internal/script-kind.js +2 -1
- package/dist/internal/script-kind.js.map +1 -1
- package/dist/internal/source-facts.d.ts +2 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +11 -0
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +5 -3
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -6
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +55 -24
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +93 -15
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +31 -8
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +37 -7
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +5 -5
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +2 -2
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-exec.d.ts +12 -4
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +50 -13
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-io.d.ts +17 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +40 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts.map +1 -1
- package/dist/mcp-isolate.js +3 -2
- package/dist/mcp-isolate.js.map +1 -1
- package/dist/mcp-project-child.js +1 -1
- package/dist/mcp-project-child.js.map +1 -1
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +50 -10
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-ws.d.ts.map +1 -1
- package/dist/mcp-ws.js +3 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -2
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +7 -0
- package/dist/migrate-layout.d.ts.map +1 -1
- package/dist/migrate-layout.js +778 -64
- package/dist/migrate-layout.js.map +1 -1
- package/dist/output-schemas.d.ts +94 -0
- package/dist/output-schemas.d.ts.map +1 -0
- package/dist/output-schemas.js +864 -0
- package/dist/output-schemas.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +3 -2
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +15 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +1 -0
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/5.0.0.d.ts +11 -0
- package/dist/recipes/5.0.0.d.ts.map +1 -0
- package/dist/recipes/5.0.0.js +34 -0
- package/dist/recipes/5.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts +7 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +12 -3
- package/dist/replay.js.map +1 -1
- package/dist/review.d.ts +3 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +38 -7
- package/dist/review.js.map +1 -1
- package/dist/rules/codes.d.ts +10 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +10 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/comment-markers.d.ts +16 -7
- package/dist/rules/comment-markers.d.ts.map +1 -1
- package/dist/rules/comment-markers.js +58 -19
- package/dist/rules/comment-markers.js.map +1 -1
- package/dist/rules/contract-visibility.d.ts +3 -0
- package/dist/rules/contract-visibility.d.ts.map +1 -0
- package/dist/rules/contract-visibility.js +337 -0
- package/dist/rules/contract-visibility.js.map +1 -0
- package/dist/rules/data-guard.d.ts +7 -0
- package/dist/rules/data-guard.d.ts.map +1 -1
- package/dist/rules/data-guard.js +319 -37
- package/dist/rules/data-guard.js.map +1 -1
- package/dist/rules/layout.d.ts +4 -0
- package/dist/rules/layout.d.ts.map +1 -0
- package/dist/rules/layout.js +19 -0
- package/dist/rules/layout.js.map +1 -0
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +158 -43
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/pages.d.ts +24 -0
- package/dist/rules/pages.d.ts.map +1 -1
- package/dist/rules/pages.js +221 -3
- package/dist/rules/pages.js.map +1 -1
- package/dist/rules/routes.d.ts +6 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +111 -16
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.js +2 -2
- package/dist/rules/secrets.js.map +1 -1
- package/dist/rules/security.d.ts +12 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +281 -68
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/zones.d.ts +13 -0
- package/dist/rules/zones.d.ts.map +1 -1
- package/dist/rules/zones.js +192 -40
- package/dist/rules/zones.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +6 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +14 -5
- package/dist/sdk.js.map +1 -1
- package/dist/stylex-migrate.d.ts.map +1 -1
- package/dist/stylex-migrate.js +163 -7
- package/dist/stylex-migrate.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +12 -3
- package/dist/test-tool.js.map +1 -1
- package/dist/typecheck-coverage.d.ts +20 -0
- package/dist/typecheck-coverage.d.ts.map +1 -0
- package/dist/typecheck-coverage.js +129 -0
- package/dist/typecheck-coverage.js.map +1 -0
- package/dist/upgrade.d.ts +69 -0
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +420 -77
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +11 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/version-drift.d.ts +22 -0
- package/dist/version-drift.d.ts.map +1 -0
- package/dist/version-drift.js +42 -0
- package/dist/version-drift.js.map +1 -0
- package/dist/web-app-options.d.ts +1 -1
- package/dist/web-app-options.d.ts.map +1 -1
- package/dist/web-app-options.js +1 -1
- package/dist/web-app-options.js.map +1 -1
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +20 -8
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +54 -6
- package/docs/llms-full.txt +1539 -138
- package/docs/types.json +1456 -206
- package/package.json +11 -12
- package/src/agent-files.ts +203 -0
- package/src/app-files.ts +34 -4
- package/src/assure-hydration.ts +15 -2
- package/src/capabilities-tool.ts +15 -3
- package/src/check-diagnostics.ts +73 -9
- package/src/check-scan.ts +1773 -381
- package/src/check.ts +237 -76
- package/src/cli.ts +141 -5
- package/src/client-module.ts +61 -0
- package/src/command-catalog.ts +166 -37
- package/src/component-check.ts +79 -0
- package/src/contract-proof.ts +3 -25
- package/src/contracts.ts +60 -3
- package/src/db-tool.ts +5 -2
- package/src/dev-bun-config.ts +51 -10
- package/src/diagnostics.ts +3 -0
- package/src/diff-tool.ts +33 -26
- package/src/doctor.ts +43 -21
- package/src/env-file.ts +7 -0
- package/src/fix-recipes.ts +25 -2
- package/src/hand-built-manifest.ts +144 -0
- package/src/hooks-report.ts +135 -0
- package/src/init-agents.ts +17 -11
- package/src/internal/nifra-project.ts +191 -0
- package/src/internal/project-specifiers.ts +424 -0
- package/src/internal/script-kind.ts +2 -1
- package/src/internal/source-facts.ts +25 -0
- package/src/internal/typescript-import.ts +10 -3
- package/src/introspect.ts +65 -27
- package/src/levels-tool.ts +117 -20
- package/src/load.ts +78 -14
- package/src/mcp-context.ts +7 -7
- package/src/mcp-exec.ts +60 -17
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +41 -0
- package/src/mcp-isolate.ts +9 -2
- package/src/mcp-project-child.ts +1 -1
- package/src/mcp-root.ts +58 -8
- package/src/mcp-ws.ts +4 -2
- package/src/mcp.ts +8 -3
- package/src/migrate-layout.ts +885 -63
- package/src/output-schemas.ts +1084 -0
- package/src/port.ts +3 -2
- package/src/project-facts.ts +17 -3
- package/src/recipes/5.0.0.ts +35 -0
- package/src/recipes/index.ts +10 -0
- package/src/replay.ts +14 -3
- package/src/review.ts +39 -10
- package/src/rules/codes.ts +10 -0
- package/src/rules/comment-markers.ts +57 -21
- package/src/rules/contract-visibility.ts +383 -0
- package/src/rules/data-guard.ts +351 -42
- package/src/rules/layout.ts +22 -0
- package/src/rules/legacy.ts +184 -51
- package/src/rules/pages.ts +238 -3
- package/src/rules/routes.ts +124 -16
- package/src/rules/secrets.ts +2 -2
- package/src/rules/security.ts +352 -71
- package/src/rules/zones.ts +205 -38
- package/src/scaffold.ts +6 -4
- package/src/sdk.ts +14 -5
- package/src/stylex-migrate.ts +134 -7
- package/src/test-tool.ts +18 -3
- package/src/typecheck-coverage.ts +134 -0
- package/src/upgrade.ts +487 -71
- package/src/verification-plan.ts +24 -0
- package/src/version-drift.ts +67 -0
- package/src/web-app-options.ts +2 -2
- package/src/work-graph.ts +17 -12
package/src/rules/routes.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { RESERVED_KEY_READOUT, reservedKeyFor } from "@nifrajs/client"
|
|
|
2
2
|
import { RoutePatternOverlapLimitError, routePatternOverlap } from "@nifrajs/core"
|
|
3
3
|
import { expandOptionalParams, paramConstraint } from "@nifrajs/core/pattern"
|
|
4
4
|
import { type Diagnostic, diagnostic } from "../diagnostics.ts"
|
|
5
|
-
import {
|
|
5
|
+
import { commentBlockExpectation, MISSING_EXPECTATION_REASON } from "./comment-markers.ts"
|
|
6
6
|
import type { CheckRule, RuleContext } from "./index.ts"
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -23,10 +23,13 @@ import type { CheckRule, RuleContext } from "./index.ts"
|
|
|
23
23
|
* own copy is a lint that can disagree with the compiler it is explaining.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
26
|
+
/** What marks a route deliberately served only to NON-typed-client consumers. */
|
|
27
|
+
const RESERVED_SEGMENT_EXPECTATION = ["NF-C018", "reserved-segment"]
|
|
28
|
+
// An intended overlap answers both findings about the pair: proven (NF-C024) or unprovable (NF-C025).
|
|
29
|
+
const ROUTE_OVERLAP_EXPECTATION = ["NF-C024", "NF-C025", "route-overlap"]
|
|
30
|
+
const PARAM_MODIFIER_EXPECTATION = ["NF-C026", "param-modifier"]
|
|
31
|
+
// A deliberately computed path is accepted with or without a reason: the finding is information.
|
|
32
|
+
const DYNAMIC_PATH_EXPECTATION = ["NF-C036"]
|
|
30
33
|
|
|
31
34
|
interface StaticRouteFact {
|
|
32
35
|
readonly file: string
|
|
@@ -92,14 +95,15 @@ export const reservedSegmentRule: CheckRule = {
|
|
|
92
95
|
lines = (ctx.project.source.read(route.file) ?? "").split("\n")
|
|
93
96
|
linesByFile.set(route.file, lines)
|
|
94
97
|
}
|
|
95
|
-
if (
|
|
98
|
+
if (commentBlockExpectation(lines, route.line, RESERVED_SEGMENT_EXPECTATION) !== undefined)
|
|
99
|
+
continue
|
|
96
100
|
findings.push(
|
|
97
101
|
diagnostic({
|
|
98
102
|
code: "NF-C018",
|
|
99
103
|
severity: "warn",
|
|
100
104
|
file: route.file,
|
|
101
105
|
line: route.line,
|
|
102
|
-
message: `${route.method} ${route.path} - segment '${segment}' collides with the reserved client proxy key '${collision}' (reserved: ${RESERVED_KEY_READOUT}); dot access cannot reach it - call the parent node with the segment instead (\`${routeEscapeHint(route.path, segment)}\`), rename the segment, or mark an intentionally non-typed-client route with \`//
|
|
106
|
+
message: `${route.method} ${route.path} - segment '${segment}' collides with the reserved client proxy key '${collision}' (reserved: ${RESERVED_KEY_READOUT}); dot access cannot reach it - call the parent node with the segment instead (\`${routeEscapeHint(route.path, segment)}\`), rename the segment, or mark an intentionally non-typed-client route with \`// nifra-expect NF-C018: <reason>\` above the registration. \`nifra fix --code NF-C018\` rewrites the broken call sites for you`,
|
|
103
107
|
evidence: [`${route.method} ${route.path}`, `segment: ${segment}`],
|
|
104
108
|
// The route is fine; what needs editing is every typed-client call site the collision
|
|
105
109
|
// broke, which the recipe finds from the compiler rather than from this one finding.
|
|
@@ -161,6 +165,54 @@ export const duplicateRouteRule: CheckRule = {
|
|
|
161
165
|
},
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
/** A segment the router reads as exactly its own text. */
|
|
169
|
+
const PLAIN_SEGMENT = /^[A-Za-z0-9_.~-]+$/
|
|
170
|
+
|
|
171
|
+
/** Routes keyed by their leading plain segments: two routes can overlap only if one's lead prefixes the other's. */
|
|
172
|
+
interface LeadNode {
|
|
173
|
+
readonly routes: number[]
|
|
174
|
+
readonly children: Map<string, LeadNode>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function leadOf(path: string): readonly string[] {
|
|
178
|
+
const lead: string[] = []
|
|
179
|
+
for (const segment of path.split("/").slice(1)) {
|
|
180
|
+
if (!PLAIN_SEGMENT.test(segment)) break
|
|
181
|
+
lead.push(segment)
|
|
182
|
+
}
|
|
183
|
+
return lead
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The routes in `root` whose lead is compatible with `lead`, in registration order. */
|
|
187
|
+
function routesSharingLead(root: LeadNode, lead: readonly string[]): number[] {
|
|
188
|
+
const found: number[] = []
|
|
189
|
+
let node: LeadNode | undefined = root
|
|
190
|
+
for (const segment of lead) {
|
|
191
|
+
for (const route of node.routes) found.push(route)
|
|
192
|
+
node = node.children.get(segment)
|
|
193
|
+
if (node === undefined) return found.sort((a, b) => a - b)
|
|
194
|
+
}
|
|
195
|
+
const below = [node]
|
|
196
|
+
for (const next of below) {
|
|
197
|
+
for (const route of next.routes) found.push(route)
|
|
198
|
+
for (const child of next.children.values()) below.push(child)
|
|
199
|
+
}
|
|
200
|
+
return found.sort((a, b) => a - b)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function addLead(root: LeadNode, lead: readonly string[], route: number): void {
|
|
204
|
+
let node = root
|
|
205
|
+
for (const segment of lead) {
|
|
206
|
+
let child = node.children.get(segment)
|
|
207
|
+
if (child === undefined) {
|
|
208
|
+
child = { routes: [], children: new Map() }
|
|
209
|
+
node.children.set(segment, child)
|
|
210
|
+
}
|
|
211
|
+
node = child
|
|
212
|
+
}
|
|
213
|
+
node.routes.push(route)
|
|
214
|
+
}
|
|
215
|
+
|
|
164
216
|
/**
|
|
165
217
|
* NF-C024: two different same-method route patterns in one source file accept at least one of the
|
|
166
218
|
* same paths. This is deliberately file-scoped, like NF-C019: the static scanner cannot prove
|
|
@@ -191,15 +243,34 @@ export const overlappingRouteRule: CheckRule = {
|
|
|
191
243
|
return value
|
|
192
244
|
}
|
|
193
245
|
|
|
246
|
+
const expectationByLine = new Map<number, ReturnType<typeof commentBlockExpectation>>()
|
|
247
|
+
const expectationAt = (line: number): ReturnType<typeof commentBlockExpectation> => {
|
|
248
|
+
if (!expectationByLine.has(line))
|
|
249
|
+
expectationByLine.set(
|
|
250
|
+
line,
|
|
251
|
+
commentBlockExpectation(lines(), line, ROUTE_OVERLAP_EXPECTATION),
|
|
252
|
+
)
|
|
253
|
+
return expectationByLine.get(line)
|
|
254
|
+
}
|
|
255
|
+
const leads = new Map<string, LeadNode>()
|
|
194
256
|
for (let laterIndex = 0; laterIndex < routes.length; laterIndex += 1) {
|
|
195
257
|
const later = routes[laterIndex]!
|
|
196
258
|
const laterKey = `${later.method} ${later.path}`
|
|
197
|
-
|
|
259
|
+
const lead = leadOf(later.path)
|
|
260
|
+
let methodLeads = leads.get(later.method)
|
|
261
|
+
if (methodLeads === undefined) {
|
|
262
|
+
methodLeads = { routes: [], children: new Map() }
|
|
263
|
+
leads.set(later.method, methodLeads)
|
|
264
|
+
}
|
|
265
|
+
const candidates = routesSharingLead(methodLeads, lead)
|
|
266
|
+
addLead(methodLeads, lead, laterIndex)
|
|
267
|
+
for (const earlierIndex of candidates) {
|
|
198
268
|
const earlier = routes[earlierIndex]!
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (
|
|
269
|
+
if (`${earlier.method} ${earlier.path}` === laterKey) continue
|
|
270
|
+
// Both are errors, so the comment silences only with a reason.
|
|
271
|
+
const expectations = [later.line, earlier.line].map(expectationAt)
|
|
272
|
+
if (expectations.some((expected) => expected?.reason !== undefined)) continue
|
|
273
|
+
const unreasoned = expectations.some((expected) => expected !== undefined)
|
|
203
274
|
|
|
204
275
|
let witness: string | undefined
|
|
205
276
|
try {
|
|
@@ -212,7 +283,7 @@ export const overlappingRouteRule: CheckRule = {
|
|
|
212
283
|
severity: "error",
|
|
213
284
|
file: later.file,
|
|
214
285
|
line: later.line,
|
|
215
|
-
message: `${laterKey} could not be proven disjoint from ${earlier.method} ${earlier.path}; bounded route-overlap analysis exceeded its safety budget - simplify the patterns or add
|
|
286
|
+
message: `${laterKey} could not be proven disjoint from ${earlier.method} ${earlier.path}; bounded route-overlap analysis exceeded its safety budget - simplify the patterns or add a \`// nifra-expect NF-C025: <reason>\` comment only after manual review${unreasoned ? `; ${MISSING_EXPECTATION_REASON}` : ""}`,
|
|
216
287
|
evidence: [
|
|
217
288
|
`${earlier.method} ${earlier.path}`,
|
|
218
289
|
`${later.method} ${later.path}`,
|
|
@@ -237,7 +308,7 @@ export const overlappingRouteRule: CheckRule = {
|
|
|
237
308
|
severity: "error",
|
|
238
309
|
file: later.file,
|
|
239
310
|
line: later.line,
|
|
240
|
-
message: `${laterKey} overlaps ${earlier.method} ${earlier.path} (first at line ${earlier.line}); witness path: ${witness} - make the patterns disjoint,
|
|
311
|
+
message: `${laterKey} overlaps ${earlier.method} ${earlier.path} (first at line ${earlier.line}); witness path: ${witness} - make the patterns disjoint, or add a \`// nifra-expect NF-C024: <reason>\` comment above either registration${unreasoned ? `; ${MISSING_EXPECTATION_REASON}` : ""}`,
|
|
241
312
|
evidence: [
|
|
242
313
|
`${earlier.method} ${earlier.path}`,
|
|
243
314
|
`${later.method} ${later.path}`,
|
|
@@ -296,7 +367,8 @@ export const paramModifierRule: CheckRule = {
|
|
|
296
367
|
lines = (ctx.project.source.read(route.file) ?? "").split("\n")
|
|
297
368
|
linesByFile.set(route.file, lines)
|
|
298
369
|
}
|
|
299
|
-
if (
|
|
370
|
+
if (commentBlockExpectation(lines, route.line, PARAM_MODIFIER_EXPECTATION) !== undefined)
|
|
371
|
+
continue
|
|
300
372
|
const character = text[text.length - 1]!
|
|
301
373
|
findings.push(
|
|
302
374
|
diagnostic({
|
|
@@ -304,7 +376,7 @@ export const paramModifierRule: CheckRule = {
|
|
|
304
376
|
severity: "warn",
|
|
305
377
|
file: route.file,
|
|
306
378
|
line: route.line,
|
|
307
|
-
message: `${route.method} ${route.path} - '${character}' after '${text.slice(0, -1)}' is matched as literal text, not as a param modifier${character === "?" ? "; a request path never contains '?', so this route cannot be reached" : ""}. Optional params are supported as the trailing whole segments of a path (\`/users/:id?\`, \`/d/:year?/:month?\`), and a constraint as one character class with an optional count (\`:id{[0-9]+}\`, \`:code{[A-Z]{2}}\`) or a list of two or more values (\`:ext{png|jpg}\`); for anything else register each path, or mark a deliberate literal with \`//
|
|
379
|
+
message: `${route.method} ${route.path} - '${character}' after '${text.slice(0, -1)}' is matched as literal text, not as a param modifier${character === "?" ? "; a request path never contains '?', so this route cannot be reached" : ""}. Optional params are supported as the trailing whole segments of a path (\`/users/:id?\`, \`/d/:year?/:month?\`), and a constraint as one character class with an optional count (\`:id{[0-9]+}\`, \`:code{[A-Z]{2}}\`) or a list of two or more values (\`:ext{png|jpg}\`); for anything else register each path, or mark a deliberate literal with \`// nifra-expect NF-C026: <reason>\` above the registration`,
|
|
308
380
|
evidence: [`${route.method} ${route.path}`, `literal: ${text}`],
|
|
309
381
|
verify: "nifra check --lints-only",
|
|
310
382
|
}),
|
|
@@ -314,9 +386,45 @@ export const paramModifierRule: CheckRule = {
|
|
|
314
386
|
},
|
|
315
387
|
}
|
|
316
388
|
|
|
389
|
+
/**
|
|
390
|
+
* NF-C036: a registration whose path the static scan cannot read. The route still serves; what it
|
|
391
|
+
* loses is every source-level analysis keyed by path - automatic capability provenance and the route
|
|
392
|
+
* lints above - so it is reported as information, naming the expression.
|
|
393
|
+
*/
|
|
394
|
+
export const unresolvedRoutePathRule: CheckRule = {
|
|
395
|
+
code: "NF-C036",
|
|
396
|
+
title: "Route path the static scan cannot read",
|
|
397
|
+
async scan(ctx) {
|
|
398
|
+
const findings: Diagnostic[] = []
|
|
399
|
+
const linesByFile = new Map<string, readonly string[]>()
|
|
400
|
+
for (const route of ctx.project.sourceFindings.routePaths) {
|
|
401
|
+
let lines = linesByFile.get(route.file)
|
|
402
|
+
if (lines === undefined) {
|
|
403
|
+
lines = (ctx.project.source.read(route.file) ?? "").split("\n")
|
|
404
|
+
linesByFile.set(route.file, lines)
|
|
405
|
+
}
|
|
406
|
+
if (commentBlockExpectation(lines, route.line, DYNAMIC_PATH_EXPECTATION) !== undefined)
|
|
407
|
+
continue
|
|
408
|
+
findings.push(
|
|
409
|
+
diagnostic({
|
|
410
|
+
code: "NF-C036",
|
|
411
|
+
severity: "info",
|
|
412
|
+
file: route.file,
|
|
413
|
+
line: route.line,
|
|
414
|
+
message: `${route.method} ${route.expression} - the static scan cannot read this route path, so automatic capability provenance and the route lints do not see the route. Write the path as a string literal, or as a module-level \`const\` (optionally \`as const\`) in this file or exported from a project file it imports; mark a deliberately computed path with a \`// nifra-expect NF-C036: <reason>\` comment above the registration`,
|
|
415
|
+
evidence: [`${route.method} ${route.expression}`],
|
|
416
|
+
verify: "nifra check --lints-only",
|
|
417
|
+
}),
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
return findings
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
|
|
317
424
|
export const routeRules = Object.freeze([
|
|
318
425
|
reservedSegmentRule,
|
|
319
426
|
duplicateRouteRule,
|
|
320
427
|
overlappingRouteRule,
|
|
321
428
|
paramModifierRule,
|
|
429
|
+
unresolvedRoutePathRule,
|
|
322
430
|
])
|
package/src/rules/secrets.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { stripComments } from "../check-scan.ts"
|
|
|
17
17
|
import { type Diagnostic, diagnostic } from "../diagnostics.ts"
|
|
18
18
|
import { codeUnitOrder } from "../internal/code-unit-order.ts"
|
|
19
19
|
import type { CheckRule } from "./index.ts"
|
|
20
|
-
import { zonedFiles } from "./zones.ts"
|
|
20
|
+
import { buildText, zonedFiles } from "./zones.ts"
|
|
21
21
|
|
|
22
22
|
const BROWSER_CODE = new Set(["route-frontend", "frontend", "shared"])
|
|
23
23
|
|
|
@@ -123,7 +123,7 @@ export const secretSourceRule: CheckRule = {
|
|
|
123
123
|
for (const { file, app, classification } of files) {
|
|
124
124
|
if (!apps.has(app)) apps.set(app, [])
|
|
125
125
|
if (!BROWSER_CODE.has(classification.zone)) continue
|
|
126
|
-
const text = ctx
|
|
126
|
+
const text = buildText(ctx, file)
|
|
127
127
|
if (text === undefined) continue
|
|
128
128
|
const name = app === "" ? file : file.slice(app.length + 1)
|
|
129
129
|
apps.get(app)?.push({ name, text, firstParty: true })
|