Skip to content

Commit d503bd3

Browse files
committed
2 parents cb2ab16 + 68ae428 commit d503bd3

21 files changed

Lines changed: 574 additions & 35 deletions

File tree

.github/FUNDING.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Sponsoring TypeScriptToLua helps us stay motivated and shows your appreciation for our work!
2+
github: [Perryvw]

language-extensions/index.d.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ declare type LuaIterable<TValue, TState = undefined> = Iterable<TValue> &
8888
declare type LuaPairsIterable<TKey extends AnyNotNil, TValue> = Iterable<[TKey, TValue]> &
8989
LuaExtension<"__luaPairsIterableBrand">;
9090

91+
/**
92+
* Represents an object that can be iterated with pairs(), where only the key value is used.
93+
*
94+
* @param TKey The type of the key returned each iteration.
95+
*/
96+
declare type LuaPairsKeyIterable<TKey extends AnyNotNil> = Iterable<TKey> & LuaExtension<"__luaPairsKeyIterableBrand">;
97+
9198
/**
9299
* Calls to functions with this type are translated to `left + right`.
93100
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
@@ -500,6 +507,24 @@ declare type LuaTableSet<TTable extends AnyTable, TKey extends AnyNotNil, TValue
500507
declare type LuaTableSetMethod<TKey extends AnyNotNil, TValue> = ((key: TKey, value: TValue) => void) &
501508
LuaExtension<"__luaTableSetMethodBrand">;
502509

510+
/**
511+
* Calls to functions with this type are translated to `table[key] = true`.
512+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
513+
*
514+
* @param TTable The type to access as a Lua table.
515+
* @param TKey The type of the key to use to access the table.
516+
*/
517+
declare type LuaTableAddKey<TTable extends AnyTable, TKey extends AnyNotNil> = ((table: TTable, key: TKey) => void) &
518+
LuaExtension<"__luaTableAddKeyBrand">;
519+
520+
/**
521+
* Calls to methods with this type are translated to `table[key] = true`, where `table` is the object with the method.
522+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
523+
* @param TKey The type of the key to use to access the table.
524+
*/
525+
declare type LuaTableAddKeyMethod<TKey extends AnyNotNil> = ((key: TKey) => void) &
526+
LuaExtension<"__luaTableAddKeyMethodBrand">;
527+
503528
/**
504529
* Calls to functions with this type are translated to `table[key] ~= nil`.
505530
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
@@ -574,3 +599,69 @@ declare type LuaTableConstructor = (new <TKey extends AnyNotNil = AnyNotNil, TVa
574599
* @param TValue The type of the values stored in the table.
575600
*/
576601
declare const LuaTable: LuaTableConstructor;
602+
603+
/**
604+
* A convenience type for working directly with a Lua table, used as a map.
605+
*
606+
* This differs from LuaTable in that the `get` method may return `nil`.
607+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
608+
* @param K The type of the keys used to access the table.
609+
* @param V The type of the values stored in the table.
610+
*/
611+
declare interface LuaMap<K extends AnyNotNil = AnyNotNil, V = any> extends LuaPairsIterable<K, V> {
612+
get: LuaTableGetMethod<K, V | undefined>;
613+
set: LuaTableSetMethod<K, V>;
614+
has: LuaTableHasMethod<K>;
615+
delete: LuaTableDeleteMethod<K>;
616+
}
617+
618+
/**
619+
* A convenience type for working directly with a Lua table, used as a map.
620+
*
621+
* This differs from LuaTable in that the `get` method may return `nil`.
622+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
623+
* @param K The type of the keys used to access the table.
624+
* @param V The type of the values stored in the table.
625+
*/
626+
declare const LuaMap: (new <K extends AnyNotNil = AnyNotNil, V = any>() => LuaMap<K, V>) &
627+
LuaExtension<"__luaTableNewBrand">;
628+
629+
/**
630+
* Readonly version of {@link LuaMap}.
631+
*
632+
* @param K The type of the keys used to access the table.
633+
* @param V The type of the values stored in the table.
634+
*/
635+
declare interface LuaReadonlyMap<K extends AnyNotNil = AnyNotNil, V = any> extends LuaPairsIterable<K, V> {
636+
get: LuaTableGetMethod<K, V>;
637+
has: LuaTableHasMethod<K>;
638+
}
639+
640+
/**
641+
* A convenience type for working directly with a Lua table, used as a set.
642+
*
643+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
644+
* @param T The type of the keys used to access the table.
645+
*/
646+
declare interface LuaSet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIterable<T> {
647+
add: LuaTableAddKeyMethod<T>;
648+
has: LuaTableHasMethod<T>;
649+
delete: LuaTableDeleteMethod<T>;
650+
}
651+
652+
/**
653+
* A convenience type for working directly with a Lua table, used as a set.
654+
*
655+
* For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions
656+
* @param T The type of the keys used to access the table.
657+
*/
658+
declare const LuaSet: (new <T extends AnyNotNil = AnyNotNil>() => LuaSet<T>) & LuaExtension<"__luaTableNewBrand">;
659+
660+
/**
661+
* Readonly version of {@link LuaSet}.
662+
*
663+
* @param T The type of the keys used to access the table.
664+
*/
665+
declare interface LuaReadonlySet<T extends AnyNotNil = AnyNotNil> extends LuaPairsKeyIterable<T> {
666+
has: LuaTableHasMethod<T>;
667+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typescript-to-lua",
3-
"version": "1.6.1",
3+
"version": "1.6.3",
44
"description": "A generic TypeScript to Lua transpiler. Write your code in TypeScript and publish Lua!",
55
"repository": "https://github.com/TypeScriptToLua/TypeScriptToLua",
66
"homepage": "https://typescripttolua.github.io/",
@@ -50,6 +50,12 @@
5050
"tstl": "dist/tstl.js",
5151
"app": "dist/tstl.js"
5252
},
53+
"pkg": {
54+
"assets": [
55+
"node_modules/typescript/lib/**/*.d.ts",
56+
"dist/lualib/**/*.*"
57+
]
58+
},
5359
"engines": {
5460
"node": ">=12.13.0"
5561
},

src/LuaPrinter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ export class LuaPrinter {
241241
// Inline lualib features
242242
sourceChunks.push("-- Lua Library inline imports\n");
243243
sourceChunks.push(loadInlineLualibFeatures(file.luaLibFeatures, this.emitHost));
244+
sourceChunks.push("-- End of Lua Library inline imports\n");
244245
}
245246

246247
if (this.options.sourceMapTraceback && !isBundleEnabled(this.options)) {

src/cli/diagnostics.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export const compilerOptionRequiresAValueOfType = createCommandLineError(
3333
(name: string, type: string) => `Compiler option '${name}' requires a value of type ${type}.`
3434
);
3535

36+
export const compilerOptionCouldNotParseJson = createCommandLineError(
37+
5025,
38+
(name: string, error: string) => `Compiler option '${name}' failed to parse the given JSON value: '${error}'.`
39+
);
40+
3641
export const optionProjectCannotBeMixedWithSourceFilesOnACommandLine = createCommandLineError(
3742
5042,
3843
() => "Option 'project' cannot be mixed with source files on a command line."

src/cli/parse.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface CommandLineOptionOfEnum extends CommandLineOptionBase {
1818
}
1919

2020
interface CommandLineOptionOfPrimitive extends CommandLineOptionBase {
21-
type: "boolean" | "string" | "object" | "array";
21+
type: "boolean" | "string" | "json-array-of-objects" | "array";
2222
}
2323

2424
type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfPrimitive;
@@ -82,7 +82,7 @@ export const optionDeclarations: CommandLineOption[] = [
8282
{
8383
name: "luaPlugins",
8484
description: "List of TypeScriptToLua plugins.",
85-
type: "object",
85+
type: "json-array-of-objects",
8686
},
8787
{
8888
name: "tstlVerbose",
@@ -124,7 +124,7 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine):
124124
continue;
125125
}
126126

127-
const { error, value } = readValue(option, rawValue);
127+
const { error, value } = readValue(option, rawValue, OptionSource.TsConfig);
128128
if (error) parsedConfigFile.errors.push(error);
129129
if (parsedConfigFile.options[name] === undefined) parsedConfigFile.options[name] = value;
130130
}
@@ -164,9 +164,9 @@ function updateParsedCommandLine(parsedCommandLine: ts.ParsedCommandLine, args:
164164
if (error) parsedCommandLine.errors.push(error);
165165
parsedCommandLine.options[option.name] = value;
166166
if (consumed) {
167-
i += 1;
168167
// Values of custom options are parsed as a file name, exclude them
169-
parsedCommandLine.fileNames = parsedCommandLine.fileNames.filter(f => f !== value);
168+
parsedCommandLine.fileNames = parsedCommandLine.fileNames.filter(f => f !== args[i + 1]);
169+
i += 1;
170170
}
171171
}
172172
}
@@ -196,21 +196,25 @@ function readCommandLineArgument(option: CommandLineOption, value: any): Command
196196
};
197197
}
198198

199-
return { ...readValue(option, value), consumed: true };
199+
return { ...readValue(option, value, OptionSource.CommandLine), consumed: true };
200+
}
201+
202+
enum OptionSource {
203+
CommandLine,
204+
TsConfig,
200205
}
201206

202207
interface ReadValueResult {
203208
error?: ts.Diagnostic;
204209
value: any;
205210
}
206211

207-
function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
212+
function readValue(option: CommandLineOption, value: unknown, source: OptionSource): ReadValueResult {
208213
if (value === null) return { value };
209214

210215
switch (option.type) {
211216
case "boolean":
212-
case "string":
213-
case "object": {
217+
case "string": {
214218
if (typeof value !== option.type) {
215219
return {
216220
value: undefined,
@@ -220,15 +224,44 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
220224

221225
return { value };
222226
}
223-
case "array": {
224-
if (!Array.isArray(value)) {
227+
case "array":
228+
case "json-array-of-objects": {
229+
const isInvalidNonCliValue = source === OptionSource.TsConfig && !Array.isArray(value);
230+
const isInvalidCliValue = source === OptionSource.CommandLine && typeof value !== "string";
231+
232+
if (isInvalidNonCliValue || isInvalidCliValue) {
225233
return {
226234
value: undefined,
227235
error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, option.type),
228236
};
229237
}
230238

231-
return { value };
239+
const shouldParseValue = source === OptionSource.CommandLine && typeof value === "string";
240+
if (!shouldParseValue) return { value };
241+
242+
if (option.type === "array") {
243+
const array = value.split(",");
244+
return { value: array };
245+
}
246+
247+
try {
248+
const objects = JSON.parse(value);
249+
if (!Array.isArray(objects)) {
250+
return {
251+
value: undefined,
252+
error: cliDiagnostics.compilerOptionRequiresAValueOfType(option.name, option.type),
253+
};
254+
}
255+
256+
return { value: objects };
257+
} catch (e) {
258+
if (!(e instanceof SyntaxError)) throw e;
259+
260+
return {
261+
value: undefined,
262+
error: cliDiagnostics.compilerOptionCouldNotParseJson(option.name, e.message),
263+
};
264+
}
232265
}
233266
case "enum": {
234267
if (typeof value !== "string") {

src/lualib/Promise.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class __TS__Promise<T> implements Promise<T> {
8585
}
8686
} else {
8787
// We always want to resolve our child promise if this promise is resolved, even if we have no handler
88-
this.fulfilledCallbacks.push(() => resolve(undefined));
88+
this.fulfilledCallbacks.push(v => resolve(v));
8989
}
9090

9191
if (onRejected) {
@@ -96,6 +96,9 @@ export class __TS__Promise<T> implements Promise<T> {
9696
// If promise already rejected, immediately call callback
9797
internalCallback(this.rejectionReason);
9898
}
99+
} else {
100+
// We always want to reject our child promise if this promise is rejected, even if we have no handler
101+
this.rejectedCallbacks.push(err => reject(err));
99102
}
100103

101104
if (isFulfilled) {

src/transformation/utils/language-extensions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum ExtensionKind {
88
VarargConstant = "VarargConstant",
99
IterableType = "IterableType",
1010
PairsIterableType = "PairsIterableType",
11+
PairsKeyIterableType = "PairsKeyIterableType",
1112
AdditionOperatorType = "AdditionOperatorType",
1213
AdditionOperatorMethodType = "AdditionOperatorMethodType",
1314
SubtractionOperatorType = "SubtractionOperatorType",
@@ -53,6 +54,8 @@ export enum ExtensionKind {
5354
TableHasMethodType = "TableHasMethodType",
5455
TableSetType = "TableSetType",
5556
TableSetMethodType = "TableSetMethodType",
57+
TableAddType = "TableAddType",
58+
TableAddMethodType = "TableAddMethodType",
5659
}
5760

5861
const extensionKindToValueName: { [T in ExtensionKind]?: string } = {
@@ -68,6 +71,7 @@ const extensionKindToTypeBrand: { [T in ExtensionKind]: string } = {
6871
[ExtensionKind.VarargConstant]: "__luaVarargConstantBrand",
6972
[ExtensionKind.IterableType]: "__luaIterableBrand",
7073
[ExtensionKind.PairsIterableType]: "__luaPairsIterableBrand",
74+
[ExtensionKind.PairsKeyIterableType]: "__luaPairsKeyIterableBrand",
7175
[ExtensionKind.AdditionOperatorType]: "__luaAdditionBrand",
7276
[ExtensionKind.AdditionOperatorMethodType]: "__luaAdditionMethodBrand",
7377
[ExtensionKind.SubtractionOperatorType]: "__luaSubtractionBrand",
@@ -113,6 +117,8 @@ const extensionKindToTypeBrand: { [T in ExtensionKind]: string } = {
113117
[ExtensionKind.TableHasMethodType]: "__luaTableHasMethodBrand",
114118
[ExtensionKind.TableSetType]: "__luaTableSetBrand",
115119
[ExtensionKind.TableSetMethodType]: "__luaTableSetMethodBrand",
120+
[ExtensionKind.TableAddType]: "__luaTableAddKeyBrand",
121+
[ExtensionKind.TableAddMethodType]: "__luaTableAddKeyMethodBrand",
116122
};
117123

118124
export function isExtensionType(type: ts.Type, extensionKind: ExtensionKind): boolean {

src/transformation/visitors/language-extensions/pairsIterable.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { cast } from "../../../utils";
44
import { TransformationContext } from "../../context";
55
import { invalidPairsIterableWithoutDestructuring } from "../../utils/diagnostics";
66
import * as extensions from "../../utils/language-extensions";
7-
import { getVariableDeclarationBinding } from "../loops/utils";
7+
import { getVariableDeclarationBinding, transformForInitializer } from "../loops/utils";
88
import { transformArrayBindingElement } from "../variable-declaration";
99

1010
function isPairsIterableType(type: ts.Type): boolean {
@@ -16,6 +16,15 @@ export function isPairsIterableExpression(context: TransformationContext, expres
1616
return isPairsIterableType(type);
1717
}
1818

19+
function isPairsKeyIterableType(type: ts.Type): boolean {
20+
return extensions.isExtensionType(type, extensions.ExtensionKind.PairsKeyIterableType);
21+
}
22+
23+
export function isPairsKeyIterableExpression(context: TransformationContext, expression: ts.Expression): boolean {
24+
const type = context.checker.getTypeAtLocation(expression);
25+
return isPairsKeyIterableType(type);
26+
}
27+
1928
export function transformForOfPairsIterableStatement(
2029
context: TransformationContext,
2130
statement: ts.ForOfStatement,
@@ -61,3 +70,15 @@ export function transformForOfPairsIterableStatement(
6170

6271
return lua.createForInStatement(block, identifiers, [pairsCall], statement);
6372
}
73+
74+
export function transformForOfPairsKeyIterableStatement(
75+
context: TransformationContext,
76+
statement: ts.ForOfStatement,
77+
block: lua.Block
78+
): lua.Statement {
79+
const pairsCall = lua.createCallExpression(lua.createIdentifier("pairs"), [
80+
context.transformExpression(statement.expression),
81+
]);
82+
const identifier = transformForInitializer(context, statement.initializer, block);
83+
return lua.createForInStatement(block, [identifier], [pairsCall], statement);
84+
}

0 commit comments

Comments
 (0)