syntaxase-wasm 0.0.0

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,62 @@
1
+ # Syntaxase
2
+
3
+ Lightning-fast type stripping and JSX lowering.
4
+
5
+ Named like an enzyme, Syntaxase removes erasable TypeScript syntax and lowers
6
+ supported TypeScript and JSX runtime constructs.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install syntaxase-wasm
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```js
17
+ import { stripTypes, transform } from "syntaxase-wasm";
18
+
19
+ const stripped = stripTypes(`
20
+ interface User { name: string }
21
+ const user: User = { name: "Ada" };
22
+ `);
23
+
24
+ const transformed = transform(`const view: JSX.Element = <h1>Hello</h1>;`, { jsx: true });
25
+ ```
26
+
27
+ ## TypeScript syntax
28
+
29
+ `stripTypes` erases fixed-width TypeScript syntax while preserving source
30
+ length and line layout. Use it with source accepted by TypeScript's
31
+ [`erasableSyntaxOnly`](https://www.typescriptlang.org/tsconfig/erasableSyntaxOnly.html)
32
+ option. It does not validate that constraint: runtime TypeScript constructs such
33
+ as enums may remain unchanged or lose TypeScript-only modifiers without gaining
34
+ the JavaScript code needed to preserve their semantics.
35
+
36
+ `transform` additionally lowers supported runtime TypeScript constructs and,
37
+ when enabled, JSX.
38
+
39
+ ## JSX options
40
+
41
+ | `options.jsx` | Behavior | Defaults and supported fields |
42
+ | -------------------------- | ------------------------------------------------- | --------------------------------------------------------------- |
43
+ | omitted or `false` | Parse TypeScript without JSX | None |
44
+ | `true` | Lower JSX with the automatic production runtime | `importSource: "react"` |
45
+ | `{ runtime: "automatic" }` | Lower JSX with the automatic runtime | `development: false`, `importSource: "react"` |
46
+ | `{ runtime: "classic" }` | Lower JSX to factory calls without adding imports | `pragma: "React.createElement"`, `pragmaFrag: "React.Fragment"` |
47
+ | `{ runtime: "preserve" }` | Parse JSX and leave it in the output | No additional fields |
48
+
49
+ Automatic development mode imports `jsxDEV` from
50
+ `<importSource>/jsx-dev-runtime`; production mode imports `jsx` and `jsxs` from
51
+ `<importSource>/jsx-runtime`. The default therefore requires React's JSX runtime
52
+ to be installed. Set `importSource` for another compatible runtime.
53
+
54
+ `development` is supported only by the automatic runtime. `pragma` and
55
+ `pragmaFrag` are supported only by the classic runtime.
56
+
57
+ ## Diagnostics and errors
58
+
59
+ The JavaScript API returns transformed strings only. Parser recovery diagnostics
60
+ are not exposed, so malformed input can still produce output. Use a TypeScript
61
+ checker or another parser when validation is required. Invalid API arguments and
62
+ internal transform failures throw.
package/index.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /** JSX lowering strategy used by every Syntaxase JavaScript package. */
2
+ export type JSXRuntime = "automatic" | "classic" | "preserve";
3
+
4
+ export type JSXConfig =
5
+ | {
6
+ runtime?: "automatic";
7
+ development?: boolean;
8
+ importSource?: string;
9
+ pragma?: never;
10
+ pragmaFrag?: never;
11
+ }
12
+ | {
13
+ runtime: "classic";
14
+ development?: never;
15
+ importSource?: never;
16
+ pragma?: string;
17
+ pragmaFrag?: string;
18
+ }
19
+ | {
20
+ runtime: "preserve";
21
+ development?: never;
22
+ importSource?: never;
23
+ pragma?: never;
24
+ pragmaFrag?: never;
25
+ };
26
+
27
+ export interface TransformOptions {
28
+ jsx?: boolean | JSXConfig;
29
+ }
30
+
31
+ /** Transform erasable TypeScript and supported runtime TypeScript/JSX syntax to JavaScript. */
32
+ export function transform(sourceText: string, options?: TransformOptions): string;
33
+
34
+ /** Erase only fixed-width TypeScript syntax while preserving source length and line layout. */
35
+ export function stripTypes(sourceText: string): string;
package/index.js ADDED
@@ -0,0 +1,57 @@
1
+ import wasmBytes from "./wasm.js";
2
+ import { assertSourceText, MODE_STRIP_TYPES, resolveTransformOptions } from "./options.js";
3
+
4
+ const wasmModule = new WebAssembly.Module(wasmBytes);
5
+ const wasmInstance = new WebAssembly.Instance(wasmModule);
6
+ const { alloc, free, memory, run } = wasmInstance.exports;
7
+ const encoder = new TextEncoder();
8
+ const decoder = new TextDecoder();
9
+
10
+ const MAX_WASM_LENGTH = 0xffff_ffff;
11
+
12
+ /** Transform erasable TypeScript and supported runtime TypeScript/JSX syntax to JavaScript. */
13
+ export function transform(sourceText, options = {}) {
14
+ assertSourceText(sourceText);
15
+ const normalized = resolveTransformOptions(options);
16
+ return invoke(sourceText, normalized.mode, normalized.first, normalized.second);
17
+ }
18
+
19
+ /** Erase only fixed-width TypeScript syntax while preserving source length and line layout. */
20
+ export function stripTypes(sourceText) {
21
+ assertSourceText(sourceText);
22
+ return invoke(sourceText, MODE_STRIP_TYPES, "", "");
23
+ }
24
+
25
+ function invoke(sourceText, mode, firstOption, secondOption) {
26
+ const source = encoder.encode(sourceText);
27
+ const first = encoder.encode(firstOption);
28
+ const second = encoder.encode(secondOption);
29
+ const inputLength = source.length + first.length + second.length;
30
+ if (inputLength > MAX_WASM_LENGTH) {
31
+ throw new RangeError("encoded transform input exceeds WebAssembly memory limits");
32
+ }
33
+
34
+ const allocationLength = Math.max(inputLength, 1);
35
+ const inputPointer = alloc(allocationLength) >>> 0;
36
+ let outputPointer = 0;
37
+ let outputLength = 0;
38
+ try {
39
+ const input = new Uint8Array(memory.buffer, inputPointer, inputLength);
40
+ input.set(source, 0);
41
+ input.set(first, source.length);
42
+ input.set(second, source.length + first.length);
43
+
44
+ outputPointer = run(inputPointer, source.length, first.length, second.length, mode) >>> 0;
45
+ if (outputPointer === 0) {
46
+ throw new Error("Syntaxase WebAssembly transform failed");
47
+ }
48
+
49
+ const header = new DataView(memory.buffer, outputPointer, 4);
50
+ outputLength = header.getUint32(0, true);
51
+ const output = new Uint8Array(memory.buffer, outputPointer + 4, outputLength);
52
+ return decoder.decode(output);
53
+ } finally {
54
+ if (outputPointer !== 0) free(outputPointer, outputLength + 4);
55
+ free(inputPointer, allocationLength);
56
+ }
57
+ }
package/options.js ADDED
@@ -0,0 +1,186 @@
1
+ // Canonical option normalization shared by all JavaScript package boundaries.
2
+ const MODE_TRANSFORM = 0;
3
+ const MODE_AUTOMATIC = 1;
4
+ const MODE_AUTOMATIC_DEVELOPMENT = 2;
5
+ const MODE_CLASSIC = 3;
6
+ const MODE_PRESERVE = 4;
7
+ export const MODE_STRIP_TYPES = 5;
8
+
9
+ const IDENTIFIER_NAME = /^[$_\p{ID_Start}][$\u200c\u200d_\p{ID_Continue}]*$/u;
10
+ const RESERVED_IDENTIFIER_REFERENCES = new Set([
11
+ "await",
12
+ "break",
13
+ "case",
14
+ "catch",
15
+ "class",
16
+ "const",
17
+ "continue",
18
+ "debugger",
19
+ "default",
20
+ "delete",
21
+ "do",
22
+ "else",
23
+ "enum",
24
+ "export",
25
+ "extends",
26
+ "false",
27
+ "finally",
28
+ "for",
29
+ "function",
30
+ "if",
31
+ "implements",
32
+ "import",
33
+ "in",
34
+ "instanceof",
35
+ "interface",
36
+ "let",
37
+ "new",
38
+ "null",
39
+ "package",
40
+ "private",
41
+ "protected",
42
+ "public",
43
+ "return",
44
+ "static",
45
+ "super",
46
+ "switch",
47
+ "this",
48
+ "throw",
49
+ "true",
50
+ "try",
51
+ "typeof",
52
+ "var",
53
+ "void",
54
+ "while",
55
+ "with",
56
+ "yield",
57
+ ]);
58
+ const JSX_CONFIG_KEYS = new Set(["runtime", "development", "importSource", "pragma", "pragmaFrag"]);
59
+ const JSX_RUNTIMES = new Set(["automatic", "classic", "preserve"]);
60
+
61
+ export function assertSourceText(sourceText) {
62
+ if (typeof sourceText !== "string") {
63
+ throw new TypeError("sourceText must be a string");
64
+ }
65
+ }
66
+
67
+ export function resolveTransformOptions(options) {
68
+ assertOptions(options);
69
+ return resolveJSXConfig(options.jsx);
70
+ }
71
+
72
+ function assertOptions(options) {
73
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
74
+ throw new TypeError("transform options must be an object");
75
+ }
76
+ for (const key of Reflect.ownKeys(options)) {
77
+ if (key !== "jsx") {
78
+ throw new TypeError(`transform options contains unknown option ${String(key)}`);
79
+ }
80
+ }
81
+ }
82
+
83
+ function resolveJSXConfig(value) {
84
+ if (value === undefined || value === false) {
85
+ return { mode: MODE_TRANSFORM, first: "", second: "" };
86
+ }
87
+ if (value === true) {
88
+ return { mode: MODE_AUTOMATIC, first: "react", second: "" };
89
+ }
90
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
91
+ throw new TypeError("transform options.jsx must be a boolean or an object");
92
+ }
93
+
94
+ assertKnownJSXKeys(value);
95
+ const runtime = readRuntime(value);
96
+ const importSource = readString(value, "importSource");
97
+ const pragma = readMemberExpression(value, "pragma");
98
+ const pragmaFrag = readMemberExpression(value, "pragmaFrag");
99
+
100
+ if (runtime === "automatic") {
101
+ const development = readBoolean(value, "development", false);
102
+ assertAbsent(value, "pragma", runtime);
103
+ assertAbsent(value, "pragmaFrag", runtime);
104
+ return {
105
+ mode: development ? MODE_AUTOMATIC_DEVELOPMENT : MODE_AUTOMATIC,
106
+ first: importSource ?? "react",
107
+ second: "",
108
+ };
109
+ }
110
+ if (runtime === "classic") {
111
+ assertAbsent(value, "development", runtime);
112
+ assertAbsent(value, "importSource", runtime);
113
+ return {
114
+ mode: MODE_CLASSIC,
115
+ first: pragma ?? "React.createElement",
116
+ second: pragmaFrag ?? "React.Fragment",
117
+ };
118
+ }
119
+
120
+ assertAbsent(value, "development", runtime);
121
+ assertAbsent(value, "importSource", runtime);
122
+ assertAbsent(value, "pragma", runtime);
123
+ assertAbsent(value, "pragmaFrag", runtime);
124
+ return { mode: MODE_PRESERVE, first: "", second: "" };
125
+ }
126
+
127
+ function assertKnownJSXKeys(config) {
128
+ for (const key of Reflect.ownKeys(config)) {
129
+ if (typeof key !== "string" || !JSX_CONFIG_KEYS.has(key)) {
130
+ throw new TypeError(`transform options.jsx contains unknown option ${String(key)}`);
131
+ }
132
+ }
133
+ }
134
+
135
+ function readRuntime(config) {
136
+ if (!Object.hasOwn(config, "runtime")) return "automatic";
137
+ const runtime = config.runtime;
138
+ if (typeof runtime !== "string" || !JSX_RUNTIMES.has(runtime)) {
139
+ throw new TypeError('transform options.jsx.runtime must be "automatic", "classic", or "preserve"');
140
+ }
141
+ return runtime;
142
+ }
143
+
144
+ function readBoolean(config, key, defaultValue) {
145
+ if (!Object.hasOwn(config, key)) return defaultValue;
146
+ const value = config[key];
147
+ if (typeof value !== "boolean") {
148
+ throw new TypeError(`transform options.jsx.${key} must be a boolean`);
149
+ }
150
+ return value;
151
+ }
152
+
153
+ function readString(config, key) {
154
+ if (!Object.hasOwn(config, key)) return undefined;
155
+ const value = config[key];
156
+ if (typeof value !== "string" || value.length === 0) {
157
+ throw new TypeError(`transform options.jsx.${key} must be a non-empty string`);
158
+ }
159
+ return value;
160
+ }
161
+
162
+ function readMemberExpression(config, key) {
163
+ if (!Object.hasOwn(config, key)) return undefined;
164
+ const value = config[key];
165
+ if (typeof value !== "string" || !isDottedMemberExpression(value)) {
166
+ throw new TypeError(`transform options.jsx.${key} must be an identifier or dotted member expression`);
167
+ }
168
+ return value;
169
+ }
170
+
171
+ function isDottedMemberExpression(value) {
172
+ const segments = value.split(".");
173
+ const root = segments[0];
174
+ if (root === undefined || !isIdentifierReference(root)) return false;
175
+ return segments.every((segment) => IDENTIFIER_NAME.test(segment));
176
+ }
177
+
178
+ function isIdentifierReference(value) {
179
+ return IDENTIFIER_NAME.test(value) && !RESERVED_IDENTIFIER_REFERENCES.has(value);
180
+ }
181
+
182
+ function assertAbsent(config, key, runtime) {
183
+ if (Object.hasOwn(config, key)) {
184
+ throw new TypeError(`transform options.jsx.${String(key)} is not supported with ${runtime} runtime`);
185
+ }
186
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "syntaxase-wasm",
3
+ "version": "0.0.0",
4
+ "description": "Lightning-fast type stripping and JSX lowering",
5
+ "license": "MIT",
6
+ "funding": {
7
+ "type": "github",
8
+ "url": "https://github.com/sponsors/magic-akari"
9
+ },
10
+ "type": "module",
11
+ "main": "index.js",
12
+ "types": "index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./index.d.ts",
16
+ "import": "./index.js",
17
+ "default": "./index.js"
18
+ }
19
+ },
20
+ "files": ["index.js", "index.d.ts", "options.js", "wasm.js"]
21
+ }