@tsrx/core 0.0.19 → 0.0.21
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 +10 -185
- package/package.json +1 -1
- package/src/diagnostics.js +7 -0
- package/src/errors.js +3 -1
- package/src/index.js +1 -0
- package/src/parse/index.js +4 -2
- package/src/plugin.js +157 -112
- package/src/scope.js +2 -2
- package/src/transform/jsx/ast-builders.js +29 -0
- package/src/transform/jsx/index.js +819 -201
- package/src/utils/builders.js +68 -0
- package/types/index.d.ts +10 -3
- package/types/jsx-platform.d.ts +7 -3
- package/types/parse.d.ts +4 -2
package/README.md
CHANGED
|
@@ -35,194 +35,19 @@ const ast = parseModule(source, 'App.tsrx');
|
|
|
35
35
|
The parser produces an ESTree-compatible AST, augmented with the TSRX node types
|
|
36
36
|
listed below. Framework compilers walk this AST to emit their own output.
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Language docs
|
|
39
39
|
|
|
40
|
-
TSRX is
|
|
41
|
-
the following productions.
|
|
40
|
+
The TSRX website is the canonical source for language documentation:
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
- [Getting Started](https://tsrx.dev/getting-started) — install TSRX for React,
|
|
43
|
+
Preact, Solid, Vue, or Ripple and configure editor/AI tooling.
|
|
44
|
+
- [Features](https://tsrx.dev/features) — examples of components, statement
|
|
45
|
+
templates, control flow, scoped styles, server blocks, and lazy destructuring.
|
|
46
|
+
- [Specification](https://tsrx.dev/specification) — the current grammar and
|
|
47
|
+
parser-level semantics.
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
that framework compilers can treat it specially.
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
component Button(props: Props) {
|
|
51
|
-
<button>{props.label}</button>
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
- `component` may be used wherever `function` may be used (declaration,
|
|
56
|
-
expression, default export).
|
|
57
|
-
- The body of a `component` may contain JSX-like elements as statements — see §3.
|
|
58
|
-
- `component` is a contextual keyword. Use as an identifier is preserved in
|
|
59
|
-
non-declaration positions.
|
|
60
|
-
|
|
61
|
-
### 2. JSX-as-statements
|
|
62
|
-
|
|
63
|
-
Inside a `component` body, JSX elements are valid _statement_ forms. They describe
|
|
64
|
-
rendered output and are not expressions — they have no value. Static text may be
|
|
65
|
-
written as a direct double-quoted child; dynamic values and other JavaScript
|
|
66
|
-
expressions stay inside `{}`.
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
component Greeting() {
|
|
70
|
-
<h1>"Hello"</h1>
|
|
71
|
-
<p>"Welcome"</p>
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Only double quotes have direct-child text meaning. Single-quoted strings and
|
|
76
|
-
template literals remain JavaScript expressions and must be written inside `{}`.
|
|
77
|
-
|
|
78
|
-
Elsewhere (outside a `component` body), JSX remains an expression, as in standard
|
|
79
|
-
JSX.
|
|
80
|
-
|
|
81
|
-
### 4. Control-flow statements in `component` bodies
|
|
82
|
-
|
|
83
|
-
Inside a `component` body, the standard JavaScript control-flow keywords `if`,
|
|
84
|
-
`else`, `for`, `switch`, and `try` gain an additional role: their branches may
|
|
85
|
-
contain JSX-as-statements (§2) describing conditionally- or repeatedly-rendered
|
|
86
|
-
output. The keywords retain their usual JavaScript syntax — no new grammar is
|
|
87
|
-
introduced — but framework compilers treat them as _reactive_ boundaries.
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
component List(props: { items: Item[]; showHeader: boolean }) {
|
|
91
|
-
if (props.showHeader) {
|
|
92
|
-
<h1>"Items"</h1>
|
|
93
|
-
} else {
|
|
94
|
-
<h2>"(no header)"</h2>
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
for (const item of props.items) {
|
|
98
|
-
<li>{item.name}</li>
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
switch (props.items.length) {
|
|
102
|
-
case 0:
|
|
103
|
-
<p>"empty"</p>
|
|
104
|
-
break;
|
|
105
|
-
default:
|
|
106
|
-
<p>"has items"</p>
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
<AsyncThing />
|
|
111
|
-
} catch (e) {
|
|
112
|
-
<pre>{String(e)}</pre>
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Early returns.** A bare `return;` (or `return` at the end of a branch) is a
|
|
118
|
-
valid statement inside a `component` body and short-circuits any remaining
|
|
119
|
-
rendering in the current branch. This composes naturally with the control-flow
|
|
120
|
-
forms above:
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
component Page(props: { user: User | null }) {
|
|
124
|
-
if (props.user == null) {
|
|
125
|
-
<LoginPrompt />
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
<Dashboard user={props.user} />
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Because a `component` body does not produce a value, `return` never carries an
|
|
134
|
-
expression — it only marks a rendering short-circuit.
|
|
135
|
-
|
|
136
|
-
**Nesting inside elements.** Control-flow statements may appear directly as
|
|
137
|
-
children of a JSX element, not only at the top level of the component body. Their
|
|
138
|
-
branches contribute children to the enclosing element in source order:
|
|
139
|
-
|
|
140
|
-
```tsx
|
|
141
|
-
component Menu(props: { items: Item[]; loading: boolean }) {
|
|
142
|
-
<ul>
|
|
143
|
-
if (props.loading) {
|
|
144
|
-
<li>{'loading…'}</li>
|
|
145
|
-
} else {
|
|
146
|
-
for (const item of props.items) {
|
|
147
|
-
<li>
|
|
148
|
-
<a href={item.href}>{item.label}</a>
|
|
149
|
-
if (item.badge) {
|
|
150
|
-
<span class="badge">{item.badge}</span>
|
|
151
|
-
}
|
|
152
|
-
</li>
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
</ul>
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Any control-flow form that is legal at the component-body level is also legal as a
|
|
160
|
-
child of a JSX element, and may be nested to arbitrary depth.
|
|
161
|
-
|
|
162
|
-
TSRX only describes what is syntactically permitted. The reactive semantics
|
|
163
|
-
(dependency tracking, list reconciliation, error boundaries, suspense) are the
|
|
164
|
-
responsibility of the framework compiler.
|
|
165
|
-
|
|
166
|
-
### 5. JSX escape hatch: `<tsx>...</tsx>`
|
|
167
|
-
|
|
168
|
-
Because JSX inside a `component` body is a _statement_ (§2), the element itself
|
|
169
|
-
has no value. To embed regular _expression_-form JSX — e.g. when a third-party
|
|
170
|
-
library accepts a JSX tree as a value — wrap it in the reserved `<tsx>` element.
|
|
171
|
-
Its children are parsed as standard JSX expressions and the whole form evaluates
|
|
172
|
-
to the JSX expression value (or an array of values if there are multiple
|
|
173
|
-
children).
|
|
174
|
-
|
|
175
|
-
```tsx
|
|
176
|
-
component Page() {
|
|
177
|
-
const header = <tsx><h1>Hello</h1></tsx>;
|
|
178
|
-
renderSomewhereElse(header);
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
`<tsx>` is a reserved tag name in TSRX. It has no runtime representation of its
|
|
183
|
-
own — the framework compiler unwraps it into the underlying JSX expression.
|
|
184
|
-
|
|
185
|
-
### 6. Lazy destructuring: `&[]` and `&{}`
|
|
186
|
-
|
|
187
|
-
Two new destructuring forms prefixed with `&` bind by _reference_ rather than by
|
|
188
|
-
value. Each bound name compiles to a lazy property lookup on the source, so reads
|
|
189
|
-
and writes are deferred to the use-site.
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
let &[count] = source; // array-style lazy destructure
|
|
193
|
-
let &{ name, age } = props; // object-style lazy destructure
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Semantics are provided by the framework compiler. TSRX only defines the syntax and
|
|
197
|
-
the AST shape (`kind: 'lazy'` binding patterns).
|
|
198
|
-
|
|
199
|
-
### 7. `#server` blocks
|
|
200
|
-
|
|
201
|
-
A `#server { ... }` block marks a lexical region whose contents are intended for
|
|
202
|
-
the server compile target. TSRX parses the block and records its exports;
|
|
203
|
-
framework compilers decide how to emit or strip it per target.
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
#server {
|
|
207
|
-
export async function load() { /* ... */ }
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### 8. `#style` identifier
|
|
212
|
-
|
|
213
|
-
`#style` is a reserved identifier that refers, at compile time, to the set of
|
|
214
|
-
scoped CSS classes declared in the current module. It is legal only in positions
|
|
215
|
-
where the framework compiler expects a class-name value.
|
|
216
|
-
|
|
217
|
-
```tsx
|
|
218
|
-
<div class={#style.card} />
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### 9. Scoped CSS blocks
|
|
222
|
-
|
|
223
|
-
A `component` may contain a trailing CSS block (delimited by the framework
|
|
224
|
-
compiler's chosen grammar). The block is parsed into a `CSS.StyleSheet` AST node
|
|
225
|
-
and hashed for scoping.
|
|
49
|
+
Keeping the language reference on the website avoids duplicating the specification
|
|
50
|
+
here and keeps package docs focused on the core parser API.
|
|
226
51
|
|
|
227
52
|
## What `@tsrx/core` provides
|
|
228
53
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const DIAGNOSTIC_CODES = {
|
|
2
|
+
JSX_EXPRESSION_VALUE: 'tsrx-jsx-expression-value',
|
|
3
|
+
JSX_RETURN_IN_COMPONENT: 'tsrx-jsx-return-in-component',
|
|
4
|
+
FUNCTION_COMPONENT_SYNTAX: 'tsrx-function-component-syntax',
|
|
5
|
+
UNCLOSED_TAG: 'tsrx-unclosed-tag',
|
|
6
|
+
MISMATCHED_CLOSING_TAG: 'tsrx-mismatched-closing-tag',
|
|
7
|
+
};
|
package/src/errors.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* @param {AST.Node | AST.NodeWithLocation} node
|
|
11
11
|
* @param {CompileError[]} [errors]
|
|
12
12
|
* @param {AST.CommentWithLocation[]} [comments]
|
|
13
|
+
* @param {string} [code]
|
|
13
14
|
* @returns {void}
|
|
14
15
|
*/
|
|
15
|
-
export function error(message, filename, node, errors, comments) {
|
|
16
|
+
export function error(message, filename, node, errors, comments, code) {
|
|
16
17
|
if (errors && comments && is_error_suppressed(node, comments)) {
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
@@ -25,6 +26,7 @@ export function error(message, filename, node, errors, comments) {
|
|
|
25
26
|
|
|
26
27
|
// custom properties
|
|
27
28
|
error.fileName = filename;
|
|
29
|
+
error.code = code;
|
|
28
30
|
error.end = node.end ?? undefined;
|
|
29
31
|
error.loc = !node.loc
|
|
30
32
|
? undefined
|
package/src/index.js
CHANGED
package/src/parse/index.js
CHANGED
|
@@ -201,7 +201,8 @@ export function createParser(...plugins) {
|
|
|
201
201
|
return function parse(source, filename, options) {
|
|
202
202
|
/** @type {AST.CommentWithLocation[]} */
|
|
203
203
|
const comments = [];
|
|
204
|
-
const
|
|
204
|
+
const collect = !!(options?.collect || options?.loose);
|
|
205
|
+
const output_comments = collect ? options?.comments : undefined;
|
|
205
206
|
|
|
206
207
|
const { onComment, add_comments } = get_comment_handlers(source, comments);
|
|
207
208
|
/** @type {AST.Program} */
|
|
@@ -216,7 +217,8 @@ export function createParser(...plugins) {
|
|
|
216
217
|
onComment,
|
|
217
218
|
tsrxOptions: {
|
|
218
219
|
filename,
|
|
219
|
-
|
|
220
|
+
collect,
|
|
221
|
+
errors: collect ? (options?.errors ?? []) : undefined,
|
|
220
222
|
loose: options?.loose || false,
|
|
221
223
|
},
|
|
222
224
|
});
|