@shapeshift-labs/frontier-lang-compiler 0.2.124 → 0.2.126
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 +7 -3
- package/dist/declarations/js-ts-safe-project-merge.d.ts +25 -0
- package/dist/js-ts-safe-merge-analyze.js +11 -0
- package/dist/js-ts-safe-merge-context.js +1 -0
- package/dist/js-ts-safe-merge-ledger-validation.js +12 -0
- package/dist/js-ts-safe-merge-parse-declarations.js +58 -1
- package/dist/js-ts-safe-project-merge-graph-conflicts.js +3 -0
- package/dist/js-ts-safe-project-merge-graph-delta-conflicts.js +9 -2
- package/dist/js-ts-safe-project-merge-graph-limits.js +117 -0
- package/dist/js-ts-safe-project-merge-graph.js +51 -11
- package/dist/js-ts-safe-project-merge.js +10 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -308,9 +308,13 @@ On a local Node v26.1.0 smoke fixture with 10 small JS/TS files and 36 scanned
|
|
|
308
308
|
stage files for the delta case, baseline project merge JSON was 115 KB at a
|
|
309
309
|
21.6 ms median. `includeOutputProjectSymbolGraph` raised the returned JSON to
|
|
310
310
|
17.8 MB at a 303.1 ms median, and `includeProjectGraphDelta` raised it to
|
|
311
|
-
83.0 MB at a 1,466.8 ms median.
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
83.0 MB at a 1,466.8 ms median. Pass `projectGraphLimits` for admission queues:
|
|
312
|
+
`maxFiles`, `maxSourceBytes`, `maxImportEdges`, `maxExportEdges`, and
|
|
313
|
+
`maxSerializedBytes` produce `project-graph-limit-exceeded` conflicts with the
|
|
314
|
+
stage, limit kind, actual value, and configured limit. Limit failures block
|
|
315
|
+
admission and omit oversized project graph artifacts from the returned result.
|
|
316
|
+
Invalid limits such as negative numbers, `NaN`, or infinity fail closed with
|
|
317
|
+
`project-graph-limit-invalid`.
|
|
314
318
|
|
|
315
319
|
High-risk native features also have explicit evidence policies. These policies are advisory in this package: they tell a swarm or admission queue what evidence is missing without silently changing the existing readiness classification.
|
|
316
320
|
|
|
@@ -53,6 +53,22 @@ export type JsTsProjectSafeMergeOutputProjectImports =
|
|
|
53
53
|
|
|
54
54
|
export type JsTsProjectGraphStageName = 'base' | 'worker' | 'head' | 'output' | string;
|
|
55
55
|
|
|
56
|
+
export type JsTsProjectGraphLimitKind =
|
|
57
|
+
| 'source-files'
|
|
58
|
+
| 'source-bytes'
|
|
59
|
+
| 'import-edges'
|
|
60
|
+
| 'export-edges'
|
|
61
|
+
| 'serialized-bytes'
|
|
62
|
+
| string;
|
|
63
|
+
|
|
64
|
+
export interface JsTsProjectGraphLimits {
|
|
65
|
+
readonly maxFiles?: number;
|
|
66
|
+
readonly maxSourceBytes?: number;
|
|
67
|
+
readonly maxImportEdges?: number;
|
|
68
|
+
readonly maxExportEdges?: number;
|
|
69
|
+
readonly maxSerializedBytes?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
export type JsTsProjectSafeMergeProjectGraphImportsByStage = Readonly<{
|
|
57
73
|
base?: JsTsProjectSafeMergeOutputProjectImports;
|
|
58
74
|
worker?: JsTsProjectSafeMergeOutputProjectImports;
|
|
@@ -72,6 +88,7 @@ export interface JsTsProjectSafeMergeInput {
|
|
|
72
88
|
readonly allowFileDeletes?: boolean;
|
|
73
89
|
readonly includeOutputProjectSymbolGraph?: boolean;
|
|
74
90
|
readonly includeProjectGraphDelta?: boolean;
|
|
91
|
+
readonly projectGraphLimits?: JsTsProjectGraphLimits;
|
|
75
92
|
readonly outputProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
76
93
|
readonly baseProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
77
94
|
readonly workerProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
@@ -132,6 +149,7 @@ export interface JsTsProjectSafeMergeAdmission {
|
|
|
132
149
|
export interface JsTsProjectGraphDeltaStageSummary {
|
|
133
150
|
readonly stage: JsTsProjectGraphStageName;
|
|
134
151
|
readonly sourceFiles: number;
|
|
152
|
+
readonly sourceBytes: number;
|
|
135
153
|
readonly documents: number;
|
|
136
154
|
readonly symbols: number;
|
|
137
155
|
readonly fileHashes: number;
|
|
@@ -143,6 +161,8 @@ export interface JsTsProjectGraphDeltaStageSummary {
|
|
|
143
161
|
readonly suppliedImports: number;
|
|
144
162
|
readonly matchedSuppliedImports: number;
|
|
145
163
|
readonly scannerFallbackImports: number;
|
|
164
|
+
readonly serializedBytes?: number;
|
|
165
|
+
readonly limitConflicts: number;
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
export interface JsTsProjectGraphDeltaStage {
|
|
@@ -152,6 +172,7 @@ export interface JsTsProjectGraphDeltaStage {
|
|
|
152
172
|
readonly projectImport?: NativeProjectImportResult;
|
|
153
173
|
readonly projectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
154
174
|
readonly summary: JsTsProjectGraphDeltaStageSummary;
|
|
175
|
+
readonly limitConflicts?: readonly JsTsSafeMergeConflict[];
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
export interface JsTsProjectGraphDeltaSummary {
|
|
@@ -165,6 +186,9 @@ export interface JsTsProjectGraphDeltaSummary {
|
|
|
165
186
|
readonly suppliedImports: number;
|
|
166
187
|
readonly matchedSuppliedImports: number;
|
|
167
188
|
readonly scannerFallbackImports: number;
|
|
189
|
+
readonly sourceBytes: number;
|
|
190
|
+
readonly serializedBytes: number;
|
|
191
|
+
readonly limitConflicts: number;
|
|
168
192
|
readonly conflicts: number;
|
|
169
193
|
readonly publicContractConflicts: number;
|
|
170
194
|
readonly reExportIdentityConflicts: number;
|
|
@@ -201,6 +225,7 @@ export interface JsTsProjectSafeMergeResult {
|
|
|
201
225
|
readonly projectGraphConflicts: number;
|
|
202
226
|
readonly outputProjectGraphConflicts: number;
|
|
203
227
|
readonly projectGraphDeltaConflicts: number;
|
|
228
|
+
readonly projectGraphLimitConflicts: number;
|
|
204
229
|
readonly projectGraphPublicContractConflicts: number;
|
|
205
230
|
readonly projectGraphReExportIdentityConflicts: number;
|
|
206
231
|
readonly projectGraphImportTargetConflicts: number;
|
|
@@ -200,6 +200,7 @@ function validateCrossSideAddedNames(workerPlan, headPlan, context) {
|
|
|
200
200
|
const nameKey = `${entry.kind}:${name}`;
|
|
201
201
|
const headEntry = headEntriesByName.get(nameKey);
|
|
202
202
|
if (headEntry) {
|
|
203
|
+
if (shouldDeferReExportNameConflict(entry, headEntry, context)) continue;
|
|
203
204
|
const typeAliasConflict = entry.declarationInfo?.declarationKind === 'type'
|
|
204
205
|
|| headEntry.declarationInfo?.declarationKind === 'type';
|
|
205
206
|
addConflict(context, {
|
|
@@ -216,6 +217,16 @@ function validateCrossSideAddedNames(workerPlan, headPlan, context) {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
function shouldDeferReExportNameConflict(left, right, context) {
|
|
221
|
+
return context.deferReExportIdentityConflictsToProjectGraph === true
|
|
222
|
+
&& isReExportEntry(left)
|
|
223
|
+
&& isReExportEntry(right);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isReExportEntry(entry) {
|
|
227
|
+
return entry?.kind === 'export' && entry.declarationInfo?.reExport === true;
|
|
228
|
+
}
|
|
229
|
+
|
|
219
230
|
function validateCrossSideImportAdditions(workerPlan, headPlan, context) {
|
|
220
231
|
for (const [key, workerAdditions] of workerPlan.importAdditions) {
|
|
221
232
|
const headAdditions = headPlan.importAdditions.get(key) ?? [];
|
|
@@ -5,6 +5,7 @@ export function createMergeContext(input) {
|
|
|
5
5
|
id: String(input.id ?? 'js_ts_safe_merge'),
|
|
6
6
|
sourcePath: input.sourcePath,
|
|
7
7
|
language: input.language ?? 'typescript',
|
|
8
|
+
deferReExportIdentityConflictsToProjectGraph: input.deferReExportIdentityConflictsToProjectGraph === true,
|
|
8
9
|
conflicts: [],
|
|
9
10
|
blockedGateIds: new Set(),
|
|
10
11
|
gateReasonCodes: new Map()
|
|
@@ -24,6 +24,7 @@ export function validateLedgerUniqueness(ledger, context) {
|
|
|
24
24
|
const nameKey = `${entry.kind}:${name}`;
|
|
25
25
|
const existing = nameOwners.get(nameKey);
|
|
26
26
|
if (existing) {
|
|
27
|
+
if (shouldDeferMergedReExportNameConflict(ledger, existing, entry, context)) continue;
|
|
27
28
|
addConflict(context, {
|
|
28
29
|
code: JsTsSafeMergeConflictCodes.duplicateName,
|
|
29
30
|
gateId: JsTsSafeMergeGateIds.uniqueNames,
|
|
@@ -65,6 +66,17 @@ function validateUniqueImportSpecifiers(entry, side, context) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function shouldDeferMergedReExportNameConflict(ledger, left, right, context) {
|
|
70
|
+
return ledger.label === 'merged'
|
|
71
|
+
&& context.deferReExportIdentityConflictsToProjectGraph === true
|
|
72
|
+
&& isReExportEntry(left)
|
|
73
|
+
&& isReExportEntry(right);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isReExportEntry(entry) {
|
|
77
|
+
return entry?.kind === 'export' && entry.declarationInfo?.reExport === true;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
export function indexBaseLedger(base, context) {
|
|
69
81
|
const entriesByKey = new Map();
|
|
70
82
|
for (const entry of base.entries) entriesByKey.set(entry.key, entry);
|
|
@@ -19,7 +19,7 @@ export function classifyStatement(text, start, end) {
|
|
|
19
19
|
if (unsupported) return { unsupported, text, start, end };
|
|
20
20
|
return {
|
|
21
21
|
kind: declarationInfo.kind,
|
|
22
|
-
key:
|
|
22
|
+
key: declarationLedgerKey(declarationInfo),
|
|
23
23
|
text,
|
|
24
24
|
start,
|
|
25
25
|
end,
|
|
@@ -126,6 +126,50 @@ function parseDeclarationInfo(text) {
|
|
|
126
126
|
const defaultClass = trimmed.match(/^export\s+default\s+(?:abstract\s+)?class(?:\s+([A-Za-z_$][\w$]*))?\b/);
|
|
127
127
|
if (defaultClass) return { kind: 'declaration', names: ['default'], declarationKind: 'class', exported: true, defaultExport: true };
|
|
128
128
|
|
|
129
|
+
const namespaceReExport = trimmed.match(/^export\s+(type\s+)?\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+(['"])([^'"]+)\3\s*;?$/);
|
|
130
|
+
if (namespaceReExport) {
|
|
131
|
+
return {
|
|
132
|
+
kind: 'export',
|
|
133
|
+
names: [namespaceReExport[2]],
|
|
134
|
+
declarationKind: 're-export-namespace',
|
|
135
|
+
exported: true,
|
|
136
|
+
typeOnly: Boolean(namespaceReExport[1]),
|
|
137
|
+
reExport: true,
|
|
138
|
+
moduleSpecifier: namespaceReExport[4],
|
|
139
|
+
namespace: namespaceReExport[2]
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const starReExport = trimmed.match(/^export\s+(type\s+)?\*\s+from\s+(['"])([^'"]+)\2\s*;?$/);
|
|
144
|
+
if (starReExport) {
|
|
145
|
+
return {
|
|
146
|
+
kind: 'export',
|
|
147
|
+
names: [],
|
|
148
|
+
declarationKind: 're-export-star',
|
|
149
|
+
exported: true,
|
|
150
|
+
typeOnly: Boolean(starReExport[1]),
|
|
151
|
+
reExport: true,
|
|
152
|
+
moduleSpecifier: starReExport[3],
|
|
153
|
+
exportStar: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const namedReExport = trimmed.match(/^export\s+(type\s+)?\{([\s\S]+)\}\s+from\s+(['"])([^'"]+)\3\s*;?$/);
|
|
158
|
+
if (namedReExport) {
|
|
159
|
+
const names = splitCommaList(namedReExport[2]).map((part) => parseExportSpecifierName(part)).filter(Boolean);
|
|
160
|
+
const expectedCount = splitCommaList(namedReExport[2]).filter((part) => part.trim()).length;
|
|
161
|
+
if (names.length !== expectedCount || names.length === 0) return undefined;
|
|
162
|
+
return {
|
|
163
|
+
kind: 'export',
|
|
164
|
+
names,
|
|
165
|
+
declarationKind: 're-export-list',
|
|
166
|
+
exported: true,
|
|
167
|
+
typeOnly: Boolean(namedReExport[1]),
|
|
168
|
+
reExport: true,
|
|
169
|
+
moduleSpecifier: namedReExport[4]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
const namedExport = trimmed.match(/^export\s+(type\s+)?\{([\s\S]+)\}\s*;?$/);
|
|
130
174
|
if (namedExport) {
|
|
131
175
|
const names = splitCommaList(namedExport[2]).map((part) => parseExportSpecifierName(part)).filter(Boolean);
|
|
@@ -154,6 +198,19 @@ function parseDeclarationInfo(text) {
|
|
|
154
198
|
return undefined;
|
|
155
199
|
}
|
|
156
200
|
|
|
201
|
+
function declarationLedgerKey(declarationInfo) {
|
|
202
|
+
if (declarationInfo.reExport) {
|
|
203
|
+
return [
|
|
204
|
+
declarationInfo.kind,
|
|
205
|
+
declarationInfo.declarationKind,
|
|
206
|
+
declarationInfo.moduleSpecifier ?? '',
|
|
207
|
+
declarationInfo.typeOnly ? 'type' : 'value',
|
|
208
|
+
declarationInfo.exportStar ? '*' : declarationInfo.names.join('|')
|
|
209
|
+
].join(':');
|
|
210
|
+
}
|
|
211
|
+
return `${declarationInfo.kind}:${declarationInfo.names.join('|')}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
157
214
|
function unsupportedDeclarationPolicy(text, declarationInfo) {
|
|
158
215
|
const trimmed = text.trim();
|
|
159
216
|
if (/^\s*@/m.test(text)) {
|
|
@@ -2,6 +2,8 @@ import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
|
2
2
|
import { projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
|
|
3
3
|
|
|
4
4
|
function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
5
|
+
const limitConflicts = Array.isArray(projectSymbolGraph?.limitConflicts) ? projectSymbolGraph.limitConflicts : [];
|
|
6
|
+
projectSymbolGraph = projectSymbolGraph?.projectSymbolGraph ?? projectSymbolGraph;
|
|
5
7
|
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
6
8
|
const missingModuleGroups = new Map();
|
|
7
9
|
const missingSymbolGroups = new Map();
|
|
@@ -21,6 +23,7 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
return [
|
|
26
|
+
...limitConflicts,
|
|
24
27
|
...[...missingModuleGroups.values()].map(projectGraphMissingImportConflict),
|
|
25
28
|
...[...missingSymbolGroups.values()].map(projectGraphMissingTargetConflict)
|
|
26
29
|
];
|
|
@@ -2,12 +2,14 @@ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
|
2
2
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
3
3
|
|
|
4
4
|
function projectGraphDeltaConflicts(projectGraphDelta) {
|
|
5
|
+
const limitConflicts = projectGraphDeltaLimitConflicts(projectGraphDelta);
|
|
5
6
|
const baseGraph = projectGraphDelta?.stages?.base?.projectSymbolGraph;
|
|
6
7
|
const workerGraph = projectGraphDelta?.stages?.worker?.projectSymbolGraph;
|
|
7
8
|
const headGraph = projectGraphDelta?.stages?.head?.projectSymbolGraph;
|
|
8
9
|
const outputGraph = projectGraphDelta?.stages?.output?.projectSymbolGraph;
|
|
9
|
-
if (!baseGraph || !workerGraph || !headGraph) return
|
|
10
|
+
if (!baseGraph || !workerGraph || !headGraph) return limitConflicts;
|
|
10
11
|
return [
|
|
12
|
+
...limitConflicts,
|
|
11
13
|
...changedIdentityConflicts({
|
|
12
14
|
code: 'project-public-contract-delta-conflict',
|
|
13
15
|
label: 'public contract',
|
|
@@ -43,11 +45,16 @@ function addProjectGraphDeltaConflictSummary(projectGraphDelta, conflicts) {
|
|
|
43
45
|
conflicts: conflicts.length,
|
|
44
46
|
publicContractConflicts: conflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
45
47
|
reExportIdentityConflicts: conflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
46
|
-
importTargetConflicts: conflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length
|
|
48
|
+
importTargetConflicts: conflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length,
|
|
49
|
+
limitConflicts: conflicts.filter((conflict) => conflict.code === 'project-graph-limit-exceeded').length
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
function projectGraphDeltaLimitConflicts(projectGraphDelta) {
|
|
55
|
+
return Object.values(projectGraphDelta?.stages ?? {}).flatMap((stage) => stage?.limitConflicts ?? []);
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
function changedIdentityConflicts(input) {
|
|
52
59
|
const base = recordsByIdentityKey(input.baseRecords, input.identityKey);
|
|
53
60
|
const worker = recordsByIdentityKey(input.workerRecords, input.identityKey);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
2
|
+
|
|
3
|
+
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : undefined;
|
|
4
|
+
const PROJECT_GRAPH_LIMIT_CODE = 'project-graph-limit-exceeded';
|
|
5
|
+
const PROJECT_GRAPH_INVALID_LIMIT_CODE = 'project-graph-limit-invalid';
|
|
6
|
+
const LIMIT_FIELDS = [
|
|
7
|
+
['maxFiles', 'source-files'],
|
|
8
|
+
['maxSourceBytes', 'source-bytes'],
|
|
9
|
+
['maxImportEdges', 'import-edges'],
|
|
10
|
+
['maxExportEdges', 'export-edges'],
|
|
11
|
+
['maxSerializedBytes', 'serialized-bytes']
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function normalizeProjectGraphLimits(value) {
|
|
15
|
+
if (!value || typeof value !== 'object') return {};
|
|
16
|
+
const limits = {};
|
|
17
|
+
const invalidLimits = [];
|
|
18
|
+
for (const [field, limitKind] of LIMIT_FIELDS) {
|
|
19
|
+
if (!Object.prototype.hasOwnProperty.call(value, field) || value[field] === undefined) continue;
|
|
20
|
+
const normalized = normalizeLimit(value[field]);
|
|
21
|
+
if (normalized === undefined) invalidLimits.push({ field, limitKind, value: String(value[field]), valueType: typeof value[field] });
|
|
22
|
+
else limits[field] = normalized;
|
|
23
|
+
}
|
|
24
|
+
return compactRecord({ ...limits, invalidLimits: invalidLimits.length ? invalidLimits : undefined });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function projectGraphSourceStats(files) {
|
|
28
|
+
return {
|
|
29
|
+
sourceFiles: files.length,
|
|
30
|
+
sourceBytes: files.reduce((total, file) => total + utf8ByteLength(file?.sourceText ?? ''), 0)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function projectGraphSourceLimitConflicts(limits, stage, stats) {
|
|
35
|
+
return [
|
|
36
|
+
...projectGraphInvalidLimitConflicts(limits, stage),
|
|
37
|
+
limitConflict(limits.maxFiles, stage, 'source-files', stats.sourceFiles),
|
|
38
|
+
limitConflict(limits.maxSourceBytes, stage, 'source-bytes', stats.sourceBytes)
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function projectGraphInvalidLimitConflicts(limits, stage) {
|
|
43
|
+
return (limits.invalidLimits ?? []).map((invalid) => ({
|
|
44
|
+
code: PROJECT_GRAPH_INVALID_LIMIT_CODE,
|
|
45
|
+
gateId: 'project-graph-limit',
|
|
46
|
+
message: `Project graph ${stage} stage received invalid ${invalid.limitKind} limit ${JSON.stringify(invalid.value)}.`,
|
|
47
|
+
details: compactRecord({
|
|
48
|
+
reasonCode: PROJECT_GRAPH_INVALID_LIMIT_CODE,
|
|
49
|
+
conflictKey: `project-graph-limit#${stage}#${invalid.limitKind}#invalid`,
|
|
50
|
+
stage,
|
|
51
|
+
limitKind: invalid.limitKind,
|
|
52
|
+
limitField: invalid.field,
|
|
53
|
+
limitValue: invalid.value,
|
|
54
|
+
limitValueType: invalid.valueType
|
|
55
|
+
})
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function projectGraphEdgeLimitConflicts(limits, stage, projectSymbolGraph) {
|
|
60
|
+
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges.length : 0;
|
|
61
|
+
const exportEdges = Array.isArray(projectSymbolGraph?.exportEdges) ? projectSymbolGraph.exportEdges.length : 0;
|
|
62
|
+
return [
|
|
63
|
+
limitConflict(limits.maxImportEdges, stage, 'import-edges', importEdges),
|
|
64
|
+
limitConflict(limits.maxExportEdges, stage, 'export-edges', exportEdges)
|
|
65
|
+
].filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function projectGraphSerializedLimitConflict(limits, stage, artifact) {
|
|
69
|
+
const serializedBytes = serializedByteLength(artifact);
|
|
70
|
+
return {
|
|
71
|
+
serializedBytes,
|
|
72
|
+
conflict: limitConflict(limits.maxSerializedBytes, stage, 'serialized-bytes', serializedBytes)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function limitConflict(limit, stage, limitKind, actual) {
|
|
77
|
+
if (limit === undefined || actual <= limit) return undefined;
|
|
78
|
+
return {
|
|
79
|
+
code: PROJECT_GRAPH_LIMIT_CODE,
|
|
80
|
+
gateId: 'project-graph-limit',
|
|
81
|
+
message: `Project graph ${stage} stage exceeded ${limitKind} limit: ${actual} > ${limit}.`,
|
|
82
|
+
details: compactRecord({
|
|
83
|
+
reasonCode: PROJECT_GRAPH_LIMIT_CODE,
|
|
84
|
+
conflictKey: `project-graph-limit#${stage}#${limitKind}`,
|
|
85
|
+
stage,
|
|
86
|
+
limitKind,
|
|
87
|
+
actual,
|
|
88
|
+
limit
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeLimit(value) {
|
|
94
|
+
if (value === null) return undefined;
|
|
95
|
+
const limit = Number(value);
|
|
96
|
+
return Number.isFinite(limit) && limit >= 0 ? Math.floor(limit) : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function serializedByteLength(value) {
|
|
100
|
+
return utf8ByteLength(JSON.stringify(value));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function utf8ByteLength(value) {
|
|
104
|
+
const text = String(value ?? '');
|
|
105
|
+
if (encoder) return encoder.encode(text).length;
|
|
106
|
+
return unescape(encodeURIComponent(text)).length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
PROJECT_GRAPH_INVALID_LIMIT_CODE,
|
|
111
|
+
PROJECT_GRAPH_LIMIT_CODE,
|
|
112
|
+
normalizeProjectGraphLimits,
|
|
113
|
+
projectGraphEdgeLimitConflicts,
|
|
114
|
+
projectGraphSerializedLimitConflict,
|
|
115
|
+
projectGraphSourceLimitConflicts,
|
|
116
|
+
projectGraphSourceStats
|
|
117
|
+
};
|
|
@@ -3,6 +3,13 @@ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
|
3
3
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
4
4
|
import { createNativeProjectImportResult } from './internal/index-impl/createNativeProjectImportResult.js';
|
|
5
5
|
import { importNativeSource } from './internal/index-impl/importNativeSource.js';
|
|
6
|
+
import {
|
|
7
|
+
normalizeProjectGraphLimits,
|
|
8
|
+
projectGraphEdgeLimitConflicts,
|
|
9
|
+
projectGraphSerializedLimitConflict,
|
|
10
|
+
projectGraphSourceLimitConflicts,
|
|
11
|
+
projectGraphSourceStats
|
|
12
|
+
} from './js-ts-safe-project-merge-graph-limits.js';
|
|
6
13
|
|
|
7
14
|
function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
|
|
8
15
|
return createProjectGraphStageArtifacts(input, outputFiles, mergeId, 'output', projectImportsForStage(input, 'output'));
|
|
@@ -32,12 +39,28 @@ function createJsTsProjectSafeMergeGraphDelta(input, files, outputFiles, mergeId
|
|
|
32
39
|
suppliedImports: sumStageSummary(stageSummaries, 'suppliedImports'),
|
|
33
40
|
matchedSuppliedImports: sumStageSummary(stageSummaries, 'matchedSuppliedImports'),
|
|
34
41
|
scannerFallbackImports: sumStageSummary(stageSummaries, 'scannerFallbackImports'),
|
|
42
|
+
sourceBytes: sumStageSummary(stageSummaries, 'sourceBytes'),
|
|
43
|
+
serializedBytes: sumStageSummary(stageSummaries, 'serializedBytes'),
|
|
44
|
+
limitConflicts: sumStageSummary(stageSummaries, 'limitConflicts'),
|
|
35
45
|
stageSummaries
|
|
36
46
|
}
|
|
37
47
|
};
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stageImports) {
|
|
51
|
+
const limits = normalizeProjectGraphLimits(input.projectGraphLimits);
|
|
52
|
+
const sourceStats = projectGraphSourceStats(files);
|
|
53
|
+
const sourceLimitConflicts = projectGraphSourceLimitConflicts(limits, stageName, sourceStats);
|
|
54
|
+
const suppliedImports = normalizeProjectImports(stageImports);
|
|
55
|
+
const projectGraphImportSource = {
|
|
56
|
+
stage: stageName,
|
|
57
|
+
suppliedImports: suppliedImports.length,
|
|
58
|
+
matchedSuppliedImports: 0,
|
|
59
|
+
scannerFallbackImports: 0
|
|
60
|
+
};
|
|
61
|
+
if (sourceLimitConflicts.length) {
|
|
62
|
+
return limitedProjectGraphStage(stageName, projectGraphImportSource, sourceStats, undefined, sourceLimitConflicts);
|
|
63
|
+
}
|
|
41
64
|
const sources = files.map((file) => ({
|
|
42
65
|
id: `${mergeId}_${stageName}_${idFragment(file.sourcePath)}`,
|
|
43
66
|
language: file.language ?? input.language ?? languageForPath(file.sourcePath),
|
|
@@ -46,7 +69,6 @@ function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stag
|
|
|
46
69
|
sourceHash: file.sourceHash,
|
|
47
70
|
metadata: { semanticImportExpected: true, projectSafeMergeStage: stageName, projectSafeMergeOutput: stageName === 'output' }
|
|
48
71
|
}));
|
|
49
|
-
const suppliedImports = normalizeProjectImports(stageImports);
|
|
50
72
|
const importSelections = sources.map((source) => {
|
|
51
73
|
const suppliedImport = matchingProjectImport(source, suppliedImports);
|
|
52
74
|
return {
|
|
@@ -55,12 +77,8 @@ function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stag
|
|
|
55
77
|
};
|
|
56
78
|
});
|
|
57
79
|
const imports = importSelections.map((selection) => selection.importResult);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
suppliedImports: suppliedImports.length,
|
|
61
|
-
matchedSuppliedImports: importSelections.filter((selection) => selection.sourceKind === `supplied-${stageName}-project-import`).length,
|
|
62
|
-
scannerFallbackImports: importSelections.filter((selection) => selection.sourceKind === `lightweight-${stageName}-project-scan`).length
|
|
63
|
-
};
|
|
80
|
+
projectGraphImportSource.matchedSuppliedImports = importSelections.filter((selection) => selection.sourceKind === `supplied-${stageName}-project-import`).length;
|
|
81
|
+
projectGraphImportSource.scannerFallbackImports = importSelections.filter((selection) => selection.sourceKind === `lightweight-${stageName}-project-scan`).length;
|
|
64
82
|
const projectImport = createNativeProjectImportResult({
|
|
65
83
|
id: `${mergeId}_${stageName}_project_import`,
|
|
66
84
|
projectRoot: input.projectRoot,
|
|
@@ -74,13 +92,32 @@ function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stag
|
|
|
74
92
|
...(stageName === 'output' ? { outputProjectImportSource: projectGraphImportSource } : {})
|
|
75
93
|
}
|
|
76
94
|
}, imports);
|
|
95
|
+
const edgeLimitConflicts = projectGraphEdgeLimitConflicts(limits, stageName, projectImport.projectSymbolGraph);
|
|
96
|
+
const serialized = projectGraphSerializedLimitConflict(limits, stageName, {
|
|
97
|
+
projectImport,
|
|
98
|
+
projectSymbolGraph: projectImport.projectSymbolGraph
|
|
99
|
+
});
|
|
100
|
+
const limitConflicts = [...edgeLimitConflicts, serialized.conflict].filter(Boolean);
|
|
101
|
+
if (limitConflicts.length) {
|
|
102
|
+
return limitedProjectGraphStage(stageName, projectGraphImportSource, sourceStats, projectImport.projectSymbolGraph, limitConflicts, serialized.serializedBytes);
|
|
103
|
+
}
|
|
77
104
|
return {
|
|
78
105
|
kind: 'frontier.lang.jsTsProjectGraphStage',
|
|
79
106
|
version: 1,
|
|
80
107
|
stage: stageName,
|
|
81
108
|
projectImport,
|
|
82
109
|
projectSymbolGraph: projectImport.projectSymbolGraph,
|
|
83
|
-
summary: projectGraphStageSummary(stageName, projectImport.projectSymbolGraph, projectGraphImportSource)
|
|
110
|
+
summary: projectGraphStageSummary(stageName, projectImport.projectSymbolGraph, projectGraphImportSource, sourceStats, serialized.serializedBytes, [])
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function limitedProjectGraphStage(stageName, importSource, sourceStats, projectSymbolGraph, limitConflicts, serializedBytes) {
|
|
115
|
+
return {
|
|
116
|
+
kind: 'frontier.lang.jsTsProjectGraphStage',
|
|
117
|
+
version: 1,
|
|
118
|
+
stage: stageName,
|
|
119
|
+
summary: projectGraphStageSummary(stageName, projectSymbolGraph, importSource, sourceStats, serializedBytes, limitConflicts),
|
|
120
|
+
limitConflicts
|
|
84
121
|
};
|
|
85
122
|
}
|
|
86
123
|
|
|
@@ -113,11 +150,12 @@ function matchingProjectImport(source, imports) {
|
|
|
113
150
|
});
|
|
114
151
|
}
|
|
115
152
|
|
|
116
|
-
function projectGraphStageSummary(stageName, projectSymbolGraph, importSource) {
|
|
153
|
+
function projectGraphStageSummary(stageName, projectSymbolGraph, importSource, sourceStats, serializedBytes, limitConflicts) {
|
|
117
154
|
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
118
155
|
return {
|
|
119
156
|
stage: stageName,
|
|
120
|
-
sourceFiles: projectSymbolGraph?.sourceCount ??
|
|
157
|
+
sourceFiles: projectSymbolGraph?.sourceCount ?? sourceStats.sourceFiles,
|
|
158
|
+
sourceBytes: sourceStats.sourceBytes,
|
|
121
159
|
documents: projectSymbolGraph?.documentCount ?? 0,
|
|
122
160
|
symbols: projectSymbolGraph?.symbolCount ?? 0,
|
|
123
161
|
fileHashes: projectSymbolGraph?.fileHashes?.length ?? 0,
|
|
@@ -128,7 +166,9 @@ function projectGraphStageSummary(stageName, projectSymbolGraph, importSource) {
|
|
|
128
166
|
unresolvedImportEdges: importEdges.filter(isMissingProjectImportEdge).length,
|
|
129
167
|
suppliedImports: importSource.suppliedImports,
|
|
130
168
|
matchedSuppliedImports: importSource.matchedSuppliedImports,
|
|
131
|
-
scannerFallbackImports: importSource.scannerFallbackImports
|
|
169
|
+
scannerFallbackImports: importSource.scannerFallbackImports,
|
|
170
|
+
serializedBytes,
|
|
171
|
+
limitConflicts: limitConflicts.length
|
|
132
172
|
};
|
|
133
173
|
}
|
|
134
174
|
|
|
@@ -25,7 +25,7 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
25
25
|
const graphArtifacts = projectGraphDelta?.stages?.output ?? (blockedFiles.length === 0 && input.includeOutputProjectSymbolGraph
|
|
26
26
|
? createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, id)
|
|
27
27
|
: undefined);
|
|
28
|
-
const outputGraphConflicts = outputProjectGraphConflicts(graphArtifacts?.projectSymbolGraph);
|
|
28
|
+
const outputGraphConflicts = outputProjectGraphConflicts(projectGraphDelta ? graphArtifacts?.projectSymbolGraph : graphArtifacts);
|
|
29
29
|
const deltaGraphConflicts = projectGraphDeltaConflicts(projectGraphDelta);
|
|
30
30
|
const projectGraphDeltaWithConflicts = addProjectGraphDeltaConflictSummary(projectGraphDelta, deltaGraphConflicts);
|
|
31
31
|
const graphConflicts = [...outputGraphConflicts, ...deltaGraphConflicts];
|
|
@@ -60,7 +60,7 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
60
60
|
reasonCodes,
|
|
61
61
|
conflictKeys
|
|
62
62
|
},
|
|
63
|
-
summary: projectSummary(fileResults, graphConflicts),
|
|
63
|
+
summary: projectSummary(fileResults, graphConflicts, Boolean(projectGraphDelta)),
|
|
64
64
|
metadata: compactRecord({
|
|
65
65
|
workerChangeSetId: input.workerChangeSetId,
|
|
66
66
|
headChangeSetId: input.headChangeSetId,
|
|
@@ -71,6 +71,7 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
71
71
|
projectGraphConflicts: graphConflicts.length || undefined,
|
|
72
72
|
outputProjectGraphConflicts: outputGraphConflicts.length || undefined,
|
|
73
73
|
projectGraphDeltaConflicts: deltaGraphConflicts.length || undefined,
|
|
74
|
+
projectGraphLimitConflicts: graphConflicts.filter((conflict) => conflict.gateId === 'project-graph-limit').length || undefined,
|
|
74
75
|
autoMergeClaim: false,
|
|
75
76
|
semanticEquivalenceClaim: false
|
|
76
77
|
})
|
|
@@ -110,6 +111,7 @@ function mergeProjectFile(file, input, projectId) {
|
|
|
110
111
|
const result = safeMergeJsTsSource({
|
|
111
112
|
...input,
|
|
112
113
|
...context,
|
|
114
|
+
deferReExportIdentityConflictsToProjectGraph: input.includeProjectGraphDelta === true,
|
|
113
115
|
id: `${projectId}_${safeId(file.sourcePath)}`,
|
|
114
116
|
baseSourceText: base,
|
|
115
117
|
workerSourceText: worker,
|
|
@@ -254,18 +256,21 @@ function sourceLedgersForFile(input, sourcePath) {
|
|
|
254
256
|
return byPath ?? (input.sourceLedgers?.base || input.sourceLedgers?.worker || input.sourceLedgers?.head ? input.sourceLedgers : undefined);
|
|
255
257
|
}
|
|
256
258
|
|
|
257
|
-
function projectSummary(files, graphConflicts = []) {
|
|
259
|
+
function projectSummary(files, graphConflicts = [], hasProjectGraphDelta = false) {
|
|
258
260
|
const byOperation = {};
|
|
259
261
|
for (const file of files) byOperation[file.operation] = (byOperation[file.operation] ?? 0) + 1;
|
|
260
|
-
const
|
|
262
|
+
const limitConflicts = graphConflicts.filter((conflict) => conflict.gateId === 'project-graph-limit');
|
|
263
|
+
const deltaConflicts = graphConflicts.filter((conflict) => conflict.gateId === 'project-graph-delta' || (hasProjectGraphDelta && conflict.gateId === 'project-graph-limit'));
|
|
264
|
+
const outputConflicts = graphConflicts.filter((conflict) => conflict.gateId === 'project-symbol-graph' || (!hasProjectGraphDelta && conflict.gateId === 'project-graph-limit'));
|
|
261
265
|
return {
|
|
262
266
|
files: files.length,
|
|
263
267
|
mergedFiles: files.filter((file) => file.status === 'merged').length,
|
|
264
268
|
blockedFiles: files.filter((file) => file.status === 'blocked').length,
|
|
265
269
|
outputFiles: files.filter((file) => typeof file.outputSourceText === 'string').length,
|
|
266
270
|
projectGraphConflicts: graphConflicts.length,
|
|
267
|
-
outputProjectGraphConflicts:
|
|
271
|
+
outputProjectGraphConflicts: outputConflicts.length,
|
|
268
272
|
projectGraphDeltaConflicts: deltaConflicts.length,
|
|
273
|
+
projectGraphLimitConflicts: limitConflicts.length,
|
|
269
274
|
projectGraphPublicContractConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
270
275
|
projectGraphReExportIdentityConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
271
276
|
projectGraphImportTargetConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length,
|
package/package.json
CHANGED