@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.
- package/dist/declarations/semantic-sidecar-admission.d.ts +6 -0
- package/dist/declarations/semantic-sidecar.d.ts +3 -3
- package/dist/internal/index-impl/createLightweightNativeImport.js +12 -0
- package/dist/internal/index-impl/createLightweightSemanticLayers.js +1 -1
- package/dist/internal/index-impl/createSemanticImportSidecar.js +4 -0
- package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +31 -0
- package/dist/lightweight-dependency-language.js +223 -0
- package/dist/lightweight-dependency-relations.js +18 -151
- package/dist/semantic-import-source-preservation.js +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
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 (!
|
|
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
|
|
67
|
+
const state = { inBlockComment: false };
|
|
145
68
|
for (let lineNumber = scan.startLine; lineNumber <= scan.endLine; lineNumber += 1) {
|
|
146
|
-
const scanLine =
|
|
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 (
|
|
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