diff --git a/src/TSHelper.ts b/src/TSHelper.ts index c3eac9460..aa87170e5 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -80,6 +80,22 @@ export class TSHelper { || (ts.isBinaryExpression(node.parent) && ts.isArrayLiteralExpression(node.parent.left))); } + // iterate over a type and its bases until the callback returns true. + public static forTypeOrAnySupertype(type: ts.Type, callback: (type: ts.Type) => boolean): boolean { + if (callback(type)) { + return true; + } + const baseTypes = type.getBaseTypes(); + if (baseTypes) { + for (const baseType of baseTypes) { + if (this.forTypeOrAnySupertype(baseType, callback)) { + return true; + } + } + } + return false; + } + public static isStringType(type: ts.Type): boolean { return (type.flags & ts.TypeFlags.String) !== 0 || (type.flags & ts.TypeFlags.StringLike) !== 0 @@ -99,13 +115,7 @@ export class TSHelper { } public static isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean { - const baseTypes = type.getBaseTypes(); - if (baseTypes) { - for (const baseType of baseTypes) { - if (this.isExplicitArrayType(baseType, checker)) { return true; } - } - } - return this.isExplicitArrayType(type, checker); + return this.forTypeOrAnySupertype(type, t => this.isExplicitArrayType(t, checker)); } public static isTupleReturnCall(node: ts.Node, checker: ts.TypeChecker): boolean { @@ -182,28 +192,34 @@ export class TSHelper { return null; } + public static hasExplicitGetAccessor(type: ts.Type, name: ts.__String): boolean { + if (type && type.symbol && type.symbol.members) { + const field = type.symbol.members.get(name); + return field && (field.flags & ts.SymbolFlags.GetAccessor) !== 0; + } + } + public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean { if (ts.isPropertyAccessExpression(node)) { const name = node.name.escapedText; const type = checker.getTypeAtLocation(node.expression); - - if (type && type.symbol && type.symbol.members) { - const field = type.symbol.members.get(name); - return field && (field.flags & ts.SymbolFlags.GetAccessor) !== 0; - } + return this.forTypeOrAnySupertype(type, t => this.hasExplicitGetAccessor(t, name)); } return false; } + public static hasExplicitSetAccessor(type: ts.Type, name: ts.__String): boolean { + if (type && type.symbol && type.symbol.members) { + const field = type.symbol.members.get(name); + return field && (field.flags & ts.SymbolFlags.SetAccessor) !== 0; + } + } + public static hasSetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean { if (ts.isPropertyAccessExpression(node)) { const name = node.name.escapedText; const type = checker.getTypeAtLocation(node.expression); - - if (type && type.symbol && type.symbol.members) { - const field = type.symbol.members.get(name); - return field && (field.flags & ts.SymbolFlags.SetAccessor) !== 0; - } + return this.forTypeOrAnySupertype(type, t => this.hasExplicitSetAccessor(t, name)); } return false; } diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 7fbea1209..868b9bee7 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -254,6 +254,37 @@ export class ExpressionTests { Expect(result).toBe(expected); } + @TestCase("inst.baseField", 7) + @TestCase("inst.field", 6) + @TestCase("inst.superField", 5) + @Test("Inherited accessors") + public inheritedAccessors(expression: string, expected: any): void { + const source = `class MyBaseClass {` + + ` public _baseField: number;` + + ` public get baseField(): number { return this._baseField + 6; }` + + ` public set baseField(v: number) { this._baseField = v; }` + + `}` + + `class MyClass extends MyBaseClass {` + + ` public _field: number;` + + ` public get field(): number { return this._field + 4; }` + + ` public set field(v: number) { this._field = v; }` + + `}` + + `class MySuperClass extends MyClass {` + + ` public _superField: number;` + + ` public get superField(): number { return this._superField + 2; }` + + ` public set superField(v: number) { this._superField = v; }` + + `}` + + `var inst = new MySuperClass();` + + `inst.baseField = 1;` + + `inst.field = 2;` + + `inst.superField = 3;` + + `return ${expression};`; + + const lua = util.transpileString(source); + const result = util.executeLua(lua); + Expect(result).toBe(expected); + } + @TestCase("i++", 10) @TestCase("i--", 10) @TestCase("++i", 11)