rbxts-transform-boost 0.1.0 → 1.1.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 +125 -125
- package/dist/config.d.ts +1 -0
- package/dist/index.js +3 -5
- package/dist/passes/annotate.js +240 -47
- package/dist/passes/native.d.ts +1 -1
- package/dist/passes/native.js +11 -5
- package/dist/util.d.ts +1 -0
- package/dist/util.js +4 -0
- package/package.json +8 -5
- package/.github/workflows/publish.yml +0 -26
- package/bench/benchmark.project.json +0 -20
- package/bench/package-lock.json +0 -59
- package/bench/package.json +0 -14
- package/bench/src/server/benchmark.server.ts +0 -57
- package/bench/src/shared/fns-bare.ts +0 -127
- package/bench/src/shared/fns.ts +0 -127
- package/bench/tsconfig.json +0 -32
- package/bench/tsconfig.notransform.json +0 -24
- package/rokit.toml +0 -3
- package/scripts/postprocess.js +0 -29
- package/src/config.ts +0 -10
- package/src/index.ts +0 -25
- package/src/passes/annotate.ts +0 -207
- package/src/passes/cache.ts +0 -163
- package/src/passes/loops.ts +0 -80
- package/src/passes/native.ts +0 -19
- package/src/util.ts +0 -33
- package/tsconfig.json +0 -16
package/dist/passes/annotate.js
CHANGED
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.annotatePass = annotatePass;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
// Luau type names for TypeScript type strings rotor knows about
|
|
40
39
|
const LUAU_TYPE = {
|
|
41
40
|
number: "number",
|
|
42
41
|
string: "string",
|
|
@@ -58,7 +57,6 @@ const LUAU_TYPE = {
|
|
|
58
57
|
Region3: "Region3",
|
|
59
58
|
Ray: "Ray",
|
|
60
59
|
buffer: "buffer",
|
|
61
|
-
// Roblox service types
|
|
62
60
|
Instance: "Instance",
|
|
63
61
|
BasePart: "BasePart",
|
|
64
62
|
Part: "Part",
|
|
@@ -68,21 +66,9 @@ const LUAU_TYPE = {
|
|
|
68
66
|
Workspace: "Workspace",
|
|
69
67
|
RunService: "RunService",
|
|
70
68
|
Players: "Players",
|
|
71
|
-
// Luau numeric arrays — kept as {number} in Luau
|
|
72
69
|
};
|
|
73
|
-
// Global sidecar: outLuauPath → list of function annotations for that file
|
|
74
70
|
const sidecar = new Map();
|
|
75
71
|
let hooked = false;
|
|
76
|
-
function luauTypeForTsType(ts, checker, node) {
|
|
77
|
-
if (node.type) {
|
|
78
|
-
const mapped = mapTypeNode(ts, node.type);
|
|
79
|
-
if (mapped)
|
|
80
|
-
return mapped;
|
|
81
|
-
}
|
|
82
|
-
const type = checker.getTypeAtLocation(node);
|
|
83
|
-
const name = checker.typeToString(type);
|
|
84
|
-
return LUAU_TYPE[name] ?? null;
|
|
85
|
-
}
|
|
86
72
|
function mapTypeNode(ts, typeNode) {
|
|
87
73
|
if (ts.isTypeReferenceNode(typeNode)) {
|
|
88
74
|
const name = ts.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : null;
|
|
@@ -90,7 +76,6 @@ function mapTypeNode(ts, typeNode) {
|
|
|
90
76
|
return null;
|
|
91
77
|
if (LUAU_TYPE[name])
|
|
92
78
|
return LUAU_TYPE[name];
|
|
93
|
-
// Array<T> → {T}
|
|
94
79
|
if ((name === "Array" || name === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
|
|
95
80
|
const inner = mapTypeNode(ts, typeNode.typeArguments[0]);
|
|
96
81
|
return inner ? `{${inner}}` : "{any}";
|
|
@@ -110,22 +95,40 @@ function mapTypeNode(ts, typeNode) {
|
|
|
110
95
|
return kw[typeNode.kind];
|
|
111
96
|
return null;
|
|
112
97
|
}
|
|
98
|
+
function luauTypeForParam(ts, checker, node) {
|
|
99
|
+
if (node.type) {
|
|
100
|
+
const mapped = mapTypeNode(ts, node.type);
|
|
101
|
+
if (mapped)
|
|
102
|
+
return mapped;
|
|
103
|
+
}
|
|
104
|
+
const name = checker.typeToString(checker.getTypeAtLocation(node));
|
|
105
|
+
return LUAU_TYPE[name] ?? null;
|
|
106
|
+
}
|
|
107
|
+
function luauTypeForReturn(ts, checker, node) {
|
|
108
|
+
if (node.type) {
|
|
109
|
+
const mapped = mapTypeNode(ts, node.type);
|
|
110
|
+
if (mapped)
|
|
111
|
+
return mapped;
|
|
112
|
+
}
|
|
113
|
+
const sig = checker.getSignatureFromDeclaration(node);
|
|
114
|
+
if (!sig)
|
|
115
|
+
return null;
|
|
116
|
+
const ret = checker.getReturnTypeOfSignature(sig);
|
|
117
|
+
const name = checker.typeToString(ret);
|
|
118
|
+
return LUAU_TYPE[name] ?? null;
|
|
119
|
+
}
|
|
113
120
|
function outPathForSource(sourceFile, program) {
|
|
114
121
|
const options = program.getCompilerOptions();
|
|
115
122
|
const outDir = options.outDir;
|
|
116
123
|
if (!outDir)
|
|
117
124
|
return null;
|
|
118
|
-
|
|
119
|
-
const rootDir = options.rootDir
|
|
120
|
-
?? commonRoot(program.getRootFileNames());
|
|
125
|
+
const rootDir = options.rootDir ?? commonRoot(program.getRootFileNames());
|
|
121
126
|
if (!rootDir)
|
|
122
127
|
return null;
|
|
123
128
|
const rel = path.relative(rootDir, sourceFile.fileName);
|
|
124
129
|
if (rel.startsWith(".."))
|
|
125
130
|
return null;
|
|
126
|
-
|
|
127
|
-
const luauRel = rel.replace(/\.tsx?$/, ".luau");
|
|
128
|
-
return path.join(outDir, luauRel);
|
|
131
|
+
return path.join(outDir, rel.replace(/\.tsx?$/, ".luau"));
|
|
129
132
|
}
|
|
130
133
|
function commonRoot(files) {
|
|
131
134
|
if (files.length === 0)
|
|
@@ -142,45 +145,239 @@ function commonRoot(files) {
|
|
|
142
145
|
return root.join(path.sep) || undefined;
|
|
143
146
|
}
|
|
144
147
|
function collectAnnotations(ts, checker, sourceFile, outPath) {
|
|
145
|
-
const
|
|
146
|
-
sidecar.set(outPath,
|
|
148
|
+
const entry = sidecar.get(outPath) ?? { fns: new Map(), consts: new Set() };
|
|
149
|
+
sidecar.set(outPath, entry);
|
|
147
150
|
function visit(node) {
|
|
148
151
|
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
if (params.some(p => p !== null)) {
|
|
152
|
-
|
|
152
|
+
const params = node.parameters.map(p => luauTypeForParam(ts, checker, p));
|
|
153
|
+
const ret = luauTypeForReturn(ts, checker, node);
|
|
154
|
+
if (params.some(p => p !== null) || ret !== null) {
|
|
155
|
+
entry.fns.set(node.name.text, { params, ret });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (ts.isVariableStatement(node)) {
|
|
159
|
+
const isConst = (node.declarationList.flags & ts.NodeFlags.Const) !== 0;
|
|
160
|
+
if (isConst) {
|
|
161
|
+
for (const decl of node.declarationList.declarations) {
|
|
162
|
+
if (ts.isIdentifier(decl.name)) {
|
|
163
|
+
entry.consts.add(decl.name.text);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
168
|
ts.forEachChild(node, visit);
|
|
156
169
|
}
|
|
157
170
|
visit(sourceFile);
|
|
158
171
|
}
|
|
159
|
-
function
|
|
172
|
+
function byLengthDesc(a, b) {
|
|
173
|
+
return b.length - a.length;
|
|
174
|
+
}
|
|
175
|
+
function organizePreamble(src) {
|
|
176
|
+
const lines = src.split("\n");
|
|
177
|
+
let i = 0;
|
|
178
|
+
// Collect --! directives separately from other leading comments
|
|
179
|
+
const shebang = [];
|
|
180
|
+
const header = [];
|
|
181
|
+
while (i < lines.length && lines[i].startsWith("--")) {
|
|
182
|
+
if (lines[i].startsWith("--!")) {
|
|
183
|
+
shebang.push(lines[i++]);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
header.push(lines[i++]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
shebang.sort(byLengthDesc);
|
|
190
|
+
const services = [];
|
|
191
|
+
const runtime = [];
|
|
192
|
+
const importGroups = [];
|
|
193
|
+
const bindings = [];
|
|
194
|
+
let pendingLabel = null;
|
|
195
|
+
let currentImports = [];
|
|
196
|
+
function flushImports() {
|
|
197
|
+
if (currentImports.length > 0) {
|
|
198
|
+
importGroups.push({ label: pendingLabel ?? "-- Imports", lines: [...currentImports] });
|
|
199
|
+
currentImports = [];
|
|
200
|
+
}
|
|
201
|
+
pendingLabel = null;
|
|
202
|
+
}
|
|
203
|
+
while (i < lines.length) {
|
|
204
|
+
const line = lines[i];
|
|
205
|
+
if (line.trim() === "") {
|
|
206
|
+
// Blank line = end of current import group
|
|
207
|
+
flushImports();
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (/^--!/.test(line)) {
|
|
212
|
+
// Rotor can emit --!native after preamble locals — hoist it up
|
|
213
|
+
shebang.push(line);
|
|
214
|
+
shebang.sort(byLengthDesc);
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
else if (/^--/.test(line)) {
|
|
218
|
+
// User comment becomes the label for the next import group
|
|
219
|
+
flushImports();
|
|
220
|
+
pendingLabel = line;
|
|
221
|
+
i++;
|
|
222
|
+
}
|
|
223
|
+
else if (/^local \w+ = game:GetService\(/.test(line)) {
|
|
224
|
+
flushImports();
|
|
225
|
+
services.push(line);
|
|
226
|
+
i++;
|
|
227
|
+
}
|
|
228
|
+
else if (/^local \w+ = require\(/.test(line)) {
|
|
229
|
+
flushImports();
|
|
230
|
+
runtime.push(line);
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
else if (/^local \w+ = TS\.import\(/.test(line)) {
|
|
234
|
+
currentImports.push(line);
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
else if (/^local \w+ = \w+[\.\[]/.test(line) && !/^local function/.test(line)) {
|
|
238
|
+
flushImports();
|
|
239
|
+
bindings.push(line);
|
|
240
|
+
i++;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
flushImports();
|
|
247
|
+
services.sort(byLengthDesc);
|
|
248
|
+
bindings.sort(byLengthDesc);
|
|
249
|
+
const out = [...shebang];
|
|
250
|
+
if (header.length > 0)
|
|
251
|
+
out.push("", ...header);
|
|
252
|
+
if (services.length > 0)
|
|
253
|
+
out.push("", "-- Services", ...services);
|
|
254
|
+
if (runtime.length > 0)
|
|
255
|
+
out.push("", "-- Runtime", ...runtime);
|
|
256
|
+
for (const group of importGroups) {
|
|
257
|
+
group.lines.sort(byLengthDesc);
|
|
258
|
+
out.push("", group.label, ...group.lines);
|
|
259
|
+
}
|
|
260
|
+
if (bindings.length > 0)
|
|
261
|
+
out.push("", "-- Bindings", ...bindings);
|
|
262
|
+
if (i < lines.length)
|
|
263
|
+
out.push("", ...lines.slice(i));
|
|
264
|
+
return out.join("\n");
|
|
265
|
+
}
|
|
266
|
+
function hoistGetService(src) {
|
|
267
|
+
// Count occurrences of each game:GetService("X") call
|
|
268
|
+
const re = /game:GetService\("([^"]+)"\)/g;
|
|
269
|
+
const counts = new Map();
|
|
270
|
+
for (const m of src.matchAll(re)) {
|
|
271
|
+
counts.set(m[1], (counts.get(m[1]) ?? 0) + 1);
|
|
272
|
+
}
|
|
273
|
+
const toHoist = [...counts.entries()].filter(([, n]) => n >= 2).map(([svc]) => svc);
|
|
274
|
+
if (toHoist.length === 0)
|
|
275
|
+
return src;
|
|
276
|
+
// Build locals and replace
|
|
277
|
+
const decls = toHoist
|
|
278
|
+
.map(svc => `local _${svc} = game:GetService("${svc}")`)
|
|
279
|
+
.join("\n");
|
|
280
|
+
for (const svc of toHoist) {
|
|
281
|
+
src = src.split(`game:GetService("${svc}")`).join(`_${svc}`);
|
|
282
|
+
}
|
|
283
|
+
// Insert after any leading --! directives and the rotor header comment
|
|
284
|
+
const insertAt = src.search(/^(?!--[!\s]|--\s*Compiled)/m);
|
|
285
|
+
if (insertAt === -1)
|
|
286
|
+
return decls + "\n" + src;
|
|
287
|
+
return src.slice(0, insertAt) + decls + "\n" + src.slice(insertAt);
|
|
288
|
+
}
|
|
289
|
+
function addSpacing(src) {
|
|
290
|
+
const lines = src.split("\n");
|
|
291
|
+
const out = [];
|
|
292
|
+
for (let i = 0; i < lines.length; i++) {
|
|
293
|
+
const line = lines[i];
|
|
294
|
+
const trimmed = line.trim();
|
|
295
|
+
const prevOut = out.length > 0 ? out[out.length - 1] : "";
|
|
296
|
+
const prevTrimmed = prevOut.trim();
|
|
297
|
+
const alreadyBlank = prevTrimmed === "";
|
|
298
|
+
if (!alreadyBlank) {
|
|
299
|
+
// Blank before top-level local function
|
|
300
|
+
if (/^local function /.test(trimmed)) {
|
|
301
|
+
out.push("");
|
|
302
|
+
}
|
|
303
|
+
// Blank before return when it's not the first statement in its block
|
|
304
|
+
else if (/^return\b/.test(trimmed) &&
|
|
305
|
+
!/\b(then|do|repeat)$/.test(prevTrimmed) &&
|
|
306
|
+
!/function\s*\([^)]*\)$/.test(prevTrimmed) &&
|
|
307
|
+
!/^local function /.test(prevTrimmed)) {
|
|
308
|
+
out.push("");
|
|
309
|
+
}
|
|
310
|
+
// Blank before a block starter (do/while/for/if/repeat) when prev is local/const
|
|
311
|
+
else if (/^(do\b|while |for |if |repeat\b)/.test(trimmed) && /^(local |const )/.test(prevTrimmed)) {
|
|
312
|
+
out.push("");
|
|
313
|
+
}
|
|
314
|
+
// Blank on const → local transition
|
|
315
|
+
else if (/^local /.test(trimmed) && /^const /.test(prevTrimmed)) {
|
|
316
|
+
out.push("");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
out.push(line);
|
|
320
|
+
// Blank after `end` when next non-blank line is not end/else/elseif/until
|
|
321
|
+
if (trimmed === "end") {
|
|
322
|
+
const next = lines[i + 1]?.trim() ?? "";
|
|
323
|
+
if (next !== "" && !/^(end\b|else\b|elseif\b|until\b)/.test(next)) {
|
|
324
|
+
out.push("");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return out.join("\n");
|
|
329
|
+
}
|
|
330
|
+
function injectAnnotations(luauPath, entry) {
|
|
160
331
|
if (!fs.existsSync(luauPath))
|
|
161
332
|
return;
|
|
162
333
|
let src = fs.readFileSync(luauPath, "utf8");
|
|
163
334
|
let changed = false;
|
|
164
|
-
|
|
165
|
-
|
|
335
|
+
// Inject param + return type annotations
|
|
336
|
+
for (const [fnName, ann] of entry.fns) {
|
|
337
|
+
if (ann.params.every(p => p === null) && ann.ret === null)
|
|
166
338
|
continue;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const re = new RegExp(`(local function ${escapeRegex(fnName)}\\()([^)]*)(\\.\\.\\.\\))?\\)`);
|
|
170
|
-
src = src.replace(re, (_match, open, rawParams, vararg) => {
|
|
339
|
+
const re = new RegExp(`(local function ${escapeRegex(fnName)}\\()([^)]*)(\\.\\.\\.)?(\\))`);
|
|
340
|
+
src = src.replace(re, (_m, open, rawParams, vararg, close) => {
|
|
171
341
|
const names = rawParams.split(",").map((s) => s.trim()).filter(Boolean);
|
|
172
342
|
const annotated = names.map((name, i) => {
|
|
173
|
-
// strip any existing annotation
|
|
174
343
|
const bare = name.split(":")[0].trim();
|
|
175
344
|
const typ = ann.params[i];
|
|
176
345
|
return typ ? `${bare}: ${typ}` : bare;
|
|
177
346
|
});
|
|
178
347
|
if (vararg)
|
|
179
348
|
annotated.push("...");
|
|
349
|
+
const retSuffix = ann.ret ? `: ${ann.ret}` : "";
|
|
180
350
|
changed = true;
|
|
181
|
-
return `${open}${annotated.join(", ")}
|
|
351
|
+
return `${open}${annotated.join(", ")}${close}${retSuffix}`;
|
|
182
352
|
});
|
|
183
353
|
}
|
|
354
|
+
// Replace local → const for TypeScript const declarations
|
|
355
|
+
for (const name of entry.consts) {
|
|
356
|
+
const re = new RegExp(`^(\\t*)local (${escapeRegex(name)}) =`, "m");
|
|
357
|
+
const next = src.replace(re, `$1const $2 =`);
|
|
358
|
+
if (next !== src) {
|
|
359
|
+
src = next;
|
|
360
|
+
changed = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Hoist any repeated game:GetService() calls injected by the compiler
|
|
364
|
+
const hoisted = hoistGetService(src);
|
|
365
|
+
if (hoisted !== src) {
|
|
366
|
+
src = hoisted;
|
|
367
|
+
changed = true;
|
|
368
|
+
}
|
|
369
|
+
// Organize preamble into labeled sections
|
|
370
|
+
const organized = organizePreamble(src);
|
|
371
|
+
if (organized !== src) {
|
|
372
|
+
src = organized;
|
|
373
|
+
changed = true;
|
|
374
|
+
}
|
|
375
|
+
// Add blank lines between top-level blocks for readability
|
|
376
|
+
const spaced = addSpacing(src);
|
|
377
|
+
if (spaced !== src) {
|
|
378
|
+
src = spaced;
|
|
379
|
+
changed = true;
|
|
380
|
+
}
|
|
184
381
|
if (changed)
|
|
185
382
|
fs.writeFileSync(luauPath, src, "utf8");
|
|
186
383
|
}
|
|
@@ -198,16 +395,14 @@ function installWatcher(outDir) {
|
|
|
198
395
|
const full = path.join(outDir, filename);
|
|
199
396
|
if (seen.has(full))
|
|
200
397
|
return;
|
|
201
|
-
const
|
|
202
|
-
if (!
|
|
398
|
+
const entry = sidecar.get(full);
|
|
399
|
+
if (!entry)
|
|
203
400
|
return;
|
|
204
401
|
seen.add(full);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
injectAnnotations(full, fileMap);
|
|
208
|
-
}
|
|
209
|
-
catch { /* ignore */ }
|
|
402
|
+
try {
|
|
403
|
+
injectAnnotations(full, entry);
|
|
210
404
|
}
|
|
405
|
+
catch { /* ignore */ }
|
|
211
406
|
});
|
|
212
407
|
watcher.unref();
|
|
213
408
|
}
|
|
@@ -215,8 +410,6 @@ function annotatePass(ts, program, sourceFile) {
|
|
|
215
410
|
const outPath = outPathForSource(sourceFile, program);
|
|
216
411
|
if (!outPath)
|
|
217
412
|
return;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const outDir = program.getCompilerOptions().outDir;
|
|
221
|
-
installWatcher(outDir);
|
|
413
|
+
collectAnnotations(ts, program.getTypeChecker(), sourceFile, outPath);
|
|
414
|
+
installWatcher(program.getCompilerOptions().outDir);
|
|
222
415
|
}
|
package/dist/passes/native.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type ts from "typescript";
|
|
2
|
-
export declare function nativePass(ts: typeof import("typescript"), ctx: ts.TransformationContext, sourceFile: ts.SourceFile): ts.SourceFile;
|
|
2
|
+
export declare function nativePass(ts: typeof import("typescript"), ctx: ts.TransformationContext, sourceFile: ts.SourceFile, optimize: boolean, strict: boolean): ts.SourceFile;
|
package/dist/passes/native.js
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nativePass = nativePass;
|
|
4
4
|
const util_1 = require("../util");
|
|
5
|
-
function nativePass(ts, ctx, sourceFile) {
|
|
6
|
-
if ((0, util_1.hasOptimizeDirective)(sourceFile))
|
|
7
|
-
return sourceFile;
|
|
5
|
+
function nativePass(ts, ctx, sourceFile, optimize, strict) {
|
|
8
6
|
const factory = ctx.factory;
|
|
9
|
-
const
|
|
10
|
-
|
|
7
|
+
const prepend = [];
|
|
8
|
+
if (strict && !(0, util_1.hasStrictDirective)(sourceFile)) {
|
|
9
|
+
prepend.push(ts.addSyntheticLeadingComment(factory.createNotEmittedStatement(sourceFile), ts.SyntaxKind.SingleLineCommentTrivia, "!strict", true));
|
|
10
|
+
}
|
|
11
|
+
if (optimize && !(0, util_1.hasOptimizeDirective)(sourceFile)) {
|
|
12
|
+
prepend.push(ts.addSyntheticLeadingComment(factory.createNotEmittedStatement(sourceFile), ts.SyntaxKind.SingleLineCommentTrivia, "!optimize 2", true));
|
|
13
|
+
}
|
|
14
|
+
if (prepend.length === 0)
|
|
15
|
+
return sourceFile;
|
|
16
|
+
return factory.updateSourceFile(sourceFile, [...prepend, ...Array.from(sourceFile.statements)]);
|
|
11
17
|
}
|
package/dist/util.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type ts from "typescript";
|
|
2
2
|
export declare function hasOptimizeDirective(sourceFile: ts.SourceFile): boolean;
|
|
3
|
+
export declare function hasStrictDirective(sourceFile: ts.SourceFile): boolean;
|
|
3
4
|
export declare function chainKey(ts: typeof import("typescript"), node: ts.Expression): string | undefined;
|
|
4
5
|
export declare function walk(ts: typeof import("typescript"), node: ts.Node, visitor: (n: ts.Node) => void): void;
|
|
5
6
|
export declare function isAssignmentTarget(ts: typeof import("typescript"), node: ts.Node): boolean;
|
package/dist/util.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hasOptimizeDirective = hasOptimizeDirective;
|
|
4
|
+
exports.hasStrictDirective = hasStrictDirective;
|
|
4
5
|
exports.chainKey = chainKey;
|
|
5
6
|
exports.walk = walk;
|
|
6
7
|
exports.isAssignmentTarget = isAssignmentTarget;
|
|
7
8
|
function hasOptimizeDirective(sourceFile) {
|
|
8
9
|
return /^--!optimize\b/m.test(sourceFile.text) || /^\/\/!optimize\b/m.test(sourceFile.text);
|
|
9
10
|
}
|
|
11
|
+
function hasStrictDirective(sourceFile) {
|
|
12
|
+
return /^--!strict\b/m.test(sourceFile.text) || /^\/\/!strict\b/m.test(sourceFile.text);
|
|
13
|
+
}
|
|
10
14
|
function chainKey(ts, node) {
|
|
11
15
|
if (ts.isIdentifier(node))
|
|
12
16
|
return node.text;
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rbxts-transform-boost",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "roblox-ts transformer: automatic --!native, GetService hoisting, property chain caching, loop bounds hoisting, and Luau type annotation injection",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
7
11
|
"scripts": {
|
|
8
12
|
"build": "tsc",
|
|
9
|
-
"dev": "tsc --watch",
|
|
10
13
|
"bench:build": "npm run build && cd bench && ~/.rokit/bin/rotor build",
|
|
11
14
|
"bench:build:baseline": "cd bench && ~/.rokit/bin/rotor build -p tsconfig.notransform.json -i out/include",
|
|
12
|
-
"bench:
|
|
13
|
-
"bench:rbxlx": "npm run bench:build && npm run bench:build:baseline && cd bench && ~/.rokit/bin/rojo build benchmark.project.json -o benchmark.rbxlx"
|
|
15
|
+
"bench:rbxlx": "npm run bench:build && npm run bench:build:baseline && cd bench && ~/.rokit/bin/rojo build default.project.json -o benchmark.rbxlx"
|
|
14
16
|
},
|
|
15
17
|
"keywords": [
|
|
16
18
|
"roblox-ts",
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
"typescript": ">=5.0.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^26.0.0"
|
|
29
|
+
"@types/node": "^26.0.0",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
28
31
|
}
|
|
29
32
|
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- "v*"
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
publish:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: 20
|
|
18
|
-
registry-url: https://registry.npmjs.org
|
|
19
|
-
|
|
20
|
-
- run: npm ci
|
|
21
|
-
|
|
22
|
-
- run: npm run build
|
|
23
|
-
|
|
24
|
-
- run: npm publish --access public
|
|
25
|
-
env:
|
|
26
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "perf-benchmark",
|
|
3
|
-
"emitLegacyScripts": false,
|
|
4
|
-
"tree": {
|
|
5
|
-
"$className": "DataModel",
|
|
6
|
-
"ServerScriptService": {
|
|
7
|
-
"$className": "ServerScriptService",
|
|
8
|
-
"$path": "out/src/server"
|
|
9
|
-
},
|
|
10
|
-
"ReplicatedStorage": {
|
|
11
|
-
"$className": "ReplicatedStorage",
|
|
12
|
-
"shared": {
|
|
13
|
-
"$path": "out/src/shared"
|
|
14
|
-
},
|
|
15
|
-
"include": {
|
|
16
|
-
"$path": "out/include"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
package/bench/package-lock.json
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "rbxts-transform-perf-test",
|
|
3
|
-
"lockfileVersion": 3,
|
|
4
|
-
"requires": true,
|
|
5
|
-
"packages": {
|
|
6
|
-
"": {
|
|
7
|
-
"name": "rbxts-transform-perf-test",
|
|
8
|
-
"devDependencies": {
|
|
9
|
-
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
10
|
-
"@rbxts/types": "^1.0.908",
|
|
11
|
-
"rbxts-transform-boost": "file:../",
|
|
12
|
-
"typescript": "=5.5.3"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"..": {
|
|
16
|
-
"name": "rbxts-transform-boost",
|
|
17
|
-
"version": "0.1.0",
|
|
18
|
-
"dev": true,
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/node": "^26.0.0"
|
|
21
|
-
},
|
|
22
|
-
"peerDependencies": {
|
|
23
|
-
"typescript": ">=5.0.0"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"node_modules/@rbxts/compiler-types": {
|
|
27
|
-
"version": "3.0.0-types.0",
|
|
28
|
-
"resolved": "https://registry.npmjs.org/@rbxts/compiler-types/-/compiler-types-3.0.0-types.0.tgz",
|
|
29
|
-
"integrity": "sha512-VGOHJPoL7+56NTatMGqQj3K7xWuzEV+aP4QD5vZiHu+bcff3kiTmtoadaF6NkJrmwfFAvbsd4Dg764ZjWNceag==",
|
|
30
|
-
"dev": true,
|
|
31
|
-
"license": "MIT"
|
|
32
|
-
},
|
|
33
|
-
"node_modules/@rbxts/types": {
|
|
34
|
-
"version": "1.0.928",
|
|
35
|
-
"resolved": "https://registry.npmjs.org/@rbxts/types/-/types-1.0.928.tgz",
|
|
36
|
-
"integrity": "sha512-GzmzBNn8fyn0ed9kVQkXUbBXpUg9Gfyf9AZJKT51VGqyrf4w6J0X72eSSGxt6YN2BMHNbqhzHCBn/fM8bdqMTg==",
|
|
37
|
-
"dev": true,
|
|
38
|
-
"license": "MIT"
|
|
39
|
-
},
|
|
40
|
-
"node_modules/rbxts-transform-boost": {
|
|
41
|
-
"resolved": "..",
|
|
42
|
-
"link": true
|
|
43
|
-
},
|
|
44
|
-
"node_modules/typescript": {
|
|
45
|
-
"version": "5.5.3",
|
|
46
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
|
47
|
-
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
|
48
|
-
"dev": true,
|
|
49
|
-
"license": "Apache-2.0",
|
|
50
|
-
"bin": {
|
|
51
|
-
"tsc": "bin/tsc",
|
|
52
|
-
"tsserver": "bin/tsserver"
|
|
53
|
-
},
|
|
54
|
-
"engines": {
|
|
55
|
-
"node": ">=14.17"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
package/bench/package.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "rbxts-transform-perf-test",
|
|
3
|
-
"private": true,
|
|
4
|
-
"scripts": {
|
|
5
|
-
"compile": "lumen src -o out/src -i out/include -p default.project.json",
|
|
6
|
-
"watch": "lumen src -o out/src -i out/include -p default.project.json --watch"
|
|
7
|
-
},
|
|
8
|
-
"devDependencies": {
|
|
9
|
-
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
10
|
-
"@rbxts/types": "^1.0.908",
|
|
11
|
-
"typescript": "=5.5.3",
|
|
12
|
-
"rbxts-transform-boost": "file:../"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import * as opt from "../shared/fns";
|
|
2
|
-
import * as base from "../shared/fns-bare";
|
|
3
|
-
|
|
4
|
-
function bench(label: string, n: number, fn: () => void): void {
|
|
5
|
-
task.wait(0.05);
|
|
6
|
-
const t0 = os.clock();
|
|
7
|
-
for (let i = 0; i < n; i++) fn();
|
|
8
|
-
const elapsed = os.clock() - t0;
|
|
9
|
-
print(` ${label}: ${string.format("%.3f", (elapsed / n) * 1e6)} us/iter`);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const N = 100000;
|
|
13
|
-
const NS = 10000;
|
|
14
|
-
|
|
15
|
-
const pos = new Vector3(1, 2, 3);
|
|
16
|
-
const vel = new Vector3(0, 1, 0);
|
|
17
|
-
const acc = new Vector3(0, -9.8, 0);
|
|
18
|
-
const vecA = new Vector3(1, 0, 0);
|
|
19
|
-
const vecB = new Vector3(0, 1, 0);
|
|
20
|
-
const eye = new Vector3(0, 5, 10);
|
|
21
|
-
const tgt = new Vector3(0, 0, 0);
|
|
22
|
-
const cf = CFrame.lookAt(eye, tgt);
|
|
23
|
-
const buf = buffer.create(256);
|
|
24
|
-
const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
25
|
-
const wts = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
|
26
|
-
const cam = game.GetService("Workspace").CurrentCamera!;
|
|
27
|
-
|
|
28
|
-
function runSuite(fns: typeof opt): void {
|
|
29
|
-
bench("integrate (verlet)", N, () => fns.integrate(pos, vel, acc, 1 / 60));
|
|
30
|
-
bench("dot (V3 manual)", N, () => fns.dot(vecA, vecB));
|
|
31
|
-
bench("cross (V3 manual)", N, () => fns.cross(vecA, vecB));
|
|
32
|
-
bench("lerpVec3 (V3 manual)", N, () => fns.lerpVec3(pos, eye, 0.5));
|
|
33
|
-
bench("encodeFixed (buf+math)", N, () => fns.encodeFixed(buf, 0, 3.14, 100));
|
|
34
|
-
bench("encodePacket(3x fixed)", N, () => fns.encodePacket(buf, 1.1, 2.2, 3.3, 100));
|
|
35
|
-
bench("sumWeighted (loop)", N, () => fns.sumWeighted(vals, wts));
|
|
36
|
-
bench("dotProduct (loop)", N, () => fns.dotProduct(vals, vals));
|
|
37
|
-
bench("norm (loop+sqrt)", N, () => fns.norm(vals));
|
|
38
|
-
bench("mathHeavy (trig+sqrt)", N, () => fns.mathHeavy(1.23, 4.56));
|
|
39
|
-
bench("fib(20) (iter)", N, () => fns.fib(20));
|
|
40
|
-
bench("cfLookAt (ctor)", NS, () => fns.cfLookAt(eye, tgt));
|
|
41
|
-
bench("cfChain (mul+angles)", N, () => fns.cfChain(cf, 0.016));
|
|
42
|
-
bench("serviceWork (GetService x2)", N, () => fns.serviceWork());
|
|
43
|
-
bench("multiSvc (GetService x3)", N, () => fns.multiService());
|
|
44
|
-
bench("cameraWork (prop chain)", N, () => fns.cameraWork(cam));
|
|
45
|
-
bench("formatStats (template)", N, () => fns.formatStats("speed", 9.81, "m/s"));
|
|
46
|
-
bench("buildKey (template)", N, () => fns.buildKey("player", 42, "data"));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
print("\n=== optimized (--!native + transformer) ===");
|
|
50
|
-
runSuite(opt);
|
|
51
|
-
print("===========================================\n");
|
|
52
|
-
|
|
53
|
-
task.wait(1);
|
|
54
|
-
|
|
55
|
-
print("\n=== baseline (plain rotor, no transformer) ===");
|
|
56
|
-
runSuite(base);
|
|
57
|
-
print("==============================================\n");
|