diff --git a/.travis.yml b/.travis.yml index 79fcbeed8..209eed141 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ node_js: - stable script: - - npm run lint - npm run build - npm test -- --coverage after_success: npx codecov diff --git a/appveyor.yml b/appveyor.yml index 078fad304..89c37dbc4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,9 @@ environment: nodejs_version: "8" +# Do not build feature branch with open Pull Requests +skip_branch_with_pr: true + # Cache dependencies cache: - node_modules @@ -19,7 +22,6 @@ test_script: - node --version - npm --version # run tests - - npm run lint - npm run build - npm test diff --git a/build_lualib.ts b/build_lualib.ts index be376fef6..18fb24bf7 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -27,5 +27,8 @@ if (fs.existsSync(bundlePath)) { fs.unlinkSync(bundlePath); } -const bundle = luaLib.loadFeatures(Object.keys(LuaLibFeature).map(lib => LuaLibFeature[lib])); +const features = Object.keys(LuaLibFeature).map( + lib => LuaLibFeature[lib as keyof typeof LuaLibFeature], +); +const bundle = luaLib.loadFeatures(features); fs.writeFileSync(bundlePath, bundle); diff --git a/package.json b/package.json index 1f15d68ea..3c063871b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "scripts": { "build": "tsc -p tsconfig.json && npm run build-lualib", "build-lualib": "ts-node ./build_lualib.ts", - "pretest": "ts-node --transpile-only ./build_lualib.ts", + "pretest": "npm run lint && ts-node --transpile-only ./build_lualib.ts", "test": "jest", "lint": "npm run lint:tslint && npm run lint:prettier", "lint:prettier": "prettier --check **/*.{js,ts,yml,json} || (echo 'Run `npm run fix:prettier` to fix it.' && exit 1)", diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index 50a1fa738..b0b552e15 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -228,7 +228,7 @@ export function parseTsConfigString( } function parseTSTLOptions(commandLine: ts.ParsedCommandLine, args: string[]): CLIParseResult { - const result = {}; + const result: { [key: string]: string | boolean } = {}; for (let i = 0; i < args.length; i++) { if (args[i].startsWith("--")) { const argumentName = args[i].substr(2); @@ -245,7 +245,7 @@ function parseTSTLOptions(commandLine: ts.ParsedCommandLine, args: string[]): CL } } else if (args[i].startsWith("-")) { const argument = args[i].substr(1); - let argumentName: string; + let argumentName: string | undefined; for (const key in optionDeclarations) { if (optionDeclarations[key].aliases && optionDeclarations[key].aliases.indexOf(argument) >= 0) { argumentName = key; @@ -253,7 +253,7 @@ function parseTSTLOptions(commandLine: ts.ParsedCommandLine, args: string[]): CL } } - if (argumentName) { + if (argumentName !== undefined) { const argumentResult = getArgumentValue(argumentName, i, args); if (argumentResult.isValid === true) { result[argumentName] = argumentResult.result; diff --git a/src/Compiler.ts b/src/Compiler.ts index 306182f34..a053b44e2 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -33,14 +33,9 @@ export function compile(argv: string[]): void { /* istanbul ignore next: tested in test/compiler/watchmode.spec with subproccess */ export function watchWithOptions(fileNames: string[], options: CompilerOptions): void { - let host: ts.WatchCompilerHost; - let config = false; - if (options.project) { - config = true; - host = ts.createWatchCompilerHost(options.project, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram); - } else { - host = ts.createWatchCompilerHost(fileNames, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram); - } + const host = options.project !== undefined + ? ts.createWatchCompilerHost(options.project, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram) + : ts.createWatchCompilerHost(fileNames, options, ts.sys, ts.createSemanticDiagnosticsBuilderProgram); let fullRecompile = true; host.afterProgramCreate = program => { @@ -71,7 +66,7 @@ export function watchWithOptions(fileNames: string[], options: CompilerOptions): } const errorDiagnostic: ts.Diagnostic = { - category: undefined, + category: ts.DiagnosticCategory.Error, code: 6194, file: undefined, length: 0, @@ -82,10 +77,13 @@ export function watchWithOptions(fileNames: string[], options: CompilerOptions): errorDiagnostic.messageText = "Found Errors. Watching for file changes."; errorDiagnostic.code = 6193; } - host.onWatchStatusChange(errorDiagnostic, host.getNewLine(), program.getCompilerOptions()); + + if (host.onWatchStatusChange) { + host.onWatchStatusChange(errorDiagnostic, host.getNewLine(), program.getCompilerOptions()); + } }; - if (config) { + if (options.project !== undefined) { ts.createWatchProgram( host as ts.WatchCompilerHostOfConfigFile ); @@ -118,8 +116,8 @@ export function createStringCompilerProgram( ): ts.Program { const compilerHost = { directoryExists: () => true, - fileExists: (fileName): boolean => true, - getCanonicalFileName: fileName => fileName, + fileExists: () => true, + getCanonicalFileName: (fileName: string) => fileName, getCurrentDirectory: () => "", getDefaultLibFileName: ts.getDefaultLibFileName, getDirectories: () => [], @@ -156,7 +154,7 @@ export function createStringCompilerProgram( useCaseSensitiveFileNames: () => false, // Don't write output - writeFile: (name, text, writeByteOrderMark) => undefined, + writeFile: () => undefined, }; const filePaths = typeof input === "string" ? [filePath] : Object.keys(input); return ts.createProgram(filePaths, options, compilerHost); @@ -182,5 +180,10 @@ export function transpileString( const transpiler = new LuaTranspiler(program); - return transpiler.transpileSourceFile(program.getSourceFile(filePath)); + const sourceFile = program.getSourceFile(filePath); + if (sourceFile !== undefined) { + return transpiler.transpileSourceFile(sourceFile); + } else { + throw new Error(`Could not find file ${filePath} in created program.`); + } } diff --git a/src/Decorator.ts b/src/Decorator.ts index 73ac39ea7..7ee84190f 100644 --- a/src/Decorator.ts +++ b/src/Decorator.ts @@ -3,7 +3,7 @@ export class Decorator { return this.getDecoratorKind(decoratorKindString) !== undefined; } - public static getDecoratorKind(decoratorKindString: string): DecoratorKind { + public static getDecoratorKind(decoratorKindString: string): DecoratorKind | undefined { switch (decoratorKindString.toLowerCase()) { case "extension": return DecoratorKind.Extension; @@ -36,7 +36,12 @@ export class Decorator { public args: string[]; constructor(name: string, args: string[]) { - this.kind = Decorator.getDecoratorKind(name); + const kind = Decorator.getDecoratorKind(name); + if (kind === undefined) { + throw new Error(`Failed to parse decorator '${name}'`); + } + + this.kind = kind; this.args = args; } } diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 3ef1f8612..6925fc885 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -109,6 +109,10 @@ export interface Node extends TextRange { } export function createNode(kind: SyntaxKind, tsOriginal?: ts.Node, parent?: Node): Node { + if (tsOriginal === undefined) { + return {kind, parent}; + } + const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { return {kind, parent, line: sourcePosition.line, column: sourcePosition.column}; @@ -128,7 +132,11 @@ export function setNodePosition(node: T, position: TextRange): T return node; } -export function setNodeOriginal(node: T, tsOriginal: ts.Node): T { +export function setNodeOriginal(node: T | undefined, tsOriginal: ts.Node): T | undefined { + if (node === undefined) { + return undefined; + } + const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { setNodePosition(node, sourcePosition); @@ -172,14 +180,14 @@ export function getOriginalPos(node: Node): TextRange { export interface Block extends Node { kind: SyntaxKind.Block; - statements?: Statement[]; + statements: Statement[]; } export function isBlock(node: Node): node is Block { return node.kind === SyntaxKind.Block; } -export function createBlock(statements?: Statement[], tsOriginal?: ts.Node, parent?: Node): Block { +export function createBlock(statements: Statement[], tsOriginal?: ts.Node, parent?: Node): Block { const block = createNode(SyntaxKind.Block, tsOriginal, parent) as Block; setParent(statements, block); block.statements = statements; @@ -192,14 +200,14 @@ export interface Statement extends Node { export interface DoStatement extends Statement { kind: SyntaxKind.DoStatement; - statements?: Statement[]; + statements: Statement[]; } export function isDoStatement(node: Node): node is DoStatement { return node.kind === SyntaxKind.DoStatement; } -export function createDoStatement(statements?: Statement[], tsOriginal?: ts.Node, parent?: Node): DoStatement { +export function createDoStatement(statements: Statement[], tsOriginal?: ts.Node, parent?: Node): DoStatement { const statement = createNode(SyntaxKind.DoStatement, tsOriginal, parent) as DoStatement; setParent(statements, statement); statement.statements = statements; @@ -257,7 +265,7 @@ export function isAssignmentStatement(node: Node): node is AssignmentStatement { export function createAssignmentStatement( left: IdentifierOrTableIndexExpression | IdentifierOrTableIndexExpression[], - right: Expression | Expression[], + right?: Expression | Expression[], tsOriginal?: ts.Node, parent?: Node ): AssignmentStatement @@ -273,7 +281,7 @@ export function createAssignmentStatement( if (Array.isArray(right)) { statement.right = right; } else { - statement.right = [right]; + statement.right = right ? [right] : []; } return statement; } @@ -878,18 +886,19 @@ export function isFunctionDefinition(statement: VariableDeclarationStatement | A : statement is FunctionDefinition { return statement.left.length === 1 - && statement.right + && statement.right !== undefined && statement.right.length === 1 && isFunctionExpression(statement.right[0]); } export type InlineFunctionExpression = FunctionExpression & { - body: { statements: [ReturnStatement]; }; + body: { statements: [ReturnStatement & { expressions: Expression[] }]; }; }; export function isInlineFunctionExpression(expression: FunctionExpression) : expression is InlineFunctionExpression { - return expression.body.statements + return expression.body.statements !== undefined && expression.body.statements.length === 1 && isReturnStatement(expression.body.statements[0]) + && (expression.body.statements[0] as ReturnStatement).expressions !== undefined && (expression.flags & FunctionExpressionFlags.Inline) !== 0; } diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 5b259f66b..2d1c5c134 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -67,8 +67,9 @@ export class LuaLib { function load(feature: LuaLibFeature): void { if (!loadedFeatures.has(feature)) { loadedFeatures.add(feature); - if (luaLibDependencies[feature]) { - luaLibDependencies[feature].forEach(load); + const dependencies = luaLibDependencies[feature]; + if (dependencies) { + dependencies.forEach(load); } const featureFile = path.resolve(__dirname, `../dist/lualib/${feature}.lua`); result += fs.readFileSync(featureFile).toString() + "\n"; diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 3d6af0049..118ae789b 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -43,14 +43,14 @@ export class LuaPrinter { private options: CompilerOptions; private currentIndent: string; - private sourceFile: string; + private sourceFile = ""; public constructor(options: CompilerOptions) { this.options = options; this.currentIndent = ""; } - public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile?: string): [string, string] { + public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile = ""): [string, string] { // Add traceback lualib if sourcemap traceback option is enabled if (this.options.sourceMapTraceback) { if (luaLibFeatures === undefined) { @@ -113,7 +113,7 @@ export class LuaPrinter { private printImplementation( block: tstl.Block, luaLibFeatures?: Set, - sourceFile?: string): SourceNode { + sourceFile = ""): SourceNode { let header = ""; @@ -162,13 +162,15 @@ export class LuaPrinter { private createSourceNode(node: tstl.Node, chunks: SourceChunk | SourceChunk[]): SourceNode { const originalPos = tstl.getOriginalPos(node); - return originalPos !== undefined + return originalPos !== undefined && originalPos.line !== undefined && originalPos.column !== undefined ? new SourceNode(originalPos.line + 1, originalPos.column, this.sourceFile, chunks) - : new SourceNode(undefined, undefined, this.sourceFile, chunks); + // tslint:disable-next-line:no-null-keyword + : new SourceNode(null, null, this.sourceFile, chunks); } private concatNodes(...chunks: SourceChunk[]): SourceNode { - return new SourceNode(undefined, undefined, this.sourceFile, chunks); + // tslint:disable-next-line:no-null-keyword + return new SourceNode(null, null, this.sourceFile, chunks); } private printBlock(block: tstl.Block): SourceNode { @@ -616,19 +618,26 @@ export class LuaPrinter { private printCallExpression(expression: tstl.CallExpression): SourceNode { const chunks = []; - const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e))); - chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")"); + const parameterChunks = expression.params !== undefined + ? expression.params.map(e => this.printExpression(e)) + : []; + + chunks.push(this.printExpression(expression.expression), "(", ...this.joinChunks(", ", parameterChunks), ")"); return this.concatNodes(...chunks); } private printMethodCallExpression(expression: tstl.MethodCallExpression): SourceNode { const prefix = this.printExpression(expression.prefixExpression); - const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e))); + + const parameterChunks = expression.params !== undefined + ? expression.params.map(e => this.printExpression(e)) + : []; + const name = this.printIdentifier(expression.name); - return this.concatNodes(prefix, ":", name, "(", ...parameterChunks, ")"); + return this.concatNodes(prefix, ":", name, "(", ...this.joinChunks(", ", parameterChunks), ")"); } private printIdentifier(expression: tstl.Identifier): SourceNode { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index ccb629938..7a3226625 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -44,26 +44,28 @@ export class LuaTransformer { ]); private isStrict = true; + private luaTarget: LuaTarget; private checker: ts.TypeChecker; protected options: CompilerOptions; protected program: ts.Program; - private isModule: boolean; + private isModule = false; private currentSourceFile?: ts.SourceFile; - private currentNamespace: ts.ModuleDeclaration; - private classStack: ts.ClassLikeDeclaration[]; + private currentNamespace: ts.ModuleDeclaration | undefined; + private classStack: ts.ClassLikeDeclaration[] = []; - private scopeStack: Scope[]; - private genVarCounter: number; + private scopeStack: Scope[] = []; + private genVarCounter = 0; - private luaLibFeatureSet: Set; + private luaLibFeatureSet = new Set(); - private symbolInfo: Map; - private symbolIds: Map; - private genSymbolIdCounter: number; + private symbolInfo = new Map(); + private symbolIds = new Map(); + + private genSymbolIdCounter = 0; private readonly typeValidationCache: Map> = new Map>(); @@ -71,17 +73,18 @@ export class LuaTransformer { this.checker = program.getTypeChecker(); this.options = options; this.program = program; - this.isStrict = this.options.alwaysStrict || (this.options.strict && this.options.alwaysStrict !== false) || - (this.isModule && this.options.target && this.options.target >= ts.ScriptTarget.ES2015); + this.isStrict = this.options.alwaysStrict !== undefined + || (this.options.strict !== undefined && this.options.alwaysStrict !== false) + || (this.isModule + && this.options.target !== undefined + && this.options.target >= ts.ScriptTarget.ES2015); - if (!this.options.luaTarget) { - this.options.luaTarget = LuaTarget.LuaJIT; - } + this.luaTarget = options.luaTarget || LuaTarget.LuaJIT; this.setupState(); } - public setupState(): void { + private setupState(): void { this.genVarCounter = 0; this.currentSourceFile = undefined; this.isModule = false; @@ -108,7 +111,9 @@ export class LuaTransformer { throw TSTLErrors.InvalidJsonFileContent(node); } - statements.push(tstl.createReturnStatement([this.transformExpression(statement.expression)])); + statements.push(tstl.createReturnStatement( + this.filterUndefined([this.transformExpression(statement.expression)])) + ); } else { this.pushScope(ScopeType.File, node); @@ -171,7 +176,7 @@ export class LuaTransformer { case ts.SyntaxKind.ExpressionStatement: return this.transformExpressionStatement(node as ts.ExpressionStatement); case ts.SyntaxKind.ReturnStatement: - return this.transformReturn(node as ts.ReturnStatement); + return this.transformReturnStatement(node as ts.ReturnStatement); case ts.SyntaxKind.IfStatement: return this.transformIfStatement(node as ts.IfStatement); case ts.SyntaxKind.WhileStatement: @@ -204,10 +209,10 @@ export class LuaTransformer { } /** Converts an array of ts.Statements into an array of tstl.Statements */ - public transformStatements(statements: ts.Statement[] | ReadonlyArray): tstl.Statement[] { + private transformStatements(statements: ts.Statement[] | ReadonlyArray): tstl.Statement[] { const tstlStatements: tstl.Statement[] = []; (statements as ts.Statement[]).forEach(statement => { - tstlStatements.push(...this.statementVisitResultToStatementArray(this.transformStatement(statement))); + tstlStatements.push(...this.statementVisitResultToArray(this.transformStatement(statement))); }); return tstlStatements; } @@ -219,7 +224,7 @@ export class LuaTransformer { return tstl.createBlock(statements, block); } - public transformBlockAsDoStatement(block: ts.Block): tstl.DoStatement { + public transformBlockAsDoStatement(block: ts.Block): StatementVisitResult { this.pushScope(ScopeType.Block, block); const statements = this.performHoisting(this.transformStatements(block.statements)); this.popScope(); @@ -228,6 +233,10 @@ export class LuaTransformer { public transformExportDeclaration(statement: ts.ExportDeclaration): StatementVisitResult { if (statement.moduleSpecifier === undefined) { + if (statement.exportClause === undefined) { + throw TSTLErrors.InvalidExportDeclaration(statement); + } + const result = []; for (const exportElement of statement.exportClause.elements) { result.push( @@ -242,8 +251,9 @@ export class LuaTransformer { if (statement.exportClause) { if (statement.exportClause.elements.some(e => - (e.name && e.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) - || (e.propertyName && e.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword)) + (e.name !== undefined && e.name.originalKeywordKind === ts.SyntaxKind.DefaultKeyword) + || (e.propertyName !== undefined + && e.propertyName.originalKeywordKind === ts.SyntaxKind.DefaultKeyword)) ) { throw TSTLErrors.UnsupportedDefaultExport(statement); } @@ -278,7 +288,7 @@ export class LuaTransformer { } // Wrap this in a DoStatement to prevent polluting the scope. - return tstl.createDoStatement(result, statement); + return tstl.createDoStatement(this.filterUndefined(result), statement); } else { const moduleRequire = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral); const tempModuleIdentifier = tstl.createIdentifier("__TSTL_export"); @@ -327,6 +337,10 @@ export class LuaTransformer { } const imports = statement.importClause.namedBindings; + if (imports === undefined) { + throw TSTLErrors.UnsupportedImportType(statement.importClause); + } + const type = this.checker.getTypeAtLocation(imports); const shouldResolve = !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoResolution); const requireCall = this.createModuleRequire(statement.moduleSpecifier as ts.StringLiteral, shouldResolve); @@ -379,14 +393,12 @@ export class LuaTransformer { ); result.push(requireStatement); return result; - } else { - throw TSTLErrors.UnsupportedImportType(imports); } } private createModuleRequire(moduleSpecifier: ts.StringLiteral, resolveModule = true): tstl.CallExpression { const modulePathString = resolveModule - ? this.getImportPath(moduleSpecifier.text.replace(new RegExp("\"", "g"), "")) + ? this.getImportPath(moduleSpecifier.text.replace(new RegExp("\"", "g"), ""), moduleSpecifier) : moduleSpecifier.text; const modulePath = tstl.createStringLiteral(modulePathString); return tstl.createCallExpression(tstl.createIdentifier("require"), [modulePath]); @@ -395,7 +407,7 @@ export class LuaTransformer { public transformClassDeclaration( statement: ts.ClassLikeDeclaration, nameOverride?: tstl.Identifier - ): tstl.Statement[] + ): StatementVisitResult { this.classStack.push(statement); @@ -403,14 +415,20 @@ export class LuaTransformer { throw TSTLErrors.MissingClassName(statement); } - let className = nameOverride !== undefined - ? nameOverride - : this.transformIdentifier(statement.name); + let className: tstl.Identifier; + if (nameOverride !== undefined) { + className = nameOverride; + } else if (statement.name !== undefined) { + className = this.transformIdentifier(statement.name); + } else { + throw TSTLErrors.MissingClassName(statement); + } const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(statement), this.checker); // Find out if this class is extension of existing class - const isExtension = decorators.has(DecoratorKind.Extension); + const extensionDirective = decorators.get(DecoratorKind.Extension); + const isExtension = extensionDirective !== undefined; const isMetaExtension = decorators.has(DecoratorKind.MetaExtension); @@ -469,8 +487,8 @@ export class LuaTransformer { result.push(assignDebugCallIndex); } - if (isExtension) { - const extensionNameArg = decorators.get(DecoratorKind.Extension).args[0]; + if (extensionDirective !== undefined) { + const extensionNameArg = extensionDirective.args[0]; if (extensionNameArg) { className = tstl.createIdentifier(extensionNameArg); } else if (extendsType) { @@ -487,9 +505,11 @@ export class LuaTransformer { result.push(...classCreationMethods); } else { for (const f of instanceFields) { - const fieldName = this.transformPropertyName(f.name); + const fieldName = this.expectExpression(this.transformPropertyName(f.name)); - const value = this.transformExpression(f.initializer); + const value = f.initializer !== undefined + ? this.transformExpression(f.initializer) + : undefined; // className["fieldName"] const classField = tstl.createTableIndexExpression( @@ -509,15 +529,22 @@ export class LuaTransformer { .filter(n => ts.isConstructorDeclaration(n) && n.body)[0] as ts.ConstructorDeclaration; if (constructor) { // Add constructor plus initialization of instance fields - result.push(this.transformConstructor(constructor, className, instanceFields, statement)); + const constructorResult = this.transformConstructorDeclaration( + constructor, + className, + instanceFields, + statement + ); + result.push(...this.statementVisitResultToArray(constructorResult)); } else if (!extendsType) { // Generate a constructor if none was defined in a base class - result.push(this.transformConstructor( + const constructorResult = this.transformConstructorDeclaration( ts.createConstructor([], [], [], ts.createBlock([], true)), className, instanceFields, statement - )); + ); + result.push(...this.statementVisitResultToArray(constructorResult)); } else if (instanceFields.length > 0 || statement.members.some(m => tsHelper.isGetAccessorOverride(m, statement, this.checker))) { @@ -529,7 +556,7 @@ export class LuaTransformer { const superCall = tstl.createExpressionStatement( tstl.createCallExpression( tstl.createTableIndexExpression( - this.transformSuperKeyword(ts.createSuper()), + this.expectExpression(this.transformSuperKeyword(ts.createSuper())), tstl.createStringLiteral("____constructor") ), [this.createSelfIdentifier(), tstl.createDotsLiteral()] @@ -553,23 +580,26 @@ export class LuaTransformer { // Transform get accessors statement.members.filter(ts.isGetAccessor).forEach(getAccessor => { - result.push(this.transformGetAccessorDeclaration(getAccessor, className, statement)); + const transformResult = this.transformGetAccessorDeclaration(getAccessor, className, statement); + result.push(...this.statementVisitResultToArray(transformResult)); }); // Transform set accessors statement.members.filter(ts.isSetAccessor).forEach(setAccessor => { - result.push(this.transformSetAccessorDeclaration(setAccessor, className, statement)); + const transformResult = this.transformSetAccessorDeclaration(setAccessor, className, statement); + result.push(...this.statementVisitResultToArray(transformResult)); }); // Transform methods statement.members.filter(ts.isMethodDeclaration).forEach(method => { - result.push(this.transformMethodDeclaration(method, className, isExtension || isMetaExtension)); + const methodResult = this.transformMethodDeclaration(method, className, isExtension || isMetaExtension); + result.push(...this.statementVisitResultToArray(methodResult)); }); // Add static declarations for (const field of staticFields) { - const fieldName = this.transformPropertyName(field.name); - const value = this.transformExpression(field.initializer); + const fieldName = this.expectExpression(this.transformPropertyName(field.name)); + const value = field.initializer ? this.transformExpression(field.initializer) : undefined; const classField = tstl.createTableIndexExpression( tstl.cloneIdentifier(className), @@ -592,7 +622,7 @@ export class LuaTransformer { public createClassCreationMethods( statement: ts.ClassLikeDeclarationBase, className: tstl.Identifier, - extendsType: ts.Type + extendsType?: ts.Type ): tstl.Statement[] { const result: tstl.Statement[] = []; @@ -756,6 +786,10 @@ export class LuaTransformer { if (extendsType) { const extendedTypeNode = tsHelper.getExtendedTypeNode(statement, this.checker); + if (extendedTypeNode === undefined) { + throw TSTLErrors.UndefinedTypeNode(statement); + } + const baseName = ts.isIdentifier(extendedTypeNode.expression) ? this.transformIdentifier(extendedTypeNode.expression) // Skip adding '____exports' : this.transformExpression(extendedTypeNode.expression); @@ -910,7 +944,7 @@ export class LuaTransformer { return result; } - public transformClassInstanceFields( + private transformClassInstanceFields( classDeclarataion: ts.ClassLikeDeclaration, instanceFields: ts.PropertyDeclaration[] ): tstl.Statement[] @@ -919,9 +953,9 @@ export class LuaTransformer { for (const f of instanceFields) { // Get identifier - const fieldName = this.transformPropertyName(f.name); + const fieldName = this.expectExpression(this.transformPropertyName(f.name)); - const value = this.transformExpression(f.initializer); + const value = f.initializer ? this.transformExpression(f.initializer) : undefined; // self[fieldName] const selfIndex = tstl.createTableIndexExpression(this.createSelfIdentifier(), fieldName); @@ -932,14 +966,17 @@ export class LuaTransformer { statements.push(assignClassField); } - const getOverrides = classDeclarataion.members.filter( - m => tsHelper.isGetAccessorOverride(m, classDeclarataion, this.checker) - ); + const getOverrides = classDeclarataion.members.filter(m => + tsHelper.isGetAccessorOverride(m, classDeclarataion, this.checker) + ) as ts.GetAccessorDeclaration[]; + for (const getter of getOverrides) { + const getterName = this.expectExpression(this.transformPropertyName(getter.name)); + const resetGetter = tstl.createExpressionStatement( tstl.createCallExpression( tstl.createIdentifier("rawset"), - [this.createSelfIdentifier(), this.transformPropertyName(getter.name), tstl.createNilLiteral()] + [this.createSelfIdentifier(), getterName, tstl.createNilLiteral()] ) ); statements.push(resetGetter); @@ -948,7 +985,7 @@ export class LuaTransformer { return statements; } - public createConstructorName(className: tstl.Identifier): tstl.TableIndexExpression { + private createConstructorName(className: tstl.Identifier): tstl.TableIndexExpression { return tstl.createTableIndexExpression( tstl.createTableIndexExpression( tstl.cloneIdentifier(className), @@ -958,12 +995,12 @@ export class LuaTransformer { ); } - public transformConstructor( + public transformConstructorDeclaration( statement: ts.ConstructorDeclaration, className: tstl.Identifier, instanceFields: ts.PropertyDeclaration[], classDeclaration: ts.ClassLikeDeclaration - ): tstl.AssignmentStatement + ): StatementVisitResult { // Don't transform methods without body (overload declarations) if (!statement.body) { @@ -986,7 +1023,8 @@ export class LuaTransformer { ), tstl.createBinaryExpression( declarationName, - this.transformExpression(declaration.initializer), tstl.SyntaxKind.OrOperator + this.expectExpression(this.transformExpression(declaration.initializer)), + tstl.SyntaxKind.OrOperator ) ); bodyStatements.push(assignement); @@ -1034,8 +1072,12 @@ export class LuaTransformer { getAccessor: ts.GetAccessorDeclaration, className: tstl.Identifier, classDeclaration: ts.ClassLikeDeclaration - ): tstl.AssignmentStatement + ): StatementVisitResult { + if (getAccessor.body === undefined) { + return undefined; + } + const name = this.transformIdentifier(getAccessor.name as ts.Identifier); const [body] = this.transformFunctionBody(getAccessor.parameters, getAccessor.body); @@ -1067,8 +1109,12 @@ export class LuaTransformer { setAccessor: ts.SetAccessorDeclaration, className: tstl.Identifier, classDeclaration: ts.ClassLikeDeclaration - ): tstl.AssignmentStatement + ): StatementVisitResult { + if (setAccessor.body === undefined) { + return undefined; + } + const name = this.transformIdentifier(setAccessor.name as ts.Identifier); const [params, dot, restParam] = this.transformParameters(setAccessor.parameters, this.createSelfIdentifier()); @@ -1102,14 +1148,14 @@ export class LuaTransformer { node: ts.MethodDeclaration, className: tstl.Identifier, noPrototype: boolean - ): tstl.AssignmentStatement + ): StatementVisitResult { // Don't transform methods without body (overload declarations) if (!node.body) { return undefined; } - let methodName = this.transformPropertyName(node.name); + let methodName = this.expectExpression(this.transformPropertyName(node.name)); if (tstl.isStringLiteral(methodName) && methodName.value === "toString") { methodName = tstl.createStringLiteral("__tostring", node.name); } @@ -1143,16 +1189,16 @@ export class LuaTransformer { ); } - public transformParameters(parameters: ts.NodeArray, context?: tstl.Identifier): - [tstl.Identifier[], tstl.DotsLiteral, tstl.Identifier | undefined] { + private transformParameters(parameters: ts.NodeArray, context?: tstl.Identifier): + [tstl.Identifier[], tstl.DotsLiteral | undefined, tstl.Identifier | undefined] { // Build parameter string const paramNames: tstl.Identifier[] = []; if (context) { paramNames.push(context); } - let restParamName: tstl.Identifier; - let dotsLiteral: tstl.DotsLiteral; + let restParamName: tstl.Identifier | undefined; + let dotsLiteral: tstl.DotsLiteral | undefined; let identifierIndex = 0; // Only push parameter name to paramName array if it isn't a spread parameter @@ -1180,7 +1226,7 @@ export class LuaTransformer { return [paramNames, dotsLiteral, restParamName]; } - public transformFunctionBody( + private transformFunctionBody( parameters: ts.NodeArray, body: ts.Block, spreadIdentifier?: tstl.Identifier @@ -1222,9 +1268,9 @@ export class LuaTransformer { return [headerStatements.concat(bodyStatements), scope]; } - public transformParameterDefaultValueDeclaration(declaration: ts.ParameterDeclaration): tstl.Statement { + private transformParameterDefaultValueDeclaration(declaration: ts.ParameterDeclaration): tstl.Statement { const parameterName = this.transformIdentifier(declaration.name as ts.Identifier); - const parameterValue = this.transformExpression(declaration.initializer); + const parameterValue = declaration.initializer ? this.transformExpression(declaration.initializer) : undefined; const assignment = tstl.createAssignmentStatement(parameterName, parameterValue); const nilCondition = tstl.createBinaryExpression( @@ -1253,7 +1299,9 @@ export class LuaTransformer { const propertyName = isObjectBindingPattern ? element.propertyName : ts.createNumericLiteral(String(index + 1)); - propertyAccessStack.push(propertyName); + if (propertyName !== undefined) { + propertyAccessStack.push(propertyName); + } yield* this.transformBindingPattern(element.name, table, propertyAccessStack); } else { // Disallow ellipsis destructure @@ -1266,7 +1314,10 @@ export class LuaTransformer { const propertyName = ts.isPropertyName(property) ? this.transformPropertyName(property) : this.transformNumericLiteral(property); - tableExpression = tstl.createTableIndexExpression(tableExpression, propertyName); + tableExpression = tstl.createTableIndexExpression( + tableExpression, + this.expectExpression(propertyName) + ); }); // The identifier of the new variable const variableName = this.transformIdentifier(element.name as ts.Identifier); @@ -1278,7 +1329,9 @@ export class LuaTransformer { : tstl.createTableIndexExpression(tableExpression, tstl.createNumericLiteral(index + 1)); if (element.initializer) { const defaultExpression = tstl.createBinaryExpression(expression, - this.transformExpression(element.initializer), tstl.SyntaxKind.OrOperator); + this.expectExpression(this.transformExpression(element.initializer)), + tstl.SyntaxKind.OrOperator + ); yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, defaultExpression); } else { yield* this.createLocalOrExportedOrGlobalDeclaration(variableName, expression); @@ -1289,7 +1342,7 @@ export class LuaTransformer { propertyAccessStack.pop(); } - public transformModuleDeclaration(statement: ts.ModuleDeclaration): tstl.Statement[] { + public transformModuleDeclaration(statement: ts.ModuleDeclaration): StatementVisitResult { const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(statement), this.checker); // If phantom namespace elide the declaration and return the body if (decorators.has(DecoratorKind.Phantom) && statement.body && ts.isModuleBlock(statement.body)) { @@ -1373,7 +1426,7 @@ export class LuaTransformer { let statements = ts.isModuleBlock(statement.body) ? this.transformStatements(statement.body.statements) : this.transformModuleDeclaration(statement.body); - statements = this.performHoisting(statements); + statements = this.performHoisting(this.statementVisitResultToArray(statements)); this.popScope(); result.push(tstl.createDoStatement(statements)); } @@ -1402,7 +1455,7 @@ export class LuaTransformer { } for (const enumMember of this.computeEnumMembers(enumDeclaration)) { - const memberName = this.transformPropertyName(enumMember.name); + const memberName = this.expectExpression(this.transformPropertyName(enumMember.name)); if (membersOnly) { if (tstl.isIdentifier(memberName)) { result.push(...this.createLocalOrExportedOrGlobalDeclaration( @@ -1430,15 +1483,15 @@ export class LuaTransformer { return result; } - public computeEnumMembers(node: ts.EnumDeclaration): + protected computeEnumMembers(node: ts.EnumDeclaration): Array<{name: ts.PropertyName, value: tstl.Expression, original: ts.Node}> { let numericValue = 0; let hasStringInitializers = false; - const valueMap = new Map(); + const valueMap = new Map(); return node.members.map(member => { - let valueExpression: tstl.Expression; + let valueExpression: ExpressionVisitResult; if (member.initializer) { if (ts.isNumericLiteral(member.initializer)) { @@ -1455,7 +1508,7 @@ export class LuaTransformer { { if (ts.isIdentifier(member.initializer)) { const [isEnumMember, originalName] = tsHelper.isEnumMember(node, member.initializer); - if (isEnumMember) { + if (isEnumMember === true && originalName !== undefined) { valueExpression = valueMap.get(originalName); } else { valueExpression = this.transformExpression(member.initializer); @@ -1480,7 +1533,7 @@ export class LuaTransformer { const enumMember = { name: member.name, original: member, - value: valueExpression, + value: this.expectExpression(valueExpression), }; return enumMember; @@ -1636,6 +1689,10 @@ export class LuaTransformer { : undefined; const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context); + if (functionDeclaration.name === undefined) { + throw TSTLErrors.MissingFunctionName(functionDeclaration); + } + const name = this.transformIdentifier(functionDeclaration.name); const [body, functionScope] = functionDeclaration.asteriskToken ? this.transformGeneratorFunction( @@ -1666,18 +1723,17 @@ export class LuaTransformer { return this.createLocalOrExportedOrGlobalDeclaration(name, functionExpression, functionDeclaration); } - public transformTypeAliasDeclaration(statement: ts.TypeAliasDeclaration): undefined { + public transformTypeAliasDeclaration(statement: ts.TypeAliasDeclaration): StatementVisitResult { return undefined; } - public transformInterfaceDeclaration(statement: ts.InterfaceDeclaration): undefined { + public transformInterfaceDeclaration(statement: ts.InterfaceDeclaration): StatementVisitResult { return undefined; } - public transformVariableDeclaration(statement: ts.VariableDeclaration) - : tstl.Statement[] + public transformVariableDeclaration(statement: ts.VariableDeclaration): StatementVisitResult { - if (statement.initializer) { + if (statement.initializer && statement.type) { // Validate assignment const initializerType = this.checker.getTypeAtLocation(statement.initializer); const varType = this.checker.getTypeFromTypeNode(statement.type); @@ -1705,13 +1761,15 @@ export class LuaTransformer { || statement.name.elements.some(elem => !ts.isBindingElement(elem) || !ts.isIdentifier(elem.name))) { const statements = []; let table: tstl.Identifier; - if (ts.isIdentifier(statement.initializer)) { + if (statement.initializer !== undefined && ts.isIdentifier(statement.initializer)) { table = this.transformIdentifier(statement.initializer); } else { // Contain the expression in a temporary variable table = tstl.createAnnonymousIdentifier(); - statements.push(tstl.createVariableDeclarationStatement( - table, this.transformExpression(statement.initializer))); + if (statement.initializer) { + statements.push(tstl.createVariableDeclarationStatement( + table, this.transformExpression(statement.initializer))); + } } statements.push(...this.transformBindingPattern(statement.name, table)); return statements; @@ -1723,7 +1781,9 @@ export class LuaTransformer { } const vars = statement.name.elements.length > 0 - ? statement.name.elements.map(e => this.transformArrayBindingElement(e)) + ? this.filterUndefinedAndCast( + statement.name.elements.map(e => this.transformArrayBindingElement(e)), + tstl.isIdentifier) : tstl.createAnnonymousIdentifier(statement.name); // Don't unpack TupleReturn decorated functions @@ -1737,7 +1797,7 @@ export class LuaTransformer { } else { // local vars = this.transpileDestructingAssignmentValue(node.initializer); const initializer = this.createUnpackCall( - this.transformExpression(statement.initializer), + this.expectExpression(this.transformExpression(statement.initializer)), statement.initializer ); return this.createLocalOrExportedOrGlobalDeclaration(vars, initializer, statement); @@ -1752,18 +1812,20 @@ export class LuaTransformer { } } - public transformVariableStatement(statement: ts.VariableStatement): tstl.Statement[] { + public transformVariableStatement(statement: ts.VariableStatement): StatementVisitResult { const result: tstl.Statement[] = []; - statement.declarationList.declarations - .forEach(declaration => result.push(...this.transformVariableDeclaration(declaration))); + statement.declarationList.declarations.forEach(declaration => { + const declarationStatements = this.transformVariableDeclaration(declaration); + result.push(...this.statementVisitResultToArray(declarationStatements)); + }); return result; } - public transformExpressionStatement(statement: ts.ExpressionStatement | ts.Expression): tstl.Statement { + public transformExpressionStatement(statement: ts.ExpressionStatement | ts.Expression): StatementVisitResult { const expression = ts.isExpressionStatement(statement) ? statement.expression : statement; if (ts.isBinaryExpression(expression)) { const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind); - if (isCompound) { + if (isCompound && replacementOperator) { // +=, -=, etc... return this.transformCompoundAssignmentStatement( expression, @@ -1777,9 +1839,9 @@ export class LuaTransformer { return this.transformAssignmentStatement(expression); } else if (expression.operatorToken.kind === ts.SyntaxKind.CommaToken) { - const lhs = this.transformExpressionStatement(expression.left); - const rhs = this.transformExpressionStatement(expression.right); - return tstl.createDoStatement([lhs, rhs], expression); + const lhs = this.statementVisitResultToArray(this.transformExpressionStatement(expression.left)); + const rhs = this.statementVisitResultToArray(this.transformExpressionStatement(expression.right)); + return tstl.createDoStatement([...lhs, ...rhs], expression); } } else if ( @@ -1829,16 +1891,22 @@ export class LuaTransformer { ); } - return tstl.createExpressionStatement(this.transformExpression(expression)); + return tstl.createExpressionStatement(this.expectExpression(this.transformExpression(expression))); } - public transformYield(expression: ts.YieldExpression): tstl.Expression { + public transformYield(expression: ts.YieldExpression): ExpressionVisitResult { return tstl.createCallExpression( - tstl.createTableIndexExpression(tstl.createIdentifier("coroutine"), tstl.createStringLiteral("yield")), - expression.expression?[this.transformExpression(expression.expression)]:[], expression); + tstl.createTableIndexExpression( + tstl.createIdentifier("coroutine"), + tstl.createStringLiteral("yield")), + expression.expression + ? [this.expectExpression(this.transformExpression(expression.expression))] + : [], + expression + ); } - public transformReturn(statement: ts.ReturnStatement): tstl.Statement { + public transformReturnStatement(statement: ts.ReturnStatement): StatementVisitResult { if (statement.expression) { const returnType = tsHelper.getContainingFunctionReturnType(statement, this.checker); if (returnType) { @@ -1849,8 +1917,9 @@ export class LuaTransformer { // Parent function is a TupleReturn function if (ts.isArrayLiteralExpression(statement.expression)) { // If return expression is an array literal, leave out brackets. - return tstl.createReturnStatement(statement.expression.elements - .map(elem => this.transformExpression(elem))); + return tstl.createReturnStatement(this.filterUndefined( + statement.expression.elements.map(elem => this.transformExpression(elem)) + )); } const expressionType = this.checker.getTypeAtLocation(statement.expression); @@ -1859,28 +1928,30 @@ export class LuaTransformer { { // If return expression is an array-type and not another TupleReturn call, unpack it const expression = this.createUnpackCall( - this.transformExpression(statement.expression), + this.expectExpression(this.transformExpression(statement.expression)), statement.expression ); return tstl.createReturnStatement([expression]); } } - return tstl.createReturnStatement([this.transformExpression(statement.expression)], statement); + const returnExpressions = [this.expectExpression(this.transformExpression(statement.expression))]; + return tstl.createReturnStatement(returnExpressions, statement); } else { // Empty return return tstl.createReturnStatement([], statement); } } - public transformIfStatement(statement: ts.IfStatement): tstl.IfStatement { + public transformIfStatement(statement: ts.IfStatement): StatementVisitResult { this.pushScope(ScopeType.Conditional, statement.thenStatement); - const condition = this.transformExpression(statement.expression); + const condition = this.expectExpression(this.transformExpression(statement.expression)); const statements = this.performHoisting(this.transformBlockOrStatement(statement.thenStatement)); this.popScope(); const ifBlock = tstl.createBlock(statements); if (statement.elseStatement) { if (ts.isIfStatement(statement.elseStatement)) { - return tstl.createIfStatement(condition, ifBlock, this.transformIfStatement(statement.elseStatement)); + const elseStatement = this.transformIfStatement(statement.elseStatement) as tstl.IfStatement; + return tstl.createIfStatement(condition, ifBlock, elseStatement); } else { this.pushScope(ScopeType.Conditional, statement.elseStatement); const elseStatements = this.performHoisting(this.transformBlockOrStatement(statement.elseStatement)); @@ -1892,36 +1963,40 @@ export class LuaTransformer { return tstl.createIfStatement(condition, ifBlock); } - public transformWhileStatement(statement: ts.WhileStatement): tstl.WhileStatement { + public transformWhileStatement(statement: ts.WhileStatement): StatementVisitResult { return tstl.createWhileStatement( tstl.createBlock(this.transformLoopBody(statement)), - this.transformExpression(statement.expression), + this.expectExpression(this.transformExpression(statement.expression)), statement ); } - public transformDoStatement(statement: ts.DoStatement): tstl.RepeatStatement { + public transformDoStatement(statement: ts.DoStatement): StatementVisitResult { return tstl.createRepeatStatement( tstl.createBlock(this.transformLoopBody(statement)), tstl.createUnaryExpression( - tstl.createParenthesizedExpression(this.transformExpression(statement.expression)), + tstl.createParenthesizedExpression( + this.expectExpression(this.transformExpression(statement.expression)) + ), tstl.SyntaxKind.NotOperator ), statement ); } - public transformForStatement(statement: ts.ForStatement): tstl.DoStatement { + public transformForStatement(statement: ts.ForStatement): StatementVisitResult { const result: tstl.Statement[] = []; if (statement.initializer) { if (ts.isVariableDeclarationList(statement.initializer)) { for (const variableDeclaration of statement.initializer.declarations) { // local initializer = value - result.push(...this.transformVariableDeclaration(variableDeclaration)); + const declarations = this.transformVariableDeclaration(variableDeclaration); + result.push(...this.statementVisitResultToArray(declarations)); } } else { - result.push(this.transformExpressionStatement(statement.initializer)); + const initializerStatements = this.transformExpressionStatement(statement.initializer); + result.push(...this.statementVisitResultToArray(initializerStatements)); } } @@ -1933,11 +2008,12 @@ export class LuaTransformer { const body: tstl.Statement[] = this.transformLoopBody(statement); if (statement.incrementor) { - body.push(this.transformExpressionStatement(statement.incrementor)); + const bodyStatements = this.transformExpressionStatement(statement.incrementor); + body.push(...this.statementVisitResultToArray(bodyStatements)); } // while (condition) do ... end - result.push(tstl.createWhileStatement(tstl.createBlock(body), condition)); + result.push(tstl.createWhileStatement(tstl.createBlock(body), this.expectExpression(condition))); return tstl.createDoStatement(result, statement); } @@ -1949,9 +2025,15 @@ export class LuaTransformer { if (ts.isArrayBindingPattern(initializer.declarations[0].name)) { expression = this.createUnpackCall(expression, initializer); } - // we can safely assume that for vars are not exported and therefore declarationstatenents - return tstl.createVariableDeclarationStatement( - (variableDeclarations[0] as tstl.VariableDeclarationStatement).left, expression); + + const variableStatements = this.statementVisitResultToArray(variableDeclarations); + if (variableStatements[0]) { + // we can safely assume that for vars are not exported and therefore declarationstatenents + return tstl.createVariableDeclarationStatement( + (variableStatements[0] as tstl.VariableDeclarationStatement).left, expression); + } else { + throw TSTLErrors.MissingForOfVariables(initializer); + } } else { // Assignment to existing variable @@ -1990,11 +2072,11 @@ export class LuaTransformer { public transformBlockOrStatement(statement: ts.Statement): tstl.Statement[] { return ts.isBlock(statement) ? this.transformStatements(statement.statements) - : this.statementVisitResultToStatementArray(this.transformStatement(statement)); + : this.statementVisitResultToArray(this.transformStatement(statement)); } public transformForOfArrayStatement(statement: ts.ForOfStatement, block: tstl.Block): StatementVisitResult { - const arrayExpression = this.transformExpression(statement.expression); + const arrayExpression = this.expectExpression(this.transformExpression(statement.expression)); // Arrays use numeric for loop (performs better than ipairs) const indexVariable = tstl.createIdentifier("____TS_index"); @@ -2034,7 +2116,7 @@ export class LuaTransformer { } public transformForOfLuaIteratorStatement(statement: ts.ForOfStatement, block: tstl.Block): StatementVisitResult { - const luaIterator = this.transformExpression(statement.expression); + const luaIterator = this.expectExpression(this.transformExpression(statement.expression)); const type = this.checker.getTypeAtLocation(statement.expression); const tupleReturn = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.TupleReturn); if (tupleReturn) { @@ -2046,7 +2128,9 @@ export class LuaTransformer { if (ts.isArrayBindingPattern(initializerVariable)) { return tstl.createForInStatement( block, - initializerVariable.elements.map(e => this.transformArrayBindingElement(e)), + this.filterUndefinedAndCast( + initializerVariable.elements.map(e => this.transformArrayBindingElement(e)), + tstl.isIdentifier), [luaIterator] ); @@ -2105,7 +2189,7 @@ export class LuaTransformer { } public transformForOfIteratorStatement(statement: ts.ForOfStatement, block: tstl.Block): StatementVisitResult { - const iterable = this.transformExpression(statement.expression); + const iterable = this.expectExpression(this.transformExpression(statement.expression)); if (ts.isVariableDeclarationList(statement.initializer) && ts.isIdentifier(statement.initializer.declarations[0].name)) { // Single variable declared in for loop @@ -2160,7 +2244,8 @@ export class LuaTransformer { // Transpile expression const pairsIdentifier = tstl.createIdentifier("pairs"); - const expression = tstl.createCallExpression(pairsIdentifier, [this.transformExpression(statement.expression)]); + const expression = this.expectExpression(this.transformExpression(statement.expression)); + const pairsCall = tstl.createCallExpression(pairsIdentifier, [expression]); if (tsHelper.isArrayType(this.checker.getTypeAtLocation(statement.expression), this.checker, this.program)) { throw TSTLErrors.ForbiddenForIn(statement); @@ -2171,14 +2256,14 @@ export class LuaTransformer { return tstl.createForInStatement( body, [this.transformIdentifier(identifier)], - [expression], + [pairsCall], statement ); } public transformSwitchStatement(statement: ts.SwitchStatement): StatementVisitResult { - if (this.options.luaTarget === LuaTarget.Lua51) { - throw TSTLErrors.UnsupportedForTarget("Switch statements", this.options.luaTarget, statement); + if (this.luaTarget === LuaTarget.Lua51) { + throw TSTLErrors.UnsupportedForTarget("Switch statements", this.luaTarget, statement); } this.pushScope(ScopeType.Switch, statement); @@ -2199,7 +2284,7 @@ export class LuaTransformer { // If the clause condition holds, go to the correct label const condition = tstl.createBinaryExpression( switchVariable, - this.transformExpression(clause.expression), + this.expectExpression(this.transformExpression(clause.expression)), tstl.SyntaxKind.EqualityOperator ); const goto = tstl.createGotoStatement(`${switchName}_case_${i}`); @@ -2234,6 +2319,11 @@ export class LuaTransformer { public transformBreakStatement(breakStatement: ts.BreakStatement): StatementVisitResult { const breakableScope = this.findScope(ScopeType.Loop | ScopeType.Switch); + + if (breakableScope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + if (breakableScope.type === ScopeType.Switch) { return tstl.createGotoStatement(`____TS_switch${breakableScope.id}_end`); } else { @@ -2280,11 +2370,18 @@ export class LuaTransformer { } public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult { + if (statement.expression === undefined) { + throw TSTLErrors.InvalidThrowExpression(statement); + } + const type = this.checker.getTypeAtLocation(statement.expression); if (tsHelper.isStringType(type)) { const error = tstl.createIdentifier("error"); return tstl.createExpressionStatement( - tstl.createCallExpression(error, [this.transformExpression(statement.expression)]), + tstl.createCallExpression( + error, + this.filterUndefined([this.transformExpression(statement.expression)]) + ), statement ); } else { @@ -2293,11 +2390,15 @@ export class LuaTransformer { } public transformContinueStatement(statement: ts.ContinueStatement): StatementVisitResult { - if (this.options.luaTarget === LuaTarget.Lua51) { - throw TSTLErrors.UnsupportedForTarget("Continue statement", this.options.luaTarget, statement); + if (this.luaTarget === LuaTarget.Lua51) { + throw TSTLErrors.UnsupportedForTarget("Continue statement", this.luaTarget, statement); } const scope = this.findScope(ScopeType.Loop); + if (scope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + scope.loopContinued = true; return tstl.createGotoStatement( `__continue${scope.id}`, @@ -2388,7 +2489,7 @@ export class LuaTransformer { right: tstl.Expression, operator: ts.BinaryOperator, tsOriginal: ts.Node - ): tstl.Expression + ): ExpressionVisitResult { switch (operator) { case ts.SyntaxKind.AmpersandToken: @@ -2408,11 +2509,11 @@ export class LuaTransformer { } } - public transformBinaryExpression(expression: ts.BinaryExpression): tstl.Expression { + public transformBinaryExpression(expression: ts.BinaryExpression): ExpressionVisitResult { // Check if this is an assignment token, then handle accordingly const [isCompound, replacementOperator] = tsHelper.isBinaryAssignmentToken(expression.operatorToken.kind); - if (isCompound) { + if (isCompound && replacementOperator) { return this.transformCompoundAssignmentExpression( expression, expression.left, @@ -2422,8 +2523,8 @@ export class LuaTransformer { ); } - const lhs = this.transformExpression(expression.left); - const rhs = this.transformExpression(expression.right); + const lhs = this.expectExpression(this.transformExpression(expression.left)); + const rhs = this.expectExpression(this.transformExpression(expression.right)); // Transpile operators switch (expression.operatorToken.kind) { @@ -2475,7 +2576,7 @@ export class LuaTransformer { case ts.SyntaxKind.CommaToken: return this.createImmediatelyInvokedFunctionExpression( - [this.transformExpressionStatement(expression.left)], + this.statementVisitResultToArray(this.transformExpressionStatement(expression.left)), rhs, expression ); @@ -2485,7 +2586,7 @@ export class LuaTransformer { } } - public transformAssignment(lhs: ts.Expression, right: tstl.Expression): tstl.Statement { + private transformAssignment(lhs: ts.Expression, right?: tstl.Expression): tstl.Statement { return tstl.createAssignmentStatement( this.transformExpression(lhs) as tstl.IdentifierOrTableIndexExpression, right, @@ -2493,7 +2594,7 @@ export class LuaTransformer { ); } - public transformAssignmentStatement(expression: ts.BinaryExpression): tstl.Statement { + public transformAssignmentStatement(expression: ts.BinaryExpression): StatementVisitResult { // Validate assignment const rightType = this.checker.getTypeAtLocation(expression.right); const leftType = this.checker.getTypeAtLocation(expression.left); @@ -2505,8 +2606,8 @@ export class LuaTransformer { this.transformLuaLibFunction( LuaLibFeature.ArraySetLength, expression, - this.transformExpression(expression.left.expression), - this.transformExpression(expression.right) + this.expectExpression(this.transformExpression(expression.left.expression)), + this.expectExpression(this.transformExpression(expression.right)) ) ); } @@ -2518,11 +2619,14 @@ export class LuaTransformer { : [tstl.createAnnonymousIdentifier(expression.left)]; let right: tstl.Expression[]; if (ts.isArrayLiteralExpression(expression.right)) { - right = expression.right.elements.length > 0 - ? expression.right.elements.map(e => this.transformExpression(e)) - : [tstl.createNilLiteral()]; + if (expression.right.elements.length > 0) { + const visitResults = expression.right.elements.map(e => this.transformExpression(e)); + right = this.filterUndefined(visitResults); + } else { + right = [tstl.createNilLiteral()]; + } } else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { - right = [this.transformExpression(expression.right)]; + right = this.filterUndefined([this.transformExpression(expression.right)]); } else { right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)]; } @@ -2550,8 +2654,8 @@ export class LuaTransformer { return this.transformLuaLibFunction( LuaLibFeature.ArraySetLength, expression, - this.transformExpression(expression.left.expression), - this.transformExpression(expression.right) + this.expectExpression(this.transformExpression(expression.left.expression)), + this.expectExpression(this.transformExpression(expression.right)) ); } @@ -2564,10 +2668,10 @@ export class LuaTransformer { let right: tstl.Expression[]; if (ts.isArrayLiteralExpression(expression.right)) { right = expression.right.elements.length > 0 - ? expression.right.elements.map(e => this.transformExpression(e)) + ? this.filterUndefined(expression.right.elements.map(e => this.transformExpression(e))) : [tstl.createNilLiteral()]; } else if (tsHelper.isTupleReturnCall(expression.right, this.checker)) { - right = [this.transformExpression(expression.right)]; + right = this.filterUndefined([this.transformExpression(expression.right)]); } else { right = [this.createUnpackCall(this.transformExpression(expression.right), expression.right)]; } @@ -2605,7 +2709,7 @@ export class LuaTransformer { indexExpression = tstl.createStringLiteral(expression.left.name.text); } else { // Element access - indexExpression = this.transformExpression(expression.left.argumentExpression); + indexExpression = this.expectExpression(this.transformExpression(expression.left.argumentExpression)); const argType = this.checker.getTypeAtLocation(expression.left.expression); if (tsHelper.isArrayType(argType, this.checker, this.program)) { // Array access needs a +1 @@ -2613,12 +2717,16 @@ export class LuaTransformer { } } const args = [objExpression, indexExpression, this.transformExpression(expression.right)]; - return tstl.createCallExpression(tstl.createParenthesizedExpression(iife), args); + return tstl.createCallExpression( + tstl.createParenthesizedExpression(iife), + this.filterUndefined(args), + expression + ); } else { // Simple assignment // (function() ${left} = ${right}; return ${left} end)() - const left = this.transformExpression(expression.left); + const left = this.expectExpression(this.transformExpression(expression.left)); const right = this.transformExpression(expression.right); return this.createImmediatelyInvokedFunctionExpression( [this.transformAssignment(expression.left, right)], @@ -2637,20 +2745,25 @@ export class LuaTransformer { ): tstl.CallExpression { const left = this.transformExpression(lhs) as tstl.IdentifierOrTableIndexExpression; - let right = this.transformExpression(rhs); + let right = this.expectExpression(this.transformExpression(rhs)); const [hasEffects, objExpression, indexExpression] = tsHelper.isAccessExpressionWithEvaluationEffects( lhs, this.checker, this.program ); - if (hasEffects) { + if (hasEffects && objExpression && indexExpression) { // Complex property/element accesses need to cache object/index expressions to avoid repeating side-effects // local __TS_obj, __TS_index = ${objExpression}, ${indexExpression}; const obj = tstl.createIdentifier("____TS_obj"); const index = tstl.createIdentifier("____TS_index"); const objAndIndexDeclaration = tstl.createVariableDeclarationStatement( - [obj, index], [this.transformExpression(objExpression), this.transformExpression(indexExpression)]); + [obj, index], + this.filterUndefined( + [this.transformExpression(objExpression), + this.transformExpression(indexExpression)] + ) + ); const accessExpression = tstl.createTableIndexExpression(obj, index); const tmp = tstl.createIdentifier("____TS_tmp"); @@ -2789,8 +2902,13 @@ export class LuaTransformer { const className = expression.name !== undefined ? this.transformIdentifier(expression.name) : tstl.createAnnonymousIdentifier(); + const classDeclaration = this.transformClassDeclaration(expression, className); - return this.createImmediatelyInvokedFunctionExpression(classDeclaration, className, expression); + return this.createImmediatelyInvokedFunctionExpression( + this.statementVisitResultToArray(classDeclaration), + className, + expression + ); } public transformCompoundAssignmentStatement( @@ -2801,21 +2919,26 @@ export class LuaTransformer { ): tstl.Statement { const left = this.transformExpression(lhs) as tstl.IdentifierOrTableIndexExpression; - const right = this.transformExpression(rhs); + const right = this.expectExpression(this.transformExpression(rhs)); const [hasEffects, objExpression, indexExpression] = tsHelper.isAccessExpressionWithEvaluationEffects( lhs, this.checker, this.program ); - if (hasEffects) { + if (hasEffects && objExpression && indexExpression) { // Complex property/element accesses need to cache object/index expressions to avoid repeating side-effects // local __TS_obj, __TS_index = ${objExpression}, ${indexExpression}; // ____TS_obj[____TS_index] = ____TS_obj[____TS_index] ${replacementOperator} ${right}; const obj = tstl.createIdentifier("____TS_obj"); const index = tstl.createIdentifier("____TS_index"); const objAndIndexDeclaration = tstl.createVariableDeclarationStatement( - [obj, index], [this.transformExpression(objExpression), this.transformExpression(indexExpression)]); + [obj, index], + this.filterUndefined([ + this.transformExpression(objExpression), + this.transformExpression(indexExpression), + ]) + ); const accessExpression = tstl.createTableIndexExpression(obj, index); const operatorExpression = this.transformBinaryOperation( accessExpression, @@ -2862,9 +2985,9 @@ export class LuaTransformer { operator: tstl.UnaryBitwiseOperator ): ExpressionVisitResult { - switch (this.options.luaTarget) { + switch (this.luaTarget) { case LuaTarget.Lua51: - throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.options.luaTarget, node); + throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.luaTarget, node); case LuaTarget.Lua52: return this.transformUnaryBitLibOperation(node, expression, operator, "bit32"); @@ -2915,16 +3038,16 @@ export class LuaTransformer { ); } - public transformBinaryBitOperation( + private transformBinaryBitOperation( node: ts.Node, left: tstl.Expression, right: tstl.Expression, operator: ts.BinaryOperator ): ExpressionVisitResult { - switch (this.options.luaTarget) { + switch (this.luaTarget) { case LuaTarget.Lua51: - throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.options.luaTarget, node); + throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.luaTarget, node); case LuaTarget.Lua52: return this.transformBinaryBitLibOperation(node, left, right, operator, "bit32"); @@ -2938,10 +3061,10 @@ export class LuaTransformer { } } - public transformProtectedConditionalExpression(expression: ts.ConditionalExpression): tstl.CallExpression { - const condition = this.transformExpression(expression.condition); - const val1 = this.transformExpression(expression.whenTrue); - const val2 = this.transformExpression(expression.whenFalse); + private transformProtectedConditionalExpression(expression: ts.ConditionalExpression): tstl.CallExpression { + const condition = this.expectExpression(this.transformExpression(expression.condition)); + const val1 = this.expectExpression(this.transformExpression(expression.whenTrue)); + const val2 = this.expectExpression(this.transformExpression(expression.whenFalse)); const val1Function = this.wrapInFunctionCall(val1); const val2Function = this.wrapInFunctionCall(val2); @@ -2952,14 +3075,14 @@ export class LuaTransformer { return tstl.createCallExpression(tstl.createParenthesizedExpression(orExpression), [], expression); } - public transformConditionalExpression(expression: ts.ConditionalExpression): tstl.Expression { - const isStrict = this.options.strict || this.options.strictNullChecks; + private transformConditionalExpression(expression: ts.ConditionalExpression): ExpressionVisitResult { + const isStrict = this.options.strict === true || this.options.strictNullChecks === true; if (tsHelper.isFalsible(this.checker.getTypeAtLocation(expression.whenTrue), isStrict)) { - return this.transformProtectedConditionalExpression(expression); + return this.transformProtectedConditionalExpression(expression); } - const condition = this.transformExpression(expression.condition); - const val1 = this.transformExpression(expression.whenTrue); - const val2 = this.transformExpression(expression.whenFalse); + const condition = this.expectExpression(this.transformExpression(expression.condition)); + const val1 = this.expectExpression(this.transformExpression(expression.whenTrue)); + const val2 = this.expectExpression(this.transformExpression(expression.whenFalse)); // condition and v1 or v2 const conditionAnd = tstl.createBinaryExpression(condition, val1, tstl.SyntaxKind.AndOperator); @@ -2971,7 +3094,7 @@ export class LuaTransformer { ); } - public transformPostfixUnaryExpression(expression: ts.PostfixUnaryExpression): tstl.Expression { + public transformPostfixUnaryExpression(expression: ts.PostfixUnaryExpression): ExpressionVisitResult { switch (expression.operator) { case ts.SyntaxKind.PlusPlusToken: return this.transformCompoundAssignmentExpression( @@ -2996,7 +3119,7 @@ export class LuaTransformer { } } - public transformPrefixUnaryExpression(expression: ts.PrefixUnaryExpression): tstl.Expression { + public transformPrefixUnaryExpression(expression: ts.PrefixUnaryExpression): ExpressionVisitResult { switch (expression.operator) { case ts.SyntaxKind.PlusPlusToken: return this.transformCompoundAssignmentExpression( @@ -3021,20 +3144,20 @@ export class LuaTransformer { case ts.SyntaxKind.MinusToken: return tstl.createUnaryExpression( - this.transformExpression(expression.operand), + this.expectExpression(this.transformExpression(expression.operand)), tstl.SyntaxKind.NegationOperator ); case ts.SyntaxKind.ExclamationToken: return tstl.createUnaryExpression( - this.transformExpression(expression.operand), + this.expectExpression(this.transformExpression(expression.operand)), tstl.SyntaxKind.NotOperator ); case ts.SyntaxKind.TildeToken: return this.transformUnaryBitOperation( expression, - this.transformExpression(expression.operand), + this.expectExpression(this.transformExpression(expression.operand)), tstl.SyntaxKind.BitwiseNotOperator ); @@ -3043,29 +3166,32 @@ export class LuaTransformer { } } - public transformArrayLiteral(node: ts.ArrayLiteralExpression): tstl.TableExpression { + public transformArrayLiteral(node: ts.ArrayLiteralExpression): ExpressionVisitResult { const values: tstl.TableFieldExpression[] = []; node.elements.forEach(child => { - values.push(tstl.createTableFieldExpression(this.transformExpression(child), undefined, child)); + const childExpression = this.transformExpression(child); + if (childExpression) { + values.push(tstl.createTableFieldExpression(childExpression, undefined, child)); + } }); return tstl.createTableExpression(values, node); } - public transformObjectLiteral(node: ts.ObjectLiteralExpression): tstl.TableExpression { + public transformObjectLiteral(node: ts.ObjectLiteralExpression): ExpressionVisitResult { const properties: tstl.TableFieldExpression[] = []; // Add all property assignments node.properties.forEach(element => { - const name = this.transformPropertyName(element.name); + const name = element.name ? this.transformPropertyName(element.name) : undefined; if (ts.isPropertyAssignment(element)) { - const expression = this.transformExpression(element.initializer); + const expression = this.expectExpression(this.transformExpression(element.initializer)); properties.push(tstl.createTableFieldExpression(expression, name, element)); } else if (ts.isShorthandPropertyAssignment(element)) { const identifier = this.transformIdentifier(element.name); properties.push(tstl.createTableFieldExpression(identifier, name, element)); } else if (ts.isMethodDeclaration(element)) { - const expression = this.transformFunctionExpression(element); + const expression = this.expectExpression(this.transformFunctionExpression(element)); properties.push(tstl.createTableFieldExpression(expression, name, element)); } else { throw TSTLErrors.UnsupportedKind("object literal element", element.kind, node); @@ -3075,7 +3201,7 @@ export class LuaTransformer { return tstl.createTableExpression(properties, node); } - public transformDeleteExpression(expression: ts.DeleteExpression): tstl.CallExpression { + public transformDeleteExpression(expression: ts.DeleteExpression): ExpressionVisitResult { const lhs = this.transformExpression(expression.expression) as tstl.IdentifierOrTableIndexExpression; const assignment = tstl.createAssignmentStatement( lhs, @@ -3110,6 +3236,11 @@ export class LuaTransformer { const [paramNames, dotsLiteral, spreadIdentifier] = this.transformParameters(node.parameters, context); let flags = tstl.FunctionExpressionFlags.None; + + if (node.body === undefined) { + throw TSTLErrors.UnsupportedFunctionWithoutBody(node); + } + let body: ts.Block; if (ts.isBlock(node.body)) { body = node.body; @@ -3117,9 +3248,12 @@ export class LuaTransformer { const returnExpression = ts.createReturn(node.body); body = ts.createBlock([returnExpression]); returnExpression.parent = body; - body.parent = node.body.parent; + if (node.body) { + body.parent = node.body.parent; + } flags |= tstl.FunctionExpressionFlags.Inline; } + const [transformedBody] = this.transformFunctionBody(node.parameters, body, spreadIdentifier); return tstl.createFunctionExpression( @@ -3132,11 +3266,11 @@ export class LuaTransformer { ); } - public transformNewExpression(node: ts.NewExpression): tstl.CallExpression { - const name = this.transformExpression(node.expression); - const sig = this.checker.getResolvedSignature(node); + public transformNewExpression(node: ts.NewExpression): ExpressionVisitResult { + const name = this.expectExpression(this.transformExpression(node.expression)); + const signature = this.checker.getResolvedSignature(node); const params = node.arguments - ? this.transformArguments(node.arguments, sig) + ? this.transformArguments(node.arguments, signature) : [tstl.createBooleanLiteral(true)]; const type = this.checker.getTypeAtLocation(node); @@ -3150,12 +3284,13 @@ export class LuaTransformer { if (classDecorators.has(DecoratorKind.CustomConstructor)) { const customDecorator = classDecorators.get(DecoratorKind.CustomConstructor); - if (!customDecorator.args[0]) { + if (customDecorator === undefined || customDecorator.args[0] === undefined) { throw TSTLErrors.InvalidDecoratorArgumentNumber("@customConstructor", 0, 1, node); } + return tstl.createCallExpression( tstl.createIdentifier(customDecorator.args[0]), - this.transformArguments(node.arguments), + this.transformArguments(node.arguments || []), node ); } @@ -3167,26 +3302,35 @@ export class LuaTransformer { ); } - public transformParenthesizedExpression(expression: ts.ParenthesizedExpression): tstl.Expression { + public transformParenthesizedExpression(expression: ts.ParenthesizedExpression): ExpressionVisitResult { if (ts.isAssertionExpression(expression.expression)) { // Strip parenthesis from casts return this.transformExpression(expression.expression); } return tstl.createParenthesizedExpression( - this.transformExpression(expression.expression), + this.expectExpression(this.transformExpression(expression.expression)), expression ); } - public transformSuperKeyword(expression: ts.SuperExpression): tstl.Expression { + public transformSuperKeyword(expression: ts.SuperExpression): ExpressionVisitResult { const classDeclaration = this.classStack[this.classStack.length - 1]; - const extendsExpression = tsHelper.getExtendedTypeNode(classDeclaration, this.checker).expression; + const typeNode = tsHelper.getExtendedTypeNode(classDeclaration, this.checker); + if (typeNode === undefined) { + throw TSTLErrors.UnknownSuperType(expression); + } + + const extendsExpression = typeNode.expression; let baseClassName: tstl.IdentifierOrTableIndexExpression; if (ts.isIdentifier(extendsExpression)) { // Use "baseClassName" if base is a simple identifier baseClassName = this.transformIdentifier(extendsExpression); } else { + if (classDeclaration.name === undefined) { + throw TSTLErrors.MissingClassName(expression); + } + // Use "className.____super" if the base is not a simple identifier baseClassName = tstl.createTableIndexExpression( this.transformIdentifier(classDeclaration.name), @@ -3197,7 +3341,7 @@ export class LuaTransformer { return tstl.createTableIndexExpression(baseClassName, tstl.createStringLiteral("prototype")); } - public transformCallExpression(node: ts.CallExpression): tstl.Expression { + public transformCallExpression(node: ts.CallExpression): ExpressionVisitResult { // Check for calls on primitives to override let parameters: tstl.Expression[] = []; @@ -3212,12 +3356,12 @@ export class LuaTransformer { && !isInSpread && returnValueIsUsed; if (ts.isPropertyAccessExpression(node.expression)) { - const result = this.transformPropertyCall(node); + const result = this.expectExpression(this.transformPropertyCall(node)); return wrapResult ? this.wrapInTable(result) : result; } if (ts.isElementAccessExpression(node.expression)) { - const result = this.transformElementCall(node); + const result = this.expectExpression(this.transformElementCall(node)); return wrapResult ? this.wrapInTable(result) : result; } @@ -3229,14 +3373,14 @@ export class LuaTransformer { return tstl.createCallExpression( tstl.createTableIndexExpression( - this.transformSuperKeyword(ts.createSuper()), + this.expectExpression(this.transformSuperKeyword(ts.createSuper())), tstl.createStringLiteral("____constructor") ), parameters ); } - const callPath = this.transformExpression(node.expression); + const callPath = this.expectExpression(this.transformExpression(node.expression)); const signatureDeclaration = signature && signature.getDeclaration(); if (signatureDeclaration && tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === ContextType.Void) @@ -3256,7 +3400,7 @@ export class LuaTransformer { return wrapResult ? this.wrapInTable(callExpression) : callExpression; } - public transformPropertyCall(node: ts.CallExpression): tstl.Expression { + public transformPropertyCall(node: ts.CallExpression): ExpressionVisitResult { let parameters: tstl.Expression[] = []; // Check if call is actually on a property access expression @@ -3279,7 +3423,7 @@ export class LuaTransformer { if (tsHelper.isStandardLibraryType(ownerType, "StringConstructor", this.program)) { return tstl.createCallExpression( - this.transformStringExpression(node.expression.name), + this.expectExpression(this.transformStringExpression(node.expression.name)), this.transformArguments(node.arguments, signature), node ); @@ -3318,26 +3462,35 @@ export class LuaTransformer { if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) { // Super calls take the format of super.call(self,...) parameters = this.transformArguments(node.arguments, signature, ts.createThis()); - return tstl.createCallExpression(this.transformExpression(node.expression), parameters); + return tstl.createCallExpression( + this.expectExpression(this.transformExpression(node.expression)), + parameters + ); } else { // Replace last . with : here const name = node.expression.name.escapedText; if (name === "toString") { const toStringIdentifier = tstl.createIdentifier("tostring"); return tstl.createCallExpression( - toStringIdentifier, [this.transformExpression(node.expression.expression)], node); + toStringIdentifier, + this.filterUndefined([this.transformExpression(node.expression.expression)]), + node + ); } else if (name === "hasOwnProperty") { const expr = this.transformExpression(node.expression.expression); parameters = this.transformArguments(node.arguments, signature); const rawGetIdentifier = tstl.createIdentifier("rawget"); - const rawGetCall = tstl.createCallExpression(rawGetIdentifier, [expr, ...parameters]); + const rawGetCall = tstl.createCallExpression( + rawGetIdentifier, + this.filterUndefined([expr, ...parameters]) + ); return tstl.createParenthesizedExpression( tstl.createBinaryExpression( rawGetCall, tstl.createNilLiteral(), tstl.SyntaxKind.InequalityOperator, node) ); } else { const parameters = this.transformArguments(node.arguments, signature); - const table = this.transformExpression(node.expression.expression); + const table = this.expectExpression(this.transformExpression(node.expression.expression)); const signatureDeclaration = signature && signature.getDeclaration(); if (!signatureDeclaration || tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void) @@ -3362,7 +3515,7 @@ export class LuaTransformer { } } - public transformElementCall(node: ts.CallExpression): tstl.CallExpression { + public transformElementCall(node: ts.CallExpression): ExpressionVisitResult { if (!ts.isElementAccessExpression(node.expression)) { throw TSTLErrors.InvalidElementCall(node); } @@ -3375,7 +3528,7 @@ export class LuaTransformer { || tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== ContextType.Void) { // Pass left-side as context - const context = this.transformExpression(node.expression.expression); + const context = this.expectExpression(this.transformExpression(node.expression.expression)); if (tsHelper.isExpressionWithEvaluationEffect(node.expression.expression)) { // Inject context parameter if (node.arguments.length > 0) { @@ -3386,23 +3539,25 @@ export class LuaTransformer { // Cache left-side if it has effects //(function() local ____TS_self = context; return ____TS_self[argument](parameters); end)() - const argument = this.transformExpression(node.expression.argumentExpression); + const argument = this.expectExpression(this.transformExpression(node.expression.argumentExpression)); const selfIdentifier = tstl.createIdentifier("____TS_self"); const selfAssignment = tstl.createVariableDeclarationStatement(selfIdentifier, context); const index = tstl.createTableIndexExpression(selfIdentifier, argument); const callExpression = tstl.createCallExpression(index, parameters); return this.createImmediatelyInvokedFunctionExpression([selfAssignment], callExpression, node); } else { - return tstl.createCallExpression(this.transformExpression(node.expression), [context, ...parameters]); + const expression = this.expectExpression(this.transformExpression(node.expression)); + return tstl.createCallExpression(expression, [context, ...parameters]); } } else { // No context - return tstl.createCallExpression(this.transformExpression(node.expression), parameters); + const expression = this.expectExpression(this.transformExpression(node.expression)); + return tstl.createCallExpression(expression, parameters); } } - public transformArguments( - params: ts.NodeArray, + private transformArguments( + params: ts.NodeArray | ts.Expression[], sig?: ts.Signature, context?: T ): tstl.Expression[] @@ -3411,7 +3566,7 @@ export class LuaTransformer { // Add context as first param if present if (context) { - parameters.push(this.transformExpression(context)); + parameters.push(this.expectExpression(this.transformExpression(context))); } if (sig && sig.parameters.length >= params.length) { @@ -3420,18 +3575,20 @@ export class LuaTransformer { const paramType = this.checker.getTypeAtLocation(param); const sigType = this.checker.getTypeAtLocation(sig.parameters[i].valueDeclaration); this.validateFunctionAssignment(param, paramType, sigType, sig.parameters[i].name); - parameters.push(this.transformExpression(param)); + + const transformedParam = this.transformExpression(param); + if (transformedParam) { + parameters.push(transformedParam); + } } } else { - params.forEach(param => { - parameters.push(this.transformExpression(param)); - }); + parameters.push(...this.filterUndefined(params.map(param => this.transformExpression(param)))); } return parameters; } - public transformPropertyAccessExpression(node: ts.PropertyAccessExpression): tstl.Expression { + public transformPropertyAccessExpression(node: ts.PropertyAccessExpression): ExpressionVisitResult { const property = node.name.text; // Check for primitive types to override @@ -3469,12 +3626,12 @@ export class LuaTransformer { } } - const callPath = this.transformExpression(node.expression); + const callPath = this.expectExpression(this.transformExpression(node.expression)); return tstl.createTableIndexExpression(callPath, tstl.createStringLiteral(property), node); } // Transpile a Math._ property - public transformMathExpression(identifier: ts.Identifier): tstl.Expression { + private transformMathExpression(identifier: ts.Identifier): tstl.Expression { const name = identifier.escapedText as string; switch (name) { case "PI": @@ -3497,7 +3654,7 @@ export class LuaTransformer { } // Transpile a Math._ property - public transformMathCallExpression(node: ts.CallExpression): tstl.Expression { + private transformMathCallExpression(node: ts.CallExpression): tstl.Expression { const expression = node.expression as ts.PropertyAccessExpression; const signature = this.checker.getResolvedSignature(node); const params = this.transformArguments(node.arguments, signature); @@ -3575,53 +3732,57 @@ export class LuaTransformer { } // Transpile access of string properties, only supported properties are allowed - public transformStringProperty(node: ts.PropertyAccessExpression): tstl.UnaryExpression { + private transformStringProperty(node: ts.PropertyAccessExpression): tstl.UnaryExpression { switch (node.name.escapedText) { case "length": - return tstl.createUnaryExpression( - this.transformExpression(node.expression), tstl.SyntaxKind.LengthOperator, node); + const expression = this.expectExpression(this.transformExpression(node.expression)); + return tstl.createUnaryExpression(expression, tstl.SyntaxKind.LengthOperator, node); default: throw TSTLErrors.UnsupportedProperty("string", node.name.escapedText as string, node); } } // Transpile access of array properties, only supported properties are allowed - public transformArrayProperty(node: ts.PropertyAccessExpression): tstl.UnaryExpression | undefined { + private transformArrayProperty(node: ts.PropertyAccessExpression): tstl.UnaryExpression | undefined { switch (node.name.escapedText) { case "length": - return tstl.createUnaryExpression( - this.transformExpression(node.expression), tstl.SyntaxKind.LengthOperator, node); + const expression = this.expectExpression(this.transformExpression(node.expression)); + return tstl.createUnaryExpression(expression, tstl.SyntaxKind.LengthOperator, node); default: return undefined; } } - public transformElementAccessExpression(node: ts.ElementAccessExpression): tstl.Expression { - const table = this.transformExpression(node.expression); - const index = this.transformExpression(node.argumentExpression); + public transformElementAccessExpression(expression: ts.ElementAccessExpression): ExpressionVisitResult { + const table = this.expectExpression(this.transformExpression(expression.expression)); + const index = this.expectExpression(this.transformExpression(expression.argumentExpression)); - const type = this.checker.getTypeAtLocation(node.expression); + const type = this.checker.getTypeAtLocation(expression.expression); if (type.symbol && (type.symbol.flags & ts.SymbolFlags.ConstEnum) - && ts.isStringLiteral(node.argumentExpression)) + && ts.isStringLiteral(expression.argumentExpression)) { - return this.transformConstEnumValue(type, node.argumentExpression.text, node); + return this.transformConstEnumValue(type, expression.argumentExpression.text, expression); } if (tsHelper.isArrayType(type, this.checker, this.program)) { - return tstl.createTableIndexExpression(table, this.expressionPlusOne(index), node); + return tstl.createTableIndexExpression(table, this.expressionPlusOne(index), expression); } else if (tsHelper.isStringType(type)) { return tstl.createCallExpression( tstl.createTableIndexExpression(tstl.createIdentifier("string"), tstl.createStringLiteral("sub")), [table, this.expressionPlusOne(index), this.expressionPlusOne(index)], - node + expression ); } else { - return tstl.createTableIndexExpression(table, index, node); + return tstl.createTableIndexExpression(table, index, expression); } } - private transformConstEnumValue(enumType: ts.EnumType, memberName: string, tsOriginal: ts.Node): tstl.Expression { + private transformConstEnumValue( + enumType: ts.EnumType, + memberName: string, + tsOriginal: ts.Node + ): ExpressionVisitResult { // Assumption: the enum only has one declaration const enumDeclaration = enumType.symbol.declarations.find(d => ts.isEnumDeclaration(d)) as ts.EnumDeclaration; const enumMember = enumDeclaration.members @@ -3631,7 +3792,7 @@ export class LuaTransformer { if (enumMember.initializer) { if (ts.isIdentifier(enumMember.initializer)) { const [isEnumMember, valueName] = tsHelper.isEnumMember(enumDeclaration, enumMember.initializer); - if (isEnumMember) { + if (isEnumMember && valueName) { if (ts.isIdentifier(valueName)) { return this.transformConstEnumValue(enumType, valueName.text, tsOriginal); } @@ -3660,11 +3821,11 @@ export class LuaTransformer { throw TSTLErrors.CouldNotFindEnumMember(enumDeclaration, memberName, tsOriginal); } - public transformStringCallExpression(node: ts.CallExpression): tstl.Expression { + private transformStringCallExpression(node: ts.CallExpression): tstl.Expression { const expression = node.expression as ts.PropertyAccessExpression; const signature = this.checker.getResolvedSignature(node); const params = this.transformArguments(node.arguments, signature); - const caller = this.transformExpression(expression.expression); + const caller = this.expectExpression(this.transformExpression(expression.expression)); const expressionName = expression.name.escapedText as string; switch (expressionName) { @@ -3698,7 +3859,8 @@ export class LuaTransformer { ); case "substr": if (node.arguments.length === 1) { - const arg1 = this.expressionPlusOne(this.transformExpression(node.arguments[0])); + const argument = this.expectExpression(this.transformExpression(node.arguments[0])); + const arg1 = this.expressionPlusOne(argument); return this.createStringCall("sub", node, caller, arg1); } else { const arg1 = params[0]; @@ -3763,7 +3925,7 @@ export class LuaTransformer { case "unpack": case "upper": // Allow lua's string instance methods - let stringVariable = this.transformExpression(expression.expression); + let stringVariable = this.expectExpression(this.transformExpression(expression.expression)); if (ts.isStringLiteral(expression.expression)) { // "foo":method() needs to be ("foo"):method() stringVariable = tstl.createParenthesizedExpression(stringVariable); @@ -3779,7 +3941,7 @@ export class LuaTransformer { } } - public createStringCall( + private createStringCall( methodName: string, tsOriginal: ts.Node, ...params: tstl.Expression[] @@ -3794,7 +3956,7 @@ export class LuaTransformer { } // Transpile a String._ property - public transformStringExpression(identifier: ts.Identifier): ExpressionVisitResult { + private transformStringExpression(identifier: ts.Identifier): ExpressionVisitResult { const identifierString = identifier.escapedText as string; switch (identifierString) { @@ -3806,14 +3968,14 @@ export class LuaTransformer { default: throw TSTLErrors.UnsupportedForTarget( `string property ${identifierString}`, - this.options.luaTarget, + this.luaTarget, identifier ); } } // Transpile an Object._ property - public transformObjectCallExpression(expression: ts.CallExpression): ExpressionVisitResult { + private transformObjectCallExpression(expression: ts.CallExpression): ExpressionVisitResult { const method = expression.expression as ts.PropertyAccessExpression; const signature = this.checker.getResolvedSignature(expression); const parameters = this.transformArguments(expression.arguments); @@ -3834,13 +3996,13 @@ export class LuaTransformer { default: throw TSTLErrors.UnsupportedForTarget( `object property ${methodName}`, - this.options.luaTarget, + this.luaTarget, expression ); } } - public transformConsoleCallExpression(expression: ts.CallExpression): ExpressionVisitResult { + private transformConsoleCallExpression(expression: ts.CallExpression): ExpressionVisitResult { const method = expression.expression as ts.PropertyAccessExpression; const methodName = method.name.escapedText; const signature = this.checker.getResolvedSignature(expression); @@ -3922,7 +4084,7 @@ export class LuaTransformer { default: throw TSTLErrors.UnsupportedForTarget( `console property ${methodName}`, - this.options.luaTarget, + this.luaTarget, expression ); } @@ -3933,7 +4095,7 @@ export class LuaTransformer { } // Transpile a Symbol._ property - public transformSymbolCallExpression(expression: ts.CallExpression): tstl.CallExpression { + private transformSymbolCallExpression(expression: ts.CallExpression): tstl.CallExpression { const method = expression.expression as ts.PropertyAccessExpression; const signature = this.checker.getResolvedSignature(expression); const parameters = this.transformArguments(expression.arguments, signature); @@ -3949,17 +4111,17 @@ export class LuaTransformer { default: throw TSTLErrors.UnsupportedForTarget( `symbol property ${methodName}`, - this.options.luaTarget, + this.luaTarget, expression ); } } - public transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression { + private transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression { const expression = node.expression as ts.PropertyAccessExpression; const signature = this.checker.getResolvedSignature(node); const params = this.transformArguments(node.arguments, signature); - const caller = this.transformExpression(expression.expression); + const caller = this.expectExpression(this.transformExpression(expression.expression)); const expressionName = expression.name.escapedText; switch (expressionName) { case "concat": @@ -4017,7 +4179,7 @@ export class LuaTransformer { } } - public transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression { + private transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression { const expression = node.expression as ts.PropertyAccessExpression; const callerType = this.checker.getTypeAtLocation(expression.expression); if (tsHelper.getFunctionContextType(callerType, this.checker) === ContextType.Void) { @@ -4025,7 +4187,7 @@ export class LuaTransformer { } const signature = this.checker.getResolvedSignature(node); const params = this.transformArguments(node.arguments, signature); - const caller = this.transformExpression(expression.expression); + const caller = this.expectExpression(this.transformExpression(expression.expression)); const expressionName = expression.name.escapedText; switch (expressionName) { case "apply": @@ -4039,7 +4201,7 @@ export class LuaTransformer { } } - public transformArrayBindingElement(name: ts.ArrayBindingElement): tstl.Identifier { + public transformArrayBindingElement(name: ts.ArrayBindingElement): ExpressionVisitResult { if (ts.isOmittedExpression(name)) { return tstl.createIdentifier("__", name); } else if (ts.isIdentifier(name)) { @@ -4051,7 +4213,7 @@ export class LuaTransformer { } } - public transformAssertionExpression(node: ts.AssertionExpression): tstl.Expression { + public transformAssertionExpression(node: ts.AssertionExpression): ExpressionVisitResult { this.validateFunctionAssignment( node, this.checker.getTypeAtLocation(node.expression), @@ -4061,7 +4223,7 @@ export class LuaTransformer { } public transformTypeOfExpression(node: ts.TypeOfExpression): ExpressionVisitResult { - const expression = this.transformExpression(node.expression); + const expression = this.expectExpression(this.transformExpression(node.expression)); const typeFunctionIdentifier = tstl.createIdentifier("type"); const typeCall = tstl.createCallExpression(typeFunctionIdentifier, [expression]); const tableString = tstl.createStringLiteral("table"); @@ -4080,7 +4242,7 @@ export class LuaTransformer { } public transformSpreadElement(expression: ts.SpreadElement): ExpressionVisitResult { - const innerExpression = this.transformExpression(expression.expression); + const innerExpression = this.expectExpression(this.transformExpression(expression.expression)); if (tsHelper.isTupleReturnCall(expression.expression, this.checker)) { return innerExpression; } else { @@ -4088,33 +4250,33 @@ export class LuaTransformer { } } - public transformStringLiteral(literal: ts.StringLiteralLike): tstl.StringLiteral { + public transformStringLiteral(literal: ts.StringLiteralLike): ExpressionVisitResult { const text = tsHelper.escapeString(literal.text); return tstl.createStringLiteral(text, literal); } - public transformNumericLiteral(literal: ts.NumericLiteral): tstl.NumericLiteral { + public transformNumericLiteral(literal: ts.NumericLiteral): ExpressionVisitResult { const value = Number(literal.text); return tstl.createNumericLiteral(value, literal); } - public transformTrueKeyword(trueKeyword: ts.BooleanLiteral): tstl.BooleanLiteral { + public transformTrueKeyword(trueKeyword: ts.BooleanLiteral): ExpressionVisitResult { return tstl.createBooleanLiteral(true, trueKeyword); } - public transformFalseKeyword(falseKeyword: ts.BooleanLiteral): tstl.BooleanLiteral { + public transformFalseKeyword(falseKeyword: ts.BooleanLiteral): ExpressionVisitResult { return tstl.createBooleanLiteral(false, falseKeyword); } - public transformNullOrUndefinedKeyword(originalNode: ts.Node): tstl.NilLiteral { + public transformNullOrUndefinedKeyword(originalNode: ts.Node): ExpressionVisitResult { return tstl.createNilLiteral(originalNode); } - public transformThisKeyword(thisKeyword: ts.ThisExpression): tstl.Expression { + public transformThisKeyword(thisKeyword: ts.ThisExpression): ExpressionVisitResult { return this.createSelfIdentifier(thisKeyword); } - public transformTemplateExpression(expression: ts.TemplateExpression): tstl.Expression { + public transformTemplateExpression(expression: ts.TemplateExpression): ExpressionVisitResult { const parts: tstl.Expression[] = []; const head = tsHelper.escapeString(expression.head.text); @@ -4123,11 +4285,14 @@ export class LuaTransformer { } expression.templateSpans.forEach(span => { - parts.push(this.wrapInToStringForConcat(this.transformExpression(span.expression))); + const expression = this.transformExpression(span.expression); + if (expression !== undefined) { + parts.push(this.wrapInToStringForConcat(expression)); - const text = tsHelper.escapeString(span.literal.text); - if (text.length > 0) { - parts.push(tstl.createStringLiteral(text, span.literal)); + const text = tsHelper.escapeString(span.literal.text); + if (text.length > 0) { + parts.push(tstl.createStringLiteral(text, span.literal)); + } } }); @@ -4138,7 +4303,7 @@ export class LuaTransformer { ); } - public transformPropertyName(propertyName: ts.PropertyName): tstl.Expression { + public transformPropertyName(propertyName: ts.PropertyName): ExpressionVisitResult { if (ts.isComputedPropertyName(propertyName)) { return this.transformExpression(propertyName.expression); } else if (ts.isStringLiteral(propertyName)) { @@ -4151,7 +4316,7 @@ export class LuaTransformer { } } - public getIdentifierText(identifier: ts.Identifier): string { + private getIdentifierText(identifier: ts.Identifier): string { let escapedText = identifier.escapedText as string; const underScoreCharCode = "_".charCodeAt(0); if (escapedText.length >= 3 && escapedText.charCodeAt(0) === underScoreCharCode && @@ -4179,7 +4344,7 @@ export class LuaTransformer { return tstl.createIdentifier(escapedText, expression, symbolId); } - public transformIdentifierExpression(expression: ts.Identifier): tstl.IdentifierOrTableIndexExpression { + private transformIdentifierExpression(expression: ts.Identifier): tstl.IdentifierOrTableIndexExpression { const identifier = this.transformIdentifier(expression); if (this.isIdentifierExported(identifier)) { return this.createExportedIdentifier(identifier); @@ -4187,7 +4352,7 @@ export class LuaTransformer { return identifier; } - public isIdentifierExported(identifier: tstl.Identifier): boolean { + private isIdentifierExported(identifier: tstl.Identifier): boolean { if (!this.isModule && !this.currentNamespace) { return false; } @@ -4198,12 +4363,21 @@ export class LuaTransformer { } const currentScope = this.currentNamespace ? this.currentNamespace : this.currentSourceFile; + if (currentScope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + const scopeSymbol = this.checker.getSymbolAtLocation(currentScope) ? this.checker.getSymbolAtLocation(currentScope) : this.checker.getTypeAtLocation(currentScope).getSymbol(); + if (scopeSymbol === undefined || scopeSymbol.exports === undefined) { + return false; + } + const scopeSymbolExports = scopeSymbol.exports; + const it: Iterable = { - [Symbol.iterator]: () => scopeSymbol.exports.values(), // Why isn't ts.SymbolTable.values() iterable? + [Symbol.iterator]: () => scopeSymbolExports.values(), // Why isn't ts.SymbolTable.values() iterable? }; for (const symbol of it) { if (symbol === symbolInfo.symbol) { @@ -4213,14 +4387,14 @@ export class LuaTransformer { return false; } - public addExportToIdentifier(identifier: tstl.Identifier): tstl.IdentifierOrTableIndexExpression { + private addExportToIdentifier(identifier: tstl.Identifier): tstl.IdentifierOrTableIndexExpression { if (this.isIdentifierExported(identifier)) { return this.createExportedIdentifier(identifier); } return identifier; } - public createExportedIdentifier(identifier: tstl.Identifier): tstl.TableIndexExpression { + private createExportedIdentifier(identifier: tstl.Identifier): tstl.TableIndexExpression { const exportTable = this.currentNamespace ? this.transformIdentifier(this.currentNamespace.name as ts.Identifier) : this.createExportsIdentifier(); @@ -4230,9 +4404,9 @@ export class LuaTransformer { tstl.createStringLiteral(identifier.text)); } - public transformLuaLibFunction( + private transformLuaLibFunction( func: LuaLibFeature, - tsParent: ts.Expression, + tsParent?: ts.Expression, ...params: tstl.Expression[] ): tstl.CallExpression { @@ -4260,7 +4434,7 @@ export class LuaTransformer { } } - public importLuaLibFeature(feature: LuaLibFeature): void { + private importLuaLibFeature(feature: LuaLibFeature): void { // Add additional lib requirements if (feature === LuaLibFeature.Map || feature === LuaLibFeature.Set) { this.luaLibFeatureSet.add(LuaLibFeature.InstanceOf); @@ -4269,7 +4443,7 @@ export class LuaTransformer { this.luaLibFeatureSet.add(feature); } - public createImmediatelyInvokedFunctionExpression( + private createImmediatelyInvokedFunctionExpression( statements: tstl.Statement[], result: tstl.Expression | tstl.Expression[], tsOriginal: ts.Node @@ -4282,18 +4456,21 @@ export class LuaTransformer { return tstl.createCallExpression(tstl.createParenthesizedExpression(iife), [], tsOriginal); } - public createUnpackCall(expression: tstl.Expression, tsOriginal: ts.Node): tstl.Expression { - switch (this.options.luaTarget) { + private createUnpackCall(expression: tstl.Expression | undefined, tsOriginal: ts.Node): tstl.Expression { + switch (this.luaTarget) { case LuaTarget.Lua51: case LuaTarget.LuaJIT: - return tstl.createCallExpression(tstl.createIdentifier("unpack"), [expression], tsOriginal); - + return tstl.createCallExpression( + tstl.createIdentifier("unpack"), + this.filterUndefined([expression]), + tsOriginal + ); case LuaTarget.Lua52: case LuaTarget.Lua53: default: return tstl.createCallExpression( tstl.createTableIndexExpression(tstl.createIdentifier("table"), tstl.createStringLiteral("unpack")), - [expression], + this.filterUndefined([expression]), tsOriginal ); } @@ -4303,10 +4480,15 @@ export class LuaTransformer { if (relativePath.charAt(0) !== "." && this.options.baseUrl) { return path.resolve(this.options.baseUrl, relativePath); } + + if (this.currentSourceFile === undefined) { + throw TSTLErrors.MissingSourceFile(); + } + return path.resolve(path.dirname(this.currentSourceFile.fileName), relativePath); } - private getImportPath(relativePath: string): string { + private getImportPath(relativePath: string, node: ts.Node): string { const rootDir = this.options.rootDir ? path.resolve(this.options.rootDir) : path.resolve("."); const absoluteImportPath = path.format(path.parse(this.getAbsoluteImportPath(relativePath))); const absoluteRootDirPath = path.format(path.parse(rootDir)); @@ -4314,7 +4496,7 @@ export class LuaTransformer { return this.formatPathToLuaPath( absoluteImportPath.replace(absoluteRootDirPath, "").slice(1)); } else { - throw TSTLErrors.UnresolvableRequirePath(undefined, + throw TSTLErrors.UnresolvableRequirePath(node, `Cannot create require path. Module does not exist within --rootDir`, relativePath); } @@ -4415,6 +4597,10 @@ export class LuaTransformer { ? this.peekScope() : this.findScope(ScopeType.Function | ScopeType.File); + if (scope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + if (!scope.variableDeclarations) { scope.variableDeclarations = []; } scope.variableDeclarations.push(declaration); } @@ -4431,8 +4617,12 @@ export class LuaTransformer { if (!this.options.noHoisting && functionDeclaration) { // Remember function definitions for hoisting later const functionSymbolId = (lhs as tstl.Identifier).symbolId; - if (functionSymbolId !== undefined) { - this.peekScope().functionDefinitions.get(functionSymbolId).definition = declaration || assignment; + const scope = this.peekScope(); + if (functionSymbolId && scope && scope.functionDefinitions) { + const definitions = scope.functionDefinitions.get(functionSymbolId); + if (definitions) { + definitions.definition = declaration || assignment; + } } } @@ -4440,8 +4630,10 @@ export class LuaTransformer { return [declaration, assignment]; } else if (declaration) { return [declaration]; - } else { + } else if (assignment) { return [assignment]; + } else { + return []; } } @@ -4490,14 +4682,19 @@ export class LuaTransformer { if ((ts.isArrayTypeNode(toTypeNode) || ts.isTupleTypeNode(toTypeNode)) && (ts.isArrayTypeNode(fromTypeNode) || ts.isTupleTypeNode(fromTypeNode))) { // Recurse into arrays/tuples - const fromTypeReference = fromType as ts.TypeReference; - const toTypeReference = toType as ts.TypeReference; - const count = Math.min(fromTypeReference.typeArguments.length, toTypeReference.typeArguments.length); + const fromTypeArguments = (fromType as ts.TypeReference).typeArguments; + const toTypeArguments = (toType as ts.TypeReference).typeArguments; + + if (fromTypeArguments === undefined || toTypeArguments === undefined) { + return; + } + + const count = Math.min(fromTypeArguments.length, toTypeArguments.length); for (let i = 0; i < count; ++i) { this.validateFunctionAssignment( node, - fromTypeReference.typeArguments[i], - toTypeReference.typeArguments[i], + fromTypeArguments[i], + toTypeArguments[i], toName ); } @@ -4509,12 +4706,18 @@ export class LuaTransformer { { // Recurse into interfaces toType.symbol.members.forEach((toMember, memberName) => { - const fromMember = fromType.symbol.members.get(memberName); - if (fromMember) { - const toMemberType = this.checker.getTypeOfSymbolAtLocation(toMember, node); - const fromMemberType = this.checker.getTypeOfSymbolAtLocation(fromMember, node); - this.validateFunctionAssignment( - node, fromMemberType, toMemberType, toName ? `${toName}.${memberName}` : memberName.toString()); + if (fromType.symbol.members) { + const fromMember = fromType.symbol.members.get(memberName); + if (fromMember) { + const toMemberType = this.checker.getTypeOfSymbolAtLocation(toMember, node); + const fromMemberType = this.checker.getTypeOfSymbolAtLocation(fromMember, node); + this.validateFunctionAssignment( + node, fromMemberType, toMemberType, + toName + ? `${toName}.${memberName}` + : memberName.toString() + ); + } } }); } @@ -4553,7 +4756,7 @@ export class LuaTransformer { return tstl.createBinaryExpression(expression, tstl.createNumericLiteral(1), tstl.SyntaxKind.AdditionOperator); } - private getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId { + private getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId | undefined { const symbol = this.checker.getSymbolAtLocation(identifier); let symbolId: number | undefined; if (symbol) { @@ -4574,12 +4777,14 @@ export class LuaTransformer { throw TSTLErrors.ReferencedBeforeDeclaration(identifier); } - } else { + } else if (symbolId !== undefined) { //Mark symbol as seen in all current scopes - this.scopeStack.forEach(s => { - if (!s.referencedSymbols) { s.referencedSymbols = new Set(); } - s.referencedSymbols.add(symbolId); - }); + for (const scope of this.scopeStack) { + if (!scope.referencedSymbols) { + scope.referencedSymbols = new Set(); + } + scope.referencedSymbols.add(symbolId); + } } } return symbolId; @@ -4617,18 +4822,25 @@ export class LuaTransformer { } if (scope.functionDefinitions) { + if (this.currentSourceFile === undefined) { + throw TSTLErrors.MissingSourceFile(); + } + for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { + if (functionDefinition.definition === undefined) { + throw TSTLErrors.UndefinedFunctionDefinition(functionSymbolId); + } + const { line, column } = tstl.getOriginalPos(functionDefinition.definition); - const definitionPos = ts.getPositionOfLineAndCharacter( - this.currentSourceFile, - line, - column); - if (functionSymbolId !== symbolId // Don't recurse into self - && declaration.pos < definitionPos // Ignore functions before symbol declaration - && functionDefinition.referencedSymbols.has(symbolId) - && this.shouldHoist(functionSymbolId, scope)) - { - return true; + if (line !== undefined && column !== undefined) { + const definitionPos = ts.getPositionOfLineAndCharacter(this.currentSourceFile, line, column); + if (functionSymbolId !== symbolId // Don't recurse into self + && declaration.pos < definitionPos // Ignore functions before symbol declaration + && functionDefinition.referencedSymbols.has(symbolId) + && this.shouldHoist(functionSymbolId, scope)) + { + return true; + } } } } @@ -4660,6 +4872,10 @@ export class LuaTransformer { const result = statements.slice(); const hoistedFunctions: Array = []; for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { + if (functionDefinition.definition === undefined) { + throw TSTLErrors.UndefinedFunctionDefinition(functionSymbolId); + } + if (this.shouldHoist(functionSymbolId, scope)) { const i = result.indexOf(functionDefinition.definition); result.splice(i, 1); @@ -4680,7 +4896,7 @@ export class LuaTransformer { const result = statements.slice(); const hoistedLocals: tstl.Identifier[] = []; for (const declaration of scope.variableDeclarations) { - const symbols = declaration.left.map(i => i.symbolId).filter(s => s !== undefined); + const symbols = this.filterUndefined(declaration.left.map(i => i.symbolId)); if (symbols.some(s => this.shouldHoist(s, scope))) { let assignment: tstl.AssignmentStatement | undefined; if (declaration.right) { @@ -4723,6 +4939,11 @@ export class LuaTransformer { protected popScope(): Scope { const scope = this.scopeStack.pop(); + + if (scope === undefined) { + throw TSTLErrors.UndefinedScope(); + } + return scope; } @@ -4743,25 +4964,37 @@ export class LuaTransformer { return declaration; } - private statementVisitResultToStatementArray(visitResult: StatementVisitResult): tstl.Statement[] { + private statementVisitResultToArray(visitResult: StatementVisitResult): tstl.Statement[] { if (!Array.isArray(visitResult)) { if (visitResult) { return [visitResult]; } return []; } - const flatten = (arr, result = []) => { - for (let i = 0, length = arr.length; i < length; i++) { - const value = arr[i]; - if (Array.isArray(value)) { - flatten(value, result); - } else if (value) { - // ignore value if undefined - result.push(value); - } - } - return result; - }; - return flatten(visitResult); + + return visitResult.filter(s => s !== undefined); + } + + private filterUndefined(items: Array): T[] { + return items.filter(i => i !== undefined) as T[]; + } + + private filterUndefinedAndCast( + items: Array, cast: (item: TOriginal) => item is TCast + ): TCast[] { + const filteredItems = items.filter(i => i !== undefined) as TOriginal[]; + if (filteredItems.every(i => cast(i))) { + return filteredItems as TCast[]; + } else { + throw TSTLErrors.CouldNotCast(cast.name); + } + } + + private expectExpression(visitResult: ExpressionVisitResult): tstl.Expression { + if (visitResult === undefined) { + throw new Error("Expected single visit result expression, but found undefined"); + } else { + return visitResult; + } } } diff --git a/src/LuaTranspiler.ts b/src/LuaTranspiler.ts index 771a32642..2f9a23ab6 100644 --- a/src/LuaTranspiler.ts +++ b/src/LuaTranspiler.ts @@ -60,7 +60,7 @@ export class LuaTranspiler { } public emitLuaLib(): string { - const outPath = path.join(this.options.outDir, "lualib_bundle.lua"); + const outPath = path.join(this.options.outDir || "", "lualib_bundle.lua"); fs.copyFileSync( path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"), outPath @@ -93,14 +93,14 @@ export class LuaTranspiler { public emitSourceFile(sourceFile: ts.SourceFile): number { if (!sourceFile.isDeclarationFile) { try { - const rootDir = this.options.rootDir; + const rootDir = this.options.rootDir || ""; const { lua, luaAST, sourceMap } = this.transpileSourceFile(sourceFile); let outPath = sourceFile.fileName; if (this.options.outDir !== this.options.rootDir) { const relativeSourcePath = path.resolve(sourceFile.fileName).replace(path.resolve(rootDir), ""); - outPath = path.join(this.options.outDir, relativeSourcePath); + outPath = path.join(this.options.outDir || "", relativeSourcePath); } // change extension or rename to outFile @@ -109,7 +109,7 @@ export class LuaTranspiler { outPath = this.options.outFile; } else { // append to workingDir or outDir - outPath = path.resolve(this.options.outDir, this.options.outFile); + outPath = path.resolve(this.options.outDir || "", this.options.outFile); } } else { const fileNameLua = path.basename(outPath, path.extname(outPath)) + ".lua"; diff --git a/src/TSHelper.ts b/src/TSHelper.ts index d50523fa1..d17214251 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -77,10 +77,7 @@ export class TSHelper { } public static isFileModule(sourceFile: ts.SourceFile): boolean { - if (sourceFile) { - return sourceFile.statements.some(TSHelper.isStatementExported); - } - return false; + return sourceFile.statements.some(TSHelper.isStatementExported); } public static isStatementExported(statement: ts.Statement): boolean { @@ -151,12 +148,12 @@ export class TSHelper { const flags = ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.AllowEmptyTuple; const typeNode = checker.typeToTypeNode(type, undefined, flags); - return typeNode && (ts.isArrayTypeNode(typeNode) || ts.isTupleTypeNode(typeNode)); + return typeNode !== undefined && (ts.isArrayTypeNode(typeNode) || ts.isTupleTypeNode(typeNode)); } public static isFunctionType(type: ts.Type, checker: ts.TypeChecker): boolean { const typeNode = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias); - return typeNode && ts.isFunctionTypeNode(typeNode); + return typeNode !== undefined && ts.isFunctionTypeNode(typeNode); } public static isFunctionTypeAtLocation(node: ts.Node, checker: ts.TypeChecker): boolean { @@ -225,11 +222,11 @@ export class TSHelper { } } - public static getContainingFunctionReturnType(node: ts.Node, checker: ts.TypeChecker): ts.Type { + public static getContainingFunctionReturnType(node: ts.Node, checker: ts.TypeChecker): ts.Type | undefined { const declaration = TSHelper.findFirstNodeAbove(node, ts.isFunctionLike); if (declaration) { const signature = checker.getSignatureFromDeclaration(declaration); - return checker.getReturnTypeOfSignature(signature); + return signature === undefined ? undefined : checker.getReturnTypeOfSignature(signature); } return undefined; } @@ -302,7 +299,9 @@ export class TSHelper { } // Search up until finding a node satisfying the callback - public static findFirstNodeAbove(node: ts.Node, callback: (n: ts.Node) => n is T): T { + public static findFirstNodeAbove( + node: ts.Node, callback: (n: ts.Node) => n is T + ): T | undefined { let current = node; while (current.parent) { if (callback(current.parent)) { @@ -314,7 +313,7 @@ export class TSHelper { return undefined; } - public static isBinaryAssignmentToken(token: ts.SyntaxKind): [boolean, ts.BinaryOperator] { + public static isBinaryAssignmentToken(token: ts.SyntaxKind): [true, ts.BinaryOperator] | [false, undefined] { switch (token) { case ts.SyntaxKind.BarEqualsToken: return [true, ts.SyntaxKind.BarToken]; @@ -371,7 +370,7 @@ export class TSHelper { node: ts.Expression, checker: ts.TypeChecker, program: ts.Program - ): [boolean, ts.Expression, ts.Expression] + ): [true, ts.Expression, ts.Expression] | [false, undefined, undefined] { if (ts.isElementAccessExpression(node) && (TSHelper.isExpressionWithEvaluationEffect(node.expression) @@ -396,7 +395,9 @@ export class TSHelper { return defaultArrayCallMethodNames.has(methodName); } - public static getExplicitThisParameter(signatureDeclaration: ts.SignatureDeclaration): ts.ParameterDeclaration { + public static getExplicitThisParameter( + signatureDeclaration: ts.SignatureDeclaration + ): ts.ParameterDeclaration | undefined { return signatureDeclaration.parameters.find( param => ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword); } @@ -405,7 +406,7 @@ export class TSHelper { classDeclaration: ts.ClassLikeDeclarationBase, callback: (classDeclaration: ts.ClassLikeDeclarationBase) => boolean, checker: ts.TypeChecker - ): ts.ClassLikeDeclarationBase + ): ts.ClassLikeDeclarationBase | undefined { if (callback(classDeclaration)) { return classDeclaration; @@ -417,7 +418,16 @@ export class TSHelper { } const symbol = extendsType.getSymbol(); - const declaration = symbol.getDeclarations().find(ts.isClassLike); + if (symbol === undefined) { + return undefined; + } + + const symbolDeclarations = symbol.getDeclarations(); + if (symbolDeclarations === undefined) { + return undefined; + } + + const declaration = symbolDeclarations.find(ts.isClassLike); if (!declaration) { return undefined; } @@ -477,7 +487,7 @@ export class TSHelper { const hasInitializedField = (e: ts.ClassElement) => ts.isPropertyDeclaration(e) - && e.initializer + && e.initializer !== undefined && TSHelper.isSamePropertyName(e.name, element.name); return TSHelper.findInClassOrAncestor( @@ -570,6 +580,11 @@ export class TSHelper { || ts.isClassExpression(n) || ts.isInterfaceDeclaration(n) ); + + if (scopeDeclaration === undefined) { + return ContextType.NonVoid; + } + const scopeType = checker.getTypeAtLocation(scopeDeclaration); if (scopeType && TSHelper.getCustomDecorators(scopeType, checker).has(DecoratorKind.NoSelf)) { return ContextType.Void; @@ -649,7 +664,7 @@ export class TSHelper { public static isValidLuaIdentifier(str: string): boolean { const match = str.match(/[a-zA-Z_][a-zA-Z0-9_]*/); - return match && match[0] === str; + return match !== undefined && match !== null && match[0] === str; } // Checks that a name is valid for use in lua function declaration syntax: @@ -657,7 +672,7 @@ export class TSHelper { // 'getFoo().bar' => fails ('function getFoo().bar()' would be illegal) public static isValidLuaFunctionDeclarationName(str: string): boolean { const match = str.match(/[a-zA-Z0-9_\.]+/); - return match && match[0] === str; + return match !== undefined && match !== null && match[0] === str; } public static isFalsible(type: ts.Type, strictNullChecks: boolean): boolean { @@ -720,7 +735,10 @@ export class TSHelper { return this.isStandardLibraryDeclaration(declaration, program); } - public static isEnumMember(enumDeclaration: ts.EnumDeclaration, value: ts.Expression): [boolean, ts.PropertyName] { + public static isEnumMember( + enumDeclaration: ts.EnumDeclaration, + value: ts.Expression + ): [true, ts.PropertyName] | [false, undefined] { if (ts.isIdentifier(value)) { const enumMember = enumDeclaration.members.find(m => ts.isIdentifier(m.name) && m.name.text === value.text); if (enumMember !== undefined) { diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index 56ee4e73b..07e57e634 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -3,6 +3,9 @@ import { TranspileError } from "./TranspileError"; import { TSHelper as tsHelper } from "./TSHelper"; export class TSTLErrors { + public static CouldNotCast = (castName: string) => + new Error(`Failed to cast all elements to expected type using ${castName}.`); + public static CouldNotFindEnumMember = (enumDeclaration: ts.EnumDeclaration, enumMember: string, node: ts.Node) => new TranspileError(`Could not find ${enumMember} in ${enumDeclaration.name.text}`, node); @@ -29,6 +32,9 @@ export class TSTLErrors { public static InvalidNewExpressionOnExtension = (node: ts.Node) => new TranspileError(`Cannot construct classes with decorator '@extension' or '@metaExtension'.`, node); + public static InvalidExportDeclaration = (declaration: ts.ExportDeclaration) => + new TranspileError("Encountered invalid export declaration without exports and without module.", declaration); + public static InvalidExtendsExtension = (node: ts.Node) => new TranspileError(`Cannot extend classes with decorator '@extension' or '@metaExtension'.`, node); @@ -56,9 +62,30 @@ export class TSTLErrors { public static MissingClassName = (node: ts.Node) => new TranspileError(`Class declarations must have a name.`, node); + public static MissingForOfVariables = (node: ts.Node) => + new TranspileError("Transpiled ForOf variable declaration list contains no declarations.", node); + + public static MissingFunctionName = (declaration: ts.FunctionLikeDeclaration) => + new TranspileError("Unsupported function declaration without name.", declaration); + public static MissingMetaExtension = (node: ts.Node) => new TranspileError(`@metaExtension requires the extension of the metatable class.`, node); + public static MissingSourceFile = () => + new Error("Expected transformer.sourceFile to be set, but it isn't."); + + public static UndefinedFunctionDefinition = (functionSymbolId: number) => + new Error(`Function definition for function symbol ${functionSymbolId} is undefined.`); + + public static UndefinedScope = () => + new Error("Expected to pop a scope, but found undefined."); + + public static UndefinedTypeNode = (node: ts.Node) => + new TranspileError("Failed to resolve required type node.", node); + + public static UnknownSuperType = (node: ts.Node) => + new TranspileError("Unable to resolve type of super expression.", node); + public static UnsupportedDefaultExport = (node: ts.Node) => new TranspileError(`Default exports are not supported.`, node); @@ -77,6 +104,9 @@ export class TSTLErrors { public static UnsupportedForTarget = (functionality: string, version: string, node: ts.Node) => new TranspileError(`${functionality} is/are not supported for target Lua ${version}.`, node); + public static UnsupportedFunctionWithoutBody = (node: ts.FunctionLikeDeclaration) => + new TranspileError("Functions with undefined bodies are not supported.", node); + public static UnsupportedNoSelfFunctionConversion = (node: ts.Node, name?: string) => { if (name) { return new TranspileError( diff --git a/test/compiler/project.spec.ts b/test/compiler/project.spec.ts index c72c0041e..a23fe9209 100644 --- a/test/compiler/project.spec.ts +++ b/test/compiler/project.spec.ts @@ -6,7 +6,7 @@ import { compile } from "../../src/Compiler"; * Find all files inside a dir, recursively. */ function getAllFiles(dir: string): string[] { - return fs.readdirSync(dir).reduce((files, file) => { + return fs.readdirSync(dir).reduce((files: string[], file) => { const name = path.join(dir, file); const isDirectory = fs.statSync(name).isDirectory(); return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name]; diff --git a/test/tsconfig.json b/test/tsconfig.json index 786d64a68..666a6006e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,5 +1,8 @@ { "compilerOptions": { + "baseUrl": ".", + "paths": { "*": ["types/*"] }, + "strict": true, "target": "es2017", "types": ["node", "jest"], diff --git a/test/types/fengari.d.ts b/test/types/fengari.d.ts new file mode 100644 index 000000000..5e76ec549 --- /dev/null +++ b/test/types/fengari.d.ts @@ -0,0 +1,345 @@ +export const FENGARI_AUTHORS: string; +export const FENGARI_COPYRIGHT: string; +export const FENGARI_RELEASE: string; +export const FENGARI_VERSION: string; +export const FENGARI_VERSION_MAJOR: string; +export const FENGARI_VERSION_MINOR: string; +export const FENGARI_VERSION_NUM: number; +export const FENGARI_VERSION_RELEASE: string; +export namespace lauxlib { + const LUA_ERRFILE: number; + const LUA_FILEHANDLE: Uint8Array; + const LUA_LOADED_TABLE: Uint8Array; + const LUA_NOREF: number; + const LUA_PRELOAD_TABLE: Uint8Array; + const LUA_REFNIL: number; + class luaL_Buffer { + L: any; + b: any; + n: any; + } + function luaL_addchar(B: any, c: any): void; + function luaL_addlstring(B: any, s: any, l: any): void; + function luaL_addsize(B: any, s: any): void; + function luaL_addstring(B: any, s: any): void; + function luaL_addvalue(B: any): void; + function luaL_argcheck(L: any, cond: any, arg: any, extramsg: any): void; + function luaL_argerror(L: any, arg: any, extramsg: any): any; + function luaL_buffinit(L: any, B: any): void; + function luaL_buffinitsize(L: any, B: any, sz: any): any; + function luaL_callmeta(L: any, obj: any, event: any): any; + function luaL_checkany(L: any, arg: any): void; + function luaL_checkinteger(L: any, arg: any): any; + function luaL_checklstring(L: any, arg: any): any; + function luaL_checknumber(L: any, arg: any): any; + function luaL_checkoption(L: any, arg: any, def: any, lst: any): any; + function luaL_checkstack(L: any, space: any, msg: any): void; + function luaL_checkstring(L: any, arg: any): any; + function luaL_checktype(L: any, arg: any, t: any): void; + function luaL_checkudata(L: any, ud: any, tname: any): any; + function luaL_checkversion(L: any): void; + function luaL_checkversion_(L: any, ver: any, sz: any): void; + function luaL_dofile(L: any, filename: any): any; + function luaL_dostring(L: any, s: any): any; + function luaL_error(L: any, fmt: any, argp: any): any; + function luaL_execresult(L: any, e: any): any; + function luaL_fileresult(L: any, stat: any, fname: any, e: any): any; + function luaL_getmetafield(L: any, obj: any, event: any): any; + function luaL_getmetatable(L: any, n: any): any; + function luaL_getsubtable(L: any, idx: any, fname: any): any; + function luaL_gsub(L: any, s: any, p: any, r: any): any; + function luaL_len(L: any, idx: any): any; + function luaL_loadbuffer(L: any, s: any, sz: any, n: any): any; + function luaL_loadbufferx(L: any, buff: any, size: any, name: any, mode: any): any; + function luaL_loadfile(L: any, filename: any): any; + function luaL_loadfilex(L: any, filename: any, mode: any): any; + function luaL_loadstring(L: any, s: any): any; + function luaL_newlib(L: any, l: any): void; + function luaL_newlibtable(L: any): void; + function luaL_newmetatable(L: any, tname: any): any; + function luaL_newstate(): any; + function luaL_opt(L: any, f: any, n: any, d: any): any; + function luaL_optinteger(L: any, arg: any, def: any): any; + function luaL_optlstring(L: any, arg: any, def: any): any; + function luaL_optnumber(L: any, arg: any, def: any): any; + function luaL_optstring(L: any, arg: any, def: any): any; + function luaL_prepbuffer(B: any): any; + function luaL_prepbuffsize(B: any, sz: any): any; + function luaL_pushresult(B: any): void; + function luaL_pushresultsize(B: any, sz: any): void; + function luaL_ref(L: any, t: any): any; + function luaL_requiref(L: any, modname: any, openf: any, glb: any): void; + function luaL_setfuncs(L: any, l: any, nup: any): void; + function luaL_setmetatable(L: any, tname: any): void; + function luaL_testudata(L: any, ud: any, tname: any): any; + function luaL_tolstring(L: any, idx: any): any; + function luaL_traceback(L: any, L1: any, msg: any, level: any): void; + function luaL_typename(L: any, i: any): any; + function luaL_unref(L: any, t: any, ref: any): void; + function luaL_where(L: any, level: any): void; + function lua_writestringerror(...args: any[]): void; +} +export namespace lua { + const LUA_AUTHORS: string; + const LUA_COPYRIGHT: string; + const LUA_ERRERR: number; + const LUA_ERRGCMM: number; + const LUA_ERRMEM: number; + const LUA_ERRRUN: number; + const LUA_ERRSYNTAX: number; + const LUA_HOOKCALL: number; + const LUA_HOOKCOUNT: number; + const LUA_HOOKLINE: number; + const LUA_HOOKRET: number; + const LUA_HOOKTAILCALL: number; + const LUA_MASKCALL: number; + const LUA_MASKCOUNT: number; + const LUA_MASKLINE: number; + const LUA_MASKRET: number; + const LUA_MINSTACK: number; + const LUA_MULTRET: number; + const LUA_NUMTAGS: number; + const LUA_OK: number; + const LUA_OPADD: number; + const LUA_OPBAND: number; + const LUA_OPBNOT: number; + const LUA_OPBOR: number; + const LUA_OPBXOR: number; + const LUA_OPDIV: number; + const LUA_OPEQ: number; + const LUA_OPIDIV: number; + const LUA_OPLE: number; + const LUA_OPLT: number; + const LUA_OPMOD: number; + const LUA_OPMUL: number; + const LUA_OPPOW: number; + const LUA_OPSHL: number; + const LUA_OPSHR: number; + const LUA_OPSUB: number; + const LUA_OPUNM: number; + const LUA_REGISTRYINDEX: number; + const LUA_RELEASE: string; + const LUA_RIDX_GLOBALS: number; + const LUA_RIDX_LAST: number; + const LUA_RIDX_MAINTHREAD: number; + const LUA_SIGNATURE: Uint8Array; + const LUA_TBOOLEAN: number; + const LUA_TFUNCTION: number; + const LUA_TLIGHTUSERDATA: number; + const LUA_TNIL: number; + const LUA_TNONE: number; + const LUA_TNUMBER: number; + const LUA_TSTRING: number; + const LUA_TTABLE: number; + const LUA_TTHREAD: number; + const LUA_TUSERDATA: number; + const LUA_VERSION: string; + const LUA_VERSION_MAJOR: string; + const LUA_VERSION_MINOR: string; + const LUA_VERSION_NUM: number; + const LUA_VERSION_RELEASE: string; + const LUA_YIELD: number; + class lua_Debug { + event: any; + name: any; + namewhat: any; + what: any; + source: any; + currentline: any; + linedefined: any; + lastlinedefined: any; + nups: any; + nparams: any; + isvararg: any; + istailcall: any; + short_src: any; + i_ci: any; + } + function lua_absindex(L: any, idx: any): any; + function lua_arith(L: any, op: any): void; + function lua_atnativeerror(L: any, errorf: any): any; + function lua_atpanic(L: any, panicf: any): any; + function lua_call(L: any, n: any, r: any): void; + function lua_callk(L: any, nargs: any, nresults: any, ctx: any, k: any): void; + function lua_checkstack(L: any, n: any): any; + function lua_close(L: any): void; + function lua_compare(L: any, index1: any, index2: any, op: any): any; + function lua_concat(L: any, n: any): void; + function lua_copy(L: any, fromidx: any, toidx: any): void; + function lua_createtable(L: any, narray: any, nrec: any): void; + function lua_dump(L: any, writer: any, data: any, strip: any): any; + function lua_error(L: any): void; + function lua_gc(): void; + function lua_getallocf(): any; + function lua_getextraspace(): any; + function lua_getfield(L: any, idx: any, k: any): any; + function lua_getglobal(L: any, name: any): any; + function lua_gethook(L: any): any; + function lua_gethookcount(L: any): any; + function lua_gethookmask(L: any): any; + function lua_geti(L: any, idx: any, n: any): any; + function lua_getinfo(L: any, what: any, ar: any): any; + function lua_getlocal(L: any, ar: any, n: any): any; + function lua_getmetatable(L: any, objindex: any): any; + function lua_getstack(L: any, level: any, ar: any): any; + function lua_gettable(L: any, idx: any): any; + function lua_gettop(L: any): any; + function lua_getupvalue(L: any, funcindex: any, n: any): any; + function lua_getuservalue(L: any, idx: any): any; + function lua_insert(L: any, idx: any): void; + function lua_isboolean(L: any, n: any): any; + function lua_iscfunction(L: any, idx: any): any; + function lua_isfunction(L: any, idx: any): any; + function lua_isinteger(L: any, idx: any): any; + function lua_islightuserdata(L: any, idx: any): any; + function lua_isnil(L: any, n: any): any; + function lua_isnone(L: any, n: any): any; + function lua_isnoneornil(L: any, n: any): any; + function lua_isnumber(L: any, idx: any): any; + function lua_isproxy(p: any, L: any): any; + function lua_isstring(L: any, idx: any): any; + function lua_istable(L: any, idx: any): any; + function lua_isthread(L: any, idx: any): any; + function lua_isuserdata(L: any, idx: any): any; + function lua_isyieldable(L: any): any; + function lua_len(L: any, idx: any): void; + function lua_load(L: any, reader: any, data: any, chunkname: any, mode: any): any; + function lua_newstate(): any; + function lua_newtable(L: any): void; + function lua_newthread(L: any): any; + function lua_newuserdata(L: any, size: any): any; + function lua_next(L: any, idx: any): any; + function lua_pcall(L: any, n: any, r: any, f: any): any; + function lua_pcallk(L: any, nargs: any, nresults: any, errfunc: any, ctx: any, k: any): any; + function lua_pop(L: any, n: any): void; + function lua_pushboolean(L: any, b: any): void; + function lua_pushcclosure(L: any, fn: any, n: any): void; + function lua_pushcfunction(L: any, fn: any): void; + function lua_pushfstring(L: any, fmt: any, argp: any): any; + function lua_pushglobaltable(L: any): void; + function lua_pushinteger(L: any, n: any): void; + function lua_pushjsclosure(L: any, fn: any, n: any): void; + function lua_pushjsfunction(L: any, fn: any): void; + function lua_pushlightuserdata(L: any, p: any): void; + function lua_pushliteral(L: any, s: any): any; + function lua_pushlstring(L: any, s: any, len: any): any; + function lua_pushnil(L: any): void; + function lua_pushnumber(L: any, n: any): void; + function lua_pushstring(L: any, s: any): any; + function lua_pushthread(L: any): any; + function lua_pushvalue(L: any, idx: any): void; + function lua_pushvfstring(L: any, fmt: any, argp: any): any; + function lua_rawequal(L: any, index1: any, index2: any): any; + function lua_rawget(L: any, idx: any): any; + function lua_rawgeti(L: any, idx: any, n: any): any; + function lua_rawgetp(L: any, idx: any, p: any): any; + function lua_rawlen(L: any, idx: any): any; + function lua_rawset(L: any, idx: any): void; + function lua_rawseti(L: any, idx: any, n: any): void; + function lua_rawsetp(L: any, idx: any, p: any): void; + function lua_register(L: any, n: any, f: any): void; + function lua_remove(L: any, idx: any): void; + function lua_replace(L: any, idx: any): void; + function lua_resume(L: any, from: any, nargs: any): any; + function lua_rotate(L: any, idx: any, n: any): void; + const lua_setallof: any; + function lua_setfield(L: any, idx: any, k: any): void; + function lua_setglobal(L: any, name: any): void; + function lua_sethook(L: any, func: any, mask: any, count: any): void; + function lua_seti(L: any, idx: any, n: any): void; + function lua_setlocal(L: any, ar: any, n: any): any; + function lua_setmetatable(L: any, objindex: any): any; + function lua_settable(L: any, idx: any): void; + function lua_settop(L: any, idx: any): void; + function lua_setupvalue(L: any, funcindex: any, n: any): any; + function lua_setuservalue(L: any, idx: any): void; + function lua_status(L: any): any; + function lua_stringtonumber(L: any, s: any): any; + function lua_toboolean(L: any, idx: any): any; + function lua_tocfunction(L: any, idx: any): any; + function lua_todataview(L: any, idx: any): any; + function lua_tointeger(L: any, idx: any): any; + function lua_tointegerx(L: any, idx: any): any; + function lua_tojsstring(L: any, idx: any): any; + function lua_tolstring(L: any, idx: any): any; + function lua_tonumber(L: any, idx: any): any; + function lua_tonumberx(L: any, idx: any): any; + function lua_topointer(L: any, idx: any): any; + function lua_toproxy(L: any, idx: any): any; + function lua_tostring(L: any, idx: any): LuaString; + function lua_tothread(L: any, idx: any): any; + function lua_touserdata(L: any, idx: any): any; + function lua_type(L: any, idx: any): any; + function lua_typename(L: any, t: any): any; + function lua_upvalueid(L: any, fidx: any, n: any): any; + function lua_upvalueindex(i: any): any; + function lua_upvaluejoin(L: any, fidx1: any, n1: any, fidx2: any, n2: any): void; + function lua_version(L: any): any; + function lua_xmove(from: any, to: any, n: any): void; + function lua_yield(L: any, n: any): void; + function lua_yieldk(L: any, nresults: any, ctx: any, k: any): any; +} +export namespace luaconf { + const LUAI_MAXSTACK: number; + const LUAL_BUFFERSIZE: number; + const LUA_COMPAT_FLOATSTRING: boolean; + const LUA_DIRSEP: string; + const LUA_EXEC_DIR: string; + const LUA_IDSIZE: number; + const LUA_INTEGER_FMT: string; + const LUA_INTEGER_FRMLEN: string; + const LUA_JSDIR: string; + const LUA_JSPATH_DEFAULT: Uint8Array; + const LUA_LDIR: string; + const LUA_MAXINTEGER: number; + const LUA_MININTEGER: number; + const LUA_NUMBER_FMT: string; + const LUA_NUMBER_FRMLEN: string; + const LUA_PATH_DEFAULT: Uint8Array; + const LUA_PATH_MARK: string; + const LUA_PATH_SEP: string; + const LUA_SHRDIR: string; + const LUA_VDIR: string; + function frexp(value: any): any; + function ldexp(mantissa: any, exponent: any): any; + function lua_getlocaledecpoint(): any; + function lua_integer2str(n: any): any; + function lua_number2str(n: any): any; + function lua_numbertointeger(n: any): any; + function luai_apicheck(l: any, e: any): void; +} +export namespace lualib { + const LUA_BITLIBNAME: string; + const LUA_COLIBNAME: string; + const LUA_DBLIBNAME: string; + const LUA_FENGARILIBNAME: string; + const LUA_IOLIBNAME: string; + const LUA_LOADLIBNAME: string; + const LUA_MATHLIBNAME: string; + const LUA_OSLIBNAME: string; + const LUA_STRLIBNAME: string; + const LUA_TABLIBNAME: string; + const LUA_UTF8LIBNAME: string; + const LUA_VERSUFFIX: string; + function luaL_openlibs(L: any): void; + function lua_assert(c: any): void; + function luaopen_coroutine(L: any): any; + function luaopen_debug(L: any): any; + function luaopen_fengari(L: any): any; + function luaopen_io(L: any): any; + function luaopen_math(L: any): any; + function luaopen_os(L: any): any; + function luaopen_package(L: any): any; + function luaopen_string(L: any): any; + function luaopen_table(L: any): any; + function luaopen_utf8(L: any): any; +} + +export type LuaString = number[]; + +export function luastring_eq(a: any, b: any): any; +export function luastring_indexOf(s: any, v: any, i: any): any; +export function luastring_of(): any; +export function to_jsstring(value: LuaString, from?: any, to?: any, replacement_char?: any): string; +export function to_luastring(str: string, cache?: any): LuaString; +export function to_uristring(a: any): any; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 0fd43b914..7a2268624 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -484,51 +484,31 @@ test("Unknown unary postfix error", () => { }); test("Incompatible fromCodePoint expression error", () => { - const transformer = util.makeTestTransformer(LuaTarget.LuaJIT); - - const identifier = ts.createIdentifier("fromCodePoint"); - expect(() => transformer.transformStringExpression(identifier)).toThrowExactError( + expect(() => util.transpileString("const abc = String.fromCodePoint(123);")).toThrowExactError( TSTLErrors.UnsupportedForTarget( "string property fromCodePoint", - LuaTarget.LuaJIT, + LuaTarget.Lua53, util.nodeStub, ), ); }); test("Unknown string expression error", () => { - const transformer = util.makeTestTransformer(LuaTarget.LuaJIT); - - const identifier = ts.createIdentifier("abcd"); - expect(() => transformer.transformStringExpression(identifier)).toThrowExactError( - TSTLErrors.UnsupportedForTarget("string property abcd", LuaTarget.LuaJIT, util.nodeStub), + expect(() => util.transpileString("const abc = String.abcd();")).toThrowExactError( + TSTLErrors.UnsupportedForTarget("string property abcd", LuaTarget.Lua53, util.nodeStub), ); }); test("Unsupported array function error", () => { - const transformer = util.makeTestTransformer(); - - const mockNode: any = { - kind: ts.SyntaxKind.CallExpression, - arguments: [], - caller: ts.createLiteral(false), - expression: { - name: ts.createIdentifier("unknownFunction"), - expression: ts.createLiteral(false), - }, - }; - - expect(() => - transformer.transformArrayCallExpression(mockNode as ts.CallExpression), - ).toThrowExactError(TSTLErrors.UnsupportedProperty("array", "unknownFunction", util.nodeStub)); + expect(() => util.transpileString("const abc = [].unknownFunction();")).toThrowExactError( + TSTLErrors.UnsupportedProperty("array", "unknownFunction", util.nodeStub), + ); }); test("Unsupported math property error", () => { - const transformer = util.makeTestTransformer(); - - expect(() => - transformer.transformMathExpression(ts.createIdentifier("unknownProperty")), - ).toThrowExactError(TSTLErrors.UnsupportedProperty("math", "unknownProperty", util.nodeStub)); + expect(() => util.transpileString("const abc = Math.unknownProperty;")).toThrowExactError( + TSTLErrors.UnsupportedProperty("math", "unknownProperty", util.nodeStub), + ); }); test("Unsupported object literal element error", () => { diff --git a/test/unit/loops.spec.ts b/test/unit/loops.spec.ts index 1b78813d8..bf1439006 100644 --- a/test/unit/loops.spec.ts +++ b/test/unit/loops.spec.ts @@ -774,8 +774,8 @@ test.each([ const lua53 = { luaTarget: LuaTarget.Lua53 }; const luajit = { luaTarget: LuaTarget.LuaJIT }; - expect(() => util.transpileString(loop, lua51)).toThrowError( - TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, undefined), + expect(() => util.transpileString(loop, lua51)).toThrowExactError( + TSTLErrors.UnsupportedForTarget("Continue statement", LuaTarget.Lua51, ts.createContinue()), ); expect(util.transpileString(loop, lua52).indexOf("::__continue1::") !== -1).toBe(true); expect(util.transpileString(loop, lua53).indexOf("::__continue1::") !== -1).toBe(true); diff --git a/test/unit/lualib/lualib.spec.ts b/test/unit/lualib/lualib.spec.ts index 87913fd84..e9c5dee7b 100644 --- a/test/unit/lualib/lualib.spec.ts +++ b/test/unit/lualib/lualib.spec.ts @@ -393,12 +393,12 @@ test.each([ }); test.each([ - { array: [1, [2, 3], [4]], map: (value: T) => value }, - { array: [1, 2, 3], map: (v: number) => v * 2 }, - { array: [1, 2, 3], map: (v: number) => [v, v * 2] }, - { array: [1, 2, 3], map: (v: number) => [v, [v]] }, - { array: [1, 2, 3], map: (v: number, i: number) => [v * 2 * i] }, -])("array.flatMap (%p)", ({ array, map }) => { + { array: [1, [2, 3], [4]], map: (value: T) => value, expected: [1, 2, 3, 4] }, + { array: [1, 2, 3], map: (v: number) => v * 2, expected: [2, 4, 6] }, + { array: [1, 2, 3], map: (v: number) => [v, v * 2], expected: [1, 2, 2, 4, 3, 6] }, + { array: [1, 2, 3], map: (v: number) => [v, [v]], expected: [1, [1], 2, [2], 3, [3]] }, + { array: [1, 2, 3], map: (v: number, i: number) => [v * 2 * i], expected: [0, 4, 12] }, +])("array.flatMap (%p)", ({ array, map, expected }) => { const result = util.transpileAndExecute(` const array = ${JSON.stringify(array)}; const result = array.flatMap(${map.toString()}); @@ -406,7 +406,6 @@ test.each([ `); // TODO(node 12): array.flatMap(map) - const expected = [].concat(...(array as any[]).map(map)); expect(JSON.parse(result)).toEqual(expected); }); @@ -447,9 +446,7 @@ test.each([ `); const result = JSON.parse(jsonResult); - for (const key in expected) { - expect(result[key]).toBe(expected[key]); - } + expect(result).toEqual(expected); }); test.each([ diff --git a/test/unit/math.spec.ts b/test/unit/math.spec.ts index c518c69a3..46c23b0f4 100644 --- a/test/unit/math.spec.ts +++ b/test/unit/math.spec.ts @@ -22,7 +22,8 @@ test.each(["E", "LN10", "LN2", "LOG10E", "LOG2E", "SQRT1_2", "SQRT2"])( "Math constant (%p)", constant => { const epsilon = 0.000001; - const code = `return Math.abs(Math.${constant} - ${Math[constant]}) <= ${epsilon}`; + const jsValue: number = (Math as Math & { [key: string]: any })[constant]; + const code = `return Math.abs(Math.${constant} - ${jsValue}) <= ${epsilon}`; expect(util.transpileAndExecute(code)).toBe(true); }, ); diff --git a/test/unit/require.spec.ts b/test/unit/require.spec.ts index 747ce7b7a..a7e5e378c 100644 --- a/test/unit/require.spec.ts +++ b/test/unit/require.spec.ts @@ -87,7 +87,10 @@ test.each([ ); const regex = /require\("(.*?)"\)/; const match = regex.exec(lua); - expect(match[1]).toBe(expectedPath); + + if (util.expectToBeDefined(match)) { + expect(match[1]).toBe(expectedPath); + } } }, ); @@ -108,6 +111,10 @@ test.each([ "src/file.ts", ); const regex = /require\("(.*?)"\)/; - expect(regex.exec(lua)[1]).toBe(expectedPath); + const match = regex.exec(lua); + + if (util.expectToBeDefined(match)) { + expect(match[1]).toBe(expectedPath); + } }, ); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index e6e6fe766..fef652744 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -128,12 +128,13 @@ test("Inline sourcemaps", () => { const inlineSourceMapMatch = lua.match( /--# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/, ); - expect(inlineSourceMapMatch !== null && inlineSourceMapMatch !== undefined).toBe(true); - const inlineSourceMap = Buffer.from(inlineSourceMapMatch[1], "base64").toString(); - expect(sourceMap).toBe(inlineSourceMap); + if (util.expectToBeDefined(inlineSourceMapMatch)) { + const inlineSourceMap = Buffer.from(inlineSourceMapMatch[1], "base64").toString(); + expect(sourceMap).toBe(inlineSourceMap); - expect(util.executeLua(lua)).toBe("foo"); + expect(util.executeLua(lua)).toBe("foo"); + } }); // Helper functions diff --git a/test/unit/spreadElement.spec.ts b/test/unit/spreadElement.spec.ts index 00acb8d05..dcd40db5e 100644 --- a/test/unit/spreadElement.spec.ts +++ b/test/unit/spreadElement.spec.ts @@ -5,9 +5,9 @@ test.each([{ inp: [] }, { inp: [1, 2, 3] }, { inp: [1, "test", 3] }])( "Spread Element Push (%p)", ({ inp }) => { const result = util.transpileAndExecute( - `return JSONStringify([].push(...${JSON.stringify(inp)}));`, + `return JSONStringify(([] as Array).push(...${JSON.stringify(inp)}));`, ); - expect(result).toBe([].push(...inp)); + expect(result).toBe(([] as Array).push(...inp)); }, ); diff --git a/test/unit/tshelper.spec.ts b/test/unit/tshelper.spec.ts index 6e1b0d9df..9513250e4 100644 --- a/test/unit/tshelper.spec.ts +++ b/test/unit/tshelper.spec.ts @@ -33,10 +33,6 @@ test.each([ expect(result).toEqual(expected); }); -test("IsFileModuleNull", () => { - expect(tsHelper.isFileModule(undefined)).toEqual(false); -}); - test("GetCustomDecorators single", () => { const source = ` /** @compileMembersOnly */ @@ -52,12 +48,15 @@ test("GetCustomDecorators single", () => { const [sourceFile, typeChecker] = util.parseTypeScript(source); const identifier = util.findFirstChild(sourceFile, ts.isEnumDeclaration); - const enumType = typeChecker.getTypeAtLocation(identifier); - const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + if (util.expectToBeDefined(identifier)) { + const enumType = typeChecker.getTypeAtLocation(identifier); - expect(decorators.size).toBe(1); - expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + + expect(decorators.size).toBe(1); + expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + } }); test("GetCustomDecorators multiple", () => { @@ -76,13 +75,16 @@ test("GetCustomDecorators multiple", () => { const [sourceFile, typeChecker] = util.parseTypeScript(source); const identifier = util.findFirstChild(sourceFile, ts.isEnumDeclaration); - const enumType = typeChecker.getTypeAtLocation(identifier); - const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + if (util.expectToBeDefined(identifier)) { + const enumType = typeChecker.getTypeAtLocation(identifier); + + const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); - expect(decorators.size).toBe(2); - expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); - expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); + expect(decorators.size).toBe(2); + expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); + } }); test("GetCustomDecorators single jsdoc", () => { @@ -100,12 +102,15 @@ test("GetCustomDecorators single jsdoc", () => { const [sourceFile, typeChecker] = util.parseTypeScript(source); const identifier = util.findFirstChild(sourceFile, ts.isEnumDeclaration); - const enumType = typeChecker.getTypeAtLocation(identifier); - const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + if (util.expectToBeDefined(identifier)) { + const enumType = typeChecker.getTypeAtLocation(identifier); - expect(decorators.size).toBe(1); - expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + + expect(decorators.size).toBe(1); + expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + } }); test("GetCustomDecorators multiple jsdoc", () => { @@ -124,13 +129,16 @@ test("GetCustomDecorators multiple jsdoc", () => { const [sourceFile, typeChecker] = util.parseTypeScript(source); const identifier = util.findFirstChild(sourceFile, ts.isEnumDeclaration); - const enumType = typeChecker.getTypeAtLocation(identifier); - const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + if (util.expectToBeDefined(identifier)) { + const enumType = typeChecker.getTypeAtLocation(identifier); + + const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); - expect(decorators.size).toBe(2); - expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); - expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + expect(decorators.size).toBe(2); + expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); + expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + } }); test("GetCustomDecorators multiple default jsdoc", () => { @@ -151,11 +159,14 @@ test("GetCustomDecorators multiple default jsdoc", () => { const [sourceFile, typeChecker] = util.parseTypeScript(source); const identifier = util.findFirstChild(sourceFile, ts.isEnumDeclaration); - const enumType = typeChecker.getTypeAtLocation(identifier); - const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); + if (util.expectToBeDefined(identifier)) { + const enumType = typeChecker.getTypeAtLocation(identifier); + + const decorators = tsHelper.getCustomDecorators(enumType, typeChecker); - expect(decorators.size).toBe(2); - expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); - expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + expect(decorators.size).toBe(2); + expect(decorators.has(DecoratorKind.Phantom)).toBeTruthy(); + expect(decorators.has(DecoratorKind.CompileMembersOnly)).toBeTruthy(); + } }); diff --git a/test/util.ts b/test/util.ts index d09cd7824..fd09d9541 100644 --- a/test/util.ts +++ b/test/util.ts @@ -198,7 +198,13 @@ export function parseTypeScript( target: LuaTarget = LuaTarget.Lua53, ): [ts.SourceFile, ts.TypeChecker] { const program = createStringCompilerProgram(typescript, { luaTarget: target }); - return [program.getSourceFile("file.ts"), program.getTypeChecker()]; + const sourceFile = program.getSourceFile("file.ts"); + + if (sourceFile === undefined) { + throw new Error("Could not find source file file.ts in program."); + } + + return [sourceFile, program.getTypeChecker()]; } export function findFirstChild( @@ -217,3 +223,8 @@ export function findFirstChild( } return undefined; } + +export function expectToBeDefined(subject: T | null | undefined): subject is T { + expect(subject).toBeDefined(); + return true; // If this was false the expect would have thrown an error +} diff --git a/tsconfig.json b/tsconfig.json index 84d667105..59ea10e64 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { - "noImplicitThis": true, - "alwaysStrict": true, + "strict": true, "rootDir": "./src", "outDir": "./dist", "declaration": true,