@tsrx/core 0.0.1 → 0.0.2

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 ADDED
@@ -0,0 +1,249 @@
1
+ # @tsrx/core
2
+
3
+ **TypeScript Ripple Extensions (TSRX)** — the shared parser and compiler
4
+ infrastructure that powers TypeScript UI frameworks.
5
+
6
+ `@tsrx/core` is framework-agnostic. It provides the parser, AST definitions, scope
7
+ analysis, and code-generation utilities needed to target _any_ framework runtime
8
+ using Ripple's syntax. Framework-specific packages (such as
9
+ [`@tsrx/ripple`](../tsrx-ripple)) build on top of `@tsrx/core` to produce the
10
+ runtime output for Ripple.
11
+
12
+ ## What is TSRX?
13
+
14
+ TSRX is an extension to TypeScript's syntax — in the same spirit that JSX is an
15
+ extension to JavaScript. It adds a small set of orthogonal syntactic forms that
16
+ are ergonomic for describing reactive UI, and leaves the semantics of those forms
17
+ to the consuming framework.
18
+
19
+ A `.tsrx` file is a TypeScript module with TSRX enabled.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pnpm add @tsrx/core
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```js
30
+ import { parseModule } from '@tsrx/core';
31
+
32
+ const ast = parseModule(source, 'App.tsrx');
33
+ ```
34
+
35
+ The parser produces an ESTree-compatible AST, augmented with the TSRX node types
36
+ listed below. Framework compilers walk this AST to emit their own output.
37
+
38
+ ## TSRX Specification (draft)
39
+
40
+ TSRX is a superset of TypeScript. All valid TypeScript is valid TSRX. TSRX adds
41
+ the following productions.
42
+
43
+ ### 1. `component` declarations
44
+
45
+ A `component` is a new top-level and expression-level declaration form. It has the
46
+ same shape as a function declaration, but is a distinct AST node (`Component`) so
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.
65
+
66
+ ```tsx
67
+ component Greeting() {
68
+ <h1>{'Hello'}</h1>
69
+ <p>{'Welcome'}</p>
70
+ }
71
+ ```
72
+
73
+ Elsewhere (outside a `component` body), JSX remains an expression, as in standard
74
+ JSX.
75
+
76
+ ### 4. Control-flow statements in `component` bodies
77
+
78
+ Inside a `component` body, the standard JavaScript control-flow keywords `if`,
79
+ `else`, `for`, `switch`, and `try` gain an additional role: their branches may
80
+ contain JSX-as-statements (§2) describing conditionally- or repeatedly-rendered
81
+ output. The keywords retain their usual JavaScript syntax — no new grammar is
82
+ introduced — but framework compilers treat them as _reactive_ boundaries.
83
+
84
+ ```tsx
85
+ component List(props: { items: Item[]; showHeader: boolean }) {
86
+ if (props.showHeader) {
87
+ <h1>{'Items'}</h1>
88
+ } else {
89
+ <h2>{'(no header)'}</h2>
90
+ }
91
+
92
+ for (const item of props.items) {
93
+ <li>{item.name}</li>
94
+ }
95
+
96
+ switch (props.items.length) {
97
+ case 0:
98
+ <p>{'empty'}</p>
99
+ break;
100
+ default:
101
+ <p>{'has items'}</p>
102
+ }
103
+
104
+ try {
105
+ <AsyncThing />
106
+ } catch (e) {
107
+ <pre>{String(e)}</pre>
108
+ }
109
+ }
110
+ ```
111
+
112
+ **Early returns.** A bare `return;` (or `return` at the end of a branch) is a
113
+ valid statement inside a `component` body and short-circuits any remaining
114
+ rendering in the current branch. This composes naturally with the control-flow
115
+ forms above:
116
+
117
+ ```tsx
118
+ component Page(props: { user: User | null }) {
119
+ if (props.user == null) {
120
+ <LoginPrompt />
121
+ return;
122
+ }
123
+
124
+ <Dashboard user={props.user} />
125
+ }
126
+ ```
127
+
128
+ Because a `component` body does not produce a value, `return` never carries an
129
+ expression — it only marks a rendering short-circuit.
130
+
131
+ **Nesting inside elements.** Control-flow statements may appear directly as
132
+ children of a JSX element, not only at the top level of the component body. Their
133
+ branches contribute children to the enclosing element in source order:
134
+
135
+ ```tsx
136
+ component Menu(props: { items: Item[]; loading: boolean }) {
137
+ <ul>
138
+ if (props.loading) {
139
+ <li>{'loading…'}</li>
140
+ } else {
141
+ for (const item of props.items) {
142
+ <li>
143
+ <a href={item.href}>{item.label}</a>
144
+ if (item.badge) {
145
+ <span class="badge">{item.badge}</span>
146
+ }
147
+ </li>
148
+ }
149
+ }
150
+ </ul>
151
+ }
152
+ ```
153
+
154
+ Any control-flow form that is legal at the component-body level is also legal as a
155
+ child of a JSX element, and may be nested to arbitrary depth.
156
+
157
+ TSRX only describes what is syntactically permitted. The reactive semantics
158
+ (dependency tracking, list reconciliation, error boundaries, suspense) are the
159
+ responsibility of the framework compiler.
160
+
161
+ ### 5. JSX escape hatch: `<tsx>...</tsx>`
162
+
163
+ Because JSX inside a `component` body is a _statement_ (§2), the element itself
164
+ has no value. To embed regular _expression_-form JSX — e.g. when a third-party
165
+ library accepts a JSX tree as a value — wrap it in the reserved `<tsx>` element.
166
+ Its children are parsed as standard JSX expressions and the whole form evaluates
167
+ to the JSX expression value (or an array of values if there are multiple
168
+ children).
169
+
170
+ ```tsx
171
+ component Page() {
172
+ const header = <tsx><h1>{'Hello'}</h1></tsx>;
173
+ renderSomewhereElse(header);
174
+ }
175
+ ```
176
+
177
+ `<tsx>` is a reserved tag name in TSRX. It has no runtime representation of its
178
+ own — the framework compiler unwraps it into the underlying JSX expression.
179
+
180
+ ### 6. Lazy destructuring: `&[]` and `&{}`
181
+
182
+ Two new destructuring forms prefixed with `&` bind by _reference_ rather than by
183
+ value. Each bound name compiles to a lazy property lookup on the source, so reads
184
+ and writes are deferred to the use-site.
185
+
186
+ ```tsx
187
+ let &[count] = source; // array-style lazy destructure
188
+ let &{ name, age } = props; // object-style lazy destructure
189
+ ```
190
+
191
+ Semantics are provided by the framework compiler. TSRX only defines the syntax and
192
+ the AST shape (`kind: 'lazy'` binding patterns).
193
+
194
+ ### 7. `#server` blocks
195
+
196
+ A `#server { ... }` block marks a lexical region whose contents are intended for
197
+ the server compile target. TSRX parses the block and records its exports;
198
+ framework compilers decide how to emit or strip it per target.
199
+
200
+ ```ts
201
+ #server {
202
+ export async function load() { /* ... */ }
203
+ }
204
+ ```
205
+
206
+ ### 8. `#style` identifier
207
+
208
+ `#style` is a reserved identifier that refers, at compile time, to the set of
209
+ scoped CSS classes declared in the current module. It is legal only in positions
210
+ where the framework compiler expects a class-name value.
211
+
212
+ ```tsx
213
+ <div class={#style.card} />
214
+ ```
215
+
216
+ ### 9. Scoped CSS blocks
217
+
218
+ A `component` may contain a trailing CSS block (delimited by the framework
219
+ compiler's chosen grammar). The block is parsed into a `CSS.StyleSheet` AST node
220
+ and hashed for scoping.
221
+
222
+ ## What `@tsrx/core` provides
223
+
224
+ - **`parseModule(source, filename, options?)`** — parse a TSRX module into an
225
+ ESTree AST.
226
+ - **Scope analysis** — `createScopes`, `Scope`, `ScopeRoot`, binding tracking
227
+ (`import`, `prop`, `let`, `const`, `function`, `component`, `for_pattern`, …).
228
+ - **AST utilities** — pattern walkers, identifier extraction, builders, location
229
+ helpers, obfuscation helpers.
230
+ - **CSS support** — `parseStyle`, `analyzeCss`, `renderStylesheets`.
231
+ - **HTML helpers** — `isVoidElement`, `isBooleanAttribute`, `isDomProperty`,
232
+ `validateNesting`.
233
+ - **Event helpers** — delegated-event utilities, event-name normalization.
234
+ - **Source maps** — `convertSourceMapToMappings`.
235
+
236
+ See `src/index.js` for the full exported surface.
237
+
238
+ ## Non-goals
239
+
240
+ - `@tsrx/core` does **not** emit runtime code. Code generation lives in framework
241
+ packages (e.g. `@tsrx/ripple`).
242
+ - `@tsrx/core` does **not** ship a runtime. There is no reactivity, rendering, or
243
+ DOM code here.
244
+ - `@tsrx/core` does **not** lock consumers to a specific output format. Multiple
245
+ compile targets can share the same parser and analysis.
246
+
247
+ ## License
248
+
249
+ MIT © Dominic Gannaway
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@tsrx/core",
3
- "description": "Core compiler infrastructure for tsrx-based frameworks",
3
+ "description": "Core compiler infrastructure for TSRX syntax",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.0.1",
6
+ "version": "0.0.2",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "exports": {
14
14
  ".": {
15
+ "types": "./src/index.js",
15
16
  "default": "./src/index.js"
16
17
  },
17
18
  "./types": {
package/src/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * @tsrx/core - Core compiler infrastructure for tsrx-based frameworks
3
3
  *
4
- * Re-exports key modules for convenience.
4
+ * Public API surface uses camelCase. Internal modules retain snake_case per
5
+ * the project's code conventions; the exports below alias them at the boundary.
5
6
  */
6
7
 
7
8
  // Parse
9
+ export { parse_module as parseModule } from './parse/parse-module.js';
8
10
  export {
9
- createParser,
10
- get_comment_handlers,
11
- convert_from_jsx,
11
+ get_comment_handlers as getCommentHandlers,
12
+ convert_from_jsx as convertFromJsx,
12
13
  skipWhitespace,
13
14
  isWhitespaceTextNode,
14
15
  BINDING_TYPES,
@@ -16,10 +17,10 @@ export {
16
17
  acorn,
17
18
  tsPlugin,
18
19
  } from './parse/index.js';
19
- export { parse_style } from './parse/style.js';
20
+ export { parse_style as parseStyle } from './parse/style.js';
20
21
 
21
22
  // Scope
22
- export { create_scopes, ScopeRoot, Scope } from './scope.js';
23
+ export { create_scopes as createScopes, ScopeRoot, Scope } from './scope.js';
23
24
 
24
25
  // Errors
25
26
  export { error } from './errors.js';
@@ -51,91 +52,92 @@ export {
51
52
  STYLE_IDENTIFIER,
52
53
  SERVER_IDENTIFIER,
53
54
  CSS_HASH_IDENTIFIER,
54
- obfuscate_identifier,
55
- is_identifier_obfuscated,
56
- deobfuscate_identifier,
55
+ obfuscate_identifier as obfuscateIdentifier,
56
+ is_identifier_obfuscated as isIdentifierObfuscated,
57
+ deobfuscate_identifier as deobfuscateIdentifier,
57
58
  } from './identifier-utils.js';
58
59
 
59
60
  // Comment utils
60
61
  export {
61
- is_ts_pragma,
62
- is_triple_slash_directive,
63
- is_jsdoc_ts_annotation,
64
- should_preserve_comment,
65
- format_comment,
62
+ is_ts_pragma as isTsPragma,
63
+ is_triple_slash_directive as isTripleSlashDirective,
64
+ is_jsdoc_ts_annotation as isJsdocTsAnnotation,
65
+ should_preserve_comment as shouldPreserveComment,
66
+ format_comment as formatComment,
66
67
  } from './comment-utils.js';
67
68
 
68
69
  // Generic utils
69
70
  export {
70
71
  hash,
71
- is_void_element,
72
- is_reserved,
73
- is_boolean_attribute,
74
- is_dom_property,
72
+ is_void_element as isVoidElement,
73
+ is_reserved as isReserved,
74
+ is_boolean_attribute as isBooleanAttribute,
75
+ is_dom_property as isDomProperty,
75
76
  } from './utils.js';
76
77
 
77
78
  // AST utils
78
79
  export {
79
80
  object,
80
- unwrap_pattern,
81
- extract_identifiers,
82
- extract_paths,
83
- build_fallback,
84
- build_assignment_value,
81
+ unwrap_pattern as unwrapPattern,
82
+ extract_identifiers as extractIdentifiers,
83
+ extract_paths as extractPaths,
84
+ build_fallback as buildFallback,
85
+ build_assignment_value as buildAssignmentValue,
85
86
  } from './utils/ast.js';
86
87
 
87
- // Builders (namespace re-export)
88
+ // Builders (namespace re-export — members mirror AST node kinds)
88
89
  export * as builders from './utils/builders.js';
89
90
 
90
91
  // Also export individual builder utilities used directly
91
- export { set_location } from './utils/builders.js';
92
+ export { set_location as setLocation } from './utils/builders.js';
92
93
 
93
94
  // Event utils
94
95
  export {
95
- is_non_delegated,
96
- is_event_attribute,
97
- is_capture_event,
98
- get_original_event_name,
99
- normalize_event_name,
100
- event_name_from_capture,
101
- get_attribute_event_name,
102
- is_passive_event,
96
+ is_non_delegated as isNonDelegated,
97
+ is_event_attribute as isEventAttribute,
98
+ is_capture_event as isCaptureEvent,
99
+ get_original_event_name as getOriginalEventName,
100
+ normalize_event_name as normalizeEventName,
101
+ event_name_from_capture as eventNameFromCapture,
102
+ get_attribute_event_name as getAttributeEventName,
103
+ is_passive_event as isPassiveEvent,
103
104
  } from './utils/events.js';
104
105
 
105
- // Hashing (also available via utils.hash)
106
- // Already exported via ./utils.js
107
-
108
106
  // Patterns
109
107
  export {
110
- regex_whitespace,
111
- regex_whitespaces,
112
- regex_starts_with_newline,
113
- regex_starts_with_whitespace,
114
- regex_starts_with_whitespaces,
115
- regex_ends_with_whitespace,
116
- regex_ends_with_whitespaces,
117
- regex_not_whitespace,
118
- regex_whitespaces_strict,
119
- regex_only_whitespaces,
120
- regex_newline_characters,
121
- regex_not_newline_characters,
122
- regex_is_valid_identifier,
123
- regex_invalid_identifier_chars,
124
- regex_starts_with_vowel,
125
- regex_heading_tags,
126
- regex_illegal_attribute_character,
108
+ regex_whitespace as regexWhitespace,
109
+ regex_whitespaces as regexWhitespaces,
110
+ regex_starts_with_newline as regexStartsWithNewline,
111
+ regex_starts_with_whitespace as regexStartsWithWhitespace,
112
+ regex_starts_with_whitespaces as regexStartsWithWhitespaces,
113
+ regex_ends_with_whitespace as regexEndsWithWhitespace,
114
+ regex_ends_with_whitespaces as regexEndsWithWhitespaces,
115
+ regex_not_whitespace as regexNotWhitespace,
116
+ regex_whitespaces_strict as regexWhitespacesStrict,
117
+ regex_only_whitespaces as regexOnlyWhitespaces,
118
+ regex_newline_characters as regexNewlineCharacters,
119
+ regex_not_newline_characters as regexNotNewlineCharacters,
120
+ regex_is_valid_identifier as regexIsValidIdentifier,
121
+ regex_invalid_identifier_chars as regexInvalidIdentifierChars,
122
+ regex_starts_with_vowel as regexStartsWithVowel,
123
+ regex_heading_tags as regexHeadingTags,
124
+ regex_illegal_attribute_character as regexIllegalAttributeCharacter,
127
125
  } from './utils/patterns.js';
128
126
 
129
127
  // Sanitize
130
- export { sanitize_template_string } from './utils/sanitize_template_string.js';
128
+ export { sanitize_template_string as sanitizeTemplateString } from './utils/sanitize_template_string.js';
131
129
 
132
130
  // Escaping
133
131
  export { escape } from './utils/escaping.js';
134
132
 
135
133
  // Transform
136
- export { render_stylesheets } from './transform/stylesheet.js';
137
- export { convert_source_map_to_mappings } from './transform/segments.js';
134
+ export { render_stylesheets as renderStylesheets } from './transform/stylesheet.js';
135
+ export {
136
+ convert_source_map_to_mappings as convertSourceMapToMappings,
137
+ create_volar_mappings_result as createVolarMappingsResult,
138
+ create_basic_volar_mappings_result as createBasicVolarMappingsResult,
139
+ } from './transform/segments.js';
138
140
 
139
141
  // Analyze
140
- export { analyze_css } from './analyze/css-analyze.js';
141
- export { validate_nesting } from './analyze/validation.js';
142
+ export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
143
+ export { validate_nesting as validateNesting } from './analyze/validation.js';
@@ -113,7 +113,7 @@ export function isWhitespaceTextNode(node) {
113
113
  /**
114
114
  * Create a parser by composing Acorn with TypeScript/JSX support and optional framework plugins.
115
115
  *
116
- * This is the core factory for building tsrx-based parsers. Framework plugins (like RipplePlugin)
116
+ * This is the core factory for building tsrx-based parsers. Framework plugins (like TSRXPlugin)
117
117
  * extend the base parser with framework-specific syntax.
118
118
  *
119
119
  * @param {...(AcornPlugin | Function)} plugins - Framework parser plugins to compose
@@ -0,0 +1,18 @@
1
+ /** @import * as AST from 'estree' */
2
+ /** @import { ParseOptions } from '../../types/index' */
3
+
4
+ import { createParser } from './index.js';
5
+ import { TSRXPlugin } from '../plugin.js';
6
+
7
+ const parse = createParser(TSRXPlugin());
8
+
9
+ /**
10
+ * Parse source code to an ESTree AST using the TSRX parser.
11
+ * @param {string} source
12
+ * @param {string} [filename]
13
+ * @param {ParseOptions} [options]
14
+ * @returns {AST.Program}
15
+ */
16
+ export function parse_module(source, filename, options) {
17
+ return parse(source, filename, options);
18
+ }