@tsrx/core 0.0.22 → 0.0.24
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/README.md +1 -1
- package/package.json +1 -1
- package/src/analyze/validation.js +79 -4
- package/src/identifier-utils.js +1 -2
- package/src/index.js +11 -1
- package/src/plugin.js +189 -99
- package/src/scope.js +12 -4
- package/src/transform/jsx/helpers.js +6 -0
- package/src/transform/jsx/index.js +596 -43
- package/src/transform/segments.js +32 -5
- package/src/utils/builders.js +10 -0
- package/types/index.d.ts +24 -33
- package/types/jsx-platform.d.ts +6 -0
- package/types/parse.d.ts +2 -5
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ The TSRX website is the canonical source for language documentation:
|
|
|
42
42
|
- [Getting Started](https://tsrx.dev/getting-started) — install TSRX for React,
|
|
43
43
|
Preact, Solid, Vue, or Ripple and configure editor/AI tooling.
|
|
44
44
|
- [Features](https://tsrx.dev/features) — examples of components, statement
|
|
45
|
-
templates, control flow, scoped styles,
|
|
45
|
+
templates, control flow, scoped styles, submodules, and lazy destructuring.
|
|
46
46
|
- [Specification](https://tsrx.dev/specification) — the current grammar and
|
|
47
47
|
parser-level semantics.
|
|
48
48
|
|
package/package.json
CHANGED
|
@@ -7,6 +7,18 @@ import { error } from '../errors.js';
|
|
|
7
7
|
|
|
8
8
|
export const COMPONENT_RETURN_VALUE_ERROR =
|
|
9
9
|
'Return statements inside components cannot have a return value.';
|
|
10
|
+
export const COMPONENT_LOOP_RETURN_ERROR =
|
|
11
|
+
'Return statements are not allowed inside component for...of loops. Use continue instead.';
|
|
12
|
+
export const COMPONENT_LOOP_BREAK_ERROR =
|
|
13
|
+
'Break statements are not allowed inside component for...of loops.';
|
|
14
|
+
export const COMPONENT_FOR_STATEMENT_ERROR =
|
|
15
|
+
'For loops are not supported in components. Use for...of instead.';
|
|
16
|
+
export const COMPONENT_FOR_IN_STATEMENT_ERROR =
|
|
17
|
+
'For...in loops are not supported in components. Use for...of instead.';
|
|
18
|
+
export const COMPONENT_WHILE_STATEMENT_ERROR =
|
|
19
|
+
'While loops are not supported in components. Move the while loop into a function.';
|
|
20
|
+
export const COMPONENT_DO_WHILE_STATEMENT_ERROR =
|
|
21
|
+
'Do...while loops are not supported in components. Move the do...while loop into a function.';
|
|
10
22
|
|
|
11
23
|
const invalid_nestings = {
|
|
12
24
|
// <p> cannot contain block-level elements
|
|
@@ -133,19 +145,29 @@ function get_element_tag(element) {
|
|
|
133
145
|
* @returns {AST.ReturnStatement}
|
|
134
146
|
*/
|
|
135
147
|
export function get_return_keyword_node(node) {
|
|
136
|
-
|
|
148
|
+
return get_statement_keyword_node(node, 'return');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @template {AST.Node} T
|
|
153
|
+
* @param {T} node
|
|
154
|
+
* @param {string} keyword
|
|
155
|
+
* @returns {T}
|
|
156
|
+
*/
|
|
157
|
+
export function get_statement_keyword_node(node, keyword) {
|
|
158
|
+
const keyword_length = keyword.length;
|
|
137
159
|
const start = /** @type {AST.NodeWithLocation} */ (node).start ?? 0;
|
|
138
160
|
const loc = /** @type {AST.NodeWithLocation} */ (node).loc;
|
|
139
161
|
|
|
140
|
-
return /** @type {
|
|
162
|
+
return /** @type {T} */ ({
|
|
141
163
|
...node,
|
|
142
|
-
end: start +
|
|
164
|
+
end: start + keyword_length,
|
|
143
165
|
loc: loc
|
|
144
166
|
? {
|
|
145
167
|
start: loc.start,
|
|
146
168
|
end: {
|
|
147
169
|
line: loc.start.line,
|
|
148
|
-
column: loc.start.column +
|
|
170
|
+
column: loc.start.column + keyword_length,
|
|
149
171
|
},
|
|
150
172
|
}
|
|
151
173
|
: undefined,
|
|
@@ -172,6 +194,59 @@ export function validate_component_return_statement(node, filename, errors, comm
|
|
|
172
194
|
);
|
|
173
195
|
}
|
|
174
196
|
|
|
197
|
+
/**
|
|
198
|
+
* @param {AST.ReturnStatement} node
|
|
199
|
+
* @param {string | null | undefined} filename
|
|
200
|
+
* @param {CompileError[]} [errors]
|
|
201
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
202
|
+
*/
|
|
203
|
+
export function validate_component_loop_return_statement(node, filename, errors, comments) {
|
|
204
|
+
error(
|
|
205
|
+
COMPONENT_LOOP_RETURN_ERROR,
|
|
206
|
+
filename ?? null,
|
|
207
|
+
get_return_keyword_node(node),
|
|
208
|
+
errors,
|
|
209
|
+
comments,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {AST.BreakStatement} node
|
|
215
|
+
* @param {string | null | undefined} filename
|
|
216
|
+
* @param {CompileError[]} [errors]
|
|
217
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
218
|
+
*/
|
|
219
|
+
export function validate_component_loop_break_statement(node, filename, errors, comments) {
|
|
220
|
+
error(
|
|
221
|
+
COMPONENT_LOOP_BREAK_ERROR,
|
|
222
|
+
filename ?? null,
|
|
223
|
+
get_statement_keyword_node(node, 'break'),
|
|
224
|
+
errors,
|
|
225
|
+
comments,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {AST.ForStatement | AST.ForInStatement | AST.WhileStatement | AST.DoWhileStatement} node
|
|
231
|
+
* @param {string | null | undefined} filename
|
|
232
|
+
* @param {CompileError[]} [errors]
|
|
233
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
234
|
+
*/
|
|
235
|
+
export function validate_component_unsupported_loop_statement(node, filename, errors, comments) {
|
|
236
|
+
let message;
|
|
237
|
+
if (node.type === 'ForStatement') {
|
|
238
|
+
message = COMPONENT_FOR_STATEMENT_ERROR;
|
|
239
|
+
} else if (node.type === 'ForInStatement') {
|
|
240
|
+
message = COMPONENT_FOR_IN_STATEMENT_ERROR;
|
|
241
|
+
} else if (node.type === 'WhileStatement') {
|
|
242
|
+
message = COMPONENT_WHILE_STATEMENT_ERROR;
|
|
243
|
+
} else {
|
|
244
|
+
message = COMPONENT_DO_WHILE_STATEMENT_ERROR;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
error(message, filename ?? null, node, errors, comments);
|
|
248
|
+
}
|
|
249
|
+
|
|
175
250
|
/**
|
|
176
251
|
* @param {AST.Element} element
|
|
177
252
|
* @param {AnalysisContext} context
|
package/src/identifier-utils.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export const IDENTIFIER_OBFUSCATION_PREFIX = '_$_';
|
|
2
|
-
export const
|
|
3
|
-
export const SERVER_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + encode_utf16_char('#') + 'server';
|
|
2
|
+
export const SERVER_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + 'server_$_';
|
|
4
3
|
export const CSS_HASH_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + 'hash';
|
|
5
4
|
|
|
6
5
|
const DECODE_UTF16_REGEX = /_u([0-9a-fA-F]{4})_/g;
|
package/src/index.js
CHANGED
|
@@ -50,7 +50,6 @@ export {
|
|
|
50
50
|
// Identifier utils
|
|
51
51
|
export {
|
|
52
52
|
IDENTIFIER_OBFUSCATION_PREFIX,
|
|
53
|
-
STYLE_IDENTIFIER,
|
|
54
53
|
SERVER_IDENTIFIER,
|
|
55
54
|
CSS_HASH_IDENTIFIER,
|
|
56
55
|
obfuscate_identifier as obfuscateIdentifier,
|
|
@@ -141,6 +140,7 @@ export { escape } from './utils/escaping.js';
|
|
|
141
140
|
export {
|
|
142
141
|
createJsxTransform,
|
|
143
142
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
143
|
+
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
144
144
|
to_jsx_attribute as toJsxAttribute,
|
|
145
145
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
|
146
146
|
component_to_function_declaration as componentToFunctionDeclaration,
|
|
@@ -210,8 +210,18 @@ export {
|
|
|
210
210
|
// Analyze
|
|
211
211
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
|
212
212
|
export {
|
|
213
|
+
COMPONENT_DO_WHILE_STATEMENT_ERROR,
|
|
214
|
+
COMPONENT_FOR_IN_STATEMENT_ERROR,
|
|
215
|
+
COMPONENT_FOR_STATEMENT_ERROR,
|
|
216
|
+
COMPONENT_LOOP_BREAK_ERROR,
|
|
217
|
+
COMPONENT_LOOP_RETURN_ERROR,
|
|
213
218
|
COMPONENT_RETURN_VALUE_ERROR,
|
|
219
|
+
COMPONENT_WHILE_STATEMENT_ERROR,
|
|
214
220
|
get_return_keyword_node as getReturnKeywordNode,
|
|
221
|
+
get_statement_keyword_node as getStatementKeywordNode,
|
|
222
|
+
validate_component_loop_break_statement as validateComponentLoopBreakStatement,
|
|
223
|
+
validate_component_loop_return_statement as validateComponentLoopReturnStatement,
|
|
215
224
|
validate_component_return_statement as validateComponentReturnStatement,
|
|
225
|
+
validate_component_unsupported_loop_statement as validateComponentUnsupportedLoopStatement,
|
|
216
226
|
validate_nesting as validateNesting,
|
|
217
227
|
} from './analyze/validation.js';
|
package/src/plugin.js
CHANGED
|
@@ -189,7 +189,7 @@ function previous_word_before(input, pos) {
|
|
|
189
189
|
/**
|
|
190
190
|
* Acorn parser plugin for Ripple syntax extensions.
|
|
191
191
|
* Adds support for: component declarations, &[]/&{} lazy destructuring,
|
|
192
|
-
*
|
|
192
|
+
* submodule imports, TSRX directives, and enhanced JSX handling.
|
|
193
193
|
*
|
|
194
194
|
* @param {import('../types/index').TSRXPluginConfig} [config] - Plugin configuration
|
|
195
195
|
* @returns {(Parser: Parse.ParserConstructor) => Parse.ParserConstructor} Parser extension function
|
|
@@ -221,6 +221,22 @@ export function TSRXPlugin(config) {
|
|
|
221
221
|
#filename = null;
|
|
222
222
|
#functionBodyDepth = 0;
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @type {Parse.Parser['finishNode']}
|
|
226
|
+
*/
|
|
227
|
+
finishNode(node, type) {
|
|
228
|
+
const finished = super.finishNode(node, type);
|
|
229
|
+
if (type === 'TSModuleDeclaration') {
|
|
230
|
+
const start = /** @type {number} */ (finished.start);
|
|
231
|
+
const source = this.input.slice(start, start + 'namespace'.length);
|
|
232
|
+
finished.metadata ??= { path: [] };
|
|
233
|
+
finished.metadata.module_keyword = source.startsWith('namespace')
|
|
234
|
+
? 'namespace'
|
|
235
|
+
: 'module';
|
|
236
|
+
}
|
|
237
|
+
return finished;
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
/**
|
|
225
241
|
* @param {Parse.Options} options
|
|
226
242
|
* @param {string} input
|
|
@@ -234,6 +250,13 @@ export function TSRXPlugin(config) {
|
|
|
234
250
|
this.#filename = tsrx_options?.filename || null;
|
|
235
251
|
}
|
|
236
252
|
|
|
253
|
+
#resetTokenStartToCurrentPosition() {
|
|
254
|
+
if (this.start !== this.pos) {
|
|
255
|
+
this.start = this.pos;
|
|
256
|
+
this.startLoc = this.curPosition();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
237
260
|
#previousNonWhitespaceChar() {
|
|
238
261
|
let index = this.pos - 1;
|
|
239
262
|
while (index >= 0) {
|
|
@@ -246,6 +269,50 @@ export function TSRXPlugin(config) {
|
|
|
246
269
|
return null;
|
|
247
270
|
}
|
|
248
271
|
|
|
272
|
+
#popTsxTokenContextBeforeTemplateExpressionChild() {
|
|
273
|
+
let index = this.pos;
|
|
274
|
+
let has_newline = false;
|
|
275
|
+
|
|
276
|
+
// Text-only Tsx nodes can leave the tokenizer in JSX text mode.
|
|
277
|
+
// Only unwind it for ASI before a following TSRX `{expr}` child;
|
|
278
|
+
// fragment props like `content={<></>}` still need the JSX context.
|
|
279
|
+
while (index < this.input.length) {
|
|
280
|
+
const ch = this.input.charCodeAt(index);
|
|
281
|
+
if (ch === 32 || ch === 9) {
|
|
282
|
+
index++;
|
|
283
|
+
} else if (ch === 10 || ch === 13) {
|
|
284
|
+
has_newline = true;
|
|
285
|
+
index++;
|
|
286
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 42) {
|
|
287
|
+
const end = this.input.indexOf('*/', index + 2);
|
|
288
|
+
const comment_end = end === -1 ? this.input.length : end + 2;
|
|
289
|
+
if (this.input.slice(index, comment_end).match(regex_newline_characters)) {
|
|
290
|
+
has_newline = true;
|
|
291
|
+
}
|
|
292
|
+
index = comment_end;
|
|
293
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 47) {
|
|
294
|
+
has_newline = true;
|
|
295
|
+
index += 2;
|
|
296
|
+
while (index < this.input.length) {
|
|
297
|
+
const comment_ch = this.input.charCodeAt(index);
|
|
298
|
+
if (comment_ch === 10 || comment_ch === 13) break;
|
|
299
|
+
index++;
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!has_newline || this.input.charCodeAt(index) !== 123) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const context_index = this.context.lastIndexOf(tstc.tc_expr);
|
|
311
|
+
if (context_index !== -1) {
|
|
312
|
+
this.context.length = context_index;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
249
316
|
#isDoubleQuotedTextChildStart() {
|
|
250
317
|
if (this.#path.findLast((n) => n.type === 'TsxCompat' || n.type === 'Tsx')) {
|
|
251
318
|
return false;
|
|
@@ -820,44 +887,6 @@ export function TSRXPlugin(config) {
|
|
|
820
887
|
}
|
|
821
888
|
}
|
|
822
889
|
|
|
823
|
-
if (code === 35) {
|
|
824
|
-
// # character
|
|
825
|
-
if (this.pos + 1 < this.input.length) {
|
|
826
|
-
/** @param {string} value */
|
|
827
|
-
const startsWith = (value) =>
|
|
828
|
-
this.input.slice(this.pos, this.pos + value.length) === value;
|
|
829
|
-
/** @param {number} length */
|
|
830
|
-
const char_after = (length) =>
|
|
831
|
-
this.pos + length < this.input.length ? this.input.charCodeAt(this.pos + length) : -1;
|
|
832
|
-
/** @param {number} ch */
|
|
833
|
-
const is_ripple_delimiter = (ch) =>
|
|
834
|
-
ch === 40 || // (
|
|
835
|
-
ch === 41 || // )
|
|
836
|
-
ch === 60 || // <
|
|
837
|
-
ch === 46 || // .
|
|
838
|
-
ch === 44 || // ,
|
|
839
|
-
ch === 59 || // ;
|
|
840
|
-
ch === 91 || // [
|
|
841
|
-
ch === 93 || // ]
|
|
842
|
-
ch === 123 || // {
|
|
843
|
-
ch === 125 || // }
|
|
844
|
-
ch === 32 || // space
|
|
845
|
-
ch === 9 || // tab
|
|
846
|
-
ch === 10 || // newline
|
|
847
|
-
ch === 13 || // carriage return
|
|
848
|
-
ch === -1; // EOF
|
|
849
|
-
|
|
850
|
-
if (startsWith('#server') && is_ripple_delimiter(char_after(7))) {
|
|
851
|
-
this.pos += 7;
|
|
852
|
-
return this.finishToken(tt.name, '#server');
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (startsWith('#style') && is_ripple_delimiter(char_after(6))) {
|
|
856
|
-
this.pos += 6;
|
|
857
|
-
return this.finishToken(tt.name, '#style');
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
890
|
this.#allowTagStartAfterDoubleQuotedText = false;
|
|
862
891
|
return super.getTokenFromCode(code);
|
|
863
892
|
}
|
|
@@ -970,22 +999,6 @@ export function TSRXPlugin(config) {
|
|
|
970
999
|
* @type {Parse.Parser['parseExprAtom']}
|
|
971
1000
|
*/
|
|
972
1001
|
parseExprAtom(refDestructuringErrors, forNew, forInit) {
|
|
973
|
-
const lookahead_type = this.lookahead().type;
|
|
974
|
-
const is_next_call_token = lookahead_type === tt.parenL || lookahead_type === tt.relational;
|
|
975
|
-
|
|
976
|
-
// Check if this is #server identifier for server function calls
|
|
977
|
-
if (this.type === tt.name && this.value === '#server') {
|
|
978
|
-
const node = this.startNode();
|
|
979
|
-
this.next();
|
|
980
|
-
return /** @type {AST.ServerIdentifier} */ (this.finishNode(node, 'ServerIdentifier'));
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
if (this.type === tt.name && this.value === '#style') {
|
|
984
|
-
const node = this.startNode();
|
|
985
|
-
this.next();
|
|
986
|
-
return /** @type {AST.StyleIdentifier} */ (this.finishNode(node, 'StyleIdentifier'));
|
|
987
|
-
}
|
|
988
|
-
|
|
989
1002
|
// Check if this is a component expression (e.g., in object literal values)
|
|
990
1003
|
if (this.type === tt.name && this.value === 'component') {
|
|
991
1004
|
return this.parseComponent();
|
|
@@ -1015,9 +1028,9 @@ export function TSRXPlugin(config) {
|
|
|
1015
1028
|
|
|
1016
1029
|
/**
|
|
1017
1030
|
* Override checkLocalExport to check all scopes in the scope stack.
|
|
1018
|
-
* This is needed because
|
|
1019
|
-
* from within
|
|
1020
|
-
* declared in the
|
|
1031
|
+
* This is needed because submodules create nested scopes, but exports
|
|
1032
|
+
* from within submodules should still be valid if the identifier is
|
|
1033
|
+
* declared in the submodule scope (not just the top-level module scope).
|
|
1021
1034
|
* @type {Parse.Parser['checkLocalExport']}
|
|
1022
1035
|
*/
|
|
1023
1036
|
checkLocalExport(id) {
|
|
@@ -1036,31 +1049,6 @@ export function TSRXPlugin(config) {
|
|
|
1036
1049
|
this.undefinedExports[name] = id;
|
|
1037
1050
|
}
|
|
1038
1051
|
|
|
1039
|
-
/**
|
|
1040
|
-
* @type {Parse.Parser['parseServerBlock']}
|
|
1041
|
-
*/
|
|
1042
|
-
parseServerBlock() {
|
|
1043
|
-
const node = /** @type {AST.ServerBlock} */ (this.startNode());
|
|
1044
|
-
this.next();
|
|
1045
|
-
|
|
1046
|
-
const body = /** @type {AST.ServerBlockStatement} */ (this.startNode());
|
|
1047
|
-
node.body = body;
|
|
1048
|
-
body.body = [];
|
|
1049
|
-
|
|
1050
|
-
this.expect(tt.braceL);
|
|
1051
|
-
this.enterScope(0);
|
|
1052
|
-
while (this.type !== tt.braceR) {
|
|
1053
|
-
const stmt = /** @type {AST.Statement} */ (this.parseStatement(null, true));
|
|
1054
|
-
body.body.push(stmt);
|
|
1055
|
-
}
|
|
1056
|
-
this.next();
|
|
1057
|
-
this.exitScope();
|
|
1058
|
-
this.finishNode(body, 'BlockStatement');
|
|
1059
|
-
|
|
1060
|
-
this.awaitPos = 0;
|
|
1061
|
-
return this.finishNode(node, 'ServerBlock');
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
1052
|
/**
|
|
1065
1053
|
* Parse a component - common implementation used by statements, expressions, and export defaults
|
|
1066
1054
|
* @type {Parse.Parser['parseComponent']}
|
|
@@ -1415,10 +1403,26 @@ export function TSRXPlugin(config) {
|
|
|
1415
1403
|
'"text" is a TSRX keyword and must be used in the form {text some_value}',
|
|
1416
1404
|
);
|
|
1417
1405
|
}
|
|
1406
|
+
} else if (
|
|
1407
|
+
this.type === tt.name &&
|
|
1408
|
+
this.value === 'style' &&
|
|
1409
|
+
this.lookahead().type === tt.string
|
|
1410
|
+
) {
|
|
1411
|
+
node.style = true;
|
|
1412
|
+
this.next();
|
|
1418
1413
|
}
|
|
1419
1414
|
|
|
1420
1415
|
node.expression =
|
|
1421
1416
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
1417
|
+
if (
|
|
1418
|
+
node.style &&
|
|
1419
|
+
(node.expression.type !== 'Literal' || typeof node.expression.value !== 'string')
|
|
1420
|
+
) {
|
|
1421
|
+
this.raise(
|
|
1422
|
+
/** @type {number} */ (node.expression.start),
|
|
1423
|
+
'"style" is a TSRX keyword and must be used in the form {style "class_name"}',
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1422
1426
|
this.expect(tt.braceR);
|
|
1423
1427
|
|
|
1424
1428
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
@@ -1812,6 +1816,7 @@ export function TSRXPlugin(config) {
|
|
|
1812
1816
|
break;
|
|
1813
1817
|
}
|
|
1814
1818
|
// If not a comment, fall through to default case
|
|
1819
|
+
this.#resetTokenStartToCurrentPosition();
|
|
1815
1820
|
this.context.push(b_stat);
|
|
1816
1821
|
this.exprAllowed = true;
|
|
1817
1822
|
return original.readToken.call(this, ch);
|
|
@@ -1831,6 +1836,7 @@ export function TSRXPlugin(config) {
|
|
|
1831
1836
|
this.#path.at(-1)?.type === 'Component' ||
|
|
1832
1837
|
this.#path.at(-1)?.type === 'Element')
|
|
1833
1838
|
) {
|
|
1839
|
+
this.#resetTokenStartToCurrentPosition();
|
|
1834
1840
|
return original.readToken.call(this, ch);
|
|
1835
1841
|
}
|
|
1836
1842
|
this.raise(
|
|
@@ -1855,6 +1861,7 @@ export function TSRXPlugin(config) {
|
|
|
1855
1861
|
} else if (ch === 32 || ch === 9) {
|
|
1856
1862
|
++this.pos;
|
|
1857
1863
|
} else {
|
|
1864
|
+
this.#resetTokenStartToCurrentPosition();
|
|
1858
1865
|
this.context.push(b_stat);
|
|
1859
1866
|
this.exprAllowed = true;
|
|
1860
1867
|
return original.readToken.call(this, ch);
|
|
@@ -1986,6 +1993,14 @@ export function TSRXPlugin(config) {
|
|
|
1986
1993
|
if (attr.value !== null) {
|
|
1987
1994
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
1988
1995
|
const expression = attr.value.expression;
|
|
1996
|
+
if (attr.value.style) {
|
|
1997
|
+
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).type = 'Style';
|
|
1998
|
+
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).value =
|
|
1999
|
+
/** @type {AST.Literal} */ (expression);
|
|
2000
|
+
delete (/** @type {any} */ (attr.value).expression);
|
|
2001
|
+
delete (/** @type {any} */ (attr.value).style);
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
1989
2004
|
if (expression.type === 'Literal') {
|
|
1990
2005
|
expression.was_expression = true;
|
|
1991
2006
|
}
|
|
@@ -2038,6 +2053,7 @@ export function TSRXPlugin(config) {
|
|
|
2038
2053
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2039
2054
|
raise_error();
|
|
2040
2055
|
}
|
|
2056
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2041
2057
|
this.next();
|
|
2042
2058
|
}
|
|
2043
2059
|
}
|
|
@@ -2223,6 +2239,7 @@ export function TSRXPlugin(config) {
|
|
|
2223
2239
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2224
2240
|
raise_error();
|
|
2225
2241
|
}
|
|
2242
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2226
2243
|
this.next();
|
|
2227
2244
|
}
|
|
2228
2245
|
} else if (element.type === 'TsxCompat') {
|
|
@@ -2254,6 +2271,7 @@ export function TSRXPlugin(config) {
|
|
|
2254
2271
|
if (this.type !== tstt.jsxTagEnd) {
|
|
2255
2272
|
raise_error();
|
|
2256
2273
|
}
|
|
2274
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2257
2275
|
this.next();
|
|
2258
2276
|
}
|
|
2259
2277
|
} else if (this.#path[this.#path.length - 1] === element) {
|
|
@@ -2455,11 +2473,23 @@ export function TSRXPlugin(config) {
|
|
|
2455
2473
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
2456
2474
|
// but convert other expressions to Html/TSRXExpression/Text nodes
|
|
2457
2475
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
2458
|
-
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode} */ (
|
|
2476
|
+
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style} */ (
|
|
2459
2477
|
/** @type {unknown} */ (node)
|
|
2460
|
-
).type = node.html
|
|
2478
|
+
).type = node.html
|
|
2479
|
+
? 'Html'
|
|
2480
|
+
: node.text
|
|
2481
|
+
? 'Text'
|
|
2482
|
+
: node.style
|
|
2483
|
+
? 'Style'
|
|
2484
|
+
: 'TSRXExpression';
|
|
2485
|
+
if (node.style) {
|
|
2486
|
+
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
2487
|
+
/** @type {AST.Literal} */ (node.expression);
|
|
2488
|
+
delete (/** @type {any} */ (node).expression);
|
|
2489
|
+
}
|
|
2461
2490
|
delete node.html;
|
|
2462
2491
|
delete node.text;
|
|
2492
|
+
delete node.style;
|
|
2463
2493
|
}
|
|
2464
2494
|
body.push(node);
|
|
2465
2495
|
} else if (this.type === tt.string && this.input.charCodeAt(this.start) === 34) {
|
|
@@ -2601,6 +2631,66 @@ export function TSRXPlugin(config) {
|
|
|
2601
2631
|
this.parseTemplateBody(body);
|
|
2602
2632
|
}
|
|
2603
2633
|
|
|
2634
|
+
/**
|
|
2635
|
+
* Parse proposal-style imports from an inline module declaration:
|
|
2636
|
+
* `import { foo } from server;`
|
|
2637
|
+
*
|
|
2638
|
+
* Acorn's import parser currently requires a string literal source. TSRX
|
|
2639
|
+
* extends only the source position; all specifier parsing stays delegated
|
|
2640
|
+
* to Acorn/@sveltejs/acorn-typescript.
|
|
2641
|
+
* @type {Parse.Parser['parseImport']}
|
|
2642
|
+
*/
|
|
2643
|
+
parseImport(node) {
|
|
2644
|
+
const tokenIsIdentifier = /** @type {any} */ (Parser.acornTypeScript).tokenIsIdentifier;
|
|
2645
|
+
const parser = /** @type {any} */ (this);
|
|
2646
|
+
const import_node = /** @type {any} */ (node);
|
|
2647
|
+
let enterHead = parser.lookahead();
|
|
2648
|
+
import_node.importKind = 'value';
|
|
2649
|
+
parser.importOrExportOuterKind = 'value';
|
|
2650
|
+
if (tokenIsIdentifier(enterHead.type) || this.match(tt.star) || this.match(tt.braceL)) {
|
|
2651
|
+
let ahead = parser.lookahead(2);
|
|
2652
|
+
if (
|
|
2653
|
+
ahead.type !== tt.comma &&
|
|
2654
|
+
!parser.isContextualWithState('from', ahead) &&
|
|
2655
|
+
ahead.type !== tt.eq &&
|
|
2656
|
+
parser.ts_eatContextualWithState('type', 1, enterHead)
|
|
2657
|
+
) {
|
|
2658
|
+
parser.importOrExportOuterKind = 'type';
|
|
2659
|
+
import_node.importKind = 'type';
|
|
2660
|
+
enterHead = parser.lookahead();
|
|
2661
|
+
ahead = parser.lookahead(2);
|
|
2662
|
+
}
|
|
2663
|
+
if (tokenIsIdentifier(enterHead.type) && ahead.type === tt.eq) {
|
|
2664
|
+
this.next();
|
|
2665
|
+
const importNode = parser.tsParseImportEqualsDeclaration(node);
|
|
2666
|
+
parser.importOrExportOuterKind = 'value';
|
|
2667
|
+
return importNode;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
this.next();
|
|
2671
|
+
if (this.type === tt.string) {
|
|
2672
|
+
import_node.specifiers = [];
|
|
2673
|
+
import_node.source = this.parseExprAtom();
|
|
2674
|
+
} else {
|
|
2675
|
+
import_node.specifiers = this.parseImportSpecifiers();
|
|
2676
|
+
this.expectContextual('from');
|
|
2677
|
+
if (this.type === tt.string) {
|
|
2678
|
+
import_node.source = this.parseExprAtom();
|
|
2679
|
+
} else if (tokenIsIdentifier(this.type)) {
|
|
2680
|
+
const source = this.parseIdent(false);
|
|
2681
|
+
source.metadata ??= { path: [] };
|
|
2682
|
+
import_node.source = source;
|
|
2683
|
+
} else {
|
|
2684
|
+
this.unexpected();
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
parser.parseMaybeImportAttributes(node);
|
|
2688
|
+
this.semicolon();
|
|
2689
|
+
this.finishNode(node, 'ImportDeclaration');
|
|
2690
|
+
parser.importOrExportOuterKind = 'value';
|
|
2691
|
+
return import_node;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2604
2694
|
/**
|
|
2605
2695
|
* @type {Parse.Parser['parseStatement']}
|
|
2606
2696
|
*/
|
|
@@ -2616,11 +2706,23 @@ export function TSRXPlugin(config) {
|
|
|
2616
2706
|
const node = this.jsx_parseExpressionContainer();
|
|
2617
2707
|
// Keep JSXEmptyExpression as-is (don't convert to TSRXExpression/Text/Html)
|
|
2618
2708
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
2619
|
-
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode} */ (
|
|
2709
|
+
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style} */ (
|
|
2620
2710
|
/** @type {unknown} */ (node)
|
|
2621
|
-
).type = node.html
|
|
2711
|
+
).type = node.html
|
|
2712
|
+
? 'Html'
|
|
2713
|
+
: node.text
|
|
2714
|
+
? 'Text'
|
|
2715
|
+
: node.style
|
|
2716
|
+
? 'Style'
|
|
2717
|
+
: 'TSRXExpression';
|
|
2718
|
+
if (node.style) {
|
|
2719
|
+
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
2720
|
+
/** @type {AST.Literal} */ (node.expression);
|
|
2721
|
+
delete (/** @type {any} */ (node).expression);
|
|
2722
|
+
}
|
|
2622
2723
|
delete node.html;
|
|
2623
2724
|
delete node.text;
|
|
2725
|
+
delete node.style;
|
|
2624
2726
|
}
|
|
2625
2727
|
|
|
2626
2728
|
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.Html | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
@@ -2628,18 +2730,6 @@ export function TSRXPlugin(config) {
|
|
|
2628
2730
|
);
|
|
2629
2731
|
}
|
|
2630
2732
|
|
|
2631
|
-
if (this.value === '#server') {
|
|
2632
|
-
// Peek ahead to see if this is a server block (#server { ... }) vs
|
|
2633
|
-
// a server identifier expression (#server.fn(), #server.fn().then())
|
|
2634
|
-
let peek_pos = this.end;
|
|
2635
|
-
while (peek_pos < this.input.length && /\s/.test(this.input[peek_pos])) peek_pos++;
|
|
2636
|
-
if (peek_pos < this.input.length && this.input.charCodeAt(peek_pos) === 123) {
|
|
2637
|
-
// Next non-whitespace character is '{' — parse as server block
|
|
2638
|
-
return this.parseServerBlock();
|
|
2639
|
-
}
|
|
2640
|
-
// Otherwise fall through to parse as expression statement (e.g., #server.fn().then(...))
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
2733
|
if (this.value === 'component') {
|
|
2644
2734
|
this.awaitPos = 0;
|
|
2645
2735
|
return this.parseComponent({ requireName: true, declareName: true });
|
package/src/scope.js
CHANGED
|
@@ -127,10 +127,18 @@ export function create_scopes(ast, root, parent, error_options) {
|
|
|
127
127
|
next({ scope });
|
|
128
128
|
},
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
TSModuleDeclaration(node, { state, next }) {
|
|
131
|
+
const is_submodule = node.metadata?.module_keyword === 'module';
|
|
132
|
+
if (is_submodule && node.id?.type === 'Identifier') {
|
|
133
|
+
state.scope.declare(node.id, 'normal', 'module', node);
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
const scope = state.scope.child();
|
|
132
|
-
scope.server_block =
|
|
137
|
+
scope.server_block = is_submodule;
|
|
133
138
|
scopes.set(node, scope);
|
|
139
|
+
if (node.body) {
|
|
140
|
+
scopes.set(node.body, scope);
|
|
141
|
+
}
|
|
134
142
|
|
|
135
143
|
next({ scope });
|
|
136
144
|
},
|
|
@@ -296,7 +304,7 @@ export class Scope {
|
|
|
296
304
|
tracing = null;
|
|
297
305
|
|
|
298
306
|
/**
|
|
299
|
-
* Is this scope a
|
|
307
|
+
* Is this scope a submodule scope
|
|
300
308
|
* @type {ScopeInterface['server_block']}
|
|
301
309
|
*/
|
|
302
310
|
server_block = false;
|
|
@@ -325,7 +333,7 @@ export class Scope {
|
|
|
325
333
|
return this.parent.declare(node, kind, declaration_kind);
|
|
326
334
|
}
|
|
327
335
|
|
|
328
|
-
if (declaration_kind === 'import' && !this.parent.server_block) {
|
|
336
|
+
if (declaration_kind === 'import' && !this.server_block && !this.parent.server_block) {
|
|
329
337
|
return this.parent.declare(node, kind, declaration_kind, initial);
|
|
330
338
|
}
|
|
331
339
|
}
|
|
@@ -204,6 +204,12 @@ export function tsx_with_ts_locations() {
|
|
|
204
204
|
}
|
|
205
205
|
context.write('>');
|
|
206
206
|
},
|
|
207
|
+
TSModuleDeclaration: (node, context) => {
|
|
208
|
+
context.write(node.metadata?.module_keyword ?? 'module');
|
|
209
|
+
context.write(' ');
|
|
210
|
+
context.visit(node.id);
|
|
211
|
+
context.visit(node.body);
|
|
212
|
+
},
|
|
207
213
|
};
|
|
208
214
|
|
|
209
215
|
// Be careful when duplicating visitors that are already defined
|