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 +62 -0
- package/index.d.ts +35 -0
- package/index.js +57 -0
- package/options.js +186 -0
- package/package.json +21 -0
- package/wasm.js +1 -0
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
|
+
}
|