@telorun/analyzer 0.62.0 → 0.63.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/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +6 -0
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +343 -13
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +2 -7
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/import-resolution-diagnostics.d.ts +9 -2
- package/dist/import-resolution-diagnostics.d.ts.map +1 -1
- package/dist/import-resolution-diagnostics.js +26 -7
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/invocation-contract.d.ts +6 -1
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +27 -4
- package/dist/loaded-types.d.ts +12 -3
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +0 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +62 -41
- package/dist/manifest-schemas.d.ts +699 -0
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +306 -14
- package/dist/module-compatibility.d.ts +40 -0
- package/dist/module-compatibility.d.ts.map +1 -0
- package/dist/module-compatibility.js +52 -0
- package/dist/ref-slot-reading.d.ts +30 -0
- package/dist/ref-slot-reading.d.ts.map +1 -0
- package/dist/ref-slot-reading.js +139 -0
- package/dist/referrer-rule.d.ts +67 -0
- package/dist/referrer-rule.d.ts.map +1 -0
- package/dist/referrer-rule.js +96 -0
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +1 -1
- package/dist/release/payload-digest.d.ts +7 -3
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +7 -3
- package/dist/release/version-stamp.d.ts +16 -0
- package/dist/release/version-stamp.d.ts.map +1 -1
- package/dist/release/version-stamp.js +52 -0
- package/dist/release/workspace-config.d.ts +10 -6
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +10 -6
- package/dist/resolve-schema-ref-kinds.d.ts +4 -3
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +16 -0
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +4 -3
- package/dist/resolve-zone-containment.d.ts +99 -0
- package/dist/resolve-zone-containment.d.ts.map +1 -0
- package/dist/resolve-zone-containment.js +212 -0
- package/dist/resolve-zone-requirements.d.ts +5 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +48 -7
- package/dist/resource-rule.d.ts +106 -0
- package/dist/resource-rule.d.ts.map +1 -0
- package/dist/resource-rule.js +236 -0
- package/dist/rule-condition.d.ts +50 -0
- package/dist/rule-condition.d.ts.map +1 -0
- package/dist/rule-condition.js +108 -0
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +20 -0
- package/dist/schema-projection.d.ts +171 -0
- package/dist/schema-projection.d.ts.map +1 -0
- package/dist/schema-projection.js +317 -0
- package/dist/step-slot.d.ts +39 -0
- package/dist/step-slot.d.ts.map +1 -0
- package/dist/step-slot.js +49 -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 +17 -3
- package/dist/validate-durable-regions.d.ts +15 -0
- package/dist/validate-durable-regions.d.ts.map +1 -0
- package/dist/validate-durable-regions.js +336 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +44 -2
- package/dist/validate-referrer-rules.d.ts +120 -0
- package/dist/validate-referrer-rules.d.ts.map +1 -0
- package/dist/validate-referrer-rules.js +255 -0
- package/dist/validate-resource-rules.d.ts +114 -0
- package/dist/validate-resource-rules.d.ts.map +1 -0
- package/dist/validate-resource-rules.js +283 -0
- package/dist/validate-schema-projection.d.ts +32 -0
- package/dist/validate-schema-projection.d.ts.map +1 -0
- package/dist/validate-schema-projection.js +110 -0
- package/dist/validate-step-inputs.d.ts +2 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +5 -3
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +11 -5
- package/dist/validate-zone-slots.d.ts +2 -2
- package/dist/validate-zone-slots.d.ts.map +1 -1
- package/dist/validate-zone-slots.js +202 -6
- package/dist/validate-zone-violations.d.ts +12 -0
- package/dist/validate-zone-violations.d.ts.map +1 -0
- package/dist/validate-zone-violations.js +169 -0
- package/dist/value-type-keyword.d.ts +1 -1
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +4 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -1
- package/dist/zone-slot.js +86 -5
- package/package.json +2 -2
- package/src/analysis-registry.ts +6 -0
- package/src/analyzer.ts +395 -14
- package/src/call-graph.ts +2 -8
- package/src/flatten-for-analyzer.ts +1 -1
- package/src/import-resolution-diagnostics.ts +25 -7
- package/src/index.ts +73 -0
- package/src/invocation-contract.ts +45 -2
- package/src/loaded-types.ts +12 -3
- package/src/manifest-loader.ts +87 -55
- package/src/manifest-schemas.ts +328 -14
- package/src/module-compatibility.ts +72 -0
- package/src/ref-slot-reading.ts +156 -0
- package/src/referrer-rule.ts +116 -0
- package/src/release/index.ts +1 -0
- package/src/release/payload-digest.ts +7 -3
- package/src/release/version-stamp.ts +58 -0
- package/src/release/workspace-config.ts +10 -6
- package/src/resolve-schema-ref-kinds.ts +28 -4
- package/src/resolve-throws-union.ts +4 -3
- package/src/resolve-zone-containment.ts +329 -0
- package/src/resolve-zone-requirements.ts +67 -7
- package/src/resource-rule.ts +252 -0
- package/src/rule-condition.ts +126 -0
- package/src/schema-keywords.ts +24 -0
- package/src/schema-projection.ts +403 -0
- package/src/step-slot.ts +64 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +17 -3
- package/src/validate-durable-regions.ts +401 -0
- package/src/validate-references.ts +57 -2
- package/src/validate-referrer-rules.ts +373 -0
- package/src/validate-resource-rules.ts +401 -0
- package/src/validate-schema-projection.ts +184 -0
- package/src/validate-step-inputs.ts +5 -3
- package/src/validate-throws-coverage.ts +12 -5
- package/src/validate-zone-slots.ts +192 -8
- package/src/validate-zone-violations.ts +190 -0
- package/src/value-type-keyword.ts +4 -0
- package/src/zone-slot.ts +114 -5
package/src/analyzer.ts
CHANGED
|
@@ -51,8 +51,39 @@ import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
|
|
|
51
51
|
import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
|
|
52
52
|
import { resolveSchemaRefKinds, type RefConstraintIssue } from "./resolve-schema-ref-kinds.js";
|
|
53
53
|
import { runZoneAnalysis, type ZoneExportCache } from "./resolve-zone-requirements.js";
|
|
54
|
+
import { validateDurableRegions } from "./validate-durable-regions.js";
|
|
55
|
+
import { validateZoneViolations } from "./validate-zone-violations.js";
|
|
54
56
|
import { MANIFEST_SCHEMA_URI, ManifestRootSchema } from "./manifest-schemas.js";
|
|
55
57
|
import { validateZoneSlotDeclarations, type ZoneSlotIssue } from "./validate-zone-slots.js";
|
|
58
|
+
import {
|
|
59
|
+
validateSchemaProjection,
|
|
60
|
+
type SchemaProjectionIssue,
|
|
61
|
+
} from "./validate-schema-projection.js";
|
|
62
|
+
import {
|
|
63
|
+
evaluateResourceRules,
|
|
64
|
+
reportResourceRules,
|
|
65
|
+
reportUnexercisedRule,
|
|
66
|
+
ruleExercised,
|
|
67
|
+
validateResourceRuleDeclarations,
|
|
68
|
+
type ResourceRuleDiagnostic,
|
|
69
|
+
type ResourceRuleIssue,
|
|
70
|
+
} from "./validate-resource-rules.js";
|
|
71
|
+
import { readResourceRules, type ResourceRule } from "./resource-rule.js";
|
|
72
|
+
import { readReferrerRules, type ReferrerRule } from "./referrer-rule.js";
|
|
73
|
+
import {
|
|
74
|
+
evaluateReferrerRules,
|
|
75
|
+
referrerRuleExercised,
|
|
76
|
+
reportReferrerRules,
|
|
77
|
+
reportUnexercisedReferrerRule,
|
|
78
|
+
validateReferrerRuleDeclarations,
|
|
79
|
+
type Referrer,
|
|
80
|
+
type ReferrerRuleDiagnostic,
|
|
81
|
+
type ReferrerRuleIssue,
|
|
82
|
+
} from "./validate-referrer-rules.js";
|
|
83
|
+
import {
|
|
84
|
+
describeProjectionFailure,
|
|
85
|
+
type ProjectionFailure,
|
|
86
|
+
} from "./schema-projection.js";
|
|
56
87
|
import {
|
|
57
88
|
validateDynamicSelectors,
|
|
58
89
|
validateRefSlotDeclarations,
|
|
@@ -111,6 +142,7 @@ import { validateReferences } from "./validate-references.js";
|
|
|
111
142
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
112
143
|
import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
|
|
113
144
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
145
|
+
import { readStepSlot } from "./step-slot.js";
|
|
114
146
|
|
|
115
147
|
const SELF_PREFIX = "Self.";
|
|
116
148
|
|
|
@@ -373,7 +405,7 @@ export function gatherPropertySchemas(
|
|
|
373
405
|
}
|
|
374
406
|
|
|
375
407
|
/**
|
|
376
|
-
* Generic, role-driven walk over
|
|
408
|
+
* Generic, role-driven walk over a step array. Calls
|
|
377
409
|
* `visit(step, stepPath)` for every step — top-level and nested through the
|
|
378
410
|
* `x-telo-topology-role` forms (`branch`, `branch-list`, `case-map`). This is
|
|
379
411
|
* the single definition of how steps nest, shared by `buildStepContextSchema`
|
|
@@ -440,7 +472,7 @@ export function walkStepArray(
|
|
|
440
472
|
}
|
|
441
473
|
|
|
442
474
|
/**
|
|
443
|
-
* Build a `steps` context schema
|
|
475
|
+
* Build a `steps` context schema for a kind's step body.
|
|
444
476
|
* Walks each step in the manifest array, resolves the invoked resource's output
|
|
445
477
|
* contract, and builds `steps.<name>.result` context entries.
|
|
446
478
|
*
|
|
@@ -461,7 +493,7 @@ export function walkStepArray(
|
|
|
461
493
|
* sub-properties (e.g. elseif: [{ if, then }]).
|
|
462
494
|
* - `case-map` — value is an object whose values are step arrays (e.g. cases).
|
|
463
495
|
* No specific Run.Sequence field name is hardcoded; any kind that uses
|
|
464
|
-
*
|
|
496
|
+
* a step body and tags its branch fields with these roles works.
|
|
465
497
|
*/
|
|
466
498
|
function buildStepContextSchema(
|
|
467
499
|
manifest: Record<string, any>,
|
|
@@ -478,7 +510,7 @@ function buildStepContextSchema(
|
|
|
478
510
|
const readingModule = (manifest.metadata as { module?: string } | undefined)?.module;
|
|
479
511
|
|
|
480
512
|
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
481
|
-
const stepCtx = fieldSchema
|
|
513
|
+
const stepCtx = readStepSlot(fieldSchema);
|
|
482
514
|
if (!stepCtx) continue;
|
|
483
515
|
|
|
484
516
|
const invokeField = stepCtx.invoke;
|
|
@@ -647,7 +679,7 @@ const NO_ENTRY_POINT_CAPABILITIES = new Set([
|
|
|
647
679
|
]);
|
|
648
680
|
|
|
649
681
|
/**
|
|
650
|
-
* Validate
|
|
682
|
+
* Validate step `invoke` references (e.g. `Run.Sequence`
|
|
651
683
|
* steps).
|
|
652
684
|
*
|
|
653
685
|
* The reference field map deliberately does NOT descend into step `invoke`
|
|
@@ -666,7 +698,7 @@ const NO_ENTRY_POINT_CAPABILITIES = new Set([
|
|
|
666
698
|
* contract has no entry point → `REFERENCE_KIND_MISMATCH` (runtime
|
|
667
699
|
* `ERR_RESOURCE_NOT_INVOKABLE`).
|
|
668
700
|
*
|
|
669
|
-
* Generic and topology-driven — it walks steps via the same
|
|
701
|
+
* Generic and topology-driven — it walks steps via the same step-slot
|
|
670
702
|
* / `x-telo-topology-role` annotations `buildStepContextSchema` uses (through the
|
|
671
703
|
* shared `walkStepArray`), so nested branches (then/else/do/catch/cases) are
|
|
672
704
|
* covered and no `Run.Sequence` field name is hardcoded. The cross-module
|
|
@@ -795,8 +827,7 @@ function validateStepInvokeReferences(
|
|
|
795
827
|
for (const [fieldName, fieldSchema] of Object.entries(
|
|
796
828
|
defSchema.properties as Record<string, any>,
|
|
797
829
|
)) {
|
|
798
|
-
const
|
|
799
|
-
const invokeField = stepCtx?.invoke;
|
|
830
|
+
const invokeField = readStepSlot(fieldSchema)?.invoke;
|
|
800
831
|
if (!invokeField) continue;
|
|
801
832
|
const steps = m[fieldName];
|
|
802
833
|
if (!Array.isArray(steps)) continue;
|
|
@@ -1253,14 +1284,18 @@ export class StaticAnalyzer {
|
|
|
1253
1284
|
}
|
|
1254
1285
|
seen.add(alias);
|
|
1255
1286
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1287
|
+
// An import whose target identity was never established registers NO
|
|
1288
|
+
// alias. The name is never guessed from the source string: a guess that
|
|
1289
|
+
// is usually right is what turned "this import did not resolve" into an
|
|
1290
|
+
// assertion that a published dependency was malformed, naming a module
|
|
1291
|
+
// no registry could ever hold. With no alias, every use degrades to
|
|
1292
|
+
// "cannot resolve alias '<X>'" — which points at the import the author
|
|
1293
|
+
// has to fix, and which the loader has already reported on its own line.
|
|
1294
|
+
if (alias && source && resolvedModuleName) {
|
|
1295
|
+
const targetModule = resolvedModuleName;
|
|
1259
1296
|
// Module identity is registered globally so x-telo-ref resolution sees
|
|
1260
1297
|
// transitively-imported modules regardless of which scope brought them in.
|
|
1261
|
-
|
|
1262
|
-
defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
|
|
1263
|
-
}
|
|
1298
|
+
defs.registerModuleIdentity(resolvedNamespace ?? null, resolvedModuleName);
|
|
1264
1299
|
// `metadata.reExportedKinds` (stamped by flattenForAnalyzer / the editor projection)
|
|
1265
1300
|
// maps an exported suffix to the true owning module's canonical kind for kinds this
|
|
1266
1301
|
// import transitively re-exports (`exports.kinds: [Alias.Kind]`).
|
|
@@ -1318,6 +1353,61 @@ export class StaticAnalyzer {
|
|
|
1318
1353
|
const refConstraintIssues: RefConstraintIssue[] = [];
|
|
1319
1354
|
const refSlotIssues: RefSlotIssue[] = [];
|
|
1320
1355
|
const zoneSlotIssues: ZoneSlotIssue[] = [];
|
|
1356
|
+
// One place a rule report becomes a diagnostic. The pass decided WHAT and
|
|
1357
|
+
// WHERE; this only carries it across to the diagnostic shape.
|
|
1358
|
+
const SEVERITY = {
|
|
1359
|
+
error: DiagnosticSeverity.Error,
|
|
1360
|
+
warning: DiagnosticSeverity.Warning,
|
|
1361
|
+
information: DiagnosticSeverity.Information,
|
|
1362
|
+
} as const;
|
|
1363
|
+
const resourceRuleDiagnostic = (report: ResourceRuleDiagnostic): AnalysisDiagnostic => ({
|
|
1364
|
+
severity: SEVERITY[report.severity],
|
|
1365
|
+
code: report.code,
|
|
1366
|
+
source: SOURCE,
|
|
1367
|
+
message: report.message,
|
|
1368
|
+
data: {
|
|
1369
|
+
resource: {
|
|
1370
|
+
kind: report.manifest.kind,
|
|
1371
|
+
name: report.manifest.metadata?.name as string,
|
|
1372
|
+
},
|
|
1373
|
+
filePath: (report.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1374
|
+
path: report.path,
|
|
1375
|
+
rule: report.rule,
|
|
1376
|
+
},
|
|
1377
|
+
});
|
|
1378
|
+
const referrerRuleDiagnostic = (report: ReferrerRuleDiagnostic): AnalysisDiagnostic => ({
|
|
1379
|
+
severity: SEVERITY[report.severity],
|
|
1380
|
+
code: report.code,
|
|
1381
|
+
source: SOURCE,
|
|
1382
|
+
message: report.message,
|
|
1383
|
+
data: {
|
|
1384
|
+
resource: {
|
|
1385
|
+
kind: report.manifest.kind,
|
|
1386
|
+
name: report.manifest.metadata?.name as string,
|
|
1387
|
+
},
|
|
1388
|
+
filePath: (report.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1389
|
+
path: report.path,
|
|
1390
|
+
rule: report.rule,
|
|
1391
|
+
},
|
|
1392
|
+
});
|
|
1393
|
+
const projectionIssues: SchemaProjectionIssue[] = [];
|
|
1394
|
+
const resourceRuleIssues: ResourceRuleIssue[] = [];
|
|
1395
|
+
const referrerRuleIssues: ReferrerRuleIssue[] = [];
|
|
1396
|
+
// A rule that never had anything to iterate is never proven — the second way
|
|
1397
|
+
// coverage varies invisibly, beside the dynamic-leaf skip. Tracked across the
|
|
1398
|
+
// whole run and reported once, since "empty on every resource" is not a fact
|
|
1399
|
+
// any single resource can establish.
|
|
1400
|
+
const ruleExercise = new Map<
|
|
1401
|
+
string,
|
|
1402
|
+
{ manifest: ResourceManifest; rule: ResourceRule; exercised: boolean; seen: boolean }
|
|
1403
|
+
>();
|
|
1404
|
+
// Same for a referrer rule, where "never exercised" means nothing the
|
|
1405
|
+
// `referrer:` filter matches ever referenced a resource of the kind — which
|
|
1406
|
+
// is exactly what a typo in that filter looks like from the outside.
|
|
1407
|
+
const referrerRuleExercise = new Map<
|
|
1408
|
+
string,
|
|
1409
|
+
{ manifest: ResourceManifest; rule: ReferrerRule; exercised: boolean; seen: boolean }
|
|
1410
|
+
>();
|
|
1321
1411
|
// `x-telo-type` is checked on EVERY manifest, not only on definition docs: a
|
|
1322
1412
|
// schema fragment is written wherever a kind declares a schema-valued field,
|
|
1323
1413
|
// so an inline `inputType:` on an ordinary resource carries one just as a
|
|
@@ -1350,6 +1440,35 @@ export class StaticAnalyzer {
|
|
|
1350
1440
|
refConstraintIssues.push(...issues);
|
|
1351
1441
|
refSlotIssues.push(...validateRefSlotDeclarations(m as unknown as ResourceManifest));
|
|
1352
1442
|
zoneSlotIssues.push(...validateZoneSlotDeclarations(m as unknown as ResourceManifest));
|
|
1443
|
+
projectionIssues.push(...validateSchemaProjection(m as unknown as ResourceManifest));
|
|
1444
|
+
// Checked against the MERGED schema, so an `in:` pointer naming an
|
|
1445
|
+
// inherited field resolves — which is what lets a rule shared by every
|
|
1446
|
+
// backend be declared once on the abstract they extend.
|
|
1447
|
+
resourceRuleIssues.push(
|
|
1448
|
+
...validateResourceRuleDeclarations(
|
|
1449
|
+
m as unknown as ResourceManifest,
|
|
1450
|
+
effectiveAuthorSchema(m as any, (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k)),
|
|
1451
|
+
),
|
|
1452
|
+
);
|
|
1453
|
+
referrerRuleIssues.push(
|
|
1454
|
+
...validateReferrerRuleDeclarations(m as unknown as ResourceManifest),
|
|
1455
|
+
);
|
|
1456
|
+
for (const rule of readReferrerRules((m as Record<string, unknown>).schema)) {
|
|
1457
|
+
referrerRuleExercise.set(`${m.metadata?.module}.${m.metadata?.name}#${rule.code}`, {
|
|
1458
|
+
manifest: m as unknown as ResourceManifest,
|
|
1459
|
+
rule,
|
|
1460
|
+
exercised: false,
|
|
1461
|
+
seen: false,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
for (const rule of readResourceRules((m as Record<string, unknown>).schema)) {
|
|
1465
|
+
ruleExercise.set(`${m.metadata?.module}.${m.metadata?.name}#${rule.code}`, {
|
|
1466
|
+
manifest: m as unknown as ResourceManifest,
|
|
1467
|
+
rule,
|
|
1468
|
+
exercised: false,
|
|
1469
|
+
seen: false,
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1353
1472
|
}
|
|
1354
1473
|
const resolvedCapability = def.capability
|
|
1355
1474
|
? (scopeResolver.resolveKind(def.capability) ?? def.capability)
|
|
@@ -1407,6 +1526,25 @@ export class StaticAnalyzer {
|
|
|
1407
1526
|
};
|
|
1408
1527
|
const filePath = (issue.manifest.metadata as { source?: string } | undefined)?.source;
|
|
1409
1528
|
const data = { resource, filePath, path: issue.path };
|
|
1529
|
+
if (issue.annotation === "referrer") {
|
|
1530
|
+
// Mirrors ZONE_PROVIDER_UNRESOLVED: a filter naming no kind matches no
|
|
1531
|
+
// referrer, so the rule would pass on every manifest while checking
|
|
1532
|
+
// nothing — reported at the kind that wrote it, since the consumer
|
|
1533
|
+
// cannot see that the check is inert.
|
|
1534
|
+
diagnostics.push({
|
|
1535
|
+
severity: DiagnosticSeverity.Error,
|
|
1536
|
+
code: "REFERRER_RULE_INVALID",
|
|
1537
|
+
source: SOURCE,
|
|
1538
|
+
message:
|
|
1539
|
+
`x-telo-referrer-rules 'referrer: ${issue.ref}' at '${issue.path}' names no kind. ` +
|
|
1540
|
+
`The prefix must be an import alias declared in this file's 'imports:' map, ` +
|
|
1541
|
+
`'Self' for a kind in this library, or 'Telo' for a built-in. A filter that ` +
|
|
1542
|
+
`matches nothing leaves the rule inert. Known aliases: ` +
|
|
1543
|
+
`${issue.knownAliases?.join(", ") || "(none)"}.`,
|
|
1544
|
+
data,
|
|
1545
|
+
});
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1410
1548
|
if (issue.annotation === "zone") {
|
|
1411
1549
|
// Mirrors X_TELO_REF_UNRESOLVED: an unresolvable provider kind would
|
|
1412
1550
|
// leave the requirement silently unenforced — no provider ever
|
|
@@ -1493,6 +1631,57 @@ export class StaticAnalyzer {
|
|
|
1493
1631
|
// OPPOSITE directions — a dropped requirement is silently unenforced, a
|
|
1494
1632
|
// dropped provision invents failures — so neither can be left to
|
|
1495
1633
|
// leniency.
|
|
1634
|
+
// A projection nothing can read does not fail — it stops typing the
|
|
1635
|
+
// consumers counting on it, which puts a misspelled field back where the
|
|
1636
|
+
// projection exists to catch it earlier.
|
|
1637
|
+
for (const issue of resourceRuleIssues) {
|
|
1638
|
+
diagnostics.push({
|
|
1639
|
+
severity: DiagnosticSeverity.Error,
|
|
1640
|
+
code: issue.code,
|
|
1641
|
+
source: SOURCE,
|
|
1642
|
+
message: issue.message,
|
|
1643
|
+
data: {
|
|
1644
|
+
resource: {
|
|
1645
|
+
kind: issue.manifest.kind,
|
|
1646
|
+
name: issue.manifest.metadata?.name as string,
|
|
1647
|
+
},
|
|
1648
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1649
|
+
path: issue.path,
|
|
1650
|
+
},
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
for (const issue of referrerRuleIssues) {
|
|
1654
|
+
diagnostics.push({
|
|
1655
|
+
severity: DiagnosticSeverity.Error,
|
|
1656
|
+
code: issue.code,
|
|
1657
|
+
source: SOURCE,
|
|
1658
|
+
message: issue.message,
|
|
1659
|
+
data: {
|
|
1660
|
+
resource: {
|
|
1661
|
+
kind: issue.manifest.kind,
|
|
1662
|
+
name: issue.manifest.metadata?.name as string,
|
|
1663
|
+
},
|
|
1664
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1665
|
+
path: issue.path,
|
|
1666
|
+
},
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
for (const issue of projectionIssues) {
|
|
1670
|
+
diagnostics.push({
|
|
1671
|
+
severity: DiagnosticSeverity.Error,
|
|
1672
|
+
code: issue.code,
|
|
1673
|
+
source: SOURCE,
|
|
1674
|
+
message: issue.message,
|
|
1675
|
+
data: {
|
|
1676
|
+
resource: {
|
|
1677
|
+
kind: issue.manifest.kind,
|
|
1678
|
+
name: issue.manifest.metadata?.name as string,
|
|
1679
|
+
},
|
|
1680
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1681
|
+
path: issue.path,
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1496
1685
|
for (const issue of zoneSlotIssues) {
|
|
1497
1686
|
diagnostics.push({
|
|
1498
1687
|
severity: DiagnosticSeverity.Error,
|
|
@@ -1532,6 +1721,57 @@ export class StaticAnalyzer {
|
|
|
1532
1721
|
aliasesByModule,
|
|
1533
1722
|
}));
|
|
1534
1723
|
|
|
1724
|
+
/**
|
|
1725
|
+
* Whether a referrer of `kind` satisfies a referrer rule's `referrer:`
|
|
1726
|
+
* filter, which is canonical by the time it gets here (`resolveSchemaRefKinds`
|
|
1727
|
+
* rewrote it in the DECLARING module's scope). The referring manifest's own
|
|
1728
|
+
* `kind:` is not — it is whatever alias its author imported the kind under —
|
|
1729
|
+
* so it is resolved the same way every other kind comparison in this pass
|
|
1730
|
+
* resolves one. Liskov-substitutable, matching `checkKind`: a child of the
|
|
1731
|
+
* named kind is one.
|
|
1732
|
+
*/
|
|
1733
|
+
const kindMatches = (filter: string, kind: string): boolean => {
|
|
1734
|
+
const resolved = aliases.resolveKind(kind) ?? kind;
|
|
1735
|
+
if (resolved === filter) return true;
|
|
1736
|
+
return defs
|
|
1737
|
+
.getByExtends(filter)
|
|
1738
|
+
.some((d) => `${d.metadata.module}.${d.metadata.name}` === resolved);
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* The resources that reach `manifest`, with the slot each one reaches it
|
|
1743
|
+
* through. A step's edge is attributed to the resource whose body declares
|
|
1744
|
+
* it: a step is not a manifest, and the requirement is about the resource
|
|
1745
|
+
* that has to declare something.
|
|
1746
|
+
*
|
|
1747
|
+
* Deduplication is the evaluation's, not this function's — a referrer
|
|
1748
|
+
* reaching one resource through two slots is two sites, and which one anchors
|
|
1749
|
+
* the diagnostic is a reporting decision.
|
|
1750
|
+
*/
|
|
1751
|
+
const referrersOf = (
|
|
1752
|
+
manifest: ResourceManifest,
|
|
1753
|
+
graph: ReturnType<typeof buildCallGraph>,
|
|
1754
|
+
): Referrer[] => {
|
|
1755
|
+
const name = manifest.metadata?.name as string | undefined;
|
|
1756
|
+
if (!name) return [];
|
|
1757
|
+
const node = graph.resource(manifest.kind, name) ?? graph.resourceByName(name);
|
|
1758
|
+
if (!node) return [];
|
|
1759
|
+
const out: Referrer[] = [];
|
|
1760
|
+
for (const edge of graph.edgesTo(node.id)) {
|
|
1761
|
+
const from = graph.nodes.get(edge.from);
|
|
1762
|
+
if (!from) continue;
|
|
1763
|
+
const owner = from.type === "step" ? graph.nodes.get(from.owner) : from;
|
|
1764
|
+
if (!owner || owner.type !== "resource") continue;
|
|
1765
|
+
out.push({
|
|
1766
|
+
manifest: owner.manifest,
|
|
1767
|
+
kind: owner.kind,
|
|
1768
|
+
name: owner.name,
|
|
1769
|
+
path: edge.path,
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
return out;
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1535
1775
|
// A `use` case map's selector written in CEL is a hard diagnostic — a call
|
|
1536
1776
|
// graph known only at runtime is not statically analyzable, and no fallback
|
|
1537
1777
|
// is conservative for every consumer. Scoped to the entry's own modules:
|
|
@@ -1567,6 +1807,32 @@ export class StaticAnalyzer {
|
|
|
1567
1807
|
cache: zoneExportCache,
|
|
1568
1808
|
}),
|
|
1569
1809
|
);
|
|
1810
|
+
|
|
1811
|
+
// Durable regions — the SAME graph again, walked DOWNWARD this time.
|
|
1812
|
+
// Every rule here keys off a zone attribute rather than off any kind, so
|
|
1813
|
+
// a backend that ships its own workflow kind is covered without the
|
|
1814
|
+
// analyzer knowing it exists: going native costs a module, not a change
|
|
1815
|
+
// here.
|
|
1816
|
+
const resolveRegionDef = (kind: string, module?: string) => {
|
|
1817
|
+
const scope = (module ? aliasesByModule.get(module) : undefined) ?? aliases;
|
|
1818
|
+
const canonical = scope.resolveKind(kind);
|
|
1819
|
+
return defs.resolve(kind) ?? (canonical ? defs.resolve(canonical) : undefined);
|
|
1820
|
+
};
|
|
1821
|
+
diagnostics.push(
|
|
1822
|
+
...validateDurableRegions({
|
|
1823
|
+
graph: getCallGraph(),
|
|
1824
|
+
resolveDef: resolveRegionDef,
|
|
1825
|
+
reportModules: rootModules,
|
|
1826
|
+
}),
|
|
1827
|
+
// The same walk once more, over EVERY attribute rather than the two
|
|
1828
|
+
// durability names — a region must not contain a resource that declares
|
|
1829
|
+
// it cannot honour what the region promises.
|
|
1830
|
+
...validateZoneViolations({
|
|
1831
|
+
graph: getCallGraph(),
|
|
1832
|
+
resolveDef: resolveRegionDef,
|
|
1833
|
+
reportModules: rootModules,
|
|
1834
|
+
}),
|
|
1835
|
+
);
|
|
1570
1836
|
}
|
|
1571
1837
|
|
|
1572
1838
|
// Phase 2.6: register each named `Telo.Type` resource's schema under its
|
|
@@ -1963,6 +2229,69 @@ export class StaticAnalyzer {
|
|
|
1963
2229
|
}
|
|
1964
2230
|
}
|
|
1965
2231
|
|
|
2232
|
+
// Resource rules — relationships between this resource's own fields that
|
|
2233
|
+
// JSON Schema cannot state, declared by the kind as CEL over `self` and
|
|
2234
|
+
// `this`. Read off the AUTHOR-FACING schema, so an `extends` child without
|
|
2235
|
+
// `base:` inherits its parent's rules and one that declares its own
|
|
2236
|
+
// replaces them, exactly as the rest of the config contract merges.
|
|
2237
|
+
// The finding→diagnostic mapping lives with the finding vocabulary in
|
|
2238
|
+
// `validate-resource-rules.ts`, the shape every neighbouring pass uses:
|
|
2239
|
+
// issues out, one emit here.
|
|
2240
|
+
const ruleDeclarer = (definition.metadata as { module?: string } | undefined)?.module;
|
|
2241
|
+
for (const report of reportResourceRules(
|
|
2242
|
+
m as unknown as ResourceManifest,
|
|
2243
|
+
definition as unknown as ResourceManifest,
|
|
2244
|
+
evaluateResourceRules(m as unknown as ResourceManifest, authorSchema),
|
|
2245
|
+
!ruleDeclarer || rootModules.has(ruleDeclarer),
|
|
2246
|
+
)) {
|
|
2247
|
+
diagnostics.push(resourceRuleDiagnostic(report));
|
|
2248
|
+
}
|
|
2249
|
+
for (const rule of readResourceRules(authorSchema)) {
|
|
2250
|
+
const key = `${definition.metadata?.module}.${definition.metadata?.name}#${rule.code}`;
|
|
2251
|
+
const tracked = ruleExercise.get(key);
|
|
2252
|
+
if (!tracked) continue;
|
|
2253
|
+
tracked.seen = true;
|
|
2254
|
+
if (ruleExercised(m as unknown as ResourceManifest, rule)) tracked.exercised = true;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// Referrer rules — what must be true of whoever REFERENCES this resource,
|
|
2258
|
+
// declared by the kind that has the requirement rather than by the kind
|
|
2259
|
+
// that must satisfy it. The subject is chosen by the EDGE, so no kind
|
|
2260
|
+
// literal appears on the referring side, where the spelling would be the
|
|
2261
|
+
// consumer's import alias rather than anything the rule's author controls.
|
|
2262
|
+
// A consumer of the shared call graph, never a second traversal.
|
|
2263
|
+
const referrerRules = readReferrerRules(authorSchema);
|
|
2264
|
+
if (referrerRules.length > 0) {
|
|
2265
|
+
const referrers = referrersOf(m as unknown as ResourceManifest, getCallGraph());
|
|
2266
|
+
for (const report of reportReferrerRules(
|
|
2267
|
+
m as unknown as ResourceManifest,
|
|
2268
|
+
definition as unknown as ResourceManifest,
|
|
2269
|
+
evaluateReferrerRules(
|
|
2270
|
+
m as unknown as ResourceManifest,
|
|
2271
|
+
authorSchema,
|
|
2272
|
+
referrers,
|
|
2273
|
+
kindMatches,
|
|
2274
|
+
),
|
|
2275
|
+
!ruleDeclarer || rootModules.has(ruleDeclarer),
|
|
2276
|
+
)) {
|
|
2277
|
+
// A VIOLATION is the referrer's data, so it is reported only when that
|
|
2278
|
+
// manifest is the entry's own — the same direction a resource-rule
|
|
2279
|
+
// violation takes, one hop further out.
|
|
2280
|
+
const owner = (report.manifest.metadata as { module?: string } | undefined)?.module;
|
|
2281
|
+
if (report.code === "REFERRER_RULE_VIOLATED" && owner && !rootModules.has(owner)) {
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
diagnostics.push(referrerRuleDiagnostic(report));
|
|
2285
|
+
}
|
|
2286
|
+
for (const rule of referrerRules) {
|
|
2287
|
+
const key = `${definition.metadata?.module}.${definition.metadata?.name}#${rule.code}`;
|
|
2288
|
+
const tracked = referrerRuleExercise.get(key);
|
|
2289
|
+
if (!tracked) continue;
|
|
2290
|
+
tracked.seen = true;
|
|
2291
|
+
if (referrerRuleExercised(rule, referrers, kindMatches)) tracked.exercised = true;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1966
2295
|
// Validate inline resources nested inside this resource's body (e.g. a
|
|
1967
2296
|
// Run.Sequence step's `invoke: { kind, ...config }`). These sit at
|
|
1968
2297
|
// x-telo-ref slots reached only through local `$ref`s, which the
|
|
@@ -1998,6 +2327,24 @@ export class StaticAnalyzer {
|
|
|
1998
2327
|
// (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
|
|
1999
2328
|
}
|
|
2000
2329
|
|
|
2330
|
+
// A rule whose collection was empty on every resource of its kind is a rule
|
|
2331
|
+
// nothing proved. `check()` types `self` only shallowly (cel-js takes a flat
|
|
2332
|
+
// field map, so `columns` is `map`), which means a typo below the first level
|
|
2333
|
+
// survives declaration validation and is caught only by evaluation — so a
|
|
2334
|
+
// rule that never evaluated has been verified by nothing at all. Reported
|
|
2335
|
+
// only when the kind HAS resources here: a kind nobody instantiated in this
|
|
2336
|
+
// workspace says nothing about the rule.
|
|
2337
|
+
for (const tracked of ruleExercise.values()) {
|
|
2338
|
+
if (!tracked.seen || tracked.exercised) continue;
|
|
2339
|
+
diagnostics.push(resourceRuleDiagnostic(reportUnexercisedRule(tracked.manifest, tracked.rule)));
|
|
2340
|
+
}
|
|
2341
|
+
for (const tracked of referrerRuleExercise.values()) {
|
|
2342
|
+
if (!tracked.seen || tracked.exercised) continue;
|
|
2343
|
+
diagnostics.push(
|
|
2344
|
+
referrerRuleDiagnostic(reportUnexercisedReferrerRule(tracked.manifest, tracked.rule)),
|
|
2345
|
+
);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2001
2348
|
// Template-body structural validations: check that template entry-points produce
|
|
2002
2349
|
// values matching the contract of their dispatch target and (for `provide:`)
|
|
2003
2350
|
// the abstract this definition `extends`. CEL fields inside the templated
|
|
@@ -2092,6 +2439,40 @@ export class StaticAnalyzer {
|
|
|
2092
2439
|
}
|
|
2093
2440
|
}
|
|
2094
2441
|
|
|
2442
|
+
// A consumer's slot typed from a referenced declaration
|
|
2443
|
+
// (`x-telo-schema-projection-from`) fails in the direction that is hardest
|
|
2444
|
+
// to notice: the contract silently reopens, so a misspelled field passes
|
|
2445
|
+
// `telo check` exactly as it did before the projection existed. That is the
|
|
2446
|
+
// failure the projection exists to move earlier, so it is reported here
|
|
2447
|
+
// rather than left to degrade. Entry-module-scoped, like
|
|
2448
|
+
// `X_TELO_REF_UNRESOLVED` — a published dependency's slot is not the
|
|
2449
|
+
// consumer's to fix.
|
|
2450
|
+
for (const m of allManifests) {
|
|
2451
|
+
const md = m as Record<string, any>;
|
|
2452
|
+
if (typeof md.kind !== "string" || md.kind.startsWith("Telo.")) continue;
|
|
2453
|
+
const ownModule = (md.metadata as { module?: string } | undefined)?.module;
|
|
2454
|
+
if (ownModule && !rootModules.has(ownModule)) continue;
|
|
2455
|
+
const definition = contractScope.resolveIn(md.kind, ownModule);
|
|
2456
|
+
if (!definition) continue;
|
|
2457
|
+
const failures: ProjectionFailure[] = [];
|
|
2458
|
+
for (const direction of ["inputType", "outputType"] as const) {
|
|
2459
|
+
resolveContract(direction, md, definition, contractScope, failures);
|
|
2460
|
+
}
|
|
2461
|
+
for (const failure of failures) {
|
|
2462
|
+
diagnostics.push({
|
|
2463
|
+
severity: DiagnosticSeverity.Error,
|
|
2464
|
+
code: "SCHEMA_PROJECTION_FROM_UNRESOLVED",
|
|
2465
|
+
source: SOURCE,
|
|
2466
|
+
message: `${md.kind}: ${describeProjectionFailure(failure)}`,
|
|
2467
|
+
data: {
|
|
2468
|
+
resource: { kind: md.kind, name: (md.metadata as any)?.name as string },
|
|
2469
|
+
filePath: (md.metadata as { source?: string } | undefined)?.source,
|
|
2470
|
+
path: failure.pointer.replace(/^\//, ""),
|
|
2471
|
+
},
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2095
2476
|
// Validate CEL syntax and context variable access in all manifests. The
|
|
2096
2477
|
// walker discovers every compiled CEL node by scanning the value tree and
|
|
2097
2478
|
// hands back the `x-telo-context` schema matched at the enclosing path; the
|
package/src/call-graph.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
type RefUse,
|
|
56
56
|
type RefUseCases,
|
|
57
57
|
} from "./ref-slot.js";
|
|
58
|
+
import { isStepSlot } from "./step-slot.js";
|
|
58
59
|
import { isRefEntry, resolveFieldEntries, type RefFieldEntry } from "./reference-field-map.js";
|
|
59
60
|
import { DEPENDENCY_GRAPH_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
60
61
|
|
|
@@ -292,12 +293,6 @@ function resolveUseAtSite(
|
|
|
292
293
|
return { use: possibleUses(slot), unresolved: entry.useCases, unresolvedReason };
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
/** Names the step-list annotation on an array property, if any. */
|
|
296
|
-
function stepContextOf(schema: Record<string, any> | undefined): Record<string, any> | undefined {
|
|
297
|
-
const annotation = schema?.["x-telo-step-context"];
|
|
298
|
-
return annotation && typeof annotation === "object" ? annotation : undefined;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
296
|
/** A resolved plain reference value (`{kind, name}`, optionally `alias`) — the
|
|
302
297
|
* shape `resolveRefSentinels` leaves at a ref site. NOT a step: a bare boot
|
|
303
298
|
* target written `!ref X` must not mint a step node. */
|
|
@@ -545,8 +540,7 @@ export function buildCallGraph(
|
|
|
545
540
|
if (!schema) return;
|
|
546
541
|
const collected: StepGraphNode[] = [];
|
|
547
542
|
for (const [key, propSchema] of propertySchemas(schema)) {
|
|
548
|
-
|
|
549
|
-
if (!annotation) continue;
|
|
543
|
+
if (!isStepSlot(propSchema)) continue;
|
|
550
544
|
const value = (node.manifest as Record<string, unknown>)[key];
|
|
551
545
|
if (!Array.isArray(value)) continue;
|
|
552
546
|
walkSteps(value, key, undefined, {
|
|
@@ -204,7 +204,7 @@ export function flattenForAnalyzer(graph: LoadedGraph): ResourceManifest[] {
|
|
|
204
204
|
const alias = m.metadata?.name as string | undefined;
|
|
205
205
|
if (!owner || !alias) continue;
|
|
206
206
|
const edge = graph.importEdges.get(owner)?.get(alias);
|
|
207
|
-
if (!edge
|
|
207
|
+
if (!edge) continue;
|
|
208
208
|
|
|
209
209
|
const newMetadata: Record<string, unknown> = {
|
|
210
210
|
...m.metadata,
|
|
@@ -20,25 +20,43 @@ function isRecognizedSourceShape(source: string): boolean {
|
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** Which of the three failures this is. Kept as one function so the code and
|
|
24
|
+
* the sentence can never disagree about which cause was established. */
|
|
25
|
+
function classify(e: GraphLoadError): "INVALID_IMPORT_TARGET" | "INVALID_IMPORT_SOURCE" | "IMPORT_UNRESOLVED" {
|
|
26
|
+
if (e.reason === "unusable-target") return "INVALID_IMPORT_TARGET";
|
|
27
|
+
return isRecognizedSourceShape(e.source ?? e.url) ? "IMPORT_UNRESOLVED" : "INVALID_IMPORT_SOURCE";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function messageFor(e: GraphLoadError, code: ReturnType<typeof classify>): string {
|
|
24
31
|
const authored = e.source ?? e.url;
|
|
25
32
|
const via = e.alias ? `import '${e.alias}' → '${authored}'` : `'${authored}'`;
|
|
26
|
-
if (
|
|
33
|
+
if (code === "INVALID_IMPORT_SOURCE") {
|
|
27
34
|
return (
|
|
28
35
|
`Cannot resolve ${via}: not a recognized module reference. Expected ` +
|
|
29
36
|
`'namespace/name@version', 'oci://host/repo@tag', 'https://…', or a relative path.`
|
|
30
37
|
);
|
|
31
38
|
}
|
|
39
|
+
// The target WAS obtained, so "cannot resolve" would name the wrong problem —
|
|
40
|
+
// and the wrong person to fix it. The loader's message already says what was
|
|
41
|
+
// fetched and why it is not importable.
|
|
42
|
+
if (code === "INVALID_IMPORT_TARGET") return `Cannot use ${via}: ${e.error.message}`;
|
|
32
43
|
return `Cannot resolve ${via}: ${e.error.message}`;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
/**
|
|
36
|
-
* Convert a graph's import
|
|
37
|
-
*
|
|
47
|
+
* Convert a graph's import failures (`graph.errors`) into structured, coded
|
|
48
|
+
* diagnostics. This is the single source of truth for surfacing a broken
|
|
38
49
|
* import — every host (CLI, VS Code, telo-editor) routes these instead of each
|
|
39
50
|
* re-deriving the channel and drifting (the VS Code extension used to drop it
|
|
40
51
|
* entirely, showing nothing for a broken import).
|
|
41
52
|
*
|
|
53
|
+
* Three codes, because three different people fix them:
|
|
54
|
+
* `INVALID_IMPORT_SOURCE` (the ref is not a module reference at all),
|
|
55
|
+
* `IMPORT_UNRESOLVED` (a well-formed ref that could not be obtained), and
|
|
56
|
+
* `INVALID_IMPORT_TARGET` (obtained, but not an importable library — an
|
|
57
|
+
* application, no library document, or one that names no module). Collapsing
|
|
58
|
+
* the last into the second would tell an author to fix a ref that is correct.
|
|
59
|
+
*
|
|
42
60
|
* The analyzer owns only this raw channel conversion; the *presentation* policy
|
|
43
61
|
* — which analysis cascade to hold back for a compromised file — lives in
|
|
44
62
|
* `@telorun/ide-support`'s `assembleGraphDiagnostics`.
|
|
@@ -51,15 +69,15 @@ function messageFor(e: GraphLoadError, malformed: boolean): string {
|
|
|
51
69
|
export function importResolutionDiagnostics(graph: LoadedGraph): AnalysisDiagnostic[] {
|
|
52
70
|
return graph.errors.map((e) => {
|
|
53
71
|
const filePath = e.fromSource ?? graph.entry.owner.source;
|
|
54
|
-
const
|
|
72
|
+
const code = classify(e);
|
|
55
73
|
const data: { filePath: string; path?: string; sourceLine?: number } = { filePath };
|
|
56
74
|
if (e.alias) data.path = `imports.${e.alias}`;
|
|
57
75
|
if (e.sourceLine !== undefined) data.sourceLine = e.sourceLine;
|
|
58
76
|
return {
|
|
59
77
|
severity: DiagnosticSeverity.Error,
|
|
60
|
-
code
|
|
78
|
+
code,
|
|
61
79
|
source: SOURCE,
|
|
62
|
-
message: messageFor(e,
|
|
80
|
+
message: messageFor(e, code),
|
|
63
81
|
data,
|
|
64
82
|
};
|
|
65
83
|
});
|