diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index e2ebe7badfcd2ba39f48d64837f5696f0eb30b79..5db2ab92553947b25da40bc80965a70f5c8d8d8d 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -3586,42 +3586,73 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } const classType = this.tsTypeChecker.getTypeAtLocation(classDecl); - const baseTypes = classType.getBaseTypes(); - if (!baseTypes || baseTypes.length === 0) { + const allBaseTypes = this.getAllBaseTypes(classType, classDecl); + if (!allBaseTypes || allBaseTypes.length === 0) { return; } const methodName = node.name.text; - for (const baseType of baseTypes) { + for (const baseType of allBaseTypes) { const baseMethod = baseType.getProperty(methodName); if (!baseMethod) { continue; } - const baseMethodDecl = baseMethod.declarations?.find(ts.isMethodDeclaration); + const baseMethodDecl = baseMethod.declarations?.find((d) => { + return ts.isMethodDeclaration(d) || ts.isMethodSignature(d); + }) as ts.MethodDeclaration | ts.MethodSignature | undefined; + if (!baseMethodDecl) { continue; } - // Check parameter compatibility this.checkMethodParameters(node, baseMethodDecl); - // Check return type compatibility this.checkMethodReturnType(node, baseMethodDecl); break; } } + private getAllBaseTypes(type: ts.Type, classDecl: ts.ClassDeclaration): ts.Type[] | undefined { + const baseClasses = type.getBaseTypes() || []; + if (!classDecl.heritageClauses) { + return baseClasses; + } + const interfaces: ts.Type[] = []; + for (const clause of classDecl.heritageClauses) { + if (clause.token !== ts.SyntaxKind.ImplementsKeyword) { + continue; + } + for (const typeNode of clause.types) { + const interfaceType = this.tsTypeChecker.getTypeAtLocation(typeNode); + interfaces.push(interfaceType); + const parentInterfaces = interfaceType.getBaseTypes(); + if (parentInterfaces) { + interfaces.push(...parentInterfaces); + } + } + } + return [...baseClasses, ...interfaces]; + } + /** - * Checks if child parameters accept at least as many types as parent parameters. - * (Child parameter type should be same or wider than parent.) + * Checks method parameter compatibility + * Derived parameter types must be same or wider than base (contravariance principle) */ - private checkMethodParameters(derivedMethod: ts.MethodDeclaration, baseMethod: ts.MethodDeclaration): void { + private checkMethodParameters( + derivedMethod: ts.MethodDeclaration, + baseMethod: ts.MethodDeclaration | ts.MethodSignature + ): void { const derivedParams = derivedMethod.parameters; const baseParams = baseMethod.parameters; + if (derivedParams.length !== baseParams.length) { + this.incrementCounters(derivedMethod.name, FaultID.MethodInheritRule); + return; + } + const paramCount = Math.min(derivedParams.length, baseParams.length); for (let i = 0; i < paramCount; i++) { @@ -3635,10 +3666,22 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } /** - * Checks return type covariance between base and derived methods. - * (Derived return type must be assignable to base return type.) + * Checks return type compatibility + * Derived return type must be same or narrower than base (covariance principle) */ - private checkMethodReturnType(derivedMethod: ts.MethodDeclaration, baseMethod: ts.MethodDeclaration): void { + private checkMethodReturnType( + derivedMethod: ts.MethodDeclaration, + baseMethod: ts.MethodDeclaration | ts.MethodSignature + ): void { + if ( + this.IsVoidTypeOnActualReturnType(baseMethod) && + derivedMethod.type && + !this.IsVoidTypeOnActualReturnType(derivedMethod) + ) { + this.incrementCounters(derivedMethod.type, FaultID.MethodInheritRule); + return; + } + if (!baseMethod.type || !derivedMethod.type) { return; } @@ -3651,6 +3694,19 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } } + private IsVoidTypeOnActualReturnType(method: ts.MethodDeclaration | ts.MethodSignature): boolean | undefined { + let type: ts.Type | undefined; + if (method.type) { + type = this.tsTypeChecker.getTypeAtLocation(method.type); + } else { + const signature = this.tsTypeChecker.getSignatureFromDeclaration(method); + if (signature) { + type = this.tsTypeChecker.getReturnTypeOfSignature(signature); + } + } + return type && TsUtils.isVoidType(type); + } + /** * Child type should include all types of parent type (be same or wider). * Returns true if every type in baseType is also included in derivedType. diff --git a/ets2panda/linter/test/main/method_inheritance.ets b/ets2panda/linter/test/main/method_inheritance.ets index ff1895fc91560a5662f08d3eb401caa34da364e0..c92fa1f0d9d509516fdbe3eabe31b71fee67282c 100644 --- a/ets2panda/linter/test/main/method_inheritance.ets +++ b/ets2panda/linter/test/main/method_inheritance.ets @@ -18,7 +18,7 @@ abstract class Y { } class X extends Y { - async getDataByName(name: string, albumUri: string): Promise { + async getDataByName(name: string, albumUri: string): Promise { // error 2 return; } } @@ -38,7 +38,7 @@ abstract class W { } class Q extends W { - async getDataByName(name: string | number, albumUri: string | number): Promise { + async getDataByName(name: string | number, albumUri: string | number): Promise {// error 1 return; }; } @@ -48,7 +48,7 @@ abstract class BaseClass3 { } class IncorrectWiderReturn extends BaseClass3 { - compute(value: string): string | number { + compute(value: string): string | number {// error 1 return value.length > 5 ? value : 0; } } @@ -58,7 +58,7 @@ abstract class BaseClass4 { } class IncorrectMultipleParamMismatch extends BaseClass4 { - setValues(x: string, y: boolean): void { + setValues(x: string, y: boolean): void {// error 2 console.log(x, y); } } @@ -68,7 +68,7 @@ abstract class BaseClass5 { } class IncorrectBothMismatch extends BaseClass5 { - transform(data: number): number | string { + transform(data: number): number | string {// error 2 return data > 10 ? data : "too small"; } } @@ -97,3 +97,122 @@ class CorrectBothWiderParamNarrowReturn extends BaseClass { } +class A1 { + a: number = 0 +} +class B1 { + a: number = 0 +} +class C { + a: number = 0 +} + +class Base { + foo(obj: A1 | B1): void { + console.log("base") + } + foo2(obj: A1 | B1): void { + console.log("base") + } + foo3(obj: A1 | B1 | C): void { + console.log("base") + } +} + +// extends +class Derived extends Base { + foo(obj: A1): void { // error 1 + console.log("Derived:" + obj.a) + } + foo2(): void { // error 1 + console.log("Derived:") + } + foo3(obj: A1 | B1): void { // error 1 + console.log("Derived:") + } +} + +interface BaseI { + foo(obj: A1 | B1):void; + foo2(obj: A1): void; + foo3(obj: A1 | B1 | C): void; +} + +// implements +class Derived2 implements BaseI { + foo(obj: A1): void { // error 1 + console.log("Drived"); + } + foo2(): void { // error 1 + console.log("Drived"); + } + foo3(obj: A1 | B1): void { // error 1 + console.log("Drived"); + } +} + +class Base2 { + foo(): A1|B1 { + console.log("base") + return new A1(); + } + foo2(){ + console.log("base") + // return new A(); + } + foo3(): A1 { + console.log("base") + return new A1(); + } + foo4():void{ + console.log("base") + // return new A(); + } +} + +//extends +class Derived3 extends Base2 { + foo(): A1|B1|C{ // error 1 + console.log("Derived:") + return new A1(); + } + + foo2(): A1{ // error 1 + console.log("Derived:") + return new A1(); + } + + foo3(): A1|B1 { // error 1 + console.log("Derived:") + return new A1(); + } + foo4(): A1{ // error 1 + console.log("Derived:") + return new A1(); + } +} + + +interface Base3 { + foo(): A1|B1 ; + foo2(): void; + foo3(): A1; +} + +// implements +class Derived4 implements Base3 { + foo(): A1|B1|C{ // error 1 + console.log("Derived:") + return new A1(); + } + + foo2(): A1{ // error 1 + console.log("Derived:") + return new A1(); + } + + foo3(): A1|B1 { // error 1 + console.log("Derived:") + return new A1(); + } +} diff --git a/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json b/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json index 717bb1ec00ad2857434448b626a61f51c958acfd..a25dd05807ca2fdea1668cd8bdd4a50061968881 100644 --- a/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json +++ b/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json @@ -133,6 +133,166 @@ "suggest": "", "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", "severity": "ERROR" + }, + { + "line": 101, + "column": 15, + "endLine": 101, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 104, + "column": 15, + "endLine": 104, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 107, + "column": 15, + "endLine": 107, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 124, + "column": 7, + "endLine": 124, + "endColumn": 14, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 127, + "column": 3, + "endLine": 127, + "endColumn": 7, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 130, + "column": 8, + "endLine": 130, + "endColumn": 20, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 143, + "column": 7, + "endLine": 143, + "endColumn": 14, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 146, + "column": 3, + "endLine": 146, + "endColumn": 7, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 149, + "column": 8, + "endLine": 149, + "endColumn": 20, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 175, + "column": 10, + "endLine": 175, + "endColumn": 17, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 180, + "column": 11, + "endLine": 180, + "endColumn": 13, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 185, + "column": 11, + "endLine": 185, + "endColumn": 16, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 189, + "column": 11, + "endLine": 189, + "endColumn": 13, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 204, + "column": 10, + "endLine": 204, + "endColumn": 17, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 209, + "column": 11, + "endLine": 209, + "endColumn": 13, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 214, + "column": 11, + "endLine": 214, + "endColumn": 16, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" } ] } \ No newline at end of file