@tsrx/core 0.1.50 → 0.1.52
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 +10 -1
- package/src/analyze/prune.js +62 -77
- package/src/analyze/validation.js +7 -7
- package/src/parse/index.js +59 -80
- package/src/plugin.js +230 -95
- package/src/runtime/language-helpers.js +34 -25
- package/src/runtime/ref.js +57 -36
- package/src/scope.js +4 -5
- package/src/source-map-utils.js +8 -22
- package/src/transform/imports.js +8 -14
- package/src/transform/jsx/ast-builders.js +4 -1
- package/src/transform/jsx/index.js +1 -13
- package/src/transform/jsx/server-module.js +102 -96
- package/src/transform/jsx-hoist.js +4 -4
- package/src/transform/lazy.js +196 -137
- package/src/transform/scoping.js +108 -100
- package/src/transform/segments.js +24 -49
- package/src/transform/style-ref.js +92 -106
- package/src/transform/stylesheet.js +5 -21
- package/src/utils/ast.js +25 -20
- package/src/utils/builders.js +66 -2
- package/src/vite/dep-scan.js +135 -0
- package/types/index.d.ts +317 -17
- package/types/parse.d.ts +98 -13
- package/types/runtime/language-helpers.d.ts +10 -5
- package/types/runtime/ref.d.ts +63 -14
- package/types/vite/dep-scan.d.ts +26 -0
package/src/parse/index.js
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
@import * as AST from 'estree'
|
|
3
3
|
@import * as ESTreeJSX from 'estree-jsx'
|
|
4
4
|
@import { Parse } from '../../types/parse'
|
|
5
|
+
@import { BaseNodeMetaData } from '../../types/index'
|
|
5
6
|
@import { NonEmptyString } from '../../types/helpers'
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import * as acorn from 'acorn';
|
|
9
10
|
import { tsPlugin } from '@sveltejs/acorn-typescript';
|
|
10
11
|
import { walk } from 'zimmerframe';
|
|
11
|
-
import { has_location } from '../utils/ast.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {(BaseParser: typeof acorn.Parser) => typeof acorn.Parser} AcornPlugin
|
|
15
|
-
*/
|
|
12
|
+
import { has_location, node_children } from '../utils/ast.js';
|
|
16
13
|
|
|
17
14
|
/** @type {Parse.BindingType} */
|
|
18
15
|
export const BINDING_TYPES = {
|
|
@@ -87,14 +84,14 @@ export function isWhitespaceTextNode(node) {
|
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
/**
|
|
90
|
-
* @type {AcornPlugin}
|
|
87
|
+
* @type {Parse.AcornPlugin}
|
|
91
88
|
*/
|
|
92
89
|
function elementTemplateClosingTagPlugin(Base) {
|
|
93
|
-
const jsxTagStart =
|
|
90
|
+
const jsxTagStart = Base.acornTypeScript?.tokTypes?.jsxTagStart;
|
|
94
91
|
if (!jsxTagStart) return Base;
|
|
95
92
|
|
|
96
93
|
/**
|
|
97
|
-
* @param {
|
|
94
|
+
* @param {Parse.Parser} parser
|
|
98
95
|
*/
|
|
99
96
|
function inElementTemplateBodyDirect(parser) {
|
|
100
97
|
const stack = parser.context;
|
|
@@ -104,7 +101,7 @@ function elementTemplateClosingTagPlugin(Base) {
|
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
/**
|
|
107
|
-
* @param {
|
|
104
|
+
* @param {Parse.Parser} parser
|
|
108
105
|
*/
|
|
109
106
|
function inElementTemplateBodyAnywhere(parser) {
|
|
110
107
|
const stack = parser.context;
|
|
@@ -122,7 +119,7 @@ function elementTemplateClosingTagPlugin(Base) {
|
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
/**
|
|
125
|
-
* @param {
|
|
122
|
+
* @param {Parse.Parser} parser
|
|
126
123
|
*/
|
|
127
124
|
function isOpeningTagAfterReturnKeyword(parser) {
|
|
128
125
|
if (parser.input.charCodeAt(parser.start + 1) === 47 /* '/' */) return false;
|
|
@@ -153,32 +150,26 @@ function elementTemplateClosingTagPlugin(Base) {
|
|
|
153
150
|
|
|
154
151
|
return class extends Base {
|
|
155
152
|
/** @param {number} code */
|
|
156
|
-
// @ts-ignore — extending acorn's Parser with internal hooks
|
|
157
153
|
getTokenFromCode(code) {
|
|
158
|
-
if (code === 60 /* '<' */ && !
|
|
159
|
-
const self = /** @type {any} */ (this);
|
|
154
|
+
if (code === 60 /* '<' */ && !this.inType) {
|
|
160
155
|
const nextChar =
|
|
161
|
-
|
|
162
|
-
if (nextChar === 47 /* '/' */ && inElementTemplateBodyDirect(
|
|
163
|
-
++
|
|
164
|
-
return
|
|
156
|
+
this.pos + 1 < this.input.length ? this.input.charCodeAt(this.pos + 1) : -1;
|
|
157
|
+
if (nextChar === 47 /* '/' */ && inElementTemplateBodyDirect(this)) {
|
|
158
|
+
++this.pos;
|
|
159
|
+
return this.finishToken(jsxTagStart);
|
|
165
160
|
}
|
|
166
161
|
}
|
|
167
|
-
// @ts-ignore — super dispatches to next layer in the plugin chain
|
|
168
162
|
return super.getTokenFromCode(code);
|
|
169
163
|
}
|
|
170
164
|
|
|
171
|
-
// @ts-ignore — extending acorn's Parser with internal hooks
|
|
172
165
|
canInsertSemicolon() {
|
|
173
|
-
const self = /** @type {any} */ (this);
|
|
174
166
|
if (
|
|
175
|
-
|
|
176
|
-
inElementTemplateBodyAnywhere(
|
|
177
|
-
!isOpeningTagAfterReturnKeyword(
|
|
167
|
+
this.type === jsxTagStart &&
|
|
168
|
+
inElementTemplateBodyAnywhere(this) &&
|
|
169
|
+
!isOpeningTagAfterReturnKeyword(this)
|
|
178
170
|
) {
|
|
179
171
|
return true;
|
|
180
172
|
}
|
|
181
|
-
// @ts-ignore
|
|
182
173
|
return super.canInsertSemicolon();
|
|
183
174
|
}
|
|
184
175
|
};
|
|
@@ -190,16 +181,20 @@ function elementTemplateClosingTagPlugin(Base) {
|
|
|
190
181
|
* This is the core factory for building tsrx-based parsers. Framework plugins (like TSRXPlugin)
|
|
191
182
|
* extend the base parser with framework-specific syntax.
|
|
192
183
|
*
|
|
193
|
-
* @param {...
|
|
194
|
-
* @returns {<T extends string>(source: string, filename: NonEmptyString<T>, options?:
|
|
184
|
+
* @param {...Parse.AcornPlugin} plugins - Framework parser plugins to compose
|
|
185
|
+
* @returns {<T extends string>(source: string, filename: NonEmptyString<T>, options?: Parse.ParseFunctionOptions) => AST.Program} A parse function
|
|
195
186
|
*/
|
|
196
187
|
export function createParser(...plugins) {
|
|
197
188
|
const parser = /** @type {Parse.ParserConstructor} */ (
|
|
198
189
|
/** @type {unknown} */ (
|
|
199
190
|
acorn.Parser.extend(
|
|
200
191
|
tsPlugin({ jsx: true }),
|
|
201
|
-
|
|
202
|
-
|
|
192
|
+
.../** @type {Array<(BaseParser: typeof acorn.Parser) => typeof acorn.Parser>} */ (
|
|
193
|
+
/** @type {unknown} */ (plugins)
|
|
194
|
+
),
|
|
195
|
+
/** @type {(BaseParser: typeof acorn.Parser) => typeof acorn.Parser} */ (
|
|
196
|
+
/** @type {unknown} */ (elementTemplateClosingTagPlugin)
|
|
197
|
+
),
|
|
203
198
|
)
|
|
204
199
|
)
|
|
205
200
|
);
|
|
@@ -207,7 +202,7 @@ export function createParser(...plugins) {
|
|
|
207
202
|
/**
|
|
208
203
|
* @param {string} source
|
|
209
204
|
* @param {string} filename
|
|
210
|
-
* @param {
|
|
205
|
+
* @param {Parse.ParseFunctionOptions} [options]
|
|
211
206
|
* @returns {AST.Program}
|
|
212
207
|
*/
|
|
213
208
|
return function parse(source, filename, options) {
|
|
@@ -225,20 +220,19 @@ export function createParser(...plugins) {
|
|
|
225
220
|
// node records. The tokenizer is the only correct source — offset
|
|
226
221
|
// arithmetic breaks on extra whitespace, and text search breaks on
|
|
227
222
|
// comments (`async /* function */ function`).
|
|
228
|
-
/** @type {
|
|
223
|
+
/** @type {Parse.KeywordToken[] | undefined} */
|
|
229
224
|
const keyword_tokens = options?.keywordTokens ? [] : undefined;
|
|
230
225
|
/** @type {Parse.Options['onToken'] | undefined} */
|
|
231
226
|
const onToken = keyword_tokens
|
|
232
227
|
? (token) => {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
if (is_function_keyword || is_async_name) {
|
|
228
|
+
const is_function_keyword = token.type.keyword === 'function';
|
|
229
|
+
const is_async_name = token.type.label === 'name' && token.value === 'async';
|
|
230
|
+
if ((is_function_keyword || is_async_name) && token.loc) {
|
|
237
231
|
keyword_tokens.push({
|
|
238
232
|
value: is_function_keyword ? 'function' : 'async',
|
|
239
|
-
start:
|
|
240
|
-
end:
|
|
241
|
-
loc:
|
|
233
|
+
start: token.start,
|
|
234
|
+
end: token.end,
|
|
235
|
+
loc: token.loc,
|
|
242
236
|
});
|
|
243
237
|
}
|
|
244
238
|
}
|
|
@@ -273,7 +267,7 @@ export function createParser(...plugins) {
|
|
|
273
267
|
add_comments(ast);
|
|
274
268
|
|
|
275
269
|
if (keyword_tokens) {
|
|
276
|
-
|
|
270
|
+
ast.tsrx_keyword_tokens = keyword_tokens;
|
|
277
271
|
}
|
|
278
272
|
|
|
279
273
|
return ast;
|
|
@@ -305,80 +299,68 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
305
299
|
}
|
|
306
300
|
|
|
307
301
|
/**
|
|
308
|
-
* @param {
|
|
309
|
-
* @returns {node is
|
|
302
|
+
* @param {AST.Node | AST.CSS.Node | null | undefined} node
|
|
303
|
+
* @returns {node is AST.NativeTSRXTemplateNode & AST.NodeWithLocation}
|
|
310
304
|
*/
|
|
311
305
|
function isNativeTemplateNode(node) {
|
|
312
306
|
return (
|
|
313
307
|
(node?.type === 'JSXElement' ||
|
|
314
308
|
node?.type === 'JSXFragment' ||
|
|
315
309
|
node?.type === 'JSXStyleElement') &&
|
|
316
|
-
node.metadata?.native_tsrx
|
|
310
|
+
node.metadata?.native_tsrx === true
|
|
317
311
|
);
|
|
318
312
|
}
|
|
319
313
|
|
|
320
314
|
/**
|
|
321
|
-
* @param {
|
|
322
|
-
* @returns {node is
|
|
315
|
+
* @param {AST.Node | AST.CSS.Node | null | undefined} node
|
|
316
|
+
* @returns {node is AST.TSRXElementNode & AST.NodeWithLocation}
|
|
323
317
|
*/
|
|
324
318
|
function isNativeTemplateElement(node) {
|
|
325
319
|
return (
|
|
326
320
|
(node?.type === 'JSXElement' || node?.type === 'JSXStyleElement') &&
|
|
327
|
-
node.metadata?.native_tsrx
|
|
321
|
+
node.metadata?.native_tsrx === true
|
|
328
322
|
);
|
|
329
323
|
}
|
|
330
324
|
|
|
331
325
|
/**
|
|
332
|
-
* @param {
|
|
333
|
-
* @returns {AST.
|
|
334
|
-
*/
|
|
335
|
-
function getTemplateChildren(node) {
|
|
336
|
-
return Array.isArray(node?.children)
|
|
337
|
-
? /** @type {AST.Node[]} */ (/** @type {unknown} */ (node.children))
|
|
338
|
-
: [];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* @param {any} node
|
|
343
|
-
* @returns {node is (ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment) & AST.NodeWithLocation}
|
|
326
|
+
* @param {AST.Node | AST.CSS.Node | null | undefined} node
|
|
327
|
+
* @returns {node is AST.NativeTSRXTemplateNode & AST.NodeWithLocation}
|
|
344
328
|
*/
|
|
345
329
|
function isEmptyTemplateNode(node) {
|
|
346
|
-
return isNativeTemplateNode(node) &&
|
|
330
|
+
return isNativeTemplateNode(node) && node.children.length === 0;
|
|
347
331
|
}
|
|
348
332
|
|
|
349
333
|
/**
|
|
350
|
-
* @param {
|
|
351
|
-
* @returns {
|
|
334
|
+
* @param {AST.Node} node
|
|
335
|
+
* @returns {BaseNodeMetaData}
|
|
352
336
|
*/
|
|
353
337
|
function getNodeMetadata(node) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return target.metadata;
|
|
338
|
+
node.metadata ??= { path: [] };
|
|
339
|
+
return node.metadata;
|
|
357
340
|
}
|
|
358
341
|
|
|
359
342
|
/**
|
|
360
|
-
* @param {
|
|
343
|
+
* @param {AST.NodeWithMaybeComments} node
|
|
361
344
|
* @param {AST.CommentWithLocation} comment
|
|
362
345
|
*/
|
|
363
346
|
function pushInnerComment(node, comment) {
|
|
364
|
-
|
|
365
|
-
(target.innerComments ||= []).push(comment);
|
|
347
|
+
(node.innerComments ||= []).push(comment);
|
|
366
348
|
}
|
|
367
349
|
|
|
368
350
|
/**
|
|
369
|
-
* @param {
|
|
351
|
+
* @param {AST.NodeWithMaybeComments} node
|
|
370
352
|
* @returns {boolean}
|
|
371
353
|
*/
|
|
372
354
|
function hasInnerComments(node) {
|
|
373
|
-
return !!
|
|
355
|
+
return !!node.innerComments?.length;
|
|
374
356
|
}
|
|
375
357
|
|
|
376
358
|
/**
|
|
377
|
-
* @param {
|
|
359
|
+
* @param {AST.TSRXElementNode} node
|
|
378
360
|
* @returns {string | null}
|
|
379
361
|
*/
|
|
380
362
|
function getJSXElementName(node) {
|
|
381
|
-
const name = node.openingElement
|
|
363
|
+
const name = node.openingElement.name;
|
|
382
364
|
if (!name) return null;
|
|
383
365
|
if (name.type === 'JSXIdentifier') return name.name;
|
|
384
366
|
if (name.type === 'JSXNamespacedName') return `${name.namespace.name}:${name.name.name}`;
|
|
@@ -478,7 +460,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
478
460
|
|
|
479
461
|
/**
|
|
480
462
|
* @param {AST.CommentWithLocation} comment
|
|
481
|
-
* @returns {(
|
|
463
|
+
* @returns {(AST.NativeTSRXTemplateNode & AST.NodeWithLocation) | null}
|
|
482
464
|
*/
|
|
483
465
|
function getEmptyElementInnerCommentTarget(comment) {
|
|
484
466
|
const element = path.findLast((ancestor) => isNativeTemplateNode(ancestor));
|
|
@@ -505,15 +487,12 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
505
487
|
// parent <style> element's content range so they don't leak to
|
|
506
488
|
// subsequent JS nodes.
|
|
507
489
|
if (node.type === 'StyleSheet') {
|
|
508
|
-
const styleElement =
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
);
|
|
515
|
-
if (styleElement) {
|
|
516
|
-
const cssStart = styleElement.openingElement?.end ?? styleElement.start;
|
|
490
|
+
const styleElement = path.findLast(
|
|
491
|
+
(ancestor) =>
|
|
492
|
+
isNativeTemplateElement(ancestor) && getJSXElementName(ancestor) === 'style',
|
|
493
|
+
);
|
|
494
|
+
if (isNativeTemplateElement(styleElement)) {
|
|
495
|
+
const cssStart = styleElement.openingElement.end ?? styleElement.start;
|
|
517
496
|
const cssEnd = styleElement.closingElement?.start ?? styleElement.end;
|
|
518
497
|
while (comments[0] && comments[0].start >= cssStart && comments[0].end <= cssEnd) {
|
|
519
498
|
comments.shift();
|
|
@@ -540,7 +519,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
540
519
|
// before the child element is pushed to the parser's #path, causing
|
|
541
520
|
// comments inside the child to get the parent's containerId.
|
|
542
521
|
const commentStart = comments[0].start;
|
|
543
|
-
const isInsideChildElement =
|
|
522
|
+
const isInsideChildElement = node_children(node).some(
|
|
544
523
|
(child) =>
|
|
545
524
|
child &&
|
|
546
525
|
child.start !== undefined &&
|