diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index e5058cbf5..3a12194bd 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -38,18 +38,10 @@ export class LuaPrinter { [tstl.SyntaxKind.BitwiseNotOperator]: "~", }; - private options: CompilerOptions; - private emitHost: EmitHost; - - private currentIndent: string; - + private currentIndent = ""; private sourceFile = ""; - public constructor(options: CompilerOptions, emitHost: EmitHost) { - this.options = options; - this.emitHost = emitHost; - this.currentIndent = ""; - } + public constructor(private options: CompilerOptions, private emitHost: EmitHost) {} public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile = ""): [string, string] { // Add traceback lualib if sourcemap traceback option is enabled diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index b1d54826d..81a435f3c 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -54,85 +54,81 @@ export interface DiagnosticsProducingTypeChecker extends ts.TypeChecker { } export class LuaTransformer { - protected isStrict: boolean; - protected luaTarget: LuaTarget; + protected readonly typeValidationCache = new Map>(); + protected currentNamespace?: ts.ModuleDeclaration; protected checker: DiagnosticsProducingTypeChecker; protected options: CompilerOptions; - // Resolver is lazy-initialized in transformSourceFile to avoid type-checking all files - protected resolver!: EmitResolver; - - protected isModule = false; - protected currentSourceFile?: ts.SourceFile; - - protected currentNamespace: ts.ModuleDeclaration | undefined; - protected classStack: ts.ClassLikeDeclaration[] = []; - - protected scopeStack: Scope[] = []; - protected genVarCounter = 0; - - protected luaLibFeatureSet = new Set(); - - protected symbolInfo = new Map(); - protected symbolIds = new Map(); - - protected genSymbolIdCounter = 0; - - protected readonly typeValidationCache: Map> = new Map>(); + protected luaTarget: LuaTarget; + protected isStrict: boolean; public constructor(protected program: ts.Program) { this.checker = (program as any).getDiagnosticsProducingTypeChecker(); this.options = program.getCompilerOptions(); + + this.luaTarget = this.options.luaTarget || LuaTarget.LuaJIT; 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); - this.luaTarget = this.options.luaTarget || LuaTarget.LuaJIT; - this.setupState(); } - protected setupState(): void { + protected genVarCounter!: number; + protected luaLibFeatureSet!: Set; + + protected scopeStack!: Scope[]; + protected classStack!: ts.ClassLikeDeclaration[]; + + protected symbolInfo!: Map; + protected symbolIds!: Map; + protected genSymbolIdCounter!: number; + + private setupState(): void { this.genVarCounter = 0; - this.currentSourceFile = undefined; - this.isModule = false; + this.luaLibFeatureSet = new Set(); + this.scopeStack = []; this.classStack = []; - this.luaLibFeatureSet = new Set(); - this.symbolIds = new Map(); + this.symbolInfo = new Map(); + this.symbolIds = new Map(); this.genSymbolIdCounter = 1; } - public transformSourceFile(node: ts.SourceFile): [tstl.Block, Set] { - this.setupState(); + protected currentSourceFile!: ts.SourceFile; + protected isModule!: boolean; + protected resolver!: EmitResolver; - this.currentSourceFile = node; + /** @internal */ + public transform(sourceFile: ts.SourceFile): [tstl.Block, Set] { + this.setupState(); + this.currentSourceFile = sourceFile; + this.isModule = tsHelper.isFileModule(sourceFile); // Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers. // It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker` // and would try to re-analyze the file, which would fail because of replaced nodes. - const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node; + const originalSourceFile = ts.getParseTreeNode(sourceFile, ts.isSourceFile) || sourceFile; this.resolver = this.checker.getEmitResolver(originalSourceFile); - let statements: tstl.Statement[] = []; - if (node.flags & ts.NodeFlags.JsonFile) { - this.isModule = false; + return [this.transformSourceFile(sourceFile), this.luaLibFeatureSet]; + } - const statement = node.statements[0]; + public transformSourceFile(sourceFile: ts.SourceFile): tstl.Block { + let statements: tstl.Statement[] = []; + if (sourceFile.flags & ts.NodeFlags.JsonFile) { + const statement = sourceFile.statements[0]; if (!statement || !ts.isExpressionStatement(statement)) { - throw TSTLErrors.InvalidJsonFileContent(node); + throw TSTLErrors.InvalidJsonFileContent(sourceFile); } statements.push(tstl.createReturnStatement([this.transformExpression(statement.expression)])); } else { this.pushScope(ScopeType.File); - - this.isModule = tsHelper.isFileModule(node); - statements = this.performHoisting(this.transformStatements(node.statements)); - + statements = this.performHoisting(this.transformStatements(sourceFile.statements)); this.popScope(); if (this.isModule) { @@ -149,7 +145,7 @@ export class LuaTransformer { } } - return [tstl.createBlock(statements, node), this.luaLibFeatureSet]; + return tstl.createBlock(statements, sourceFile); } public transformStatement(node: ts.Statement): StatementVisitResult { @@ -484,10 +480,6 @@ export class LuaTransformer { expression = this.transformExternalModuleReference(declaration.moduleReference); } else { - if (this.currentSourceFile === undefined) { - throw TSTLErrors.MissingSourceFile(); - } - const shouldEmit = this.resolver.isReferencedAliasDeclaration(declaration) || (!ts.isExternalModule(this.currentSourceFile) && @@ -4974,14 +4966,11 @@ export class LuaTransformer { } protected isSymbolExported(symbol: ts.Symbol): boolean { - if (tsHelper.getExportedSymbolDeclaration(symbol) !== undefined) { - return true; - } else if (this.currentSourceFile) { + return ( + tsHelper.getExportedSymbolDeclaration(symbol) !== undefined || // Symbol may have been exported separately (e.g. 'const foo = "bar"; export { foo }') - return this.isSymbolExportedFromScope(symbol, this.currentSourceFile); - } else { - return false; - } + this.isSymbolExportedFromScope(symbol, this.currentSourceFile) + ); } protected isSymbolExportedFromScope(symbol: ts.Symbol, scope: ts.SourceFile | ts.ModuleDeclaration): boolean { @@ -5120,10 +5109,6 @@ export class LuaTransformer { return path.resolve(this.options.baseUrl, relativePath); } - if (this.currentSourceFile === undefined) { - throw TSTLErrors.MissingSourceFile(); - } - return path.resolve(path.dirname(this.currentSourceFile.fileName), relativePath); } @@ -5194,7 +5179,8 @@ export class LuaTransformer { let isFirstDeclaration = true; // var can have multiple declarations for the same variable :/ if (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.parent) { isLetOrConst = (tsOriginal.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0; - isFirstDeclaration = isLetOrConst || tsHelper.isFirstDeclaration(tsOriginal, this.checker); + isFirstDeclaration = + isLetOrConst || tsHelper.isFirstDeclaration(tsOriginal, this.checker, this.currentSourceFile); } if ((this.isModule || this.currentNamespace || insideFunction || isLetOrConst) && isFirstDeclaration) { // local @@ -5560,10 +5546,6 @@ 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); diff --git a/src/TSHelper.ts b/src/TSHelper.ts index b587f99e9..b50858be5 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -731,14 +731,12 @@ export function isFalsible(type: ts.Type, strictNullChecks: boolean): boolean { return false; } -export function getFirstDeclaration(symbol: ts.Symbol, sourceFile?: ts.SourceFile): ts.Declaration | undefined { +export function getFirstDeclaration(symbol: ts.Symbol, sourceFile: ts.SourceFile): ts.Declaration | undefined { let declarations = symbol.getDeclarations(); if (!declarations) { return undefined; } - if (sourceFile) { - declarations = declarations.filter(d => findFirstNodeAbove(d, ts.isSourceFile) === sourceFile); - } + declarations = declarations.filter(d => findFirstNodeAbove(d, ts.isSourceFile) === sourceFile); return declarations.length > 0 ? declarations.reduce((p, c) => (p.pos < c.pos ? p : c)) : undefined; } @@ -751,12 +749,17 @@ export function getRawLiteral(node: ts.LiteralLikeNode): string { return text; } -export function isFirstDeclaration(node: ts.VariableDeclaration, checker: ts.TypeChecker): boolean { +export function isFirstDeclaration( + node: ts.VariableDeclaration, + checker: ts.TypeChecker, + sourceFile: ts.SourceFile +): boolean { const symbol = checker.getSymbolAtLocation(node.name); if (!symbol) { return false; } - const firstDeclaration = getFirstDeclaration(symbol); + + const firstDeclaration = getFirstDeclaration(symbol, sourceFile); return firstDeclaration === node; } diff --git a/src/TSTLErrors.ts b/src/TSTLErrors.ts index a00d6faeb..cc351f2b7 100644 --- a/src/TSTLErrors.ts +++ b/src/TSTLErrors.ts @@ -90,8 +90,6 @@ export const MissingFunctionName = (declaration: ts.FunctionLikeDeclaration) => export const MissingMetaExtension = (node: ts.Node) => new TranspileError(`@metaExtension requires the extension of the metatable class.`, node); -export const MissingSourceFile = () => new Error("Expected transformer.sourceFile to be set, but it isn't."); - export const UndefinedFunctionDefinition = (functionSymbolId: number) => new Error(`Function definition for function symbol ${functionSymbolId} is undefined.`); diff --git a/src/Transpile.ts b/src/Transpile.ts index b0d0d4c0a..b0a83d3e7 100644 --- a/src/Transpile.ts +++ b/src/Transpile.ts @@ -82,7 +82,7 @@ export function transpile({ const processSourceFile = (sourceFile: ts.SourceFile) => { try { - const [luaAst, lualibFeatureSet] = transformer.transformSourceFile(sourceFile); + const [luaAst, lualibFeatureSet] = transformer.transform(sourceFile); if (!options.noEmit && !options.emitDeclarationOnly) { const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName); updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap });