@gwigz/slua-tstl-plugin 1.0.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.
@@ -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[];
@@ -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
+ ];
@@ -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;