@shapeshift-labs/frontier-lang-compiler 0.2.148 → 0.2.149

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.
@@ -19,6 +19,7 @@ export const JsTsSafeMergeConflictCodes = Object.freeze({
19
19
  malformedSyntax: 'malformed-syntax',
20
20
  sideEffectImportReorder: 'side-effect-import-reorder',
21
21
  topLevelOrderChanged: 'top-level-order-changed',
22
+ topLevelRenamePublicExportContract: 'top-level-rename-public-export-contract',
22
23
  changedExistingDeclaration: 'changed-existing-declaration',
23
24
  typeAliasConflict: 'type-alias-conflict',
24
25
  importShapeChanged: 'import-shape-changed',
@@ -0,0 +1,111 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { semanticFallbackPhase } from './js-ts-safe-merge-semantic-edit-fallback-utils.js';
3
+ import { idFragment, uniqueStrings } from './native-import-utils.js';
4
+
5
+ function semanticEditArtifacts(input) {
6
+ const reasonCodes = semanticEditArtifactReasonCodes(input);
7
+ const status = reasonCodes.length ? 'blocked' : 'verified';
8
+ const core = {
9
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
10
+ version: 1,
11
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
12
+ id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(input.id)}`,
13
+ sourcePath: input.sourcePath,
14
+ language: input.language,
15
+ status,
16
+ script: input.script,
17
+ projection: input.projection,
18
+ replay: input.replay,
19
+ alreadyAppliedReplay: input.alreadyAppliedReplay,
20
+ admission: {
21
+ status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
22
+ action: status === 'verified' ? 'apply' : 'human-review',
23
+ reviewRequired: status !== 'verified',
24
+ autoApplyCandidate: status === 'verified',
25
+ autoMergeClaim: false,
26
+ semanticEquivalenceClaim: false,
27
+ reasonCodes
28
+ },
29
+ summary: {
30
+ operations: input.script.summary.operations,
31
+ edits: input.projection.edits.length,
32
+ replayStatus: input.replay.status,
33
+ alreadyAppliedReplayStatus: input.alreadyAppliedReplay.status,
34
+ projectedSourceMatchesMerged: input.projection.sourceText === input.replay.outputSourceText,
35
+ replayOutputMatchesMerged: input.replay.outputSourceText === input.projection.sourceText
36
+ },
37
+ evidence: [{
38
+ id: `evidence_${idFragment(input.id)}_js_ts_semantic_edit_replay`,
39
+ kind: 'js-ts-semantic-edit-replay',
40
+ status: status === 'verified' ? 'passed' : 'needs-review',
41
+ path: input.sourcePath,
42
+ summary: status === 'verified'
43
+ ? `JS/TS semantic edit replay verified ${input.script.summary.operations} operation(s).`
44
+ : `JS/TS semantic edit replay requires review: ${reasonCodes.join(', ')}.`
45
+ }],
46
+ metadata: {
47
+ autoMergeClaim: false,
48
+ semanticEquivalenceClaim: false,
49
+ source: input.stagedFallback ? semanticFallbackPhase(input.stagedFallback) : 'js-ts-semantic-edit-fallback',
50
+ originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
51
+ stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
52
+ neutralization: input.stagedFallback?.neutralization?.summary
53
+ }
54
+ };
55
+ return { ...core, hash: hashSemanticValue(core) };
56
+ }
57
+
58
+ function blockedSemanticEditArtifacts(input, topLevelResult, reasonCodes, error) {
59
+ const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
60
+ const core = {
61
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
62
+ version: 1,
63
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
64
+ id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
65
+ sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
66
+ language: input.language ?? topLevelResult.language ?? 'typescript',
67
+ status: 'blocked',
68
+ admission: {
69
+ status: 'blocked',
70
+ action: 'human-review',
71
+ reviewRequired: true,
72
+ autoApplyCandidate: false,
73
+ autoMergeClaim: false,
74
+ semanticEquivalenceClaim: false,
75
+ reasonCodes
76
+ },
77
+ summary: {
78
+ operations: 0,
79
+ edits: 0,
80
+ replayStatus: 'blocked',
81
+ alreadyAppliedReplayStatus: 'blocked',
82
+ projectedSourceMatchesMerged: false,
83
+ replayOutputMatchesMerged: false
84
+ },
85
+ metadata: {
86
+ source: 'js-ts-semantic-edit-fallback',
87
+ error: error?.message
88
+ }
89
+ };
90
+ return { ...core, hash: hashSemanticValue(core) };
91
+ }
92
+
93
+ function semanticEditArtifactReasonCodes(input) {
94
+ const scriptReady = input.script.admission.status === 'auto-merge-candidate';
95
+ const projectionReady = input.projection.status === 'projected';
96
+ const replayReady = input.replay.status === 'accepted-clean';
97
+ const alreadyAppliedReady = input.alreadyAppliedReplay.status === 'already-applied';
98
+ return uniqueStrings([
99
+ scriptReady ? undefined : `semantic-edit-script-${input.script.admission.status}`,
100
+ projectionReady ? undefined : `semantic-edit-projection-${input.projection.status}`,
101
+ replayReady ? undefined : `semantic-edit-replay-${input.replay.status}`,
102
+ input.replay.outputSourceText !== input.projection.sourceText ? 'semantic-edit-replay-output-mismatch' : undefined,
103
+ alreadyAppliedReady ? undefined : `semantic-edit-already-applied-${input.alreadyAppliedReplay.status}`,
104
+ ...(scriptReady ? [] : input.script.admission.reasonCodes),
105
+ ...(projectionReady ? [] : input.projection.admission.reasonCodes),
106
+ ...(replayReady ? [] : input.replay.admission.reasonCodes),
107
+ ...(alreadyAppliedReady ? [] : input.alreadyAppliedReplay.admission.reasonCodes)
108
+ ]);
109
+ }
110
+
111
+ export { blockedSemanticEditArtifacts, semanticEditArtifacts };
@@ -33,7 +33,18 @@ function semanticFallbackPhase(fallback) {
33
33
  : 'staged-top-level-semantic-edit-fallback';
34
34
  }
35
35
 
36
+ function semanticFallbackCandidates(stagedFallback) {
37
+ if (!stagedFallback) return [undefined];
38
+ const headChanged = (stagedFallback.neutralization?.summary?.headChangedExistingDeclarations ?? 0) > 0;
39
+ const directFallback = stagedFallback.directProjectionHeadSourceText && (headChanged || stagedFallback.safeTopLevelChanges > 0)
40
+ ? { ...stagedFallback, projectionMode: 'direct' }
41
+ : undefined;
42
+ if (headChanged) return directFallback ? [directFallback, undefined] : [undefined];
43
+ return directFallback ? [stagedFallback, directFallback, undefined] : [stagedFallback];
44
+ }
45
+
36
46
  export {
47
+ semanticFallbackCandidates,
37
48
  semanticFallbackChangedExistingDeclarations,
38
49
  semanticFallbackConflictCode,
39
50
  semanticFallbackPhase,
@@ -1,11 +1,12 @@
1
- import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
1
  import { createSemanticEditScript } from './internal/index-impl/semanticEditScripts.js';
3
2
  import { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
4
3
  import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
5
4
  import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
6
5
  import { independentTopLevelDeletionFallbackResult } from './js-ts-safe-merge-independent-deletion-fallback.js';
7
6
  import { normalizeAlreadyAppliedDeleteReplay } from './js-ts-safe-merge-semantic-edit-already-applied.js';
7
+ import { blockedSemanticEditArtifacts, semanticEditArtifacts } from './js-ts-safe-merge-semantic-edit-artifacts.js';
8
8
  import {
9
+ semanticFallbackCandidates,
9
10
  semanticFallbackChangedExistingDeclarations,
10
11
  semanticFallbackConflictCode,
11
12
  semanticFallbackPhase,
@@ -19,11 +20,23 @@ import {
19
20
  } from './js-ts-safe-merge-staged-declaration-replay.js';
20
21
  import { createStagedTopLevelSemanticFallback } from './js-ts-safe-merge-staged-top-level-fallback.js';
21
22
  import { createSourceShapeSemanticFallbackResult } from './js-ts-safe-merge-source-shape-fallbacks.js';
22
- import { idFragment, uniqueStrings } from './native-import-utils.js';
23
+ import { analyzeTopLevelRenameAdmission } from './js-ts-safe-merge-top-level-rename-fallback.js';
24
+ import { topLevelRenameBlockedResult } from './js-ts-safe-merge-top-level-rename-result.js';
23
25
 
24
26
  function semanticEditFallbackResult(input, topLevelResult) {
25
27
  const independentDeletionResult = independentTopLevelDeletionFallbackResult(input, topLevelResult);
26
28
  if (independentDeletionResult) return independentDeletionResult;
29
+ const topLevelRenameAdmission = analyzeTopLevelRenameAdmission(input, topLevelResult);
30
+ if (topLevelRenameAdmission?.status === 'blocked') {
31
+ return topLevelRenameBlockedResult(input, topLevelResult, topLevelRenameAdmission);
32
+ }
33
+ if (topLevelRenameAdmission?.status === 'candidate') {
34
+ const artifacts = createSemanticEditFallbackArtifacts(input, topLevelResult);
35
+ if (artifacts.status !== 'verified') {
36
+ return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts, topLevelRenameAdmission);
37
+ }
38
+ return semanticEditFallbackMergedResult(input, topLevelResult, undefined, artifacts, topLevelRenameAdmission);
39
+ }
27
40
  if (!shouldTrySemanticEditFallback(topLevelResult)) return topLevelResult;
28
41
  const stagedFallback = createStagedTopLevelSemanticFallback(input, topLevelResult);
29
42
  const candidates = semanticFallbackCandidates(stagedFallback);
@@ -40,6 +53,10 @@ function semanticEditFallbackResult(input, topLevelResult) {
40
53
  if (sourceShapeResult) return sourceShapeResult;
41
54
  return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
42
55
  }
56
+ return semanticEditFallbackMergedResult(input, topLevelResult, selectedFallback, artifacts);
57
+ }
58
+
59
+ function semanticEditFallbackMergedResult(input, topLevelResult, selectedFallback, artifacts, topLevelRenameAdmission) {
43
60
  const resultBase = selectedFallback?.stagedTopLevelResult ?? topLevelResult;
44
61
  const mergedSourceText = artifacts.projection.sourceText;
45
62
  const gates = semanticEditGates(artifacts);
@@ -68,32 +85,26 @@ function semanticEditFallbackResult(input, topLevelResult) {
68
85
  semanticEditOperations: artifacts.script.summary.operations,
69
86
  semanticEditAppliedOperations: artifacts.replay.summary.applied,
70
87
  semanticEditReplayStatus: artifacts.replay.status,
88
+ topLevelDeclarationRenames: topLevelRenameAdmission ? 1 : resultBase.summary?.topLevelDeclarationRenames,
71
89
  composedPhases: 2
72
90
  },
73
91
  metadata: {
74
92
  ...resultBase.metadata,
75
93
  composed: {
76
- phase: semanticFallbackPhase(selectedFallback),
77
- phases: selectedFallback ? ['top-level-neutralization', 'top-level-ledger', 'semantic-edit'] : ['top-level-ledger', 'semantic-edit'],
94
+ phase: topLevelRenameAdmission ? 'top-level-rename-semantic-edit-fallback' : semanticFallbackPhase(selectedFallback),
95
+ phases: topLevelRenameAdmission
96
+ ? ['top-level-rename-admission', 'semantic-edit']
97
+ : selectedFallback ? ['top-level-neutralization', 'top-level-ledger', 'semantic-edit'] : ['top-level-ledger', 'semantic-edit'],
78
98
  originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
79
99
  stagedTopLevelSummary: selectedFallback?.stagedTopLevelResult?.summary,
80
- neutralization: selectedFallback?.neutralization?.summary
100
+ neutralization: selectedFallback?.neutralization?.summary,
101
+ topLevelRenameAdmission: topLevelRenameAdmission?.summary
81
102
  }
82
103
  },
83
104
  semanticArtifacts: artifacts
84
105
  };
85
106
  }
86
107
 
87
- function semanticFallbackCandidates(stagedFallback) {
88
- if (!stagedFallback) return [undefined];
89
- const headChanged = (stagedFallback.neutralization?.summary?.headChangedExistingDeclarations ?? 0) > 0;
90
- const directFallback = stagedFallback.directProjectionHeadSourceText && (headChanged || stagedFallback.safeTopLevelChanges > 0)
91
- ? { ...stagedFallback, projectionMode: 'direct' }
92
- : undefined;
93
- if (headChanged) return directFallback ? [directFallback, undefined] : [undefined];
94
- return directFallback ? [stagedFallback, directFallback, undefined] : [stagedFallback];
95
- }
96
-
97
108
  function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback) {
98
109
  try {
99
110
  const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
@@ -166,112 +177,6 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallba
166
177
  }
167
178
  }
168
179
 
169
- function semanticEditArtifacts(input) {
170
- const reasonCodes = semanticEditArtifactReasonCodes(input);
171
- const status = reasonCodes.length ? 'blocked' : 'verified';
172
- const core = {
173
- kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
174
- version: 1,
175
- schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
176
- id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(input.id)}`,
177
- sourcePath: input.sourcePath,
178
- language: input.language,
179
- status,
180
- script: input.script,
181
- projection: input.projection,
182
- replay: input.replay,
183
- alreadyAppliedReplay: input.alreadyAppliedReplay,
184
- admission: {
185
- status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
186
- action: status === 'verified' ? 'apply' : 'human-review',
187
- reviewRequired: status !== 'verified',
188
- autoApplyCandidate: status === 'verified',
189
- autoMergeClaim: false,
190
- semanticEquivalenceClaim: false,
191
- reasonCodes
192
- },
193
- summary: {
194
- operations: input.script.summary.operations,
195
- edits: input.projection.edits.length,
196
- replayStatus: input.replay.status,
197
- alreadyAppliedReplayStatus: input.alreadyAppliedReplay.status,
198
- projectedSourceMatchesMerged: input.projection.sourceText === input.replay.outputSourceText,
199
- replayOutputMatchesMerged: input.replay.outputSourceText === input.projection.sourceText
200
- },
201
- evidence: [{
202
- id: `evidence_${idFragment(input.id)}_js_ts_semantic_edit_replay`,
203
- kind: 'js-ts-semantic-edit-replay',
204
- status: status === 'verified' ? 'passed' : 'needs-review',
205
- path: input.sourcePath,
206
- summary: status === 'verified'
207
- ? `JS/TS semantic edit replay verified ${input.script.summary.operations} operation(s).`
208
- : `JS/TS semantic edit replay requires review: ${reasonCodes.join(', ')}.`
209
- }],
210
- metadata: {
211
- autoMergeClaim: false,
212
- semanticEquivalenceClaim: false,
213
- source: input.stagedFallback ? semanticFallbackPhase(input.stagedFallback) : 'js-ts-semantic-edit-fallback',
214
- originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
215
- stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
216
- neutralization: input.stagedFallback?.neutralization?.summary
217
- }
218
- };
219
- return { ...core, hash: hashSemanticValue(core) };
220
- }
221
-
222
- function semanticEditArtifactReasonCodes(input) {
223
- const scriptReady = input.script.admission.status === 'auto-merge-candidate';
224
- const projectionReady = input.projection.status === 'projected';
225
- const replayReady = input.replay.status === 'accepted-clean';
226
- const alreadyAppliedReady = input.alreadyAppliedReplay.status === 'already-applied';
227
- return uniqueStrings([
228
- scriptReady ? undefined : `semantic-edit-script-${input.script.admission.status}`,
229
- projectionReady ? undefined : `semantic-edit-projection-${input.projection.status}`,
230
- replayReady ? undefined : `semantic-edit-replay-${input.replay.status}`,
231
- input.replay.outputSourceText !== input.projection.sourceText ? 'semantic-edit-replay-output-mismatch' : undefined,
232
- alreadyAppliedReady ? undefined : `semantic-edit-already-applied-${input.alreadyAppliedReplay.status}`,
233
- ...(scriptReady ? [] : input.script.admission.reasonCodes),
234
- ...(projectionReady ? [] : input.projection.admission.reasonCodes),
235
- ...(replayReady ? [] : input.replay.admission.reasonCodes),
236
- ...(alreadyAppliedReady ? [] : input.alreadyAppliedReplay.admission.reasonCodes)
237
- ]);
238
- }
239
-
240
- function blockedSemanticEditArtifacts(input, topLevelResult, reasonCodes, error) {
241
- const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
242
- const core = {
243
- kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
244
- version: 1,
245
- schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
246
- id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
247
- sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
248
- language: input.language ?? topLevelResult.language ?? 'typescript',
249
- status: 'blocked',
250
- admission: {
251
- status: 'blocked',
252
- action: 'human-review',
253
- reviewRequired: true,
254
- autoApplyCandidate: false,
255
- autoMergeClaim: false,
256
- semanticEquivalenceClaim: false,
257
- reasonCodes
258
- },
259
- summary: {
260
- operations: 0,
261
- edits: 0,
262
- replayStatus: 'blocked',
263
- alreadyAppliedReplayStatus: 'blocked',
264
- projectedSourceMatchesMerged: false,
265
- replayOutputMatchesMerged: false
266
- },
267
- metadata: {
268
- source: 'js-ts-semantic-edit-fallback',
269
- error: error?.message
270
- }
271
- };
272
- return { ...core, hash: hashSemanticValue(core) };
273
- }
274
-
275
180
  function semanticEditFallbackBlockedResult(input, topLevelResult, artifacts) {
276
181
  const reasonCodes = artifacts.admission.reasonCodes.length
277
182
  ? artifacts.admission.reasonCodes
@@ -0,0 +1,149 @@
1
+ import { JsTsSafeMergeConflictCodes } from './js-ts-safe-merge-constants.js';
2
+ import { createMergeContext, sameStatementText } from './js-ts-safe-merge-context.js';
3
+ import { scanJsTsTopLevelLedger, validateLedgerUniqueness } from './js-ts-safe-merge-ledger.js';
4
+ import { uniqueStrings } from './native-import-utils.js';
5
+
6
+ const supportedRenameDeclarationKinds = new Set(['function', 'class', 'type']);
7
+
8
+ function analyzeTopLevelRenameAdmission(input, topLevelResult) {
9
+ const originalReasonCodes = topLevelResult?.admission?.reasonCodes ?? [];
10
+ if (!originalReasonCodes.includes(JsTsSafeMergeConflictCodes.topLevelOrderChanged)) return undefined;
11
+ if (typeof input.baseSourceText !== 'string'
12
+ || typeof input.workerSourceText !== 'string'
13
+ || typeof input.headSourceText !== 'string') {
14
+ return undefined;
15
+ }
16
+
17
+ const context = createMergeContext(input);
18
+ const base = scanJsTsTopLevelLedger(input.baseSourceText, 'base', context);
19
+ const worker = scanJsTsTopLevelLedger(input.workerSourceText, 'worker', context);
20
+ const head = scanJsTsTopLevelLedger(input.headSourceText, 'head', context);
21
+ if (!context.conflicts.length) {
22
+ validateLedgerUniqueness(base, context);
23
+ validateLedgerUniqueness(worker, context);
24
+ validateLedgerUniqueness(head, context);
25
+ }
26
+ if (context.conflicts.length) return undefined;
27
+
28
+ const candidate = topLevelRenameCandidate(base, worker, head);
29
+ if (!candidate) return undefined;
30
+
31
+ const publicContractReasonCodes = publicContractRenameReasonCodes(base, worker, head, candidate);
32
+ if (publicContractReasonCodes.length) {
33
+ return {
34
+ status: 'blocked',
35
+ reasonCodes: publicContractReasonCodes,
36
+ summary: candidateSummary(candidate, publicContractReasonCodes),
37
+ ledgers: { base, worker, head }
38
+ };
39
+ }
40
+
41
+ return {
42
+ status: 'candidate',
43
+ reasonCodes: ['top-level-rename-source-shape-matches'],
44
+ summary: candidateSummary(candidate, ['top-level-rename-source-shape-matches']),
45
+ ledgers: { base, worker, head }
46
+ };
47
+ }
48
+
49
+ function topLevelRenameCandidate(base, worker, head) {
50
+ const baseByKey = entriesByKey(base.entries);
51
+ const workerByKey = entriesByKey(worker.entries);
52
+ const baseKeys = base.entries.map((entry) => entry.key);
53
+ const missingBaseDeclarations = base.entries
54
+ .filter((entry) => !workerByKey.has(entry.key))
55
+ .filter(isSupportedRenameDeclaration);
56
+ const addedWorkerDeclarations = worker.entries
57
+ .filter((entry) => !baseByKey.has(entry.key))
58
+ .filter(isSupportedRenameDeclaration);
59
+
60
+ if (missingBaseDeclarations.length !== 1 || addedWorkerDeclarations.length !== 1) return undefined;
61
+ const fromEntry = missingBaseDeclarations[0];
62
+ const toEntry = addedWorkerDeclarations[0];
63
+ const fromName = fromEntry.names?.[0];
64
+ const toName = toEntry.names?.[0];
65
+ if (!fromName || !toName || fromName === toName) return undefined;
66
+ if (fromEntry.declarationInfo?.declarationKind !== toEntry.declarationInfo?.declarationKind) return undefined;
67
+ if (!sameStatementText(renameDeclarationText(fromEntry.text, fromEntry.declarationInfo.declarationKind, fromName, toName), toEntry.text)) {
68
+ return undefined;
69
+ }
70
+
71
+ const workerProjectedBaseKeys = worker.entries.map((entry) => entry.key === toEntry.key ? fromEntry.key : entry.key);
72
+ if (!sameStringList(workerProjectedBaseKeys, baseKeys)) return undefined;
73
+ const headProjectedBaseKeys = head.entries
74
+ .filter((entry) => baseByKey.has(entry.key))
75
+ .map((entry) => entry.key);
76
+ if (!sameStringList(headProjectedBaseKeys, baseKeys)) return undefined;
77
+ if (head.entries.length !== base.entries.length) return undefined;
78
+
79
+ return {
80
+ fromEntry,
81
+ toEntry,
82
+ fromName,
83
+ toName,
84
+ declarationKind: fromEntry.declarationInfo.declarationKind
85
+ };
86
+ }
87
+
88
+ function isSupportedRenameDeclaration(entry) {
89
+ return entry?.kind === 'declaration'
90
+ && entry.names?.length === 1
91
+ && supportedRenameDeclarationKinds.has(entry.declarationInfo?.declarationKind)
92
+ && !entry.declarationInfo.defaultExport
93
+ && !/^\s*(?:export\s+)?declare\b/.test(entry.text ?? '');
94
+ }
95
+
96
+ function publicContractRenameReasonCodes(base, worker, head, candidate) {
97
+ const directExport = candidate.fromEntry.declarationInfo?.exported === true
98
+ || candidate.toEntry.declarationInfo?.exported === true;
99
+ const exportListMention = [base, worker, head]
100
+ .some((ledger) => ledger.entries
101
+ .some((entry) => entry.kind === 'export' && mentionsName(entry.text, candidate.fromName, candidate.toName)));
102
+ return directExport || exportListMention
103
+ ? [JsTsSafeMergeConflictCodes.topLevelRenamePublicExportContract]
104
+ : [];
105
+ }
106
+
107
+ function mentionsName(text, ...names) {
108
+ return names.some((name) => new RegExp(`\\b${escapeRegExp(name)}\\b`).test(text ?? ''));
109
+ }
110
+
111
+ function renameDeclarationText(text, declarationKind, fromName, toName) {
112
+ if (typeof text !== 'string') return undefined;
113
+ const escaped = escapeRegExp(fromName);
114
+ const replacement = `$1${toName}`;
115
+ if (declarationKind === 'function') {
116
+ return text.replace(new RegExp(`^((?:export\\s+)?(?:async\\s+)?function\\*?\\s+)${escaped}\\b`), replacement);
117
+ }
118
+ if (declarationKind === 'class') {
119
+ return text.replace(new RegExp(`^((?:export\\s+)?(?:abstract\\s+)?class\\s+)${escaped}\\b`), replacement);
120
+ }
121
+ if (declarationKind === 'type') {
122
+ return text.replace(new RegExp(`^((?:export\\s+)?type\\s+)${escaped}\\b`), replacement);
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ function candidateSummary(candidate, reasonCodes) {
128
+ return {
129
+ fromName: candidate.fromName,
130
+ toName: candidate.toName,
131
+ declarationKind: candidate.declarationKind,
132
+ exported: candidate.fromEntry.declarationInfo?.exported === true || candidate.toEntry.declarationInfo?.exported === true,
133
+ reasonCodes: uniqueStrings(reasonCodes)
134
+ };
135
+ }
136
+
137
+ function entriesByKey(entries) {
138
+ return new Map(entries.map((entry) => [entry.key, entry]));
139
+ }
140
+
141
+ function sameStringList(left, right) {
142
+ return left.length === right.length && left.every((value, index) => value === right[index]);
143
+ }
144
+
145
+ function escapeRegExp(value) {
146
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
147
+ }
148
+
149
+ export { analyzeTopLevelRenameAdmission };
@@ -0,0 +1,44 @@
1
+ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
2
+ import { uniqueStrings } from './native-import-utils.js';
3
+
4
+ function topLevelRenameBlockedResult(input, topLevelResult, topLevelRenameAdmission) {
5
+ const reasonCodes = uniqueStrings([
6
+ ...(topLevelResult.admission?.reasonCodes ?? []),
7
+ ...(topLevelRenameAdmission.reasonCodes ?? [])
8
+ ]);
9
+ const conflict = {
10
+ code: JsTsSafeMergeConflictCodes.topLevelRenamePublicExportContract,
11
+ gateId: JsTsSafeMergeGateIds.stableExistingDeclarations,
12
+ message: 'Top-level rename changes a public export contract without project-level evidence.',
13
+ side: 'worker',
14
+ sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
15
+ details: {
16
+ ...topLevelRenameAdmission.summary,
17
+ reasonCodes: topLevelRenameAdmission.reasonCodes
18
+ }
19
+ };
20
+ return {
21
+ ...topLevelResult,
22
+ conflicts: [...(topLevelResult.conflicts ?? []), conflict],
23
+ admission: {
24
+ status: 'blocked',
25
+ action: 'human-review',
26
+ reviewRequired: true,
27
+ autoApplyCandidate: false,
28
+ autoMergeClaim: false,
29
+ semanticEquivalenceClaim: false,
30
+ reasonCodes
31
+ },
32
+ summary: {
33
+ ...topLevelResult.summary,
34
+ conflicts: (topLevelResult.conflicts?.length ?? 0) + 1,
35
+ topLevelDeclarationRenames: 1
36
+ },
37
+ metadata: {
38
+ ...topLevelResult.metadata,
39
+ topLevelRenameAdmission: topLevelRenameAdmission.summary
40
+ }
41
+ };
42
+ }
43
+
44
+ export { topLevelRenameBlockedResult };
@@ -6,6 +6,7 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
6
6
  const limitConflicts = Array.isArray(projectSymbolGraph?.limitConflicts) ? projectSymbolGraph.limitConflicts : [];
7
7
  projectSymbolGraph = projectSymbolGraph?.projectSymbolGraph ?? projectSymbolGraph;
8
8
  const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
9
+ const exportEdges = Array.isArray(projectSymbolGraph?.exportEdges) ? projectSymbolGraph.exportEdges : [];
9
10
  const missingModuleGroups = new Map();
10
11
  const missingSymbolGroups = new Map();
11
12
  for (const edge of importEdges) {
@@ -16,7 +17,7 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
16
17
  missingModuleGroups.set(key, group);
17
18
  continue;
18
19
  }
19
- if (isMissingProjectImportTargetEdge(edge)) {
20
+ if (isMissingProjectImportTargetEdge(edge, exportEdges)) {
20
21
  const key = [edge.sourcePath, edge.moduleSpecifier, projectImportTargetName(edge), edge.resolvedModulePath].join('\u0000');
21
22
  const group = missingSymbolGroups.get(key) ?? [];
22
23
  group.push(edge);
@@ -81,8 +82,11 @@ function isMissingProjectImportEdge(edge) {
81
82
  return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
82
83
  }
83
84
 
84
- function isMissingProjectImportTargetEdge(edge) {
85
- return hasResolvedProjectModule(edge) && Boolean(projectImportTargetName(edge)) && !edge.resolvedTargetSymbolId;
85
+ function isMissingProjectImportTargetEdge(edge, exportEdges = []) {
86
+ return hasResolvedProjectModule(edge)
87
+ && Boolean(projectImportTargetName(edge))
88
+ && !edge.resolvedTargetSymbolId
89
+ && !commonJsRequireResolvedByExportAssignment(edge, exportEdges);
86
90
  }
87
91
 
88
92
  function hasResolvedProjectModule(edge) {
@@ -96,6 +100,23 @@ function projectImportTargetName(edge) {
96
100
  return String(name);
97
101
  }
98
102
 
103
+ function commonJsRequireResolvedByExportAssignment(edge, exportEdges) {
104
+ if (edge?.importKind !== 'commonjs-require' || projectImportTargetName(edge) !== 'default') return false;
105
+ return exportEdges.some((exportEdge) => exportEdge?.exportKind === 'assignment'
106
+ && exportEdge.exportedName === 'module.exports'
107
+ && sameProjectDocument(edge, exportEdge));
108
+ }
109
+
110
+ function sameProjectDocument(importEdge, exportEdge) {
111
+ if (importEdge?.targetDocumentId && exportEdge?.sourceDocumentId) {
112
+ return importEdge.targetDocumentId === exportEdge.sourceDocumentId;
113
+ }
114
+ if (importEdge?.resolvedModulePath && exportEdge?.sourcePath) {
115
+ return importEdge.resolvedModulePath === exportEdge.sourcePath;
116
+ }
117
+ return false;
118
+ }
119
+
99
120
  function duplicateReExportIdentityConflicts(records = []) {
100
121
  const groups = new Map();
101
122
  for (const record of records) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.148",
3
+ "version": "0.2.149",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",