@shapeshift-labs/frontier-lang-compiler 0.2.62 → 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 = treeSitterNamedText(node, ['string', 'string_fragment', 'identifier']) ?? shortNodeText(node);
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 (/variable_declarator|property_declaration|field_declaration/.test(kind)) {
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
- if (exact) return shortNodeText(exact);
34
- return treeSitterNamedFieldText(node, field);
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
- const text = shortNodeText(child);
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 = [];
@@ -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,215 +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 (/^(?:React\.)?(?:forwardRef|memo|lazy|observer)\s*(?:<[^>]+>)?\s*\(/.test(initializer)) return 'function';
200
- if (initializer.startsWith('{')) return 'object';
201
- if (initializer.startsWith('[')) return 'array';
202
- if (/^new\s+/.test(initializer)) return 'instance';
203
- if (/^['"`]/.test(initializer)) return 'string';
204
- if (/^(?:true|false)\b/.test(initializer)) return 'boolean';
205
- if (/^[0-9]/.test(initializer)) return 'number';
206
- return 'expression';
207
- }
208
-
209
- function jsVariableHasBody(initializerKind, declarationLine) {
210
- return initializerKind === 'function'
211
- || initializerKind === 'object'
212
- || initializerKind === 'array'
213
- || /\{/.test(String(declarationLine ?? ''));
214
- }
215
-
216
- function jsVariableSymbolKind(regionKind, initializerKind) {
217
- if (regionKind === 'route') return 'route';
218
- if (initializerKind === 'function') return 'function';
219
- return 'variable';
220
- }
221
-
222
- function jsRegionKindForDeclarationName(name, source = '') {
223
- const raw = `${name ?? ''} ${source ?? ''}`;
224
- const text = raw.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
225
- const compact = raw.toLowerCase();
226
- if (/\b(routes?|router|screens?|pages?|navigation|navitems?|menuitems?)\b/.test(text) || /(route|router|screen|page|navigation|navitem|menuitem)/.test(compact)) return 'route';
227
- 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';
228
- 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';
229
- return undefined;
230
- }
231
-
232
- function jsObjectPropertyDeclaration(input, lineNumber, trimmed, context) {
233
- if (/^[}\])]/.test(trimmed) || trimmed.startsWith('...')) return undefined;
234
- const methodMatch = trimmed.match(/^(?:(?:async|get|set)\s+)?(['"]?)([A-Za-z_$][\w$-]*)\1\s*\(([^)]*)\)\s*(?:[:\w\s<>\[\]]*)?(?:\{|=>|,|$)/);
235
- if (methodMatch && !jsControlKeyword(methodMatch[2])) {
236
- const name = `${context.name}.${methodMatch[2]}`;
237
- return nativeDeclaration(input, lineNumber, 'ObjectMethod', 'function', name, {
238
- owner: context.name,
239
- propertyName: methodMatch[2],
240
- parameters: splitParameters(methodMatch[3])
241
- }, true, {
242
- regionKind: jsPropertyRegionKind(context, methodMatch[2], 'function'),
243
- metadata: { owner: context.name, propertyName: methodMatch[2], initializerKind: 'function' }
244
- });
245
- }
246
- const propertyMatch = trimmed.match(/^(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$-]*))\s*:\s*(.+?)(?:,)?$/);
247
- if (!propertyMatch) return undefined;
248
- const propertyName = propertyMatch[2] ?? propertyMatch[3];
249
- if (!propertyName || jsControlKeyword(propertyName)) return undefined;
250
- const value = propertyMatch[4].trim();
251
- const initializerKind = jsPropertyInitializerKind(value);
252
- const functionLike = initializerKind === 'function';
253
- const name = `${context.name}.${propertyName}`;
254
- return nativeDeclaration(input, lineNumber, functionLike ? 'ObjectFunctionProperty' : 'ObjectProperty', functionLike ? 'function' : 'property', name, {
255
- owner: context.name,
256
- propertyName,
257
- initializerKind
258
- }, functionLike || initializerKind === 'object' || initializerKind === 'array', {
259
- regionKind: jsPropertyRegionKind(context, propertyName, value),
260
- metadata: {
261
- owner: context.name,
262
- propertyName,
263
- initializerKind
264
- }
265
- });
266
- }
267
-
268
- function jsRouteRecordDeclaration(input, lineNumber, trimmed, context) {
269
- if (context.regionKind !== 'route') return undefined;
270
- const match = trimmed.match(/\b(?:path|route|href|url)\s*:\s*(['"`])([^'"`]+)\1/);
271
- if (!match) return undefined;
272
- const routePath = match[2];
273
- return nativeDeclaration(input, lineNumber, 'RouteRecord', 'route', `${context.name}.${routePath}`, {
274
- owner: context.name,
275
- routePath
276
- }, true, {
277
- regionKind: 'route',
278
- metadata: { owner: context.name, routePath, initializerKind: 'object' }
279
- });
280
- }
281
-
282
- function jsPropertyInitializerKind(value) {
283
- const text = String(value ?? '').trim();
284
- if (/^(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(text)) return 'function';
285
- if (text.startsWith('{')) return 'object';
286
- if (text.startsWith('[')) return 'array';
287
- if (/^['"`]/.test(text)) return 'string';
288
- if (/^(?:true|false)\b/.test(text)) return 'boolean';
289
- if (/^[0-9]/.test(text)) return 'number';
290
- return 'expression';
291
- }
292
-
293
- function jsPropertyRegionKind(context, propertyName, value) {
294
- const named = jsRegionKindForDeclarationName(propertyName, value);
295
- if (named) return named;
296
- if (context.regionKind === 'route') return 'route';
297
- if (context.regionKind === 'content') return 'content';
298
- if (context.regionKind === 'config') return 'config';
299
- return 'property';
300
- }
301
-
302
- function jsContainerDelta(source) {
303
- let delta = 0;
304
- for (const char of String(source ?? '')) {
305
- if (char === '{' || char === '[') delta += 1;
306
- if (char === '}' || char === ']') delta -= 1;
307
- }
308
- return delta;
309
- }
310
-
311
116
  export { scanJavaScriptLike };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.62",
3
+ "version": "0.2.63",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",