@shapeshift-labs/frontier-lang-compiler 0.2.143 → 0.2.144

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.
@@ -0,0 +1,225 @@
1
+ import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
2
+ import { createJsTsSafeMergeSemanticArtifacts } from './js-ts-safe-merge-semantic-artifacts.js';
3
+ import { uniqueStrings } from './js-ts-safe-merge-context.js';
4
+ import { semanticFallbackChangedExistingDeclarations } from './js-ts-safe-merge-semantic-edit-fallback-utils.js';
5
+ import {
6
+ enumDeclarationsByName,
7
+ sameEnumMemberText,
8
+ sameEnumShell
9
+ } from './js-ts-safe-merge-enum-member-parser.js';
10
+
11
+ function createEnumMemberSemanticFallbackResult(input, topLevelResult, stagedFallback) {
12
+ const currentSourceText = fallbackCurrentSourceText(input, stagedFallback);
13
+ const merge = mergeEnumMemberSources({
14
+ baseSourceText: input.baseSourceText,
15
+ workerSourceText: input.workerSourceText,
16
+ headSourceText: input.headSourceText,
17
+ currentSourceText
18
+ });
19
+ if (!merge.ok || merge.sourceText === currentSourceText) return undefined;
20
+ const resultBase = stagedFallback?.stagedTopLevelResult ?? topLevelResult;
21
+ const language = input.language ?? topLevelResult.language ?? 'typescript';
22
+ const sourcePath = input.sourcePath ?? topLevelResult.sourcePath ?? 'inline.ts';
23
+ const phase = stagedFallback
24
+ ? 'staged-top-level-enum-member-semantic-fallback'
25
+ : 'enum-member-semantic-fallback';
26
+ const artifacts = createJsTsSafeMergeSemanticArtifacts({
27
+ ...input,
28
+ id: `${String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge')}_enum_member`,
29
+ language,
30
+ sourcePath,
31
+ headSourceText: currentSourceText,
32
+ headHash: undefined,
33
+ currentSourceHash: undefined
34
+ }, {
35
+ ...resultBase,
36
+ id: `${String(input.id ?? resultBase.id ?? 'js_ts_safe_merge')}_enum_member`,
37
+ language,
38
+ sourcePath,
39
+ mergedSourceText: merge.sourceText,
40
+ outputSourceText: merge.sourceText
41
+ });
42
+ if (artifacts.status !== 'verified') return undefined;
43
+ const gates = semanticArtifactGates(artifacts);
44
+ return {
45
+ ...resultBase,
46
+ id: String(input.id ?? resultBase.id ?? topLevelResult.id),
47
+ status: JsTsSafeMergeStatuses.merged,
48
+ mergedSourceText: merge.sourceText,
49
+ outputSourceText: merge.sourceText,
50
+ conflicts: [],
51
+ gates,
52
+ admission: {
53
+ status: 'auto-merge-candidate',
54
+ action: 'apply',
55
+ reviewRequired: false,
56
+ autoApplyCandidate: true,
57
+ autoMergeClaim: false,
58
+ semanticEquivalenceClaim: false,
59
+ reasonCodes: []
60
+ },
61
+ summary: {
62
+ ...resultBase.summary,
63
+ changedExistingDeclarations: semanticFallbackChangedExistingDeclarations(topLevelResult, resultBase, stagedFallback),
64
+ conflicts: 0,
65
+ gatesPassed: gates.filter((gate) => gate.status === 'passed').length,
66
+ semanticEditOperations: artifacts.script.summary.operations,
67
+ semanticEditAppliedOperations: artifacts.replay.summary.applied,
68
+ semanticEditReplayStatus: artifacts.replay.status,
69
+ enumMemberDeclarations: merge.summary.declarations,
70
+ enumMemberEdits: merge.summary.edits,
71
+ enumMemberAdditions: merge.summary.additions,
72
+ composedPhases: 2
73
+ },
74
+ metadata: {
75
+ ...resultBase.metadata,
76
+ composed: {
77
+ phase,
78
+ phases: stagedFallback
79
+ ? ['top-level-neutralization', 'top-level-ledger', 'enum-member']
80
+ : ['top-level-ledger', 'enum-member'],
81
+ originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
82
+ stagedTopLevelSummary: stagedFallback?.stagedTopLevelResult?.summary,
83
+ neutralization: stagedFallback?.neutralization?.summary,
84
+ enumMemberFallback: merge.summary
85
+ }
86
+ },
87
+ semanticArtifacts: artifacts
88
+ };
89
+ }
90
+
91
+ function fallbackCurrentSourceText(input, stagedFallback) {
92
+ return stagedFallback?.directReplayCurrentSourceText
93
+ ?? stagedFallback?.replayCurrentSourceText
94
+ ?? input.headSourceText;
95
+ }
96
+
97
+ function mergeEnumMemberSources(input) {
98
+ if (![input.baseSourceText, input.workerSourceText, input.headSourceText, input.currentSourceText].every(isString)) {
99
+ return blocked('missing-source-text');
100
+ }
101
+ const base = enumDeclarationsByName(input.baseSourceText);
102
+ const worker = enumDeclarationsByName(input.workerSourceText);
103
+ const head = enumDeclarationsByName(input.headSourceText);
104
+ const current = enumDeclarationsByName(input.currentSourceText);
105
+ if ([base, worker, head, current].some((source) => source.reasonCodes.length)) {
106
+ return blocked('enum-member-parse-blocked');
107
+ }
108
+ const edits = [];
109
+ const summary = { declarations: 0, edits: 0, additions: 0 };
110
+ for (const declaration of base.declarations) {
111
+ const workerDeclaration = worker.byName.get(declaration.name);
112
+ const headDeclaration = head.byName.get(declaration.name);
113
+ const currentDeclaration = current.byName.get(declaration.name);
114
+ if (!workerDeclaration || !headDeclaration || !currentDeclaration) continue;
115
+ const merged = mergeEnumDeclaration(declaration, workerDeclaration, headDeclaration, currentDeclaration);
116
+ if (merged.status === 'blocked') return blocked(...merged.reasonCodes);
117
+ if (!merged.replacement || sameEnumMemberText(merged.replacement, currentDeclaration.text)) continue;
118
+ edits.push({ start: currentDeclaration.start, end: currentDeclaration.end, replacement: merged.replacement });
119
+ summary.declarations += 1;
120
+ summary.edits += 1;
121
+ summary.additions += merged.additions;
122
+ }
123
+ if (!edits.length) return blocked('no-enum-member-merge-candidate');
124
+ const sourceText = edits.sort((left, right) => right.start - left.start)
125
+ .reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), input.currentSourceText);
126
+ return { ok: true, sourceText, summary };
127
+ }
128
+
129
+ function mergeEnumDeclaration(base, worker, head, current) {
130
+ if (!sameEnumShell(base, worker) || !sameEnumShell(base, head) || !sameEnumShell(base, current)) {
131
+ return blockedDeclaration('enum-member-shell-changed');
132
+ }
133
+ const baseNames = base.members.map((member) => member.name);
134
+ if (![worker, head, current].every((declaration) => containsBaseMembersInOrder(declaration, baseNames))) {
135
+ return blockedDeclaration('enum-member-base-order-changed');
136
+ }
137
+ const memberMaps = [base, worker, head, current].map(membersByName);
138
+ if (memberMaps.some((map) => map.reasonCodes.length)) return blockedDeclaration('duplicate-enum-member-name');
139
+ const [baseMembers, workerMembers, headMembers, currentMembers] = memberMaps.map((map) => map.byName);
140
+ const mergedByName = new Map();
141
+ for (const name of baseNames) {
142
+ const merged = mergeExistingMember(name, baseMembers, workerMembers, headMembers, currentMembers);
143
+ if (merged.status === 'blocked') return merged;
144
+ mergedByName.set(name, merged.text);
145
+ }
146
+ const workerAdditions = new Map();
147
+ for (const member of worker.members.filter((entry) => !baseMembers.has(entry.name))) {
148
+ const currentMember = currentMembers.get(member.name);
149
+ if (currentMember && !sameEnumMemberText(currentMember.text, member.text)) {
150
+ return blockedDeclaration('enum-member-addition-conflict');
151
+ }
152
+ if (!currentMember) workerAdditions.set(member.name, member.text);
153
+ }
154
+ const outputMembers = [];
155
+ for (const member of current.members) outputMembers.push(mergedByName.get(member.name) ?? member.text);
156
+ for (const text of workerAdditions.values()) outputMembers.push(text);
157
+ const replacement = `${current.prefix}${outputMembers.join(',')}${current.tail}${current.suffix}`;
158
+ return {
159
+ status: 'merged',
160
+ replacement,
161
+ additions: workerAdditions.size
162
+ };
163
+ }
164
+
165
+ function mergeExistingMember(name, baseMembers, workerMembers, headMembers, currentMembers) {
166
+ const base = baseMembers.get(name);
167
+ const worker = workerMembers.get(name);
168
+ const head = headMembers.get(name);
169
+ const current = currentMembers.get(name);
170
+ if (!base || !worker || !head || !current) return blockedDeclaration('enum-member-removed');
171
+ const workerChanged = !sameEnumMemberText(base.text, worker.text);
172
+ const headChanged = !sameEnumMemberText(base.text, head.text);
173
+ if (workerChanged && headChanged && !sameEnumMemberText(worker.text, head.text)) {
174
+ return blockedDeclaration('enum-member-conflict');
175
+ }
176
+ if (!sameEnumMemberText(current.text, head.text) && !sameEnumMemberText(current.text, worker.text)) {
177
+ return blockedDeclaration('enum-member-current-diverged');
178
+ }
179
+ return { status: 'merged', text: workerChanged ? worker.text : current.text };
180
+ }
181
+
182
+ function containsBaseMembersInOrder(declaration, baseNames) {
183
+ let index = 0;
184
+ for (const member of declaration.members) {
185
+ if (member.name === baseNames[index]) index += 1;
186
+ }
187
+ return index === baseNames.length;
188
+ }
189
+
190
+ function membersByName(declaration) {
191
+ const byName = new Map();
192
+ const reasonCodes = [];
193
+ for (const member of declaration.members) {
194
+ if (byName.has(member.name)) reasonCodes.push('duplicate-enum-member-name');
195
+ byName.set(member.name, member);
196
+ }
197
+ return { byName, reasonCodes: uniqueStrings(reasonCodes) };
198
+ }
199
+
200
+ function semanticArtifactGates(artifacts) {
201
+ return [
202
+ gate('semantic-edit-script', artifacts.script?.admission?.status === 'auto-merge-candidate', artifacts.script?.admission?.reasonCodes),
203
+ gate('semantic-edit-projection', artifacts.projection?.status === 'projected', artifacts.projection?.admission?.reasonCodes),
204
+ gate('semantic-edit-replay', artifacts.replay?.status === 'accepted-clean', artifacts.replay?.admission?.reasonCodes),
205
+ gate('semantic-edit-already-applied', artifacts.alreadyAppliedReplay?.status === 'already-applied', artifacts.alreadyAppliedReplay?.admission?.reasonCodes)
206
+ ];
207
+ }
208
+
209
+ function gate(id, passed, reasonCodes = []) {
210
+ return { id, status: passed ? 'passed' : 'blocked', reasonCodes: passed ? [] : uniqueStrings(reasonCodes) };
211
+ }
212
+
213
+ function blocked(...reasonCodes) {
214
+ return { ok: false, reasonCodes: uniqueStrings(reasonCodes) };
215
+ }
216
+
217
+ function blockedDeclaration(...reasonCodes) {
218
+ return { status: 'blocked', reasonCodes: uniqueStrings(reasonCodes) };
219
+ }
220
+
221
+ function isString(value) {
222
+ return typeof value === 'string';
223
+ }
224
+
225
+ export { createEnumMemberSemanticFallbackResult };
@@ -0,0 +1,185 @@
1
+ import { normalizeLineEndings, uniqueStrings } from './js-ts-safe-merge-context.js';
2
+
3
+ function enumDeclarationsByName(sourceText) {
4
+ const reasonCodes = [];
5
+ const declarations = topLevelEnumDeclarations(sourceText)
6
+ .map((declaration) => parseEnumDeclaration(sourceText, declaration))
7
+ .filter(Boolean);
8
+ const byName = new Map();
9
+ for (const declaration of declarations) {
10
+ if (byName.has(declaration.name)) reasonCodes.push('duplicate-enum-declaration-name');
11
+ byName.set(declaration.name, declaration);
12
+ }
13
+ return { declarations, byName, reasonCodes: uniqueStrings(reasonCodes) };
14
+ }
15
+
16
+ function parseEnumDeclaration(sourceText, match) {
17
+ const header = sourceText.slice(match.start, match.bodyStart);
18
+ const nameMatch = /\benum\s+([A-Za-z_$][\w$]*)\s*$/u.exec(header);
19
+ if (!nameMatch) return undefined;
20
+ const body = sourceText.slice(match.bodyStart + 1, match.bodyEnd);
21
+ const members = splitEnumMembers(body, match.bodyStart + 1);
22
+ if (!members.length || members.some((member) => !member.name)) return undefined;
23
+ const tailStart = members.at(-1).end;
24
+ return {
25
+ name: nameMatch[1],
26
+ start: match.start,
27
+ end: match.end,
28
+ text: sourceText.slice(match.start, match.end),
29
+ prefix: sourceText.slice(match.start, match.bodyStart + 1),
30
+ tail: sourceText.slice(tailStart, match.bodyEnd),
31
+ suffix: sourceText.slice(match.bodyEnd, match.end),
32
+ members
33
+ };
34
+ }
35
+
36
+ function topLevelEnumDeclarations(sourceText) {
37
+ const declarations = [];
38
+ let state = 'code';
39
+ let depth = 0;
40
+ for (let index = 0; index < sourceText.length; index += 1) {
41
+ const nextIndex = advanceState(sourceText, index, state);
42
+ if (nextIndex.state !== state || nextIndex.index !== index) {
43
+ state = nextIndex.state;
44
+ index = nextIndex.index;
45
+ continue;
46
+ }
47
+ if (state !== 'code') continue;
48
+ const char = sourceText[index];
49
+ if (char === '(' || char === '[' || char === '{') depth += 1;
50
+ else if (char === ')' || char === ']' || char === '}') depth -= 1;
51
+ if (depth !== 0 || !wordAt(sourceText, index, 'enum')) continue;
52
+ const bodyStart = sourceText.indexOf('{', index);
53
+ if (bodyStart === -1) continue;
54
+ const bodyEnd = matchingBrace(sourceText, bodyStart);
55
+ if (bodyEnd === -1) continue;
56
+ declarations.push({
57
+ start: enumDeclarationStart(sourceText, index),
58
+ bodyStart,
59
+ bodyEnd,
60
+ end: enumDeclarationEnd(sourceText, bodyEnd + 1)
61
+ });
62
+ index = bodyEnd;
63
+ }
64
+ return declarations;
65
+ }
66
+
67
+ function splitEnumMembers(body, offset) {
68
+ const members = [];
69
+ let start = 0;
70
+ let state = 'code';
71
+ let depth = 0;
72
+ for (let index = 0; index < body.length; index += 1) {
73
+ const nextIndex = advanceState(body, index, state);
74
+ if (nextIndex.state !== state || nextIndex.index !== index) {
75
+ state = nextIndex.state;
76
+ index = nextIndex.index;
77
+ continue;
78
+ }
79
+ if (state !== 'code') continue;
80
+ const char = body[index];
81
+ if (char === '(' || char === '[' || char === '{') depth += 1;
82
+ else if (char === ')' || char === ']' || char === '}') depth -= 1;
83
+ else if (char === ',' && depth === 0) {
84
+ pushEnumMember(members, body, start, index, offset);
85
+ start = index + 1;
86
+ }
87
+ if (depth < 0) return [];
88
+ }
89
+ if (state !== 'code' || depth !== 0) return [];
90
+ pushEnumMember(members, body, start, body.length, offset);
91
+ return members;
92
+ }
93
+
94
+ function pushEnumMember(members, body, start, end, offset) {
95
+ let localStart = start;
96
+ let localEnd = end;
97
+ while (localStart < localEnd && /\s/u.test(body[localStart])) localStart += 1;
98
+ while (localEnd > localStart && /\s/u.test(body[localEnd - 1])) localEnd -= 1;
99
+ if (localStart === localEnd) return;
100
+ const text = body.slice(start, localEnd);
101
+ const trimmed = body.slice(localStart, localEnd);
102
+ const name = enumMemberName(trimmed);
103
+ members.push({ name, text, start: offset + start, end: offset + localEnd });
104
+ }
105
+
106
+ function enumMemberName(text) {
107
+ const identifier = /^[A-Za-z_$][\w$]*/u.exec(text)?.[0];
108
+ if (identifier) return identifier;
109
+ const quoted = /^(["'])(.*?)\1/u.exec(text);
110
+ return quoted ? quoted[2] : undefined;
111
+ }
112
+
113
+ function matchingBrace(sourceText, bodyStart) {
114
+ let state = 'code';
115
+ let depth = 0;
116
+ for (let index = bodyStart; index < sourceText.length; index += 1) {
117
+ const nextIndex = advanceState(sourceText, index, state);
118
+ if (nextIndex.state !== state || nextIndex.index !== index) {
119
+ state = nextIndex.state;
120
+ index = nextIndex.index;
121
+ continue;
122
+ }
123
+ if (state !== 'code') continue;
124
+ if (sourceText[index] === '{') depth += 1;
125
+ else if (sourceText[index] === '}') {
126
+ depth -= 1;
127
+ if (depth === 0) return index;
128
+ }
129
+ }
130
+ return -1;
131
+ }
132
+
133
+ function enumDeclarationStart(sourceText, enumOffset) {
134
+ let start = sourceText.lastIndexOf('\n', enumOffset) + 1;
135
+ const leading = sourceText.slice(start, enumOffset);
136
+ if (/\b(?:export|declare|const)\s+$/u.test(leading)) return start;
137
+ const exportIndex = leading.search(/\bexport\b/u);
138
+ return exportIndex === -1 ? enumOffset : start + exportIndex;
139
+ }
140
+
141
+ function enumDeclarationEnd(sourceText, bodyCloseEnd) {
142
+ let end = bodyCloseEnd;
143
+ while (end < sourceText.length && /[ \t]/u.test(sourceText[end])) end += 1;
144
+ if (sourceText[end] === ';') end += 1;
145
+ if (sourceText[end] === '\r' && sourceText[end + 1] === '\n') return end + 2;
146
+ if (sourceText[end] === '\n') return end + 1;
147
+ return end;
148
+ }
149
+
150
+ function advanceState(sourceText, index, state) {
151
+ const char = sourceText[index];
152
+ const next = sourceText[index + 1];
153
+ if (state === 'line-comment') return char === '\n' ? { state: 'code', index } : { state, index };
154
+ if (state === 'block-comment') {
155
+ return char === '*' && next === '/' ? { state: 'code', index: index + 1 } : { state, index };
156
+ }
157
+ if (state !== 'code') {
158
+ if (char === '\\') return { state, index: index + 1 };
159
+ return char === state ? { state: 'code', index } : { state, index };
160
+ }
161
+ if (char === '/' && next === '/') return { state: 'line-comment', index: index + 1 };
162
+ if (char === '/' && next === '*') return { state: 'block-comment', index: index + 1 };
163
+ if (char === '"' || char === "'" || char === '`') return { state: char, index };
164
+ return { state, index };
165
+ }
166
+
167
+ function wordAt(sourceText, index, word) {
168
+ return sourceText.slice(index, index + word.length) === word
169
+ && !/[A-Za-z0-9_$]/u.test(sourceText[index - 1] ?? '')
170
+ && !/[A-Za-z0-9_$]/u.test(sourceText[index + word.length] ?? '');
171
+ }
172
+
173
+ function sameEnumMemberText(left, right) {
174
+ return normalizeLineEndings(String(left ?? '').trim(), '\n') === normalizeLineEndings(String(right ?? '').trim(), '\n');
175
+ }
176
+
177
+ function sameEnumShell(left, right) {
178
+ return sameEnumMemberText(left.prefix, right.prefix) && sameEnumMemberText(left.suffix, right.suffix);
179
+ }
180
+
181
+ export {
182
+ enumDeclarationsByName,
183
+ sameEnumMemberText,
184
+ sameEnumShell
185
+ };
@@ -174,6 +174,7 @@ function entryName(entry) {
174
174
 
175
175
  function entrySymbolKind(entry) {
176
176
  if (entry.kind === 'import') return 'import';
177
+ if (entry.declarationInfo?.declarationKind === 'enum') return 'type';
177
178
  return entry.declarationInfo?.declarationKind ?? entry.kind;
178
179
  }
179
180
 
@@ -15,7 +15,7 @@ import {
15
15
  createStagedDeclarationReplayRecord
16
16
  } from './js-ts-safe-merge-staged-declaration-replay.js';
17
17
  import { createStagedTopLevelSemanticFallback } from './js-ts-safe-merge-staged-top-level-fallback.js';
18
- import { createVariableDeclaratorSemanticFallbackResult } from './js-ts-safe-merge-variable-declarator-fallback.js';
18
+ import { createSourceShapeSemanticFallbackResult } from './js-ts-safe-merge-source-shape-fallbacks.js';
19
19
  import { idFragment, uniqueStrings } from './native-import-utils.js';
20
20
 
21
21
  function semanticEditFallbackResult(input, topLevelResult) {
@@ -31,8 +31,8 @@ function semanticEditFallbackResult(input, topLevelResult) {
31
31
  artifacts = nextArtifacts.status === 'verified' ? nextArtifacts : artifacts;
32
32
  }
33
33
  if (artifacts.status !== 'verified') {
34
- const variableDeclaratorResult = createVariableDeclaratorSemanticFallbackResult(input, topLevelResult, stagedFallback);
35
- if (variableDeclaratorResult) return variableDeclaratorResult;
34
+ const sourceShapeResult = createSourceShapeSemanticFallbackResult(input, topLevelResult, stagedFallback);
35
+ if (sourceShapeResult) return sourceShapeResult;
36
36
  return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
37
37
  }
38
38
  const resultBase = selectedFallback?.stagedTopLevelResult ?? topLevelResult;
@@ -0,0 +1,9 @@
1
+ import { createEnumMemberSemanticFallbackResult } from './js-ts-safe-merge-enum-member-fallback.js';
2
+ import { createVariableDeclaratorSemanticFallbackResult } from './js-ts-safe-merge-variable-declarator-fallback.js';
3
+
4
+ function createSourceShapeSemanticFallbackResult(input, topLevelResult, stagedFallback) {
5
+ return createVariableDeclaratorSemanticFallbackResult(input, topLevelResult, stagedFallback)
6
+ ?? createEnumMemberSemanticFallbackResult(input, topLevelResult, stagedFallback);
7
+ }
8
+
9
+ export { createSourceShapeSemanticFallbackResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.143",
3
+ "version": "0.2.144",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",