@shapeshift-labs/frontier-lang-compiler 0.2.103 → 0.2.105
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 +14 -0
- package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
- package/dist/declarations/bidirectional-target-change.d.ts +10 -0
- package/dist/declarations/js-ts-safe-member-merge.d.ts +86 -0
- package/dist/declarations/js-ts-safe-merge.d.ts +131 -0
- package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
- package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
- package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
- package/dist/declarations/native-import-losses.d.ts +3 -0
- package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
- package/dist/declarations/semantic-edit-script.d.ts +7 -4
- package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
- package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
- package/dist/declarations/semantic-transform-identity.d.ts +3 -0
- package/dist/declarations/source-preservation.d.ts +72 -0
- package/dist/declarations/universal-capability.d.ts +4 -0
- package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
- package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
- package/dist/declarations/universal-conversion-plan.d.ts +6 -1
- package/dist/declarations/universal-representation-coverage.d.ts +90 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
- package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
- package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
- package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
- package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
- package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
- package/dist/internal/index-impl/diffNativeSymbols.js +3 -3
- package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +43 -8
- package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
- package/dist/internal/index-impl/replaySemanticEditProjection.js +39 -19
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
- package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
- package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
- package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
- package/dist/internal/index-impl/semanticEditProjectionRecord.js +29 -0
- package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
- package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
- package/dist/internal/index-impl/semanticEditScripts.js +4 -0
- package/dist/internal/index-impl/semanticEditSourceRanges.js +27 -0
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +41 -7
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
- package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
- package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
- package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
- package/dist/js-ts-safe-member-merge-result.js +158 -0
- package/dist/js-ts-safe-member-merge.js +265 -0
- package/dist/js-ts-safe-merge-analyze.js +279 -0
- package/dist/js-ts-safe-merge-composed.js +170 -0
- package/dist/js-ts-safe-merge-constants.js +50 -0
- package/dist/js-ts-safe-merge-context.js +118 -0
- package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
- package/dist/js-ts-safe-merge-ledger.js +85 -0
- package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
- package/dist/js-ts-safe-merge-parse-statements.js +155 -0
- package/dist/js-ts-safe-merge-plan.js +190 -0
- package/dist/js-ts-safe-merge.js +175 -0
- package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
- package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
- package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
- package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
- package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
- package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
- package/dist/js-ts-semantic-merge-contracts.js +217 -0
- package/dist/js-ts-semantic-merge-member-containers.js +100 -0
- package/dist/js-ts-semantic-merge-member-keys.js +142 -0
- package/dist/js-ts-semantic-merge-member-segments.js +185 -0
- package/dist/js-ts-semantic-merge-member-source.js +82 -0
- package/dist/js-ts-semantic-merge-member-utils.js +18 -0
- package/dist/js-ts-semantic-merge-parse.js +16 -0
- package/dist/js-ts-semantic-merge.js +24 -0
- package/dist/lightweight-dependency-effects.js +51 -0
- package/dist/lightweight-dependency-language.js +12 -1
- package/dist/lightweight-dependency-relations.js +14 -27
- package/dist/native-region-scanner-core.js +33 -1
- package/dist/native-region-scanner-csharp.js +151 -0
- package/dist/native-region-scanner-dart.js +91 -0
- package/dist/native-region-scanner-dynamic.js +21 -151
- package/dist/native-region-scanner-functional.js +40 -13
- package/dist/native-region-scanner-java.js +97 -0
- package/dist/native-region-scanner-js-class.js +100 -0
- package/dist/native-region-scanner-js-helpers.js +28 -86
- package/dist/native-region-scanner-js-imports.js +121 -1
- package/dist/native-region-scanner-js-nested.js +96 -8
- package/dist/native-region-scanner-js-structure.js +27 -0
- package/dist/native-region-scanner-js-types.js +99 -0
- package/dist/native-region-scanner-js.js +70 -118
- package/dist/native-region-scanner-kotlin.js +94 -0
- package/dist/native-region-scanner-main.js +15 -181
- package/dist/native-region-scanner-php.js +80 -0
- package/dist/native-region-scanner-python.js +62 -0
- package/dist/native-region-scanner-ruby.js +72 -0
- package/dist/native-region-scanner-scala.js +91 -0
- package/dist/native-region-scanner-spans.js +74 -0
- package/dist/native-region-scanner-swift.js +155 -0
- package/dist/native-region-scanner.js +14 -10
- package/dist/native-source-ledger-helpers.js +195 -0
- package/dist/native-source-ledger.js +306 -0
- package/dist/native-source-preservation-scanner.js +4 -0
- package/dist/semantic-import-callsite-regions.js +136 -0
- package/dist/semantic-import-effect-regions.js +283 -0
- package/dist/semantic-import-regions.js +11 -2
- package/dist/semantic-import-sidecar-entry.js +16 -2
- package/dist/semantic-import-sidecar-types.d.ts +2 -0
- package/dist/semantic-sidecar-example.js +68 -0
- package/dist/universal-capability-matrix.js +23 -0
- package/dist/universal-conversion-artifact-query.js +79 -2
- package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
- package/dist/universal-conversion-artifact-summary.js +33 -1
- package/dist/universal-conversion-artifacts.js +13 -48
- package/dist/universal-conversion-plan-scoring.js +21 -1
- package/dist/universal-conversion-plan-summary.js +30 -0
- package/dist/universal-conversion-plan.js +25 -9
- package/dist/universal-conversion-route-metadata.js +96 -0
- package/dist/universal-conversion-route-operations.js +7 -0
- package/dist/universal-representation-coverage.js +193 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, identifierRegExp } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
|
|
3
|
+
export function classifyStatement(text, start, end) {
|
|
4
|
+
const importInfo = parseImportInfo(text);
|
|
5
|
+
if (importInfo) {
|
|
6
|
+
return {
|
|
7
|
+
kind: 'import',
|
|
8
|
+
key: importLedgerKey(importInfo),
|
|
9
|
+
text,
|
|
10
|
+
start,
|
|
11
|
+
end,
|
|
12
|
+
importInfo,
|
|
13
|
+
names: importInfo.specifiers.map((specifier) => specifier.localName).filter(Boolean)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const declarationInfo = parseDeclarationInfo(text);
|
|
17
|
+
if (declarationInfo) {
|
|
18
|
+
const unsupported = unsupportedDeclarationPolicy(text, declarationInfo);
|
|
19
|
+
if (unsupported) return { unsupported, text, start, end };
|
|
20
|
+
return {
|
|
21
|
+
kind: declarationInfo.kind,
|
|
22
|
+
key: `${declarationInfo.kind}:${declarationInfo.names.join('|')}`,
|
|
23
|
+
text,
|
|
24
|
+
start,
|
|
25
|
+
end,
|
|
26
|
+
declarationInfo,
|
|
27
|
+
names: declarationInfo.names
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseImportInfo(text) {
|
|
34
|
+
const trimmed = text.trim();
|
|
35
|
+
const sideEffect = trimmed.match(/^import\s+(['"])([^'"]+)\1\s*;?$/s);
|
|
36
|
+
if (sideEffect) {
|
|
37
|
+
return {
|
|
38
|
+
moduleSpecifier: sideEffect[2],
|
|
39
|
+
quote: sideEffect[1],
|
|
40
|
+
typeOnly: false,
|
|
41
|
+
sideEffectOnly: true,
|
|
42
|
+
defaultLocalName: undefined,
|
|
43
|
+
namespaceLocalName: undefined,
|
|
44
|
+
specifiers: []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const match = trimmed.match(/^import\s+(type\s+)?([\s\S]+?)\s+from\s+(['"])([^'"]+)\3\s*;?$/s);
|
|
49
|
+
if (!match) return undefined;
|
|
50
|
+
const typeOnly = Boolean(match[1]);
|
|
51
|
+
const clause = match[2].trim();
|
|
52
|
+
const parsedClause = parseImportClause(clause, { typeOnly });
|
|
53
|
+
if (!parsedClause) return undefined;
|
|
54
|
+
return {
|
|
55
|
+
moduleSpecifier: match[4],
|
|
56
|
+
quote: match[3],
|
|
57
|
+
typeOnly,
|
|
58
|
+
sideEffectOnly: false,
|
|
59
|
+
...parsedClause
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseImportClause(clause, options) {
|
|
64
|
+
const namespace = clause.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
|
|
65
|
+
if (namespace) {
|
|
66
|
+
return {
|
|
67
|
+
defaultLocalName: undefined,
|
|
68
|
+
namespaceLocalName: namespace[1],
|
|
69
|
+
specifiers: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const named = clause.match(/\{([\s\S]*)\}/);
|
|
74
|
+
if (!named) {
|
|
75
|
+
if (!identifierRegExp.test(clause)) return undefined;
|
|
76
|
+
return {
|
|
77
|
+
defaultLocalName: clause,
|
|
78
|
+
namespaceLocalName: undefined,
|
|
79
|
+
specifiers: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const beforeNamed = clause.slice(0, named.index).trim();
|
|
84
|
+
const afterNamed = clause.slice(named.index + named[0].length).trim();
|
|
85
|
+
if (afterNamed) return undefined;
|
|
86
|
+
let defaultLocalName;
|
|
87
|
+
if (beforeNamed) {
|
|
88
|
+
const defaultText = beforeNamed.replace(/,$/, '').trim();
|
|
89
|
+
if (!identifierRegExp.test(defaultText)) return undefined;
|
|
90
|
+
defaultLocalName = defaultText;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const specifiers = splitCommaList(named[1]).map((part) => parseImportSpecifier(part, options)).filter(Boolean);
|
|
94
|
+
if (splitCommaList(named[1]).filter((part) => part.trim()).length !== specifiers.length) return undefined;
|
|
95
|
+
return {
|
|
96
|
+
defaultLocalName,
|
|
97
|
+
namespaceLocalName: undefined,
|
|
98
|
+
specifiers
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseImportSpecifier(raw, options) {
|
|
103
|
+
let text = String(raw ?? '').trim();
|
|
104
|
+
if (!text) return undefined;
|
|
105
|
+
let typeOnly = Boolean(options.typeOnly);
|
|
106
|
+
if (text.startsWith('type ')) {
|
|
107
|
+
typeOnly = true;
|
|
108
|
+
text = text.slice(5).trim();
|
|
109
|
+
}
|
|
110
|
+
const match = text.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
|
|
111
|
+
if (!match) return undefined;
|
|
112
|
+
const importedName = match[1];
|
|
113
|
+
const localName = match[2] ?? importedName;
|
|
114
|
+
return {
|
|
115
|
+
importedName,
|
|
116
|
+
localName,
|
|
117
|
+
typeOnly,
|
|
118
|
+
canonical: importSpecifierCanonical({ importedName, localName, typeOnly })
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseDeclarationInfo(text) {
|
|
123
|
+
const trimmed = text.trim();
|
|
124
|
+
const defaultFunction = trimmed.match(/^export\s+default\s+(?:async\s+)?function\*?(?:\s+([A-Za-z_$][\w$]*))?\b/);
|
|
125
|
+
if (defaultFunction) return { kind: 'declaration', names: ['default'], declarationKind: 'function', exported: true, defaultExport: true };
|
|
126
|
+
const defaultClass = trimmed.match(/^export\s+default\s+(?:abstract\s+)?class(?:\s+([A-Za-z_$][\w$]*))?\b/);
|
|
127
|
+
if (defaultClass) return { kind: 'declaration', names: ['default'], declarationKind: 'class', exported: true, defaultExport: true };
|
|
128
|
+
|
|
129
|
+
const namedExport = trimmed.match(/^export\s+(type\s+)?\{([\s\S]+)\}\s*;?$/);
|
|
130
|
+
if (namedExport) {
|
|
131
|
+
const names = splitCommaList(namedExport[2]).map((part) => parseExportSpecifierName(part)).filter(Boolean);
|
|
132
|
+
const expectedCount = splitCommaList(namedExport[2]).filter((part) => part.trim()).length;
|
|
133
|
+
if (names.length !== expectedCount || names.length === 0) return undefined;
|
|
134
|
+
return { kind: 'export', names, declarationKind: 'export-list', exported: true, typeOnly: Boolean(namedExport[1]) };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const source = trimmed
|
|
138
|
+
.replace(/^export\s+/, '')
|
|
139
|
+
.replace(/^declare\s+/, '');
|
|
140
|
+
const functionMatch = source.match(/^(?:async\s+)?function\*?\s+([A-Za-z_$][\w$]*)\b/);
|
|
141
|
+
if (functionMatch) return { kind: 'declaration', names: [functionMatch[1]], declarationKind: 'function', exported: trimmed.startsWith('export ') };
|
|
142
|
+
const classMatch = source.match(/^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)\b/);
|
|
143
|
+
if (classMatch) return { kind: 'declaration', names: [classMatch[1]], declarationKind: 'class', exported: trimmed.startsWith('export ') };
|
|
144
|
+
const interfaceMatch = source.match(/^interface\s+([A-Za-z_$][\w$]*)\b/);
|
|
145
|
+
if (interfaceMatch) return { kind: 'declaration', names: [interfaceMatch[1]], declarationKind: 'interface', exported: trimmed.startsWith('export ') };
|
|
146
|
+
const typeMatch = source.match(/^type\s+([A-Za-z_$][\w$]*)\b/);
|
|
147
|
+
if (typeMatch) return { kind: 'declaration', names: [typeMatch[1]], declarationKind: 'type', exported: trimmed.startsWith('export ') };
|
|
148
|
+
const enumMatch = source.match(/^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)\b/);
|
|
149
|
+
if (enumMatch) return { kind: 'declaration', names: [enumMatch[1]], declarationKind: 'enum', exported: trimmed.startsWith('export ') };
|
|
150
|
+
const moduleMatch = source.match(/^(?:namespace|module)\s+([A-Za-z_$][\w$.]*)\b/);
|
|
151
|
+
if (moduleMatch) return { kind: 'declaration', names: [moduleMatch[1]], declarationKind: 'module', exported: trimmed.startsWith('export ') };
|
|
152
|
+
const variableMatch = source.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/);
|
|
153
|
+
if (variableMatch) return { kind: 'declaration', names: [variableMatch[1]], declarationKind: 'variable', exported: trimmed.startsWith('export ') };
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function unsupportedDeclarationPolicy(text, declarationInfo) {
|
|
158
|
+
const trimmed = text.trim();
|
|
159
|
+
if (/^\s*@/m.test(text)) {
|
|
160
|
+
return {
|
|
161
|
+
code: JsTsSafeMergeConflictCodes.unsupportedDecorator,
|
|
162
|
+
details: { reasonCode: 'unsupported-decorator-merge-anchor', declarationKind: declarationInfo.declarationKind }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (declarationInfo.declarationKind === 'function' && !trimmed.includes('{') && /;\s*$/.test(trimmed)) {
|
|
166
|
+
return {
|
|
167
|
+
code: JsTsSafeMergeConflictCodes.unsupportedOverload,
|
|
168
|
+
details: { reasonCode: 'unsupported-overload-merge-anchor', names: declarationInfo.names }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (hasComputedMemberKey(text)) {
|
|
172
|
+
return {
|
|
173
|
+
code: JsTsSafeMergeConflictCodes.computedKey,
|
|
174
|
+
details: { reasonCode: 'computed-key', declarationKind: declarationInfo.declarationKind, names: declarationInfo.names }
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function hasComputedMemberKey(text) {
|
|
181
|
+
return /(?:^|[\n{,;])\s*(?:readonly\s+)?\[[^\]\n]+\]\??\s*(?::|\()/m.test(text);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseExportSpecifierName(raw) {
|
|
185
|
+
let text = String(raw ?? '').trim();
|
|
186
|
+
if (!text) return undefined;
|
|
187
|
+
if (text.startsWith('type ')) text = text.slice(5).trim();
|
|
188
|
+
const match = text.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
|
|
189
|
+
if (!match) return undefined;
|
|
190
|
+
return match[2] ?? match[1];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function splitCommaList(raw) {
|
|
194
|
+
return String(raw ?? '').split(',');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function importLedgerKey(importInfo) {
|
|
198
|
+
if (importInfo.sideEffectOnly) return `import:side-effect:${importInfo.moduleSpecifier}`;
|
|
199
|
+
return [
|
|
200
|
+
'import:named',
|
|
201
|
+
importInfo.moduleSpecifier,
|
|
202
|
+
importInfo.typeOnly ? 'type' : 'value',
|
|
203
|
+
importInfo.defaultLocalName ?? '',
|
|
204
|
+
importInfo.namespaceLocalName ?? ''
|
|
205
|
+
].join(':');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function importSpecifierCanonical(specifier) {
|
|
209
|
+
return `${specifier.typeOnly ? 'type ' : ''}${specifier.importedName}${specifier.localName === specifier.importedName ? '' : ` as ${specifier.localName}`}`;
|
|
210
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export function skipTopLevelTrivia(sourceText, offset) {
|
|
2
|
+
let index = offset;
|
|
3
|
+
while (index < sourceText.length) {
|
|
4
|
+
const char = sourceText[index];
|
|
5
|
+
const next = sourceText[index + 1];
|
|
6
|
+
if (/\s/.test(char)) {
|
|
7
|
+
index += 1;
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (char === '/' && next === '/') {
|
|
11
|
+
index = consumeLineComment(sourceText, index + 2);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (char === '/' && next === '*') {
|
|
15
|
+
const end = sourceText.indexOf('*/', index + 2);
|
|
16
|
+
if (end === -1) return { offset: index, error: 'unterminated-block-comment' };
|
|
17
|
+
index = end + 2;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
return { offset: index };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function consumeLineComment(sourceText, offset) {
|
|
26
|
+
const lineEnd = sourceText.indexOf('\n', offset);
|
|
27
|
+
return lineEnd === -1 ? sourceText.length : lineEnd + 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function findStatementEnd(sourceText, start) {
|
|
31
|
+
const text = sourceText.slice(start);
|
|
32
|
+
const endOnBalancedBlock = statementCanEndOnBalancedBlock(text);
|
|
33
|
+
let braceDepth = 0;
|
|
34
|
+
let parenDepth = 0;
|
|
35
|
+
let bracketDepth = 0;
|
|
36
|
+
let openedBrace = false;
|
|
37
|
+
let quote;
|
|
38
|
+
let inTemplate = false;
|
|
39
|
+
let inLineComment = false;
|
|
40
|
+
let inBlockComment = false;
|
|
41
|
+
let escaped = false;
|
|
42
|
+
|
|
43
|
+
for (let index = start; index < sourceText.length; index += 1) {
|
|
44
|
+
const char = sourceText[index];
|
|
45
|
+
const next = sourceText[index + 1];
|
|
46
|
+
|
|
47
|
+
if (inLineComment) {
|
|
48
|
+
if (char === '\n') inLineComment = false;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inBlockComment) {
|
|
52
|
+
if (char === '*' && next === '/') {
|
|
53
|
+
inBlockComment = false;
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (quote) {
|
|
59
|
+
if (escaped) {
|
|
60
|
+
escaped = false;
|
|
61
|
+
} else if (char === '\\') {
|
|
62
|
+
escaped = true;
|
|
63
|
+
} else if (char === quote) {
|
|
64
|
+
quote = undefined;
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (inTemplate) {
|
|
69
|
+
if (escaped) {
|
|
70
|
+
escaped = false;
|
|
71
|
+
} else if (char === '\\') {
|
|
72
|
+
escaped = true;
|
|
73
|
+
} else if (char === '`') {
|
|
74
|
+
inTemplate = false;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (char === '/' && next === '/') {
|
|
80
|
+
inLineComment = true;
|
|
81
|
+
index += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (char === '/' && next === '*') {
|
|
85
|
+
inBlockComment = true;
|
|
86
|
+
index += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (char === '"' || char === "'") {
|
|
90
|
+
quote = char;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (char === '`') {
|
|
94
|
+
inTemplate = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (char === '(') {
|
|
98
|
+
parenDepth += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === ')') {
|
|
102
|
+
parenDepth -= 1;
|
|
103
|
+
if (parenDepth < 0) return { error: 'unbalanced-paren' };
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (char === '[') {
|
|
107
|
+
bracketDepth += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (char === ']') {
|
|
111
|
+
bracketDepth -= 1;
|
|
112
|
+
if (bracketDepth < 0) return { error: 'unbalanced-bracket' };
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (char === '{') {
|
|
116
|
+
braceDepth += 1;
|
|
117
|
+
openedBrace = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (char === '}') {
|
|
121
|
+
braceDepth -= 1;
|
|
122
|
+
if (braceDepth < 0) return { error: 'unbalanced-brace' };
|
|
123
|
+
if (endOnBalancedBlock && openedBrace && braceDepth === 0 && parenDepth === 0 && bracketDepth === 0) {
|
|
124
|
+
return { offset: consumeOptionalSameLineSemicolon(sourceText, index + 1) };
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (char === ';' && braceDepth === 0 && parenDepth === 0 && bracketDepth === 0) {
|
|
129
|
+
return { offset: index + 1 };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (quote || inTemplate || inBlockComment || braceDepth !== 0 || parenDepth !== 0 || bracketDepth !== 0) {
|
|
134
|
+
return { error: 'unterminated-statement' };
|
|
135
|
+
}
|
|
136
|
+
return { error: 'missing-top-level-statement-terminator' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function consumeOptionalSameLineSemicolon(sourceText, offset) {
|
|
140
|
+
let index = offset;
|
|
141
|
+
while (index < sourceText.length && (sourceText[index] === ' ' || sourceText[index] === '\t')) index += 1;
|
|
142
|
+
if (sourceText[index] === ';') return index + 1;
|
|
143
|
+
return offset;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function statementCanEndOnBalancedBlock(text) {
|
|
147
|
+
const trimmed = text.trimStart();
|
|
148
|
+
return new RegExp(`^(?:export\\s+)?(?:declare\\s+)?(?:async\\s+)?function\\*?\\b`).test(trimmed)
|
|
149
|
+
|| new RegExp(`^(?:export\\s+)?(?:declare\\s+)?(?:abstract\\s+)?class\\b`).test(trimmed)
|
|
150
|
+
|| new RegExp(`^(?:export\\s+)?(?:declare\\s+)?interface\\b`).test(trimmed)
|
|
151
|
+
|| new RegExp(`^(?:export\\s+)?(?:declare\\s+)?(?:const\\s+)?enum\\b`).test(trimmed)
|
|
152
|
+
|| new RegExp(`^(?:export\\s+)?(?:declare\\s+)?(?:namespace|module)\\b`).test(trimmed)
|
|
153
|
+
|| new RegExp(`^export\\s+default\\s+(?:async\\s+)?function\\*?\\b`).test(trimmed)
|
|
154
|
+
|| new RegExp(`^export\\s+default\\s+(?:abstract\\s+)?class\\b`).test(trimmed);
|
|
155
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { addConflict, detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
|
|
4
|
+
|
|
5
|
+
export function createSourceMergePlan(base, worker, head, workerPlan, headPlan, context) {
|
|
6
|
+
const edits = [];
|
|
7
|
+
let importSpecifierAdditions = 0;
|
|
8
|
+
for (const baseEntry of base.entries.filter((entry) => entry.kind === 'import')) {
|
|
9
|
+
const workerAdditions = workerPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
10
|
+
const headAdditions = headPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
11
|
+
if (!workerAdditions.length && !headAdditions.length) continue;
|
|
12
|
+
importSpecifierAdditions += workerAdditions.length + headAdditions.length;
|
|
13
|
+
const headEntry = head.entries.find((entry) => entry.key === baseEntry.key);
|
|
14
|
+
if (!headEntry) {
|
|
15
|
+
addConflict(context, {
|
|
16
|
+
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
17
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
18
|
+
side: 'head',
|
|
19
|
+
message: 'Head source is missing a base import anchor.',
|
|
20
|
+
details: { key: baseEntry.key }
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
edits.push({
|
|
25
|
+
kind: 'replace-import',
|
|
26
|
+
start: headEntry.start,
|
|
27
|
+
end: headEntry.end,
|
|
28
|
+
text: renderImportStatement(baseEntry.importInfo, mergedImportSpecifiers(baseEntry.importInfo.specifiers, workerAdditions, headAdditions))
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const insertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context);
|
|
33
|
+
const headInsertionGroups = variantInsertionGroups(head, headPlan.baseKeys, 'head', context);
|
|
34
|
+
const headInsertionGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
35
|
+
const headEntriesByKey = new Map(head.entries.map((entry) => [entry.key, entry]));
|
|
36
|
+
let topLevelDeclarationAdditions = 0;
|
|
37
|
+
for (const group of insertionGroups) {
|
|
38
|
+
const anchor = headEntriesByKey.get(group.anchorKey);
|
|
39
|
+
if (!anchor) {
|
|
40
|
+
addConflict(context, {
|
|
41
|
+
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
42
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
43
|
+
side: 'head',
|
|
44
|
+
message: 'Head source is missing a declaration insertion anchor.',
|
|
45
|
+
details: { anchorKey: group.anchorKey, mode: group.mode }
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const headGroup = headInsertionGroupsByAnchor.get(insertionGroupKey(group));
|
|
50
|
+
const insertionSpan = declarationInsertionSpan(head.sourceText, anchor, group.mode, headGroup);
|
|
51
|
+
const entries = mergedDeclarationEntries(group.entries, headGroup?.entries ?? []);
|
|
52
|
+
edits.push({
|
|
53
|
+
kind: 'insert-declarations',
|
|
54
|
+
start: insertionSpan.start,
|
|
55
|
+
end: insertionSpan.end,
|
|
56
|
+
text: declarationInsertionText(entries, detectLineEnding(head.sourceText))
|
|
57
|
+
});
|
|
58
|
+
topLevelDeclarationAdditions += group.entries.length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
edits,
|
|
63
|
+
importSpecifierAdditions,
|
|
64
|
+
topLevelDeclarationAdditions
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function variantInsertionGroups(variant, baseKeys, side, context) {
|
|
69
|
+
const groups = [];
|
|
70
|
+
let index = 0;
|
|
71
|
+
while (index < variant.entries.length) {
|
|
72
|
+
const entry = variant.entries[index];
|
|
73
|
+
if (baseKeys.has(entry.key)) {
|
|
74
|
+
index += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const start = index;
|
|
78
|
+
while (index < variant.entries.length && !baseKeys.has(variant.entries[index].key)) index += 1;
|
|
79
|
+
const entries = variant.entries.slice(start, index);
|
|
80
|
+
const previousBase = findPreviousBaseEntry(variant.entries, start, baseKeys);
|
|
81
|
+
const nextBase = findNextBaseEntry(variant.entries, index, baseKeys);
|
|
82
|
+
if (!previousBase && !nextBase) {
|
|
83
|
+
addConflict(context, {
|
|
84
|
+
code: JsTsSafeMergeConflictCodes.ambiguousInsertionPoint,
|
|
85
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
86
|
+
side,
|
|
87
|
+
message: `${side} additions have no stable base declaration or import anchor.`,
|
|
88
|
+
details: { entries: entries.map((item) => item.names?.[0] ?? item.key) }
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
groups.push(previousBase
|
|
93
|
+
? { mode: 'after', anchorKey: previousBase.key, entries }
|
|
94
|
+
: { mode: 'before', anchorKey: nextBase.key, entries });
|
|
95
|
+
}
|
|
96
|
+
return groups;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function insertionGroupKey(group) {
|
|
100
|
+
return `${group.mode}:${group.anchorKey}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findPreviousBaseEntry(entries, startIndex, baseKeys) {
|
|
104
|
+
for (let index = startIndex - 1; index >= 0; index -= 1) {
|
|
105
|
+
if (baseKeys.has(entries[index].key)) return entries[index];
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findNextBaseEntry(entries, startIndex, baseKeys) {
|
|
111
|
+
for (let index = startIndex; index < entries.length; index += 1) {
|
|
112
|
+
if (baseKeys.has(entries[index].key)) return entries[index];
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function applySourceMergePlan(sourceText, mergePlan) {
|
|
118
|
+
return [...mergePlan.edits]
|
|
119
|
+
.sort((left, right) => right.start - left.start || right.end - left.end)
|
|
120
|
+
.reduce((current, edit) => `${current.slice(0, edit.start)}${edit.text}${current.slice(edit.end)}`, sourceText);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions) {
|
|
124
|
+
const seen = new Set(baseSpecifiers.map((specifier) => specifier.canonical));
|
|
125
|
+
const additions = [];
|
|
126
|
+
for (const specifier of [...workerAdditions, ...headAdditions]) {
|
|
127
|
+
if (seen.has(specifier.canonical)) continue;
|
|
128
|
+
seen.add(specifier.canonical);
|
|
129
|
+
additions.push(specifier);
|
|
130
|
+
}
|
|
131
|
+
additions.sort((left, right) => left.canonical.localeCompare(right.canonical));
|
|
132
|
+
return [...baseSpecifiers, ...additions];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderImportStatement(importInfo, specifiers) {
|
|
136
|
+
const clause = [];
|
|
137
|
+
if (importInfo.defaultLocalName) clause.push(importInfo.defaultLocalName);
|
|
138
|
+
if (importInfo.namespaceLocalName) clause.push(`* as ${importInfo.namespaceLocalName}`);
|
|
139
|
+
if (specifiers.length) clause.push(`{ ${specifiers.map(importSpecifierCanonical).join(', ')} }`);
|
|
140
|
+
const importType = importInfo.typeOnly ? 'type ' : '';
|
|
141
|
+
return `import ${importType}${clause.join(', ')} from ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function declarationInsertionText(entries, lineEnding) {
|
|
145
|
+
return entries
|
|
146
|
+
.map((entry) => normalizeLineEndings(entry.text.trimEnd(), lineEnding))
|
|
147
|
+
.map((text) => text.endsWith(lineEnding) ? text : `${text}${lineEnding}`)
|
|
148
|
+
.join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function mergedDeclarationEntries(workerEntries, headEntries) {
|
|
152
|
+
const entriesByKey = new Map();
|
|
153
|
+
for (const entry of [...workerEntries, ...headEntries]) {
|
|
154
|
+
const key = `${entry.kind}:${entry.key}:${entry.text.trim()}`;
|
|
155
|
+
if (!entriesByKey.has(key)) entriesByKey.set(key, entry);
|
|
156
|
+
}
|
|
157
|
+
return [...entriesByKey.values()].sort((left, right) => {
|
|
158
|
+
const keyOrder = left.key.localeCompare(right.key);
|
|
159
|
+
if (keyOrder !== 0) return keyOrder;
|
|
160
|
+
return left.text.trim().localeCompare(right.text.trim());
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function declarationInsertionSpan(sourceText, anchor, mode, headGroup) {
|
|
165
|
+
if (!headGroup?.entries.length) {
|
|
166
|
+
const offset = mode === 'after'
|
|
167
|
+
? offsetAfterEntryLine(sourceText, anchor)
|
|
168
|
+
: offsetBeforeEntryLine(sourceText, anchor);
|
|
169
|
+
return { start: offset, end: offset };
|
|
170
|
+
}
|
|
171
|
+
if (mode === 'after') {
|
|
172
|
+
return {
|
|
173
|
+
start: offsetAfterEntryLine(sourceText, anchor),
|
|
174
|
+
end: offsetAfterEntryLine(sourceText, headGroup.entries[headGroup.entries.length - 1])
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
start: offsetBeforeEntryLine(sourceText, headGroup.entries[0]),
|
|
179
|
+
end: offsetBeforeEntryLine(sourceText, anchor)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function offsetAfterEntryLine(sourceText, entry) {
|
|
184
|
+
const lineEnd = sourceText.indexOf('\n', entry.end);
|
|
185
|
+
return lineEnd === -1 ? sourceText.length : lineEnd + 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function offsetBeforeEntryLine(sourceText, entry) {
|
|
189
|
+
return sourceText.lastIndexOf('\n', Math.max(0, entry.start - 1)) + 1;
|
|
190
|
+
}
|