@telorun/analyzer 0.73.0 → 0.74.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/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +26 -7
- package/dist/artifact-axes.d.ts +14 -0
- package/dist/artifact-axes.d.ts.map +1 -0
- package/dist/artifact-axes.js +12 -0
- package/dist/artifact-layer-index.d.ts +2 -2
- package/dist/artifact-layer-index.d.ts.map +1 -1
- package/dist/artifact-layer-index.js +9 -4
- package/dist/artifact-selector.d.ts +47 -21
- package/dist/artifact-selector.d.ts.map +1 -1
- package/dist/artifact-selector.js +85 -16
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +73 -16
- package/dist/call-graph.d.ts +12 -1
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +22 -0
- package/dist/catch-scope.js +1 -1
- package/dist/forwarded-declaration.d.ts +26 -0
- package/dist/forwarded-declaration.d.ts.map +1 -0
- package/dist/forwarded-declaration.js +31 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +21 -5
- package/dist/module-alias-scope.d.ts +11 -0
- package/dist/module-alias-scope.d.ts.map +1 -1
- package/dist/module-alias-scope.js +10 -0
- package/dist/module-file-claims.d.ts +17 -1
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +38 -3
- package/dist/module-named-files.d.ts +72 -0
- package/dist/module-named-files.d.ts.map +1 -0
- package/dist/module-named-files.js +115 -0
- package/dist/native-entries.d.ts +83 -0
- package/dist/native-entries.d.ts.map +1 -0
- package/dist/native-entries.js +169 -0
- package/dist/normalize-inline-resources.d.ts +41 -13
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +236 -54
- package/dist/reference-field-map.d.ts +59 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +200 -34
- package/dist/release/payload-digest.d.ts +2 -1
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +2 -1
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +8 -5
- package/dist/resolve-throws-union.d.ts +25 -4
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +62 -32
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +2 -1
- package/dist/rewrite-synthetic-origins.d.ts +6 -1
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
- package/dist/rewrite-synthetic-origins.js +107 -13
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +18 -5
- package/dist/schema-walk.d.ts +47 -13
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +150 -30
- package/dist/scope-declarations.d.ts +59 -0
- package/dist/scope-declarations.d.ts.map +1 -0
- package/dist/scope-declarations.js +45 -0
- package/dist/source-entries.d.ts +119 -0
- package/dist/source-entries.d.ts.map +1 -0
- package/dist/source-entries.js +349 -0
- package/dist/step-bodies.d.ts +24 -0
- package/dist/step-bodies.d.ts.map +1 -0
- package/dist/step-bodies.js +28 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +3 -2
- package/dist/validate-module-artifact.d.ts +4 -2
- package/dist/validate-module-artifact.d.ts.map +1 -1
- package/dist/validate-module-artifact.js +43 -4
- package/dist/validate-native-entries.d.ts +15 -0
- package/dist/validate-native-entries.d.ts.map +1 -0
- package/dist/validate-native-entries.js +103 -0
- package/dist/validate-observed-state.d.ts +7 -1
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +24 -6
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +2 -2
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +11 -5
- package/dist/validate-scope-reach.d.ts +31 -0
- package/dist/validate-scope-reach.d.ts.map +1 -0
- package/dist/validate-scope-reach.js +180 -0
- package/dist/validate-source-entries.d.ts +16 -0
- package/dist/validate-source-entries.d.ts.map +1 -0
- package/dist/validate-source-entries.js +90 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +21 -39
- package/dist/value-type-keyword.d.ts +22 -5
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +16 -7
- package/package.json +3 -3
- package/src/analyzer.ts +33 -9
- package/src/artifact-axes.ts +24 -0
- package/src/artifact-layer-index.ts +9 -6
- package/src/artifact-selector.ts +110 -35
- package/src/builtins.ts +75 -16
- package/src/call-graph.ts +30 -0
- package/src/catch-scope.ts +1 -1
- package/src/forwarded-declaration.ts +32 -0
- package/src/index.ts +36 -1
- package/src/kernel-globals.ts +28 -5
- package/src/migrations/CLAUDE.md +35 -0
- package/src/module-alias-scope.ts +17 -0
- package/src/module-file-claims.ts +63 -4
- package/src/module-named-files.ts +162 -0
- package/src/native-entries.ts +227 -0
- package/src/normalize-inline-resources.ts +326 -70
- package/src/reference-field-map.ts +257 -52
- package/src/release/CLAUDE.md +32 -0
- package/src/release/payload-digest.ts +2 -1
- package/src/resolve-ref-sentinels.ts +7 -6
- package/src/resolve-throws-union.ts +87 -32
- package/src/resolve-zone-requirements.ts +2 -1
- package/src/rewrite-synthetic-origins.ts +107 -16
- package/src/schema-compat.ts +18 -4
- package/src/schema-walk.ts +162 -34
- package/src/scope-declarations.ts +98 -0
- package/src/source-entries.ts +502 -0
- package/src/step-bodies.ts +55 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +3 -2
- package/src/validate-module-artifact.ts +46 -3
- package/src/validate-native-entries.ts +147 -0
- package/src/validate-observed-state.ts +36 -7
- package/src/validate-reference-forms.ts +2 -3
- package/src/validate-references.ts +10 -6
- package/src/validate-scope-reach.ts +213 -0
- package/src/validate-source-entries.ts +120 -0
- package/src/validate-throws-coverage.ts +24 -31
- package/src/value-type-keyword.ts +35 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseLayerIndex, LayerIndexError } from "./artifact-layer-index.js";
|
|
2
|
-
import { ArtifactSelectorError, PLATFORM_AXES, selectorFromQualifiers, selectorKey, } from "./artifact-selector.js";
|
|
2
|
+
import { ArtifactSelectorError, PLATFORM_AXES, selectorContradictions, selectorFromQualifiers, selectorKey, } from "./artifact-selector.js";
|
|
3
3
|
import { readLibraryCandidates } from "./module-library.js";
|
|
4
4
|
import { DiagnosticSeverity } from "./types.js";
|
|
5
5
|
const SOURCE = "telo-analyzer";
|
|
@@ -17,10 +17,12 @@ const SOURCE = "telo-analyzer";
|
|
|
17
17
|
* selector (spec §1), which is what every module with two `js` controllers relies
|
|
18
18
|
* on.
|
|
19
19
|
*
|
|
20
|
-
* 1. **Controller selector qualifiers.**
|
|
20
|
+
* 1. **Controller selector qualifiers.** The platform axes and `siblings` are
|
|
21
21
|
* authored surface. An unknown qualifier is reported rather than ignored, since
|
|
22
22
|
* ignoring is what makes a typo invisible; an invalid value is reported here
|
|
23
|
-
* instead of throwing from the loader later
|
|
23
|
+
* instead of throwing from the loader later, and so is a combination no host
|
|
24
|
+
* can match (`selectorContradictions`, shared with `native:` and
|
|
25
|
+
* `exports.code:`).
|
|
24
26
|
* 2. **The published layer index.** The owner doc's JSON Schema covers shape; the
|
|
25
27
|
* semantic rules — controller-requires-selector, singletons carry none, no
|
|
26
28
|
* duplicate selector, the token grammar (`os: Linux` passes the schema and
|
|
@@ -68,6 +70,15 @@ function validateLibraryCandidates(manifest, out) {
|
|
|
68
70
|
// mean a consumer's import resolves by whichever candidate is read first.
|
|
69
71
|
const seen = new Map();
|
|
70
72
|
for (const candidate of candidates) {
|
|
73
|
+
for (const contradiction of selectorContradictions(candidate.selector)) {
|
|
74
|
+
out.push({
|
|
75
|
+
severity: DiagnosticSeverity.Error,
|
|
76
|
+
code: `LIBRARY_${contradiction.rule}`,
|
|
77
|
+
source: SOURCE,
|
|
78
|
+
message: `Telo.Library/${metadata?.name ?? "(unnamed)"}: ${candidate.origin}: ${contradiction.detail}`,
|
|
79
|
+
data: { resource, filePath: metadata?.source, path: "exports/code" },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
71
82
|
const key = selectorKey(candidate.selector);
|
|
72
83
|
const first = seen.get(key);
|
|
73
84
|
if (first) {
|
|
@@ -143,7 +154,35 @@ function validateControllerSelectors(manifest, out) {
|
|
|
143
154
|
// Validate the selector; the value is not otherwise needed here, since
|
|
144
155
|
// candidates sharing a selector legitimately share a layer.
|
|
145
156
|
try {
|
|
146
|
-
selectorFromQualifiers(parsed.format, parsed.qualifiers, candidate);
|
|
157
|
+
const selector = selectorFromQualifiers(parsed.format, parsed.qualifiers, candidate);
|
|
158
|
+
for (const contradiction of selectorContradictions(selector)) {
|
|
159
|
+
out.push({
|
|
160
|
+
severity: DiagnosticSeverity.Error,
|
|
161
|
+
code: `CONTROLLER_${contradiction.rule}`,
|
|
162
|
+
source: SOURCE,
|
|
163
|
+
message: `${manifest.kind}/${name ?? "(unnamed)"}: controller candidate "${candidate}": ` +
|
|
164
|
+
contradiction.detail,
|
|
165
|
+
data: { resource, filePath, path: `${at}?${contradiction.axis}` },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// A `dylib` opens over the Rust controller ABI, and a library built
|
|
169
|
+
// against another version of it must never match — so the candidate states
|
|
170
|
+
// which, in the `telo` family.
|
|
171
|
+
if (parsed.format === "dylib" && !selector.abi?.startsWith("telo-")) {
|
|
172
|
+
out.push({
|
|
173
|
+
severity: DiagnosticSeverity.Error,
|
|
174
|
+
code: "CONTROLLER_DYLIB_ABI_MISSING",
|
|
175
|
+
source: SOURCE,
|
|
176
|
+
message: `${manifest.kind}/${name ?? "(unnamed)"}: dylib candidate "${candidate}" ` +
|
|
177
|
+
(selector.abi === undefined
|
|
178
|
+
? `states no abi. `
|
|
179
|
+
: `states abi=${selector.abi}, which is not a Rust controller ABI. `) +
|
|
180
|
+
`A dylib is built against one version of the Rust controller ABI and must say which — ` +
|
|
181
|
+
`add abi=telo-<version>, e.g. abi=telo-2. Without it the Rust kernel downloads the ` +
|
|
182
|
+
`library on every host and refuses it only when it opens it.`,
|
|
183
|
+
data: { resource, filePath, path: at },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
147
186
|
}
|
|
148
187
|
catch (err) {
|
|
149
188
|
if (!(err instanceof ArtifactSelectorError))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The strict half of `native-entries.ts`: what `telo check` reports about a
|
|
5
|
+
* module doc's `native:` block.
|
|
6
|
+
*
|
|
7
|
+
* Every rule is decidable from the manifest and would otherwise surface only on
|
|
8
|
+
* a consumer's host, as a native file that never matches or a layer that
|
|
9
|
+
* overwrites another's file. Shape errors (a missing key, an unknown one) are
|
|
10
|
+
* left to the owner doc's JSON Schema, which reports them in the same pass.
|
|
11
|
+
*
|
|
12
|
+
* Entry-module-scoped: a dependency's block is not the consumer's to fix.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateNativeEntries(manifests: ResourceManifest[], entryModules?: ReadonlySet<string>): AnalysisDiagnostic[];
|
|
15
|
+
//# sourceMappingURL=validate-native-entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-native-entries.d.ts","sourceRoot":"","sources":["../src/validate-native-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,kBAAkB,EAAE,CA0HtB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describeSelector, selectorContradictions, selectorKey } from "./artifact-selector.js";
|
|
2
|
+
import { moduleDocumentClaims } from "./module-file-claims.js";
|
|
3
|
+
import { describeClaim, nativeClaimConflicts } from "./module-named-files.js";
|
|
4
|
+
import { readNativeEntries } from "./native-entries.js";
|
|
5
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
6
|
+
const SOURCE = "telo-analyzer";
|
|
7
|
+
/**
|
|
8
|
+
* The strict half of `native-entries.ts`: what `telo check` reports about a
|
|
9
|
+
* module doc's `native:` block.
|
|
10
|
+
*
|
|
11
|
+
* Every rule is decidable from the manifest and would otherwise surface only on
|
|
12
|
+
* a consumer's host, as a native file that never matches or a layer that
|
|
13
|
+
* overwrites another's file. Shape errors (a missing key, an unknown one) are
|
|
14
|
+
* left to the owner doc's JSON Schema, which reports them in the same pass.
|
|
15
|
+
*
|
|
16
|
+
* Entry-module-scoped: a dependency's block is not the consumer's to fix.
|
|
17
|
+
*/
|
|
18
|
+
export function validateNativeEntries(manifests, entryModules) {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const manifest of manifests) {
|
|
21
|
+
if (manifest.kind !== "Telo.Application" && manifest.kind !== "Telo.Library")
|
|
22
|
+
continue;
|
|
23
|
+
const metadata = (manifest.metadata ?? {});
|
|
24
|
+
const name = typeof metadata.name === "string" ? metadata.name : undefined;
|
|
25
|
+
const owned = entryModules === undefined ||
|
|
26
|
+
(typeof metadata.module === "string"
|
|
27
|
+
? entryModules.has(metadata.module)
|
|
28
|
+
: name === undefined || entryModules.has(name));
|
|
29
|
+
if (!owned)
|
|
30
|
+
continue;
|
|
31
|
+
const label = `${manifest.kind}/${name ?? "(unnamed)"}`;
|
|
32
|
+
const report = (code, path, message) => out.push({
|
|
33
|
+
severity: DiagnosticSeverity.Error,
|
|
34
|
+
code,
|
|
35
|
+
source: SOURCE,
|
|
36
|
+
message: `${label}: ${message}`,
|
|
37
|
+
data: {
|
|
38
|
+
resource: { kind: manifest.kind, name },
|
|
39
|
+
filePath: typeof metadata.source === "string" ? metadata.source : undefined,
|
|
40
|
+
path,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const { entries, problems } = readNativeEntries(manifest);
|
|
44
|
+
for (const problem of problems) {
|
|
45
|
+
if (problem.kind === "shape")
|
|
46
|
+
continue;
|
|
47
|
+
report(problem.kind === "escape" ? "NATIVE_PATH_ESCAPES_MODULE" : "NATIVE_ENTRY_INVALID", problem.path, problem.message);
|
|
48
|
+
}
|
|
49
|
+
if (entries.length === 0)
|
|
50
|
+
continue;
|
|
51
|
+
const moduleName = typeof metadata.module === "string" ? metadata.module : name;
|
|
52
|
+
for (const { entry, claim } of nativeClaimConflicts(entries, moduleDocumentClaims(manifests, moduleName))) {
|
|
53
|
+
report("NATIVE_PATH_CLAIMED", `native[${entry.index}].path`, `${entry.origin}: path '${entry.path}' is also named by ${describeClaim(claim)}. A native ` +
|
|
54
|
+
`file ships only in its platform's native layer and a file extracts from exactly one ` +
|
|
55
|
+
`layer, so publish refuses it — give the native entry its own file, or drop the other ` +
|
|
56
|
+
`declaration.`);
|
|
57
|
+
}
|
|
58
|
+
const bySelector = new Map();
|
|
59
|
+
const byPath = new Map();
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const at = `native[${entry.index}]`;
|
|
62
|
+
if (entry.selector.format === "node" && entry.selector.abi === undefined) {
|
|
63
|
+
report("NATIVE_NODE_ABI_MISSING", at, `${entry.origin}: a 'node' addon is built against one Node ABI and must state it — ` +
|
|
64
|
+
`add abi: node-<NODE_MODULE_VERSION>, e.g. 'node-137'. Without it the file matches ` +
|
|
65
|
+
`every Node release and fails to load on all but one.`);
|
|
66
|
+
}
|
|
67
|
+
for (const contradiction of selectorContradictions(entry.selector)) {
|
|
68
|
+
report(`NATIVE_${contradiction.rule}`, `${at}.${contradiction.axis}`, `${entry.origin}: ${contradiction.detail}`);
|
|
69
|
+
}
|
|
70
|
+
const key = `${entry.name}\0${selectorKey(entry.selector)}`;
|
|
71
|
+
const twin = bySelector.get(key);
|
|
72
|
+
if (twin) {
|
|
73
|
+
report("NATIVE_ENTRY_DUPLICATE", at, `${entry.origin}: ${twin.origin} already declares '${entry.name}' for ` +
|
|
74
|
+
`${describeSelector(entry.selector)}. A name has one entry per platform tuple, or ` +
|
|
75
|
+
`a lookup by name could resolve to either file.`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
bySelector.set(key, entry);
|
|
79
|
+
}
|
|
80
|
+
const sharer = byPath.get(entry.path);
|
|
81
|
+
if (sharer && selectorKey(sharer.selector) !== selectorKey(entry.selector)) {
|
|
82
|
+
report("NATIVE_PATH_SHARED", `${at}.path`, `${entry.origin}: path '${entry.path}' is also declared by ${sharer.origin} for ` +
|
|
83
|
+
`${describeSelector(sharer.selector)}. Every layer of a module extracts into one ` +
|
|
84
|
+
`directory, so each platform tuple needs its own path — e.g. ` +
|
|
85
|
+
`'native/<os>-<arch>/…'.`);
|
|
86
|
+
}
|
|
87
|
+
else if (!sharer) {
|
|
88
|
+
byPath.set(entry.path, entry);
|
|
89
|
+
}
|
|
90
|
+
const through = [...byPath.values()].find((other) => other !== entry &&
|
|
91
|
+
(entry.path.startsWith(`${other.path}/`) || other.path.startsWith(`${entry.path}/`)));
|
|
92
|
+
if (through) {
|
|
93
|
+
const relation = entry.path.startsWith(`${through.path}/`)
|
|
94
|
+
? `runs through '${through.path}' as a directory`
|
|
95
|
+
: `is a directory that '${through.path}' runs through`;
|
|
96
|
+
report("NATIVE_PATH_NESTED", `${at}.path`, `${entry.origin}: path '${entry.path}' ${relation}, and ${through.origin} declares ` +
|
|
97
|
+
`'${through.path}'. Every layer of a module extracts into one directory, so a path ` +
|
|
98
|
+
`cannot be both a file and a directory — give each entry its own path.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
@@ -70,12 +70,18 @@ export declare function observedStateRead(chain: readonly string[]): ObservedSta
|
|
|
70
70
|
export declare function collectRunReachableNames(graph: CallGraph): Set<string>;
|
|
71
71
|
/** What a resource name resolves to for CEL purposes. `status` is present only
|
|
72
72
|
* when the kind declares one; `scoped` marks a resource declared inside an
|
|
73
|
-
* `x-telo-scope` slot, which resolves only within that scope's regions.
|
|
73
|
+
* `x-telo-scope` slot, which resolves only within that scope's regions.
|
|
74
|
+
* `forwardedFrom` marks a dependency's declaration, keyed by
|
|
75
|
+
* `forwardedResourceKey`: it is in no consumer's `resources`, but a read inside
|
|
76
|
+
* that dependency's own manifests still resolves to it. */
|
|
74
77
|
export interface AnalyzedResource {
|
|
75
78
|
kind: string;
|
|
76
79
|
status?: Record<string, any>;
|
|
77
80
|
scoped?: boolean;
|
|
81
|
+
forwardedFrom?: string;
|
|
78
82
|
}
|
|
83
|
+
/** The index key of a name a dependency declares, as read from inside it. */
|
|
84
|
+
export declare function forwardedResourceKey(module: string, name: string): string;
|
|
79
85
|
/**
|
|
80
86
|
* Index every resource a CEL `resources.…` read can name: the module's own
|
|
81
87
|
* top-level resources, the ones declared inside `x-telo-scope` slots (a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAgBjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB3E;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC;+CAC2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAOzF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AAGD;;;;;4DAK4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,gBAAgB,EAAE,EACtC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAAE,EAC/D,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,MAAM,CAAC,EAAE,YAAY,GACpB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAyD/B;AAyDD;;;gDAGgD;AAChD,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAmBN"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OBSERVED_STATE_KEY } from "@telorun/sdk";
|
|
2
2
|
import { effectiveStatusSchema } from "./extends-resolution.js";
|
|
3
|
+
import { isForwardedDeclaration } from "./forwarded-declaration.js";
|
|
3
4
|
import { parseExportEntry } from "./flatten-for-analyzer.js";
|
|
4
5
|
import { moduleScopedDefResolver, } from "./alias-resolver.js";
|
|
5
6
|
import { buildReferenceFieldMap, isScopeEntry, resolveFieldValues, } from "./reference-field-map.js";
|
|
@@ -100,6 +101,10 @@ export function collectRunReachableNames(graph) {
|
|
|
100
101
|
names.add(edge.toName);
|
|
101
102
|
return names;
|
|
102
103
|
}
|
|
104
|
+
/** The index key of a name a dependency declares, as read from inside it. */
|
|
105
|
+
export function forwardedResourceKey(module, name) {
|
|
106
|
+
return `${module}\0${name}`;
|
|
107
|
+
}
|
|
103
108
|
/**
|
|
104
109
|
* Index every resource a CEL `resources.…` read can name: the module's own
|
|
105
110
|
* top-level resources, the ones declared inside `x-telo-scope` slots (a
|
|
@@ -115,17 +120,30 @@ export function buildObservedStateIndex(manifests, defs, aliases, scopes) {
|
|
|
115
120
|
/** `module` is the resource's DECLARING module: an exported instance is
|
|
116
121
|
* written with that library's aliases (`kind: Self.Listener`), which the
|
|
117
122
|
* consumer's table cannot resolve. */
|
|
118
|
-
const record = (kind, key, scoped, module) => {
|
|
123
|
+
const record = (kind, key, scoped, module, forwardedFrom) => {
|
|
119
124
|
const status = effectiveStatusSchema(resolve.in(kind, module), resolve);
|
|
120
|
-
out.set(key, {
|
|
125
|
+
out.set(key, {
|
|
126
|
+
kind,
|
|
127
|
+
...(status ? { status } : {}),
|
|
128
|
+
...(scoped ? { scoped } : {}),
|
|
129
|
+
...(forwardedFrom !== undefined ? { forwardedFrom } : {}),
|
|
130
|
+
});
|
|
121
131
|
};
|
|
122
132
|
for (const manifest of manifests) {
|
|
123
133
|
const kind = manifest.kind;
|
|
124
134
|
const name = manifest.metadata?.name;
|
|
125
135
|
if (!kind || SYSTEM_KINDS.has(kind))
|
|
126
136
|
continue;
|
|
137
|
+
const module = manifest.metadata?.module;
|
|
138
|
+
// A dependency's declarations publish into ITS module's `resources`, not this
|
|
139
|
+
// one's: they are keyed by that module, and an export is also indexed under
|
|
140
|
+
// `<Alias>.<name>` below.
|
|
141
|
+
const forwardedFrom = isForwardedDeclaration(manifest) ? module : undefined;
|
|
142
|
+
if (forwardedFrom === undefined && isForwardedDeclaration(manifest))
|
|
143
|
+
continue;
|
|
144
|
+
const keyOf = (n) => forwardedFrom === undefined ? n : forwardedResourceKey(forwardedFrom, n);
|
|
127
145
|
if (name)
|
|
128
|
-
record(kind, name, false,
|
|
146
|
+
record(kind, keyOf(name), false, module, forwardedFrom);
|
|
129
147
|
const schema = resolve(kind)?.schema;
|
|
130
148
|
if (!schema)
|
|
131
149
|
continue;
|
|
@@ -137,7 +155,7 @@ export function buildObservedStateIndex(manifests, defs, aliases, scopes) {
|
|
|
137
155
|
const scopedKind = scopedEntry?.kind;
|
|
138
156
|
const scopedName = scopedEntry?.metadata?.name;
|
|
139
157
|
if (typeof scopedKind === "string" && typeof scopedName === "string") {
|
|
140
|
-
record(scopedKind, scopedName, true);
|
|
158
|
+
record(scopedKind, keyOf(scopedName), true, forwardedFrom, forwardedFrom);
|
|
141
159
|
}
|
|
142
160
|
}
|
|
143
161
|
}
|
|
@@ -207,8 +225,8 @@ function* importedExports(manifests, aliases) {
|
|
|
207
225
|
* flat field pass exactly as they do today. */
|
|
208
226
|
export function buildObservedStateResourcesSchema(index, open) {
|
|
209
227
|
const properties = {};
|
|
210
|
-
for (const [key, { status }] of index) {
|
|
211
|
-
if (!status)
|
|
228
|
+
for (const [key, { status, forwardedFrom }] of index) {
|
|
229
|
+
if (!status || forwardedFrom !== undefined)
|
|
212
230
|
continue;
|
|
213
231
|
applyObservedStateNode(properties, key, status);
|
|
214
232
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAKnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CAwEtB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
|
+
import { isForwardedDeclaration } from "./forwarded-declaration.js";
|
|
2
3
|
import { visitManifest } from "./manifest-visitor.js";
|
|
3
4
|
import { satisfiesValueBranch } from "./reference-field-map.js";
|
|
4
5
|
import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
@@ -40,8 +41,7 @@ export function validateReferenceForms(resources, registry, aliases, aliasesByMo
|
|
|
40
41
|
if (!aliases)
|
|
41
42
|
return [];
|
|
42
43
|
const diagnostics = [];
|
|
43
|
-
const
|
|
44
|
-
const localResources = resources.filter((r) => !isForeign(r));
|
|
44
|
+
const localResources = resources.filter((r) => !isForwardedDeclaration(r));
|
|
45
45
|
visitManifest(localResources, registry, {
|
|
46
46
|
onRef: (e) => {
|
|
47
47
|
const value = e.value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AA2InE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB;AAC5B;8EAC8E;AAC9E,aAAa,UAAQ,GACpB,OAAO,CAwBT;AAgED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAwgBtB"}
|
|
@@ -8,6 +8,8 @@ import { DiagnosticSeverity } from "./types.js";
|
|
|
8
8
|
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
9
9
|
import { isModuleKind } from "./module-kinds.js";
|
|
10
10
|
import { isInjectedDeclaration } from "./resource-input.js";
|
|
11
|
+
import { outsideScopesOf } from "./scope-declarations.js";
|
|
12
|
+
import { isForwardedDeclaration, isForwardedExport } from "./forwarded-declaration.js";
|
|
11
13
|
const SOURCE = "telo-analyzer";
|
|
12
14
|
/** What a name IS on one side of a collision. The three shapes share the
|
|
13
15
|
* kernel's one namespace but read as different things to an author — an alias
|
|
@@ -287,10 +289,10 @@ export function validateReferences(resources, context) {
|
|
|
287
289
|
// metadata.module that isn't a root module) are resolution TARGETS only: excluded from
|
|
288
290
|
// duplicate detection and local name resolution, and never walked as ref sources.
|
|
289
291
|
const moduleOf = (r) => r.metadata?.module;
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
const isForeign =
|
|
292
|
+
// A dependency's code — a forwarded export, or what extraction pulled out of one — is
|
|
293
|
+
// excluded from duplicate detection and local name resolution, and never walked as a
|
|
294
|
+
// ref source. Only an EXPORT is a cross-module resolution target.
|
|
295
|
+
const isForeign = isForwardedDeclaration;
|
|
294
296
|
// Forwarded exported instances keyed `${module}\0${name}` — the lookup that resolves
|
|
295
297
|
// whether a cross-module `!ref Alias.name` names a real exported instance.
|
|
296
298
|
const byModuleName = new Map();
|
|
@@ -308,7 +310,7 @@ export function validateReferences(resources, context) {
|
|
|
308
310
|
loadedModules.add(m);
|
|
309
311
|
continue;
|
|
310
312
|
}
|
|
311
|
-
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !
|
|
313
|
+
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForwardedExport(r))
|
|
312
314
|
continue;
|
|
313
315
|
const m = moduleOf(r);
|
|
314
316
|
if (!m)
|
|
@@ -398,6 +400,10 @@ export function validateReferences(resources, context) {
|
|
|
398
400
|
const target = visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
|
|
399
401
|
byName.get(localName);
|
|
400
402
|
if (!target) {
|
|
403
|
+
// A scoped name out of this declaration's reach has its own
|
|
404
|
+
// diagnostic, which says why; this one would say only "not found".
|
|
405
|
+
if (outsideScopesOf(r).some((scope) => scope.names.includes(localName)))
|
|
406
|
+
return;
|
|
401
407
|
diagnostics.push({
|
|
402
408
|
severity: DiagnosticSeverity.Error,
|
|
403
409
|
code: "UNRESOLVED_REFERENCE",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* `SCOPED_NAME_OUT_OF_REACH` — a declaration written inside a scope's region but
|
|
7
|
+
* created outside the scope (see {@link OutsideScope}) naming one of the scope's
|
|
8
|
+
* resources.
|
|
9
|
+
*
|
|
10
|
+
* Written there, the name reads as reachable — the scope's names shadow the
|
|
11
|
+
* module's in its regions — while at run time it resolves to nothing, or to a
|
|
12
|
+
* module-level resource that merely shares it. Reported at the reference, in
|
|
13
|
+
* each spelling a name can be read in:
|
|
14
|
+
*
|
|
15
|
+
* - a `!ref` sentinel, anywhere — the tag is unambiguous;
|
|
16
|
+
* - a resolved `{kind, name}`, but ONLY where the declaring kind says a
|
|
17
|
+
* reference is: a reference slot of its field map, or a step's dispatch slot.
|
|
18
|
+
* An object that merely has those keys is data;
|
|
19
|
+
* - `resources.<name>` in a CEL expression.
|
|
20
|
+
*
|
|
21
|
+
* Lexical: a scope declared on the way from the out-of-reach declaration down to
|
|
22
|
+
* the reference — found through the kinds' own scope slots — shadows the name
|
|
23
|
+
* again, since that one IS created around it. Entry-module-scoped: a
|
|
24
|
+
* dependency's code is its own analysis's to report.
|
|
25
|
+
*
|
|
26
|
+
* Browser-safe.
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateScopedNameReach(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, aliasesByModule: Map<string, AliasResolver>, rootModules: ReadonlySet<string>,
|
|
29
|
+
/** Member-access chains of a CEL expression — the analyzer's own parse. */
|
|
30
|
+
accessChains: (expression: string) => string[][]): AnalysisDiagnostic[];
|
|
31
|
+
//# sourceMappingURL=validate-scope-reach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-scope-reach.d.ts","sourceRoot":"","sources":["../src/validate-scope-reach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAanE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAYzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAC3C,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;AAChC,2EAA2E;AAC3E,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,EAAE,EAAE,GAC/C,kBAAkB,EAAE,CAyItB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { walkCelExpressions } from "@telorun/templating";
|
|
2
|
+
import { isForwardedDeclaration } from "./forwarded-declaration.js";
|
|
3
|
+
import { definitionInScope } from "./module-alias-scope.js";
|
|
4
|
+
import { isRefEntry, resolveFieldEntries } from "./reference-field-map.js";
|
|
5
|
+
import { declaredScopes, isScopeMember, outsideScopesOf, scopeEncloses, } from "./scope-declarations.js";
|
|
6
|
+
import { forEachStep, stepBodiesOf } from "./step-bodies.js";
|
|
7
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
8
|
+
const SOURCE = "telo-analyzer";
|
|
9
|
+
const DECLARATION_KINDS = new Set([
|
|
10
|
+
"Telo.Definition",
|
|
11
|
+
"Telo.Abstract",
|
|
12
|
+
"Telo.Application",
|
|
13
|
+
"Telo.Library",
|
|
14
|
+
"Telo.Import",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* `SCOPED_NAME_OUT_OF_REACH` — a declaration written inside a scope's region but
|
|
18
|
+
* created outside the scope (see {@link OutsideScope}) naming one of the scope's
|
|
19
|
+
* resources.
|
|
20
|
+
*
|
|
21
|
+
* Written there, the name reads as reachable — the scope's names shadow the
|
|
22
|
+
* module's in its regions — while at run time it resolves to nothing, or to a
|
|
23
|
+
* module-level resource that merely shares it. Reported at the reference, in
|
|
24
|
+
* each spelling a name can be read in:
|
|
25
|
+
*
|
|
26
|
+
* - a `!ref` sentinel, anywhere — the tag is unambiguous;
|
|
27
|
+
* - a resolved `{kind, name}`, but ONLY where the declaring kind says a
|
|
28
|
+
* reference is: a reference slot of its field map, or a step's dispatch slot.
|
|
29
|
+
* An object that merely has those keys is data;
|
|
30
|
+
* - `resources.<name>` in a CEL expression.
|
|
31
|
+
*
|
|
32
|
+
* Lexical: a scope declared on the way from the out-of-reach declaration down to
|
|
33
|
+
* the reference — found through the kinds' own scope slots — shadows the name
|
|
34
|
+
* again, since that one IS created around it. Entry-module-scoped: a
|
|
35
|
+
* dependency's code is its own analysis's to report.
|
|
36
|
+
*
|
|
37
|
+
* Browser-safe.
|
|
38
|
+
*/
|
|
39
|
+
export function validateScopedNameReach(manifests, registry, aliases, aliasesByModule, rootModules,
|
|
40
|
+
/** Member-access chains of a CEL expression — the analyzer's own parse. */
|
|
41
|
+
accessChains) {
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const root of manifests) {
|
|
44
|
+
if (!root.kind || DECLARATION_KINDS.has(root.kind) || isForwardedDeclaration(root))
|
|
45
|
+
continue;
|
|
46
|
+
const meta = root.metadata;
|
|
47
|
+
if (typeof meta?.name !== "string")
|
|
48
|
+
continue;
|
|
49
|
+
const module = typeof meta.module === "string" ? meta.module : undefined;
|
|
50
|
+
if (module && !rootModules.has(module))
|
|
51
|
+
continue;
|
|
52
|
+
const resource = { kind: root.kind, name: meta.name };
|
|
53
|
+
const filePath = typeof meta.source === "string" ? meta.source : undefined;
|
|
54
|
+
const report = (path, name, scope) => out.push({
|
|
55
|
+
severity: DiagnosticSeverity.Error,
|
|
56
|
+
code: "SCOPED_NAME_OUT_OF_REACH",
|
|
57
|
+
source: SOURCE,
|
|
58
|
+
message: `${scope.ownerKind}/${scope.ownerName}: '${name}' is declared in '${scope.field}:', but ` +
|
|
59
|
+
`the inline declaration referencing it is created where '${scope.ownerName}' is ` +
|
|
60
|
+
`declared, outside that scope, so the reference cannot reach it. Declare the ` +
|
|
61
|
+
`referencing resource under '${scope.field}:' with a name of its own and point at it ` +
|
|
62
|
+
`with '!ref'.`,
|
|
63
|
+
data: { resource, filePath, path },
|
|
64
|
+
});
|
|
65
|
+
/** A declaration's kind, field map and scopes, read in its module's aliases. */
|
|
66
|
+
const shape = (declaration) => {
|
|
67
|
+
const view = module !== undefined && typeof declaration.metadata?.module !== "string"
|
|
68
|
+
? { ...declaration, metadata: { ...declaration.metadata, module } }
|
|
69
|
+
: declaration;
|
|
70
|
+
const fieldMap = registry.expandedFieldMapForResource(view, aliases, aliasesByModule);
|
|
71
|
+
const definition = definitionInScope(registry, view.kind, view.metadata, aliases, aliasesByModule);
|
|
72
|
+
return {
|
|
73
|
+
fieldMap,
|
|
74
|
+
scopes: declaredScopes(declaration, fieldMap),
|
|
75
|
+
schema: registry.effectiveSchemaOf(definition),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const check = (declaration, prefix, reach, shadowed) => {
|
|
79
|
+
const stamped = outsideScopesOf(declaration);
|
|
80
|
+
const nextReach = new Map(reach);
|
|
81
|
+
const nextShadowed = new Set(shadowed);
|
|
82
|
+
for (const scope of stamped) {
|
|
83
|
+
for (const name of scope.names) {
|
|
84
|
+
nextReach.set(name, scope);
|
|
85
|
+
nextShadowed.delete(name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const { fieldMap, scopes, schema } = shape(declaration);
|
|
89
|
+
for (const scope of scopes) {
|
|
90
|
+
for (const member of scope.declarations) {
|
|
91
|
+
if (isScopeMember(member))
|
|
92
|
+
nextShadowed.add(member.metadata.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const at = (path) => (prefix ? (path ? `${prefix}.${path}` : prefix) : path);
|
|
96
|
+
const test = (name, path) => {
|
|
97
|
+
if (name === undefined || nextShadowed.has(name))
|
|
98
|
+
return;
|
|
99
|
+
const scope = nextReach.get(name);
|
|
100
|
+
if (scope)
|
|
101
|
+
report(at(path), name, scope);
|
|
102
|
+
};
|
|
103
|
+
const inScopeArray = (path) => scopes.some((scope) => scopeEncloses({ ...scope, regions: [scope.path] }, path));
|
|
104
|
+
// Tagged `!ref`s and CEL, anywhere but the declaration's own metadata and
|
|
105
|
+
// scope arrays (their members are checked below, with their own shadowing).
|
|
106
|
+
walkCelExpressions(declaration, "", (source, path, engine) => {
|
|
107
|
+
if (path === "metadata" || path.startsWith("metadata.") || inScopeArray(path))
|
|
108
|
+
return;
|
|
109
|
+
if (engine === "ref") {
|
|
110
|
+
test(localName(source), path);
|
|
111
|
+
}
|
|
112
|
+
else if (engine === "cel") {
|
|
113
|
+
for (const chain of accessChains(source)) {
|
|
114
|
+
if (chain[0] === "resources" && chain.length > 1)
|
|
115
|
+
test(chain[1], path);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// A resolved reference, where the kind declares one.
|
|
120
|
+
if (fieldMap) {
|
|
121
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
122
|
+
if (!isRefEntry(entry))
|
|
123
|
+
continue;
|
|
124
|
+
for (const { value, path } of resolveFieldEntries(declaration, fieldPath)) {
|
|
125
|
+
if (inScopeArray(path))
|
|
126
|
+
continue;
|
|
127
|
+
test(resolvedLocalName(value), path);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (schema) {
|
|
132
|
+
for (const body of stepBodiesOf(declaration, schema)) {
|
|
133
|
+
forEachStep(body, schema, (step, stepPath) => test(resolvedLocalName(step[body.invokeField]), `${stepPath}.${body.invokeField}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const scope of scopes) {
|
|
137
|
+
scope.declarations.forEach((member, i) => {
|
|
138
|
+
if (isScopeMember(member)) {
|
|
139
|
+
check(member, at(`${scope.path}[${i}]`), nextReach, nextShadowed);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
/** Only a stamped declaration can hold an out-of-reach reference; the rest
|
|
145
|
+
* is searched through the scope arrays that can hold one. */
|
|
146
|
+
const visit = (declaration, prefix) => {
|
|
147
|
+
if (outsideScopesOf(declaration).length > 0) {
|
|
148
|
+
check(declaration, prefix, new Map(), new Set());
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const scope of shape(declaration).scopes) {
|
|
152
|
+
scope.declarations.forEach((member, i) => {
|
|
153
|
+
if (!isScopeMember(member))
|
|
154
|
+
return;
|
|
155
|
+
visit(member, `${prefix ? `${prefix}.` : ""}${scope.path}[${i}]`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
visit(root, "");
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** The local name a `!ref` source names, or undefined for a cross-module one. */
|
|
164
|
+
function localName(source) {
|
|
165
|
+
const dot = source.indexOf(".");
|
|
166
|
+
if (dot === -1)
|
|
167
|
+
return source;
|
|
168
|
+
return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
|
|
169
|
+
}
|
|
170
|
+
/** The local name a resolved `{kind, name, alias?}` reference names. */
|
|
171
|
+
function resolvedLocalName(value) {
|
|
172
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
173
|
+
return undefined;
|
|
174
|
+
const ref = value;
|
|
175
|
+
if (typeof ref.kind !== "string" || typeof ref.name !== "string")
|
|
176
|
+
return undefined;
|
|
177
|
+
if (typeof ref.alias === "string" && ref.alias !== "Self")
|
|
178
|
+
return undefined;
|
|
179
|
+
return ref.name;
|
|
180
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The strict half of `source-entries.ts`: what `telo check` reports about a
|
|
5
|
+
* module doc's `sources:` block.
|
|
6
|
+
*
|
|
7
|
+
* Every rule the block alone decides is a reader problem, reported here under
|
|
8
|
+
* its own code. This adds the rules that need the rest of the module or that
|
|
9
|
+
* only publish enforces: an entry must stage a file the manifest names, a link
|
|
10
|
+
* ships in its target's layer, and every file entry and every build carries its
|
|
11
|
+
* pin. Shape errors are left to the owner doc's JSON Schema.
|
|
12
|
+
*
|
|
13
|
+
* Entry-module-scoped: a dependency's block is not the consumer's to fix.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateSourceEntries(manifests: ResourceManifest[], entryModules?: ReadonlySet<string>): AnalysisDiagnostic[];
|
|
16
|
+
//# sourceMappingURL=validate-source-entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-source-entries.d.ts","sourceRoot":"","sources":["../src/validate-source-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACjC,kBAAkB,EAAE,CA0FtB"}
|