diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1e0686e41..5bd26294b 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -3583,6 +3583,9 @@ export class LuaTransformer { properties.push(tstl.createTableFieldExpression(expression, name, element)); } else if (ts.isShorthandPropertyAssignment(element)) { const valueSymbol = this.checker.getShorthandAssignmentValueSymbol(element); + if (valueSymbol) { + this.trackSymbolReference(valueSymbol, element.name); + } const identifier = this.createShorthandIdentifier(valueSymbol, element.name); properties.push(tstl.createTableFieldExpression(identifier, name, element)); } else if (ts.isMethodDeclaration(element)) { @@ -5483,47 +5486,50 @@ export class LuaTransformer { return "____" + tsHelper.fixInvalidLuaIdentifier(name); } - protected getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId | undefined { - const symbol = this.checker.getSymbolAtLocation(identifier); - let symbolId: tstl.SymbolId | undefined; - if (symbol) { - // Track first time symbols are seen - if (!this.symbolIds.has(symbol)) { - symbolId = this.genSymbolIdCounter++; + protected trackSymbolReference(symbol: ts.Symbol, identifier: ts.Identifier): tstl.SymbolId | undefined { + // Track first time symbols are seen + let symbolId = this.symbolIds.get(symbol); + if (!symbolId) { + symbolId = this.genSymbolIdCounter++; - const symbolInfo: SymbolInfo = { symbol, firstSeenAtPos: identifier.pos }; - this.symbolIds.set(symbol, symbolId); - this.symbolInfo.set(symbolId, symbolInfo); - } else { - symbolId = this.symbolIds.get(symbol); - } + const symbolInfo: SymbolInfo = { symbol, firstSeenAtPos: identifier.pos }; + this.symbolIds.set(symbol, symbolId); + this.symbolInfo.set(symbolId, symbolInfo); + } - if (this.options.noHoisting) { - // Check for reference-before-declaration - const declaration = tsHelper.getFirstDeclaration(symbol, this.currentSourceFile); - if (declaration && identifier.pos < declaration.pos) { - throw TSTLErrors.ReferencedBeforeDeclaration(identifier); - } + if (this.options.noHoisting) { + // Check for reference-before-declaration + const declaration = tsHelper.getFirstDeclaration(symbol, this.currentSourceFile); + if (declaration && identifier.pos < declaration.pos) { + throw TSTLErrors.ReferencedBeforeDeclaration(identifier); } + } - if (symbolId !== undefined) { - //Mark symbol as seen in all current scopes - for (const scope of this.scopeStack) { - if (!scope.referencedSymbols) { - scope.referencedSymbols = new Map(); - } - let references = scope.referencedSymbols.get(symbolId); - if (!references) { - references = []; - scope.referencedSymbols.set(symbolId, references); - } - references.push(identifier); - } + //Mark symbol as seen in all current scopes + for (const scope of this.scopeStack) { + if (!scope.referencedSymbols) { + scope.referencedSymbols = new Map(); + } + let references = scope.referencedSymbols.get(symbolId); + if (!references) { + references = []; + scope.referencedSymbols.set(symbolId, references); } + references.push(identifier); } + return symbolId; } + protected getIdentifierSymbolId(identifier: ts.Identifier): tstl.SymbolId | undefined { + const symbol = this.checker.getSymbolAtLocation(identifier); + if (symbol) { + return this.trackSymbolReference(symbol, identifier); + } else { + return undefined; + } + } + protected findScope(scopeTypes: ScopeType): Scope | undefined { return this.scopeStack .slice() diff --git a/test/unit/functions.spec.ts b/test/unit/functions.spec.ts index 455b6d70d..92e853cbe 100644 --- a/test/unit/functions.spec.ts +++ b/test/unit/functions.spec.ts @@ -557,6 +557,18 @@ test.each([{}, { noHoisting: true }])("Function rest parameter (unreferenced)", expect(util.transpileAndExecute(code, compilerOptions)).toBe("foobar"); }); +test.each([{}, { noHoisting: true }])("Function rest parameter (referenced in property shorthand)", compilerOptions => { + const code = ` + function foo(a: unknown, ...b: string[]) { + const c = { b }; + return c.b.join(""); + } + return foo("A", "B", "C", "D"); + `; + + expect(util.transpileAndExecute(code, compilerOptions)).toBe("BCD"); +}); + test.each([{}, { noHoisting: true }])("@vararg", compilerOptions => { const code = ` /** @vararg */ type LuaVarArg = A & { __luaVarArg?: never }; diff --git a/test/unit/hoisting.spec.ts b/test/unit/hoisting.spec.ts index 1d91e3684..c5ad0a1c9 100644 --- a/test/unit/hoisting.spec.ts +++ b/test/unit/hoisting.spec.ts @@ -235,6 +235,7 @@ test.each([ }, { code: `function makeFoo() { return new Foo(); } class Foo {}`, identifier: "Foo" }, { code: `function bar() { return E.A; } enum E { A = "foo" }`, identifier: "E" }, + { code: `function setBar() { const bar = { foo }; } let foo = "foo";`, identifier: "foo" }, ])("No Hoisting (%p)", ({ code, identifier }) => { expect(() => util.transpileString(code, { noHoisting: true })).toThrowExactError( TSTLErrors.ReferencedBeforeDeclaration(ts.createIdentifier(identifier)) @@ -292,3 +293,13 @@ test("Import hoisted before function", () => { const code = "return bar;"; expect(util.transpileAndExecute(code, undefined, luaHeader, tsHeader)).toBe("foobar"); }); + +test("Hoisting Shorthand Property", () => { + const code = ` + function foo() { + return { bar }.bar; + } + let bar = "foobar"; + return foo();`; + expect(util.transpileAndExecute(code)).toBe("foobar"); +});