toilscript 0.1.12 → 0.1.14
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/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/importmap.json +2 -2
- package/dist/web.js +3 -3
- package/package.json +1 -1
- package/std/assembly/toilscript.d.ts +51 -0
- package/std/ts-plugin.cjs +152 -26
package/dist/importmap.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"imports": {
|
|
3
|
-
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
4
|
-
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
3
|
+
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.14/dist/toilscript.js",
|
|
4
|
+
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.14/dist/cli.js",
|
|
5
5
|
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@129.0.0-nightly.20260428/index.js",
|
|
6
6
|
"long": "https://cdn.jsdelivr.net/npm/long@5.3.2/index.js"
|
|
7
7
|
}
|
package/dist/web.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var ASSEMBLYSCRIPT_VERSION = "0.1.
|
|
1
|
+
var ASSEMBLYSCRIPT_VERSION = "0.1.14";
|
|
2
2
|
var ASSEMBLYSCRIPT_IMPORTMAP = {
|
|
3
3
|
"imports": {
|
|
4
|
-
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
5
|
-
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.
|
|
4
|
+
"toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.14/dist/toilscript.js",
|
|
5
|
+
"toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.14/dist/cli.js",
|
|
6
6
|
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@129.0.0-nightly.20260428/index.js",
|
|
7
7
|
"long": "https://cdn.jsdelivr.net/npm/long@5.3.2/index.js"
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -323,3 +323,54 @@ declare class i256 {
|
|
|
323
323
|
isNeg(): bool;
|
|
324
324
|
isZero(): bool;
|
|
325
325
|
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The dynamic JSON value tree. A `@data` class's `toJSON()` returns one of these, and
|
|
329
|
+
* `JSON.parse(...)` produces one. Globalized by std/assembly/json, so it needs no import.
|
|
330
|
+
*/
|
|
331
|
+
declare class JSON {
|
|
332
|
+
/** A JSON null. */
|
|
333
|
+
static nul(): JSON;
|
|
334
|
+
/** An empty JSON object, build it with `.set(key, value)`. */
|
|
335
|
+
static obj(): JSON;
|
|
336
|
+
/** An empty JSON array, build it with `.push(value)`. */
|
|
337
|
+
static arr(): JSON;
|
|
338
|
+
/** Wrap a scalar/string/bool/array as a JSON value. */
|
|
339
|
+
static of<T>(value: T): JSON;
|
|
340
|
+
/** Parse JSON text into a value tree (returns an error value on malformed input). */
|
|
341
|
+
static parse(text: string): JSON;
|
|
342
|
+
/** Serialize a scalar/string/bool/array value to a JSON string. */
|
|
343
|
+
static stringify<T>(value: T): string;
|
|
344
|
+
|
|
345
|
+
/** Append a value to a JSON array; returns `this` for chaining. */
|
|
346
|
+
push(value: JSON): JSON;
|
|
347
|
+
/** Set a key on a JSON object; returns `this` for chaining. */
|
|
348
|
+
set(key: string, value: JSON): JSON;
|
|
349
|
+
|
|
350
|
+
isNull(): bool;
|
|
351
|
+
isBool(): bool;
|
|
352
|
+
isNumber(): bool;
|
|
353
|
+
isString(): bool;
|
|
354
|
+
isArray(): bool;
|
|
355
|
+
isObject(): bool;
|
|
356
|
+
|
|
357
|
+
asBool(): bool;
|
|
358
|
+
asF64(): f64;
|
|
359
|
+
asI64(): i64;
|
|
360
|
+
asU64(): u64;
|
|
361
|
+
asString(): string;
|
|
362
|
+
|
|
363
|
+
/** Element count of an array (or 0). */
|
|
364
|
+
length(): i32;
|
|
365
|
+
/** Element at `index` of an array. */
|
|
366
|
+
at(index: i32): JSON;
|
|
367
|
+
/** Whether an object has `key`. */
|
|
368
|
+
has(key: string): bool;
|
|
369
|
+
/** Value for `key` on an object. */
|
|
370
|
+
get(key: string): JSON;
|
|
371
|
+
/** The keys of an object. */
|
|
372
|
+
objectKeys(): Array<string>;
|
|
373
|
+
|
|
374
|
+
/** Serialize this value tree to a JSON string. */
|
|
375
|
+
toString(): string;
|
|
376
|
+
}
|
package/std/ts-plugin.cjs
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ToilScript TypeScript Language Service plugin.
|
|
3
3
|
*
|
|
4
|
-
* Stock TypeScript
|
|
5
|
-
* top-level functions
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `tsc`/compiler builds) and removes exactly those false positives:
|
|
4
|
+
* Stock TypeScript can't see what the toilscript compiler does to a source file:
|
|
5
|
+
* the native decorators on top-level functions (`@main`, `@inline`, ...), the
|
|
6
|
+
* members it injects into a `@data` class (`encode`/`decode`/`toJSON`/...), or the
|
|
7
|
+
* fact that a class used only via a toil decorator is actually used. This plugin
|
|
8
|
+
* runs only inside the editor's language service (VS Code, WebStorm, etc. - never
|
|
9
|
+
* `tsc`/compiler builds) and bridges that gap:
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* 1. Declaration-merges TYPED members onto each `@data` class (so `value.toJSON()`
|
|
12
|
+
* is `JSON`, `Type.decode(bytes)` is the class, etc.) by appending ambient decls
|
|
13
|
+
* to the file the editor sees. Diagnostics in that appended region are hidden.
|
|
14
|
+
* 2. As a fallback (if 1 doesn't apply), still removes the false positives:
|
|
15
|
+
* TS1206 / TS1249 decorator grammar
|
|
16
|
+
* TS2339 a `@data`-injected member accessed on a `@data` class
|
|
17
|
+
* TS6133 / TS6196 a toil-decorated class/method reported as unused
|
|
17
18
|
*
|
|
18
|
-
* Every other diagnostic
|
|
19
|
-
* through untouched, so real type errors are still surfaced.
|
|
19
|
+
* Every other diagnostic passes through untouched, so real errors still surface.
|
|
20
20
|
*/
|
|
21
21
|
const DECORATOR_GRAMMAR_CODES = new Set([1206, 1249]);
|
|
22
22
|
const PROPERTY_NOT_EXIST = 2339;
|
|
23
|
+
const DECLARED_NEVER_USED = new Set([6133, 6196]);
|
|
23
24
|
|
|
24
25
|
/** Members the compiler injects into every `@data` class (instance + static). */
|
|
25
26
|
const DATA_MEMBERS = new Set([
|
|
@@ -32,6 +33,32 @@ const DATA_MEMBERS = new Set([
|
|
|
32
33
|
'dataId',
|
|
33
34
|
]);
|
|
34
35
|
|
|
36
|
+
/** Toil-native decorators whose presence means the compiler uses the declaration. */
|
|
37
|
+
const TOIL_DECORATORS = new Set([
|
|
38
|
+
'data',
|
|
39
|
+
'remote',
|
|
40
|
+
'service',
|
|
41
|
+
'rest',
|
|
42
|
+
'route',
|
|
43
|
+
'get',
|
|
44
|
+
'post',
|
|
45
|
+
'put',
|
|
46
|
+
'del',
|
|
47
|
+
'patch',
|
|
48
|
+
'head',
|
|
49
|
+
'options',
|
|
50
|
+
'main',
|
|
51
|
+
'global',
|
|
52
|
+
'inline',
|
|
53
|
+
'external',
|
|
54
|
+
'unmanaged',
|
|
55
|
+
'final',
|
|
56
|
+
'operator',
|
|
57
|
+
'lazy',
|
|
58
|
+
'unsafe',
|
|
59
|
+
'builtin',
|
|
60
|
+
]);
|
|
61
|
+
|
|
35
62
|
function init(modules) {
|
|
36
63
|
const ts = modules.typescript;
|
|
37
64
|
|
|
@@ -46,37 +73,107 @@ function init(modules) {
|
|
|
46
73
|
return found;
|
|
47
74
|
}
|
|
48
75
|
|
|
49
|
-
/**
|
|
50
|
-
function
|
|
76
|
+
/** A decorator's bare name, for `@name` and `@name(...)`. */
|
|
77
|
+
function decoratorName(d) {
|
|
78
|
+
const e = d.expression;
|
|
79
|
+
return e && (e.text || (e.expression && e.expression.text));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function declHasDecorator(decl, names) {
|
|
51
83
|
const decorators = (ts.getDecorators ? ts.getDecorators(decl) : decl.decorators) || [];
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
84
|
+
return decorators.some((d) => {
|
|
85
|
+
const n = decoratorName(d);
|
|
86
|
+
return typeof names === 'string' ? n === names : !!n && names.has(n);
|
|
87
|
+
});
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
function declsAreDataClass(declarations) {
|
|
61
91
|
return (
|
|
62
92
|
!!declarations &&
|
|
63
|
-
declarations.some((d) => ts.isClassDeclaration(d) &&
|
|
93
|
+
declarations.some((d) => ts.isClassDeclaration(d) && declHasDecorator(d, 'data'))
|
|
64
94
|
);
|
|
65
95
|
}
|
|
66
96
|
|
|
67
|
-
/** Whether `expr` is (an instance of, or the static side of) a `@data` class. */
|
|
68
97
|
function resolvesToDataClass(expr, checker) {
|
|
69
98
|
const type = checker.getTypeAtLocation(expr);
|
|
70
99
|
const typeSym = type && type.getSymbol && type.getSymbol();
|
|
71
100
|
if (typeSym && declsAreDataClass(typeSym.declarations)) return true;
|
|
72
|
-
// Static access like `Player.decode(...)`: resolve the identifier's own symbol.
|
|
73
101
|
const sym = checker.getSymbolAtLocation(expr);
|
|
74
102
|
return !!sym && declsAreDataClass(sym.declarations);
|
|
75
103
|
}
|
|
76
104
|
|
|
105
|
+
function canHoldDecorators(node) {
|
|
106
|
+
return (
|
|
107
|
+
ts.isClassDeclaration(node) ||
|
|
108
|
+
ts.isClassExpression(node) ||
|
|
109
|
+
ts.isMethodDeclaration(node) ||
|
|
110
|
+
ts.isFunctionDeclaration(node) ||
|
|
111
|
+
ts.isPropertyDeclaration(node) ||
|
|
112
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
113
|
+
ts.isSetAccessorDeclaration(node)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Ambient declarations to append so the editor types each `@data` class's injected
|
|
119
|
+
* codec members. Returns "" when the file declares no `@data` class. Uses only
|
|
120
|
+
* editor-visible globals (`Uint8Array`, `JSON`, `u32`), so the appended block is
|
|
121
|
+
* itself error-free; any diagnostics there are filtered anyway.
|
|
122
|
+
*/
|
|
123
|
+
function dataAugmentation(text) {
|
|
124
|
+
if (text.indexOf('@data') < 0) return '';
|
|
125
|
+
let sf;
|
|
126
|
+
try {
|
|
127
|
+
sf = ts.createSourceFile('__toil_aug__.ts', text, ts.ScriptTarget.Latest, true);
|
|
128
|
+
} catch {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
let out = '';
|
|
132
|
+
sf.forEachChild((node) => {
|
|
133
|
+
if (ts.isClassDeclaration(node) && node.name && declHasDecorator(node, 'data')) {
|
|
134
|
+
const n = node.name.text;
|
|
135
|
+
out +=
|
|
136
|
+
`\n// toilscript: editor types for the compiler-injected @data ${n} codec\n` +
|
|
137
|
+
`interface ${n} { encode(): Uint8Array; toJSON(): JSON; }\n` +
|
|
138
|
+
`declare namespace ${n} { function decode(buf: Uint8Array): ${n}; function fromJSON(v: JSON): ${n}; function dataId(): u32; }\n`;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
77
144
|
return {
|
|
78
145
|
create(info) {
|
|
79
146
|
const ls = info.languageService;
|
|
147
|
+
const host = info.languageServiceHost;
|
|
148
|
+
|
|
149
|
+
// Original text length per augmented file, so diagnostics in the appended region drop.
|
|
150
|
+
const originalLength = new Map();
|
|
151
|
+
|
|
152
|
+
// Inject the typed @data members into the editor's view of each source file.
|
|
153
|
+
if (typeof host.getScriptSnapshot === 'function' && typeof host.getScriptVersion === 'function') {
|
|
154
|
+
const origSnapshot = host.getScriptSnapshot.bind(host);
|
|
155
|
+
const origVersion = host.getScriptVersion.bind(host);
|
|
156
|
+
host.getScriptSnapshot = (fileName) => {
|
|
157
|
+
const snap = origSnapshot(fileName);
|
|
158
|
+
if (!snap) return snap;
|
|
159
|
+
let aug = '';
|
|
160
|
+
try {
|
|
161
|
+
aug = dataAugmentation(snap.getText(0, snap.getLength()));
|
|
162
|
+
} catch {
|
|
163
|
+
aug = '';
|
|
164
|
+
}
|
|
165
|
+
if (!aug) {
|
|
166
|
+
originalLength.delete(fileName);
|
|
167
|
+
return snap;
|
|
168
|
+
}
|
|
169
|
+
const text = snap.getText(0, snap.getLength());
|
|
170
|
+
originalLength.set(fileName, text.length);
|
|
171
|
+
return ts.ScriptSnapshot.fromString(text + aug);
|
|
172
|
+
};
|
|
173
|
+
// A stable suffix so the service re-reads once and picks up the augmentation; it
|
|
174
|
+
// still changes whenever the underlying file changes.
|
|
175
|
+
host.getScriptVersion = (fileName) => origVersion(fileName) + ':toil-data';
|
|
176
|
+
}
|
|
80
177
|
|
|
81
178
|
// Proxy the language service, forwarding everything to the real one.
|
|
82
179
|
const proxy = Object.create(null);
|
|
@@ -85,6 +182,12 @@ function init(modules) {
|
|
|
85
182
|
proxy[key] = typeof value === 'function' ? value.bind(ls) : value;
|
|
86
183
|
}
|
|
87
184
|
|
|
185
|
+
const inAugmentedRegion = (fileName, diag) => {
|
|
186
|
+
const len = originalLength.get(fileName);
|
|
187
|
+
return len != null && diag.start != null && diag.start >= len;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Fallback: a TS2339 for a `@data`-injected member on a `@data` class.
|
|
88
191
|
const isInjectedDataMember = (fileName, diag) => {
|
|
89
192
|
if (diag.code !== PROPERTY_NOT_EXIST || diag.start == null) return false;
|
|
90
193
|
const program = ls.getProgram();
|
|
@@ -101,9 +204,25 @@ function init(modules) {
|
|
|
101
204
|
return resolvesToDataClass(access.expression, program.getTypeChecker());
|
|
102
205
|
};
|
|
103
206
|
|
|
207
|
+
// A "declared but never used" for a class/method/function with a toil decorator.
|
|
208
|
+
const isToilDecoratedUnused = (fileName, diag) => {
|
|
209
|
+
if (!DECLARED_NEVER_USED.has(diag.code) || diag.start == null) return false;
|
|
210
|
+
const program = ls.getProgram();
|
|
211
|
+
const sf = program && program.getSourceFile(fileName);
|
|
212
|
+
if (!sf) return false;
|
|
213
|
+
const node = nodeAt(sf, diag.start);
|
|
214
|
+
if (!node) return false;
|
|
215
|
+
const decl = canHoldDecorators(node) ? node : node.parent;
|
|
216
|
+
return !!decl && canHoldDecorators(decl) && declHasDecorator(decl, TOIL_DECORATORS);
|
|
217
|
+
};
|
|
218
|
+
|
|
104
219
|
const strip = (fileName, diagnostics) =>
|
|
105
220
|
diagnostics.filter(
|
|
106
|
-
(d) =>
|
|
221
|
+
(d) =>
|
|
222
|
+
!DECORATOR_GRAMMAR_CODES.has(d.code) &&
|
|
223
|
+
!inAugmentedRegion(fileName, d) &&
|
|
224
|
+
!isInjectedDataMember(fileName, d) &&
|
|
225
|
+
!isToilDecoratedUnused(fileName, d),
|
|
107
226
|
);
|
|
108
227
|
|
|
109
228
|
proxy.getSemanticDiagnostics = (fileName) =>
|
|
@@ -111,6 +230,13 @@ function init(modules) {
|
|
|
111
230
|
proxy.getSyntacticDiagnostics = (fileName) =>
|
|
112
231
|
strip(fileName, ls.getSyntacticDiagnostics(fileName));
|
|
113
232
|
|
|
233
|
+
if (typeof ls.getSuggestionDiagnostics === 'function') {
|
|
234
|
+
proxy.getSuggestionDiagnostics = (fileName) =>
|
|
235
|
+
ls
|
|
236
|
+
.getSuggestionDiagnostics(fileName)
|
|
237
|
+
.filter((d) => !inAugmentedRegion(fileName, d) && !isToilDecoratedUnused(fileName, d));
|
|
238
|
+
}
|
|
239
|
+
|
|
114
240
|
return proxy;
|
|
115
241
|
},
|
|
116
242
|
};
|