Skip to content

Commit b3b4a21

Browse files
authored
Fix array.join accepting only string and number elements (#887)
1 parent 42b3568 commit b3b4a21

File tree

8 files changed

+47
-14
lines changed

8 files changed

+47
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
- Fixed iteration over generators stopping at first yielded `nil` value
2727

28+
- Fixed `Array.prototype.join` throwing an error when array contains anything other than strings and numbers
29+
2830
- Fixed extending a class not keeping `toString` implementation from a super class
2931

3032
## 0.33.0

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum LuaLibFeature {
1010
ArrayFindIndex = "ArrayFindIndex",
1111
ArrayIncludes = "ArrayIncludes",
1212
ArrayIndexOf = "ArrayIndexOf",
13+
ArrayJoin = "ArrayJoin",
1314
ArrayMap = "ArrayMap",
1415
ArrayPush = "ArrayPush",
1516
ArrayReduce = "ArrayReduce",

src/lualib/ArrayJoin.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function __TS__ArrayJoin(this: unknown[], separator = ",") {
2+
let result = "";
3+
for (const [index, value] of ipairs(this)) {
4+
if (index > 1) result += separator;
5+
result += value.toString();
6+
}
7+
return result;
8+
}

src/lualib/Iterator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ function __TS__Iterator<T>(
2727
const iterator = iterable[Symbol.iterator]();
2828
return [__TS__IteratorIteratorStep, iterator];
2929
} else {
30-
return ipairs(iterable as readonly T[]);
30+
return ipairs(iterable as readonly T[]) as any;
3131
}
3232
}

src/lualib/declarations/global.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ declare function select<T>(index: "#", ...args: T[]): number;
2929
* @luaIterator
3030
* @tupleReturn
3131
*/
32-
declare function ipairs<T>(t: Record<number, T>): LuaTupleIterable<[number, T]>;
32+
type LuaTupleIterator<T extends any[]> = Iterable<T> & { " LuaTupleIterator": never };
33+
34+
declare function ipairs<T>(t: Record<number, T>): LuaTupleIterator<[number, T]>;

src/transformation/builtins/array.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { TransformationContext } from "../context";
44
import { unsupportedProperty } from "../utils/diagnostics";
55
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
66
import { PropertyCallExpression, transformArguments } from "../visitors/call";
7+
import { isStringType, isNumberType } from "../utils/typescript";
78

89
export function transformArrayPrototypeCall(
910
context: TransformationContext,
@@ -61,19 +62,25 @@ export function transformArrayPrototypeCall(
6162
case "splice":
6263
return transformLuaLibFunction(context, LuaLibFeature.ArraySplice, node, caller, ...params);
6364
case "join":
64-
const defaultSeparatorLiteral = lua.createStringLiteral(",");
65-
const parameters = [
66-
caller,
67-
node.arguments.length === 0
68-
? defaultSeparatorLiteral
69-
: lua.createBinaryExpression(params[0], defaultSeparatorLiteral, lua.SyntaxKind.OrOperator),
70-
];
65+
const callerType = context.checker.getTypeAtLocation(expression.expression);
66+
const elementType = context.checker.getElementTypeOfArrayType(callerType);
67+
if (elementType && (isStringType(context, elementType) || isNumberType(context, elementType))) {
68+
const defaultSeparatorLiteral = lua.createStringLiteral(",");
69+
const parameters = [
70+
caller,
71+
node.arguments.length === 0
72+
? defaultSeparatorLiteral
73+
: lua.createBinaryExpression(params[0], defaultSeparatorLiteral, lua.SyntaxKind.OrOperator),
74+
];
7175

72-
return lua.createCallExpression(
73-
lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("concat")),
74-
parameters,
75-
node
76-
);
76+
return lua.createCallExpression(
77+
lua.createTableIndexExpression(lua.createIdentifier("table"), lua.createStringLiteral("concat")),
78+
parameters,
79+
node
80+
);
81+
}
82+
83+
return transformLuaLibFunction(context, LuaLibFeature.ArrayJoin, node, caller, ...params);
7784
case "flat":
7885
return transformLuaLibFunction(context, LuaLibFeature.ArrayFlat, node, caller, ...params);
7986
case "flatMap":

src/typescript-internal.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ declare module "typescript" {
2020
configFile?: TsConfigSourceFile;
2121
configFilePath?: string;
2222
}
23+
24+
interface TypeChecker {
25+
getElementTypeOfArrayType(type: Type): Type | undefined;
26+
}
2327
}

test/unit/builtins/array.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,19 @@ test.each([
424424
{ array: ["test1", "test2"] },
425425
{ array: ["test1", "test2"], separator: ";" },
426426
{ array: ["test1", "test2"], separator: "" },
427+
{ array: [1, "2"] },
427428
])("array.join (%p)", ({ array, separator }) => {
428429
util.testExpression`${util.formatCode(array)}.join(${util.formatCode(separator)})`.expectToMatchJsResult();
429430
});
430431

432+
test('array.join (1, "2", {})', () => {
433+
util.testExpression`[1, "2", {}].join()`.expectToEqual("1,2,table: 0x168");
434+
});
435+
436+
test('array.join (1, "2", Symbol("foo"))', () => {
437+
util.testExpression`[1, "2", Symbol("foo")].join()`.expectToEqual("1,2,Symbol(foo)");
438+
});
439+
431440
test("array.join without separator argument", () => {
432441
util.testExpression`["test1", "test2"].join()`.expectToMatchJsResult();
433442
});

0 commit comments

Comments
 (0)