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.
@@ -1,163 +0,0 @@
1
- import type ts from "typescript";
2
- import { chainKey, walk, isAssignmentTarget } from "../util";
3
-
4
- function isGetServiceCall(ts: typeof import("typescript"), node: ts.Node): node is ts.CallExpression {
5
- if (!ts.isCallExpression(node)) return false;
6
- const expr = node.expression;
7
- if (!ts.isPropertyAccessExpression(expr)) return false;
8
- const obj = expr.expression;
9
- return ts.isIdentifier(obj) && obj.text === "game" && expr.name.text === "GetService";
10
- }
11
-
12
- function getServiceName(ts: typeof import("typescript"), call: ts.CallExpression): string | undefined {
13
- const args = call.arguments;
14
- if (args.length !== 1) return undefined;
15
- const arg = args[0];
16
- if (!ts.isStringLiteral(arg)) return undefined;
17
- return arg.text;
18
- }
19
-
20
- export function cachePass(
21
- ts: typeof import("typescript"),
22
- _program: ts.Program,
23
- ctx: ts.TransformationContext,
24
- sourceFile: ts.SourceFile,
25
- ): ts.SourceFile {
26
- const factory = ctx.factory;
27
-
28
- // --- GetService hoisting ---
29
- const services = new Map<string, string>();
30
- walk(ts, sourceFile, node => {
31
- if (!node || !isGetServiceCall(ts, node)) return;
32
- const name = getServiceName(ts, node as ts.CallExpression);
33
- if (name && !services.has(name)) services.set(name, `_${name}`);
34
- });
35
-
36
- const serviceVisitor = (node: ts.Node): ts.Node => {
37
- if (isGetServiceCall(ts, node)) {
38
- const name = getServiceName(ts, node as ts.CallExpression);
39
- if (name && services.has(name)) return factory.createIdentifier(services.get(name)!);
40
- }
41
- return ts.visitEachChild(node, serviceVisitor, ctx);
42
- };
43
-
44
- let result = ts.visitEachChild(sourceFile, serviceVisitor, ctx) as ts.SourceFile;
45
-
46
- if (services.size > 0) {
47
- const hoistDecls = Array.from(services.entries()).map(([name, localName]) =>
48
- factory.createVariableStatement(
49
- undefined,
50
- factory.createVariableDeclarationList(
51
- [factory.createVariableDeclaration(
52
- factory.createIdentifier(localName),
53
- undefined,
54
- undefined,
55
- factory.createCallExpression(
56
- factory.createPropertyAccessExpression(
57
- factory.createIdentifier("game"),
58
- "GetService",
59
- ),
60
- undefined,
61
- [factory.createStringLiteral(name)],
62
- ),
63
- )],
64
- ts.NodeFlags.Const,
65
- ),
66
- )
67
- );
68
- result = factory.updateSourceFile(result, [...hoistDecls, ...Array.from(result.statements)]);
69
- }
70
-
71
- // --- Property chain hoisting within functions ---
72
- result = hoistPropertyChains(ts, result, factory, ctx);
73
- return result;
74
- }
75
-
76
- function hoistPropertyChains(
77
- ts: typeof import("typescript"),
78
- sourceFile: ts.SourceFile,
79
- factory: ts.NodeFactory,
80
- ctx: ts.TransformationContext,
81
- ): ts.SourceFile {
82
- const visitor = (node: ts.Node): ts.Node => {
83
- if (
84
- ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) ||
85
- ts.isArrowFunction(node) || ts.isMethodDeclaration(node)
86
- ) {
87
- return hoistInFunction(ts, node as ts.FunctionLikeDeclaration, factory, ctx);
88
- }
89
- return ts.visitEachChild(node, visitor, ctx);
90
- };
91
- return ts.visitEachChild(sourceFile, visitor, ctx) as ts.SourceFile;
92
- }
93
-
94
- function hoistInFunction(
95
- ts: typeof import("typescript"),
96
- fn: ts.FunctionLikeDeclaration,
97
- factory: ts.NodeFactory,
98
- ctx: ts.TransformationContext,
99
- ): ts.FunctionLikeDeclaration {
100
- if (!fn.body || !ts.isBlock(fn.body)) return fn;
101
-
102
- const counts = new Map<string, number>();
103
- walk(ts, fn.body, node => {
104
- if (!node || !ts.isPropertyAccessExpression(node)) return;
105
- const key = chainKey(ts, node);
106
- if (!key || !key.includes(".")) return;
107
- if (isAssignmentTarget(ts, node)) return;
108
- counts.set(key, (counts.get(key) ?? 0) + 1);
109
- });
110
-
111
- const toHoist = new Map<string, string>();
112
- let counter = 0;
113
-
114
- const candidates = Array.from(counts.entries())
115
- .filter(([, count]) => count >= 2)
116
- .sort((a, b) => b[0].length - a[0].length);
117
-
118
- for (const [key] of candidates) {
119
- const alreadyCovered = Array.from(toHoist.keys()).some(h => h.startsWith(key + "."));
120
- if (alreadyCovered) continue;
121
- toHoist.set(key, `_cache${counter++}`);
122
- }
123
-
124
- if (toHoist.size === 0) return fn;
125
-
126
- const chainVisitor = (node: ts.Node): ts.Node => {
127
- if (ts.isPropertyAccessExpression(node)) {
128
- const key = chainKey(ts, node);
129
- if (key && toHoist.has(key) && !isAssignmentTarget(ts, node)) {
130
- return factory.createIdentifier(toHoist.get(key)!);
131
- }
132
- }
133
- return ts.visitEachChild(node, chainVisitor, ctx);
134
- };
135
-
136
- const newBody = ts.visitEachChild(fn.body, chainVisitor, ctx) as ts.Block;
137
-
138
- const hoistStmts = Array.from(toHoist.entries()).map(([key, localName]) => {
139
- const parts = key.split(".");
140
- let expr: ts.Expression = factory.createIdentifier(parts[0]);
141
- for (let i = 1; i < parts.length; i++) {
142
- expr = factory.createPropertyAccessExpression(expr, parts[i]);
143
- }
144
- return factory.createVariableStatement(
145
- undefined,
146
- factory.createVariableDeclarationList(
147
- [factory.createVariableDeclaration(
148
- factory.createIdentifier(localName),
149
- undefined, undefined, expr,
150
- )],
151
- ts.NodeFlags.Const,
152
- ),
153
- );
154
- });
155
-
156
- const updatedBody = factory.updateBlock(newBody, [...hoistStmts, ...Array.from(newBody.statements)]);
157
-
158
- if (ts.isFunctionDeclaration(fn)) return factory.updateFunctionDeclaration(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.typeParameters, fn.parameters, fn.type, updatedBody);
159
- if (ts.isFunctionExpression(fn)) return factory.updateFunctionExpression(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.typeParameters, fn.parameters, fn.type, updatedBody);
160
- if (ts.isArrowFunction(fn)) return factory.updateArrowFunction(fn, fn.modifiers, fn.typeParameters, fn.parameters, fn.type, fn.equalsGreaterThanToken, updatedBody);
161
- if (ts.isMethodDeclaration(fn)) return factory.updateMethodDeclaration(fn, fn.modifiers, fn.asteriskToken, fn.name, fn.questionToken, fn.typeParameters, fn.parameters, fn.type, updatedBody);
162
- return fn;
163
- }
@@ -1,80 +0,0 @@
1
- import type ts from "typescript";
2
-
3
- export function loopsPass(
4
- ts: typeof import("typescript"),
5
- _program: ts.Program,
6
- ctx: ts.TransformationContext,
7
- sourceFile: ts.SourceFile,
8
- ): ts.SourceFile {
9
- const factory = ctx.factory;
10
-
11
- function isArrayLengthExpr(node: ts.Expression): ts.Expression | undefined {
12
- // matches: arr.size() or arr.length (if it ever appears)
13
- if (ts.isCallExpression(node)) {
14
- const expr = node.expression;
15
- if (ts.isPropertyAccessExpression(expr) && expr.name.text === "size" && node.arguments.length === 0) {
16
- return expr.expression;
17
- }
18
- }
19
- return undefined;
20
- }
21
-
22
- function rewriteForLoop(node: ts.ForStatement): ts.Statement {
23
- // Match: for (let i = 0; i < arr.size(); i++) { ... }
24
- const { initializer, condition, incrementor, statement } = node;
25
- if (!initializer || !condition || !incrementor) return node;
26
- if (!ts.isVariableDeclarationList(initializer)) return node;
27
- const decls = initializer.declarations;
28
- if (decls.length !== 1) return node;
29
- const decl = decls[0];
30
- if (!ts.isIdentifier(decl.name)) return node;
31
- if (!decl.initializer || !ts.isNumericLiteral(decl.initializer) || decl.initializer.text !== "0") return node;
32
- if (!ts.isBinaryExpression(condition)) return node;
33
- if (condition.operatorToken.kind !== ts.SyntaxKind.LessThanToken) return node;
34
-
35
- const arr = isArrayLengthExpr(condition.right);
36
- if (!arr) return node;
37
-
38
- const loopVar = decl.name.text;
39
- const arrName = ts.isIdentifier(arr) ? arr.text : undefined;
40
- if (!arrName) return node;
41
-
42
- const lenName = `_len_${arrName}`;
43
- const lenDecl = factory.createVariableStatement(
44
- undefined,
45
- factory.createVariableDeclarationList(
46
- [factory.createVariableDeclaration(
47
- factory.createIdentifier(lenName),
48
- undefined,
49
- factory.createTypeReferenceNode("number"),
50
- factory.createCallExpression(
51
- factory.createPropertyAccessExpression(arr, "size"),
52
- undefined, [],
53
- ),
54
- )],
55
- ts.NodeFlags.Const,
56
- ),
57
- );
58
-
59
- const newCondition = factory.updateBinaryExpression(
60
- condition,
61
- condition.left,
62
- condition.operatorToken,
63
- factory.createIdentifier(lenName),
64
- );
65
- const newFor = factory.updateForStatement(
66
- node, initializer, newCondition, incrementor, statement,
67
- );
68
-
69
- return factory.createBlock([lenDecl, newFor], true) as unknown as ts.Statement;
70
- }
71
-
72
- const visitor = (node: ts.Node): ts.Node => {
73
- if (ts.isForStatement(node)) {
74
- return rewriteForLoop(node);
75
- }
76
- return ts.visitEachChild(node, visitor, ctx);
77
- };
78
-
79
- return ts.visitEachChild(sourceFile, visitor, ctx) as ts.SourceFile;
80
- }
@@ -1,19 +0,0 @@
1
- import type ts from "typescript";
2
- import { hasOptimizeDirective } from "../util";
3
-
4
- export function nativePass(
5
- ts: typeof import("typescript"),
6
- ctx: ts.TransformationContext,
7
- sourceFile: ts.SourceFile,
8
- ): ts.SourceFile {
9
- if (hasOptimizeDirective(sourceFile)) return sourceFile;
10
-
11
- const factory = ctx.factory;
12
- const optimize = ts.addSyntheticLeadingComment(
13
- factory.createNotEmittedStatement(sourceFile),
14
- ts.SyntaxKind.SingleLineCommentTrivia,
15
- "!optimize 2",
16
- true,
17
- );
18
- return factory.updateSourceFile(sourceFile, [optimize, ...Array.from(sourceFile.statements)]);
19
- }
package/src/util.ts DELETED
@@ -1,33 +0,0 @@
1
- import type ts from "typescript";
2
-
3
- export function hasOptimizeDirective(sourceFile: ts.SourceFile): boolean {
4
- return /^--!optimize\b/m.test(sourceFile.text) || /^\/\/!optimize\b/m.test(sourceFile.text);
5
- }
6
-
7
- export function chainKey(ts: typeof import("typescript"), node: ts.Expression): string | undefined {
8
- if (ts.isIdentifier(node)) return node.text;
9
- if (ts.isPropertyAccessExpression(node)) {
10
- const left = chainKey(ts, node.expression);
11
- if (left === undefined) return undefined;
12
- return `${left}.${node.name.text}`;
13
- }
14
- return undefined;
15
- }
16
-
17
- export function walk(ts: typeof import("typescript"), node: ts.Node, visitor: (n: ts.Node) => void): void {
18
- if (!node) return;
19
- visitor(node);
20
- ts.forEachChild(node, child => { if (child) walk(ts, child, visitor); });
21
- }
22
-
23
- export function isAssignmentTarget(ts: typeof import("typescript"), node: ts.Node): boolean {
24
- const parent = node.parent;
25
- if (!parent) return false;
26
- if (ts.isBinaryExpression(parent)) {
27
- return parent.left === node &&
28
- parent.operatorToken.kind === ts.SyntaxKind.EqualsToken;
29
- }
30
- if (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) return true;
31
- return false;
32
- }
33
-
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "commonjs",
5
- "moduleResolution": "node",
6
- "strict": true,
7
- "declaration": true,
8
- "outDir": "dist",
9
- "rootDir": "src",
10
- "esModuleInterop": true,
11
- "paths": { "typescript": ["./test/node_modules/typescript"] },
12
- "skipLibCheck": true,
13
- "types": ["node"]
14
- },
15
- "include": ["src"]
16
- }