@tsrx/core 0.1.60 → 0.1.62
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 +4 -3
- package/package.json +3 -3
- package/src/constants.js +1 -1
- package/src/errors.js +1 -6
- package/src/parse/index.js +5 -9
- package/src/plugin.js +4 -4
- package/src/transform/jsx/ast-builders.js +6 -3
- package/src/transform/jsx/helpers.js +1 -1
- package/src/transform/jsx/index.js +2 -2
- package/src/transform/segments.js +9 -4
- package/types/index.d.ts +5 -5
- package/types/jsx-platform.d.ts +5 -5
- package/types/parse.d.ts +1 -7
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@ infrastructure that powers TypeScript UI frameworks.
|
|
|
5
5
|
|
|
6
6
|
`@tsrx/core` is framework-agnostic. It provides the parser, AST definitions, scope
|
|
7
7
|
analysis, and code-generation utilities needed to target _any_ framework runtime
|
|
8
|
-
using
|
|
9
|
-
[`@tsrx/
|
|
10
|
-
|
|
8
|
+
using TSRX syntax. Framework-specific packages—such as
|
|
9
|
+
[`@tsrx/react`](../tsrx-react), [`@tsrx/solid`](../tsrx-solid), and the external
|
|
10
|
+
[`@tsrx/ripple`](https://github.com/Ripple-TS/ripple)—build on `@tsrx/core` to
|
|
11
|
+
produce target-runtime output.
|
|
11
12
|
|
|
12
13
|
## What is TSRX?
|
|
13
14
|
|
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.62",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
10
|
+
"url": "git+https://github.com/tsrx-org/tsrx.git",
|
|
11
11
|
"directory": "packages/tsrx"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"is-reference": "^3.0.3",
|
|
80
80
|
"magic-string": "^0.30.18",
|
|
81
81
|
"zimmerframe": "^1.1.2",
|
|
82
|
-
"@tsrx/runtime": "0.1.
|
|
82
|
+
"@tsrx/runtime": "0.1.2"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/node": "^24.3.0",
|
package/src/constants.js
CHANGED
|
@@ -4,7 +4,7 @@ export const IS_CONTROLLED = 1 << 2;
|
|
|
4
4
|
export const IS_INDEXED = 1 << 3;
|
|
5
5
|
// A control-flow block that is its component's sole root: renders before the
|
|
6
6
|
// parent `__anchor` (sibling semantics, no `<!>` wrapper). Must match the value
|
|
7
|
-
// in
|
|
7
|
+
// in a target runtime's constants.
|
|
8
8
|
export const ROOT_CONTROLLED = 1 << 4;
|
|
9
9
|
export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
|
|
10
10
|
export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
|
package/src/errors.js
CHANGED
|
@@ -57,12 +57,7 @@ export function error(message, filename, node, errors, comments, code) {
|
|
|
57
57
|
*/
|
|
58
58
|
function is_error_suppress_comment(comment) {
|
|
59
59
|
const text = comment.value.trim();
|
|
60
|
-
return (
|
|
61
|
-
text.startsWith('@tsrx-ignore') ||
|
|
62
|
-
text.startsWith('@tsrx-expect-error') ||
|
|
63
|
-
text.startsWith('@ripple-ignore') ||
|
|
64
|
-
text.startsWith('@ripple-expect-error')
|
|
65
|
-
);
|
|
60
|
+
return text.startsWith('@tsrx-ignore') || text.startsWith('@tsrx-expect-error');
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
/**
|
package/src/parse/index.js
CHANGED
|
@@ -809,6 +809,10 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
809
809
|
const hasBlankLine = /\n\s*\n/.test(slice);
|
|
810
810
|
const nodeEndLine = node.loc?.end?.line ?? null;
|
|
811
811
|
const commentStartLine = comments[0].loc?.start?.line ?? null;
|
|
812
|
+
const commentOnSameLine =
|
|
813
|
+
nodeEndLine !== null &&
|
|
814
|
+
commentStartLine !== null &&
|
|
815
|
+
nodeEndLine === commentStartLine;
|
|
812
816
|
const isImmediateNextLine =
|
|
813
817
|
nodeEndLine !== null &&
|
|
814
818
|
commentStartLine !== null &&
|
|
@@ -853,10 +857,7 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
853
857
|
// For function parameters, only attach as trailing comment if it's on the same line
|
|
854
858
|
// Comments on next line after comma should be leading comments of next parameter
|
|
855
859
|
if (isParam) {
|
|
856
|
-
|
|
857
|
-
const nodeEndLine = source.slice(0, node.end).split('\n').length;
|
|
858
|
-
const commentStartLine = source.slice(0, comments[0].start).split('\n').length;
|
|
859
|
-
if (nodeEndLine === commentStartLine) {
|
|
860
|
+
if (commentOnSameLine) {
|
|
860
861
|
node.trailingComments = [
|
|
861
862
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
|
862
863
|
];
|
|
@@ -868,11 +869,6 @@ export function get_comment_handlers(source, comments, index = 0) {
|
|
|
868
869
|
// Only attach as trailing if:
|
|
869
870
|
// 1. It's on the same line as this node, OR
|
|
870
871
|
// 2. This is the last item in the array (no next sibling to attach to)
|
|
871
|
-
const commentOnSameLine =
|
|
872
|
-
nodeEndLine !== null &&
|
|
873
|
-
commentStartLine !== null &&
|
|
874
|
-
nodeEndLine === commentStartLine;
|
|
875
|
-
|
|
876
872
|
if (commentOnSameLine || is_last_in_array) {
|
|
877
873
|
node.trailingComments = [
|
|
878
874
|
/** @type {AST.CommentWithLocation} */ (comments.shift()),
|
package/src/plugin.js
CHANGED
|
@@ -246,7 +246,7 @@ function looks_like_generic_arrow(input, pos) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/**
|
|
249
|
-
* Acorn parser plugin for
|
|
249
|
+
* Acorn parser plugin for TSRX syntax extensions.
|
|
250
250
|
* Adds support for: native TSRX templates, &[]/&{} lazy destructuring,
|
|
251
251
|
* submodule imports, TSRX directives, and enhanced JSX handling.
|
|
252
252
|
*
|
|
@@ -358,7 +358,7 @@ export function TSRXPlugin(config) {
|
|
|
358
358
|
constructor(options, input) {
|
|
359
359
|
super(options, input);
|
|
360
360
|
this.context ??= [b_stat];
|
|
361
|
-
const tsrx_options = options?.tsrxOptions
|
|
361
|
+
const tsrx_options = options?.tsrxOptions;
|
|
362
362
|
this.#collect = tsrx_options?.collect === true || tsrx_options?.loose === true;
|
|
363
363
|
this.#loose = tsrx_options?.loose === true;
|
|
364
364
|
this.#errors = tsrx_options?.errors;
|
|
@@ -2222,7 +2222,7 @@ export function TSRXPlugin(config) {
|
|
|
2222
2222
|
* is exposed twice: verbatim on `content`, and as a single `JSXText` child so
|
|
2223
2223
|
* generic element paths (factory targets, static hoisting, printers) emit the
|
|
2224
2224
|
* body without knowing about raw-text elements. Consumers that handle
|
|
2225
|
-
* `content` directly (
|
|
2225
|
+
* `content` directly (target transforms, the Prettier plugin) must skip
|
|
2226
2226
|
* the children instead of emitting both.
|
|
2227
2227
|
*
|
|
2228
2228
|
* @param {ESTreeJSX.TSRXJSXOpeningElement & AST.NodeWithLocation} open
|
|
@@ -2956,7 +2956,7 @@ export function TSRXPlugin(config) {
|
|
|
2956
2956
|
}
|
|
2957
2957
|
|
|
2958
2958
|
/**
|
|
2959
|
-
* Get token from character code - handles
|
|
2959
|
+
* Get token from character code - handles TSRX-specific tokens
|
|
2960
2960
|
* @type {Parse.Parser['getTokenFromCode']}
|
|
2961
2961
|
*/
|
|
2962
2962
|
getTokenFromCode(code) {
|
|
@@ -391,14 +391,17 @@ export function flatten_switch_consequent(consequent) {
|
|
|
391
391
|
export function clone_ast_node(node, with_locations = true) {
|
|
392
392
|
if (!node || typeof node !== 'object') return node;
|
|
393
393
|
if (Array.isArray(node)) {
|
|
394
|
-
|
|
394
|
+
const clone = new Array(node.length);
|
|
395
|
+
for (let i = 0; i < node.length; i++) {
|
|
396
|
+
clone[i] = clone_ast_node(node[i], with_locations);
|
|
397
|
+
}
|
|
398
|
+
return /** @type {T} */ (clone);
|
|
395
399
|
}
|
|
396
|
-
const clone = { ...node };
|
|
400
|
+
const clone = /** @type {T} */ (with_locations ? { ...node } : {});
|
|
397
401
|
const clone_record = /** @type {Record<string, unknown>} */ (clone);
|
|
398
402
|
|
|
399
403
|
for (const key of Object.keys(node)) {
|
|
400
404
|
if (!with_locations && (key === 'loc' || key === 'start' || key === 'end')) {
|
|
401
|
-
delete clone_record[key];
|
|
402
405
|
continue;
|
|
403
406
|
}
|
|
404
407
|
if (key === 'metadata') {
|
|
@@ -41,7 +41,7 @@ export function is_empty_jsx_fragment(node) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Maintain TSRX transform path metadata: every node seen by the walker
|
|
45
45
|
* carries its current ancestor path for downstream CSS pruning and mapping
|
|
46
46
|
* helpers.
|
|
47
47
|
*
|
|
@@ -268,7 +268,7 @@ function wrap_in_native_tsrx_fragment(node) {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
/**
|
|
271
|
-
* An AUTHORED `<> … </>` fragment (not a compiler-generated wrapper, nor a
|
|
271
|
+
* An AUTHORED `<> … </>` fragment (not a compiler-generated wrapper, nor a TSRX
|
|
272
272
|
* code-block-chain wrapper). These are kept verbatim in the output instead of
|
|
273
273
|
* being unwrapped to their single child.
|
|
274
274
|
* @param {AST.Node | null | undefined} node
|
|
@@ -2362,7 +2362,7 @@ function prepare_tsrx_fragment_styles(node, transform_context) {
|
|
|
2362
2362
|
const region_hash = sheet.hash;
|
|
2363
2363
|
sheet.hash = css.hash;
|
|
2364
2364
|
// `prune_css` inside marks the matching selectors as used/scoped; selectors
|
|
2365
|
-
// that match no element render commented out,
|
|
2365
|
+
// that match no element render commented out, matching target behavior.
|
|
2366
2366
|
apply_css_definition_metadata(
|
|
2367
2367
|
node,
|
|
2368
2368
|
sheet,
|
|
@@ -517,6 +517,11 @@ export function convert_source_map_to_mappings(
|
|
|
517
517
|
function set_bracket_computed_mapping(node, mappings) {
|
|
518
518
|
if (has_location(node.key)) {
|
|
519
519
|
const key = node.key;
|
|
520
|
+
const start_key = `${key.loc.start.line}:${key.loc.start.column - 1}`;
|
|
521
|
+
const end_key = `${key.loc.end.line}:${key.loc.end.column + 1}`;
|
|
522
|
+
if (!src_to_gen_map.get(start_key)?.length || !src_to_gen_map.get(end_key)?.length) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
520
525
|
mappings.push(
|
|
521
526
|
get_mapping_from_node(
|
|
522
527
|
/** @type {AST.NodeWithLocation} */ ({
|
|
@@ -625,7 +630,7 @@ export function convert_source_map_to_mappings(
|
|
|
625
630
|
if (node.name && node.loc) {
|
|
626
631
|
/** @type {MappingToken} */
|
|
627
632
|
let token;
|
|
628
|
-
// Check if this identifier was changed in metadata (
|
|
633
|
+
// Check if this identifier was changed in metadata (for example, #Map -> ReactiveMap)
|
|
629
634
|
// Or if it was capitalized during transformation
|
|
630
635
|
if (node.metadata?.source_name) {
|
|
631
636
|
token = {
|
|
@@ -933,7 +938,7 @@ export function convert_source_map_to_mappings(
|
|
|
933
938
|
// text itself — and get_mapping_from_node just takes the first, so its generated length
|
|
934
939
|
// spans the wrong region and the editor can't map a completion's edit back to source
|
|
935
940
|
// (it then drops the item). The token resolves to the position whose generated text
|
|
936
|
-
// matches the node's value, giving a well-formed same-length mapping.
|
|
941
|
+
// matches the node's value, giving a well-formed same-length mapping. TSRX keeps text
|
|
937
942
|
// verbatim in to_ts, so `source` and `generated` are identical. Other text stays unmapped.
|
|
938
943
|
if (node.loc && typeof node.value === 'string' && node.value.trimStart().startsWith('@')) {
|
|
939
944
|
tokens.push({
|
|
@@ -1299,14 +1304,14 @@ export function convert_source_map_to_mappings(
|
|
|
1299
1304
|
);
|
|
1300
1305
|
return;
|
|
1301
1306
|
} else if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
|
|
1302
|
-
// Visit in source order: left, right, index
|
|
1307
|
+
// Visit in source order: left, right, TSRX index extension, body
|
|
1303
1308
|
if (node.left) {
|
|
1304
1309
|
visit(node.left);
|
|
1305
1310
|
}
|
|
1306
1311
|
if (node.right) {
|
|
1307
1312
|
visit(node.right);
|
|
1308
1313
|
}
|
|
1309
|
-
//
|
|
1314
|
+
// TSRX index extension: index variable
|
|
1310
1315
|
if (/** @type {AST.ForOfStatement} */ (node).index) {
|
|
1311
1316
|
visit(/** @type {AST.Node} */ (/** @type {AST.ForOfStatement} */ (node).index));
|
|
1312
1317
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -352,7 +352,7 @@ declare module 'estree' {
|
|
|
352
352
|
lazy?: boolean;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
//
|
|
355
|
+
// Target analysis may mark a whole member expression as tracked metadata.
|
|
356
356
|
interface MemberExpression {
|
|
357
357
|
tracked?: boolean;
|
|
358
358
|
}
|
|
@@ -425,7 +425,7 @@ declare module 'estree' {
|
|
|
425
425
|
* `#parseScriptElement` (analogous to {@link JSXStyleElement.css}). Present only
|
|
426
426
|
* on `<script>` elements that have a body. The parser also mirrors the body as
|
|
427
427
|
* a single `JSXText` child so generic element consumers emit it; consumers that
|
|
428
|
-
* handle `content` directly (
|
|
428
|
+
* handle `content` directly (target transforms, the Prettier plugin, the
|
|
429
429
|
* type-only editor output) skip the children instead of emitting both.
|
|
430
430
|
*/
|
|
431
431
|
content?: string;
|
|
@@ -1630,7 +1630,7 @@ export interface Binding {
|
|
|
1630
1630
|
/** Additional metadata for this binding */
|
|
1631
1631
|
metadata: {
|
|
1632
1632
|
pattern?: AST.Identifier;
|
|
1633
|
-
|
|
1633
|
+
is_tsrx_object?: boolean;
|
|
1634
1634
|
is_template_value?: boolean;
|
|
1635
1635
|
lazy_array_source?: string;
|
|
1636
1636
|
lazy_array_index?: number;
|
|
@@ -2232,7 +2232,7 @@ export type RuntimeImportMode = 'compiler' | 'direct';
|
|
|
2232
2232
|
|
|
2233
2233
|
/**
|
|
2234
2234
|
* Common base options accepted by every TSRX target's `compile` entry point.
|
|
2235
|
-
* Targets that need extra knobs (
|
|
2235
|
+
* Targets that need extra knobs (for example Ripple's `mode`/`dev`/`hmr`, Preact's
|
|
2236
2236
|
* `suspenseSource`) intersect their own option type with this base when
|
|
2237
2237
|
* declaring their `compile` export.
|
|
2238
2238
|
*/
|
|
@@ -2256,7 +2256,7 @@ export interface BaseCompileOptions {
|
|
|
2256
2256
|
* @template TOptions Per-target options accepted as the third argument.
|
|
2257
2257
|
* Defaults to {@link BaseCompileOptions}.
|
|
2258
2258
|
* @template TResult Per-target result type. Must extend {@link CompileResult};
|
|
2259
|
-
* targets may add fields (
|
|
2259
|
+
* targets may add fields (for example Ripple's deprecated `js` compatibility field)
|
|
2260
2260
|
* via intersection.
|
|
2261
2261
|
*/
|
|
2262
2262
|
export type CompileFn<
|
package/types/jsx-platform.d.ts
CHANGED
|
@@ -158,7 +158,7 @@ export interface JsxTransformOptions {
|
|
|
158
158
|
export interface JsxPlatformHooks {
|
|
159
159
|
/**
|
|
160
160
|
* Per-statement control-flow rewrites. Each hook receives the original
|
|
161
|
-
*
|
|
161
|
+
* TSRX statement (with children already walked) and returns a JSX
|
|
162
162
|
* child (or an expression container wrapping one).
|
|
163
163
|
*/
|
|
164
164
|
controlFlow?: {
|
|
@@ -205,7 +205,7 @@ export interface JsxPlatformHooks {
|
|
|
205
205
|
*/
|
|
206
206
|
injectImports?: (program: AST.Program, ctx: JsxTransformContext, suspenseSource: string) => void;
|
|
207
207
|
/**
|
|
208
|
-
* Transform a
|
|
208
|
+
* Transform a TSRX element's parser-native JSX attributes. The result
|
|
209
209
|
* is passed through the shared multi-`ref` merge. Platforms that own a
|
|
210
210
|
* `transformElement` hook (e.g. Solid) run their own attribute pass inside
|
|
211
211
|
* that hook.
|
|
@@ -343,7 +343,7 @@ export interface JsxPlatformHooks {
|
|
|
343
343
|
|
|
344
344
|
/**
|
|
345
345
|
* A JSX platform descriptor is the parameter to `createJsxTransform`. It
|
|
346
|
-
* declares how to render a
|
|
346
|
+
* declares how to render a TSRX AST as valid TSX for the target platform
|
|
347
347
|
* (React, Preact, Solid). The shared transformer in `@tsrx/core` reads this
|
|
348
348
|
* descriptor at each platform-specific decision point instead of branching
|
|
349
349
|
* on the platform name.
|
|
@@ -432,7 +432,7 @@ export interface JsxPlatform {
|
|
|
432
432
|
|
|
433
433
|
jsx: {
|
|
434
434
|
/**
|
|
435
|
-
* Rewrite
|
|
435
|
+
* Rewrite TSRX's `class` attribute to `className` for targets
|
|
436
436
|
* that require it. First-party targets keep authored `class`.
|
|
437
437
|
*/
|
|
438
438
|
rewriteClassAttr: boolean;
|
|
@@ -527,7 +527,7 @@ export interface JsxPlatform {
|
|
|
527
527
|
|
|
528
528
|
/**
|
|
529
529
|
* Build a `transform()` function for a specific JSX platform. The returned
|
|
530
|
-
* function takes a parsed
|
|
530
|
+
* function takes a parsed TSRX AST and produces a TSX module plus source
|
|
531
531
|
* map and optional CSS.
|
|
532
532
|
*/
|
|
533
533
|
export function createJsxTransform(
|
package/types/parse.d.ts
CHANGED
|
@@ -193,12 +193,6 @@ export namespace Parse {
|
|
|
193
193
|
errors: CoreCompiler.CompileError[] | undefined;
|
|
194
194
|
filename: string | undefined;
|
|
195
195
|
};
|
|
196
|
-
rippleOptions?: {
|
|
197
|
-
collect: boolean;
|
|
198
|
-
loose: boolean;
|
|
199
|
-
errors: CoreCompiler.CompileError[] | undefined;
|
|
200
|
-
filename: string | undefined;
|
|
201
|
-
};
|
|
202
196
|
// The type has "latest" but it's converted to 1e8 at runtime
|
|
203
197
|
// and if (ecmaVersion >= 2015) { ecmaVersion -= 2009 }
|
|
204
198
|
// so we're making it always a number to reflect the runtime values
|
|
@@ -1666,7 +1660,7 @@ export namespace Parse {
|
|
|
1666
1660
|
/**
|
|
1667
1661
|
* Check if a local export refers to a defined variable.
|
|
1668
1662
|
* Acorn's default implementation only checks the top-level module scope,
|
|
1669
|
-
* but
|
|
1663
|
+
* but TSRX overrides this to check all scopes (for submodules).
|
|
1670
1664
|
* @param id The identifier being exported
|
|
1671
1665
|
*/
|
|
1672
1666
|
checkLocalExport(id: AST.Identifier): void;
|