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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "imports": {
3
- "toilscript": "https://cdn.jsdelivr.net/npm/toilscript@0.1.12/dist/toilscript.js",
4
- "toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.12/dist/cli.js",
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.12";
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.12/dist/toilscript.js",
5
- "toilscript/cli": "https://cdn.jsdelivr.net/npm/toilscript@0.1.12/dist/cli.js",
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
@@ -8,7 +8,7 @@
8
8
  "toilscript",
9
9
  "wasm"
10
10
  ],
11
- "version": "0.1.12",
11
+ "version": "0.1.14",
12
12
  "author": "Daniel Wirtz <dcode+assemblyscript@dcode.io>",
13
13
  "license": "Apache-2.0",
14
14
  "homepage": "https://github.com/dacely-cloud/toilscript",
@@ -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 has no grammar for toil-native decorators applied to
5
- * top-level functions and globals (`@main`, `@inline`, `@unmanaged`, ...), so
6
- * the editor's language service flags them as errors even though the toilscript
7
- * compiler handles them correctly. It also can't see the members the compiler
8
- * injects into a `@data` class (`encode`/`decode`/`toJSON`/...). This plugin runs
9
- * only inside the editor's language service (VS Code, WebStorm, etc. - never
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
- * TS1206 "Decorators are not valid here."
13
- * TS1249 "A decorator can only decorate a method implementation, not an overload."
14
- * TS2339 "Property '<m>' does not exist on type '<T>'." - but ONLY when `<m>` is a
15
- * `@data`-injected member and `<T>` is a `@data` class (so a typo on any
16
- * other type still surfaces).
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 - unknown types, bad calls, missing names - passes
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
- /** Whether a class declaration carries the `@data` decorator (bare or called). */
50
- function hasDataDecorator(decl) {
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
- for (const d of decorators) {
53
- const e = d.expression;
54
- const name = e && (e.text || (e.expression && e.expression.text));
55
- if (name === 'data') return true;
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) && hasDataDecorator(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) => !DECORATOR_GRAMMAR_CODES.has(d.code) && !isInjectedDataMember(fileName, 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
  };