@shapeshift-labs/frontier-lang-compiler 0.2.59 → 0.2.60

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.
@@ -109,6 +109,18 @@ export function createLightweightNativeImport(input) {
109
109
  occurrences.push(...dependencies.occurrences);
110
110
  relations.push(...dependencies.relations);
111
111
  facts.push(...dependencies.facts);
112
+ for (const occurrence of dependencies.occurrences) {
113
+ mappings.push({
114
+ id: `map_${idFragment(occurrence.id)}`,
115
+ nativeAstNodeId: occurrence.nativeAstNodeId,
116
+ semanticSymbolId: occurrence.symbolId,
117
+ semanticOccurrenceId: occurrence.id,
118
+ sourceSpan: occurrence.span,
119
+ evidenceIds: [evidenceId],
120
+ lossIds: [],
121
+ precision: 'line'
122
+ });
123
+ }
112
124
  losses.push(...lightweightCoverageLosses(input, declarations, input.sourcePreservation));
113
125
 
114
126
  const semanticIndex = createSemanticIndexRecord({
@@ -48,7 +48,7 @@ export function createLightweightSemanticLayers(input) {
48
48
  })),
49
49
  evaluationModels: [evaluationModel(input, evidenceIds)],
50
50
  effectRegions: effectRegions(input, evidenceIds),
51
- loweringRecords: mappings.slice(0, 40).map((mapping) => ({
51
+ loweringRecords: mappings.map((mapping) => ({
52
52
  id: `lowering_${mapping.id}`,
53
53
  kind: 'nativeSourceToFrontierSemanticIndex',
54
54
  sourceMapId: mapping.sourceMapId,
@@ -0,0 +1,223 @@
1
+ const ignoredIdentifiers = new Set([
2
+ 'abstract', 'alias', 'and', 'as', 'async', 'await', 'break', 'case', 'catch',
3
+ 'class', 'const', 'continue', 'def', 'default', 'defer', 'delete', 'do',
4
+ 'else', 'enum', 'export', 'extends', 'false', 'finally', 'fn', 'for', 'from',
5
+ 'func', 'function', 'if', 'impl', 'import', 'in', 'interface', 'let', 'match',
6
+ 'mod', 'module', 'namespace', 'new', 'nil', 'none', 'not', 'null', 'or',
7
+ 'package', 'pass', 'protocol', 'pub', 'public', 'return', 'self', 'static',
8
+ 'struct', 'super', 'switch', 'this', 'throw', 'trait', 'true', 'try', 'type',
9
+ 'undefined', 'use', 'using', 'var', 'void', 'while', 'with', 'yield'
10
+ ]);
11
+
12
+ export function dependencyIdentifiers(input, declaration, line) {
13
+ const identifiers = new Set();
14
+ addIdentifier(identifiers, declaration.name);
15
+ for (const part of splitIdentifierPath(declaration.name)) addIdentifier(identifiers, part);
16
+ for (const field of ['methodName', 'propertyName', 'owner']) addIdentifier(identifiers, declaration.fields?.[field]);
17
+ if (declaration.role === 'import') {
18
+ for (const identifier of importLineIdentifiers(input, line)) addIdentifier(identifiers, identifier);
19
+ for (const identifier of importPathIdentifiers(declaration.importPath ?? declaration.name)) addIdentifier(identifiers, identifier);
20
+ }
21
+ return [...identifiers];
22
+ }
23
+
24
+ export function addIdentifier(set, value) {
25
+ if (isDependencyIdentifier(value)) set.add(String(value));
26
+ }
27
+
28
+ export function isDependencyIdentifier(value) {
29
+ const text = String(value ?? '');
30
+ return /^[A-Za-z_$][\w$]*$/.test(text) && !ignoredIdentifiers.has(text.toLowerCase());
31
+ }
32
+
33
+ export function dependencyScanRanges(input, declarations, lines) {
34
+ const ordered = [...(declarations ?? [])].sort((left, right) => (left.span?.startLine ?? 0) - (right.span?.startLine ?? 0));
35
+ return ordered
36
+ .filter((declaration) => declaration?.symbolId && declaration.role !== 'import')
37
+ .map((declaration, index) => ({
38
+ declaration,
39
+ startLine: declaration.span?.startLine ?? 1,
40
+ endLine: dependencyScanEndLine(input, declaration, lines, ordered[index + 1]?.span?.startLine)
41
+ }));
42
+ }
43
+
44
+ export function maskDependencyLine(input, line, state) {
45
+ const language = String(input.language ?? '').toLowerCase();
46
+ const text = String(line ?? '');
47
+ let output = '';
48
+ let quote;
49
+ let escaped = false;
50
+ for (let index = 0; index < text.length; index += 1) {
51
+ const char = text[index];
52
+ const next = text[index + 1];
53
+ if (state.inBlockComment) {
54
+ output += ' ';
55
+ if (char === '*' && next === '/') {
56
+ output += ' ';
57
+ index += 1;
58
+ state.inBlockComment = false;
59
+ }
60
+ continue;
61
+ }
62
+ if (quote) {
63
+ output += ' ';
64
+ if (escaped) escaped = false;
65
+ else if (char === '\\') escaped = true;
66
+ else if (char === quote) quote = undefined;
67
+ continue;
68
+ }
69
+ if (char === '/' && next === '*') {
70
+ output += ' ';
71
+ index += 1;
72
+ state.inBlockComment = true;
73
+ continue;
74
+ }
75
+ if (startsLineComment(language, char, next, text, index)) break;
76
+ if (char === '\'' || char === '"' || char === '`') {
77
+ output += ' ';
78
+ quote = char;
79
+ continue;
80
+ }
81
+ output += char;
82
+ }
83
+ return output;
84
+ }
85
+
86
+ export function emptyDependencySummary() {
87
+ return { total: 0, calls: 0, uses: 0 };
88
+ }
89
+
90
+ function importLineIdentifiers(input, line) {
91
+ const language = String(input.language ?? '').toLowerCase();
92
+ const source = String(line ?? '').trim();
93
+ if (language === 'python') return pythonImportIdentifiers(source);
94
+ if (language === 'rust') return rustImportIdentifiers(source);
95
+ if (language === 'go') return aliasedPathImportIdentifiers(source);
96
+ if (language === 'javascript' || language === 'typescript') return jsImportIdentifiers(source);
97
+ if (language === 'haskell') return haskellImportIdentifiers(source);
98
+ return aliasedPathImportIdentifiers(source);
99
+ }
100
+
101
+ function jsImportIdentifiers(source) {
102
+ const identifiers = [];
103
+ let match = source.match(/^import\s+([A-Za-z_$][\w$]*)\s+from\b/);
104
+ if (match) identifiers.push(match[1]);
105
+ match = source.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\b/);
106
+ if (match) identifiers.push(match[1]);
107
+ match = source.match(/^import\s+[^,{]+,\s*\{([^}]+)\}\s+from\b|^import\s+\{([^}]+)\}\s+from\b/);
108
+ if (match) identifiers.push(...namedImportIdentifiers(match[1] ?? match[2]));
109
+ return identifiers;
110
+ }
111
+
112
+ function pythonImportIdentifiers(source) {
113
+ const identifiers = [];
114
+ const importMatch = source.match(/^import\s+(.+)$/);
115
+ if (importMatch) identifiers.push(...commaParts(importMatch[1]).map((part) => part.split(/\s+as\s+/i).pop()));
116
+ const fromMatch = source.match(/^from\s+\S+\s+import\s+(.+)$/);
117
+ if (fromMatch) identifiers.push(...namedImportIdentifiers(fromMatch[1]));
118
+ return identifiers;
119
+ }
120
+
121
+ function rustImportIdentifiers(source) {
122
+ const match = source.match(/^use\s+(.+?);?$/);
123
+ if (!match) return [];
124
+ const raw = match[1].replace(/[{}]/g, ' ');
125
+ return raw.split(/::|,|\s+as\s+/i).map((part) => part.trim()).filter(Boolean);
126
+ }
127
+
128
+ function haskellImportIdentifiers(source) {
129
+ const match = source.match(/^import\s+(?:qualified\s+)?([A-Za-z_][\w.]*)(?:\s+as\s+([A-Za-z_]\w*))?/);
130
+ return match ? [match[2], ...splitIdentifierPath(match[1])].filter(Boolean) : [];
131
+ }
132
+
133
+ function aliasedPathImportIdentifiers(source) {
134
+ const alias = source.match(/^(?:import|using|use)\s+([A-Za-z_$][\w$]*)\s*=/)?.[1]
135
+ ?? source.match(/^(?:import|using|use)\s+([A-Za-z_$][\w$]*)\s+["']/)?.[1]
136
+ ?? source.match(/\bas\s+([A-Za-z_$][\w$]*)\b/)?.[1];
137
+ return alias ? [alias] : [];
138
+ }
139
+
140
+ function namedImportIdentifiers(raw) {
141
+ return commaParts(raw).map((part) => part.trim().split(/\s+as\s+/i).pop()?.trim()).filter(Boolean);
142
+ }
143
+
144
+ function importPathIdentifiers(value) {
145
+ return splitIdentifierPath(String(value ?? '').replace(/['";]/g, ''));
146
+ }
147
+
148
+ function splitIdentifierPath(value) {
149
+ return String(value ?? '').split(/[^A-Za-z0-9_$]+/).filter(isDependencyIdentifier);
150
+ }
151
+
152
+ function commaParts(raw) {
153
+ return String(raw ?? '').split(',').map((part) => part.trim()).filter(Boolean);
154
+ }
155
+
156
+ function dependencyScanEndLine(input, declaration, lines, nextStartLine) {
157
+ const startLine = declaration.span?.startLine ?? 1;
158
+ if (!declaration.metadata?.hasBody) return startLine;
159
+ if (usesIndentationBlocks(input)) {
160
+ return indentationRegionEndLine(lines, startLine);
161
+ }
162
+ const balancedEnd = balancedRegionEndLine(lines, startLine);
163
+ const fallbackEnd = nextStartLine ? nextStartLine - 1 : lines.length;
164
+ return Math.max(startLine, Math.min(balancedEnd > startLine ? balancedEnd : fallbackEnd, lines.length));
165
+ }
166
+
167
+ function usesIndentationBlocks(input) {
168
+ return ['python'].includes(String(input.language ?? '').toLowerCase());
169
+ }
170
+
171
+ function indentationRegionEndLine(lines, startLine) {
172
+ const header = lines[startLine - 1]?.line ?? '';
173
+ const baseIndent = indentationWidth(header);
174
+ let lastBodyLine = startLine;
175
+ let sawBodyLine = false;
176
+ for (let index = startLine; index < lines.length; index += 1) {
177
+ const line = lines[index]?.line ?? '';
178
+ if (!line.trim()) {
179
+ if (sawBodyLine) lastBodyLine = index + 1;
180
+ continue;
181
+ }
182
+ const indent = indentationWidth(line);
183
+ if (indent <= baseIndent) return Math.max(startLine, lastBodyLine);
184
+ sawBodyLine = true;
185
+ lastBodyLine = index + 1;
186
+ }
187
+ return lastBodyLine;
188
+ }
189
+
190
+ function indentationWidth(line) {
191
+ let width = 0;
192
+ for (const char of String(line ?? '')) {
193
+ if (char === ' ') width += 1;
194
+ else if (char === '\t') width += 4;
195
+ else break;
196
+ }
197
+ return width;
198
+ }
199
+
200
+ function balancedRegionEndLine(lines, startLine) {
201
+ const state = { inBlockComment: false };
202
+ let depth = 0;
203
+ let opened = false;
204
+ for (let index = Math.max(0, startLine - 1); index < lines.length; index += 1) {
205
+ for (const char of maskDependencyLine({ language: 'javascript' }, lines[index].line, state)) {
206
+ if (char === '{' || char === '[' || char === '(') {
207
+ depth += 1;
208
+ opened = true;
209
+ } else if (char === '}' || char === ']' || char === ')') {
210
+ depth -= 1;
211
+ }
212
+ }
213
+ if (opened && depth <= 0) return lines[index].number;
214
+ }
215
+ return startLine;
216
+ }
217
+
218
+ function startsLineComment(language, char, next, text, index) {
219
+ if (char === '/' && next === '/') return true;
220
+ if (char === '#' && !['csharp'].includes(language)) return true;
221
+ if (char === '-' && next === '-' && ['sql', 'haskell', 'lua'].includes(language)) return true;
222
+ return text.startsWith('rem ', index) && language === 'shell';
223
+ }
@@ -1,23 +1,20 @@
1
1
  import { idFragment } from './native-import-utils.js';
2
+ import {
3
+ addIdentifier,
4
+ dependencyIdentifiers,
5
+ dependencyScanRanges,
6
+ emptyDependencySummary,
7
+ isDependencyIdentifier,
8
+ maskDependencyLine
9
+ } from './lightweight-dependency-language.js';
2
10
  import { sourceLines } from './native-region-scanner-core.js';
3
11
 
4
- const jsLikeLanguages = new Set(['javascript', 'typescript']);
5
- const ignoredIdentifiers = new Set([
6
- 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default',
7
- 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'from',
8
- 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'null', 'of',
9
- 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
10
- 'undefined', 'var', 'void', 'while', 'with', 'yield'
11
- ]);
12
-
13
12
  export function lightweightDependencyRelations(input, declarations, documentId) {
14
- if (!jsLikeLanguages.has(String(input.language ?? '').toLowerCase())) {
15
- return { relations: [], occurrences: [], facts: [], summary: emptySummary() };
16
- }
13
+ if (typeof input.sourceText !== 'string') return { relations: [], occurrences: [], facts: [], summary: emptyDependencySummary() };
17
14
  const lines = sourceLines(input.sourceText);
18
15
  const identifiers = declarationIdentifierMap(input, declarations, lines);
19
16
  const records = { relations: [], occurrences: [], facts: [], seen: new Set() };
20
- for (const scan of declarationScanRanges(declarations, lines)) {
17
+ for (const scan of dependencyScanRanges(input, declarations, lines)) {
21
18
  scanDeclarationDependencies(input, documentId, scan, identifiers, lines, records);
22
19
  }
23
20
  return {
@@ -52,101 +49,27 @@ function declarationIdentifierMap(input, declarations, lines) {
52
49
 
53
50
  function identifiersForDeclaration(input, declaration, lines) {
54
51
  const identifiers = new Set();
55
- addIdentifier(identifiers, declaration.name);
56
- const parts = String(declaration.name ?? '').split('.');
57
- addIdentifier(identifiers, parts[parts.length - 1]);
58
- if (declaration.role === 'import') {
59
- const line = lines[(declaration.span?.startLine ?? 1) - 1]?.line ?? '';
60
- for (const identifier of importLocalIdentifiers(line)) addIdentifier(identifiers, identifier);
61
- addIdentifier(identifiers, packageIdentifier(declaration.importPath ?? declaration.name));
52
+ const line = lines[(declaration.span?.startLine ?? 1) - 1]?.line ?? '';
53
+ for (const identifier of dependencyIdentifiers(input, declaration, line)) {
54
+ addIdentifier(identifiers, identifier);
62
55
  }
63
56
  return [...identifiers];
64
57
  }
65
58
 
66
- function importLocalIdentifiers(line) {
67
- const source = String(line ?? '');
68
- const identifiers = [];
69
- let match = source.match(/^import\s+([A-Za-z_$][\w$]*)\s+from\b/);
70
- if (match) identifiers.push(match[1]);
71
- match = source.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\b/);
72
- if (match) identifiers.push(match[1]);
73
- match = source.match(/^import\s+[^,{]+,\s*\{([^}]+)\}\s+from\b/);
74
- if (match) identifiers.push(...namedImportIdentifiers(match[1]));
75
- match = source.match(/^import\s+\{([^}]+)\}\s+from\b/);
76
- if (match) identifiers.push(...namedImportIdentifiers(match[1]));
77
- return identifiers;
78
- }
79
-
80
- function namedImportIdentifiers(raw) {
81
- return String(raw ?? '')
82
- .split(',')
83
- .map((part) => part.trim().split(/\s+as\s+/i).pop()?.trim())
84
- .filter(Boolean);
85
- }
86
-
87
- function packageIdentifier(value) {
88
- const parts = String(value ?? '').split('/').filter(Boolean);
89
- const last = parts[parts.length - 1] ?? value;
90
- return String(last).replace(/[^A-Za-z0-9_$]/g, '');
91
- }
92
-
93
59
  function addIdentifierTarget(map, identifier, target) {
94
- if (!isIdentifier(identifier)) return;
60
+ if (!isDependencyIdentifier(identifier)) return;
95
61
  const existing = map.get(identifier) ?? [];
96
62
  if (!existing.some((entry) => entry.symbolId === target.symbolId)) existing.push(target);
97
63
  map.set(identifier, existing);
98
64
  }
99
65
 
100
- function addIdentifier(set, value) {
101
- if (isIdentifier(value)) set.add(value);
102
- }
103
-
104
- function isIdentifier(value) {
105
- const text = String(value ?? '');
106
- return /^[A-Za-z_$][\w$]*$/.test(text) && !ignoredIdentifiers.has(text);
107
- }
108
-
109
- function declarationScanRanges(declarations, lines) {
110
- return (declarations ?? [])
111
- .filter((declaration) => declaration?.symbolId && declaration.role !== 'import')
112
- .map((declaration) => ({
113
- declaration,
114
- startLine: declaration.span?.startLine ?? 1,
115
- endLine: declarationScanEndLine(declaration, lines)
116
- }));
117
- }
118
-
119
- function declarationScanEndLine(declaration, lines) {
120
- const startLine = declaration.span?.startLine ?? 1;
121
- if (!declaration.metadata?.hasBody || declaration.kind === 'ClassDeclaration') return startLine;
122
- return balancedRegionEndLine(lines, startLine);
123
- }
124
-
125
- function balancedRegionEndLine(lines, startLine) {
126
- const state = { inBlockComment: false, inTemplateString: false };
127
- let depth = 0;
128
- let opened = false;
129
- for (let index = Math.max(0, startLine - 1); index < lines.length; index += 1) {
130
- for (const char of maskReferenceLine(lines[index].line, state)) {
131
- if (char === '{' || char === '[' || char === '(') {
132
- depth += 1;
133
- opened = true;
134
- } else if (char === '}' || char === ']' || char === ')') {
135
- depth -= 1;
136
- }
137
- }
138
- if (opened && depth <= 0) return lines[index].number;
139
- }
140
- return startLine;
141
- }
142
-
143
66
  function scanDeclarationDependencies(input, documentId, scan, identifiers, lines, records) {
144
- const state = { inBlockComment: false, inTemplateString: false };
67
+ const state = { inBlockComment: false };
145
68
  for (let lineNumber = scan.startLine; lineNumber <= scan.endLine; lineNumber += 1) {
146
- const scanLine = maskReferenceLine(lines[lineNumber - 1]?.line ?? '', state);
69
+ const scanLine = maskDependencyLine(input, lines[lineNumber - 1]?.line ?? '', state);
147
70
  for (const match of scanLine.matchAll(/[A-Za-z_$][\w$]*/g)) {
148
71
  const name = match[0];
149
- if (ignoredIdentifiers.has(name) || !identifiers.has(name)) continue;
72
+ if (!isDependencyIdentifier(name) || !identifiers.has(name)) continue;
150
73
  const targets = identifiers.get(name).filter((target) => target.symbolId !== scan.declaration.symbolId);
151
74
  for (const target of targets) {
152
75
  addDependencyRecord(input, documentId, scan.declaration, target, {
@@ -197,7 +120,7 @@ function addDependencyRecord(input, documentId, caller, target, occurrence, reco
197
120
  nativeAstNodeId: caller.nodeId
198
121
  });
199
122
  records.facts.push({
200
- id: `fact_${idFragment(relationId)}_lightweight_dependency`,
123
+ id: `fact_${idFragment(relationId)}_${records.facts.length + 1}_lightweight_dependency`,
201
124
  predicate: 'lightweightDependency',
202
125
  subjectId: caller.symbolId,
203
126
  value: {
@@ -213,59 +136,3 @@ function addDependencyRecord(input, documentId, caller, target, occurrence, reco
213
136
  function isCallReference(line, afterIdentifierIndex) {
214
137
  return /^\s*\(/.test(String(line ?? '').slice(afterIdentifierIndex));
215
138
  }
216
-
217
- function maskReferenceLine(line, state) {
218
- const text = String(line ?? '');
219
- let output = '';
220
- let quote;
221
- let escaped = false;
222
- for (let index = 0; index < text.length; index += 1) {
223
- const char = text[index];
224
- const next = text[index + 1];
225
- if (state.inBlockComment) {
226
- output += ' ';
227
- if (char === '*' && next === '/') {
228
- output += ' ';
229
- index += 1;
230
- state.inBlockComment = false;
231
- }
232
- continue;
233
- }
234
- if (state.inTemplateString) {
235
- output += ' ';
236
- if (char === '`' && !escaped) state.inTemplateString = false;
237
- escaped = char === '\\' && !escaped;
238
- continue;
239
- }
240
- if (quote) {
241
- output += ' ';
242
- if (escaped) escaped = false;
243
- else if (char === '\\') escaped = true;
244
- else if (char === quote) quote = undefined;
245
- continue;
246
- }
247
- if (char === '/' && next === '/') break;
248
- if (char === '/' && next === '*') {
249
- output += ' ';
250
- index += 1;
251
- state.inBlockComment = true;
252
- continue;
253
- }
254
- if (char === '\'' || char === '"') {
255
- output += ' ';
256
- quote = char;
257
- continue;
258
- }
259
- if (char === '`') {
260
- output += ' ';
261
- state.inTemplateString = true;
262
- continue;
263
- }
264
- output += char;
265
- }
266
- return output;
267
- }
268
-
269
- function emptySummary() {
270
- return { total: 0, calls: 0, uses: 0 };
271
- }
@@ -46,9 +46,9 @@ function createKernelSourcePreservationRecords(input) {
46
46
  }
47
47
 
48
48
  for (const sourceMap of input.sourceMaps ?? []) {
49
- for (const mapping of sourceMap.mappings ?? []) {
49
+ for (const [index, mapping] of (sourceMap.mappings ?? []).entries()) {
50
50
  records.push(explainSourcePreservation({
51
- id: `source_preservation_${idFragment(sourceMap.id)}_${idFragment(mapping.id)}`,
51
+ id: `source_preservation_${idFragment(sourceMap.id)}_${index + 1}_${idFragment(mapping.id)}`,
52
52
  sourceMap,
53
53
  mapping,
54
54
  level: mapping.preservation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.59",
3
+ "version": "0.2.60",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",