Skip to content

Commit 26cf33c

Browse files
ark120202Perryvw
authored andcommitted
Add string.{repeat,padStart,padEnd} (#571)
* Add string.{repeat,padStart,padEnd} * Add non-integer string.repeat test cases * Fix non-integer length errors in string pad
1 parent e385af0 commit 26cf33c

File tree

7 files changed

+106
-20
lines changed

7 files changed

+106
-20
lines changed

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export enum LuaLibFeature {
4747
Spread = "Spread",
4848
StringConcat = "StringConcat",
4949
StringEndsWith = "StringEndsWith",
50+
StringPadEnd = "StringPadEnd",
51+
StringPadStart = "StringPadStart",
5052
StringReplace = "StringReplace",
5153
StringSplit = "StringSplit",
5254
StringStartsWith = "StringStartsWith",

src/LuaTransformer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,15 @@ export class LuaTransformer {
41164116
return this.transformLuaLibFunction(LuaLibFeature.StringStartsWith, node, caller, ...params);
41174117
case "endsWith":
41184118
return this.transformLuaLibFunction(LuaLibFeature.StringEndsWith, node, caller, ...params);
4119+
case "repeat":
4120+
const math = tstl.createIdentifier("math");
4121+
const floor = tstl.createStringLiteral("floor");
4122+
const parameter = tstl.createCallExpression(tstl.createTableIndexExpression(math, floor), [params[0]]);
4123+
return this.createStringCall("rep", node, caller, parameter);
4124+
case "padStart":
4125+
return this.transformLuaLibFunction(LuaLibFeature.StringPadStart, node, caller, ...params);
4126+
case "padEnd":
4127+
return this.transformLuaLibFunction(LuaLibFeature.StringPadEnd, node, caller, ...params);
41194128
case "byte":
41204129
case "char":
41214130
case "dump":

src/lualib/StringPadEnd.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function __TS__StringPadEnd(this: string, maxLength: number, fillString = " "): string {
2+
if (maxLength !== maxLength) maxLength = 0;
3+
if (maxLength === -Infinity || maxLength === Infinity) {
4+
// tslint:disable-next-line: no-string-throw
5+
throw "Invalid string length";
6+
}
7+
8+
if (this.length >= maxLength || fillString.length === 0) {
9+
return this;
10+
}
11+
12+
maxLength = maxLength - this.length;
13+
if (maxLength > fillString.length) {
14+
fillString += fillString.repeat(maxLength / fillString.length);
15+
}
16+
17+
return this + fillString.slice(0, Math.floor(maxLength));
18+
}

src/lualib/StringPadStart.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function __TS__StringPadStart(this: string, maxLength: number, fillString = " "): string {
2+
if (maxLength !== maxLength) maxLength = 0;
3+
if (maxLength === -Infinity || maxLength === Infinity) {
4+
// tslint:disable-next-line: no-string-throw
5+
throw "Invalid string length";
6+
}
7+
8+
if (this.length >= maxLength || fillString.length === 0) {
9+
return this;
10+
}
11+
12+
maxLength = maxLength - this.length;
13+
if (maxLength > fillString.length) {
14+
fillString += fillString.repeat(maxLength / fillString.length);
15+
}
16+
17+
return fillString.slice(0, Math.floor(maxLength)) + this;
18+
}

test/unit/numbers.spec.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,41 +39,35 @@ const stringCases = ["-1", "0", "1", "1.5", "Infinity", "-Infinity"];
3939
const restCases: any[] = [true, false, "", " ", "\t", "\n", "foo", {}];
4040
const cases: any[] = [...numberCases, ...stringCases, ...restCases];
4141

42-
// TODO: Add more general utils to serialize values
43-
const valueToString = (value: unknown) =>
44-
value === Infinity || value === -Infinity || (typeof value === "number" && Number.isNaN(value))
45-
? String(value)
46-
: JSON.stringify(value);
47-
4842
describe("Number", () => {
4943
test.each(cases)("constructor(%p)", value => {
50-
const result = util.transpileAndExecute(`return Number(${valueToString(value)})`);
44+
const result = util.transpileAndExecute(`return Number(${util.valueToString(value)})`);
5145
expect(result).toBe(Number(value));
5246
});
5347

5448
test.each(cases)("isNaN(%p)", value => {
5549
const result = util.transpileAndExecute(`
56-
return Number.isNaN(${valueToString(value)} as any)
50+
return Number.isNaN(${util.valueToString(value)} as any)
5751
`);
5852

5953
expect(result).toBe(Number.isNaN(value));
6054
});
6155

6256
test.each(cases)("isFinite(%p)", value => {
6357
const result = util.transpileAndExecute(`
64-
return Number.isFinite(${valueToString(value)} as any)
58+
return Number.isFinite(${util.valueToString(value)} as any)
6559
`);
6660

6761
expect(result).toBe(Number.isFinite(value));
6862
});
6963
});
7064

7165
test.each(cases)("isNaN(%p)", value => {
72-
const result = util.transpileAndExecute(`return isNaN(${valueToString(value)} as any)`);
66+
const result = util.transpileAndExecute(`return isNaN(${util.valueToString(value)} as any)`);
7367
expect(result).toBe(isNaN(value));
7468
});
7569

7670
test.each(cases)("isFinite(%p)", value => {
77-
const result = util.transpileAndExecute(`return isFinite(${valueToString(value)} as any)`);
71+
const result = util.transpileAndExecute(`return isFinite(${util.valueToString(value)} as any)`);
7872
expect(result).toBe(isFinite(value));
7973
});

test/unit/string.spec.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ test.each([
7373
expect(result).toBe(`${a} ${b} test ${c}`);
7474
});
7575

76+
test.each([
77+
{ input: "abcd", index: 3 },
78+
{ input: "abcde", index: 3 },
79+
{ input: "abcde", index: 0 },
80+
{ input: "a", index: 0 },
81+
])("string index (%p)", ({ input, index }) => {
82+
const result = util.transpileAndExecute(`return "${input}"[${index}];`);
83+
84+
expect(result).toBe(input[index]);
85+
});
86+
7687
test.each([
7788
{ inp: "hello test", searchValue: "", replaceValue: "" },
7889
{ inp: "hello test", searchValue: " ", replaceValue: "" },
@@ -290,7 +301,7 @@ test.each<{ inp: string; args: Parameters<string["startsWith"]> }>([
290301
{ inp: "hello test", args: ["test"] },
291302
{ inp: "hello test", args: ["test", 6] },
292303
])("string.startsWith (%p)", ({ inp, args }) => {
293-
const argsString = args.map(arg => JSON.stringify(arg)).join(", ");
304+
const argsString = util.valuesToString(args);
294305
const result = util.transpileAndExecute(`return "${inp}".startsWith(${argsString})`);
295306

296307
expect(result).toBe(inp.startsWith(...args));
@@ -302,19 +313,46 @@ test.each<{ inp: string; args: Parameters<string["endsWith"]> }>([
302313
{ inp: "hello test", args: ["hello"] },
303314
{ inp: "hello test", args: ["hello", 5] },
304315
])("string.endsWith (%p)", ({ inp, args }) => {
305-
const argsString = args.map(arg => JSON.stringify(arg)).join(", ");
316+
const argsString = util.valuesToString(args);
306317
const result = util.transpileAndExecute(`return "${inp}".endsWith(${argsString})`);
307318

308319
expect(result).toBe(inp.endsWith(...args));
309320
});
310321

311322
test.each([
312-
{ input: "abcd", index: 3 },
313-
{ input: "abcde", index: 3 },
314-
{ input: "abcde", index: 0 },
315-
{ input: "a", index: 0 },
316-
])("string index (%p)", ({ input, index }) => {
317-
const result = util.transpileAndExecute(`return "${input}"[${index}];`);
323+
{ inp: "hello test", count: 0 },
324+
{ inp: "hello test", count: 1 },
325+
{ inp: "hello test", count: 2 },
326+
{ inp: "hello test", count: 1.1 },
327+
{ inp: "hello test", count: 1.5 },
328+
{ inp: "hello test", count: 1.9 },
329+
])("string.repeat (%p)", ({ inp, count }) => {
330+
const result = util.transpileAndExecute(`return "${inp}".repeat(${count})`);
331+
332+
expect(result).toBe(inp.repeat(count));
333+
});
318334

319-
expect(result).toBe(input[index]);
335+
const padCases = [
336+
{ inp: "foo", maxLength: 0 },
337+
{ inp: "foo", maxLength: 3 },
338+
{ inp: "foo", maxLength: 5 },
339+
{ inp: "foo", maxLength: 4, fillString: " " },
340+
{ inp: "foo", maxLength: 10, fillString: " " },
341+
{ inp: "foo", maxLength: 5, fillString: "1234" },
342+
{ inp: "foo", maxLength: 5.9, fillString: "1234" },
343+
{ inp: "foo", maxLength: NaN },
344+
];
345+
346+
test.each(padCases)("string.padStart (%p)", ({ inp, maxLength, fillString }) => {
347+
const argsString = util.valuesToString([maxLength, fillString]);
348+
const result = util.transpileAndExecute(`return "${inp}".padStart(${argsString})`);
349+
350+
expect(result).toBe(inp.padStart(maxLength, fillString));
351+
});
352+
353+
test.each(padCases)("string.padEnd (%p)", ({ inp, maxLength, fillString }) => {
354+
const argsString = util.valuesToString([maxLength, fillString]);
355+
const result = util.transpileAndExecute(`return "${inp}".padEnd(${argsString})`);
356+
357+
expect(result).toBe(inp.padEnd(maxLength, fillString));
320358
});

test/util.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,10 @@ export function expectToBeDefined<T>(subject: T | null | undefined): subject is
167167
expect(subject).toBeDefined();
168168
return true; // If this was false the expect would have thrown an error
169169
}
170+
171+
export const valueToString = (value: unknown) =>
172+
value === Infinity || value === -Infinity || (typeof value === "number" && Number.isNaN(value))
173+
? String(value)
174+
: JSON.stringify(value);
175+
176+
export const valuesToString = (values: Array<unknown>) => values.map(valueToString).join(", ");

0 commit comments

Comments
 (0)