@tsrx/core 0.1.36 → 0.1.38
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/package.json +1 -1
- package/src/plugin.js +110 -34
- package/src/transform/jsx/index.js +10 -1
- package/src/transform/segments.js +58 -1
- package/types/index.d.ts +17 -0
package/package.json
CHANGED
package/src/plugin.js
CHANGED
|
@@ -465,12 +465,17 @@ export function TSRXPlugin(config) {
|
|
|
465
465
|
return !!node && node.metadata?.templateMode !== 'template';
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Whether the `<` token at `this.start` opens the given raw-text element
|
|
470
|
+
* (`<style` or `<script` followed by `>`, `/`, or whitespace).
|
|
471
|
+
* @param {string} tagName
|
|
472
|
+
*/
|
|
473
|
+
#isRawTextOpeningTagStart(tagName) {
|
|
469
474
|
let index = this.start + 1;
|
|
470
475
|
if (this.input.charCodeAt(index) === CharCode.slash) return false;
|
|
471
|
-
if (this.input.slice(index, index +
|
|
476
|
+
if (this.input.slice(index, index + tagName.length) !== tagName) return false;
|
|
472
477
|
|
|
473
|
-
const after = this.input.charCodeAt(index +
|
|
478
|
+
const after = this.input.charCodeAt(index + tagName.length);
|
|
474
479
|
return (
|
|
475
480
|
after === CharCode.greaterThan ||
|
|
476
481
|
after === CharCode.slash ||
|
|
@@ -2051,34 +2056,22 @@ export function TSRXPlugin(config) {
|
|
|
2051
2056
|
}
|
|
2052
2057
|
|
|
2053
2058
|
/**
|
|
2059
|
+
* Read a raw-text element body: capture everything between the opening `>`
|
|
2060
|
+
* and the literal `</tagName>` verbatim (never as template markup),
|
|
2061
|
+
* synthesize the closing element, and restore the tokenizer state past it.
|
|
2062
|
+
* Shared by `<style>` and `<script>`.
|
|
2063
|
+
*
|
|
2054
2064
|
* @param {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} open
|
|
2055
|
-
* @param {AST.JSXStyleElement} node
|
|
2056
|
-
* @param {
|
|
2065
|
+
* @param {AST.JSXStyleElement | AST.TSRXJSXElement} node
|
|
2066
|
+
* @param {'style' | 'script'} tagName
|
|
2067
|
+
* @returns {string} The raw body text
|
|
2057
2068
|
*/
|
|
2058
|
-
#
|
|
2059
|
-
const
|
|
2060
|
-
if (!filename) {
|
|
2061
|
-
throw new Error(
|
|
2062
|
-
'<style> elements require a filename: pass one to parse so style scope hashes are unique per file.',
|
|
2063
|
-
);
|
|
2064
|
-
}
|
|
2069
|
+
#parseRawTextElement(open, node, tagName) {
|
|
2070
|
+
const closeTag = `</${tagName}>`;
|
|
2065
2071
|
const contentStart = open.end;
|
|
2066
2072
|
const input = this.input.slice(contentStart);
|
|
2067
|
-
const relativeCloseStart = input.indexOf(
|
|
2073
|
+
const relativeCloseStart = input.indexOf(closeTag);
|
|
2068
2074
|
const content = relativeCloseStart === -1 ? input : input.slice(0, relativeCloseStart);
|
|
2069
|
-
const parsedCss = parse_style(
|
|
2070
|
-
content,
|
|
2071
|
-
{
|
|
2072
|
-
filename,
|
|
2073
|
-
line: open.loc.start.line,
|
|
2074
|
-
column: open.loc.start.column,
|
|
2075
|
-
},
|
|
2076
|
-
{ loose: this.#loose },
|
|
2077
|
-
);
|
|
2078
|
-
|
|
2079
|
-
if (!insideHead) {
|
|
2080
|
-
node.metadata.styleScopeHash = parsedCss.hash;
|
|
2081
|
-
}
|
|
2082
2075
|
|
|
2083
2076
|
const newLines = content.match(regex_newline_characters)?.length;
|
|
2084
2077
|
if (newLines) {
|
|
@@ -2091,7 +2084,7 @@ export function TSRXPlugin(config) {
|
|
|
2091
2084
|
const closingLineInfo = acorn.getLineInfo(this.input, closingStart);
|
|
2092
2085
|
const closingStartLoc = new acorn.Position(closingLineInfo.line, closingLineInfo.column);
|
|
2093
2086
|
const nameStart = closingStart + 2;
|
|
2094
|
-
const nameEnd = nameStart +
|
|
2087
|
+
const nameEnd = nameStart + tagName.length;
|
|
2095
2088
|
const nameStartInfo = acorn.getLineInfo(this.input, nameStart);
|
|
2096
2089
|
const nameEndInfo = acorn.getLineInfo(this.input, nameEnd);
|
|
2097
2090
|
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
@@ -2100,14 +2093,14 @@ export function TSRXPlugin(config) {
|
|
|
2100
2093
|
new acorn.Position(nameStartInfo.line, nameStartInfo.column),
|
|
2101
2094
|
)
|
|
2102
2095
|
);
|
|
2103
|
-
name.name =
|
|
2096
|
+
name.name = tagName;
|
|
2104
2097
|
this.finishNodeAt(
|
|
2105
2098
|
name,
|
|
2106
2099
|
'JSXIdentifier',
|
|
2107
2100
|
nameEnd,
|
|
2108
2101
|
new acorn.Position(nameEndInfo.line, nameEndInfo.column),
|
|
2109
2102
|
);
|
|
2110
|
-
const closingEnd = closingStart +
|
|
2103
|
+
const closingEnd = closingStart + closeTag.length;
|
|
2111
2104
|
const closingEndInfo = acorn.getLineInfo(this.input, closingEnd);
|
|
2112
2105
|
const closingElement =
|
|
2113
2106
|
/** @type {ESTreeJSX.TSRXJSXClosingElement & AST.NodeWithLocation} */ (
|
|
@@ -2131,8 +2124,8 @@ export function TSRXPlugin(config) {
|
|
|
2131
2124
|
this.curLine = closingEndInfo.line;
|
|
2132
2125
|
this.lineStart = closingEnd - closingEndInfo.column;
|
|
2133
2126
|
if (insideTemplate && relativeCloseStart === 0) {
|
|
2134
|
-
// Acorn has already tokenized the adjacent
|
|
2135
|
-
// that close manually, so drop the stale
|
|
2127
|
+
// Acorn has already tokenized the adjacent closing tag; TSRX
|
|
2128
|
+
// synthesizes that close manually, so drop the stale tag context.
|
|
2136
2129
|
if (this.curContext() === tstc.tc_oTag) {
|
|
2137
2130
|
this.context.pop();
|
|
2138
2131
|
}
|
|
@@ -2153,15 +2146,85 @@ export function TSRXPlugin(config) {
|
|
|
2153
2146
|
} else {
|
|
2154
2147
|
this.#report_broken_markup_error(
|
|
2155
2148
|
open.end,
|
|
2156
|
-
|
|
2149
|
+
`Unclosed tag '<${tagName}>'. Expected '${closeTag}' before end of template.`,
|
|
2157
2150
|
);
|
|
2158
2151
|
node.unclosed = true;
|
|
2159
2152
|
}
|
|
2160
2153
|
|
|
2154
|
+
return content;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* @param {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} open
|
|
2159
|
+
* @param {AST.JSXStyleElement} node
|
|
2160
|
+
* @param {boolean} insideHead
|
|
2161
|
+
*/
|
|
2162
|
+
#parseStyleElement(open, node, insideHead) {
|
|
2163
|
+
const filename = this.#filename;
|
|
2164
|
+
if (!filename) {
|
|
2165
|
+
throw new Error(
|
|
2166
|
+
'<style> elements require a filename: pass one to parse so style scope hashes are unique per file.',
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
const content = this.#parseRawTextElement(open, node, 'style');
|
|
2170
|
+
const parsedCss = parse_style(
|
|
2171
|
+
content,
|
|
2172
|
+
{
|
|
2173
|
+
filename,
|
|
2174
|
+
line: open.loc.start.line,
|
|
2175
|
+
column: open.loc.start.column,
|
|
2176
|
+
},
|
|
2177
|
+
{ loose: this.#loose },
|
|
2178
|
+
);
|
|
2179
|
+
|
|
2180
|
+
if (!insideHead) {
|
|
2181
|
+
node.metadata.styleScopeHash = parsedCss.hash;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2161
2184
|
node.css = content;
|
|
2162
2185
|
node.children = [parsedCss];
|
|
2163
2186
|
}
|
|
2164
2187
|
|
|
2188
|
+
/**
|
|
2189
|
+
* Parse a `<script>` element as a raw-text element, exactly like `<style>`:
|
|
2190
|
+
* the body is captured verbatim as `node.content`, letting authors write real
|
|
2191
|
+
* JS/TS (with `<`, `{`, `}`) and letting the editor treat the body as an
|
|
2192
|
+
* embedded TypeScript/JavaScript document.
|
|
2193
|
+
*
|
|
2194
|
+
* Mirroring `JSXStyleElement` (raw `css` string + parsed children), the body
|
|
2195
|
+
* is exposed twice: verbatim on `content`, and as a single `JSXText` child so
|
|
2196
|
+
* generic element paths (factory targets, static hoisting, printers) emit the
|
|
2197
|
+
* body without knowing about raw-text elements. Consumers that handle
|
|
2198
|
+
* `content` directly (the Ripple transforms, the prettier plugin) must skip
|
|
2199
|
+
* the children instead of emitting both.
|
|
2200
|
+
*
|
|
2201
|
+
* @param {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} open
|
|
2202
|
+
* @param {AST.TSRXJSXElement} node
|
|
2203
|
+
*/
|
|
2204
|
+
#parseScriptElement(open, node) {
|
|
2205
|
+
const content = this.#parseRawTextElement(open, node, 'script');
|
|
2206
|
+
node.content = content;
|
|
2207
|
+
node.children = [];
|
|
2208
|
+
|
|
2209
|
+
if (content.length > 0) {
|
|
2210
|
+
const bodyStartInfo = acorn.getLineInfo(this.input, open.end);
|
|
2211
|
+
const text = /** @type {ESTreeJSX.JSXText} */ (
|
|
2212
|
+
this.startNodeAt(open.end, new acorn.Position(bodyStartInfo.line, bodyStartInfo.column))
|
|
2213
|
+
);
|
|
2214
|
+
text.value = content;
|
|
2215
|
+
text.raw = content;
|
|
2216
|
+
const bodyEnd = open.end + content.length;
|
|
2217
|
+
const bodyEndInfo = acorn.getLineInfo(this.input, bodyEnd);
|
|
2218
|
+
this.finishNodeAt(
|
|
2219
|
+
text,
|
|
2220
|
+
'JSXText',
|
|
2221
|
+
bodyEnd,
|
|
2222
|
+
new acorn.Position(bodyEndInfo.line, bodyEndInfo.column),
|
|
2223
|
+
);
|
|
2224
|
+
node.children = [/** @type {AST.Node} */ (text)];
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2165
2228
|
#parseNativeTemplateExpressionContainer() {
|
|
2166
2229
|
const allow_trailing_semicolon = this.#allowExpressionContainerTrailingSemicolon;
|
|
2167
2230
|
this.#allowExpressionContainerTrailingSemicolon = true;
|
|
@@ -2888,9 +2951,18 @@ export function TSRXPlugin(config) {
|
|
|
2888
2951
|
this.exprAllowed = false;
|
|
2889
2952
|
}
|
|
2890
2953
|
|
|
2954
|
+
// A `/` or `#` in template TEXT position joins the text run as a
|
|
2955
|
+
// literal character (`<div>5/2</div>`, `<div>#tag</div>`). This must
|
|
2956
|
+
// not fire in the JS positions that can sit under a template element
|
|
2957
|
+
// on the path: inside a `{ … }` expression container (an attribute or
|
|
2958
|
+
// child expression — `<rect x={a / 2}/>`, `{this.#x}`) or inside a
|
|
2959
|
+
// control-flow directive header (`@if (a / 2 > 1)`), where `/` is
|
|
2960
|
+
// division and `#` is a private-field access.
|
|
2891
2961
|
if (
|
|
2892
2962
|
(code === CharCode.numberSign || code === CharCode.slash) &&
|
|
2893
2963
|
this.#functionBodyDepth === 0 &&
|
|
2964
|
+
this.#jsxExpressionContainerDepth === 0 &&
|
|
2965
|
+
!this.#readingJSXControlFlowHeader &&
|
|
2894
2966
|
this.#isNativeTemplateNode(this.#path.at(-1)) &&
|
|
2895
2967
|
!(
|
|
2896
2968
|
code === CharCode.slash &&
|
|
@@ -4161,7 +4233,7 @@ export function TSRXPlugin(config) {
|
|
|
4161
4233
|
*/
|
|
4162
4234
|
jsx_parseElement() {
|
|
4163
4235
|
if (this.#forceScriptJSXElementDepth > 0 || this.#isInsideNativeTemplateScriptSection()) {
|
|
4164
|
-
if (this.#
|
|
4236
|
+
if (this.#isRawTextOpeningTagStart('style') || this.#isRawTextOpeningTagStart('script')) {
|
|
4165
4237
|
this.next();
|
|
4166
4238
|
return /** @type {ESTreeJSX.JSXElement | AST.JSXStyleElement} */ (
|
|
4167
4239
|
/** @type {unknown} */ (this.parseElement())
|
|
@@ -4294,6 +4366,7 @@ export function TSRXPlugin(config) {
|
|
|
4294
4366
|
const tag_name = open.name ? this.getElementName(open.name) : null;
|
|
4295
4367
|
const is_dynamic = this.#isDynamicJSXElementName(open.name);
|
|
4296
4368
|
const is_style = tag_name === 'style';
|
|
4369
|
+
const is_script = tag_name === 'script';
|
|
4297
4370
|
const inside_head = this.#path.findLast((n) => this.#isNativeElementNamed(n, 'head'));
|
|
4298
4371
|
|
|
4299
4372
|
// Fragments (<>) produce JSXOpeningFragment with no `name` property
|
|
@@ -4348,6 +4421,9 @@ export function TSRXPlugin(config) {
|
|
|
4348
4421
|
} else if (is_style) {
|
|
4349
4422
|
this.#parseStyleElement(open, /** @type {AST.JSXStyleElement} */ (node), !!inside_head);
|
|
4350
4423
|
this.#path.pop();
|
|
4424
|
+
} else if (is_script) {
|
|
4425
|
+
this.#parseScriptElement(open, /** @type {AST.TSRXJSXElement} */ (node));
|
|
4426
|
+
this.#path.pop();
|
|
4351
4427
|
} else {
|
|
4352
4428
|
this.#parseNativeTemplateBody(node, /** @type {AST.Node[]} */ (node.children), {
|
|
4353
4429
|
enterScope: true,
|
|
@@ -4391,7 +4467,7 @@ export function TSRXPlugin(config) {
|
|
|
4391
4467
|
}
|
|
4392
4468
|
}
|
|
4393
4469
|
|
|
4394
|
-
if (is_style && /** @type {AST.JSXStyleElement} */ (node).closingElement) {
|
|
4470
|
+
if ((is_style || is_script) && /** @type {AST.JSXStyleElement} */ (node).closingElement) {
|
|
4395
4471
|
const closing = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
4396
4472
|
/** @type {AST.JSXStyleElement} */ (node).closingElement
|
|
4397
4473
|
);
|
|
@@ -3634,7 +3634,16 @@ function to_jsx_element(
|
|
|
3634
3634
|
transform_context,
|
|
3635
3635
|
node,
|
|
3636
3636
|
);
|
|
3637
|
-
|
|
3637
|
+
let walked_children = node.children || [];
|
|
3638
|
+
// A raw-text `<script>` body (mirrored by the parser as a JSXText child of
|
|
3639
|
+
// `node.content`) must not appear in the type-only editor TSX: raw JS/TS
|
|
3640
|
+
// (`{`, `<`) doesn't lex as JSX text there and would surface bogus syntactic
|
|
3641
|
+
// diagnostics. The embedded TS document built from `scriptMappings` covers
|
|
3642
|
+
// the body in the editor; runtime output keeps the text child.
|
|
3643
|
+
if (transform_context.typeOnly && typeof node.content === 'string') {
|
|
3644
|
+
walked_children = [];
|
|
3645
|
+
raw_children = [];
|
|
3646
|
+
}
|
|
3638
3647
|
let selfClosing = !!source_opening.selfClosing;
|
|
3639
3648
|
let children;
|
|
3640
3649
|
const child_transform = transform_context.platform.hooks?.transformElementChildren?.(
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
content: string,
|
|
23
23
|
id: string,
|
|
24
24
|
}} CssSourceRegion;
|
|
25
|
+
@typedef {{
|
|
26
|
+
start: number,
|
|
27
|
+
end: number,
|
|
28
|
+
content: string,
|
|
29
|
+
id: string,
|
|
30
|
+
}} ScriptSourceRegion;
|
|
25
31
|
@typedef {{
|
|
26
32
|
source: string | null | undefined;
|
|
27
33
|
generated: string;
|
|
@@ -130,12 +136,40 @@ function get_style_region_id(hash, fallback) {
|
|
|
130
136
|
* @param {{
|
|
131
137
|
* regions: CssSourceRegion[],
|
|
132
138
|
* css_element_info: CssElementInfo,
|
|
139
|
+
* script_regions: ScriptSourceRegion[],
|
|
133
140
|
* }} param2
|
|
134
141
|
* @returns {void}
|
|
135
142
|
*/
|
|
136
|
-
function visit_source_ast(ast, src_line_offsets, { regions, css_element_info }) {
|
|
143
|
+
function visit_source_ast(ast, src_line_offsets, { regions, css_element_info, script_regions }) {
|
|
137
144
|
let region_id = 0;
|
|
145
|
+
let script_region_id = 0;
|
|
138
146
|
walk(ast, null, {
|
|
147
|
+
JSXElement(node, context) {
|
|
148
|
+
// Raw-text `<script>` elements carry their body verbatim on `node.content`
|
|
149
|
+
// (see the parser's `#parseScriptElement`). Expose that body as an embedded
|
|
150
|
+
// TypeScript region so the editor can offer intellisense inside it,
|
|
151
|
+
// mirroring how `<style>` bodies become embedded CSS regions below. The
|
|
152
|
+
// editor treats every script body as TypeScript (a superset of JS, matching
|
|
153
|
+
// the TextMate/tree-sitter/prettier treatment); the `type` attribute only
|
|
154
|
+
// matters to the runtime transforms, which read it off the AST.
|
|
155
|
+
const element_name = node.openingElement?.name;
|
|
156
|
+
const content = node.content;
|
|
157
|
+
if (
|
|
158
|
+
element_name?.type === 'JSXIdentifier' &&
|
|
159
|
+
element_name.name === 'script' &&
|
|
160
|
+
typeof content === 'string'
|
|
161
|
+
) {
|
|
162
|
+
const start = /** @type {AST.NodeWithLocation} */ (node.openingElement).end;
|
|
163
|
+
script_regions.push({
|
|
164
|
+
start,
|
|
165
|
+
end: start + content.length,
|
|
166
|
+
content,
|
|
167
|
+
id: `script_${script_region_id++}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
context.next();
|
|
172
|
+
},
|
|
139
173
|
JSXStyleElement(node, context) {
|
|
140
174
|
if (node.css) {
|
|
141
175
|
const openLoc = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
|
|
@@ -370,10 +404,13 @@ export function convert_source_map_to_mappings(
|
|
|
370
404
|
const css_regions = [];
|
|
371
405
|
/** @type {CssElementInfo} */
|
|
372
406
|
const css_element_info = new Map();
|
|
407
|
+
/** @type {ScriptSourceRegion[]} */
|
|
408
|
+
const script_regions = [];
|
|
373
409
|
|
|
374
410
|
visit_source_ast(ast_from_source, src_line_offsets, {
|
|
375
411
|
regions: css_regions,
|
|
376
412
|
css_element_info,
|
|
413
|
+
script_regions,
|
|
377
414
|
});
|
|
378
415
|
|
|
379
416
|
/** @type {Map<string, number>} */
|
|
@@ -2320,10 +2357,30 @@ export function convert_source_map_to_mappings(
|
|
|
2320
2357
|
});
|
|
2321
2358
|
}
|
|
2322
2359
|
|
|
2360
|
+
/** @type {CodeMapping[]} */
|
|
2361
|
+
const scriptMappings = [];
|
|
2362
|
+
for (let i = 0; i < script_regions.length; i++) {
|
|
2363
|
+
const region = script_regions[i];
|
|
2364
|
+
scriptMappings.push({
|
|
2365
|
+
sourceOffsets: [region.start],
|
|
2366
|
+
generatedOffsets: [0],
|
|
2367
|
+
lengths: [region.content.length],
|
|
2368
|
+
generatedLengths: [region.content.length],
|
|
2369
|
+
data: {
|
|
2370
|
+
...mapping_data,
|
|
2371
|
+
customData: {
|
|
2372
|
+
embeddedId: region.id,
|
|
2373
|
+
content: region.content,
|
|
2374
|
+
},
|
|
2375
|
+
},
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2323
2379
|
return {
|
|
2324
2380
|
code: generated_code,
|
|
2325
2381
|
mappings,
|
|
2326
2382
|
cssMappings,
|
|
2383
|
+
scriptMappings,
|
|
2327
2384
|
};
|
|
2328
2385
|
}
|
|
2329
2386
|
|
package/types/index.d.ts
CHANGED
|
@@ -327,6 +327,15 @@ declare module 'estree' {
|
|
|
327
327
|
isDynamic?: boolean;
|
|
328
328
|
/** Loose-mode recovery: the element was never closed. */
|
|
329
329
|
unclosed?: boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Raw-text `<script>` body captured verbatim by the parser's
|
|
332
|
+
* `#parseScriptElement` (analogous to {@link JSXStyleElement.css}). Present only
|
|
333
|
+
* on `<script>` elements that have a body. The parser also mirrors the body as
|
|
334
|
+
* a single `JSXText` child so generic element consumers emit it; consumers that
|
|
335
|
+
* handle `content` directly (the Ripple transforms, the prettier plugin, the
|
|
336
|
+
* type-only editor output) skip the children instead of emitting both.
|
|
337
|
+
*/
|
|
338
|
+
content?: string;
|
|
330
339
|
/**
|
|
331
340
|
* The parser emits {@link TSRXJSXChild}; the compile pre-passes lower
|
|
332
341
|
* template children in place (retyped directives, code-block IIFEs,
|
|
@@ -1687,6 +1696,14 @@ export interface VolarMappingsResult {
|
|
|
1687
1696
|
code: string;
|
|
1688
1697
|
mappings: CodeMapping[];
|
|
1689
1698
|
cssMappings: CodeMapping[];
|
|
1699
|
+
/**
|
|
1700
|
+
* Embedded raw-text `<script>` body regions, each mapped to its source range so
|
|
1701
|
+
* the editor can treat the body as an embedded TypeScript document (TS is a
|
|
1702
|
+
* superset of JS, so every body is treated as TypeScript regardless of the
|
|
1703
|
+
* `type` attribute — that attribute only matters to the runtime transforms).
|
|
1704
|
+
* Mirrors {@link cssMappings} for `<style>` bodies.
|
|
1705
|
+
*/
|
|
1706
|
+
scriptMappings: CodeMapping[];
|
|
1690
1707
|
errors: CompileError[];
|
|
1691
1708
|
sourceAst: AST.Program;
|
|
1692
1709
|
}
|