@shapeshift-labs/frontier-lang-compiler 0.2.58 → 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.
@@ -24,6 +24,9 @@ export interface SemanticImportSidecarProofAdmissionSummary {
24
24
 
25
25
  export interface SemanticImportSidecarQuality {
26
26
  readonly schema: 'frontier.lang.semanticSidecarQuality.v1';
27
+ readonly expected: boolean;
28
+ readonly expectedSatisfied: boolean;
29
+ readonly expectedMissingReasonCodes: readonly string[];
27
30
  readonly selected: boolean;
28
31
  readonly eligible: boolean;
29
32
  readonly imported: boolean;
@@ -40,6 +43,9 @@ export interface SemanticImportSidecarQuality {
40
43
 
41
44
  export interface SemanticImportSidecarAdmission {
42
45
  readonly schema: 'frontier.lang.semanticSidecarAdmission.v1';
46
+ readonly expected: boolean;
47
+ readonly expectedSatisfied: boolean;
48
+ readonly expectedMissingReasonCodes: readonly string[];
43
49
  readonly selected: boolean;
44
50
  readonly eligible: boolean;
45
51
  readonly imported: boolean;
@@ -304,8 +304,8 @@ export interface SemanticImportSidecar {
304
304
  readonly dependencyRelations: number; readonly dependencyPredicates: readonly string[];
305
305
  readonly patchHints: number;
306
306
  readonly evidenceWarnings: number;
307
- readonly readiness: SemanticMergeReadiness;
308
- readonly emptySemanticIndex: boolean;
307
+ readonly semanticImportExpected: boolean; readonly semanticImportExpectedSatisfied: boolean; readonly semanticImportExpectedMissingReasonCodes: readonly string[];
308
+ readonly readiness: SemanticMergeReadiness; readonly emptySemanticIndex: boolean;
309
309
  };
310
310
  readonly metadata?: Record<string, unknown>;
311
311
  }
@@ -314,6 +314,6 @@ export interface SemanticImportSidecarOptions {
314
314
  readonly id?: string;
315
315
  readonly generatedAt?: number;
316
316
  readonly regionPrefix?: string;
317
- readonly targetPath?: string;
317
+ readonly targetPath?: string; readonly expected?: boolean; readonly semanticImportExpected?: boolean;
318
318
  readonly metadata?: Record<string, unknown>;
319
319
  }
@@ -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,
@@ -28,6 +28,7 @@ export function createSemanticImportSidecar(importResult, options = {}) {
28
28
  );
29
29
  const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
30
30
  const quality = createSemanticImportSidecarQuality({
31
+ expected: options.expected === true || options.semanticImportExpected === true,
31
32
  importEntries,
32
33
  symbols,
33
34
  ownershipRegions,
@@ -102,6 +103,9 @@ export function createSemanticImportSidecar(importResult, options = {}) {
102
103
  dependencyPredicates: dependencies.predicates,
103
104
  patchHints: patchHints.length,
104
105
  evidenceWarnings: quality.emptyEvidenceWarnings.length,
106
+ semanticImportExpected: quality.expected,
107
+ semanticImportExpectedSatisfied: quality.expectedSatisfied,
108
+ semanticImportExpectedMissingReasonCodes: quality.expectedMissingReasonCodes,
105
109
  readiness,
106
110
  emptySemanticIndex: symbols.length === 0
107
111
  },
@@ -14,15 +14,28 @@ function sidecarQualityWarning(code, message, action, sourcePaths) {
14
14
 
15
15
  export function createSemanticImportSidecarQuality(input) {
16
16
  const { importEntries, symbols, ownershipRegions, patchHints, proofSpec, evidence, readiness } = input;
17
+ const expected = input.expected === true;
17
18
  const sourcePaths = sidecarSourcePaths(importEntries);
18
19
  const importCount = importEntries.length;
19
20
  const warnings = [];
21
+ if (expected && importCount === 0) warnings.push(sidecarQualityWarning(
22
+ 'expected-semantic-import-missing',
23
+ 'Semantic import was expected but no import entries were selected.',
24
+ 'check-semantic-import-include-globs-and-workspace-paths',
25
+ sourcePaths
26
+ ));
20
27
  if (importCount === 0) warnings.push(sidecarQualityWarning(
21
28
  'missing-imports',
22
29
  'Semantic sidecar has no import entries; run native import before merge admission.',
23
30
  'run-native-import',
24
31
  sourcePaths
25
32
  ));
33
+ if (expected && importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
34
+ 'expected-semantic-import-empty',
35
+ 'Semantic import was expected but selected imports produced zero semantic symbols.',
36
+ 'rerun-importer-with-semantic-source-selection',
37
+ sourcePaths
38
+ ));
26
39
  if (importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
27
40
  'empty-semantic-index',
28
41
  'Semantic sidecar has import entries but no semantic symbols.',
@@ -86,9 +99,21 @@ export function createSemanticImportSidecarQuality(input) {
86
99
  const emptyEvidenceWarnings = warnings.filter((warning) => (
87
100
  warning.code === 'empty-evidence' ||
88
101
  warning.code === 'empty-semantic-index' ||
102
+ warning.code === 'expected-semantic-import-empty' ||
103
+ warning.code === 'expected-semantic-import-missing' ||
89
104
  warning.code === 'missing-ownership-regions' ||
90
105
  warning.code === 'missing-patch-hints'
91
106
  ));
107
+ const expectedMissingReasonCodes = expected
108
+ ? emptyEvidenceWarnings.map((warning) => warning.code)
109
+ : [];
110
+ const expectedSatisfied = !expected || (
111
+ importCount > 0 &&
112
+ symbols.length > 0 &&
113
+ ownershipRegions.length > 0 &&
114
+ patchHints.length > 0 &&
115
+ evidence.length > 0
116
+ );
92
117
  const proofSummary = {
93
118
  total: proofSpec.total,
94
119
  obligations: proofSpec.obligations,
@@ -104,6 +129,9 @@ export function createSemanticImportSidecarQuality(input) {
104
129
  };
105
130
  return {
106
131
  schema: 'frontier.lang.semanticSidecarQuality.v1',
132
+ expected,
133
+ expectedSatisfied,
134
+ expectedMissingReasonCodes,
107
135
  selected: importCount > 0,
108
136
  eligible: importCount > 0 && emptyEvidenceWarnings.length === 0 && proofSpec.failed === 0 && readiness !== 'blocked',
109
137
  imported: importCount > 0,
@@ -122,6 +150,9 @@ export function createSemanticImportSidecarQuality(input) {
122
150
  export function createSemanticImportSidecarAdmission(quality, readiness) {
123
151
  return {
124
152
  schema: 'frontier.lang.semanticSidecarAdmission.v1',
153
+ expected: quality.expected,
154
+ expectedSatisfied: quality.expectedSatisfied,
155
+ expectedMissingReasonCodes: quality.expectedMissingReasonCodes,
125
156
  selected: quality.selected,
126
157
  eligible: quality.eligible,
127
158
  imported: quality.imported,
@@ -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.58",
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",