@shapeshift-labs/frontier-lang-compiler 0.2.139 → 0.2.141

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 CHANGED
@@ -203,7 +203,8 @@ re-emitted with safe commas when both sides add final properties.
203
203
  If staged whole-declaration replay cannot verify because head changed a sibling
204
204
  member inside the same declaration, `safeMergeJsTsSource` retries the direct
205
205
  semantic edit projection and admits the merge only when replay still verifies
206
- cleanly.
206
+ cleanly. That direct retry can project onto the staged top-level merge output,
207
+ so safe import/declaration additions are preserved alongside the sibling edit.
207
208
 
208
209
  Project-level JS/TS safe merges compose the same file-level gates across a
209
210
  base/worker/head file set. They preserve head-only files, admit worker-only
@@ -294,6 +295,9 @@ Public contract regions include `apiSurfaceKind`, `signatureHash`, and
294
295
  For `export * from './module.js'`, project graphs fan out re-export identities
295
296
  for each named export in the resolved target document and omit `default`, which
296
297
  matches JavaScript module semantics.
298
+ Output graph admission can use those expanded identities to accept disjoint
299
+ export-star additions while blocking incompatible duplicate exported names as
300
+ `project-output-re-export-identity-conflict`.
297
301
 
298
302
  When using `createTypeScriptCompilerNativeImporterAdapter`,
299
303
  `createEstreeNativeImporterAdapter`, or `createBabelNativeImporterAdapter`,
@@ -0,0 +1,41 @@
1
+ import { JsTsSafeMergeConflictCodes } from './js-ts-safe-merge-constants.js';
2
+
3
+ const semanticFallbackConflictCodes = new Set([
4
+ JsTsSafeMergeConflictCodes.changedExistingDeclaration,
5
+ JsTsSafeMergeConflictCodes.typeAliasConflict
6
+ ]);
7
+
8
+ function shouldTrySemanticEditFallback(result) {
9
+ const conflicts = result.conflicts ?? [];
10
+ return conflicts.length > 0 && conflicts.every((conflict) => semanticFallbackConflictCodes.has(conflict.code));
11
+ }
12
+
13
+ function semanticFallbackConflictCode(result) {
14
+ return result.conflicts?.find((conflict) => semanticFallbackConflictCodes.has(conflict.code))?.code
15
+ ?? JsTsSafeMergeConflictCodes.changedExistingDeclaration;
16
+ }
17
+
18
+ function semanticFallbackChangedExistingDeclarations(topLevelResult, resultBase, stagedFallback) {
19
+ const conflictCount = (topLevelResult.conflicts ?? [])
20
+ .filter((conflict) => semanticFallbackConflictCodes.has(conflict.code)).length;
21
+ return Math.max(
22
+ topLevelResult.summary?.changedExistingDeclarations ?? 0,
23
+ resultBase?.summary?.changedExistingDeclarations ?? 0,
24
+ stagedFallback?.neutralization?.summary?.workerChangedExistingDeclarations ?? 0,
25
+ conflictCount
26
+ );
27
+ }
28
+
29
+ function semanticFallbackPhase(fallback) {
30
+ if (!fallback) return 'semantic-edit-fallback';
31
+ return fallback.projectionMode === 'direct'
32
+ ? 'staged-top-level-direct-semantic-edit-fallback'
33
+ : 'staged-top-level-semantic-edit-fallback';
34
+ }
35
+
36
+ export {
37
+ semanticFallbackChangedExistingDeclarations,
38
+ semanticFallbackConflictCode,
39
+ semanticFallbackPhase,
40
+ shouldTrySemanticEditFallback
41
+ };
@@ -2,7 +2,13 @@ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { createSemanticEditScript } from './internal/index-impl/semanticEditScripts.js';
3
3
  import { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
4
4
  import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
5
- import { JsTsSafeMergeConflictCodes, JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
5
+ import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
6
+ import {
7
+ semanticFallbackChangedExistingDeclarations,
8
+ semanticFallbackConflictCode,
9
+ semanticFallbackPhase,
10
+ shouldTrySemanticEditFallback
11
+ } from './js-ts-safe-merge-semantic-edit-fallback-utils.js';
6
12
  import {
7
13
  createStagedDeclarationAlreadyAppliedReplay,
8
14
  createStagedDeclarationProjection,
@@ -17,10 +23,19 @@ function semanticEditFallbackResult(input, topLevelResult) {
17
23
  let selectedFallback = stagedFallback;
18
24
  let artifacts = createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback);
19
25
  if (stagedFallback && artifacts.status !== 'verified') {
20
- const directArtifacts = createSemanticEditFallbackArtifacts(input, topLevelResult, undefined);
26
+ const directFallback = stagedFallback.directProjectionHeadSourceText && stagedFallback.safeTopLevelChanges > 0
27
+ ? { ...stagedFallback, projectionMode: 'direct' }
28
+ : undefined;
29
+ const directArtifacts = createSemanticEditFallbackArtifacts(input, topLevelResult, directFallback);
21
30
  if (directArtifacts.status === 'verified') {
22
31
  artifacts = directArtifacts;
23
- selectedFallback = undefined;
32
+ selectedFallback = directFallback;
33
+ } else if (directFallback) {
34
+ const plainDirectArtifacts = createSemanticEditFallbackArtifacts(input, topLevelResult, undefined);
35
+ if (plainDirectArtifacts.status === 'verified') {
36
+ artifacts = plainDirectArtifacts;
37
+ selectedFallback = undefined;
38
+ }
24
39
  }
25
40
  }
26
41
  if (artifacts.status !== 'verified') return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
@@ -57,7 +72,7 @@ function semanticEditFallbackResult(input, topLevelResult) {
57
72
  metadata: {
58
73
  ...resultBase.metadata,
59
74
  composed: {
60
- phase: selectedFallback ? 'staged-top-level-semantic-edit-fallback' : 'semantic-edit-fallback',
75
+ phase: semanticFallbackPhase(selectedFallback),
61
76
  phases: selectedFallback ? ['top-level-neutralization', 'top-level-ledger', 'semantic-edit'] : ['top-level-ledger', 'semantic-edit'],
62
77
  originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
63
78
  stagedTopLevelSummary: selectedFallback?.stagedTopLevelResult?.summary,
@@ -68,47 +83,26 @@ function semanticEditFallbackResult(input, topLevelResult) {
68
83
  };
69
84
  }
70
85
 
71
- function shouldTrySemanticEditFallback(result) {
72
- const conflicts = result.conflicts ?? [];
73
- return conflicts.length > 0 && conflicts.every((conflict) => semanticFallbackConflictCodes.has(conflict.code));
74
- }
75
-
76
- const semanticFallbackConflictCodes = new Set([
77
- JsTsSafeMergeConflictCodes.changedExistingDeclaration,
78
- JsTsSafeMergeConflictCodes.typeAliasConflict
79
- ]);
80
-
81
- function semanticFallbackConflictCode(result) {
82
- return result.conflicts?.find((conflict) => semanticFallbackConflictCodes.has(conflict.code))?.code
83
- ?? JsTsSafeMergeConflictCodes.changedExistingDeclaration;
84
- }
85
-
86
- function semanticFallbackChangedExistingDeclarations(topLevelResult, resultBase, stagedFallback) {
87
- const conflictCount = (topLevelResult.conflicts ?? [])
88
- .filter((conflict) => semanticFallbackConflictCodes.has(conflict.code)).length;
89
- return Math.max(
90
- topLevelResult.summary?.changedExistingDeclarations ?? 0,
91
- resultBase?.summary?.changedExistingDeclarations ?? 0,
92
- stagedFallback?.neutralization?.summary?.workerChangedExistingDeclarations ?? 0,
93
- conflictCount
94
- );
95
- }
96
-
97
86
  function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback) {
98
87
  try {
99
88
  const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
100
89
  const language = input.language ?? topLevelResult.language ?? 'typescript';
101
90
  const sourcePath = input.sourcePath ?? topLevelResult.sourcePath ?? 'inline.ts';
91
+ const stagedDeclarationProjection = stagedFallback && stagedFallback.projectionMode !== 'direct';
102
92
  const scriptInput = stagedFallback?.scriptInput ?? input;
103
- const projectionHeadSourceText = stagedFallback?.projectionHeadSourceText ?? input.headSourceText;
104
- const replayCurrentSourceText = stagedFallback?.replayCurrentSourceText ?? input.headSourceText;
93
+ const projectionHeadSourceText = stagedFallback?.projectionMode === 'direct'
94
+ ? stagedFallback.directProjectionHeadSourceText
95
+ : stagedFallback?.projectionHeadSourceText ?? input.headSourceText;
96
+ const replayCurrentSourceText = stagedFallback?.projectionMode === 'direct'
97
+ ? stagedFallback.directReplayCurrentSourceText
98
+ : stagedFallback?.replayCurrentSourceText ?? input.headSourceText;
105
99
  const script = createSemanticEditScript({
106
100
  ...scriptInput,
107
101
  id: `${id}_semantic_edit`,
108
102
  language,
109
103
  sourcePath
110
104
  });
111
- const projection = stagedFallback
105
+ const projection = stagedDeclarationProjection
112
106
  ? createStagedDeclarationProjection({ id, script, sourcePath, language, stagedFallback })
113
107
  : projectSemanticEditScriptToSource({
114
108
  id: `${id}_semantic_edit_projection`,
@@ -119,7 +113,7 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallba
119
113
  parser: input.parser,
120
114
  metadata: stagedFallback?.metadata
121
115
  });
122
- const replay = stagedFallback
116
+ const replay = stagedDeclarationProjection
123
117
  ? createStagedDeclarationReplayRecord({ id, projection, sourcePath, language, stagedFallback, replayCurrentSourceText })
124
118
  : replaySemanticEditProjection({
125
119
  id: `${id}_semantic_edit_replay`,
@@ -130,7 +124,7 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallba
130
124
  parser: input.parser,
131
125
  metadata: stagedFallback?.metadata
132
126
  });
133
- const alreadyAppliedReplay = stagedFallback
127
+ const alreadyAppliedReplay = stagedDeclarationProjection
134
128
  ? createStagedDeclarationAlreadyAppliedReplay({ id, projection, sourcePath, language })
135
129
  : replaySemanticEditProjection({
136
130
  id: `${id}_semantic_edit_already_applied`,
@@ -201,7 +195,7 @@ function semanticEditArtifacts(input) {
201
195
  metadata: {
202
196
  autoMergeClaim: false,
203
197
  semanticEquivalenceClaim: false,
204
- source: input.stagedFallback ? 'js-ts-staged-top-level-semantic-edit-fallback' : 'js-ts-semantic-edit-fallback',
198
+ source: input.stagedFallback ? semanticFallbackPhase(input.stagedFallback) : 'js-ts-semantic-edit-fallback',
205
199
  originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
206
200
  stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
207
201
  neutralization: input.stagedFallback?.neutralization?.summary
@@ -14,11 +14,13 @@ function createStagedTopLevelSemanticFallback(input, topLevelResult) {
14
14
  if (stagedTopLevelResult.status !== JsTsSafeMergeStatuses.merged) return undefined;
15
15
  const safeTopLevelChanges = safeTopLevelChangeCount(stagedTopLevelResult.summary);
16
16
  const declarationReplay = createJsTsChangedDeclarationReplay(input, neutralization, stagedTopLevelResult.mergedSourceText);
17
+ const directSemanticHeadReplay = createJsTsChangedDeclarationReplay(input, neutralization, stagedTopLevelResult.mergedSourceText, 'head');
17
18
  const workerDeclarationChanges = neutralization.summary.workerChangedExistingDeclarations ?? 0;
18
19
  if (safeTopLevelChanges === 0 && workerDeclarationChanges === 0) return undefined;
19
20
  return {
20
21
  neutralization,
21
22
  declarationReplay,
23
+ safeTopLevelChanges,
22
24
  stagedTopLevelResult,
23
25
  scriptInput: {
24
26
  ...input,
@@ -28,13 +30,19 @@ function createStagedTopLevelSemanticFallback(input, topLevelResult) {
28
30
  headSourceHash: undefined
29
31
  },
30
32
  projectionHeadSourceText: stagedTopLevelResult.mergedSourceText,
33
+ directProjectionHeadSourceText: directSemanticHeadReplay.outputSourceText,
31
34
  replayCurrentSourceText: stagedTopLevelResult.mergedSourceText,
35
+ directReplayCurrentSourceText: directSemanticHeadReplay.outputSourceText,
32
36
  metadata: {
33
37
  stagedTopLevelSummary: stagedTopLevelResult.summary,
34
38
  declarationReplay: {
35
39
  edits: declarationReplay.edits.length,
36
40
  reasonCodes: declarationReplay.reasonCodes
37
41
  },
42
+ directSemanticHeadReplay: {
43
+ edits: directSemanticHeadReplay.edits.length,
44
+ reasonCodes: directSemanticHeadReplay.reasonCodes
45
+ },
38
46
  neutralization: neutralization.summary,
39
47
  originalReasonCodes: topLevelResult.admission?.reasonCodes ?? []
40
48
  }
@@ -42,7 +42,7 @@ function neutralizeJsTsSafeTopLevelMergeSources(input = {}) {
42
42
  };
43
43
  }
44
44
 
45
- function createJsTsChangedDeclarationReplay(input = {}, neutralization, currentSourceText) {
45
+ function createJsTsChangedDeclarationReplay(input = {}, neutralization, currentSourceText, side = 'worker') {
46
46
  if (!neutralization?.ok || typeof currentSourceText !== 'string') {
47
47
  return { ok: false, reasonCodes: ['missing-neutralized-current-source'], edits: [] };
48
48
  }
@@ -56,15 +56,17 @@ function createJsTsChangedDeclarationReplay(input = {}, neutralization, currentS
56
56
  const workerMatches = matchedEntriesByBaseKey(neutralization.worker, baseEntries);
57
57
  const headMatches = matchedEntriesByBaseKey(neutralization.head, baseEntries);
58
58
  const currentMatches = matchedEntriesByBaseKey(current, baseEntries);
59
+ const sourceMatches = side === 'head' ? headMatches : workerMatches;
60
+ const guardMatches = side === 'head' ? undefined : headMatches;
59
61
  const edits = [];
60
62
  const reasonCodes = [];
61
63
  for (const baseEntry of baseEntries) {
62
64
  if (baseEntry.kind === 'import') continue;
63
- const workerEntry = workerMatches.get(baseEntry.key);
64
- if (!workerEntry || sameStatementText(workerEntry.text, baseEntry.text)) continue;
65
- const headEntry = headMatches.get(baseEntry.key);
65
+ const sourceEntry = sourceMatches.get(baseEntry.key);
66
+ if (!sourceEntry || sameStatementText(sourceEntry.text, baseEntry.text)) continue;
66
67
  const currentEntry = currentMatches.get(baseEntry.key);
67
- if (!headEntry || !sameStatementText(headEntry.text, baseEntry.text)) {
68
+ const guardEntry = guardMatches?.get(baseEntry.key);
69
+ if (guardMatches && (!guardEntry || !sameStatementText(guardEntry.text, baseEntry.text))) {
68
70
  reasonCodes.push(`head-anchor-changed-since-base:${baseEntry.key}`);
69
71
  continue;
70
72
  }
@@ -74,12 +76,12 @@ function createJsTsChangedDeclarationReplay(input = {}, neutralization, currentS
74
76
  }
75
77
  edits.push({
76
78
  key: baseEntry.key,
77
- names: workerEntry.names ?? baseEntry.names ?? [],
78
- declarationKind: workerEntry.declarationInfo?.declarationKind ?? baseEntry.declarationInfo?.declarationKind,
79
+ names: sourceEntry.names ?? baseEntry.names ?? [],
80
+ declarationKind: sourceEntry.declarationInfo?.declarationKind ?? baseEntry.declarationInfo?.declarationKind,
79
81
  start: currentEntry.start,
80
82
  end: currentEntry.end,
81
83
  currentText: currentEntry.text,
82
- replacementText: workerEntry.text
84
+ replacementText: sourceEntry.text
83
85
  });
84
86
  }
85
87
  return {
@@ -1,3 +1,4 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
1
2
  import { compactRecord } from './js-ts-safe-merge-context.js';
2
3
  import { projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
3
4
 
@@ -25,7 +26,8 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
25
26
  return [
26
27
  ...limitConflicts,
27
28
  ...[...missingModuleGroups.values()].map(projectGraphMissingImportConflict),
28
- ...[...missingSymbolGroups.values()].map(projectGraphMissingTargetConflict)
29
+ ...[...missingSymbolGroups.values()].map(projectGraphMissingTargetConflict),
30
+ ...duplicateReExportIdentityConflicts(projectSymbolGraph?.reExportIdentities)
29
31
  ];
30
32
  }
31
33
 
@@ -94,6 +96,83 @@ function projectImportTargetName(edge) {
94
96
  return String(name);
95
97
  }
96
98
 
99
+ function duplicateReExportIdentityConflicts(records = []) {
100
+ const groups = new Map();
101
+ for (const record of records) {
102
+ const key = reExportIdentityKey(record);
103
+ if (!key) continue;
104
+ const group = groups.get(key) ?? [];
105
+ group.push(record);
106
+ groups.set(key, group);
107
+ }
108
+ return [...groups.entries()]
109
+ .filter(([, group]) => new Set(group.map(reExportIdentityFingerprint)).size > 1)
110
+ .map(([identityKey, group]) => projectGraphDuplicateReExportIdentityConflict(identityKey, group));
111
+ }
112
+
113
+ function projectGraphDuplicateReExportIdentityConflict(identityKey, group) {
114
+ const record = group[0] ?? {};
115
+ return {
116
+ code: 'project-output-re-export-identity-conflict',
117
+ gateId: 'project-symbol-graph',
118
+ message: `Output project graph exposes incompatible re-export identity ${JSON.stringify(identityKey)}.`,
119
+ sourcePath: record.sourcePath,
120
+ details: compactRecord({
121
+ reasonCode: 'project-output-re-export-identity-conflict',
122
+ conflictKey: `project-output-re-export-identity#${identityKey}`,
123
+ identityKey,
124
+ sourcePath: record.sourcePath,
125
+ exportedName: record.exportedName,
126
+ records: group.map(reExportIdentityDetails)
127
+ })
128
+ };
129
+ }
130
+
131
+ function reExportIdentityKey(record) {
132
+ return stableKey([
133
+ 're-export-identity',
134
+ record?.sourcePath,
135
+ record?.exportedName ?? record?.localName ?? record?.namespace ?? (record?.exportStar || record?.isExportStar ? '*' : undefined)
136
+ ]);
137
+ }
138
+
139
+ function reExportIdentityFingerprint(record) {
140
+ return hashSemanticValue({
141
+ moduleSpecifier: record.moduleSpecifier,
142
+ exportedName: record.exportedName,
143
+ importedName: record.importedName,
144
+ localName: record.localName,
145
+ namespace: record.namespace,
146
+ isTypeOnly: record.isTypeOnly,
147
+ exportStar: record.exportStar,
148
+ isExportStar: record.isExportStar,
149
+ originSymbolId: record.originSymbolId,
150
+ exportedSymbolId: record.exportedSymbolId,
151
+ localSymbolId: record.localSymbolId
152
+ });
153
+ }
154
+
155
+ function reExportIdentityDetails(record) {
156
+ return compactRecord({
157
+ moduleSpecifier: record.moduleSpecifier,
158
+ exportedName: record.exportedName,
159
+ importedName: record.importedName,
160
+ localName: record.localName,
161
+ namespace: record.namespace,
162
+ isTypeOnly: record.isTypeOnly,
163
+ exportStar: record.exportStar,
164
+ isExportStar: record.isExportStar,
165
+ originSymbolId: record.originSymbolId,
166
+ exportedSymbolId: record.exportedSymbolId,
167
+ localSymbolId: record.localSymbolId
168
+ });
169
+ }
170
+
171
+ function stableKey(parts) {
172
+ const values = parts.map((part) => part === undefined || part === null ? '' : String(part));
173
+ return values.some(Boolean) ? values.join('#') : undefined;
174
+ }
175
+
97
176
  function uniqueStrings(values) {
98
177
  return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
99
178
  }
@@ -111,7 +111,7 @@ function mergeProjectFile(file, input, projectId) {
111
111
  const result = safeMergeJsTsSource({
112
112
  ...input,
113
113
  ...context,
114
- deferReExportIdentityConflictsToProjectGraph: input.includeProjectGraphDelta === true,
114
+ deferReExportIdentityConflictsToProjectGraph: input.includeProjectGraphDelta === true || input.includeOutputProjectSymbolGraph === true,
115
115
  id: `${projectId}_${safeId(file.sourcePath)}`,
116
116
  baseSourceText: base,
117
117
  workerSourceText: worker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.139",
3
+ "version": "0.2.141",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",