ripple 0.2.182 → 0.2.184
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 +4 -2
- package/src/compiler/errors.js +3 -1
- package/src/compiler/index.d.ts +2 -1
- package/src/compiler/phases/1-parse/index.js +525 -311
- package/src/compiler/phases/1-parse/style.js +3 -1
- package/src/compiler/phases/2-analyze/css-analyze.js +116 -97
- package/src/compiler/phases/2-analyze/index.js +81 -51
- package/src/compiler/phases/2-analyze/prune.js +200 -58
- package/src/compiler/phases/2-analyze/validation.js +9 -7
- package/src/compiler/phases/3-transform/client/index.js +871 -394
- package/src/compiler/phases/3-transform/segments.js +99 -53
- package/src/compiler/phases/3-transform/server/index.js +278 -121
- package/src/compiler/scope.js +51 -104
- package/src/compiler/types/index.d.ts +834 -197
- package/src/compiler/types/parse.d.ts +1668 -0
- package/src/compiler/utils.js +62 -74
- package/src/utils/ast.js +247 -192
- package/src/utils/builders.js +309 -247
- package/src/utils/sanitize_template_string.js +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
/** @import
|
|
1
|
+
/** @import * as AST from 'estree' */
|
|
2
|
+
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
|
+
/** @import { Parse } from '#parser' */
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* @import {
|
|
5
|
-
* CommentWithLocation,
|
|
6
7
|
* RipplePluginConfig
|
|
7
8
|
* } from '#compiler' */
|
|
8
9
|
|
|
@@ -14,22 +15,69 @@ import { parse_style } from './style.js';
|
|
|
14
15
|
import { walk } from 'zimmerframe';
|
|
15
16
|
import { regex_newline_characters } from '../../../utils/patterns.js';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {(BaseParser: typeof acorn.Parser) => typeof acorn.Parser} AcornPlugin
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const parser = /** @type {Parse.ParserConstructor} */ (
|
|
23
|
+
/** @type {unknown} */ (
|
|
24
|
+
acorn.Parser.extend(
|
|
25
|
+
tsPlugin({ jsx: true }),
|
|
26
|
+
/** @type {AcornPlugin} */ (/** @type {unknown} */ (RipplePlugin())),
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/** @type {Parse.BindingType} */
|
|
32
|
+
const BINDING_TYPES = {
|
|
33
|
+
BIND_NONE: 0, // Not a binding
|
|
34
|
+
BIND_VAR: 1, // Var-style binding
|
|
35
|
+
BIND_LEXICAL: 2, // Let- or const-style binding
|
|
36
|
+
BIND_FUNCTION: 3, // Function declaration
|
|
37
|
+
BIND_SIMPLE_CATCH: 4, // Simple (identifier pattern) catch binding
|
|
38
|
+
BIND_OUTSIDE: 5, // Special case for function names as bound inside the function
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @this {Parse.DestructuringErrors}
|
|
43
|
+
* @returns {Parse.DestructuringErrors}
|
|
44
|
+
*/
|
|
45
|
+
function DestructuringErrors() {
|
|
46
|
+
if (!(this instanceof DestructuringErrors)) {
|
|
47
|
+
throw new TypeError("'DestructuringErrors' must be invoked with 'new'");
|
|
48
|
+
}
|
|
49
|
+
this.shorthandAssign = -1;
|
|
50
|
+
this.trailingComma = -1;
|
|
51
|
+
this.parenthesizedAssign = -1;
|
|
52
|
+
this.parenthesizedBind = -1;
|
|
53
|
+
this.doubleProto = -1;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
18
56
|
|
|
19
57
|
/**
|
|
20
58
|
* Convert JSX node types to regular JavaScript node types
|
|
21
|
-
* @param {
|
|
22
|
-
* @returns {
|
|
59
|
+
* @param {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXMemberExpression | AST.Node} node - The JSX node to convert
|
|
60
|
+
* @returns {AST.Identifier | AST.MemberExpression | AST.Node} The converted node
|
|
23
61
|
*/
|
|
24
62
|
function convert_from_jsx(node) {
|
|
63
|
+
/** @type {AST.Identifier | AST.MemberExpression | AST.Node} */
|
|
64
|
+
let converted_node;
|
|
25
65
|
if (node.type === 'JSXIdentifier') {
|
|
26
|
-
|
|
66
|
+
converted_node = /** @type {AST.Identifier} */ (/** @type {unknown} */ (node));
|
|
67
|
+
converted_node.type = 'Identifier';
|
|
27
68
|
} else if (node.type === 'JSXMemberExpression') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
69
|
+
converted_node = /** @type {AST.MemberExpression} */ (/** @type {unknown} */ (node));
|
|
70
|
+
converted_node.type = 'MemberExpression';
|
|
71
|
+
converted_node.object = /** @type {AST.Identifier | AST.MemberExpression} */ (
|
|
72
|
+
convert_from_jsx(converted_node.object)
|
|
73
|
+
);
|
|
74
|
+
converted_node.property = /** @type {AST.Identifier} */ (
|
|
75
|
+
convert_from_jsx(converted_node.property)
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
converted_node = node;
|
|
31
79
|
}
|
|
32
|
-
return
|
|
80
|
+
return converted_node;
|
|
33
81
|
}
|
|
34
82
|
|
|
35
83
|
const regex_whitespace_only = /\s/;
|
|
@@ -38,7 +86,7 @@ const regex_whitespace_only = /\s/;
|
|
|
38
86
|
* Skip whitespace characters without skipping comments.
|
|
39
87
|
* This is needed because Acorn's skipSpace() also skips comments, which breaks
|
|
40
88
|
* parsing in certain contexts. Updates parser position and line tracking.
|
|
41
|
-
* @param {
|
|
89
|
+
* @param {Parse.Parser} parser
|
|
42
90
|
*/
|
|
43
91
|
function skipWhitespace(parser) {
|
|
44
92
|
const originalStart = parser.start;
|
|
@@ -62,22 +110,56 @@ function skipWhitespace(parser) {
|
|
|
62
110
|
parser.startLoc = lineInfo || acorn.getLineInfo(parser.input, parser.start);
|
|
63
111
|
}
|
|
64
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @param {AST.Node | null | undefined} node
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
65
117
|
function isWhitespaceTextNode(node) {
|
|
66
118
|
if (!node || node.type !== 'Text') {
|
|
67
119
|
return false;
|
|
68
120
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
121
|
+
|
|
122
|
+
const expr = node.expression;
|
|
123
|
+
if (expr && expr.type === 'Literal' && typeof expr.value === 'string') {
|
|
124
|
+
return /^\s*$/.test(expr.value);
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {AST.Element} element
|
|
131
|
+
* @param {ESTreeJSX.JSXOpeningElement} open
|
|
132
|
+
*/
|
|
133
|
+
function addOpeningAndClosing(element, open) {
|
|
134
|
+
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name;
|
|
135
|
+
|
|
136
|
+
element.openingElement = open;
|
|
137
|
+
element.closingElement = {
|
|
138
|
+
type: 'JSXClosingElement',
|
|
139
|
+
name: open.name,
|
|
140
|
+
start: /** @type {AST.NodeWithLocation} */ (element).end - `</${name}>`.length,
|
|
141
|
+
end: element.end,
|
|
142
|
+
loc: {
|
|
143
|
+
start: {
|
|
144
|
+
line: element.loc.end.line,
|
|
145
|
+
column: element.loc.end.column - `</${name}>`.length,
|
|
146
|
+
},
|
|
147
|
+
end: {
|
|
148
|
+
line: element.loc.end.line,
|
|
149
|
+
column: element.loc.end.column,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
metadata: { path: [] },
|
|
153
|
+
};
|
|
72
154
|
}
|
|
73
155
|
|
|
74
156
|
/**
|
|
75
157
|
* Acorn parser plugin for Ripple syntax extensions
|
|
76
158
|
* @param {RipplePluginConfig} [config] - Plugin configuration
|
|
77
|
-
* @returns {
|
|
159
|
+
* @returns {(Parser: Parse.ParserConstructor) => Parse.ParserConstructor} Parser extension function
|
|
78
160
|
*/
|
|
79
161
|
function RipplePlugin(config) {
|
|
80
|
-
return (/** @type {
|
|
162
|
+
return (/** @type {Parse.ParserConstructor} */ Parser) => {
|
|
81
163
|
const original = acorn.Parser.prototype;
|
|
82
164
|
const tt = Parser.tokTypes || acorn.tokTypes;
|
|
83
165
|
const tc = Parser.tokContexts || acorn.tokContexts;
|
|
@@ -85,16 +167,23 @@ function RipplePlugin(config) {
|
|
|
85
167
|
const tstc = Parser.acornTypeScript.tokContexts;
|
|
86
168
|
|
|
87
169
|
class RippleParser extends Parser {
|
|
88
|
-
/** @type {
|
|
170
|
+
/** @type {AST.Node[]} */
|
|
89
171
|
#path = [];
|
|
90
172
|
#commentContextId = 0;
|
|
91
173
|
#loose = false;
|
|
92
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @param {Parse.Options} options
|
|
177
|
+
* @param {string} input
|
|
178
|
+
*/
|
|
93
179
|
constructor(options, input) {
|
|
94
180
|
super(options, input);
|
|
95
181
|
this.#loose = options?.rippleOptions.loose === true;
|
|
96
182
|
}
|
|
97
183
|
|
|
184
|
+
/**
|
|
185
|
+
* @returns {Parse.CommentMetaData | null}
|
|
186
|
+
*/
|
|
98
187
|
#createCommentMetadata() {
|
|
99
188
|
if (this.#path.length === 0) {
|
|
100
189
|
return null;
|
|
@@ -114,23 +203,21 @@ function RipplePlugin(config) {
|
|
|
114
203
|
return null;
|
|
115
204
|
}
|
|
116
205
|
|
|
117
|
-
container.metadata ??= {};
|
|
206
|
+
container.metadata ??= { path: [] };
|
|
118
207
|
if (container.metadata.commentContainerId === undefined) {
|
|
119
208
|
container.metadata.commentContainerId = ++this.#commentContextId;
|
|
120
209
|
}
|
|
121
210
|
|
|
122
|
-
return {
|
|
211
|
+
return /*** @type {Parse.CommentMetaData} */ ({
|
|
123
212
|
containerId: container.metadata.commentContainerId,
|
|
124
|
-
containerType: container.type,
|
|
125
213
|
childIndex: children.length,
|
|
126
214
|
beforeMeaningfulChild: !hasMeaningfulChildren,
|
|
127
|
-
};
|
|
215
|
+
});
|
|
128
216
|
}
|
|
129
217
|
|
|
130
218
|
/**
|
|
131
219
|
* Helper method to get the element name from a JSX identifier or member expression
|
|
132
|
-
* @
|
|
133
|
-
* @returns {string | null} Element name or null
|
|
220
|
+
* @type {Parse.Parser['getElementName']}
|
|
134
221
|
*/
|
|
135
222
|
getElementName(node) {
|
|
136
223
|
if (!node) return null;
|
|
@@ -145,8 +232,7 @@ function RipplePlugin(config) {
|
|
|
145
232
|
|
|
146
233
|
/**
|
|
147
234
|
* Get token from character code - handles Ripple-specific tokens
|
|
148
|
-
* @
|
|
149
|
-
* @returns {any} Token or calls super method
|
|
235
|
+
* @type {Parse.Parser['getTokenFromCode']}
|
|
150
236
|
*/
|
|
151
237
|
getTokenFromCode(code) {
|
|
152
238
|
if (code === 60) {
|
|
@@ -376,7 +462,7 @@ function RipplePlugin(config) {
|
|
|
376
462
|
|
|
377
463
|
/**
|
|
378
464
|
* Read an @ prefixed identifier
|
|
379
|
-
* @
|
|
465
|
+
* @type {Parse.Parser['readAtIdentifier']}
|
|
380
466
|
*/
|
|
381
467
|
readAtIdentifier() {
|
|
382
468
|
const start = this.pos;
|
|
@@ -410,11 +496,12 @@ function RipplePlugin(config) {
|
|
|
410
496
|
|
|
411
497
|
/**
|
|
412
498
|
* Override parseIdent to mark @ identifiers as tracked
|
|
413
|
-
* @
|
|
414
|
-
* @returns {any} Parsed identifier node
|
|
499
|
+
* @type {Parse.Parser['parseIdent']}
|
|
415
500
|
*/
|
|
416
501
|
parseIdent(liberal) {
|
|
417
|
-
const node =
|
|
502
|
+
const node = /** @type {AST.Identifier &AST.NodeWithLocation} */ (
|
|
503
|
+
super.parseIdent(liberal)
|
|
504
|
+
);
|
|
418
505
|
if (node.name && node.name.startsWith('@')) {
|
|
419
506
|
node.name = node.name.slice(1); // Remove the '@' for internal use
|
|
420
507
|
node.tracked = true;
|
|
@@ -429,14 +516,7 @@ function RipplePlugin(config) {
|
|
|
429
516
|
|
|
430
517
|
/**
|
|
431
518
|
* Override parseSubscripts to handle `.@[expression]` syntax for reactive computed member access
|
|
432
|
-
* @
|
|
433
|
-
* @param {number} startPos - Start position
|
|
434
|
-
* @param {any} startLoc - Start location
|
|
435
|
-
* @param {boolean} noCalls - Whether calls are disallowed
|
|
436
|
-
* @param {any} maybeAsyncArrow - Optional async arrow flag
|
|
437
|
-
* @param {any} optionalChained - Optional chaining flag
|
|
438
|
-
* @param {any} forInit - For-init flag
|
|
439
|
-
* @returns {any} Parsed subscript expression
|
|
519
|
+
* @type {Parse.Parser['parseSubscripts']}
|
|
440
520
|
*/
|
|
441
521
|
parseSubscripts(
|
|
442
522
|
base,
|
|
@@ -458,7 +538,7 @@ function RipplePlugin(config) {
|
|
|
458
538
|
|
|
459
539
|
// Check for @[ pattern (@ = 64, [ = 91)
|
|
460
540
|
if (nextChar === 64 && charAfter === 91) {
|
|
461
|
-
const node = this.startNodeAt(startPos, startLoc);
|
|
541
|
+
const node = /** @type {AST.MemberExpression} */ (this.startNodeAt(startPos, startLoc));
|
|
462
542
|
node.object = base;
|
|
463
543
|
node.computed = true;
|
|
464
544
|
node.optional = this.type === tt.questionDot;
|
|
@@ -483,7 +563,7 @@ function RipplePlugin(config) {
|
|
|
483
563
|
this.expect(tt.bracketR);
|
|
484
564
|
|
|
485
565
|
// Finish this MemberExpression node
|
|
486
|
-
base = this.finishNode(node, 'MemberExpression');
|
|
566
|
+
base = /** @type {AST.MemberExpression} */ (this.finishNode(node, 'MemberExpression'));
|
|
487
567
|
|
|
488
568
|
// Recursively handle any further subscripts (chaining)
|
|
489
569
|
return this.parseSubscripts(
|
|
@@ -512,10 +592,7 @@ function RipplePlugin(config) {
|
|
|
512
592
|
|
|
513
593
|
/**
|
|
514
594
|
* Parse expression atom - handles TrackedArray and TrackedObject literals
|
|
515
|
-
* @
|
|
516
|
-
* @param {any} [forNew]
|
|
517
|
-
* @param {any} [forInit]
|
|
518
|
-
* @returns {any} Parsed expression atom
|
|
595
|
+
* @type {Parse.Parser['parseExprAtom']}
|
|
519
596
|
*/
|
|
520
597
|
parseExprAtom(refDestructuringErrors, forNew, forInit) {
|
|
521
598
|
// Check if this is @(expression) for unboxing tracked values
|
|
@@ -527,7 +604,7 @@ function RipplePlugin(config) {
|
|
|
527
604
|
if (this.type === tt.name && this.value === '#server') {
|
|
528
605
|
const node = this.startNode();
|
|
529
606
|
this.next();
|
|
530
|
-
return this.finishNode(node, 'ServerIdentifier');
|
|
607
|
+
return /** @type {AST.ServerIdentifier} */ (this.finishNode(node, 'ServerIdentifier'));
|
|
531
608
|
}
|
|
532
609
|
|
|
533
610
|
// Check if this is #Map( or #Set(
|
|
@@ -554,6 +631,7 @@ function RipplePlugin(config) {
|
|
|
554
631
|
/**
|
|
555
632
|
* Override to track parenthesized expressions in metadata
|
|
556
633
|
* This allows the prettier plugin to preserve parentheses where they existed
|
|
634
|
+
* @type {Parse.Parser['parseParenAndDistinguishExpression']}
|
|
557
635
|
*/
|
|
558
636
|
parseParenAndDistinguishExpression(canBeArrow, forInit) {
|
|
559
637
|
const startPos = this.start;
|
|
@@ -561,8 +639,8 @@ function RipplePlugin(config) {
|
|
|
561
639
|
|
|
562
640
|
// If the expression's start position is after the opening paren,
|
|
563
641
|
// it means it was wrapped in parentheses. Mark it in metadata.
|
|
564
|
-
if (expr && expr.start > startPos) {
|
|
565
|
-
expr.metadata ??= {};
|
|
642
|
+
if (expr && /** @type {AST.NodeWithLocation} */ (expr).start > startPos) {
|
|
643
|
+
expr.metadata ??= { path: [] };
|
|
566
644
|
expr.metadata.parenthesized = true;
|
|
567
645
|
}
|
|
568
646
|
|
|
@@ -572,10 +650,10 @@ function RipplePlugin(config) {
|
|
|
572
650
|
/**
|
|
573
651
|
* Parse `@(expression)` syntax for unboxing tracked values
|
|
574
652
|
* Creates a TrackedExpression node with the argument property
|
|
575
|
-
* @
|
|
653
|
+
* @type {Parse.Parser['parseTrackedExpression']}
|
|
576
654
|
*/
|
|
577
655
|
parseTrackedExpression() {
|
|
578
|
-
const node = this.startNode();
|
|
656
|
+
const node = /** @type {AST.TrackedExpression} */ (this.startNode());
|
|
579
657
|
this.next(); // consume '@(' token
|
|
580
658
|
node.argument = this.parseExpression();
|
|
581
659
|
this.expect(tt.parenR); // expect ')'
|
|
@@ -584,9 +662,7 @@ function RipplePlugin(config) {
|
|
|
584
662
|
|
|
585
663
|
/**
|
|
586
664
|
* Override to allow TrackedExpression as a valid lvalue for update expressions
|
|
587
|
-
* @
|
|
588
|
-
* @param {any} bindingType - Binding type
|
|
589
|
-
* @param {any} checkClashes - Check for clashes
|
|
665
|
+
* @type {Parse.Parser['checkLValSimple']}
|
|
590
666
|
*/
|
|
591
667
|
checkLValSimple(expr, bindingType, checkClashes) {
|
|
592
668
|
// Allow TrackedExpression as a valid lvalue for ++/-- operators
|
|
@@ -596,18 +672,21 @@ function RipplePlugin(config) {
|
|
|
596
672
|
return super.checkLValSimple(expr, bindingType, checkClashes);
|
|
597
673
|
}
|
|
598
674
|
|
|
675
|
+
/**
|
|
676
|
+
* @type {Parse.Parser['parseServerBlock']}
|
|
677
|
+
*/
|
|
599
678
|
parseServerBlock() {
|
|
600
|
-
const node = this.startNode();
|
|
679
|
+
const node = /** @type {AST.ServerBlock} */ (this.startNode());
|
|
601
680
|
this.next();
|
|
602
681
|
|
|
603
|
-
const body = this.startNode();
|
|
682
|
+
const body = /** @type {AST.BlockStatement} */ (this.startNode());
|
|
604
683
|
node.body = body;
|
|
605
684
|
body.body = [];
|
|
606
685
|
|
|
607
686
|
this.expect(tt.braceL);
|
|
608
687
|
this.enterScope(0);
|
|
609
688
|
while (this.type !== tt.braceR) {
|
|
610
|
-
const stmt = this.parseStatement(null, true);
|
|
689
|
+
const stmt = /** @type {AST.Statement} */ (this.parseStatement(null, true));
|
|
611
690
|
body.body.push(stmt);
|
|
612
691
|
}
|
|
613
692
|
this.next();
|
|
@@ -621,11 +700,13 @@ function RipplePlugin(config) {
|
|
|
621
700
|
/**
|
|
622
701
|
* Parse `#Map(...)` or `#Set(...)` syntax for tracked collections
|
|
623
702
|
* Creates a TrackedMap or TrackedSet node with the arguments property
|
|
624
|
-
* @
|
|
625
|
-
* @returns {any} TrackedMap or TrackedSet node
|
|
703
|
+
* @type {Parse.Parser['parseTrackedCollectionExpression']}
|
|
626
704
|
*/
|
|
627
705
|
parseTrackedCollectionExpression(type) {
|
|
628
|
-
const node =
|
|
706
|
+
const node =
|
|
707
|
+
/** @type {(AST.TrackedMapExpression | AST.TrackedSetExpression) & AST.NodeWithLocation} */ (
|
|
708
|
+
this.startNode()
|
|
709
|
+
);
|
|
629
710
|
this.next(); // consume '#Map' or '#Set'
|
|
630
711
|
|
|
631
712
|
// Check if we should NOT consume the parentheses
|
|
@@ -639,21 +720,23 @@ function RipplePlugin(config) {
|
|
|
639
720
|
const beforeStart = this.input.substring(Math.max(0, node.start - 5), node.start);
|
|
640
721
|
const isAfterNew = /new\s*$/.test(beforeStart);
|
|
641
722
|
|
|
642
|
-
if (isAfterNew
|
|
723
|
+
if (!isAfterNew) {
|
|
724
|
+
// If we reach here, it means #Map or #Set is being called without 'new'
|
|
725
|
+
// Throw a TypeError to match JavaScript class constructor behavior
|
|
726
|
+
const constructorName =
|
|
727
|
+
type === 'TrackedMapExpression' ? '#Map (TrackedMap)' : '#Set (TrackedSet)';
|
|
728
|
+
this.raise(
|
|
729
|
+
node.start,
|
|
730
|
+
`TypeError: Class constructor ${constructorName} cannot be invoked without 'new'`,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (this.type === tt.parenL) {
|
|
643
735
|
// Don't consume parens - they belong to NewExpression
|
|
644
736
|
node.arguments = [];
|
|
645
737
|
return this.finishNode(node, type);
|
|
646
738
|
}
|
|
647
739
|
|
|
648
|
-
// If we reach here, it means #Map or #Set is being called without 'new'
|
|
649
|
-
// Throw a TypeError to match JavaScript class constructor behavior
|
|
650
|
-
const constructorName =
|
|
651
|
-
type === 'TrackedMapExpression' ? '#Map (TrackedMap)' : '#Set (TrackedSet)';
|
|
652
|
-
this.raise(
|
|
653
|
-
node.start,
|
|
654
|
-
`TypeError: Class constructor ${constructorName} cannot be invoked without 'new'`,
|
|
655
|
-
);
|
|
656
|
-
|
|
657
740
|
this.expect(tt.parenL); // expect '('
|
|
658
741
|
|
|
659
742
|
node.arguments = [];
|
|
@@ -680,8 +763,11 @@ function RipplePlugin(config) {
|
|
|
680
763
|
return this.finishNode(node, type);
|
|
681
764
|
}
|
|
682
765
|
|
|
766
|
+
/**
|
|
767
|
+
* @type {Parse.Parser['parseTrackedArrayExpression']}
|
|
768
|
+
*/
|
|
683
769
|
parseTrackedArrayExpression() {
|
|
684
|
-
const node = this.startNode();
|
|
770
|
+
const node = /** @type {AST.TrackedArrayExpression} */ (this.startNode());
|
|
685
771
|
this.next(); // consume the '#['
|
|
686
772
|
|
|
687
773
|
node.elements = [];
|
|
@@ -715,8 +801,11 @@ function RipplePlugin(config) {
|
|
|
715
801
|
return this.finishNode(node, 'TrackedArrayExpression');
|
|
716
802
|
}
|
|
717
803
|
|
|
804
|
+
/**
|
|
805
|
+
* @type {Parse.Parser['parseTrackedObjectExpression']}
|
|
806
|
+
*/
|
|
718
807
|
parseTrackedObjectExpression() {
|
|
719
|
-
const node = this.startNode();
|
|
808
|
+
const node = /** @type {AST.TrackedObjectExpression} */ (this.startNode());
|
|
720
809
|
this.next(); // consume the '#{'
|
|
721
810
|
|
|
722
811
|
node.properties = [];
|
|
@@ -740,7 +829,7 @@ function RipplePlugin(config) {
|
|
|
740
829
|
}
|
|
741
830
|
} else {
|
|
742
831
|
// Regular property
|
|
743
|
-
node.properties.push(this.parseProperty(false,
|
|
832
|
+
node.properties.push(this.parseProperty(false, new DestructuringErrors()));
|
|
744
833
|
}
|
|
745
834
|
}
|
|
746
835
|
|
|
@@ -749,14 +838,10 @@ function RipplePlugin(config) {
|
|
|
749
838
|
|
|
750
839
|
/**
|
|
751
840
|
* Parse a component - common implementation used by statements, expressions, and export defaults
|
|
752
|
-
* @
|
|
753
|
-
* @param {boolean} [options.requireName=false] - Whether component name is required
|
|
754
|
-
* @param {boolean} [options.isDefault=false] - Whether this is an export default component
|
|
755
|
-
* @param {boolean} [options.declareName=false] - Whether to declare the name in scope
|
|
756
|
-
* @returns {any} Component node
|
|
841
|
+
* @type {Parse.Parser['parseComponent']}
|
|
757
842
|
*/
|
|
758
843
|
parseComponent({ requireName = false, isDefault = false, declareName = false } = {}) {
|
|
759
|
-
const node = this.startNode();
|
|
844
|
+
const node = /** @type {AST.Component} */ (this.startNode());
|
|
760
845
|
node.type = 'Component';
|
|
761
846
|
node.css = null;
|
|
762
847
|
node.default = isDefault;
|
|
@@ -766,12 +851,20 @@ function RipplePlugin(config) {
|
|
|
766
851
|
if (requireName) {
|
|
767
852
|
node.id = this.parseIdent();
|
|
768
853
|
if (declareName) {
|
|
769
|
-
this.declareName(
|
|
854
|
+
this.declareName(
|
|
855
|
+
node.id.name,
|
|
856
|
+
BINDING_TYPES.BIND_VAR,
|
|
857
|
+
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
858
|
+
);
|
|
770
859
|
}
|
|
771
860
|
} else {
|
|
772
861
|
node.id = this.type.label === 'name' ? this.parseIdent() : null;
|
|
773
862
|
if (declareName && node.id) {
|
|
774
|
-
this.declareName(
|
|
863
|
+
this.declareName(
|
|
864
|
+
node.id.name,
|
|
865
|
+
BINDING_TYPES.BIND_VAR,
|
|
866
|
+
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
867
|
+
);
|
|
775
868
|
}
|
|
776
869
|
}
|
|
777
870
|
|
|
@@ -792,6 +885,9 @@ function RipplePlugin(config) {
|
|
|
792
885
|
return node;
|
|
793
886
|
}
|
|
794
887
|
|
|
888
|
+
/**
|
|
889
|
+
* @type {Parse.Parser['parseExportDefaultDeclaration']}
|
|
890
|
+
*/
|
|
795
891
|
parseExportDefaultDeclaration() {
|
|
796
892
|
// Check if this is "export default component"
|
|
797
893
|
if (this.value === 'component') {
|
|
@@ -801,6 +897,7 @@ function RipplePlugin(config) {
|
|
|
801
897
|
return super.parseExportDefaultDeclaration();
|
|
802
898
|
}
|
|
803
899
|
|
|
900
|
+
/** @type {Parse.Parser['parseForStatement']} */
|
|
804
901
|
parseForStatement(node) {
|
|
805
902
|
this.next();
|
|
806
903
|
let awaitAt =
|
|
@@ -818,12 +915,16 @@ function RipplePlugin(config) {
|
|
|
818
915
|
|
|
819
916
|
let isLet = this.isLet();
|
|
820
917
|
if (this.type === tt._var || this.type === tt._const || isLet) {
|
|
821
|
-
let init = this.startNode(),
|
|
822
|
-
kind = isLet ? 'let' : this.value;
|
|
918
|
+
let init = /** @type {AST.VariableDeclaration} */ (this.startNode()),
|
|
919
|
+
kind = isLet ? 'let' : /** @type {AST.VariableDeclaration['kind']} */ (this.value);
|
|
823
920
|
this.next();
|
|
824
921
|
this.parseVar(init, true, kind);
|
|
825
922
|
this.finishNode(init, 'VariableDeclaration');
|
|
826
|
-
return this.parseForAfterInitWithIndex(
|
|
923
|
+
return this.parseForAfterInitWithIndex(
|
|
924
|
+
/** @type {AST.ForInStatement | AST.ForOfStatement} */ (node),
|
|
925
|
+
init,
|
|
926
|
+
awaitAt,
|
|
927
|
+
);
|
|
827
928
|
}
|
|
828
929
|
|
|
829
930
|
// Handle other cases like using declarations if they exist
|
|
@@ -836,7 +937,7 @@ function RipplePlugin(config) {
|
|
|
836
937
|
? 'await using'
|
|
837
938
|
: null;
|
|
838
939
|
if (usingKind) {
|
|
839
|
-
let init = this.startNode();
|
|
940
|
+
let init = /** @type {AST.VariableDeclaration} */ (this.startNode());
|
|
840
941
|
this.next();
|
|
841
942
|
if (usingKind === 'await using') {
|
|
842
943
|
if (!this.canAwait) {
|
|
@@ -846,13 +947,17 @@ function RipplePlugin(config) {
|
|
|
846
947
|
}
|
|
847
948
|
this.parseVar(init, true, usingKind);
|
|
848
949
|
this.finishNode(init, 'VariableDeclaration');
|
|
849
|
-
return this.parseForAfterInitWithIndex(
|
|
950
|
+
return this.parseForAfterInitWithIndex(
|
|
951
|
+
/** @type {AST.ForInStatement | AST.ForOfStatement} */ (node),
|
|
952
|
+
init,
|
|
953
|
+
awaitAt,
|
|
954
|
+
);
|
|
850
955
|
}
|
|
851
956
|
|
|
852
957
|
let containsEsc = this.containsEsc;
|
|
853
|
-
let refDestructuringErrors =
|
|
958
|
+
let refDestructuringErrors = new DestructuringErrors();
|
|
854
959
|
let initPos = this.start;
|
|
855
|
-
let
|
|
960
|
+
let init_expr =
|
|
856
961
|
awaitAt > -1
|
|
857
962
|
? this.parseExprSubscripts(refDestructuringErrors, 'await')
|
|
858
963
|
: this.parseExpression(true, refDestructuringErrors);
|
|
@@ -864,30 +969,38 @@ function RipplePlugin(config) {
|
|
|
864
969
|
if (awaitAt > -1) {
|
|
865
970
|
// implies `ecmaVersion >= 9`
|
|
866
971
|
if (this.type === tt._in) this.unexpected(awaitAt);
|
|
867
|
-
node.await = true;
|
|
972
|
+
/** @type {AST.ForOfStatement} */ (node).await = true;
|
|
868
973
|
} else if (isForOf && this.options.ecmaVersion >= 8) {
|
|
869
974
|
if (
|
|
870
|
-
|
|
975
|
+
init_expr.start === initPos &&
|
|
871
976
|
!containsEsc &&
|
|
872
|
-
|
|
873
|
-
|
|
977
|
+
init_expr.type === 'Identifier' &&
|
|
978
|
+
init_expr.name === 'async'
|
|
874
979
|
)
|
|
875
980
|
this.unexpected();
|
|
876
|
-
else if (this.options.ecmaVersion >= 9)
|
|
981
|
+
else if (this.options.ecmaVersion >= 9)
|
|
982
|
+
/** @type {AST.ForOfStatement} */ (node).await = false;
|
|
877
983
|
}
|
|
878
984
|
if (startsWithLet && isForOf)
|
|
879
|
-
this.raise(
|
|
880
|
-
|
|
985
|
+
this.raise(
|
|
986
|
+
/** @type {AST.NodeWithLocation} */ (init_expr).start,
|
|
987
|
+
"The left-hand side of a for-of loop may not start with 'let'.",
|
|
988
|
+
);
|
|
989
|
+
const init = this.toAssignable(init_expr, false, refDestructuringErrors);
|
|
881
990
|
this.checkLValPattern(init);
|
|
882
|
-
return this.parseForInWithIndex(
|
|
991
|
+
return this.parseForInWithIndex(
|
|
992
|
+
/** @type {AST.ForInStatement | AST.ForOfStatement} */ (node),
|
|
993
|
+
init,
|
|
994
|
+
);
|
|
883
995
|
} else {
|
|
884
996
|
this.checkExpressionErrors(refDestructuringErrors, true);
|
|
885
997
|
}
|
|
886
998
|
|
|
887
999
|
if (awaitAt > -1) this.unexpected(awaitAt);
|
|
888
|
-
return this.parseFor(node,
|
|
1000
|
+
return this.parseFor(node, init_expr);
|
|
889
1001
|
}
|
|
890
1002
|
|
|
1003
|
+
/** @type {Parse.Parser['parseForAfterInitWithIndex']} */
|
|
891
1004
|
parseForAfterInitWithIndex(node, init, awaitAt) {
|
|
892
1005
|
if (
|
|
893
1006
|
(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual('of'))) &&
|
|
@@ -895,15 +1008,25 @@ function RipplePlugin(config) {
|
|
|
895
1008
|
) {
|
|
896
1009
|
if (this.options.ecmaVersion >= 9) {
|
|
897
1010
|
if (this.type === tt._in) {
|
|
898
|
-
if (awaitAt > -1)
|
|
899
|
-
|
|
1011
|
+
if (awaitAt > -1) {
|
|
1012
|
+
this.unexpected(awaitAt);
|
|
1013
|
+
}
|
|
1014
|
+
} else {
|
|
1015
|
+
/** @type {AST.ForOfStatement} */ (node).await = awaitAt > -1;
|
|
1016
|
+
}
|
|
900
1017
|
}
|
|
901
|
-
return this.parseForInWithIndex(
|
|
1018
|
+
return this.parseForInWithIndex(
|
|
1019
|
+
/** @type {AST.ForInStatement | AST.ForOfStatement} */ (node),
|
|
1020
|
+
init,
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (awaitAt > -1) {
|
|
1024
|
+
this.unexpected(awaitAt);
|
|
902
1025
|
}
|
|
903
|
-
if (awaitAt > -1) this.unexpected(awaitAt);
|
|
904
1026
|
return this.parseFor(node, init);
|
|
905
1027
|
}
|
|
906
1028
|
|
|
1029
|
+
/** @type {Parse.Parser['parseForInWithIndex']} */
|
|
907
1030
|
parseForInWithIndex(node, init) {
|
|
908
1031
|
const isForIn = this.type === tt._in;
|
|
909
1032
|
this.next();
|
|
@@ -918,7 +1041,7 @@ function RipplePlugin(config) {
|
|
|
918
1041
|
init.declarations[0].id.type !== 'Identifier')
|
|
919
1042
|
) {
|
|
920
1043
|
this.raise(
|
|
921
|
-
init.start,
|
|
1044
|
+
/** @type {AST.NodeWithLocation} */ (init).start,
|
|
922
1045
|
`${isForIn ? 'for-in' : 'for-of'} loop variable declaration may not have an initializer`,
|
|
923
1046
|
);
|
|
924
1047
|
}
|
|
@@ -932,8 +1055,13 @@ function RipplePlugin(config) {
|
|
|
932
1055
|
|
|
933
1056
|
if (this.isContextual('index')) {
|
|
934
1057
|
this.next(); // consume 'index'
|
|
935
|
-
node.index =
|
|
936
|
-
|
|
1058
|
+
/** @type {AST.ForOfStatement} */ (node).index = /** @type {AST.Identifier} */ (
|
|
1059
|
+
this.parseExpression()
|
|
1060
|
+
);
|
|
1061
|
+
if (
|
|
1062
|
+
/** @type {AST.Identifier} */ (/** @type {AST.ForOfStatement} */ (node).index)
|
|
1063
|
+
.type !== 'Identifier'
|
|
1064
|
+
) {
|
|
937
1065
|
this.raise(this.start, 'Expected identifier after "index" keyword');
|
|
938
1066
|
}
|
|
939
1067
|
this.eat(tt.semi);
|
|
@@ -941,7 +1069,7 @@ function RipplePlugin(config) {
|
|
|
941
1069
|
|
|
942
1070
|
if (this.isContextual('key')) {
|
|
943
1071
|
this.next(); // consume 'key'
|
|
944
|
-
node.key = this.parseExpression();
|
|
1072
|
+
/** @type {AST.ForOfStatement} */ (node).key = this.parseExpression();
|
|
945
1073
|
}
|
|
946
1074
|
|
|
947
1075
|
if (this.isContextual('index')) {
|
|
@@ -949,16 +1077,19 @@ function RipplePlugin(config) {
|
|
|
949
1077
|
}
|
|
950
1078
|
} else if (!isForIn) {
|
|
951
1079
|
// Set index to null for standard for-of loops
|
|
952
|
-
node.index = null;
|
|
1080
|
+
/** @type {AST.ForOfStatement} */ (node).index = null;
|
|
953
1081
|
}
|
|
954
1082
|
|
|
955
1083
|
this.expect(tt.parenR);
|
|
956
|
-
node.body = this.parseStatement('for');
|
|
1084
|
+
node.body = /** @type {AST.BlockStatement} */ (this.parseStatement('for'));
|
|
957
1085
|
this.exitScope();
|
|
958
1086
|
this.labels.pop();
|
|
959
1087
|
return this.finishNode(node, isForIn ? 'ForInStatement' : 'ForOfStatement');
|
|
960
1088
|
}
|
|
961
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* @type {Parse.Parser['checkUnreserved']}
|
|
1092
|
+
*/
|
|
962
1093
|
checkUnreserved(ref) {
|
|
963
1094
|
if (ref.name === 'component') {
|
|
964
1095
|
this.raise(
|
|
@@ -969,6 +1100,7 @@ function RipplePlugin(config) {
|
|
|
969
1100
|
return super.checkUnreserved(ref);
|
|
970
1101
|
}
|
|
971
1102
|
|
|
1103
|
+
/** @type {Parse.Parser['shouldParseExportStatement']} */
|
|
972
1104
|
shouldParseExportStatement() {
|
|
973
1105
|
if (super.shouldParseExportStatement()) {
|
|
974
1106
|
return true;
|
|
@@ -979,8 +1111,11 @@ function RipplePlugin(config) {
|
|
|
979
1111
|
return this.type.keyword === 'var';
|
|
980
1112
|
}
|
|
981
1113
|
|
|
1114
|
+
/**
|
|
1115
|
+
* @return {ESTreeJSX.JSXExpressionContainer}
|
|
1116
|
+
*/
|
|
982
1117
|
jsx_parseExpressionContainer() {
|
|
983
|
-
let node = this.startNode();
|
|
1118
|
+
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
984
1119
|
this.next();
|
|
985
1120
|
let tracked = false;
|
|
986
1121
|
|
|
@@ -1010,17 +1145,25 @@ function RipplePlugin(config) {
|
|
|
1010
1145
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1011
1146
|
}
|
|
1012
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* @type {Parse.Parser['jsx_parseEmptyExpression']}
|
|
1150
|
+
*/
|
|
1013
1151
|
jsx_parseEmptyExpression() {
|
|
1014
1152
|
// Override to properly handle the range for JSXEmptyExpression
|
|
1015
1153
|
// The range should be from after { to before }
|
|
1016
|
-
const node =
|
|
1154
|
+
const node = /** @type {ESTreeJSX.JSXEmptyExpression} */ (
|
|
1155
|
+
this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc)
|
|
1156
|
+
);
|
|
1017
1157
|
node.end = this.start;
|
|
1018
1158
|
node.loc.end = this.startLoc;
|
|
1019
1159
|
return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);
|
|
1020
1160
|
}
|
|
1021
1161
|
|
|
1162
|
+
/**
|
|
1163
|
+
* @type {Parse.Parser['jsx_parseTupleContainer']}
|
|
1164
|
+
*/
|
|
1022
1165
|
jsx_parseTupleContainer() {
|
|
1023
|
-
const t = this.startNode();
|
|
1166
|
+
const t = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1024
1167
|
return (
|
|
1025
1168
|
this.next(),
|
|
1026
1169
|
(t.expression =
|
|
@@ -1030,8 +1173,11 @@ function RipplePlugin(config) {
|
|
|
1030
1173
|
);
|
|
1031
1174
|
}
|
|
1032
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* @type {Parse.Parser['jsx_parseAttribute']}
|
|
1178
|
+
*/
|
|
1033
1179
|
jsx_parseAttribute() {
|
|
1034
|
-
let node = this.startNode();
|
|
1180
|
+
let node = /** @type {AST.RippleAttribute | ESTreeJSX.JSXAttribute} */ (this.startNode());
|
|
1035
1181
|
|
|
1036
1182
|
if (this.eat(tt.braceL)) {
|
|
1037
1183
|
if (this.value === 'ref') {
|
|
@@ -1042,57 +1188,71 @@ function RipplePlugin(config) {
|
|
|
1042
1188
|
'"ref" is a Ripple keyword and must be used in the form {ref fn}',
|
|
1043
1189
|
);
|
|
1044
1190
|
}
|
|
1045
|
-
node.argument = this.parseMaybeAssign();
|
|
1191
|
+
/** @type {AST.RefAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1046
1192
|
this.expect(tt.braceR);
|
|
1047
|
-
return this.finishNode(node, 'RefAttribute');
|
|
1193
|
+
return /** @type {AST.RefAttribute} */ (this.finishNode(node, 'RefAttribute'));
|
|
1048
1194
|
} else if (this.type === tt.ellipsis) {
|
|
1049
1195
|
this.expect(tt.ellipsis);
|
|
1050
|
-
node.argument = this.parseMaybeAssign();
|
|
1196
|
+
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1051
1197
|
this.expect(tt.braceR);
|
|
1052
1198
|
return this.finishNode(node, 'SpreadAttribute');
|
|
1053
1199
|
} else if (this.lookahead().type === tt.ellipsis) {
|
|
1054
1200
|
this.expect(tt.ellipsis);
|
|
1055
|
-
node.argument = this.parseMaybeAssign();
|
|
1201
|
+
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1056
1202
|
this.expect(tt.braceR);
|
|
1057
1203
|
return this.finishNode(node, 'SpreadAttribute');
|
|
1058
1204
|
} else {
|
|
1059
|
-
const id = this.parseIdentNode();
|
|
1205
|
+
const id = /** @type {AST.Identifier} */ (this.parseIdentNode());
|
|
1060
1206
|
id.tracked = false;
|
|
1061
1207
|
if (id.name.startsWith('@')) {
|
|
1062
1208
|
id.tracked = true;
|
|
1063
1209
|
id.name = id.name.slice(1);
|
|
1064
1210
|
}
|
|
1065
1211
|
this.finishNode(id, 'Identifier');
|
|
1066
|
-
node.name = id;
|
|
1067
|
-
node.value = id;
|
|
1068
|
-
node.shorthand = true; // Mark as shorthand since name and value are the same
|
|
1212
|
+
/** @type {AST.Attribute} */ (node).name = id;
|
|
1213
|
+
/** @type {AST.Attribute} */ (node).value = id;
|
|
1214
|
+
/** @type {AST.Attribute} */ (node).shorthand = true; // Mark as shorthand since name and value are the same
|
|
1069
1215
|
this.next();
|
|
1070
1216
|
this.expect(tt.braceR);
|
|
1071
1217
|
return this.finishNode(node, 'Attribute');
|
|
1072
1218
|
}
|
|
1073
1219
|
}
|
|
1074
|
-
node.name = this.jsx_parseNamespacedName();
|
|
1075
|
-
|
|
1220
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = this.jsx_parseNamespacedName();
|
|
1221
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).value =
|
|
1222
|
+
/** @type {ESTreeJSX.JSXAttribute['value'] | null} */ (
|
|
1223
|
+
this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null
|
|
1224
|
+
);
|
|
1076
1225
|
return this.finishNode(node, 'JSXAttribute');
|
|
1077
1226
|
}
|
|
1078
1227
|
|
|
1228
|
+
/**
|
|
1229
|
+
* @type {Parse.Parser['jsx_parseNamespacedName']}
|
|
1230
|
+
*/
|
|
1079
1231
|
jsx_parseNamespacedName() {
|
|
1080
1232
|
const base = this.jsx_parseIdentifier();
|
|
1081
1233
|
if (!this.eat(tt.colon)) return base;
|
|
1082
|
-
const node =
|
|
1234
|
+
const node = /** @type {ESTreeJSX.JSXNamespacedName} */ (
|
|
1235
|
+
this.startNodeAt(
|
|
1236
|
+
/** @type {AST.NodeWithLocation} */ (base).start,
|
|
1237
|
+
/** @type {AST.NodeWithLocation} */ (base).loc.start,
|
|
1238
|
+
)
|
|
1239
|
+
);
|
|
1083
1240
|
node.namespace = base;
|
|
1084
1241
|
node.name = this.jsx_parseIdentifier();
|
|
1085
1242
|
return this.finishNode(node, 'JSXNamespacedName');
|
|
1086
1243
|
}
|
|
1087
1244
|
|
|
1245
|
+
/**
|
|
1246
|
+
* @type {Parse.Parser['jsx_parseIdentifier']}
|
|
1247
|
+
*/
|
|
1088
1248
|
jsx_parseIdentifier() {
|
|
1089
|
-
const node = this.startNode();
|
|
1249
|
+
const node = /** @type {ESTreeJSX.JSXIdentifier} */ (this.startNode());
|
|
1090
1250
|
|
|
1091
1251
|
if (this.type.label === '@') {
|
|
1092
1252
|
this.next(); // consume @
|
|
1093
1253
|
|
|
1094
1254
|
if (this.type === tt.name || this.type === tstt.jsxName) {
|
|
1095
|
-
node.name = this.value;
|
|
1255
|
+
node.name = /** @type {string} */ (this.value);
|
|
1096
1256
|
node.tracked = true;
|
|
1097
1257
|
this.next();
|
|
1098
1258
|
} else {
|
|
@@ -1102,13 +1262,13 @@ function RipplePlugin(config) {
|
|
|
1102
1262
|
} else if (
|
|
1103
1263
|
(this.type === tt.name || this.type === tstt.jsxName) &&
|
|
1104
1264
|
this.value &&
|
|
1105
|
-
this.value.startsWith('@')
|
|
1265
|
+
/** @type {string} */ (this.value).startsWith('@')
|
|
1106
1266
|
) {
|
|
1107
|
-
node.name = this.value.substring(1);
|
|
1267
|
+
node.name = /** @type {string} */ (this.value).substring(1);
|
|
1108
1268
|
node.tracked = true;
|
|
1109
1269
|
this.next();
|
|
1110
1270
|
} else if (this.type === tt.name || this.type.keyword || this.type === tstt.jsxName) {
|
|
1111
|
-
node.name = this.value;
|
|
1271
|
+
node.name = /** @type {string} */ (this.value);
|
|
1112
1272
|
node.tracked = false; // Explicitly mark as not tracked
|
|
1113
1273
|
this.next();
|
|
1114
1274
|
} else {
|
|
@@ -1118,6 +1278,9 @@ function RipplePlugin(config) {
|
|
|
1118
1278
|
return this.finishNode(node, 'JSXIdentifier');
|
|
1119
1279
|
}
|
|
1120
1280
|
|
|
1281
|
+
/**
|
|
1282
|
+
* @type {Parse.Parser['jsx_parseElementName']}
|
|
1283
|
+
*/
|
|
1121
1284
|
jsx_parseElementName() {
|
|
1122
1285
|
if (this.type === tstt.jsxTagEnd) {
|
|
1123
1286
|
return '';
|
|
@@ -1130,7 +1293,12 @@ function RipplePlugin(config) {
|
|
|
1130
1293
|
}
|
|
1131
1294
|
|
|
1132
1295
|
if (this.eat(tt.dot)) {
|
|
1133
|
-
let memberExpr =
|
|
1296
|
+
let memberExpr = /** @type {ESTreeJSX.JSXMemberExpression} */ (
|
|
1297
|
+
this.startNodeAt(
|
|
1298
|
+
/** @type {AST.NodeWithLocation} */ (node).start,
|
|
1299
|
+
/** @type {AST.NodeWithLocation} */ (node).loc.start,
|
|
1300
|
+
)
|
|
1301
|
+
);
|
|
1134
1302
|
memberExpr.object = node;
|
|
1135
1303
|
|
|
1136
1304
|
// Check for .@[expression] syntax for tracked computed member access
|
|
@@ -1151,8 +1319,8 @@ function RipplePlugin(config) {
|
|
|
1151
1319
|
this.expect(tt.bracketL);
|
|
1152
1320
|
|
|
1153
1321
|
// Parse the expression inside brackets
|
|
1154
|
-
memberExpr.property = this.parseExpression();
|
|
1155
|
-
memberExpr.property.tracked = true;
|
|
1322
|
+
memberExpr.property = /** @type {ESTreeJSX.JSXIdentifier} */ (this.parseExpression());
|
|
1323
|
+
/** @type {AST.TrackedNode} */ (memberExpr.property).tracked = true;
|
|
1156
1324
|
|
|
1157
1325
|
// Expect closing bracket
|
|
1158
1326
|
this.expect(tt.bracketR);
|
|
@@ -1167,9 +1335,11 @@ function RipplePlugin(config) {
|
|
|
1167
1335
|
memberExpr.computed = false;
|
|
1168
1336
|
}
|
|
1169
1337
|
while (this.eat(tt.dot)) {
|
|
1170
|
-
let newMemberExpr =
|
|
1171
|
-
|
|
1172
|
-
|
|
1338
|
+
let newMemberExpr = /** @type {ESTreeJSX.JSXMemberExpression} */ (
|
|
1339
|
+
this.startNodeAt(
|
|
1340
|
+
/** @type {AST.NodeWithLocation} */ (memberExpr).start,
|
|
1341
|
+
/** @type {AST.NodeWithLocation} */ (memberExpr).loc.start,
|
|
1342
|
+
)
|
|
1173
1343
|
);
|
|
1174
1344
|
newMemberExpr.object = memberExpr;
|
|
1175
1345
|
newMemberExpr.property = this.jsx_parseIdentifier();
|
|
@@ -1181,13 +1351,17 @@ function RipplePlugin(config) {
|
|
|
1181
1351
|
return node;
|
|
1182
1352
|
}
|
|
1183
1353
|
|
|
1354
|
+
/** @type {Parse.Parser['jsx_parseAttributeValue']} */
|
|
1184
1355
|
jsx_parseAttributeValue() {
|
|
1185
1356
|
switch (this.type) {
|
|
1186
1357
|
case tt.braceL:
|
|
1187
1358
|
const t = this.jsx_parseExpressionContainer();
|
|
1188
1359
|
return (
|
|
1189
|
-
|
|
1190
|
-
this.raise(
|
|
1360
|
+
t.expression.type === 'JSXEmptyExpression' &&
|
|
1361
|
+
this.raise(
|
|
1362
|
+
/** @type {AST.NodeWithLocation} */ (t).start,
|
|
1363
|
+
'attributes must only be assigned a non-empty expression',
|
|
1364
|
+
),
|
|
1191
1365
|
t
|
|
1192
1366
|
);
|
|
1193
1367
|
case tstt.jsxTagStart:
|
|
@@ -1198,6 +1372,9 @@ function RipplePlugin(config) {
|
|
|
1198
1372
|
}
|
|
1199
1373
|
}
|
|
1200
1374
|
|
|
1375
|
+
/**
|
|
1376
|
+
* @type {Parse.Parser['parseTryStatement']}
|
|
1377
|
+
*/
|
|
1201
1378
|
parseTryStatement(node) {
|
|
1202
1379
|
this.next();
|
|
1203
1380
|
node.block = this.parseBlock();
|
|
@@ -1211,7 +1388,7 @@ function RipplePlugin(config) {
|
|
|
1211
1388
|
}
|
|
1212
1389
|
|
|
1213
1390
|
if (this.type === tt._catch) {
|
|
1214
|
-
const clause = this.startNode();
|
|
1391
|
+
const clause = /** @type {AST.CatchClause} */ (this.startNode());
|
|
1215
1392
|
this.next();
|
|
1216
1393
|
if (this.eat(tt.parenL)) {
|
|
1217
1394
|
clause.param = this.parseCatchClauseParam();
|
|
@@ -1229,11 +1406,15 @@ function RipplePlugin(config) {
|
|
|
1229
1406
|
node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null;
|
|
1230
1407
|
|
|
1231
1408
|
if (!node.handler && !node.finalizer && !node.pending) {
|
|
1232
|
-
this.raise(
|
|
1409
|
+
this.raise(
|
|
1410
|
+
/** @type {AST.NodeWithLocation} */ (node).start,
|
|
1411
|
+
'Missing catch or finally clause',
|
|
1412
|
+
);
|
|
1233
1413
|
}
|
|
1234
1414
|
return this.finishNode(node, 'TryStatement');
|
|
1235
1415
|
}
|
|
1236
1416
|
|
|
1417
|
+
/** @type {Parse.Parser['jsx_readToken']} */
|
|
1237
1418
|
jsx_readToken() {
|
|
1238
1419
|
const inside_tsx_compat = this.#path.findLast((n) => n.type === 'TsxCompat');
|
|
1239
1420
|
if (inside_tsx_compat) {
|
|
@@ -1386,64 +1567,83 @@ function RipplePlugin(config) {
|
|
|
1386
1567
|
}
|
|
1387
1568
|
}
|
|
1388
1569
|
|
|
1570
|
+
/**
|
|
1571
|
+
* @type {Parse.Parser['parseElement']}
|
|
1572
|
+
*/
|
|
1389
1573
|
parseElement() {
|
|
1390
1574
|
const inside_head = this.#path.findLast(
|
|
1391
1575
|
(n) => n.type === 'Element' && n.id.type === 'Identifier' && n.id.name === 'head',
|
|
1392
1576
|
);
|
|
1393
1577
|
// Adjust the start so we capture the `<` as part of the element
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
element.
|
|
1401
|
-
element.loc.start = position;
|
|
1402
|
-
element.metadata = {};
|
|
1578
|
+
const start = this.start - 1;
|
|
1579
|
+
const position = new acorn.Position(this.curLine, start - this.lineStart);
|
|
1580
|
+
|
|
1581
|
+
const element = /** @type {AST.Element | AST.TsxCompat} */ (this.startNode());
|
|
1582
|
+
element.start = start;
|
|
1583
|
+
/** @type {AST.NodeWithLocation} */ (element).loc.start = position;
|
|
1584
|
+
element.metadata = { path: [] };
|
|
1403
1585
|
element.children = [];
|
|
1404
1586
|
|
|
1405
1587
|
// Check if this is a <script> or <style> tag
|
|
1406
1588
|
const tagName = this.value;
|
|
1407
1589
|
const isScriptOrStyle = tagName === 'script' || tagName === 'style';
|
|
1408
|
-
|
|
1590
|
+
/** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */
|
|
1409
1591
|
let open;
|
|
1410
1592
|
if (isScriptOrStyle) {
|
|
1411
1593
|
// Manually parse opening tag to avoid jsx_parseOpeningElementAt consuming content
|
|
1412
|
-
const
|
|
1413
|
-
const
|
|
1594
|
+
const tagEndPos = this.input.indexOf('>', start) + 1; // +1 as end positions are exclusive
|
|
1595
|
+
const opening_position = new acorn.Position(position.line, position.column);
|
|
1596
|
+
const end_position = acorn.getLineInfo(this.input, tagEndPos);
|
|
1414
1597
|
|
|
1415
1598
|
open = {
|
|
1416
1599
|
type: 'JSXOpeningElement',
|
|
1417
|
-
name: {
|
|
1600
|
+
name: {
|
|
1601
|
+
type: 'JSXIdentifier',
|
|
1602
|
+
name: tagName,
|
|
1603
|
+
metadata: { path: [] },
|
|
1604
|
+
start: start + 1,
|
|
1605
|
+
end: tagEndPos - 1,
|
|
1606
|
+
loc: {
|
|
1607
|
+
start: { ...position, column: position.column + 1 },
|
|
1608
|
+
end: {
|
|
1609
|
+
...end_position,
|
|
1610
|
+
column: end_position.column - 1,
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1418
1614
|
attributes: [],
|
|
1419
1615
|
selfClosing: false,
|
|
1420
|
-
|
|
1616
|
+
start,
|
|
1617
|
+
end: tagEndPos,
|
|
1421
1618
|
loc: {
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
column: tagEndPos + 1,
|
|
1425
|
-
},
|
|
1619
|
+
start: opening_position,
|
|
1620
|
+
end: end_position,
|
|
1426
1621
|
},
|
|
1622
|
+
metadata: { path: [] },
|
|
1427
1623
|
};
|
|
1428
1624
|
|
|
1429
1625
|
// Position after the '>'
|
|
1430
|
-
this.pos = tagEndPos
|
|
1626
|
+
this.pos = tagEndPos;
|
|
1431
1627
|
|
|
1432
1628
|
// Add opening and closing for easier location tracking
|
|
1433
1629
|
// TODO: we should also parse attributes inside the opening tag
|
|
1434
1630
|
} else {
|
|
1435
|
-
open =
|
|
1631
|
+
open =
|
|
1632
|
+
/** @type {ReturnType<Parse.Parser['jsx_parseOpeningElementAt']> & AST.NodeWithLocation} */ (
|
|
1633
|
+
this.jsx_parseOpeningElementAt()
|
|
1634
|
+
);
|
|
1436
1635
|
}
|
|
1437
1636
|
|
|
1438
1637
|
// Check if this is a namespaced element (tsx:react)
|
|
1439
1638
|
const is_tsx_compat = open.name.type === 'JSXNamespacedName';
|
|
1440
1639
|
|
|
1441
1640
|
if (is_tsx_compat) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1641
|
+
const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
|
|
1642
|
+
/** @type {AST.TsxCompat} */ (element).type = 'TsxCompat';
|
|
1643
|
+
/** @type {AST.TsxCompat} */ (element).kind = namespace_node.name.name; // e.g., "react" from "tsx:react"
|
|
1444
1644
|
|
|
1445
1645
|
if (open.selfClosing) {
|
|
1446
|
-
const tagName =
|
|
1646
|
+
const tagName = namespace_node.namespace.name + ':' + namespace_node.name.name;
|
|
1447
1647
|
this.raise(
|
|
1448
1648
|
open.start,
|
|
1449
1649
|
`TSX compatibility elements cannot be self-closing. '<${tagName} />' must have a closing tag '</${tagName}>'.`,
|
|
@@ -1457,65 +1657,34 @@ function RipplePlugin(config) {
|
|
|
1457
1657
|
|
|
1458
1658
|
for (const attr of open.attributes) {
|
|
1459
1659
|
if (attr.type === 'JSXAttribute') {
|
|
1460
|
-
attr.type = 'Attribute';
|
|
1660
|
+
/** @type {AST.Attribute} */ (/** @type {unknown} */ (attr)).type = 'Attribute';
|
|
1461
1661
|
if (attr.name.type === 'JSXIdentifier') {
|
|
1462
|
-
attr.name.type =
|
|
1662
|
+
/** @type {AST.Identifier} */ (/** @type {unknown} */ (attr.name)).type =
|
|
1663
|
+
'Identifier';
|
|
1463
1664
|
}
|
|
1464
1665
|
if (attr.value !== null) {
|
|
1465
1666
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
1466
|
-
|
|
1667
|
+
/** @type {ESTreeJSX.JSXExpressionContainer['expression']} */ (attr.value) =
|
|
1668
|
+
attr.value.expression;
|
|
1467
1669
|
}
|
|
1468
1670
|
}
|
|
1469
1671
|
}
|
|
1470
1672
|
}
|
|
1471
1673
|
|
|
1472
1674
|
if (!is_tsx_compat) {
|
|
1473
|
-
|
|
1474
|
-
open.name
|
|
1475
|
-
|
|
1476
|
-
element.id = convert_from_jsx(open.name);
|
|
1675
|
+
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
1676
|
+
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
1677
|
+
);
|
|
1477
1678
|
element.selfClosing = open.selfClosing;
|
|
1478
1679
|
}
|
|
1479
1680
|
|
|
1480
1681
|
element.attributes = open.attributes;
|
|
1481
|
-
element.metadata ??= {};
|
|
1682
|
+
element.metadata ??= { path: [] };
|
|
1482
1683
|
element.metadata.commentContainerId = ++this.#commentContextId;
|
|
1483
1684
|
// Store opening tag's end position for use in loose mode when element is unclosed
|
|
1484
1685
|
element.metadata.openingTagEnd = open.end;
|
|
1485
1686
|
element.metadata.openingTagEndLoc = open.loc.end;
|
|
1486
1687
|
|
|
1487
|
-
function addOpeningAndClosing() {
|
|
1488
|
-
//TODO: once parse attributes of style and script
|
|
1489
|
-
// we should the open element loc properly
|
|
1490
|
-
const name = open.name.name;
|
|
1491
|
-
open.loc = {
|
|
1492
|
-
start: {
|
|
1493
|
-
line: element.loc.start.line,
|
|
1494
|
-
column: element.loc.start.column,
|
|
1495
|
-
},
|
|
1496
|
-
end: {
|
|
1497
|
-
line: element.loc.start.line,
|
|
1498
|
-
column: element.loc.start.column + `${name}>`.length,
|
|
1499
|
-
},
|
|
1500
|
-
};
|
|
1501
|
-
|
|
1502
|
-
element.openingElement = open;
|
|
1503
|
-
element.closingElement = {
|
|
1504
|
-
type: 'JSXClosingElement',
|
|
1505
|
-
name: open.name,
|
|
1506
|
-
loc: {
|
|
1507
|
-
start: {
|
|
1508
|
-
line: element.loc.end.line,
|
|
1509
|
-
column: element.loc.end.column - `</${name}>`.length,
|
|
1510
|
-
},
|
|
1511
|
-
end: {
|
|
1512
|
-
line: element.loc.end.line,
|
|
1513
|
-
column: element.loc.end.column,
|
|
1514
|
-
},
|
|
1515
|
-
},
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
1688
|
if (element.selfClosing) {
|
|
1520
1689
|
this.#path.pop();
|
|
1521
1690
|
|
|
@@ -1524,7 +1693,7 @@ function RipplePlugin(config) {
|
|
|
1524
1693
|
this.next();
|
|
1525
1694
|
}
|
|
1526
1695
|
} else {
|
|
1527
|
-
if (open.name.name === 'script') {
|
|
1696
|
+
if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'script') {
|
|
1528
1697
|
let content = '';
|
|
1529
1698
|
|
|
1530
1699
|
// TODO implement this where we get a string for content of the content of the script tag
|
|
@@ -1551,10 +1720,10 @@ function RipplePlugin(config) {
|
|
|
1551
1720
|
this.next();
|
|
1552
1721
|
}
|
|
1553
1722
|
|
|
1554
|
-
element.content = content;
|
|
1723
|
+
/** @type {AST.Element} */ (element).content = content;
|
|
1555
1724
|
this.finishNode(element, 'Element');
|
|
1556
|
-
addOpeningAndClosing(element, open);
|
|
1557
|
-
} else if (open.name.name === 'style') {
|
|
1725
|
+
addOpeningAndClosing(/** @type {AST.Element} */ (element), open);
|
|
1726
|
+
} else if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'style') {
|
|
1558
1727
|
// jsx_parseOpeningElementAt treats ID selectors (ie. #myid) or type selectors (ie. div) as identifier and read it
|
|
1559
1728
|
// So backtrack to the end of the <style> tag to make sure everything is included
|
|
1560
1729
|
const start = open.end;
|
|
@@ -1562,7 +1731,9 @@ function RipplePlugin(config) {
|
|
|
1562
1731
|
const end = input.indexOf('</style>');
|
|
1563
1732
|
const content = input.slice(0, end);
|
|
1564
1733
|
|
|
1565
|
-
const component =
|
|
1734
|
+
const component = /** @type {AST.Component} */ (
|
|
1735
|
+
this.#path.findLast((n) => n.type === 'Component')
|
|
1736
|
+
);
|
|
1566
1737
|
const parsed_css = parse_style(content, { loose: this.#loose });
|
|
1567
1738
|
|
|
1568
1739
|
if (!inside_head) {
|
|
@@ -1590,7 +1761,9 @@ function RipplePlugin(config) {
|
|
|
1590
1761
|
}
|
|
1591
1762
|
// This node is used for Prettier - always add parsed CSS as children
|
|
1592
1763
|
// for proper formatting, regardless of whether it's inside head or not
|
|
1593
|
-
element.children = [
|
|
1764
|
+
/** @type {AST.Element} */ (element).children = [
|
|
1765
|
+
/** @type {AST.Node} */ (/** @type {unknown} */ (parsed_css)),
|
|
1766
|
+
];
|
|
1594
1767
|
|
|
1595
1768
|
// Ensure we escape JSX <tag></tag> context
|
|
1596
1769
|
const curContext = this.curContext();
|
|
@@ -1599,13 +1772,13 @@ function RipplePlugin(config) {
|
|
|
1599
1772
|
this.context.pop();
|
|
1600
1773
|
}
|
|
1601
1774
|
|
|
1602
|
-
element.css = content;
|
|
1775
|
+
/** @type {AST.Element} */ (element).css = content;
|
|
1603
1776
|
this.finishNode(element, 'Element');
|
|
1604
|
-
addOpeningAndClosing(element, open);
|
|
1777
|
+
addOpeningAndClosing(/** @type {AST.Element} */ (element), open);
|
|
1605
1778
|
return element;
|
|
1606
1779
|
} else {
|
|
1607
1780
|
this.enterScope(0);
|
|
1608
|
-
this.parseTemplateBody(element.children);
|
|
1781
|
+
this.parseTemplateBody(/** @type {AST.Element} */ (element).children);
|
|
1609
1782
|
this.exitScope();
|
|
1610
1783
|
|
|
1611
1784
|
if (element.type === 'TsxCompat') {
|
|
@@ -1669,6 +1842,9 @@ function RipplePlugin(config) {
|
|
|
1669
1842
|
return element;
|
|
1670
1843
|
}
|
|
1671
1844
|
|
|
1845
|
+
/**
|
|
1846
|
+
* @type {Parse.Parser['parseTemplateBody']}
|
|
1847
|
+
*/
|
|
1672
1848
|
parseTemplateBody(body) {
|
|
1673
1849
|
const inside_func =
|
|
1674
1850
|
this.context.some((n) => n.token === 'function') || this.scopeStack.length > 1;
|
|
@@ -1720,13 +1896,13 @@ function RipplePlugin(config) {
|
|
|
1720
1896
|
}
|
|
1721
1897
|
|
|
1722
1898
|
if (text) {
|
|
1723
|
-
const node = {
|
|
1899
|
+
const node = /** @type {ESTreeJSX.JSXText} */ ({
|
|
1724
1900
|
type: 'JSXText',
|
|
1725
1901
|
value: text,
|
|
1726
1902
|
raw: text,
|
|
1727
1903
|
start,
|
|
1728
1904
|
end: this.pos,
|
|
1729
|
-
};
|
|
1905
|
+
});
|
|
1730
1906
|
body.push(node);
|
|
1731
1907
|
}
|
|
1732
1908
|
|
|
@@ -1739,7 +1915,9 @@ function RipplePlugin(config) {
|
|
|
1739
1915
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
1740
1916
|
// but convert other expressions to Text/Html nodes
|
|
1741
1917
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
1742
|
-
node.type = node.html
|
|
1918
|
+
/** @type {AST.Html | AST.TextNode} */ (/** @type {unknown} */ (node)).type = node.html
|
|
1919
|
+
? 'Html'
|
|
1920
|
+
: 'Text';
|
|
1743
1921
|
delete node.html;
|
|
1744
1922
|
}
|
|
1745
1923
|
body.push(node);
|
|
@@ -1749,7 +1927,10 @@ function RipplePlugin(config) {
|
|
|
1749
1927
|
this.next();
|
|
1750
1928
|
if (this.value === '/') {
|
|
1751
1929
|
this.next();
|
|
1752
|
-
const closingTag =
|
|
1930
|
+
const closingTag =
|
|
1931
|
+
/** @type {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXNamespacedName | ESTreeJSX.JSXMemberExpression} */ (
|
|
1932
|
+
this.jsx_parseElementName()
|
|
1933
|
+
);
|
|
1753
1934
|
this.exprAllowed = true;
|
|
1754
1935
|
|
|
1755
1936
|
// Validate that the closing tag matches the opening tag
|
|
@@ -1761,7 +1942,9 @@ function RipplePlugin(config) {
|
|
|
1761
1942
|
this.raise(this.start, 'Unexpected closing tag');
|
|
1762
1943
|
}
|
|
1763
1944
|
|
|
1945
|
+
/** @type {string | null} */
|
|
1764
1946
|
let openingTagName;
|
|
1947
|
+
/** @type {string | null} */
|
|
1765
1948
|
let closingTagName;
|
|
1766
1949
|
|
|
1767
1950
|
if (currentElement.type === 'TsxCompat') {
|
|
@@ -1808,9 +1991,11 @@ function RipplePlugin(config) {
|
|
|
1808
1991
|
// Mark as unclosed and adjust location
|
|
1809
1992
|
elem.unclosed = true;
|
|
1810
1993
|
const position = this.curPosition();
|
|
1811
|
-
position.line = elem.metadata.openingTagEndLoc.line;
|
|
1812
|
-
position.column =
|
|
1813
|
-
|
|
1994
|
+
position.line = /** @type {AST.Position} */ (elem.metadata.openingTagEndLoc).line;
|
|
1995
|
+
position.column = /** @type {AST.Position} */ (
|
|
1996
|
+
elem.metadata.openingTagEndLoc
|
|
1997
|
+
).column;
|
|
1998
|
+
/** @type {AST.NodeWithLocation} */ (elem).loc.end = position;
|
|
1814
1999
|
elem.end = elem.metadata.openingTagEnd;
|
|
1815
2000
|
|
|
1816
2001
|
this.#path.pop(); // Remove from stack
|
|
@@ -1841,24 +2026,29 @@ function RipplePlugin(config) {
|
|
|
1841
2026
|
this.parseTemplateBody(body);
|
|
1842
2027
|
}
|
|
1843
2028
|
|
|
2029
|
+
/**
|
|
2030
|
+
* @type {Parse.Parser['parseStatement']}
|
|
2031
|
+
*/
|
|
1844
2032
|
parseStatement(context, topLevel, exports) {
|
|
1845
2033
|
if (
|
|
1846
2034
|
context !== 'for' &&
|
|
1847
2035
|
context !== 'if' &&
|
|
1848
2036
|
this.context.at(-1) === tc.b_stat &&
|
|
1849
2037
|
this.type === tt.braceL &&
|
|
1850
|
-
this.context.some((c) => c ===
|
|
2038
|
+
this.context.some((c) => c === tstc.tc_expr)
|
|
1851
2039
|
) {
|
|
1852
2040
|
this.next();
|
|
1853
2041
|
const node = this.jsx_parseExpressionContainer();
|
|
1854
2042
|
// Keep JSXEmptyExpression as-is (don't convert to Text)
|
|
1855
2043
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
1856
|
-
node.type = 'Text';
|
|
2044
|
+
/** @type {AST.TextNode} */ (/** @type {unknown} */ (node)).type = 'Text';
|
|
1857
2045
|
}
|
|
1858
2046
|
this.next();
|
|
1859
2047
|
this.context.pop();
|
|
1860
2048
|
this.context.pop();
|
|
1861
|
-
return
|
|
2049
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
2050
|
+
/** @type {unknown} */ (node)
|
|
2051
|
+
);
|
|
1862
2052
|
}
|
|
1863
2053
|
|
|
1864
2054
|
if (this.value === '#server') {
|
|
@@ -1875,7 +2065,7 @@ function RipplePlugin(config) {
|
|
|
1875
2065
|
// interfering with legitimate decorator syntax
|
|
1876
2066
|
this.skip_decorator = true;
|
|
1877
2067
|
const expressionResult = this.tryParse(() => {
|
|
1878
|
-
const node = this.startNode();
|
|
2068
|
+
const node = /** @type {AST.ExpressionStatement} */ (this.startNode());
|
|
1879
2069
|
this.next();
|
|
1880
2070
|
// Force expression context to ensure @ is tokenized correctly
|
|
1881
2071
|
const old_expr_allowed = this.exprAllowed;
|
|
@@ -1883,17 +2073,19 @@ function RipplePlugin(config) {
|
|
|
1883
2073
|
node.expression = this.parseExpression();
|
|
1884
2074
|
|
|
1885
2075
|
if (node.expression.type === 'UpdateExpression') {
|
|
2076
|
+
/** @type {AST.Expression} */
|
|
1886
2077
|
let object = node.expression.argument;
|
|
1887
2078
|
while (object.type === 'MemberExpression') {
|
|
1888
|
-
object = object.object;
|
|
2079
|
+
object = /** @type {AST.Expression} */ (object.object);
|
|
1889
2080
|
}
|
|
1890
2081
|
if (object.type === 'Identifier') {
|
|
1891
2082
|
object.tracked = true;
|
|
1892
2083
|
}
|
|
1893
2084
|
} else if (node.expression.type === 'AssignmentExpression') {
|
|
2085
|
+
/** @type {AST.Expression | AST.Pattern | AST.Identifier} */
|
|
1894
2086
|
let object = node.expression.left;
|
|
1895
2087
|
while (object.type === 'MemberExpression') {
|
|
1896
|
-
object = object.object;
|
|
2088
|
+
object = /** @type {AST.Expression} */ (object.object);
|
|
1897
2089
|
}
|
|
1898
2090
|
if (object.type === 'Identifier') {
|
|
1899
2091
|
object.tracked = true;
|
|
@@ -1931,12 +2123,15 @@ function RipplePlugin(config) {
|
|
|
1931
2123
|
return super.parseStatement(context, topLevel, exports);
|
|
1932
2124
|
}
|
|
1933
2125
|
|
|
2126
|
+
/**
|
|
2127
|
+
* @type {Parse.Parser['parseBlock']}
|
|
2128
|
+
*/
|
|
1934
2129
|
parseBlock(createNewLexicalScope, node, exitStrict) {
|
|
1935
2130
|
const parent = this.#path.at(-1);
|
|
1936
2131
|
|
|
1937
2132
|
if (parent?.type === 'Component' || parent?.type === 'Element') {
|
|
1938
2133
|
if (createNewLexicalScope === void 0) createNewLexicalScope = true;
|
|
1939
|
-
if (node === void 0) node = this.startNode();
|
|
2134
|
+
if (node === void 0) node = /** @type {AST.BlockStatement} */ (this.startNode());
|
|
1940
2135
|
|
|
1941
2136
|
node.body = [];
|
|
1942
2137
|
this.expect(tt.braceL);
|
|
@@ -1961,7 +2156,7 @@ function RipplePlugin(config) {
|
|
|
1961
2156
|
}
|
|
1962
2157
|
}
|
|
1963
2158
|
|
|
1964
|
-
return RippleParser;
|
|
2159
|
+
return /** @type {Parse.ParserConstructor} */ (RippleParser);
|
|
1965
2160
|
};
|
|
1966
2161
|
}
|
|
1967
2162
|
|
|
@@ -1970,11 +2165,15 @@ function RipplePlugin(config) {
|
|
|
1970
2165
|
* to add them after the fact. They are needed in order to support `ripple-ignore` comments
|
|
1971
2166
|
* in JS code and so that `prettier-plugin` doesn't remove all comments when formatting.
|
|
1972
2167
|
* @param {string} source
|
|
1973
|
-
* @param {CommentWithLocation[]} comments
|
|
2168
|
+
* @param {AST.CommentWithLocation[]} comments
|
|
1974
2169
|
* @param {number} [index=0] - Starting index
|
|
1975
|
-
* @returns {{ onComment: Function, add_comments: Function }} Comment handler functions
|
|
1976
2170
|
*/
|
|
1977
2171
|
function get_comment_handlers(source, comments, index = 0) {
|
|
2172
|
+
/**
|
|
2173
|
+
* @param {string} text
|
|
2174
|
+
* @param {number} startIndex
|
|
2175
|
+
* @returns {string | null}
|
|
2176
|
+
*/
|
|
1978
2177
|
function getNextNonWhitespaceCharacter(text, startIndex) {
|
|
1979
2178
|
for (let i = startIndex; i < text.length; i++) {
|
|
1980
2179
|
const char = text[i];
|
|
@@ -1986,6 +2185,9 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1986
2185
|
}
|
|
1987
2186
|
|
|
1988
2187
|
return {
|
|
2188
|
+
/**
|
|
2189
|
+
* @type {Parse.Options['onComment']}
|
|
2190
|
+
*/
|
|
1989
2191
|
onComment: (block, value, start, end, start_loc, end_loc, metadata) => {
|
|
1990
2192
|
if (block && /\n/.test(value)) {
|
|
1991
2193
|
let a = start;
|
|
@@ -2004,12 +2206,16 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2004
2206
|
start,
|
|
2005
2207
|
end,
|
|
2006
2208
|
loc: {
|
|
2007
|
-
start:
|
|
2008
|
-
end:
|
|
2209
|
+
start: start_loc,
|
|
2210
|
+
end: end_loc,
|
|
2009
2211
|
},
|
|
2010
2212
|
context: metadata ?? null,
|
|
2011
2213
|
});
|
|
2012
2214
|
},
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* @param {AST.Node} ast
|
|
2218
|
+
*/
|
|
2013
2219
|
add_comments: (ast) => {
|
|
2014
2220
|
if (comments.length === 0) return;
|
|
2015
2221
|
|
|
@@ -2024,14 +2230,13 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2024
2230
|
context,
|
|
2025
2231
|
}));
|
|
2026
2232
|
|
|
2233
|
+
/**
|
|
2234
|
+
* @param {AST.Node} ast
|
|
2235
|
+
*/
|
|
2027
2236
|
walk(ast, null, {
|
|
2237
|
+
/** @param {AST.Node} node */
|
|
2028
2238
|
_(node, { next, path }) {
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
const metadata =
|
|
2032
|
-
/** @type {{ commentContainerId?: number, elementLeadingComments?: CommentWithLocation[] }} */ (
|
|
2033
|
-
node?.metadata
|
|
2034
|
-
);
|
|
2239
|
+
const metadata = node?.metadata;
|
|
2035
2240
|
|
|
2036
2241
|
if (metadata && metadata.commentContainerId !== undefined) {
|
|
2037
2242
|
while (
|
|
@@ -2040,15 +2245,17 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2040
2245
|
comments[0].context.containerId === metadata.commentContainerId &&
|
|
2041
2246
|
comments[0].context.beforeMeaningfulChild
|
|
2042
2247
|
) {
|
|
2043
|
-
const elementComment = /** @type {CommentWithLocation
|
|
2044
|
-
|
|
2045
|
-
);
|
|
2248
|
+
const elementComment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2249
|
+
|
|
2046
2250
|
(metadata.elementLeadingComments ||= []).push(elementComment);
|
|
2047
2251
|
}
|
|
2048
2252
|
}
|
|
2049
2253
|
|
|
2050
|
-
while (
|
|
2051
|
-
|
|
2254
|
+
while (
|
|
2255
|
+
comments[0] &&
|
|
2256
|
+
comments[0].start < /** @type {AST.NodeWithLocation} */ (node).start
|
|
2257
|
+
) {
|
|
2258
|
+
const comment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2052
2259
|
|
|
2053
2260
|
// Skip leading comments for BlockStatement that is a function body
|
|
2054
2261
|
// These comments should be dangling on the function instead
|
|
@@ -2067,21 +2274,20 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2067
2274
|
}
|
|
2068
2275
|
}
|
|
2069
2276
|
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
.sort((a, b) => a.loc.start.line - b.loc.start.line);
|
|
2277
|
+
const ancestorElements = /** @type {(AST.Element & AST.NodeWithLocation)[]} */ (
|
|
2278
|
+
path.filter((ancestor) => ancestor && ancestor.type === 'Element' && ancestor.loc)
|
|
2279
|
+
).sort((a, b) => a.loc.start.line - b.loc.start.line);
|
|
2074
2280
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2281
|
+
const targetAncestor = ancestorElements.find(
|
|
2282
|
+
(ancestor) => comment.loc.start.line < ancestor.loc.start.line,
|
|
2283
|
+
);
|
|
2078
2284
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
}
|
|
2285
|
+
if (targetAncestor) {
|
|
2286
|
+
targetAncestor.metadata ??= { path: [] };
|
|
2287
|
+
(targetAncestor.metadata.elementLeadingComments ||= []).push(comment);
|
|
2288
|
+
continue;
|
|
2084
2289
|
}
|
|
2290
|
+
|
|
2085
2291
|
(node.leadingComments ||= []).push(comment);
|
|
2086
2292
|
}
|
|
2087
2293
|
|
|
@@ -2090,8 +2296,12 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2090
2296
|
if (comments[0]) {
|
|
2091
2297
|
if (node.type === 'BlockStatement' && node.body.length === 0) {
|
|
2092
2298
|
// Collect all comments that fall within this empty block
|
|
2093
|
-
while (
|
|
2094
|
-
|
|
2299
|
+
while (
|
|
2300
|
+
comments[0] &&
|
|
2301
|
+
comments[0].start < /** @type {AST.NodeWithLocation} */ (node).end &&
|
|
2302
|
+
comments[0].end < /** @type {AST.NodeWithLocation} */ (node).end
|
|
2303
|
+
) {
|
|
2304
|
+
const comment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2095
2305
|
(node.innerComments ||= []).push(comment);
|
|
2096
2306
|
}
|
|
2097
2307
|
if (node.innerComments && node.innerComments.length > 0) {
|
|
@@ -2103,10 +2313,10 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2103
2313
|
// Collect all comments that fall within this JSXEmptyExpression
|
|
2104
2314
|
while (
|
|
2105
2315
|
comments[0] &&
|
|
2106
|
-
comments[0].start >= node.start &&
|
|
2107
|
-
comments[0].end <= node.end
|
|
2316
|
+
comments[0].start >= /** @type {AST.NodeWithLocation} */ (node).start &&
|
|
2317
|
+
comments[0].end <= /** @type {AST.NodeWithLocation} */ (node).end
|
|
2108
2318
|
) {
|
|
2109
|
-
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
2319
|
+
const comment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2110
2320
|
(node.innerComments ||= []).push(comment);
|
|
2111
2321
|
}
|
|
2112
2322
|
if (node.innerComments && node.innerComments.length > 0) {
|
|
@@ -2116,8 +2326,12 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2116
2326
|
// Handle empty Element nodes the same way as empty BlockStatements
|
|
2117
2327
|
if (node.type === 'Element' && (!node.children || node.children.length === 0)) {
|
|
2118
2328
|
// Collect all comments that fall within this empty element
|
|
2119
|
-
while (
|
|
2120
|
-
|
|
2329
|
+
while (
|
|
2330
|
+
comments[0] &&
|
|
2331
|
+
comments[0].start < /** @type {AST.NodeWithLocation} */ (node).end &&
|
|
2332
|
+
comments[0].end < /** @type {AST.NodeWithLocation} */ (node).end
|
|
2333
|
+
) {
|
|
2334
|
+
const comment = /** @type {AST.CommentWithLocation} */ (comments.shift());
|
|
2121
2335
|
(node.innerComments ||= []).push(comment);
|
|
2122
2336
|
}
|
|
2123
2337
|
if (node.innerComments && node.innerComments.length > 0) {
|
|
@@ -2125,57 +2339,58 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2125
2339
|
}
|
|
2126
2340
|
}
|
|
2127
2341
|
|
|
2128
|
-
const parent = /** @type {
|
|
2342
|
+
const parent = /** @type {AST.Node & AST.NodeWithLocation} */ (path.at(-1));
|
|
2129
2343
|
|
|
2130
2344
|
if (parent === undefined || node.end !== parent.end) {
|
|
2131
2345
|
const slice = source.slice(node.end, comments[0].start);
|
|
2132
2346
|
|
|
2133
2347
|
// Check if this node is the last item in an array-like structure
|
|
2134
2348
|
let is_last_in_array = false;
|
|
2135
|
-
|
|
2349
|
+
/** @type {(AST.Node | null)[] | null} */
|
|
2350
|
+
let node_array = null;
|
|
2351
|
+
let isParam = false;
|
|
2352
|
+
let isArgument = false;
|
|
2353
|
+
let isSwitchCaseSibling = false;
|
|
2136
2354
|
|
|
2137
2355
|
if (
|
|
2138
|
-
parent
|
|
2139
|
-
parent
|
|
2140
|
-
parent
|
|
2141
|
-
parent
|
|
2142
|
-
) {
|
|
2143
|
-
array_prop = 'body';
|
|
2144
|
-
} else if (parent?.type === 'SwitchStatement') {
|
|
2145
|
-
array_prop = 'cases';
|
|
2146
|
-
} else if (parent?.type === 'SwitchCase') {
|
|
2147
|
-
array_prop = 'consequent';
|
|
2148
|
-
} else if (
|
|
2149
|
-
parent?.type === 'ArrayExpression' ||
|
|
2150
|
-
parent?.type === 'TrackedArrayExpression'
|
|
2356
|
+
parent.type === 'BlockStatement' ||
|
|
2357
|
+
parent.type === 'Program' ||
|
|
2358
|
+
parent.type === 'Component' ||
|
|
2359
|
+
parent.type === 'ClassBody'
|
|
2151
2360
|
) {
|
|
2152
|
-
|
|
2361
|
+
node_array = parent.body;
|
|
2362
|
+
} else if (parent.type === 'SwitchStatement') {
|
|
2363
|
+
node_array = parent.cases;
|
|
2364
|
+
isSwitchCaseSibling = true;
|
|
2365
|
+
} else if (parent.type === 'SwitchCase') {
|
|
2366
|
+
node_array = parent.consequent;
|
|
2153
2367
|
} else if (
|
|
2154
|
-
parent
|
|
2155
|
-
parent
|
|
2368
|
+
parent.type === 'ArrayExpression' ||
|
|
2369
|
+
parent.type === 'TrackedArrayExpression'
|
|
2156
2370
|
) {
|
|
2157
|
-
|
|
2371
|
+
node_array = parent.elements;
|
|
2158
2372
|
} else if (
|
|
2159
|
-
parent
|
|
2160
|
-
parent
|
|
2161
|
-
parent?.type === 'ArrowFunctionExpression'
|
|
2373
|
+
parent.type === 'ObjectExpression' ||
|
|
2374
|
+
parent.type === 'TrackedObjectExpression'
|
|
2162
2375
|
) {
|
|
2163
|
-
|
|
2376
|
+
node_array = parent.properties;
|
|
2164
2377
|
} else if (
|
|
2165
|
-
parent
|
|
2166
|
-
parent
|
|
2167
|
-
parent
|
|
2378
|
+
parent.type === 'FunctionDeclaration' ||
|
|
2379
|
+
parent.type === 'FunctionExpression' ||
|
|
2380
|
+
parent.type === 'ArrowFunctionExpression'
|
|
2168
2381
|
) {
|
|
2169
|
-
|
|
2382
|
+
node_array = parent.params;
|
|
2383
|
+
isParam = true;
|
|
2384
|
+
} else if (parent.type === 'CallExpression' || parent.type === 'NewExpression') {
|
|
2385
|
+
node_array = parent.arguments;
|
|
2386
|
+
isArgument = true;
|
|
2170
2387
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2388
|
+
|
|
2389
|
+
if (node_array && Array.isArray(node_array)) {
|
|
2390
|
+
is_last_in_array = node_array.indexOf(node) === node_array.length - 1;
|
|
2174
2391
|
}
|
|
2175
2392
|
|
|
2176
2393
|
if (is_last_in_array) {
|
|
2177
|
-
const isParam = array_prop === 'params';
|
|
2178
|
-
const isArgument = array_prop === 'arguments';
|
|
2179
2394
|
if (isParam || isArgument) {
|
|
2180
2395
|
while (comments.length) {
|
|
2181
2396
|
const potentialComment = comments[0];
|
|
@@ -2186,7 +2401,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2186
2401
|
const nextChar = getNextNonWhitespaceCharacter(source, potentialComment.end);
|
|
2187
2402
|
if (nextChar === ')') {
|
|
2188
2403
|
(node.trailingComments ||= []).push(
|
|
2189
|
-
/** @type {CommentWithLocation} */ (comments.shift()),
|
|
2404
|
+
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
2190
2405
|
);
|
|
2191
2406
|
continue;
|
|
2192
2407
|
}
|
|
@@ -2207,7 +2422,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2207
2422
|
end = comment.end;
|
|
2208
2423
|
}
|
|
2209
2424
|
}
|
|
2210
|
-
} else if (node.end <= comments[0].start) {
|
|
2425
|
+
} else if (/** @type {AST.NodeWithLocation} */ (node).end <= comments[0].start) {
|
|
2211
2426
|
const onlySimpleWhitespace = /^[,) \t]*$/.test(slice);
|
|
2212
2427
|
const onlyWhitespace = /^\s*$/.test(slice);
|
|
2213
2428
|
const hasBlankLine = /\n\s*\n/.test(slice);
|
|
@@ -2217,7 +2432,6 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2217
2432
|
nodeEndLine !== null &&
|
|
2218
2433
|
commentStartLine !== null &&
|
|
2219
2434
|
commentStartLine === nodeEndLine + 1;
|
|
2220
|
-
const isSwitchCaseSibling = array_prop === 'cases';
|
|
2221
2435
|
|
|
2222
2436
|
if (isSwitchCaseSibling && !is_last_in_array) {
|
|
2223
2437
|
if (
|
|
@@ -2225,7 +2439,9 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2225
2439
|
commentStartLine !== null &&
|
|
2226
2440
|
nodeEndLine === commentStartLine
|
|
2227
2441
|
) {
|
|
2228
|
-
node.trailingComments = [
|
|
2442
|
+
node.trailingComments = [
|
|
2443
|
+
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
2444
|
+
];
|
|
2229
2445
|
}
|
|
2230
2446
|
return;
|
|
2231
2447
|
}
|
|
@@ -2237,14 +2453,9 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2237
2453
|
// Check if this is a block comment that's inline with the next statement
|
|
2238
2454
|
// e.g., /** @type {SomeType} */ (a) = 5;
|
|
2239
2455
|
// These should be leading comments, not trailing
|
|
2240
|
-
if (
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
array_prop &&
|
|
2244
|
-
parent[array_prop]
|
|
2245
|
-
) {
|
|
2246
|
-
const currentIndex = parent[array_prop].indexOf(node);
|
|
2247
|
-
const nextSibling = parent[array_prop][currentIndex + 1];
|
|
2456
|
+
if (comments[0].type === 'Block' && !is_last_in_array && node_array) {
|
|
2457
|
+
const currentIndex = node_array.indexOf(node);
|
|
2458
|
+
const nextSibling = node_array[currentIndex + 1];
|
|
2248
2459
|
|
|
2249
2460
|
if (nextSibling && nextSibling.loc) {
|
|
2250
2461
|
const commentEndLine = comments[0].loc?.end?.line;
|
|
@@ -2260,21 +2471,22 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2260
2471
|
|
|
2261
2472
|
// For function parameters, only attach as trailing comment if it's on the same line
|
|
2262
2473
|
// Comments on next line after comma should be leading comments of next parameter
|
|
2263
|
-
const isParam = array_prop === 'params';
|
|
2264
2474
|
if (isParam) {
|
|
2265
2475
|
// Check if comment is on same line as the node
|
|
2266
2476
|
const nodeEndLine = source.slice(0, node.end).split('\n').length;
|
|
2267
2477
|
const commentStartLine = source.slice(0, comments[0].start).split('\n').length;
|
|
2268
2478
|
if (nodeEndLine === commentStartLine) {
|
|
2269
2479
|
node.trailingComments = [
|
|
2270
|
-
/** @type {CommentWithLocation} */ (comments.shift()),
|
|
2480
|
+
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
2271
2481
|
];
|
|
2272
2482
|
}
|
|
2273
2483
|
// Otherwise leave it for next parameter's leading comments
|
|
2274
2484
|
} else {
|
|
2275
|
-
node.trailingComments = [
|
|
2485
|
+
node.trailingComments = [
|
|
2486
|
+
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
2487
|
+
];
|
|
2276
2488
|
}
|
|
2277
|
-
} else if (hasBlankLine && onlyWhitespace &&
|
|
2489
|
+
} else if (hasBlankLine && onlyWhitespace && node_array) {
|
|
2278
2490
|
// When there's a blank line between node and comment(s),
|
|
2279
2491
|
// check if there's also a blank line after the comment(s) before the next node
|
|
2280
2492
|
// If so, attach comments as trailing to preserve the grouping
|
|
@@ -2289,8 +2501,8 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2289
2501
|
return;
|
|
2290
2502
|
}
|
|
2291
2503
|
|
|
2292
|
-
const currentIndex =
|
|
2293
|
-
const nextSibling =
|
|
2504
|
+
const currentIndex = node_array.indexOf(node);
|
|
2505
|
+
const nextSibling = node_array[currentIndex + 1];
|
|
2294
2506
|
|
|
2295
2507
|
if (nextSibling && nextSibling.loc) {
|
|
2296
2508
|
// Find where the comment block ends
|
|
@@ -2336,7 +2548,9 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2336
2548
|
if (!commentsInsideElement) {
|
|
2337
2549
|
// Attach all the comments as trailing
|
|
2338
2550
|
for (let i = 0; i <= lastCommentIndex; i++) {
|
|
2339
|
-
(node.trailingComments ||= []).push(
|
|
2551
|
+
(node.trailingComments ||= []).push(
|
|
2552
|
+
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
2553
|
+
);
|
|
2340
2554
|
}
|
|
2341
2555
|
}
|
|
2342
2556
|
}
|
|
@@ -2355,10 +2569,10 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
2355
2569
|
* Parse Ripple source code into an AST
|
|
2356
2570
|
* @param {string} source
|
|
2357
2571
|
* @param {ParseOptions} [options]
|
|
2358
|
-
* @returns {Program}
|
|
2572
|
+
* @returns {AST.Program}
|
|
2359
2573
|
*/
|
|
2360
2574
|
export function parse(source, options) {
|
|
2361
|
-
/** @type {CommentWithLocation[]} */
|
|
2575
|
+
/** @type {AST.CommentWithLocation[]} */
|
|
2362
2576
|
const comments = [];
|
|
2363
2577
|
|
|
2364
2578
|
// Preprocess step 1: Add trailing commas to single-parameter generics followed by (
|
|
@@ -2413,7 +2627,7 @@ export function parse(source, options) {
|
|
|
2413
2627
|
sourceType: 'module',
|
|
2414
2628
|
ecmaVersion: 13,
|
|
2415
2629
|
locations: true,
|
|
2416
|
-
onComment
|
|
2630
|
+
onComment,
|
|
2417
2631
|
rippleOptions: {
|
|
2418
2632
|
loose: options?.loose || false,
|
|
2419
2633
|
},
|
|
@@ -2424,5 +2638,5 @@ export function parse(source, options) {
|
|
|
2424
2638
|
|
|
2425
2639
|
add_comments(ast);
|
|
2426
2640
|
|
|
2427
|
-
return
|
|
2641
|
+
return ast;
|
|
2428
2642
|
}
|