@shapeshift-labs/frontier-lang-compiler 0.2.61 → 0.2.63
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.
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import{declarationRecord}from'./declarationRecord.js';import{shortNodeText}from'./shortNodeText.js';import{treeSitterChildText,treeSitterFieldText}from'./treeSitterNodeAccess.js';
|
|
1
|
+
import{declarationRecord}from'./declarationRecord.js';import{shortNodeText}from'./shortNodeText.js';import{treeSitterChildText,treeSitterFieldKind,treeSitterFieldText}from'./treeSitterNodeAccess.js';
|
|
2
2
|
export function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
3
|
+
if (kind === 'variable_declarator') {
|
|
4
|
+
const name = treeSitterNamedText(node, ['identifier', 'property_identifier', 'private_property_identifier']);
|
|
5
|
+
if (name) return declarationRecord(input, nativeNodeId, name, treeSitterVariableSymbolKind(node), 'definition');
|
|
6
|
+
}
|
|
7
|
+
if (kind === 'method_definition') {
|
|
8
|
+
const name = treeSitterNamedText(node, ['property_identifier', 'private_property_identifier', 'identifier']);
|
|
9
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'method', 'definition');
|
|
10
|
+
}
|
|
3
11
|
if (/import|include|use/.test(kind)) {
|
|
4
|
-
const name =
|
|
12
|
+
const name = treeSitterImportName(node, kind);
|
|
5
13
|
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
6
14
|
}
|
|
7
15
|
if (/function|method|fn_item|function_declaration/.test(kind)) {
|
|
@@ -20,8 +28,8 @@ export function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
|
20
28
|
const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
|
|
21
29
|
if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
|
|
22
30
|
}
|
|
23
|
-
if (/
|
|
24
|
-
const name = treeSitterNamedText(node, ['identifier', 'property_identifier', 'field_identifier']);
|
|
31
|
+
if (/property_declaration|field_declaration/.test(kind)) {
|
|
32
|
+
const name = treeSitterNamedText(node, ['identifier', 'property_identifier', 'field_identifier', 'private_property_identifier']);
|
|
25
33
|
if (name) return declarationRecord(input, nativeNodeId, name, 'variable', 'definition');
|
|
26
34
|
}
|
|
27
35
|
return undefined;
|
|
@@ -30,3 +38,14 @@ export function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
|
30
38
|
function treeSitterNamedText(node, childKinds) {
|
|
31
39
|
return treeSitterFieldText(node, 'name') ?? treeSitterFieldText(node, 'declarator') ?? treeSitterChildText(node, childKinds);
|
|
32
40
|
}
|
|
41
|
+
|
|
42
|
+
function treeSitterImportName(node, kind) {
|
|
43
|
+
const fieldName = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source');
|
|
44
|
+
if (fieldName || /import/.test(kind)) return fieldName;
|
|
45
|
+
return treeSitterNamedText(node, ['string', 'string_fragment', 'identifier']) ?? shortNodeText(node);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function treeSitterVariableSymbolKind(node) {
|
|
49
|
+
const valueKind = String(treeSitterFieldKind(node, 'value') ?? '');
|
|
50
|
+
return /function|arrow_function|generator/.test(valueKind) ? 'function' : 'variable';
|
|
51
|
+
}
|
|
@@ -30,26 +30,80 @@ export function treeSitterChildText(node, kinds) {
|
|
|
30
30
|
|
|
31
31
|
export function treeSitterFieldText(node, field) {
|
|
32
32
|
const exact = treeSitterFieldNode(node, field);
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const exactText = shortNodeText(exact);
|
|
34
|
+
if (exactText) return exactText;
|
|
35
|
+
const valueText = treeSitterFieldValueText(node?.fields?.[field] ?? node?.[field]);
|
|
36
|
+
if (valueText) return valueText;
|
|
37
|
+
return treeSitterNamedFieldText(node, field) ?? treeSitterFallbackFieldText(node, field);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function treeSitterFieldKind(node, field) {
|
|
41
|
+
const exact = treeSitterFieldNode(node, field)
|
|
42
|
+
?? treeSitterFieldValueNode(node?.fields?.[field] ?? node?.[field])
|
|
43
|
+
?? treeSitterNamedFieldNode(node, field);
|
|
44
|
+
return exact ? treeSitterNodeKind(exact) : undefined;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
function treeSitterFieldNode(node, field) {
|
|
38
48
|
if (typeof node?.childForFieldName === 'function') return node.childForFieldName(field);
|
|
39
49
|
if (typeof node?.child_by_field_name === 'function') return node.child_by_field_name(field);
|
|
50
|
+
if (typeof node?.childrenForFieldName === 'function') return treeSitterFirstNode(node.childrenForFieldName(field));
|
|
51
|
+
if (typeof node?.children_by_field_name === 'function') return treeSitterFirstNode(node.children_by_field_name(field));
|
|
40
52
|
return undefined;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
function treeSitterNamedFieldText(node, field) {
|
|
56
|
+
const fieldNode = treeSitterNamedFieldNode(node, field);
|
|
57
|
+
return shortNodeText(fieldNode);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function treeSitterNamedFieldNode(node, field) {
|
|
44
61
|
for (const child of treeSitterChildren(node)) {
|
|
45
62
|
if (treeSitterFieldName(child) === field) {
|
|
46
|
-
|
|
63
|
+
return child;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function treeSitterFallbackFieldText(node, field) {
|
|
70
|
+
const children = treeSitterChildren(node);
|
|
71
|
+
if (field === 'name') return treeSitterFirstChildText(children, /^(?:identifier|type_identifier|property_identifier|field_identifier|private_property_identifier)$/);
|
|
72
|
+
if (field === 'path' || field === 'source') return treeSitterFirstChildText(children, /^(?:string|template_string|string_fragment|raw_string_literal)$/);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function treeSitterFirstChildText(children, pattern) {
|
|
77
|
+
for (const child of children) {
|
|
78
|
+
if (!pattern.test(treeSitterNodeKind(child))) continue;
|
|
79
|
+
const text = shortNodeText(child);
|
|
80
|
+
if (text) return text;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function treeSitterFieldValueText(value) {
|
|
86
|
+
if (typeof value === 'string') return shortNodeText({ text: value });
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
for (const item of value) {
|
|
89
|
+
const text = treeSitterFieldValueText(item);
|
|
47
90
|
if (text) return text;
|
|
48
91
|
}
|
|
49
92
|
}
|
|
93
|
+
if (value && typeof value === 'object') return shortNodeText(value);
|
|
50
94
|
return undefined;
|
|
51
95
|
}
|
|
52
96
|
|
|
97
|
+
function treeSitterFieldValueNode(value) {
|
|
98
|
+
if (Array.isArray(value)) return value.find((item) => item && typeof item === 'object');
|
|
99
|
+
return value && typeof value === 'object' ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function treeSitterFirstNode(value) {
|
|
103
|
+
if (Array.isArray(value)) return value.find((item) => item && typeof item === 'object');
|
|
104
|
+
return value && typeof value === 'object' ? value : undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
53
107
|
function treeSitterFieldName(node) {
|
|
54
108
|
if (typeof node?.fieldName === 'string') return node.fieldName;
|
|
55
109
|
if (typeof node?.field_name === 'string') return node.field_name;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
jsControlKeyword,
|
|
3
|
+
nativeDeclaration,
|
|
4
|
+
splitParameters
|
|
5
|
+
} from './native-region-scanner-core.js';
|
|
6
|
+
|
|
7
|
+
function jsCommentOnlyLine(trimmed) {
|
|
8
|
+
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function jsDeclarationScanLine(line, state) {
|
|
12
|
+
let text = String(line ?? '');
|
|
13
|
+
if (state.inTemplateString) {
|
|
14
|
+
const close = findUnescapedBacktick(text, 0);
|
|
15
|
+
if (close < 0) return '';
|
|
16
|
+
text = text.slice(close + 1);
|
|
17
|
+
state.inTemplateString = false;
|
|
18
|
+
}
|
|
19
|
+
if (state.inBlockComment) {
|
|
20
|
+
const close = text.indexOf('*/');
|
|
21
|
+
if (close < 0) return '';
|
|
22
|
+
text = text.slice(close + 2);
|
|
23
|
+
state.inBlockComment = false;
|
|
24
|
+
}
|
|
25
|
+
let output = '';
|
|
26
|
+
let quote;
|
|
27
|
+
let escaped = false;
|
|
28
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
29
|
+
const char = text[index];
|
|
30
|
+
const next = text[index + 1];
|
|
31
|
+
if (quote) {
|
|
32
|
+
output += char;
|
|
33
|
+
if (escaped) escaped = false;
|
|
34
|
+
else if (char === '\\') escaped = true;
|
|
35
|
+
else if (char === quote) quote = undefined;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (char === '/' && next === '/') break;
|
|
39
|
+
if (char === '/' && next === '*') {
|
|
40
|
+
const close = text.indexOf('*/', index + 2);
|
|
41
|
+
if (close < 0) {
|
|
42
|
+
state.inBlockComment = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
index = close + 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '\'' || char === '"') {
|
|
49
|
+
quote = char;
|
|
50
|
+
output += char;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (char === '`') {
|
|
54
|
+
const close = findUnescapedBacktick(text, index + 1);
|
|
55
|
+
if (close < 0) {
|
|
56
|
+
state.inTemplateString = true;
|
|
57
|
+
output += '``';
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
output += text.slice(index, close + 1);
|
|
61
|
+
index = close;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
output += char;
|
|
65
|
+
}
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function jsObjectRegionContext(name, declarationLine, lineNumber, regionKind) {
|
|
70
|
+
const initializerKind = jsInitializerKind(declarationLine, name);
|
|
71
|
+
if (initializerKind !== 'object' && initializerKind !== 'array') return undefined;
|
|
72
|
+
const depth = jsContainerDelta(declarationLine);
|
|
73
|
+
if (depth <= 0) return undefined;
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
regionKind: regionKind ?? jsRegionKindForDeclarationName(name, declarationLine),
|
|
77
|
+
initializerKind,
|
|
78
|
+
depth,
|
|
79
|
+
startLine: lineNumber
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function jsInitializerKind(line, name) {
|
|
84
|
+
const initializer = String(line ?? '').split('=').slice(1).join('=').trim();
|
|
85
|
+
if (!initializer) return 'unknown';
|
|
86
|
+
if (/^(?:async\s+)?function\b/.test(initializer) || /=>/.test(initializer)) return 'function';
|
|
87
|
+
if (/^(?:React\.)?(?:forwardRef|memo|lazy|observer)\s*(?:<[^>]+>)?\s*\(/.test(initializer)) return 'function';
|
|
88
|
+
const containerKind = jsContainerInitializerKind(initializer, name, line);
|
|
89
|
+
if (containerKind) return containerKind;
|
|
90
|
+
if (/^new\s+/.test(initializer)) return 'instance';
|
|
91
|
+
if (/^['"`]/.test(initializer)) return 'string';
|
|
92
|
+
if (/^(?:true|false)\b/.test(initializer)) return 'boolean';
|
|
93
|
+
if (/^[0-9]/.test(initializer)) return 'number';
|
|
94
|
+
return 'expression';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function jsVariableHasBody(initializerKind, declarationLine) {
|
|
98
|
+
return initializerKind === 'function'
|
|
99
|
+
|| initializerKind === 'object'
|
|
100
|
+
|| initializerKind === 'array'
|
|
101
|
+
|| /\{/.test(String(declarationLine ?? ''));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function jsVariableSymbolKind(regionKind, initializerKind) {
|
|
105
|
+
if (regionKind === 'route') return 'route';
|
|
106
|
+
if (initializerKind === 'function') return 'function';
|
|
107
|
+
return 'variable';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function jsRegionKindForDeclarationName(name, source = '') {
|
|
111
|
+
const raw = `${name ?? ''} ${source ?? ''}`;
|
|
112
|
+
const text = raw.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
|
|
113
|
+
const compact = raw.toLowerCase();
|
|
114
|
+
if (/\b(routes?|router|screens?|pages?|navigation|navitems?|menuitems?)\b/.test(text) || /(route|router|screen|page|navigation|navitem|menuitem)/.test(compact)) return 'route';
|
|
115
|
+
if (/\b(config|settings|options|flags?|schema|manifest|registry|catalog)\b/.test(text) || /(config|settings|options|flags|schema|manifest|registry|catalog)/.test(compact)) return 'config';
|
|
116
|
+
if (/\b(content|copy|docs?|legal|messages?|strings?|i18n|locale|translations?)\b/.test(text) || /(content|copy|docs|legal|messages|strings|i18n|locale|translations)/.test(compact)) return 'content';
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function jsExportedContainerDeclaration(input, lineNumber, trimmed) {
|
|
121
|
+
let match = trimmed.match(/^export\s+default\s+(.+)$/);
|
|
122
|
+
if (match) return jsContainerExport(input, lineNumber, 'ExportDefaultContainer', 'default', match[1], { exportDefault: true });
|
|
123
|
+
match = trimmed.match(/^(?:module\.)?exports(?:\.([A-Za-z_$][\w$]*))?\s*=\s*(.+)$/);
|
|
124
|
+
if (!match) return undefined;
|
|
125
|
+
const name = match[1] ? `exports.${match[1]}` : 'module.exports';
|
|
126
|
+
return jsContainerExport(input, lineNumber, 'CommonJsContainerExport', name, match[2], { export: 'commonjs' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function jsContainerExport(input, lineNumber, languageKind, name, initializer, fields) {
|
|
130
|
+
const initializerKind = jsContainerInitializerKind(initializer, name, initializer);
|
|
131
|
+
if (initializerKind !== 'object' && initializerKind !== 'array') return undefined;
|
|
132
|
+
const regionKind = jsRegionKindForDeclarationName(name, initializer);
|
|
133
|
+
const declaration = nativeDeclaration(input, lineNumber, languageKind, jsVariableSymbolKind(regionKind, initializerKind), name, {
|
|
134
|
+
...fields,
|
|
135
|
+
initializerKind
|
|
136
|
+
}, true, {
|
|
137
|
+
regionKind,
|
|
138
|
+
metadata: { ...fields, initializerKind }
|
|
139
|
+
});
|
|
140
|
+
const context = jsObjectRegionContext(name, `const ${name} = ${initializer}`, lineNumber, regionKind);
|
|
141
|
+
return { declaration, context };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function jsObjectPropertyDeclaration(input, lineNumber, trimmed, context) {
|
|
145
|
+
if (/^[}\])]/.test(trimmed) || trimmed.startsWith('...')) return undefined;
|
|
146
|
+
const methodMatch = trimmed.match(/^(?:(?:async|get|set)\s+)?(['"]?)([A-Za-z_$][\w$-]*)\1\s*\(([^)]*)\)\s*(?:[:\w\s<>\[\]]*)?(?:\{|=>|,|$)/);
|
|
147
|
+
if (methodMatch && !jsControlKeyword(methodMatch[2])) {
|
|
148
|
+
const name = `${context.name}.${methodMatch[2]}`;
|
|
149
|
+
return nativeDeclaration(input, lineNumber, 'ObjectMethod', 'function', name, {
|
|
150
|
+
owner: context.name,
|
|
151
|
+
propertyName: methodMatch[2],
|
|
152
|
+
parameters: splitParameters(methodMatch[3])
|
|
153
|
+
}, true, {
|
|
154
|
+
regionKind: jsPropertyRegionKind(context, methodMatch[2], 'function'),
|
|
155
|
+
metadata: { owner: context.name, propertyName: methodMatch[2], initializerKind: 'function' }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const propertyMatch = trimmed.match(/^(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$-]*))\s*:\s*(.+?)(?:,)?$/);
|
|
159
|
+
if (!propertyMatch) return undefined;
|
|
160
|
+
const propertyName = propertyMatch[2] ?? propertyMatch[3];
|
|
161
|
+
if (!propertyName || jsControlKeyword(propertyName)) return undefined;
|
|
162
|
+
const value = propertyMatch[4].trim();
|
|
163
|
+
const initializerKind = jsPropertyInitializerKind(value);
|
|
164
|
+
const functionLike = initializerKind === 'function';
|
|
165
|
+
const name = `${context.name}.${propertyName}`;
|
|
166
|
+
return nativeDeclaration(input, lineNumber, functionLike ? 'ObjectFunctionProperty' : 'ObjectProperty', functionLike ? 'function' : 'property', name, {
|
|
167
|
+
owner: context.name,
|
|
168
|
+
propertyName,
|
|
169
|
+
initializerKind
|
|
170
|
+
}, functionLike || initializerKind === 'object' || initializerKind === 'array', {
|
|
171
|
+
regionKind: jsPropertyRegionKind(context, propertyName, value),
|
|
172
|
+
metadata: { owner: context.name, propertyName, initializerKind }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function jsRouteRecordDeclaration(input, lineNumber, trimmed, context) {
|
|
177
|
+
if (context.regionKind !== 'route') return undefined;
|
|
178
|
+
const match = trimmed.match(/\b(?:path|route|href|url)\s*:\s*(['"`])([^'"`]+)\1/);
|
|
179
|
+
if (!match) return undefined;
|
|
180
|
+
const routePath = match[2];
|
|
181
|
+
return nativeDeclaration(input, lineNumber, 'RouteRecord', 'route', `${context.name}.${routePath}`, {
|
|
182
|
+
owner: context.name,
|
|
183
|
+
routePath
|
|
184
|
+
}, true, {
|
|
185
|
+
regionKind: 'route',
|
|
186
|
+
metadata: { owner: context.name, routePath, initializerKind: 'object' }
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function jsPropertyInitializerKind(value) {
|
|
191
|
+
const text = String(value ?? '').trim();
|
|
192
|
+
if (/^(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(text)) return 'function';
|
|
193
|
+
const containerKind = jsContainerInitializerKind(text, undefined, text);
|
|
194
|
+
if (containerKind) return containerKind;
|
|
195
|
+
if (/^['"`]/.test(text)) return 'string';
|
|
196
|
+
if (/^(?:true|false)\b/.test(text)) return 'boolean';
|
|
197
|
+
if (/^[0-9]/.test(text)) return 'number';
|
|
198
|
+
return 'expression';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function jsPropertyRegionKind(context, propertyName, value) {
|
|
202
|
+
const named = jsRegionKindForDeclarationName(propertyName, value);
|
|
203
|
+
if (named) return named;
|
|
204
|
+
if (context.regionKind === 'route') return 'route';
|
|
205
|
+
if (context.regionKind === 'content') return 'content';
|
|
206
|
+
if (context.regionKind === 'config') return 'config';
|
|
207
|
+
return 'property';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function jsContainerInitializerKind(initializer, name, source) {
|
|
211
|
+
const text = String(initializer ?? '').trim();
|
|
212
|
+
if (text.startsWith('{')) return 'object';
|
|
213
|
+
if (text.startsWith('[')) return 'array';
|
|
214
|
+
const match = text.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*(?:<[^)]*>)?\(\s*([\[{])/);
|
|
215
|
+
if (!match || !jsContainerWrapperLooksSemantic(match[1], name, source)) return undefined;
|
|
216
|
+
return match[2] === '{' ? 'object' : 'array';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function jsContainerWrapperLooksSemantic(callee, name, source) {
|
|
220
|
+
const signal = `${callee ?? ''} ${name ?? ''} ${source ?? ''}`.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
|
|
221
|
+
return /\b(define|create|make|build|object\.freeze)\b/.test(signal) && /\b(config|settings|options|routes?|router|content|docs?|schema|registry|manifest|catalog|menu|nav)\b/.test(signal);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function jsContainerDelta(source) {
|
|
225
|
+
let delta = 0;
|
|
226
|
+
for (const char of String(source ?? '')) {
|
|
227
|
+
if (char === '{' || char === '[') delta += 1;
|
|
228
|
+
if (char === '}' || char === ']') delta -= 1;
|
|
229
|
+
}
|
|
230
|
+
return delta;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function findUnescapedBacktick(text, startIndex) {
|
|
234
|
+
let escaped = false;
|
|
235
|
+
for (let index = startIndex; index < text.length; index += 1) {
|
|
236
|
+
const char = text[index];
|
|
237
|
+
if (escaped) escaped = false;
|
|
238
|
+
else if (char === '\\') escaped = true;
|
|
239
|
+
else if (char === '`') return index;
|
|
240
|
+
}
|
|
241
|
+
return -1;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
jsCommentOnlyLine,
|
|
246
|
+
jsContainerDelta,
|
|
247
|
+
jsDeclarationScanLine,
|
|
248
|
+
jsExportedContainerDeclaration,
|
|
249
|
+
jsInitializerKind,
|
|
250
|
+
jsObjectPropertyDeclaration,
|
|
251
|
+
jsObjectRegionContext,
|
|
252
|
+
jsRegionKindForDeclarationName,
|
|
253
|
+
jsRouteRecordDeclaration,
|
|
254
|
+
jsVariableHasBody,
|
|
255
|
+
jsVariableSymbolKind
|
|
256
|
+
};
|
|
@@ -6,6 +6,19 @@ import {
|
|
|
6
6
|
sourceLines,
|
|
7
7
|
splitParameters
|
|
8
8
|
} from './native-region-scanner-core.js';
|
|
9
|
+
import {
|
|
10
|
+
jsCommentOnlyLine,
|
|
11
|
+
jsContainerDelta,
|
|
12
|
+
jsDeclarationScanLine,
|
|
13
|
+
jsExportedContainerDeclaration,
|
|
14
|
+
jsInitializerKind,
|
|
15
|
+
jsObjectPropertyDeclaration,
|
|
16
|
+
jsObjectRegionContext,
|
|
17
|
+
jsRegionKindForDeclarationName,
|
|
18
|
+
jsRouteRecordDeclaration,
|
|
19
|
+
jsVariableHasBody,
|
|
20
|
+
jsVariableSymbolKind
|
|
21
|
+
} from './native-region-scanner-js-helpers.js';
|
|
9
22
|
|
|
10
23
|
function scanJavaScriptLike(input) {
|
|
11
24
|
const declarations = [];
|
|
@@ -38,8 +51,8 @@ function scanJavaScriptLike(input) {
|
|
|
38
51
|
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, declarationLine.includes('{')));
|
|
39
52
|
} else if ((match = trimmed.match(/^export\s+default\s+(?:async\s+)?function\*?\s*([A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/))) {
|
|
40
53
|
declarations.push(nativeDeclaration(input, number, 'ExportDefaultFunctionDeclaration', 'function', match[1] ?? 'default', { parameters: splitParameters(match[2]), exportDefault: true }, trimmed.includes('{')));
|
|
41
|
-
} else if ((match = declarationLine.match(/^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
|
|
42
|
-
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, declarationLine.includes('{')));
|
|
54
|
+
} else if ((match = declarationLine.match(/^(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
|
|
55
|
+
declarations.push(nativeDeclaration(input, number, declarationLine.startsWith('default ') ? 'ExportDefaultClassDeclaration' : 'ClassDeclaration', 'class', match[1], { exportDefault: declarationLine.startsWith('default ') || undefined }, declarationLine.includes('{')));
|
|
43
56
|
if (declarationLine.includes('{') && !declarationLine.includes('}')) {
|
|
44
57
|
currentClass = match[1];
|
|
45
58
|
classDepth = 0;
|
|
@@ -55,7 +68,7 @@ function scanJavaScriptLike(input) {
|
|
|
55
68
|
} else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
|
|
56
69
|
declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
57
70
|
} else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
|
|
58
|
-
const initializerKind = jsInitializerKind(declarationLine);
|
|
71
|
+
const initializerKind = jsInitializerKind(declarationLine, match[1]);
|
|
59
72
|
const regionKind = jsRegionKindForDeclarationName(match[1], declarationLine);
|
|
60
73
|
declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', jsVariableSymbolKind(regionKind, initializerKind), match[1], {
|
|
61
74
|
initializerKind
|
|
@@ -64,6 +77,9 @@ function scanJavaScriptLike(input) {
|
|
|
64
77
|
metadata: { initializerKind }
|
|
65
78
|
}));
|
|
66
79
|
currentObject = jsObjectRegionContext(match[1], declarationLine, number, regionKind);
|
|
80
|
+
} else if ((match = jsExportedContainerDeclaration(input, number, trimmed))) {
|
|
81
|
+
declarations.push(match.declaration);
|
|
82
|
+
currentObject = match.context;
|
|
67
83
|
} else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
|
|
68
84
|
declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
69
85
|
} else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
|
|
@@ -97,214 +113,4 @@ function scanJavaScriptLike(input) {
|
|
|
97
113
|
return declarations;
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
function jsCommentOnlyLine(trimmed) {
|
|
101
|
-
return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function jsDeclarationScanLine(line, state) {
|
|
105
|
-
let text = String(line ?? '');
|
|
106
|
-
if (state.inTemplateString) {
|
|
107
|
-
const close = findUnescapedBacktick(text, 0);
|
|
108
|
-
if (close < 0) return '';
|
|
109
|
-
text = text.slice(close + 1);
|
|
110
|
-
state.inTemplateString = false;
|
|
111
|
-
}
|
|
112
|
-
if (state.inBlockComment) {
|
|
113
|
-
const close = text.indexOf('*/');
|
|
114
|
-
if (close < 0) return '';
|
|
115
|
-
text = text.slice(close + 2);
|
|
116
|
-
state.inBlockComment = false;
|
|
117
|
-
}
|
|
118
|
-
let output = '';
|
|
119
|
-
let quote;
|
|
120
|
-
let escaped = false;
|
|
121
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
122
|
-
const char = text[index];
|
|
123
|
-
const next = text[index + 1];
|
|
124
|
-
if (quote) {
|
|
125
|
-
output += char;
|
|
126
|
-
if (escaped) {
|
|
127
|
-
escaped = false;
|
|
128
|
-
} else if (char === '\\') {
|
|
129
|
-
escaped = true;
|
|
130
|
-
} else if (char === quote) {
|
|
131
|
-
quote = undefined;
|
|
132
|
-
}
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (char === '/' && next === '/') break;
|
|
136
|
-
if (char === '/' && next === '*') {
|
|
137
|
-
const close = text.indexOf('*/', index + 2);
|
|
138
|
-
if (close < 0) {
|
|
139
|
-
state.inBlockComment = true;
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
index = close + 1;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (char === '\'' || char === '"') {
|
|
146
|
-
quote = char;
|
|
147
|
-
output += char;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (char === '`') {
|
|
151
|
-
const close = findUnescapedBacktick(text, index + 1);
|
|
152
|
-
if (close < 0) {
|
|
153
|
-
state.inTemplateString = true;
|
|
154
|
-
output += '``';
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
output += text.slice(index, close + 1);
|
|
158
|
-
index = close;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
output += char;
|
|
162
|
-
}
|
|
163
|
-
return output;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function findUnescapedBacktick(text, startIndex) {
|
|
167
|
-
let escaped = false;
|
|
168
|
-
for (let index = startIndex; index < text.length; index += 1) {
|
|
169
|
-
const char = text[index];
|
|
170
|
-
if (escaped) {
|
|
171
|
-
escaped = false;
|
|
172
|
-
} else if (char === '\\') {
|
|
173
|
-
escaped = true;
|
|
174
|
-
} else if (char === '`') {
|
|
175
|
-
return index;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return -1;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function jsObjectRegionContext(name, declarationLine, lineNumber, regionKind) {
|
|
182
|
-
const initializerKind = jsInitializerKind(declarationLine);
|
|
183
|
-
if (initializerKind !== 'object' && initializerKind !== 'array') return undefined;
|
|
184
|
-
const depth = jsContainerDelta(declarationLine);
|
|
185
|
-
if (depth <= 0) return undefined;
|
|
186
|
-
return {
|
|
187
|
-
name,
|
|
188
|
-
regionKind: regionKind ?? jsRegionKindForDeclarationName(name, declarationLine),
|
|
189
|
-
initializerKind,
|
|
190
|
-
depth,
|
|
191
|
-
startLine: lineNumber
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function jsInitializerKind(line) {
|
|
196
|
-
const initializer = String(line ?? '').split('=').slice(1).join('=').trim();
|
|
197
|
-
if (!initializer) return 'unknown';
|
|
198
|
-
if (/^(?:async\s+)?function\b/.test(initializer) || /=>/.test(initializer)) return 'function';
|
|
199
|
-
if (initializer.startsWith('{')) return 'object';
|
|
200
|
-
if (initializer.startsWith('[')) return 'array';
|
|
201
|
-
if (/^new\s+/.test(initializer)) return 'instance';
|
|
202
|
-
if (/^['"`]/.test(initializer)) return 'string';
|
|
203
|
-
if (/^(?:true|false)\b/.test(initializer)) return 'boolean';
|
|
204
|
-
if (/^[0-9]/.test(initializer)) return 'number';
|
|
205
|
-
return 'expression';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function jsVariableHasBody(initializerKind, declarationLine) {
|
|
209
|
-
return initializerKind === 'function'
|
|
210
|
-
|| initializerKind === 'object'
|
|
211
|
-
|| initializerKind === 'array'
|
|
212
|
-
|| /\{/.test(String(declarationLine ?? ''));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function jsVariableSymbolKind(regionKind, initializerKind) {
|
|
216
|
-
if (regionKind === 'route') return 'route';
|
|
217
|
-
if (initializerKind === 'function') return 'function';
|
|
218
|
-
return 'variable';
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function jsRegionKindForDeclarationName(name, source = '') {
|
|
222
|
-
const raw = `${name ?? ''} ${source ?? ''}`;
|
|
223
|
-
const text = raw.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
|
|
224
|
-
const compact = raw.toLowerCase();
|
|
225
|
-
if (/\b(routes?|router|screens?|pages?|navigation|navitems?|menuitems?)\b/.test(text) || /(route|router|screen|page|navigation|navitem|menuitem)/.test(compact)) return 'route';
|
|
226
|
-
if (/\b(config|settings|options|flags?|schema|manifest|registry|catalog)\b/.test(text) || /(config|settings|options|flags|schema|manifest|registry|catalog)/.test(compact)) return 'config';
|
|
227
|
-
if (/\b(content|copy|docs?|legal|messages?|strings?|i18n|locale|translations?)\b/.test(text) || /(content|copy|docs|legal|messages|strings|i18n|locale|translations)/.test(compact)) return 'content';
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function jsObjectPropertyDeclaration(input, lineNumber, trimmed, context) {
|
|
232
|
-
if (/^[}\])]/.test(trimmed) || trimmed.startsWith('...')) return undefined;
|
|
233
|
-
const methodMatch = trimmed.match(/^(?:(?:async|get|set)\s+)?(['"]?)([A-Za-z_$][\w$-]*)\1\s*\(([^)]*)\)\s*(?:[:\w\s<>\[\]]*)?(?:\{|=>|,|$)/);
|
|
234
|
-
if (methodMatch && !jsControlKeyword(methodMatch[2])) {
|
|
235
|
-
const name = `${context.name}.${methodMatch[2]}`;
|
|
236
|
-
return nativeDeclaration(input, lineNumber, 'ObjectMethod', 'function', name, {
|
|
237
|
-
owner: context.name,
|
|
238
|
-
propertyName: methodMatch[2],
|
|
239
|
-
parameters: splitParameters(methodMatch[3])
|
|
240
|
-
}, true, {
|
|
241
|
-
regionKind: jsPropertyRegionKind(context, methodMatch[2], 'function'),
|
|
242
|
-
metadata: { owner: context.name, propertyName: methodMatch[2], initializerKind: 'function' }
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
const propertyMatch = trimmed.match(/^(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$-]*))\s*:\s*(.+?)(?:,)?$/);
|
|
246
|
-
if (!propertyMatch) return undefined;
|
|
247
|
-
const propertyName = propertyMatch[2] ?? propertyMatch[3];
|
|
248
|
-
if (!propertyName || jsControlKeyword(propertyName)) return undefined;
|
|
249
|
-
const value = propertyMatch[4].trim();
|
|
250
|
-
const initializerKind = jsPropertyInitializerKind(value);
|
|
251
|
-
const functionLike = initializerKind === 'function';
|
|
252
|
-
const name = `${context.name}.${propertyName}`;
|
|
253
|
-
return nativeDeclaration(input, lineNumber, functionLike ? 'ObjectFunctionProperty' : 'ObjectProperty', functionLike ? 'function' : 'property', name, {
|
|
254
|
-
owner: context.name,
|
|
255
|
-
propertyName,
|
|
256
|
-
initializerKind
|
|
257
|
-
}, functionLike || initializerKind === 'object' || initializerKind === 'array', {
|
|
258
|
-
regionKind: jsPropertyRegionKind(context, propertyName, value),
|
|
259
|
-
metadata: {
|
|
260
|
-
owner: context.name,
|
|
261
|
-
propertyName,
|
|
262
|
-
initializerKind
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function jsRouteRecordDeclaration(input, lineNumber, trimmed, context) {
|
|
268
|
-
if (context.regionKind !== 'route') return undefined;
|
|
269
|
-
const match = trimmed.match(/\b(?:path|route|href|url)\s*:\s*(['"`])([^'"`]+)\1/);
|
|
270
|
-
if (!match) return undefined;
|
|
271
|
-
const routePath = match[2];
|
|
272
|
-
return nativeDeclaration(input, lineNumber, 'RouteRecord', 'route', `${context.name}.${routePath}`, {
|
|
273
|
-
owner: context.name,
|
|
274
|
-
routePath
|
|
275
|
-
}, true, {
|
|
276
|
-
regionKind: 'route',
|
|
277
|
-
metadata: { owner: context.name, routePath, initializerKind: 'object' }
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function jsPropertyInitializerKind(value) {
|
|
282
|
-
const text = String(value ?? '').trim();
|
|
283
|
-
if (/^(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(text)) return 'function';
|
|
284
|
-
if (text.startsWith('{')) return 'object';
|
|
285
|
-
if (text.startsWith('[')) return 'array';
|
|
286
|
-
if (/^['"`]/.test(text)) return 'string';
|
|
287
|
-
if (/^(?:true|false)\b/.test(text)) return 'boolean';
|
|
288
|
-
if (/^[0-9]/.test(text)) return 'number';
|
|
289
|
-
return 'expression';
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function jsPropertyRegionKind(context, propertyName, value) {
|
|
293
|
-
const named = jsRegionKindForDeclarationName(propertyName, value);
|
|
294
|
-
if (named) return named;
|
|
295
|
-
if (context.regionKind === 'route') return 'route';
|
|
296
|
-
if (context.regionKind === 'content') return 'content';
|
|
297
|
-
if (context.regionKind === 'config') return 'config';
|
|
298
|
-
return 'property';
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function jsContainerDelta(source) {
|
|
302
|
-
let delta = 0;
|
|
303
|
-
for (const char of String(source ?? '')) {
|
|
304
|
-
if (char === '{' || char === '[') delta += 1;
|
|
305
|
-
if (char === '}' || char === ']') delta -= 1;
|
|
306
|
-
}
|
|
307
|
-
return delta;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
116
|
export { scanJavaScriptLike };
|
package/package.json
CHANGED