@tsrx/core 0.1.62 → 0.1.64
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 +2 -2
- package/src/analyze/index.js +65 -18
- package/src/analyze/validation.js +19 -0
- package/src/diagnostics.js +1 -0
- package/src/index.js +3 -0
- package/src/plugin.js +313 -39
- package/src/source-map-utils.js +21 -66
- package/src/transform/jsx/index.js +48 -24
- package/src/transform/lazy.js +527 -72
- package/src/transform/segments.js +17 -4
- package/src/utils/ast.js +90 -7
- package/types/index.d.ts +2 -0
- package/types/parse.d.ts +7 -0
package/src/plugin.js
CHANGED
|
@@ -49,6 +49,60 @@ const CharCode = Object.freeze({
|
|
|
49
49
|
closeBrace: 125,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
/** @type {WeakMap<Parse.Parser, number[]>} */
|
|
53
|
+
const parser_line_starts = new WeakMap();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve an offset without rescanning the source from the beginning on every
|
|
57
|
+
* TSRX tokenizer rewind. Acorn's `getLineInfo` is linear in the offset; the
|
|
58
|
+
* parser calls this path often enough that large modules otherwise pay a
|
|
59
|
+
* quadratic location-tracking cost.
|
|
60
|
+
*
|
|
61
|
+
* @param {Parse.Parser} parser
|
|
62
|
+
* @param {number} offset
|
|
63
|
+
* @returns {acorn.Position}
|
|
64
|
+
*/
|
|
65
|
+
function get_line_info(parser, offset) {
|
|
66
|
+
let starts = parser_line_starts.get(parser);
|
|
67
|
+
if (starts === undefined) {
|
|
68
|
+
starts = [0];
|
|
69
|
+
for (let i = 0; i < parser.input.length; i++) {
|
|
70
|
+
const ch = parser.input.charCodeAt(i);
|
|
71
|
+
if (ch === CharCode.carriageReturn && parser.input.charCodeAt(i + 1) === CharCode.lineFeed) {
|
|
72
|
+
i++;
|
|
73
|
+
starts.push(i + 1);
|
|
74
|
+
} else if (
|
|
75
|
+
ch === CharCode.lineFeed ||
|
|
76
|
+
ch === CharCode.carriageReturn ||
|
|
77
|
+
ch === 0x2028 ||
|
|
78
|
+
ch === 0x2029
|
|
79
|
+
) {
|
|
80
|
+
starts.push(i + 1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
parser_line_starts.set(parser, starts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let low = 0;
|
|
87
|
+
let high = starts.length;
|
|
88
|
+
while (low + 1 < high) {
|
|
89
|
+
const middle = (low + high) >>> 1;
|
|
90
|
+
if (starts[middle] <= offset) low = middle;
|
|
91
|
+
else high = middle;
|
|
92
|
+
}
|
|
93
|
+
// `getLineInfo(input, offset)` treats a CR as a complete line break when
|
|
94
|
+
// `offset` points at the LF of a CRLF pair, even though later offsets treat
|
|
95
|
+
// the pair as one terminator. Preserve that boundary behavior exactly.
|
|
96
|
+
if (
|
|
97
|
+
offset > 0 &&
|
|
98
|
+
parser.input.charCodeAt(offset) === CharCode.lineFeed &&
|
|
99
|
+
parser.input.charCodeAt(offset - 1) === CharCode.carriageReturn
|
|
100
|
+
) {
|
|
101
|
+
return new acorn.Position(low + 2, 0);
|
|
102
|
+
}
|
|
103
|
+
return new acorn.Position(low + 1, offset - starts[low]);
|
|
104
|
+
}
|
|
105
|
+
|
|
52
106
|
// Transparent wrappers to look through when validating a dynamic tag
|
|
53
107
|
// expression (`<{expr}>`), and syntax that disqualifies one outright.
|
|
54
108
|
const DYNAMIC_TAG_WRAPPER_TYPES = new Set([
|
|
@@ -108,6 +162,88 @@ function get_argument_clash_reported_names(check_clashes) {
|
|
|
108
162
|
return reported_names;
|
|
109
163
|
}
|
|
110
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The position of a pending `&{…}`/`&[…]` lazy binding pattern recorded on a
|
|
167
|
+
* DestructuringErrors context, or -1. Acorn's DestructuringErrors class knows
|
|
168
|
+
* nothing about the field, so absence means none was recorded.
|
|
169
|
+
*
|
|
170
|
+
* @param {Parse.DestructuringErrors | undefined | null} refDestructuringErrors
|
|
171
|
+
* @returns {number}
|
|
172
|
+
*/
|
|
173
|
+
function get_lazy_binding_pos(refDestructuringErrors) {
|
|
174
|
+
return refDestructuringErrors?.lazyBindingPos ?? -1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @type {ReadonlySet<string>} */
|
|
178
|
+
const lazy_target_wrapper_types = new Set([
|
|
179
|
+
'ParenthesizedExpression',
|
|
180
|
+
'TSAsExpression',
|
|
181
|
+
'TSSatisfiesExpression',
|
|
182
|
+
'TSNonNullExpression',
|
|
183
|
+
'TSTypeAssertion',
|
|
184
|
+
'TSTypeCastExpression',
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Whether the lazy binding pattern recorded at `pos` (the position of its `&`,
|
|
189
|
+
* one character before the pattern node) sits in a pattern-forming position of
|
|
190
|
+
* `node` — a slot that toAssignable converts into a binding or assignment
|
|
191
|
+
* target. A lazy pattern reached only through an expression position (for
|
|
192
|
+
* example as a member expression's object in `&{ a }.b = x`) is an expression
|
|
193
|
+
* use and must keep its pending error. `node` is the pre-conversion tree, so
|
|
194
|
+
* both the expression and pattern spellings of each slot appear here.
|
|
195
|
+
*
|
|
196
|
+
* @param {AST.Node | null | undefined} node
|
|
197
|
+
* @param {number} pos
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
function pattern_position_contains_lazy(node, pos) {
|
|
201
|
+
if (!node || typeof node !== 'object') return false;
|
|
202
|
+
if (
|
|
203
|
+
/** @type {AST.ObjectPattern | AST.ArrayPattern} */ (node).lazy &&
|
|
204
|
+
/** @type {number} */ (node.start) === pos + 1
|
|
205
|
+
) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
// Parentheses and the TypeScript expression wrappers acorn-typescript's
|
|
209
|
+
// toAssignable unwraps when converting a target (`[&{ a }!] = arr`) — the
|
|
210
|
+
// wrapped node stays in a pattern-forming position. TSTypeCastExpression is
|
|
211
|
+
// internal to acorn-typescript, hence the string set rather than switch
|
|
212
|
+
// cases over the ESTree union.
|
|
213
|
+
if (lazy_target_wrapper_types.has(node.type)) {
|
|
214
|
+
return pattern_position_contains_lazy(
|
|
215
|
+
/** @type {{ expression: AST.Node }} */ (/** @type {unknown} */ (node)).expression,
|
|
216
|
+
pos,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
switch (node.type) {
|
|
220
|
+
case 'ObjectExpression':
|
|
221
|
+
case 'ObjectPattern':
|
|
222
|
+
return node.properties.some((property) =>
|
|
223
|
+
pattern_position_contains_lazy(
|
|
224
|
+
property.type === 'Property'
|
|
225
|
+
? /** @type {AST.Node} */ (property.value)
|
|
226
|
+
: property.argument,
|
|
227
|
+
pos,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
case 'ArrayExpression':
|
|
231
|
+
case 'ArrayPattern':
|
|
232
|
+
return node.elements.some(
|
|
233
|
+
(element) => element && pattern_position_contains_lazy(element, pos),
|
|
234
|
+
);
|
|
235
|
+
case 'AssignmentExpression':
|
|
236
|
+
return node.operator === '=' && pattern_position_contains_lazy(node.left, pos);
|
|
237
|
+
case 'AssignmentPattern':
|
|
238
|
+
return pattern_position_contains_lazy(node.left, pos);
|
|
239
|
+
case 'SpreadElement':
|
|
240
|
+
case 'RestElement':
|
|
241
|
+
return pattern_position_contains_lazy(node.argument, pos);
|
|
242
|
+
default:
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
111
247
|
/**
|
|
112
248
|
* A `<` opens a tag only when the character after it can begin one: `/` for a
|
|
113
249
|
* closing tag, `>` for a fragment, `{` for a dynamic tag, or an element or
|
|
@@ -276,6 +412,8 @@ export function TSRXPlugin(config) {
|
|
|
276
412
|
#errors = undefined;
|
|
277
413
|
/** @type {string | null} */
|
|
278
414
|
#filename = null;
|
|
415
|
+
/** @type {WeakMap<object, { names: Set<string>, lexicalLength: number, varLength: number }>} */
|
|
416
|
+
#localExportNamesByScope = new WeakMap();
|
|
279
417
|
#functionBodyDepth = 0;
|
|
280
418
|
#allowExpressionContainerTrailingSemicolon = false;
|
|
281
419
|
#jsxAttributeValueExpressionDepth = 0;
|
|
@@ -625,12 +763,12 @@ export function TSRXPlugin(config) {
|
|
|
625
763
|
}
|
|
626
764
|
if (this.#isTemplateBlockCommentStart(index)) {
|
|
627
765
|
const comment_start = index;
|
|
628
|
-
const comment_start_loc =
|
|
766
|
+
const comment_start_loc = get_line_info(this, comment_start);
|
|
629
767
|
const close = this.input.indexOf('*/', index + 2);
|
|
630
768
|
const value_end = close === -1 ? this.input.length : close;
|
|
631
769
|
index = close === -1 ? this.input.length : close + 2;
|
|
632
770
|
if (this.options.onComment && comment_start >= token_end) {
|
|
633
|
-
const comment_end_loc =
|
|
771
|
+
const comment_end_loc = get_line_info(this, index);
|
|
634
772
|
this.options.onComment(
|
|
635
773
|
true,
|
|
636
774
|
this.input.slice(comment_start + 2, value_end),
|
|
@@ -657,7 +795,7 @@ export function TSRXPlugin(config) {
|
|
|
657
795
|
index++;
|
|
658
796
|
}
|
|
659
797
|
|
|
660
|
-
const endLoc =
|
|
798
|
+
const endLoc = get_line_info(this, index);
|
|
661
799
|
const node = /** @type {ESTreeJSX.JSXText} */ (this.startNodeAt(start, this.startLoc));
|
|
662
800
|
node.value = value;
|
|
663
801
|
node.raw = this.input.slice(start, index);
|
|
@@ -709,7 +847,7 @@ export function TSRXPlugin(config) {
|
|
|
709
847
|
}
|
|
710
848
|
}
|
|
711
849
|
if (!has_newline) return;
|
|
712
|
-
const loc =
|
|
850
|
+
const loc = get_line_info(this, index);
|
|
713
851
|
this.start = index;
|
|
714
852
|
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
715
853
|
if (this.pos <= index) {
|
|
@@ -738,8 +876,8 @@ export function TSRXPlugin(config) {
|
|
|
738
876
|
this.input.slice(start + 2, end),
|
|
739
877
|
start,
|
|
740
878
|
end,
|
|
741
|
-
|
|
742
|
-
|
|
879
|
+
get_line_info(this, start),
|
|
880
|
+
get_line_info(this, end),
|
|
743
881
|
metadata,
|
|
744
882
|
);
|
|
745
883
|
}
|
|
@@ -852,7 +990,7 @@ export function TSRXPlugin(config) {
|
|
|
852
990
|
}
|
|
853
991
|
this.pos = start;
|
|
854
992
|
this.start = start;
|
|
855
|
-
this.startLoc =
|
|
993
|
+
this.startLoc = get_line_info(this, start);
|
|
856
994
|
this.exprAllowed = true;
|
|
857
995
|
this.#suppressTemplateRawTextToken = true;
|
|
858
996
|
this.next();
|
|
@@ -947,7 +1085,7 @@ export function TSRXPlugin(config) {
|
|
|
947
1085
|
index++;
|
|
948
1086
|
}
|
|
949
1087
|
|
|
950
|
-
const endLoc =
|
|
1088
|
+
const endLoc = get_line_info(this, index);
|
|
951
1089
|
const node = /** @type {ESTreeJSX.JSXText} */ (this.startNodeAt(start, this.startLoc));
|
|
952
1090
|
node.value = this.input.slice(start, index);
|
|
953
1091
|
node.raw = node.value;
|
|
@@ -1111,7 +1249,7 @@ export function TSRXPlugin(config) {
|
|
|
1111
1249
|
const start = this.pos;
|
|
1112
1250
|
const index = this.#templateRawTextEnd(start);
|
|
1113
1251
|
|
|
1114
|
-
const endLoc =
|
|
1252
|
+
const endLoc = get_line_info(this, index);
|
|
1115
1253
|
const value = this.input.slice(start, index);
|
|
1116
1254
|
if (value.match(regex_newline_characters)) {
|
|
1117
1255
|
this.curLine = endLoc.line;
|
|
@@ -1310,7 +1448,7 @@ export function TSRXPlugin(config) {
|
|
|
1310
1448
|
let node;
|
|
1311
1449
|
try {
|
|
1312
1450
|
if (this.type === tstt.jsxText || this.type === tstt.jsxName) {
|
|
1313
|
-
const loc =
|
|
1451
|
+
const loc = get_line_info(this, this.start);
|
|
1314
1452
|
this.pos = this.start;
|
|
1315
1453
|
this.curLine = loc.line;
|
|
1316
1454
|
this.lineStart = this.start - loc.column;
|
|
@@ -1345,7 +1483,7 @@ export function TSRXPlugin(config) {
|
|
|
1345
1483
|
// (a preceding setup statement's context restore can strip the JSX tag
|
|
1346
1484
|
// contexts the trailing `<`/`@` token first pushed).
|
|
1347
1485
|
if (this.start !== at_index) {
|
|
1348
|
-
const loc =
|
|
1486
|
+
const loc = get_line_info(this, at_index);
|
|
1349
1487
|
this.pos = at_index;
|
|
1350
1488
|
this.start = at_index;
|
|
1351
1489
|
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
@@ -1457,7 +1595,7 @@ export function TSRXPlugin(config) {
|
|
|
1457
1595
|
if (this.curContext() !== b_stat) {
|
|
1458
1596
|
this.context.push(b_stat);
|
|
1459
1597
|
}
|
|
1460
|
-
const braceLoc =
|
|
1598
|
+
const braceLoc = get_line_info(this, braceStart);
|
|
1461
1599
|
this.pos = braceStart;
|
|
1462
1600
|
this.start = braceStart;
|
|
1463
1601
|
this.startLoc = new acorn.Position(braceLoc.line, braceLoc.column);
|
|
@@ -1514,6 +1652,25 @@ export function TSRXPlugin(config) {
|
|
|
1514
1652
|
* @type {Parse.Parser['parseExprAtom']}
|
|
1515
1653
|
*/
|
|
1516
1654
|
parseExprAtom(refDestructuringErrors, forInit, forNew) {
|
|
1655
|
+
// A `&{…}`/`&[…]` lazy binding pattern is only meaningful where the
|
|
1656
|
+
// expression may still turn out to be a binding or assignment target —
|
|
1657
|
+
// an arrow parameter list, a destructuring assignment target, or a
|
|
1658
|
+
// for–of/for–in loop target. Those are exactly the positions Acorn
|
|
1659
|
+
// parses with a DestructuringErrors context, so parse the pattern there
|
|
1660
|
+
// and record it as a pending error the same way Acorn treats `{a = b}`
|
|
1661
|
+
// shorthand: pattern conversion accepts the node as-is, while contexts
|
|
1662
|
+
// that remain expressions raise in checkExpressionErrors.
|
|
1663
|
+
if (
|
|
1664
|
+
refDestructuringErrors &&
|
|
1665
|
+
this.type === tt.bitwiseAND &&
|
|
1666
|
+
(this.input.charCodeAt(this.end) === CharCode.openBrace ||
|
|
1667
|
+
this.input.charCodeAt(this.end) === CharCode.openBracket)
|
|
1668
|
+
) {
|
|
1669
|
+
if (get_lazy_binding_pos(refDestructuringErrors) < 0) {
|
|
1670
|
+
refDestructuringErrors.lazyBindingPos = this.start;
|
|
1671
|
+
}
|
|
1672
|
+
return /** @type {AST.Expression} */ (/** @type {unknown} */ (this.parseBindingAtom()));
|
|
1673
|
+
}
|
|
1517
1674
|
// A token already consumed as JSX text (a script-mode element child) must
|
|
1518
1675
|
// stay text even when it happens to begin at an `@` — otherwise whether
|
|
1519
1676
|
// `@if` parses as a directive would depend on leading whitespace.
|
|
@@ -1578,7 +1735,7 @@ export function TSRXPlugin(config) {
|
|
|
1578
1735
|
const keywordStart = start + 1;
|
|
1579
1736
|
this.pos = keywordStart;
|
|
1580
1737
|
this.start = keywordStart;
|
|
1581
|
-
this.startLoc =
|
|
1738
|
+
this.startLoc = get_line_info(this, keywordStart);
|
|
1582
1739
|
this.curLine = this.startLoc.line;
|
|
1583
1740
|
this.lineStart = keywordStart - this.startLoc.column;
|
|
1584
1741
|
this.#filterTemplateScriptContexts();
|
|
@@ -1711,13 +1868,13 @@ export function TSRXPlugin(config) {
|
|
|
1711
1868
|
start: keywordStart,
|
|
1712
1869
|
end: keywordEnd,
|
|
1713
1870
|
loc: {
|
|
1714
|
-
start:
|
|
1715
|
-
end:
|
|
1871
|
+
start: get_line_info(this, keywordStart),
|
|
1872
|
+
end: get_line_info(this, keywordEnd),
|
|
1716
1873
|
},
|
|
1717
1874
|
};
|
|
1718
1875
|
this.pos = wordStart;
|
|
1719
1876
|
this.start = wordStart;
|
|
1720
|
-
this.startLoc =
|
|
1877
|
+
this.startLoc = get_line_info(this, wordStart);
|
|
1721
1878
|
this.curLine = this.startLoc.line;
|
|
1722
1879
|
this.lineStart = wordStart - this.startLoc.column;
|
|
1723
1880
|
this.#filterTemplateScriptContexts();
|
|
@@ -1753,7 +1910,7 @@ export function TSRXPlugin(config) {
|
|
|
1753
1910
|
|
|
1754
1911
|
this.pos = wordStart;
|
|
1755
1912
|
this.start = wordStart;
|
|
1756
|
-
this.startLoc =
|
|
1913
|
+
this.startLoc = get_line_info(this, wordStart);
|
|
1757
1914
|
this.curLine = this.startLoc.line;
|
|
1758
1915
|
this.lineStart = wordStart - this.startLoc.column;
|
|
1759
1916
|
this.#filterTemplateScriptContexts();
|
|
@@ -2108,12 +2265,12 @@ export function TSRXPlugin(config) {
|
|
|
2108
2265
|
|
|
2109
2266
|
if (relativeCloseStart !== -1) {
|
|
2110
2267
|
const closingStart = contentStart + content.length;
|
|
2111
|
-
const closingLineInfo =
|
|
2268
|
+
const closingLineInfo = get_line_info(this, closingStart);
|
|
2112
2269
|
const closingStartLoc = new acorn.Position(closingLineInfo.line, closingLineInfo.column);
|
|
2113
2270
|
const nameStart = closingStart + 2;
|
|
2114
2271
|
const nameEnd = nameStart + tagName.length;
|
|
2115
|
-
const nameStartInfo =
|
|
2116
|
-
const nameEndInfo =
|
|
2272
|
+
const nameStartInfo = get_line_info(this, nameStart);
|
|
2273
|
+
const nameEndInfo = get_line_info(this, nameEnd);
|
|
2117
2274
|
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
2118
2275
|
this.startNodeAt(
|
|
2119
2276
|
nameStart,
|
|
@@ -2128,7 +2285,7 @@ export function TSRXPlugin(config) {
|
|
|
2128
2285
|
new acorn.Position(nameEndInfo.line, nameEndInfo.column),
|
|
2129
2286
|
);
|
|
2130
2287
|
const closingEnd = closingStart + closeTag.length;
|
|
2131
|
-
const closingEndInfo =
|
|
2288
|
+
const closingEndInfo = get_line_info(this, closingEnd);
|
|
2132
2289
|
const closingElement =
|
|
2133
2290
|
/** @type {ESTreeJSX.TSRXJSXClosingElement & AST.NodeWithLocation} */ (
|
|
2134
2291
|
this.startNodeAt(closingStart, closingStartLoc)
|
|
@@ -2234,14 +2391,14 @@ export function TSRXPlugin(config) {
|
|
|
2234
2391
|
node.children = [];
|
|
2235
2392
|
|
|
2236
2393
|
if (content.length > 0) {
|
|
2237
|
-
const bodyStartInfo =
|
|
2394
|
+
const bodyStartInfo = get_line_info(this, open.end);
|
|
2238
2395
|
const text = /** @type {ESTreeJSX.JSXText} */ (
|
|
2239
2396
|
this.startNodeAt(open.end, new acorn.Position(bodyStartInfo.line, bodyStartInfo.column))
|
|
2240
2397
|
);
|
|
2241
2398
|
text.value = content;
|
|
2242
2399
|
text.raw = content;
|
|
2243
2400
|
const bodyEnd = open.end + content.length;
|
|
2244
|
-
const bodyEndInfo =
|
|
2401
|
+
const bodyEndInfo = get_line_info(this, bodyEnd);
|
|
2245
2402
|
this.finishNodeAt(
|
|
2246
2403
|
text,
|
|
2247
2404
|
'JSXText',
|
|
@@ -2522,8 +2679,8 @@ export function TSRXPlugin(config) {
|
|
|
2522
2679
|
#report_recoverable_error_range(position, end, message, code) {
|
|
2523
2680
|
const start = Math.max(0, Math.min(position, this.input.length));
|
|
2524
2681
|
const range_end = Math.max(start, Math.min(end, this.input.length));
|
|
2525
|
-
const start_loc =
|
|
2526
|
-
const end_loc =
|
|
2682
|
+
const start_loc = get_line_info(this, start);
|
|
2683
|
+
const end_loc = get_line_info(this, range_end);
|
|
2527
2684
|
|
|
2528
2685
|
error(
|
|
2529
2686
|
message,
|
|
@@ -2923,7 +3080,7 @@ export function TSRXPlugin(config) {
|
|
|
2923
3080
|
this.input.charCodeAt(this.pos - 1) === CharCode.equals
|
|
2924
3081
|
) {
|
|
2925
3082
|
const start = this.pos - 1;
|
|
2926
|
-
const loc =
|
|
3083
|
+
const loc = get_line_info(this, start);
|
|
2927
3084
|
this.start = start;
|
|
2928
3085
|
this.startLoc = loc;
|
|
2929
3086
|
this.pos++;
|
|
@@ -2975,7 +3132,7 @@ export function TSRXPlugin(config) {
|
|
|
2975
3132
|
this.input.charCodeAt(this.pos - 1) === CharCode.equals
|
|
2976
3133
|
) {
|
|
2977
3134
|
const start = this.pos - 1;
|
|
2978
|
-
const loc =
|
|
3135
|
+
const loc = get_line_info(this, start);
|
|
2979
3136
|
this.start = start;
|
|
2980
3137
|
this.startLoc = loc;
|
|
2981
3138
|
this.pos++;
|
|
@@ -3236,6 +3393,89 @@ export function TSRXPlugin(config) {
|
|
|
3236
3393
|
return expr;
|
|
3237
3394
|
}
|
|
3238
3395
|
|
|
3396
|
+
/**
|
|
3397
|
+
* A recorded lazy binding pattern that never became a binding or
|
|
3398
|
+
* assignment target is a pending error, exactly like `{a = b}` shorthand
|
|
3399
|
+
* outside a destructuring pattern. Acorn calls the throwing form at every
|
|
3400
|
+
* boundary where a context definitively stayed an expression
|
|
3401
|
+
* (parenthesized expressions, call arguments, plain assignments'
|
|
3402
|
+
* right-hand sides, …) — that is where the record is enforced.
|
|
3403
|
+
*
|
|
3404
|
+
* The non-throwing form is deliberately left untouched: Acorn uses it in
|
|
3405
|
+
* parseExprOps/parseMaybeConditional to stop parsing operators early, and
|
|
3406
|
+
* returning true there would cut the pattern off from the `as` /
|
|
3407
|
+
* `satisfies` operator lane before toAssignable can accept the wrapped
|
|
3408
|
+
* target (`[&{ a } as T] = arr`). Every path that keeps a stale record
|
|
3409
|
+
* still ends in a throwing check.
|
|
3410
|
+
*
|
|
3411
|
+
* @type {Parse.Parser['checkExpressionErrors']}
|
|
3412
|
+
*/
|
|
3413
|
+
checkExpressionErrors(refDestructuringErrors, andThrow) {
|
|
3414
|
+
if (andThrow) {
|
|
3415
|
+
const lazy_binding_pos = get_lazy_binding_pos(refDestructuringErrors);
|
|
3416
|
+
if (lazy_binding_pos >= 0) {
|
|
3417
|
+
this.raise(
|
|
3418
|
+
lazy_binding_pos,
|
|
3419
|
+
'Lazy binding patterns are only valid as binding or assignment targets',
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
return super.checkExpressionErrors(refDestructuringErrors, andThrow);
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
/**
|
|
3427
|
+
* acorn-typescript unwraps TS expression wrappers in checkLValSimple,
|
|
3428
|
+
* which is only right for simple targets (`[b as any] = arr`). A wrapped
|
|
3429
|
+
* destructuring pattern (`[&{ a } as T] = arr`) reaches checkLValPattern
|
|
3430
|
+
* still wrapped — toAssignableList ignores return values, so the wrapper
|
|
3431
|
+
* survives conversion — and would fall through to checkLValSimple's
|
|
3432
|
+
* "Assigning to rvalue". Unwrap here so wrapped patterns take the
|
|
3433
|
+
* pattern lane.
|
|
3434
|
+
*
|
|
3435
|
+
* @type {Parse.Parser['checkLValPattern']}
|
|
3436
|
+
*/
|
|
3437
|
+
checkLValPattern(expr, bindingType, checkClashes) {
|
|
3438
|
+
let node = expr;
|
|
3439
|
+
while (
|
|
3440
|
+
node.type === 'TSNonNullExpression' ||
|
|
3441
|
+
node.type === 'TSAsExpression' ||
|
|
3442
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
3443
|
+
node.type === 'TSTypeAssertion'
|
|
3444
|
+
) {
|
|
3445
|
+
node = /** @type {AST.Node} */ (
|
|
3446
|
+
/** @type {{ expression: AST.Node }} */ (/** @type {unknown} */ (node)).expression
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
return super.checkLValPattern(node, bindingType, checkClashes);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
/**
|
|
3453
|
+
* Converting a node into an assignment target resolves any lazy binding
|
|
3454
|
+
* pattern recorded inside it — the pattern landed in a valid position,
|
|
3455
|
+
* so its pending error must not outlive the conversion (e.g.
|
|
3456
|
+
* `({ pair: &{ a } } = obj)` would otherwise still raise when the
|
|
3457
|
+
* enclosing parenthesized expression runs checkExpressionErrors). This
|
|
3458
|
+
* mirrors how Acorn resets `shorthandAssign` once the shorthand ends up
|
|
3459
|
+
* inside a converted left-hand side.
|
|
3460
|
+
*
|
|
3461
|
+
* @type {Parse.Parser['toAssignable']}
|
|
3462
|
+
*/
|
|
3463
|
+
toAssignable(node, isBinding, refDestructuringErrors, preserveTypeScriptWrapper) {
|
|
3464
|
+
const lazy_binding_pos = get_lazy_binding_pos(refDestructuringErrors);
|
|
3465
|
+
// Only a pattern-forming position resolves the record: a lazy pattern
|
|
3466
|
+
// that is merely inside the target's span but reached through an
|
|
3467
|
+
// expression position (`&{ a }.b = x`) is still an expression use.
|
|
3468
|
+
if (lazy_binding_pos >= 0 && pattern_position_contains_lazy(node, lazy_binding_pos)) {
|
|
3469
|
+
/** @type {Parse.DestructuringErrors} */ (refDestructuringErrors).lazyBindingPos = -1;
|
|
3470
|
+
}
|
|
3471
|
+
return super.toAssignable(
|
|
3472
|
+
node,
|
|
3473
|
+
isBinding,
|
|
3474
|
+
refDestructuringErrors,
|
|
3475
|
+
preserveTypeScriptWrapper,
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3239
3479
|
/**
|
|
3240
3480
|
* Override checkLocalExport to check all scopes in the scope stack.
|
|
3241
3481
|
* This is needed because submodules create nested scopes, but exports
|
|
@@ -3248,8 +3488,7 @@ export function TSRXPlugin(config) {
|
|
|
3248
3488
|
if (this.hasImport(name)) return;
|
|
3249
3489
|
// Check all scopes in the scope stack, not just the top-level scope
|
|
3250
3490
|
for (let i = this.scopeStack.length - 1; i >= 0; i--) {
|
|
3251
|
-
|
|
3252
|
-
if (scope.lexical.indexOf(name) !== -1 || scope.var.indexOf(name) !== -1) {
|
|
3491
|
+
if (this.#scopeDeclaredNames(this.scopeStack[i]).has(name)) {
|
|
3253
3492
|
// Found in a scope, remove from undefinedExports if it was added
|
|
3254
3493
|
delete this.undefinedExports[name];
|
|
3255
3494
|
return;
|
|
@@ -3259,6 +3498,32 @@ export function TSRXPlugin(config) {
|
|
|
3259
3498
|
this.undefinedExports[name] = id;
|
|
3260
3499
|
}
|
|
3261
3500
|
|
|
3501
|
+
/**
|
|
3502
|
+
* The names declared in `scope`, as a cached Set. Acorn only ever
|
|
3503
|
+
* appends to a scope's `lexical` and `var` arrays during the scope's
|
|
3504
|
+
* lifetime, so syncing from the last-seen lengths is enough to keep the
|
|
3505
|
+
* Set current.
|
|
3506
|
+
*
|
|
3507
|
+
* @param {{ lexical: string[], var: string[] }} scope
|
|
3508
|
+
* @returns {Set<string>}
|
|
3509
|
+
*/
|
|
3510
|
+
#scopeDeclaredNames(scope) {
|
|
3511
|
+
let cached = this.#localExportNamesByScope.get(scope);
|
|
3512
|
+
if (!cached) {
|
|
3513
|
+
cached = { names: new Set(), lexicalLength: 0, varLength: 0 };
|
|
3514
|
+
this.#localExportNamesByScope.set(scope, cached);
|
|
3515
|
+
}
|
|
3516
|
+
for (let i = cached.lexicalLength; i < scope.lexical.length; i++) {
|
|
3517
|
+
cached.names.add(scope.lexical[i]);
|
|
3518
|
+
}
|
|
3519
|
+
for (let i = cached.varLength; i < scope.var.length; i++) {
|
|
3520
|
+
cached.names.add(scope.var[i]);
|
|
3521
|
+
}
|
|
3522
|
+
cached.lexicalLength = scope.lexical.length;
|
|
3523
|
+
cached.varLength = scope.var.length;
|
|
3524
|
+
return cached.names;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3262
3527
|
/** @type {Parse.Parser['parseForStatement']} */
|
|
3263
3528
|
parseForStatement(node) {
|
|
3264
3529
|
this.next();
|
|
@@ -3612,8 +3877,8 @@ export function TSRXPlugin(config) {
|
|
|
3612
3877
|
}
|
|
3613
3878
|
const brace_start = skip_whitespace_from(this.input, name_end);
|
|
3614
3879
|
if (this.input.charCodeAt(brace_start) === CharCode.closeBrace) {
|
|
3615
|
-
const name_start_loc =
|
|
3616
|
-
const name_end_loc =
|
|
3880
|
+
const name_start_loc = get_line_info(this, name_start);
|
|
3881
|
+
const name_end_loc = get_line_info(this, name_end);
|
|
3617
3882
|
const name_value = this.input.slice(name_start, name_end);
|
|
3618
3883
|
const id = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
3619
3884
|
this.startNodeAt(name_start, name_start_loc)
|
|
@@ -3633,14 +3898,14 @@ export function TSRXPlugin(config) {
|
|
|
3633
3898
|
expression,
|
|
3634
3899
|
'JSXExpressionContainer',
|
|
3635
3900
|
brace_start + 1,
|
|
3636
|
-
|
|
3901
|
+
get_line_info(this, brace_start + 1),
|
|
3637
3902
|
);
|
|
3638
3903
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = id;
|
|
3639
3904
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).value = expression;
|
|
3640
3905
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).shorthand = true;
|
|
3641
3906
|
|
|
3642
3907
|
const end = brace_start + 1;
|
|
3643
|
-
const endLoc =
|
|
3908
|
+
const endLoc = get_line_info(this, end);
|
|
3644
3909
|
this.pos = end;
|
|
3645
3910
|
this.curLine = endLoc.line;
|
|
3646
3911
|
this.lineStart = end - endLoc.column;
|
|
@@ -3893,7 +4158,7 @@ export function TSRXPlugin(config) {
|
|
|
3893
4158
|
if (this.input.charCodeAt(paramStart) === CharCode.openParen) {
|
|
3894
4159
|
this.pos = paramStart;
|
|
3895
4160
|
this.start = paramStart;
|
|
3896
|
-
this.startLoc =
|
|
4161
|
+
this.startLoc = get_line_info(this, paramStart);
|
|
3897
4162
|
this.curLine = this.startLoc.line;
|
|
3898
4163
|
this.lineStart = paramStart - this.startLoc.column;
|
|
3899
4164
|
this.#filterTemplateScriptContexts();
|
|
@@ -4063,7 +4328,7 @@ export function TSRXPlugin(config) {
|
|
|
4063
4328
|
this.input.charCodeAt(index + 1) === CharCode.greaterThan &&
|
|
4064
4329
|
this.context.includes(tstc.tc_expr)
|
|
4065
4330
|
) {
|
|
4066
|
-
const loc =
|
|
4331
|
+
const loc = get_line_info(this, index);
|
|
4067
4332
|
this.pos = index;
|
|
4068
4333
|
this.start = index;
|
|
4069
4334
|
this.startLoc = loc;
|
|
@@ -4256,7 +4521,7 @@ export function TSRXPlugin(config) {
|
|
|
4256
4521
|
!this.#shouldReadTemplateRawTextToken()
|
|
4257
4522
|
) {
|
|
4258
4523
|
const start = this.pos - 1;
|
|
4259
|
-
const loc =
|
|
4524
|
+
const loc = get_line_info(this, start);
|
|
4260
4525
|
this.start = start;
|
|
4261
4526
|
this.startLoc = loc;
|
|
4262
4527
|
this.pos++;
|
|
@@ -4607,7 +4872,7 @@ export function TSRXPlugin(config) {
|
|
|
4607
4872
|
);
|
|
4608
4873
|
text_node.value = ws_value;
|
|
4609
4874
|
text_node.raw = ws_value;
|
|
4610
|
-
const loc =
|
|
4875
|
+
const loc = get_line_info(this, at_index);
|
|
4611
4876
|
const at_position = new acorn.Position(loc.line, loc.column);
|
|
4612
4877
|
this.finishNodeAt(text_node, 'JSXText', at_index, at_position);
|
|
4613
4878
|
if (this.#shouldKeepTemplateTextNode(text_node)) {
|
|
@@ -4717,7 +4982,7 @@ export function TSRXPlugin(config) {
|
|
|
4717
4982
|
this.start > blockEnd &&
|
|
4718
4983
|
/^\s*$/.test(this.input.slice(blockEnd, this.start))
|
|
4719
4984
|
) {
|
|
4720
|
-
const loc =
|
|
4985
|
+
const loc = get_line_info(this, blockEnd);
|
|
4721
4986
|
this.pos = blockEnd;
|
|
4722
4987
|
this.start = blockEnd;
|
|
4723
4988
|
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
@@ -5162,7 +5427,16 @@ export function TSRXPlugin(config) {
|
|
|
5162
5427
|
parseBlock(createNewLexicalScope, node, exitStrict) {
|
|
5163
5428
|
const parent = this.#path.at(-1);
|
|
5164
5429
|
|
|
5165
|
-
|
|
5430
|
+
// `#templateControlFlowBlockDepth` alone decides here — don't also
|
|
5431
|
+
// require `#isNativeTemplateNode(parent)`: a directive body's own
|
|
5432
|
+
// parsing (`#parseTemplateControlFlowBlock`) empties `#path`, so for
|
|
5433
|
+
// a `@for` nested inside another directive's branch, `parent` is
|
|
5434
|
+
// `undefined` and that check would skip this redirect, dropping the
|
|
5435
|
+
// `@for`'s body into plain statement parsing (nested directives then
|
|
5436
|
+
// land in expression position instead of template position). The
|
|
5437
|
+
// depth counter is set only around a `@for`'s own header+body and
|
|
5438
|
+
// `@empty` clause, so plain JS `for` loops can't false-positive.
|
|
5439
|
+
if (this.#templateControlFlowBlockDepth > 0) {
|
|
5166
5440
|
this.#templateControlFlowBlockDepth--;
|
|
5167
5441
|
try {
|
|
5168
5442
|
return this.#parseTemplateControlFlowBlock(createNewLexicalScope, node, exitStrict);
|