@gwigz/slua-tstl-plugin 0.3.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 +188 -16
- package/dist/constants.d.ts +25 -0
- package/dist/constants.js +50 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +352 -659
- package/dist/optimize.d.ts +29 -0
- package/dist/optimize.js +105 -0
- package/dist/transforms.d.ts +7 -0
- package/dist/transforms.js +195 -0
- package/dist/utils.d.ts +130 -0
- package/dist/utils.js +412 -0
- package/package.json +5 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { CallTransform } from "./transforms.js";
|
|
3
|
+
export interface OptimizeFlags {
|
|
4
|
+
/** Inline `.filter()` calls as `for` loops with `ipairs`. Default: false */
|
|
5
|
+
filter?: boolean;
|
|
6
|
+
/** Rewrite `x = x + n` to `x += n` (Luau compound assignment). Default: false */
|
|
7
|
+
compoundAssignment?: boolean;
|
|
8
|
+
/** Reorder `Math.floor((a / b) * c)` to `a * c // b`. */
|
|
9
|
+
floorMultiply?: boolean;
|
|
10
|
+
/** Emit bare `string.find`/`table.find` for indexOf presence checks. */
|
|
11
|
+
indexOf?: boolean;
|
|
12
|
+
/** Shorten TSTL destructuring temp names (`____fn_result_N` -> `_rN`). */
|
|
13
|
+
shortenTemps?: boolean;
|
|
14
|
+
/** Merge forward-declared `local x` with its first `x = value` assignment. */
|
|
15
|
+
inlineLocals?: boolean;
|
|
16
|
+
/** Strip `tostring()` from number-typed template literal interpolations. */
|
|
17
|
+
numericConcat?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const ALL_OPTIMIZE: Required<OptimizeFlags>;
|
|
20
|
+
/**
|
|
21
|
+
* Count `arr.filter(cb)` calls and return a set of file names where inlining
|
|
22
|
+
* should be skipped (the shared `__TS__ArrayFilter` helper is smaller).
|
|
23
|
+
*
|
|
24
|
+
* When `bundle` is true (luaBundle mode), all source files end up in a single
|
|
25
|
+
* output, so the total across the program is what matters. Otherwise each
|
|
26
|
+
* file is counted independently.
|
|
27
|
+
*/
|
|
28
|
+
export declare function countFilterCalls(program: ts.Program, bundle: boolean): Set<string>;
|
|
29
|
+
export declare function createOptimizeTransforms(filterSkipFiles: Set<string>): CallTransform[];
|
package/dist/optimize.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import * as tstl from "typescript-to-lua";
|
|
3
|
+
import { isMethodCall, isArrayType } from "./utils.js";
|
|
4
|
+
export const ALL_OPTIMIZE = {
|
|
5
|
+
filter: true,
|
|
6
|
+
compoundAssignment: true,
|
|
7
|
+
floorMultiply: true,
|
|
8
|
+
indexOf: true,
|
|
9
|
+
shortenTemps: true,
|
|
10
|
+
inlineLocals: true,
|
|
11
|
+
numericConcat: true,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Count `arr.filter(cb)` calls and return a set of file names where inlining
|
|
15
|
+
* should be skipped (the shared `__TS__ArrayFilter` helper is smaller).
|
|
16
|
+
*
|
|
17
|
+
* When `bundle` is true (luaBundle mode), all source files end up in a single
|
|
18
|
+
* output, so the total across the program is what matters. Otherwise each
|
|
19
|
+
* file is counted independently.
|
|
20
|
+
*/
|
|
21
|
+
export function countFilterCalls(program, bundle) {
|
|
22
|
+
const skip = new Set();
|
|
23
|
+
const checker = program.getTypeChecker();
|
|
24
|
+
const sourceFiles = program.getSourceFiles().filter((sf) => !sf.isDeclarationFile);
|
|
25
|
+
if (bundle) {
|
|
26
|
+
let total = 0;
|
|
27
|
+
for (const sf of sourceFiles) {
|
|
28
|
+
ts.forEachChild(sf, function visit(node) {
|
|
29
|
+
if (isArrayFilterCall(node, checker))
|
|
30
|
+
total++;
|
|
31
|
+
ts.forEachChild(node, visit);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (total > 1) {
|
|
35
|
+
for (const sf of sourceFiles) {
|
|
36
|
+
skip.add(sf.fileName);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
for (const sf of sourceFiles) {
|
|
42
|
+
let count = 0;
|
|
43
|
+
ts.forEachChild(sf, function visit(node) {
|
|
44
|
+
if (isArrayFilterCall(node, checker))
|
|
45
|
+
count++;
|
|
46
|
+
ts.forEachChild(node, visit);
|
|
47
|
+
});
|
|
48
|
+
if (count > 1) {
|
|
49
|
+
skip.add(sf.fileName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return skip;
|
|
54
|
+
}
|
|
55
|
+
function isArrayFilterCall(node, checker) {
|
|
56
|
+
return (ts.isCallExpression(node) &&
|
|
57
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
58
|
+
node.expression.name.text === "filter" &&
|
|
59
|
+
node.arguments.length === 1 &&
|
|
60
|
+
isArrayType(node.expression.expression, checker));
|
|
61
|
+
}
|
|
62
|
+
export function createOptimizeTransforms(filterSkipFiles) {
|
|
63
|
+
let counter = 0;
|
|
64
|
+
return [
|
|
65
|
+
// arr.filter(cb) -> inline for loop with ipairs
|
|
66
|
+
{
|
|
67
|
+
match: (node, checker) => {
|
|
68
|
+
if (filterSkipFiles.has(node.getSourceFile().fileName))
|
|
69
|
+
return false;
|
|
70
|
+
return isMethodCall(node, checker, isArrayType, "filter", 1);
|
|
71
|
+
},
|
|
72
|
+
emit: (node, context) => {
|
|
73
|
+
const n = counter++;
|
|
74
|
+
const resultId = tstl.createIdentifier(`____opt_${n}`);
|
|
75
|
+
const valueId = tstl.createIdentifier(`____opt_v_${n}`);
|
|
76
|
+
const cbId = tstl.createIdentifier(`____opt_fn_${n}`);
|
|
77
|
+
const arr = context.transformExpression(node.expression.expression);
|
|
78
|
+
const cb = context.transformExpression(node.arguments[0]);
|
|
79
|
+
// Strip TSTL's context parameter (____) from the callback if present.
|
|
80
|
+
// Array callbacks are always called positionally; the context param is dead.
|
|
81
|
+
if (tstl.isFunctionExpression(cb) &&
|
|
82
|
+
cb.params &&
|
|
83
|
+
cb.params.length > 0 &&
|
|
84
|
+
cb.params[0].text === "____") {
|
|
85
|
+
cb.params = cb.params.slice(1);
|
|
86
|
+
}
|
|
87
|
+
// local ____opt_fn_N = <callback>
|
|
88
|
+
context.addPrecedingStatements(tstl.createVariableDeclarationStatement(cbId, cb, node));
|
|
89
|
+
// local ____opt_N = {}
|
|
90
|
+
context.addPrecedingStatements(tstl.createVariableDeclarationStatement(tstl.cloneIdentifier(resultId), tstl.createTableExpression(), node));
|
|
91
|
+
// ____opt_fn_N(____opt_v_N)
|
|
92
|
+
const filterCall = tstl.createCallExpression(tstl.cloneIdentifier(cbId), [
|
|
93
|
+
tstl.cloneIdentifier(valueId),
|
|
94
|
+
]);
|
|
95
|
+
// ____opt_N[#____opt_N + 1] = ____opt_v_N
|
|
96
|
+
const appendStmt = tstl.createAssignmentStatement(tstl.createTableIndexExpression(tstl.cloneIdentifier(resultId), tstl.createBinaryExpression(tstl.createUnaryExpression(tstl.cloneIdentifier(resultId), tstl.SyntaxKind.LengthOperator), tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator)), tstl.cloneIdentifier(valueId));
|
|
97
|
+
// if ____opt_fn_N(____opt_v_N) then ... end
|
|
98
|
+
const ifStmt = tstl.createIfStatement(filterCall, tstl.createBlock([appendStmt]));
|
|
99
|
+
// for _, ____opt_v_N in ipairs(arr) do ... end
|
|
100
|
+
context.addPrecedingStatements(tstl.createForInStatement(tstl.createBlock([ifStmt]), [tstl.createIdentifier("_"), valueId], [tstl.createCallExpression(tstl.createIdentifier("ipairs"), [arr])], node));
|
|
101
|
+
return tstl.cloneIdentifier(resultId);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import * as tstl from "typescript-to-lua";
|
|
3
|
+
export type CallTransform = {
|
|
4
|
+
match: (node: ts.CallExpression, checker: ts.TypeChecker) => boolean;
|
|
5
|
+
emit: (node: ts.CallExpression, context: tstl.TransformationContext) => tstl.Expression;
|
|
6
|
+
};
|
|
7
|
+
export declare const CALL_TRANSFORMS: CallTransform[];
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import * as tstl from "typescript-to-lua";
|
|
3
|
+
import { isMethodCall, isNamespaceCall, isGlobalCall, isStringType, isArrayType, createNamespacedCall, createStringFindCall, } from "./utils.js";
|
|
4
|
+
export const CALL_TRANSFORMS = [
|
|
5
|
+
// JSON.stringify(val) -> lljson.encode(val)
|
|
6
|
+
{
|
|
7
|
+
match: (node) => isNamespaceCall(node, "JSON", "stringify"),
|
|
8
|
+
emit: (node, context) => {
|
|
9
|
+
const args = node.arguments.map((a) => context.transformExpression(a));
|
|
10
|
+
return createNamespacedCall("lljson", "encode", args, node);
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
// JSON.parse(str) -> lljson.decode(str)
|
|
14
|
+
{
|
|
15
|
+
match: (node) => isNamespaceCall(node, "JSON", "parse"),
|
|
16
|
+
emit: (node, context) => {
|
|
17
|
+
const args = node.arguments.map((a) => context.transformExpression(a));
|
|
18
|
+
return createNamespacedCall("lljson", "decode", args, node);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
// btoa(str) -> llbase64.encode(str)
|
|
22
|
+
{
|
|
23
|
+
match: (node) => isGlobalCall(node, "btoa") && node.arguments.length === 1,
|
|
24
|
+
emit: (node, context) => {
|
|
25
|
+
const args = node.arguments.map((a) => context.transformExpression(a));
|
|
26
|
+
return createNamespacedCall("llbase64", "encode", args, node);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
// atob(str) -> llbase64.decode(str)
|
|
30
|
+
{
|
|
31
|
+
match: (node) => isGlobalCall(node, "atob") && node.arguments.length === 1,
|
|
32
|
+
emit: (node, context) => {
|
|
33
|
+
const args = node.arguments.map((a) => context.transformExpression(a));
|
|
34
|
+
return createNamespacedCall("llbase64", "decode", args, node);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
// str.toUpperCase() -> ll.ToUpper(str)
|
|
38
|
+
{
|
|
39
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "toUpperCase", 0),
|
|
40
|
+
emit: (node, context) => {
|
|
41
|
+
const str = context.transformExpression(node.expression.expression);
|
|
42
|
+
return createNamespacedCall("ll", "ToUpper", [str], node);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
// str.toLowerCase() -> ll.ToLower(str)
|
|
46
|
+
{
|
|
47
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "toLowerCase", 0),
|
|
48
|
+
emit: (node, context) => {
|
|
49
|
+
const str = context.transformExpression(node.expression.expression);
|
|
50
|
+
return createNamespacedCall("ll", "ToLower", [str], node);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
// str.trim() -> ll.StringTrim(str, STRING_TRIM)
|
|
54
|
+
{
|
|
55
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "trim", 0),
|
|
56
|
+
emit: (node, context) => {
|
|
57
|
+
const str = context.transformExpression(node.expression.expression);
|
|
58
|
+
return createNamespacedCall("ll", "StringTrim", [str, tstl.createIdentifier("STRING_TRIM")], node);
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
// str.trimStart() -> ll.StringTrim(str, STRING_TRIM_HEAD)
|
|
62
|
+
{
|
|
63
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "trimStart", 0),
|
|
64
|
+
emit: (node, context) => {
|
|
65
|
+
const str = context.transformExpression(node.expression.expression);
|
|
66
|
+
return createNamespacedCall("ll", "StringTrim", [str, tstl.createIdentifier("STRING_TRIM_HEAD")], node);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
// str.trimEnd() -> ll.StringTrim(str, STRING_TRIM_TAIL)
|
|
70
|
+
{
|
|
71
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "trimEnd", 0),
|
|
72
|
+
emit: (node, context) => {
|
|
73
|
+
const str = context.transformExpression(node.expression.expression);
|
|
74
|
+
return createNamespacedCall("ll", "StringTrim", [str, tstl.createIdentifier("STRING_TRIM_TAIL")], node);
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
// str.indexOf(x) -> (string.find(str, x, 1, true) or 0) - 1
|
|
78
|
+
{
|
|
79
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "indexOf", 1),
|
|
80
|
+
emit: (node, context) => {
|
|
81
|
+
const str = context.transformExpression(node.expression.expression);
|
|
82
|
+
const search = context.transformExpression(node.arguments[0]);
|
|
83
|
+
const findOrZero = tstl.createBinaryExpression(createStringFindCall(str, search, node), tstl.createNumericLiteral(0), tstl.SyntaxKind.OrOperator, node);
|
|
84
|
+
return tstl.createBinaryExpression(tstl.createParenthesizedExpression(findOrZero), tstl.createNumericLiteral(1), tstl.SyntaxKind.SubtractionOperator, node);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
// str.indexOf(x, fromIndex) -> (string.find(str, x, fromIndex + 1, true) or 0) - 1
|
|
88
|
+
{
|
|
89
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "indexOf", 2),
|
|
90
|
+
emit: (node, context) => {
|
|
91
|
+
const str = context.transformExpression(node.expression.expression);
|
|
92
|
+
const search = context.transformExpression(node.arguments[0]);
|
|
93
|
+
const fromArg = node.arguments[1];
|
|
94
|
+
const init = ts.isNumericLiteral(fromArg)
|
|
95
|
+
? tstl.createNumericLiteral(Number(fromArg.text) + 1)
|
|
96
|
+
: tstl.createBinaryExpression(context.transformExpression(fromArg), tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator, node);
|
|
97
|
+
const findCall = createNamespacedCall("string", "find", [str, search, init, tstl.createBooleanLiteral(true)], node);
|
|
98
|
+
const findOrZero = tstl.createBinaryExpression(findCall, tstl.createNumericLiteral(0), tstl.SyntaxKind.OrOperator, node);
|
|
99
|
+
return tstl.createBinaryExpression(tstl.createParenthesizedExpression(findOrZero), tstl.createNumericLiteral(1), tstl.SyntaxKind.SubtractionOperator, node);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
// str.includes(x) -> string.find(str, x, 1, true) ~= nil
|
|
103
|
+
{
|
|
104
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "includes", 1),
|
|
105
|
+
emit: (node, context) => {
|
|
106
|
+
const str = context.transformExpression(node.expression.expression);
|
|
107
|
+
const search = context.transformExpression(node.arguments[0]);
|
|
108
|
+
return tstl.createBinaryExpression(createStringFindCall(str, search, node), tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
// str.split(sep) -> string.split(str, sep) (1-arg only)
|
|
112
|
+
{
|
|
113
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "split", 1),
|
|
114
|
+
emit: (node, context) => {
|
|
115
|
+
const str = context.transformExpression(node.expression.expression);
|
|
116
|
+
const sep = context.transformExpression(node.arguments[0]);
|
|
117
|
+
return createNamespacedCall("string", "split", [str, sep], node);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
// str.repeat(n) -> string.rep(str, n)
|
|
121
|
+
{
|
|
122
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "repeat", 1),
|
|
123
|
+
emit: (node, context) => {
|
|
124
|
+
const str = context.transformExpression(node.expression.expression);
|
|
125
|
+
const n = context.transformExpression(node.arguments[0]);
|
|
126
|
+
return createNamespacedCall("string", "rep", [str, n], node);
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
// str.startsWith(search) -> string.find(str, search, 1, true) == 1 (1-arg only)
|
|
130
|
+
{
|
|
131
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, "startsWith", 1),
|
|
132
|
+
emit: (node, context) => {
|
|
133
|
+
const str = context.transformExpression(node.expression.expression);
|
|
134
|
+
const search = context.transformExpression(node.arguments[0]);
|
|
135
|
+
return tstl.createBinaryExpression(createStringFindCall(str, search, node), tstl.createNumericLiteral(1), tstl.SyntaxKind.EqualityOperator, node);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
// str.substring(start) -> string.sub(str, start + 1)
|
|
139
|
+
// str.substring(start, end) -> string.sub(str, start + 1, end)
|
|
140
|
+
{
|
|
141
|
+
match: (node, checker) => {
|
|
142
|
+
if (!isMethodCall(node, checker, isStringType, "substring")) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return node.arguments.length === 1 || node.arguments.length === 2;
|
|
146
|
+
},
|
|
147
|
+
emit: (node, context) => {
|
|
148
|
+
const str = context.transformExpression(node.expression.expression);
|
|
149
|
+
const startArg = node.arguments[0];
|
|
150
|
+
const start = ts.isNumericLiteral(startArg)
|
|
151
|
+
? tstl.createNumericLiteral(Number(startArg.text) + 1)
|
|
152
|
+
: tstl.createBinaryExpression(context.transformExpression(startArg), tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator, node);
|
|
153
|
+
const args = [str, start];
|
|
154
|
+
if (node.arguments.length === 2) {
|
|
155
|
+
args.push(context.transformExpression(node.arguments[1]));
|
|
156
|
+
}
|
|
157
|
+
return createNamespacedCall("string", "sub", args, node);
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
// str.replace / str.replaceAll -> ll.ReplaceSubString(str, search, replacement, count)
|
|
161
|
+
// count=1 for replace (first match only), count=0 for replaceAll
|
|
162
|
+
...[
|
|
163
|
+
["replace", 1],
|
|
164
|
+
["replaceAll", 0],
|
|
165
|
+
].map(([method, count]) => ({
|
|
166
|
+
match: (node, checker) => isMethodCall(node, checker, isStringType, method, 2),
|
|
167
|
+
emit: (node, context) => {
|
|
168
|
+
const str = context.transformExpression(node.expression.expression);
|
|
169
|
+
const search = context.transformExpression(node.arguments[0]);
|
|
170
|
+
const replacement = context.transformExpression(node.arguments[1]);
|
|
171
|
+
return createNamespacedCall("ll", "ReplaceSubString", [str, search, replacement, tstl.createNumericLiteral(count)], node);
|
|
172
|
+
},
|
|
173
|
+
})),
|
|
174
|
+
// arr.includes(val) -> table.find(arr, val) ~= nil
|
|
175
|
+
{
|
|
176
|
+
match: (node, checker) => isMethodCall(node, checker, isArrayType, "includes", 1),
|
|
177
|
+
emit: (node, context) => {
|
|
178
|
+
const arr = context.transformExpression(node.expression.expression);
|
|
179
|
+
const val = context.transformExpression(node.arguments[0]);
|
|
180
|
+
const findCall = createNamespacedCall("table", "find", [arr, val], node);
|
|
181
|
+
return tstl.createBinaryExpression(findCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node);
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
// arr.indexOf(val) -> (table.find(arr, val) or 0) - 1 (1-arg only)
|
|
185
|
+
{
|
|
186
|
+
match: (node, checker) => isMethodCall(node, checker, isArrayType, "indexOf", 1),
|
|
187
|
+
emit: (node, context) => {
|
|
188
|
+
const arr = context.transformExpression(node.expression.expression);
|
|
189
|
+
const val = context.transformExpression(node.arguments[0]);
|
|
190
|
+
const findCall = createNamespacedCall("table", "find", [arr, val], node);
|
|
191
|
+
const findOrZero = tstl.createBinaryExpression(findCall, tstl.createNumericLiteral(0), tstl.SyntaxKind.OrOperator, node);
|
|
192
|
+
return tstl.createBinaryExpression(tstl.createParenthesizedExpression(findOrZero), tstl.createNumericLiteral(1), tstl.SyntaxKind.SubtractionOperator, node);
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
];
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import * as tstl from "typescript-to-lua";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a `bit32.<fn>(...args)` Lua call expression.
|
|
5
|
+
* The optional `node` attaches TypeScript source-map information; when
|
|
6
|
+
* patching already-lowered Lua AST nodes (e.g. from compound-assignment
|
|
7
|
+
* desugaring) there is no originating TS node, so it may be omitted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createBit32Call(fn: string, args: tstl.Expression[], node?: ts.Node): tstl.CallExpression;
|
|
10
|
+
/**
|
|
11
|
+
* Returns true when `node` is `Math.floor(<single-arg>)`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isMathFloor(node: ts.CallExpression): node is ts.CallExpression & {
|
|
14
|
+
arguments: [ts.Expression];
|
|
15
|
+
};
|
|
16
|
+
export declare function isZeroLiteral(node: ts.Expression): boolean;
|
|
17
|
+
export declare function isNegatedEquality(op: ts.SyntaxKind): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Detect `(a & b) !== 0`, `0 === (a & b)`, etc. and return the `&` expression
|
|
20
|
+
* plus whether to negate the btest result.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractBtestPattern(node: ts.BinaryExpression): {
|
|
23
|
+
band: ts.BinaryExpression;
|
|
24
|
+
negate: boolean;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Detects `-1` as either a PrefixUnaryExpression (minus + 1) or a numeric literal with text "-1".
|
|
28
|
+
*/
|
|
29
|
+
export declare function isMinusOneLiteral(node: ts.Expression): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Detect `s.indexOf(x) >= 0`, `s.indexOf(x) !== -1`, etc. and return
|
|
32
|
+
* the indexOf call plus whether the check means "not found" (negate).
|
|
33
|
+
*
|
|
34
|
+
* Presence patterns (found -> truthy): `>= 0`, `> -1`, `!== -1`, `!= -1`
|
|
35
|
+
* Absence patterns (not found -> negate): `< 0`, `=== -1`, `== -1`
|
|
36
|
+
* Handles both operand orders (indexOf on left or right).
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractIndexOfPresence(node: ts.BinaryExpression, checker: ts.TypeChecker): {
|
|
39
|
+
call: ts.CallExpression;
|
|
40
|
+
isString: boolean;
|
|
41
|
+
negate: boolean;
|
|
42
|
+
} | null;
|
|
43
|
+
/**
|
|
44
|
+
* Type-checking helpers for catalog transforms.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isStringType(expr: ts.Expression, checker: ts.TypeChecker): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Checks whether `expr` resolves to a string or number type.
|
|
49
|
+
* Falls back to inspecting the symbol's declared type annotation when
|
|
50
|
+
* `getTypeAtLocation` returns an unexpected type (e.g. `void` for identifiers
|
|
51
|
+
* that shadow DOM globals like `name`).
|
|
52
|
+
*/
|
|
53
|
+
export declare function isStringOrNumberLike(checker: ts.TypeChecker, expr: ts.Expression): boolean;
|
|
54
|
+
export declare function isArrayType(expr: ts.Expression, checker: ts.TypeChecker): boolean;
|
|
55
|
+
export declare function isDetectedEventType(expr: ts.Expression, checker: ts.TypeChecker): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Returns true when `node` is `detectedEvent.index`, a property access
|
|
58
|
+
* on a `DetectedEvent` reading the `.index` field.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isDetectedEventIndex(node: ts.PropertyAccessExpression, checker: ts.TypeChecker): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Checks whether `node` is `obj.method(args)` where `obj` matches the
|
|
63
|
+
* given type predicate and the method name matches.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isMethodCall(node: ts.CallExpression, checker: ts.TypeChecker, typeGuard: (expr: ts.Expression, checker: ts.TypeChecker) => boolean, method: string, argCount?: number): node is ts.CallExpression & {
|
|
66
|
+
expression: ts.PropertyAccessExpression;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Checks whether `node` is `Namespace.method(args)` using syntactic
|
|
70
|
+
* identifier matching (no TypeChecker needed).
|
|
71
|
+
*/
|
|
72
|
+
export declare function isNamespaceCall(node: ts.CallExpression, namespace: string, method: string): node is ts.CallExpression & {
|
|
73
|
+
expression: ts.PropertyAccessExpression;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Checks whether `node` is a call to a global function by name.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isGlobalCall(node: ts.CallExpression, name: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a `ns.fn(...args)` Lua call expression.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createNamespacedCall(ns: string, fn: string, args: tstl.Expression[], node?: ts.Node): tstl.CallExpression;
|
|
83
|
+
/** Creates a `string.find(str, search, 1, true)` plain-text search call. */
|
|
84
|
+
export declare function createStringFindCall(str: tstl.Expression, search: tstl.Expression, node?: ts.Node): tstl.CallExpression;
|
|
85
|
+
/** Escapes a string for literal use inside `new RegExp(...)`. */
|
|
86
|
+
export declare function escapeRegex(s: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Detects `arr = arr.concat(b, c, ...)` where LHS is a simple identifier,
|
|
89
|
+
* the receiver matches LHS, and all concat arguments are array-typed.
|
|
90
|
+
*/
|
|
91
|
+
export declare function extractConcatSelfAssignment(expr: ts.BinaryExpression, checker: ts.TypeChecker): {
|
|
92
|
+
name: ts.Identifier;
|
|
93
|
+
args: readonly ts.Expression[];
|
|
94
|
+
} | null;
|
|
95
|
+
/**
|
|
96
|
+
* Detects `arr = [...arr, ...b, ...c]` where LHS is a simple identifier,
|
|
97
|
+
* all elements are spreads, the first spread matches LHS, and all tail
|
|
98
|
+
* spread expressions are array-typed.
|
|
99
|
+
*/
|
|
100
|
+
export declare function extractSpreadSelfAssignment(expr: ts.BinaryExpression, checker: ts.TypeChecker): {
|
|
101
|
+
name: ts.Identifier;
|
|
102
|
+
args: ts.Expression[];
|
|
103
|
+
} | null;
|
|
104
|
+
/**
|
|
105
|
+
* Builds nested `table.extend` calls:
|
|
106
|
+
* - Single arg: `table.extend(arr, b)`
|
|
107
|
+
* - Multiple: `table.extend(table.extend(arr, b), c)`
|
|
108
|
+
*/
|
|
109
|
+
export declare function emitChainedExtend(target: tstl.Expression, args: [tstl.Expression, ...tstl.Expression[]], node: ts.Node): tstl.CallExpression;
|
|
110
|
+
export interface LLIndexSemantics {
|
|
111
|
+
indexArgs: Set<string>;
|
|
112
|
+
indexReturn: boolean;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* For an `ll.Foo(...)` call, inspect the resolved signature's JSDoc tags
|
|
116
|
+
* to determine which arguments have `@indexArg` semantics and whether
|
|
117
|
+
* the return value has `@indexReturn` semantics.
|
|
118
|
+
*/
|
|
119
|
+
export declare function getLLIndexSemantics(node: ts.CallExpression, checker: ts.TypeChecker): LLIndexSemantics | null;
|
|
120
|
+
/**
|
|
121
|
+
* Adjusts a 0-based index argument to 1-based for Lua.
|
|
122
|
+
* Constant-folds numeric literals; otherwise emits `expr + 1`.
|
|
123
|
+
*/
|
|
124
|
+
export declare function adjustIndexArg(arg: ts.Expression, context: tstl.TransformationContext): tstl.Expression;
|
|
125
|
+
/**
|
|
126
|
+
* Emit an `ll.Foo(...)` call with automatic index adjustments:
|
|
127
|
+
* - `@indexArg` parameters get `+1`
|
|
128
|
+
* - `@indexReturn` wraps the result in a nil-safe `__tmp and (__tmp - 1)`
|
|
129
|
+
*/
|
|
130
|
+
export declare function emitLLIndexCall(node: ts.CallExpression, context: tstl.TransformationContext, semantics: LLIndexSemantics): tstl.Expression;
|