Skip to content

Commit ad3401d

Browse files
committed
Merge remote-tracking branch 'upstream/master' into simplify-and-improve-enum-transform
2 parents 5d1e48d + 0ed6fe3 commit ad3401d

File tree

118 files changed

+6800
-7001
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+6800
-7001
lines changed

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export enum LuaLibFeature {
1818
ArraySlice = "ArraySlice",
1919
ArraySome = "ArraySome",
2020
ArraySplice = "ArraySplice",
21+
ArrayToObject = "ArrayToObject",
2122
ArrayFlat = "ArrayFlat",
2223
ArrayFlatMap = "ArrayFlatMap",
2324
ArraySetLength = "ArraySetLength",

src/LuaTransformer.ts

Lines changed: 690 additions & 338 deletions
Large diffs are not rendered by default.

src/TSHelper.ts

Lines changed: 183 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as ts from "typescript";
2+
import * as path from "path";
23
import { Decorator, DecoratorKind } from "./Decorator";
34
import * as tstl from "./LuaAST";
5+
import * as TSTLErrors from "./TSTLErrors";
6+
import { EmitResolver } from "./LuaTransformer";
47

58
export enum ContextType {
69
None,
@@ -53,6 +56,51 @@ export function getExtendedType(node: ts.ClassLikeDeclarationBase, checker: ts.T
5356
return extendedTypeNode && checker.getTypeAtLocation(extendedTypeNode);
5457
}
5558

59+
export function isAssignmentPattern(node: ts.Node): node is ts.AssignmentPattern {
60+
return ts.isObjectLiteralExpression(node) || ts.isArrayLiteralExpression(node);
61+
}
62+
63+
export function getExportable(exportSpecifiers: ts.NamedExports, resolver: EmitResolver): ts.ExportSpecifier[] {
64+
return exportSpecifiers.elements.filter(exportSpecifier => resolver.isValueAliasDeclaration(exportSpecifier));
65+
}
66+
67+
export function isDefaultExportSpecifier(node: ts.ExportSpecifier): boolean {
68+
return (
69+
(node.name !== undefined && node.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) ||
70+
(node.propertyName !== undefined && node.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword)
71+
);
72+
}
73+
74+
export function hasDefaultExportModifier(modifiers?: ts.NodeArray<ts.Modifier>): boolean {
75+
return modifiers ? modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword) : false;
76+
}
77+
78+
export function shouldResolveModulePath(moduleSpecifier: ts.Expression, checker: ts.TypeChecker): boolean {
79+
const moduleOwnerSymbol = checker.getSymbolAtLocation(moduleSpecifier);
80+
if (moduleOwnerSymbol) {
81+
const decorators = new Map<DecoratorKind, Decorator>();
82+
collectCustomDecorators(moduleOwnerSymbol, checker, decorators);
83+
if (decorators.has(DecoratorKind.NoResolution)) {
84+
return false;
85+
}
86+
}
87+
return true;
88+
}
89+
90+
export function shouldBeImported(
91+
importNode: ts.ImportClause | ts.ImportSpecifier,
92+
checker: ts.TypeChecker,
93+
resolver: EmitResolver
94+
): boolean {
95+
const decorators = getCustomDecorators(checker.getTypeAtLocation(importNode), checker);
96+
97+
return (
98+
resolver.isReferencedAliasDeclaration(importNode) &&
99+
!decorators.has(DecoratorKind.Extension) &&
100+
!decorators.has(DecoratorKind.MetaExtension)
101+
);
102+
}
103+
56104
export function isFileModule(sourceFile: ts.SourceFile): boolean {
57105
return sourceFile.statements.some(isStatementExported);
58106
}
@@ -132,34 +180,45 @@ export function isStaticNode(node: ts.Node): boolean {
132180
return node.modifiers !== undefined && node.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
133181
}
134182

135-
export function isStringType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): boolean {
183+
export function isTypeWithFlags(
184+
type: ts.Type,
185+
flags: ts.TypeFlags,
186+
checker: ts.TypeChecker,
187+
program: ts.Program
188+
): boolean {
136189
if (type.symbol) {
137190
const baseConstraint = checker.getBaseConstraintOfType(type);
138191
if (baseConstraint && baseConstraint !== type) {
139-
return isStringType(baseConstraint, checker, program);
192+
return isTypeWithFlags(baseConstraint, flags, checker, program);
140193
}
141194
}
142195

143196
if (type.isUnion()) {
144-
return type.types.every(t => isStringType(t, checker, program));
197+
return type.types.every(t => isTypeWithFlags(t, flags, checker, program));
145198
}
146199

147200
if (type.isIntersection()) {
148-
return type.types.some(t => isStringType(t, checker, program));
201+
return type.types.some(t => isTypeWithFlags(t, flags, checker, program));
149202
}
150203

151-
return (
152-
(type.flags & ts.TypeFlags.String) !== 0 ||
153-
(type.flags & ts.TypeFlags.StringLike) !== 0 ||
154-
(type.flags & ts.TypeFlags.StringLiteral) !== 0
204+
return (type.flags & flags) !== 0;
205+
}
206+
207+
export function isStringType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): boolean {
208+
return isTypeWithFlags(
209+
type,
210+
ts.TypeFlags.String | ts.TypeFlags.StringLike | ts.TypeFlags.StringLiteral,
211+
checker,
212+
program
155213
);
156214
}
157215

158-
export function isNumberType(type: ts.Type): boolean {
159-
return (
160-
(type.flags & ts.TypeFlags.Number) !== 0 ||
161-
(type.flags & ts.TypeFlags.NumberLike) !== 0 ||
162-
(type.flags & ts.TypeFlags.NumberLiteral) !== 0
216+
export function isNumberType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program): boolean {
217+
return isTypeWithFlags(
218+
type,
219+
ts.TypeFlags.Number | ts.TypeFlags.NumberLike | ts.TypeFlags.NumberLiteral,
220+
checker,
221+
program
163222
);
164223
}
165224

@@ -337,19 +396,25 @@ export function getCustomDecorators(type: ts.Type, checker: ts.TypeChecker): Map
337396
return decMap;
338397
}
339398

399+
export function getCustomNodeDirectives(node: ts.Node): Map<DecoratorKind, Decorator> {
400+
const directivesMap = new Map<DecoratorKind, Decorator>();
401+
402+
ts.getJSDocTags(node).forEach(tag => {
403+
const tagName = tag.tagName.escapedText as string;
404+
if (Decorator.isValid(tagName)) {
405+
const dec = new Decorator(tagName, tag.comment ? tag.comment.split(" ") : []);
406+
directivesMap.set(dec.kind, dec);
407+
}
408+
});
409+
410+
return directivesMap;
411+
}
412+
340413
export function getCustomFileDirectives(file: ts.SourceFile): Map<DecoratorKind, Decorator> {
341-
const decMap = new Map<DecoratorKind, Decorator>();
342414
if (file.statements.length > 0) {
343-
const tags = ts.getJSDocTags(file.statements[0]);
344-
for (const tag of tags) {
345-
const tagName = tag.tagName.escapedText as string;
346-
if (Decorator.isValid(tagName)) {
347-
const dec = new Decorator(tagName, tag.comment ? tag.comment.split(" ") : []);
348-
decMap.set(dec.kind, dec);
349-
}
350-
}
415+
return getCustomNodeDirectives(file.statements[0]);
351416
}
352-
return decMap;
417+
return new Map();
353418
}
354419

355420
export function getCustomSignatureDirectives(
@@ -596,8 +661,7 @@ export function hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeC
596661
if (ts.isSourceFile(scopeDeclaration)) {
597662
return getCustomFileDirectives(scopeDeclaration).has(DecoratorKind.NoSelfInFile);
598663
}
599-
const scopeType = checker.getTypeAtLocation(scopeDeclaration);
600-
if (scopeType && getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) {
664+
if (getCustomNodeDirectives(scopeDeclaration).has(DecoratorKind.NoSelf)) {
601665
return true;
602666
}
603667
return hasNoSelfAncestor(scopeDeclaration, checker);
@@ -634,8 +698,7 @@ export function getDeclarationContextType(
634698
return ContextType.NonVoid;
635699
}
636700

637-
const scopeType = checker.getTypeAtLocation(scopeDeclaration);
638-
if (scopeType && getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) {
701+
if (getCustomNodeDirectives(scopeDeclaration).has(DecoratorKind.NoSelf)) {
639702
return ContextType.Void;
640703
}
641704
return ContextType.NonVoid;
@@ -812,8 +875,14 @@ export function isWithinLiteralAssignmentStatement(node: ts.Node): boolean {
812875
if (!node.parent) {
813876
return false;
814877
}
815-
if (ts.isArrayLiteralExpression(node.parent) || ts.isObjectLiteralExpression(node.parent)) {
878+
if (
879+
ts.isArrayLiteralExpression(node.parent) ||
880+
ts.isArrayBindingPattern(node.parent) ||
881+
ts.isObjectLiteralExpression(node.parent)
882+
) {
816883
return isWithinLiteralAssignmentStatement(node.parent);
884+
} else if (isInDestructingAssignment(node)) {
885+
return true;
817886
} else if (ts.isBinaryExpression(node.parent) && node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
818887
return true;
819888
} else {
@@ -839,27 +908,51 @@ export function moduleHasEmittedBody(
839908
return false;
840909
}
841910

842-
export function isArrayLengthAssignment(
843-
expression: ts.BinaryExpression,
911+
export function isValidFlattenableDestructuringAssignmentLeftHandSide(
912+
node: ts.DestructuringAssignment,
844913
checker: ts.TypeChecker,
845914
program: ts.Program
846-
): expression is ts.BinaryExpression & { left: ts.PropertyAccessExpression | ts.ElementAccessExpression } {
847-
if (expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
848-
return false;
915+
): boolean {
916+
if (ts.isArrayLiteralExpression(node.left)) {
917+
if (node.left.elements.length > 0) {
918+
return !node.left.elements.some(element => {
919+
switch (element.kind) {
920+
case ts.SyntaxKind.Identifier:
921+
case ts.SyntaxKind.PropertyAccessExpression:
922+
if (isArrayLength(element, checker, program)) {
923+
return true;
924+
}
925+
case ts.SyntaxKind.ElementAccessExpression:
926+
// Can be on the left hand side of a Lua assignment statement
927+
return false;
928+
default:
929+
// Cannot be
930+
return true;
931+
}
932+
});
933+
}
849934
}
850935

851-
if (!ts.isPropertyAccessExpression(expression.left) && !ts.isElementAccessExpression(expression.left)) {
936+
return false;
937+
}
938+
939+
export function isArrayLength(
940+
expression: ts.Expression,
941+
checker: ts.TypeChecker,
942+
program: ts.Program
943+
): expression is ts.PropertyAccessExpression | ts.ElementAccessExpression {
944+
if (!ts.isPropertyAccessExpression(expression) && !ts.isElementAccessExpression(expression)) {
852945
return false;
853946
}
854947

855-
const type = checker.getTypeAtLocation(expression.left.expression);
948+
const type = checker.getTypeAtLocation(expression.expression);
856949
if (!isArrayType(type, checker, program)) {
857950
return false;
858951
}
859952

860-
const name = ts.isPropertyAccessExpression(expression.left)
861-
? (expression.left.name.escapedText as string)
862-
: ts.isStringLiteral(expression.left.argumentExpression) && expression.left.argumentExpression.text;
953+
const name = ts.isPropertyAccessExpression(expression)
954+
? (expression.name.escapedText as string)
955+
: ts.isStringLiteral(expression.argumentExpression) && expression.argumentExpression.text;
863956

864957
return name === "length";
865958
}
@@ -899,3 +992,55 @@ export function isSimpleExpression(expression: tstl.Expression): boolean {
899992
}
900993
return true;
901994
}
995+
996+
export function getAbsoluteImportPath(
997+
relativePath: string,
998+
directoryPath: string,
999+
options: ts.CompilerOptions
1000+
): string {
1001+
if (relativePath.charAt(0) !== "." && options.baseUrl) {
1002+
return path.resolve(options.baseUrl, relativePath);
1003+
}
1004+
1005+
return path.resolve(directoryPath, relativePath);
1006+
}
1007+
1008+
export function getImportPath(
1009+
fileName: string,
1010+
relativePath: string,
1011+
node: ts.Node,
1012+
options: ts.CompilerOptions
1013+
): string {
1014+
const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve(".");
1015+
1016+
const absoluteImportPath = path.format(
1017+
path.parse(getAbsoluteImportPath(relativePath, path.dirname(fileName), options))
1018+
);
1019+
const absoluteRootDirPath = path.format(path.parse(rootDir));
1020+
if (absoluteImportPath.includes(absoluteRootDirPath)) {
1021+
return formatPathToLuaPath(absoluteImportPath.replace(absoluteRootDirPath, "").slice(1));
1022+
} else {
1023+
throw TSTLErrors.UnresolvableRequirePath(
1024+
node,
1025+
`Cannot create require path. Module does not exist within --rootDir`,
1026+
relativePath
1027+
);
1028+
}
1029+
}
1030+
1031+
export function getExportPath(fileName: string, options: ts.CompilerOptions): string {
1032+
const rootDir = options.rootDir ? path.resolve(options.rootDir) : path.resolve(".");
1033+
1034+
const absolutePath = path.resolve(fileName.replace(/.ts$/, ""));
1035+
const absoluteRootDirPath = path.format(path.parse(rootDir));
1036+
return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1));
1037+
}
1038+
1039+
export function formatPathToLuaPath(filePath: string): string {
1040+
filePath = filePath.replace(/\.json$/, "");
1041+
if (process.platform === "win32") {
1042+
// Windows can use backslashes
1043+
filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, ".");
1044+
}
1045+
return filePath.replace(/\.\//g, "").replace(/\//g, ".");
1046+
}

src/TSTLErrors.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ?
77
export const CouldNotCast = (castName: string) =>
88
new Error(`Failed to cast all elements to expected type using ${castName}.`);
99

10-
export const DefaultImportsNotSupported = (node: ts.Node) =>
11-
new TranspileError(`Default Imports are not supported, please use named imports instead!`, node);
12-
1310
export const ForbiddenEllipsisDestruction = (node: ts.Node) =>
1411
new TranspileError(`Ellipsis destruction is not allowed.`, node);
1512

@@ -83,6 +80,9 @@ export const MissingFunctionName = (declaration: ts.FunctionLikeDeclaration) =>
8380
export const MissingMetaExtension = (node: ts.Node) =>
8481
new TranspileError(`@metaExtension requires the extension of the metatable class.`, node);
8582

83+
export const NonFlattenableDestructure = (node: ts.Node) =>
84+
new TranspileError(`This node cannot be destructured using a standard Lua assignment statement.`, node);
85+
8686
export const UndefinedFunctionDefinition = (functionSymbolId: number) =>
8787
new Error(`Function definition for function symbol ${functionSymbolId} is undefined.`);
8888

@@ -93,9 +93,6 @@ export const UndefinedTypeNode = (node: ts.Node) => new TranspileError("Failed t
9393
export const UnknownSuperType = (node: ts.Node) =>
9494
new TranspileError("Unable to resolve type of super expression.", node);
9595

96-
export const UnsupportedDefaultExport = (node: ts.Node) =>
97-
new TranspileError(`Default exports are not supported.`, node);
98-
9996
export const UnsupportedImportType = (node: ts.Node) => new TranspileError(`Unsupported import type.`, node);
10097

10198
export const UnsupportedKind = (description: string, kind: ts.SyntaxKind, node: ts.Node) =>

src/lualib/ArrayToObject.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
function __TS__ArrayToObject(this: void, array: any[]): object {
2+
const object: Record<number, any> = {};
3+
for (let i = 0; i < array.length; i += 1) {
4+
object[i] = array[i];
5+
}
6+
return object;
7+
}

src/lualib/Spread.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
function __TS__Spread<T>(this: void, iterable: Iterable<T>): T[] {
2-
const arr: T[] = [];
3-
for (const item of iterable) {
4-
arr[arr.length] = item;
1+
function __TS__Spread<T>(this: void, iterable: string | Iterable<T>): T[] {
2+
const arr = [];
3+
if (typeof iterable === "string") {
4+
for (let i = 0; i < iterable.length; i += 1) {
5+
arr[arr.length] = iterable[i];
6+
}
7+
} else {
8+
for (const item of iterable) {
9+
arr[arr.length] = item;
10+
}
511
}
612
return (table.unpack || unpack)(arr);
713
}

0 commit comments

Comments
 (0)