rbxts-transform-boost 0.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.
@@ -0,0 +1,127 @@
1
+ // ── Vector3 / physics ────────────────────────────────────────────────────────
2
+
3
+ export function integrate(pos: Vector3, vel: Vector3, acc: Vector3, dt: number): [Vector3, Vector3] {
4
+ const newVel = vel.add(acc.mul(dt));
5
+ const newPos = pos.add(newVel.mul(dt));
6
+ return [newPos, newVel];
7
+ }
8
+
9
+ export function dot(a: Vector3, b: Vector3): number {
10
+ return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
11
+ }
12
+
13
+ export function cross(a: Vector3, b: Vector3): Vector3 {
14
+ return new Vector3(
15
+ a.Y * b.Z - a.Z * b.Y,
16
+ a.Z * b.X - a.X * b.Z,
17
+ a.X * b.Y - a.Y * b.X,
18
+ );
19
+ }
20
+
21
+ export function lerpVec3(a: Vector3, b: Vector3, t: number): Vector3 {
22
+ return new Vector3(
23
+ a.X + (b.X - a.X) * t,
24
+ a.Y + (b.Y - a.Y) * t,
25
+ a.Z + (b.Z - a.Z) * t,
26
+ );
27
+ }
28
+
29
+ // ── Buffer / encoding ────────────────────────────────────────────────────────
30
+
31
+ export function encodeFixed(buf: buffer, offset: number, value: number, scale: number): number {
32
+ const fixed = math.floor(value * scale);
33
+ const clamped = math.clamp(fixed, -32768, 32767);
34
+ buffer.writei16(buf, offset, clamped);
35
+ return offset + 2;
36
+ }
37
+
38
+ export function encodePacket(buf: buffer, x: number, y: number, z: number, scale: number): void {
39
+ let off = 0;
40
+ off = encodeFixed(buf, off, x, scale);
41
+ off = encodeFixed(buf, off, y, scale);
42
+ encodeFixed(buf, off, z, scale);
43
+ }
44
+
45
+ // ── Math / arithmetic ────────────────────────────────────────────────────────
46
+
47
+ export function sumWeighted(values: Array<number>, weights: Array<number>): number {
48
+ let total = 0;
49
+ for (let i = 0; i < values.size(); i++) {
50
+ total += values[i] * weights[i];
51
+ }
52
+ return total;
53
+ }
54
+
55
+ export function dotProduct(a: Array<number>, b: Array<number>): number {
56
+ let sum = 0;
57
+ for (let i = 0; i < a.size(); i++) {
58
+ sum += a[i] * b[i];
59
+ }
60
+ return sum;
61
+ }
62
+
63
+ export function norm(values: Array<number>): number {
64
+ let sq = 0;
65
+ for (let i = 0; i < values.size(); i++) {
66
+ sq += values[i] * values[i];
67
+ }
68
+ return math.sqrt(sq);
69
+ }
70
+
71
+ export function mathHeavy(x: number, y: number): number {
72
+ return math.sin(x) * math.cos(y) + math.sqrt(x * x + y * y) + math.atan2(y, x);
73
+ }
74
+
75
+ export function fib(n: number): number {
76
+ if (n <= 1) return n;
77
+ let a = 0;
78
+ let b = 1;
79
+ for (let i = 2; i <= n; i++) {
80
+ const tmp = a + b;
81
+ a = b;
82
+ b = tmp;
83
+ }
84
+ return b;
85
+ }
86
+
87
+ // ── CFrame ───────────────────────────────────────────────────────────────────
88
+
89
+ export function cfLookAt(eye: Vector3, target: Vector3): CFrame {
90
+ return CFrame.lookAt(eye, target);
91
+ }
92
+
93
+ export function cfChain(cf: CFrame, dt: number): CFrame {
94
+ return cf.mul(CFrame.Angles(0, dt, 0));
95
+ }
96
+
97
+ // ── Services / instance ──────────────────────────────────────────────────────
98
+
99
+ export function serviceWork(): string {
100
+ const count = game.GetService("Players").GetPlayers().size();
101
+ const running = game.GetService("RunService").IsRunning();
102
+ return `${count}-${running}`;
103
+ }
104
+
105
+ export function multiService(): boolean {
106
+ const players = game.GetService("Players");
107
+ const rs = game.GetService("RunService");
108
+ const ws = game.GetService("Workspace");
109
+ return rs.IsRunning() && players.MaxPlayers > 0 && ws.Gravity > 0;
110
+ }
111
+
112
+ export function cameraWork(camera: Camera): number {
113
+ const pos = camera.CFrame.Position;
114
+ const look = camera.CFrame.LookVector;
115
+ const fov = camera.FieldOfView;
116
+ return pos.Magnitude + look.X + fov;
117
+ }
118
+
119
+ // ── String ───────────────────────────────────────────────────────────────────
120
+
121
+ export function formatStats(label: string, value: number, unit: string): string {
122
+ return `${label}: ${string.format("%.4f", value)} ${unit}`;
123
+ }
124
+
125
+ export function buildKey(prefix: string, id: number, suffix: string): string {
126
+ return `${prefix}_${id}_${suffix}`;
127
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "commonjs",
5
+ "moduleDetection": "force",
6
+ "moduleResolution": "Node",
7
+ "strict": true,
8
+ "noLib": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "rootDir": "./src",
13
+ "outDir": "./out/src",
14
+ "typeRoots": [
15
+ "./node_modules/@rbxts"
16
+ ],
17
+ "plugins": [
18
+ {
19
+ "transform": "rbxts-transform-boost",
20
+ "optimize": true,
21
+ "hoist": true
22
+ }
23
+ ]
24
+ },
25
+ "include": [
26
+ "./src/server",
27
+ "./src/shared/fns.ts"
28
+ ],
29
+ "rbxts": {
30
+ "includePath": "out/include"
31
+ }
32
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "commonjs",
5
+ "moduleDetection": "force",
6
+ "moduleResolution": "Node",
7
+ "strict": true,
8
+ "noLib": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "rootDir": "./src",
13
+ "outDir": "./out/src",
14
+ "typeRoots": [
15
+ "./node_modules/@rbxts"
16
+ ],
17
+ },
18
+ "include": [
19
+ "./src/shared/fns-bare.ts"
20
+ ],
21
+ "rbxts": {
22
+ "includePath": "out/include"
23
+ }
24
+ }
@@ -0,0 +1,4 @@
1
+ export interface PluginConfig {
2
+ optimize?: boolean;
3
+ hoist?: boolean;
4
+ }
package/dist/config.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import type ts from "typescript";
2
+ import type { PluginConfig } from "./config";
3
+ export type { PluginConfig };
4
+ export default function (program: ts.Program, config: PluginConfig | undefined, { ts }: {
5
+ ts: typeof import("typescript");
6
+ }): ts.TransformerFactory<ts.SourceFile>;
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const native_1 = require("./passes/native");
5
+ const cache_1 = require("./passes/cache");
6
+ const loops_1 = require("./passes/loops");
7
+ const annotate_1 = require("./passes/annotate");
8
+ function default_1(program, config = {}, { ts }) {
9
+ const { optimize = true, hoist = true } = config;
10
+ return (ctx) => (sourceFile) => {
11
+ (0, annotate_1.annotatePass)(ts, program, sourceFile);
12
+ let result = sourceFile;
13
+ if (hoist)
14
+ result = (0, cache_1.cachePass)(ts, program, ctx, result);
15
+ result = (0, loops_1.loopsPass)(ts, program, ctx, result);
16
+ if (optimize)
17
+ result = (0, native_1.nativePass)(ts, ctx, result);
18
+ return result;
19
+ };
20
+ }
@@ -0,0 +1,2 @@
1
+ import type ts from "typescript";
2
+ export declare function annotatePass(ts: typeof import("typescript"), program: ts.Program, sourceFile: ts.SourceFile): void;
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.annotatePass = annotatePass;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ // Luau type names for TypeScript type strings rotor knows about
40
+ const LUAU_TYPE = {
41
+ number: "number",
42
+ string: "string",
43
+ boolean: "boolean",
44
+ Vector3: "Vector3",
45
+ Vector2: "Vector2",
46
+ Vector2int16: "Vector2int16",
47
+ Vector3int16: "Vector3int16",
48
+ CFrame: "CFrame",
49
+ UDim: "UDim",
50
+ UDim2: "UDim2",
51
+ Color3: "Color3",
52
+ BrickColor: "BrickColor",
53
+ TweenInfo: "TweenInfo",
54
+ NumberRange: "NumberRange",
55
+ NumberSequence: "NumberSequence",
56
+ ColorSequence: "ColorSequence",
57
+ Rect: "Rect",
58
+ Region3: "Region3",
59
+ Ray: "Ray",
60
+ buffer: "buffer",
61
+ // Roblox service types
62
+ Instance: "Instance",
63
+ BasePart: "BasePart",
64
+ Part: "Part",
65
+ Model: "Model",
66
+ Player: "Player",
67
+ Camera: "Camera",
68
+ Workspace: "Workspace",
69
+ RunService: "RunService",
70
+ Players: "Players",
71
+ // Luau numeric arrays — kept as {number} in Luau
72
+ };
73
+ // Global sidecar: outLuauPath → list of function annotations for that file
74
+ const sidecar = new Map();
75
+ 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
+ function mapTypeNode(ts, typeNode) {
87
+ if (ts.isTypeReferenceNode(typeNode)) {
88
+ const name = ts.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : null;
89
+ if (!name)
90
+ return null;
91
+ if (LUAU_TYPE[name])
92
+ return LUAU_TYPE[name];
93
+ // Array<T> → {T}
94
+ if ((name === "Array" || name === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
95
+ const inner = mapTypeNode(ts, typeNode.typeArguments[0]);
96
+ return inner ? `{${inner}}` : "{any}";
97
+ }
98
+ return null;
99
+ }
100
+ if (ts.isArrayTypeNode(typeNode)) {
101
+ const inner = mapTypeNode(ts, typeNode.elementType);
102
+ return inner ? `{${inner}}` : "{any}";
103
+ }
104
+ const kw = {
105
+ [ts.SyntaxKind.NumberKeyword]: "number",
106
+ [ts.SyntaxKind.StringKeyword]: "string",
107
+ [ts.SyntaxKind.BooleanKeyword]: "boolean",
108
+ };
109
+ if (typeNode.kind in kw)
110
+ return kw[typeNode.kind];
111
+ return null;
112
+ }
113
+ function outPathForSource(sourceFile, program) {
114
+ const options = program.getCompilerOptions();
115
+ const outDir = options.outDir;
116
+ if (!outDir)
117
+ return null;
118
+ // Compute rootDir: explicit option or the common root of all source files
119
+ const rootDir = options.rootDir
120
+ ?? commonRoot(program.getRootFileNames());
121
+ if (!rootDir)
122
+ return null;
123
+ const rel = path.relative(rootDir, sourceFile.fileName);
124
+ if (rel.startsWith(".."))
125
+ return null;
126
+ // Change .ts / .tsx extension to .luau
127
+ const luauRel = rel.replace(/\.tsx?$/, ".luau");
128
+ return path.join(outDir, luauRel);
129
+ }
130
+ function commonRoot(files) {
131
+ if (files.length === 0)
132
+ return undefined;
133
+ const parts = files[0].split(path.sep);
134
+ let root = parts.slice(0, parts.length - 1);
135
+ for (const f of files.slice(1)) {
136
+ const fp = f.split(path.sep);
137
+ let i = 0;
138
+ while (i < root.length && i < fp.length - 1 && root[i] === fp[i])
139
+ i++;
140
+ root = root.slice(0, i);
141
+ }
142
+ return root.join(path.sep) || undefined;
143
+ }
144
+ function collectAnnotations(ts, checker, sourceFile, outPath) {
145
+ const fileMap = sidecar.get(outPath) ?? new Map();
146
+ sidecar.set(outPath, fileMap);
147
+ function visit(node) {
148
+ if (ts.isFunctionDeclaration(node) && node.name) {
149
+ const fnName = node.name.text;
150
+ const params = node.parameters.map(p => luauTypeForTsType(ts, checker, p));
151
+ if (params.some(p => p !== null)) {
152
+ fileMap.set(fnName, { params });
153
+ }
154
+ }
155
+ ts.forEachChild(node, visit);
156
+ }
157
+ visit(sourceFile);
158
+ }
159
+ function injectAnnotations(luauPath, fileMap) {
160
+ if (!fs.existsSync(luauPath))
161
+ return;
162
+ let src = fs.readFileSync(luauPath, "utf8");
163
+ let changed = false;
164
+ for (const [fnName, ann] of fileMap) {
165
+ if (ann.params.every(p => p === null))
166
+ continue;
167
+ // Match: local function fnName(a, b, c)
168
+ // Captures the param list so we can replace individual names
169
+ const re = new RegExp(`(local function ${escapeRegex(fnName)}\\()([^)]*)(\\.\\.\\.\\))?\\)`);
170
+ src = src.replace(re, (_match, open, rawParams, vararg) => {
171
+ const names = rawParams.split(",").map((s) => s.trim()).filter(Boolean);
172
+ const annotated = names.map((name, i) => {
173
+ // strip any existing annotation
174
+ const bare = name.split(":")[0].trim();
175
+ const typ = ann.params[i];
176
+ return typ ? `${bare}: ${typ}` : bare;
177
+ });
178
+ if (vararg)
179
+ annotated.push("...");
180
+ changed = true;
181
+ return `${open}${annotated.join(", ")})`;
182
+ });
183
+ }
184
+ if (changed)
185
+ fs.writeFileSync(luauPath, src, "utf8");
186
+ }
187
+ function escapeRegex(s) {
188
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
189
+ }
190
+ function installWatcher(outDir) {
191
+ if (hooked)
192
+ return;
193
+ hooked = true;
194
+ const seen = new Set();
195
+ const watcher = fs.watch(outDir, { recursive: true }, (_event, filename) => {
196
+ if (!filename || !filename.endsWith(".luau"))
197
+ return;
198
+ const full = path.join(outDir, filename);
199
+ if (seen.has(full))
200
+ return;
201
+ const fileMap = sidecar.get(full);
202
+ if (!fileMap)
203
+ return;
204
+ seen.add(full);
205
+ if (fileMap.size > 0) {
206
+ try {
207
+ injectAnnotations(full, fileMap);
208
+ }
209
+ catch { /* ignore */ }
210
+ }
211
+ });
212
+ watcher.unref();
213
+ }
214
+ function annotatePass(ts, program, sourceFile) {
215
+ const outPath = outPathForSource(sourceFile, program);
216
+ if (!outPath)
217
+ return;
218
+ const checker = program.getTypeChecker();
219
+ collectAnnotations(ts, checker, sourceFile, outPath);
220
+ const outDir = program.getCompilerOptions().outDir;
221
+ installWatcher(outDir);
222
+ }
@@ -0,0 +1,2 @@
1
+ import type ts from "typescript";
2
+ export declare function cachePass(ts: typeof import("typescript"), _program: ts.Program, ctx: ts.TransformationContext, sourceFile: ts.SourceFile): ts.SourceFile;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cachePass = cachePass;
4
+ const util_1 = require("../util");
5
+ function isGetServiceCall(ts, node) {
6
+ if (!ts.isCallExpression(node))
7
+ return false;
8
+ const expr = node.expression;
9
+ if (!ts.isPropertyAccessExpression(expr))
10
+ return false;
11
+ const obj = expr.expression;
12
+ return ts.isIdentifier(obj) && obj.text === "game" && expr.name.text === "GetService";
13
+ }
14
+ function getServiceName(ts, call) {
15
+ const args = call.arguments;
16
+ if (args.length !== 1)
17
+ return undefined;
18
+ const arg = args[0];
19
+ if (!ts.isStringLiteral(arg))
20
+ return undefined;
21
+ return arg.text;
22
+ }
23
+ function cachePass(ts, _program, ctx, sourceFile) {
24
+ const factory = ctx.factory;
25
+ // --- GetService hoisting ---
26
+ const services = new Map();
27
+ (0, util_1.walk)(ts, sourceFile, node => {
28
+ if (!node || !isGetServiceCall(ts, node))
29
+ return;
30
+ const name = getServiceName(ts, node);
31
+ if (name && !services.has(name))
32
+ services.set(name, `_${name}`);
33
+ });
34
+ const serviceVisitor = (node) => {
35
+ if (isGetServiceCall(ts, node)) {
36
+ const name = getServiceName(ts, node);
37
+ if (name && services.has(name))
38
+ return factory.createIdentifier(services.get(name));
39
+ }
40
+ return ts.visitEachChild(node, serviceVisitor, ctx);
41
+ };
42
+ let result = ts.visitEachChild(sourceFile, serviceVisitor, ctx);
43
+ if (services.size > 0) {
44
+ const hoistDecls = Array.from(services.entries()).map(([name, localName]) => factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(localName), undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("game"), "GetService"), undefined, [factory.createStringLiteral(name)]))], ts.NodeFlags.Const)));
45
+ result = factory.updateSourceFile(result, [...hoistDecls, ...Array.from(result.statements)]);
46
+ }
47
+ // --- Property chain hoisting within functions ---
48
+ result = hoistPropertyChains(ts, result, factory, ctx);
49
+ return result;
50
+ }
51
+ function hoistPropertyChains(ts, sourceFile, factory, ctx) {
52
+ const visitor = (node) => {
53
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) ||
54
+ ts.isArrowFunction(node) || ts.isMethodDeclaration(node)) {
55
+ return hoistInFunction(ts, node, factory, ctx);
56
+ }
57
+ return ts.visitEachChild(node, visitor, ctx);
58
+ };
59
+ return ts.visitEachChild(sourceFile, visitor, ctx);
60
+ }
61
+ function hoistInFunction(ts, fn, factory, ctx) {
62
+ if (!fn.body || !ts.isBlock(fn.body))
63
+ return fn;
64
+ const counts = new Map();
65
+ (0, util_1.walk)(ts, fn.body, node => {
66
+ if (!node || !ts.isPropertyAccessExpression(node))
67
+ return;
68
+ const key = (0, util_1.chainKey)(ts, node);
69
+ if (!key || !key.includes("."))
70
+ return;
71
+ if ((0, util_1.isAssignmentTarget)(ts, node))
72
+ return;
73
+ counts.set(key, (counts.get(key) ?? 0) + 1);
74
+ });
75
+ const toHoist = new Map();
76
+ let counter = 0;
77
+ const candidates = Array.from(counts.entries())
78
+ .filter(([, count]) => count >= 2)
79
+ .sort((a, b) => b[0].length - a[0].length);
80
+ for (const [key] of candidates) {
81
+ const alreadyCovered = Array.from(toHoist.keys()).some(h => h.startsWith(key + "."));
82
+ if (alreadyCovered)
83
+ continue;
84
+ toHoist.set(key, `_cache${counter++}`);
85
+ }
86
+ if (toHoist.size === 0)
87
+ return fn;
88
+ const chainVisitor = (node) => {
89
+ if (ts.isPropertyAccessExpression(node)) {
90
+ const key = (0, util_1.chainKey)(ts, node);
91
+ if (key && toHoist.has(key) && !(0, util_1.isAssignmentTarget)(ts, node)) {
92
+ return factory.createIdentifier(toHoist.get(key));
93
+ }
94
+ }
95
+ return ts.visitEachChild(node, chainVisitor, ctx);
96
+ };
97
+ const newBody = ts.visitEachChild(fn.body, chainVisitor, ctx);
98
+ const hoistStmts = Array.from(toHoist.entries()).map(([key, localName]) => {
99
+ const parts = key.split(".");
100
+ let expr = factory.createIdentifier(parts[0]);
101
+ for (let i = 1; i < parts.length; i++) {
102
+ expr = factory.createPropertyAccessExpression(expr, parts[i]);
103
+ }
104
+ return factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(localName), undefined, undefined, expr)], ts.NodeFlags.Const));
105
+ });
106
+ const updatedBody = factory.updateBlock(newBody, [...hoistStmts, ...Array.from(newBody.statements)]);
107
+ if (ts.isFunctionDeclaration(fn))
108
+ return factory.updateFunctionDeclaration(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.typeParameters, fn.parameters, fn.type, updatedBody);
109
+ if (ts.isFunctionExpression(fn))
110
+ return factory.updateFunctionExpression(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.typeParameters, fn.parameters, fn.type, updatedBody);
111
+ if (ts.isArrowFunction(fn))
112
+ return factory.updateArrowFunction(fn, fn.modifiers, fn.typeParameters, fn.parameters, fn.type, fn.equalsGreaterThanToken, updatedBody);
113
+ if (ts.isMethodDeclaration(fn))
114
+ return factory.updateMethodDeclaration(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.questionToken, fn.typeParameters, fn.parameters, fn.type, updatedBody);
115
+ return fn;
116
+ }
@@ -0,0 +1,2 @@
1
+ import type ts from "typescript";
2
+ export declare function loopsPass(ts: typeof import("typescript"), _program: ts.Program, ctx: ts.TransformationContext, sourceFile: ts.SourceFile): ts.SourceFile;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loopsPass = loopsPass;
4
+ function loopsPass(ts, _program, ctx, sourceFile) {
5
+ const factory = ctx.factory;
6
+ function isArrayLengthExpr(node) {
7
+ // matches: arr.size() or arr.length (if it ever appears)
8
+ if (ts.isCallExpression(node)) {
9
+ const expr = node.expression;
10
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === "size" && node.arguments.length === 0) {
11
+ return expr.expression;
12
+ }
13
+ }
14
+ return undefined;
15
+ }
16
+ function rewriteForLoop(node) {
17
+ // Match: for (let i = 0; i < arr.size(); i++) { ... }
18
+ const { initializer, condition, incrementor, statement } = node;
19
+ if (!initializer || !condition || !incrementor)
20
+ return node;
21
+ if (!ts.isVariableDeclarationList(initializer))
22
+ return node;
23
+ const decls = initializer.declarations;
24
+ if (decls.length !== 1)
25
+ return node;
26
+ const decl = decls[0];
27
+ if (!ts.isIdentifier(decl.name))
28
+ return node;
29
+ if (!decl.initializer || !ts.isNumericLiteral(decl.initializer) || decl.initializer.text !== "0")
30
+ return node;
31
+ if (!ts.isBinaryExpression(condition))
32
+ return node;
33
+ if (condition.operatorToken.kind !== ts.SyntaxKind.LessThanToken)
34
+ return node;
35
+ const arr = isArrayLengthExpr(condition.right);
36
+ if (!arr)
37
+ return node;
38
+ const loopVar = decl.name.text;
39
+ const arrName = ts.isIdentifier(arr) ? arr.text : undefined;
40
+ if (!arrName)
41
+ return node;
42
+ const lenName = `_len_${arrName}`;
43
+ const lenDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(lenName), undefined, factory.createTypeReferenceNode("number"), factory.createCallExpression(factory.createPropertyAccessExpression(arr, "size"), undefined, []))], ts.NodeFlags.Const));
44
+ const newCondition = factory.updateBinaryExpression(condition, condition.left, condition.operatorToken, factory.createIdentifier(lenName));
45
+ const newFor = factory.updateForStatement(node, initializer, newCondition, incrementor, statement);
46
+ return factory.createBlock([lenDecl, newFor], true);
47
+ }
48
+ const visitor = (node) => {
49
+ if (ts.isForStatement(node)) {
50
+ return rewriteForLoop(node);
51
+ }
52
+ return ts.visitEachChild(node, visitor, ctx);
53
+ };
54
+ return ts.visitEachChild(sourceFile, visitor, ctx);
55
+ }
@@ -0,0 +1,2 @@
1
+ import type ts from "typescript";
2
+ export declare function nativePass(ts: typeof import("typescript"), ctx: ts.TransformationContext, sourceFile: ts.SourceFile): ts.SourceFile;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nativePass = nativePass;
4
+ const util_1 = require("../util");
5
+ function nativePass(ts, ctx, sourceFile) {
6
+ if ((0, util_1.hasOptimizeDirective)(sourceFile))
7
+ return sourceFile;
8
+ const factory = ctx.factory;
9
+ const optimize = ts.addSyntheticLeadingComment(factory.createNotEmittedStatement(sourceFile), ts.SyntaxKind.SingleLineCommentTrivia, "!optimize 2", true);
10
+ return factory.updateSourceFile(sourceFile, [optimize, ...Array.from(sourceFile.statements)]);
11
+ }
package/dist/util.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type ts from "typescript";
2
+ export declare function hasOptimizeDirective(sourceFile: ts.SourceFile): boolean;
3
+ export declare function chainKey(ts: typeof import("typescript"), node: ts.Expression): string | undefined;
4
+ export declare function walk(ts: typeof import("typescript"), node: ts.Node, visitor: (n: ts.Node) => void): void;
5
+ export declare function isAssignmentTarget(ts: typeof import("typescript"), node: ts.Node): boolean;