From 0c81fc4811c083f46d48b586f4860892baab3a66 Mon Sep 17 00:00:00 2001 From: ZhongNing Date: Wed, 2 Jul 2025 17:31:15 +0800 Subject: [PATCH] fix arkts-method-inherit-rule Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICJC5K Test scenarios: fix bug Signed-off-by: ZhongNing --- ets2panda/linter/src/lib/TypeScriptLinter.ts | 168 +++++++++++++++--- .../explicit_function_type.ets.arkts2.json | 10 ++ .../explicit_function_type.ets.autofix.json | 10 ++ .../explicit_function_type.ets.migrate.json | 10 ++ .../linter/test/main/method_inheritance2.ets | 116 ++++++++++++ .../main/method_inheritance2.ets.args.json | 19 ++ .../main/method_inheritance2.ets.arkts2.json | 148 +++++++++++++++ .../test/main/method_inheritance2.ets.json | 17 ++ 8 files changed, 472 insertions(+), 26 deletions(-) create mode 100755 ets2panda/linter/test/main/method_inheritance2.ets create mode 100755 ets2panda/linter/test/main/method_inheritance2.ets.args.json create mode 100755 ets2panda/linter/test/main/method_inheritance2.ets.arkts2.json create mode 100755 ets2panda/linter/test/main/method_inheritance2.ets.json diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 17606498287..39f5492facf 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -3452,25 +3452,24 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { return; } - const classType = this.tsTypeChecker.getTypeAtLocation(classDecl); - const allBaseTypes = this.getAllBaseTypes(classType, classDecl); + const isStatic = node.modifiers?.some(mod => { + return mod.kind === ts.SyntaxKind.StaticKeyword; + }) || false; + const classType: ts.Type | undefined = this.getClassType(classDecl, isStatic); + const allBaseTypes = classType && this.getAllBaseTypes(classType, classDecl, isStatic); if (!allBaseTypes || allBaseTypes.length === 0) { return; } - const methodName = node.name.text; - for (const baseType of allBaseTypes) { const baseMethod = baseType.getProperty(methodName); if (!baseMethod) { continue; } - + const baseMethodDecl = baseMethod.declarations?.find((d) => { - return ( - (ts.isMethodDeclaration(d) || ts.isMethodSignature(d)) && - this.tsTypeChecker.getTypeAtLocation(d.parent) === baseType - ); + return (ts.isMethodDeclaration(d) || ts.isMethodSignature(d)) && + this.isDeclarationInType(d, baseType, isStatic); }) as ts.MethodDeclaration | ts.MethodSignature; if (!baseMethodDecl) { @@ -3478,17 +3477,104 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } this.checkMethodParameters(node, baseMethodDecl); - this.checkMethodReturnType(node, baseMethodDecl); break; } } - private getAllBaseTypes(type: ts.Type, classDecl: ts.ClassDeclaration): ts.Type[] | undefined { + private getClassType( classDecl: ts.ClassDeclaration, isStatic?: boolean): ts.Type | undefined { + let classType: ts.Type; + + if (isStatic) { + const classConstructorSymbol = classDecl.symbol; + if (!classConstructorSymbol) { + return undefined; + } + classType = this.tsTypeChecker.getTypeOfSymbolAtLocation(classConstructorSymbol, classDecl); + } else { + classType = this.tsTypeChecker.getTypeAtLocation(classDecl); + } + return classType; + } + + private isDeclarationInType(decl: ts.Declaration, type: ts.Type, isStatic: boolean = false): boolean { + const declParent = decl.parent; + if (!declParent) { + return false; + } + + let declParentType: ts.Type; + if (isStatic && ts.isClassDeclaration(declParent)) { + if (!declParent.symbol) { + return false; + } + declParentType = this.tsTypeChecker.getTypeOfSymbolAtLocation(declParent.symbol, declParent); + } else { + declParentType = this.tsTypeChecker.getTypeAtLocation(declParent); + } + + return this.isSameType(declParentType, type); + } + + private isSameType(type1: ts.Type, type2: ts.Type): boolean { + if (type1.flags & ts.TypeFlags.Any || type2.flags & ts.TypeFlags.Any) { + return true; + } + + if (type1.flags & ts.TypeFlags.TypeParameter && type2.flags & ts.TypeFlags.TypeParameter) { + const constraint1 = (type1 as ts.TypeParameter).getConstraint(); + const constraint2 = (type2 as ts.TypeParameter).getConstraint(); + if (constraint1 && constraint2) { + return this.isSameType(constraint1, constraint2); + } + } + + if (!type1.symbol || type1.symbol !== type2.symbol) { + return false; + } + const type1Args = (type1 as ts.TypeReference).typeArguments; + const type2Args = (type2 as ts.TypeReference).typeArguments; + + if (type1Args && type2Args && type1Args.length === type2Args.length) { + for (let i = 0; i < type1Args.length; i++) { + if (!this.isTypeAssignable(type2Args[i], type1Args[i])) { + return false; + } + } + return true; + } + + return this.tsTypeChecker.typeToString(type1) === this.tsTypeChecker.typeToString(type2); + } + + private getAllBaseTypes(type: ts.Type, classDecl: ts.ClassDeclaration, isStatic?: boolean): ts.Type[] | undefined { + if (isStatic) { + const baseTypes: ts.Type[] = []; + if (!classDecl.heritageClauses) { + return baseTypes; + } + for (const clause of classDecl.heritageClauses) { + if (clause.token !== ts.SyntaxKind.ExtendsKeyword) { + continue; + } + for (const typeNode of clause.types) { + const baseType = this.tsTypeChecker.getTypeAtLocation(typeNode); + baseTypes.push(baseType); + } + } + + return baseTypes; + } + const baseClasses = type.getBaseTypes() || []; + const resolvedBaseClasses = baseClasses.flatMap((baseType) => { + const symbol = baseType.getSymbol(); + return symbol ? [this.tsTypeChecker.getDeclaredTypeOfSymbol(symbol)] : [baseType]; + }); + if (!classDecl.heritageClauses) { - return baseClasses; + return resolvedBaseClasses; } const interfaces: ts.Type[] = []; for (const clause of classDecl.heritageClauses) { @@ -3498,13 +3584,17 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { for (const typeNode of clause.types) { const interfaceType = this.tsTypeChecker.getTypeAtLocation(typeNode); interfaces.push(interfaceType); - const parentInterfaces = interfaceType.getBaseTypes(); - if (parentInterfaces) { - interfaces.push(...parentInterfaces); - } + + const baseInterfaces = interfaceType.getBaseTypes() || []; + baseInterfaces.forEach((baseInterface) => { + const symbol = baseInterface.getSymbol(); + if (symbol) { + interfaces.push(this.tsTypeChecker.getDeclaredTypeOfSymbol(symbol)); + } + }); } } - return [...baseClasses, ...interfaces]; + return [...resolvedBaseClasses, ...interfaces]; } /** @@ -3577,33 +3667,59 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { return 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. - */ private isTypeSameOrWider(baseType: ts.Type, derivedType: ts.Type): boolean { + if (derivedType.flags & ts.TypeFlags.Any) { + return true; + } + + if (baseType.symbol === derivedType.symbol && baseType.symbol) { + const baseArgs = (baseType as ts.TypeReference).typeArguments; + const derivedArgs = (derivedType as ts.TypeReference).typeArguments; + + if (!baseArgs || !derivedArgs || baseArgs.length !== derivedArgs.length) { + return false; + } + for (let i = 0; i < baseArgs.length; i++) { + if (!this.isTypeAssignable(baseArgs[i], derivedArgs[i])) { + return false; + } + } + return true; + } + const baseTypeSet = new Set(this.flattenUnionTypes(baseType)); const derivedTypeSet = new Set(this.flattenUnionTypes(derivedType)); - // Check if every type in baseType is also present in derivedType for (const typeStr of baseTypeSet) { if (!derivedTypeSet.has(typeStr)) { return false; } } - return true; } - // Checks structural assignability between two types. private isTypeAssignable(fromType: ts.Type, toType: ts.Type): boolean { - if (this.isDerivedTypeAssignable(fromType, toType)) { + if (fromType.flags & ts.TypeFlags.Any) { return true; } + + if (fromType.symbol === toType.symbol && fromType.symbol) { + const fromArgs = (fromType as ts.TypeReference).typeArguments; + const toArgs = (toType as ts.TypeReference).typeArguments; + + if (fromArgs && toArgs && fromArgs.length === toArgs.length) { + for (let i = 0; i < fromArgs.length; i++) { + if (!this.isTypeAssignable(fromArgs[i], toArgs[i])) { + return false; + } + } + return true; + } + } + const fromTypes = this.flattenUnionTypes(fromType); const toTypes = new Set(this.flattenUnionTypes(toType)); - // All types in `fromTypes` should exist in `toTypes` for assignability. return fromTypes.every((typeStr) => { return toTypes.has(typeStr); }); diff --git a/ets2panda/linter/test/main/explicit_function_type.ets.arkts2.json b/ets2panda/linter/test/main/explicit_function_type.ets.arkts2.json index 34273528033..8e4efd0bd0b 100755 --- a/ets2panda/linter/test/main/explicit_function_type.ets.arkts2.json +++ b/ets2panda/linter/test/main/explicit_function_type.ets.arkts2.json @@ -94,6 +94,16 @@ "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", "severity": "ERROR" }, + { + "line": 96, + "column": 17, + "endLine": 96, + "endColumn": 29, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, { "line": 118, "column": 16, diff --git a/ets2panda/linter/test/main/explicit_function_type.ets.autofix.json b/ets2panda/linter/test/main/explicit_function_type.ets.autofix.json index 9512b29aac9..bd2ee0408af 100644 --- a/ets2panda/linter/test/main/explicit_function_type.ets.autofix.json +++ b/ets2panda/linter/test/main/explicit_function_type.ets.autofix.json @@ -160,6 +160,16 @@ "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", "severity": "ERROR" }, + { + "line": 96, + "column": 17, + "endLine": 96, + "endColumn": 29, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, { "line": 118, "column": 16, diff --git a/ets2panda/linter/test/main/explicit_function_type.ets.migrate.json b/ets2panda/linter/test/main/explicit_function_type.ets.migrate.json index b4e8db486ee..9d4733bcfb0 100644 --- a/ets2panda/linter/test/main/explicit_function_type.ets.migrate.json +++ b/ets2panda/linter/test/main/explicit_function_type.ets.migrate.json @@ -34,6 +34,16 @@ "rule": "Classes cannot be used as objects (arkts-no-classes-as-obj)", "severity": "ERROR" }, + { + "line": 96, + "column": 17, + "endLine": 96, + "endColumn": 29, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, { "line": 118, "column": 16, diff --git a/ets2panda/linter/test/main/method_inheritance2.ets b/ets2panda/linter/test/main/method_inheritance2.ets new file mode 100755 index 00000000000..2591755adc6 --- /dev/null +++ b/ets2panda/linter/test/main/method_inheritance2.ets @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class A { + a: number = 0 +} +class B { + a: number = 0 +} +class C { + a: number = 0 +} + +class D extends A{ + a: number = 0; + b: number = 1; +} + + +// T +class Base3 { + foo(obj: A | B): void { + console.log("base") + } + foo2(obj: A | B): void { + console.log("base") + } + foo3(obj: A | B | C): void { + console.log("base") + } +} +// T extends +class Derived3 extends Base3 { + foo(obj: A): void { // (arkts-method-inherit-rule) error + console.log("Derived:" + obj.a) + } + foo2(): void { // (arkts-method-inherit-rule) error + console.log("Derived:") + } + foo3(obj: A | B): void { //(arkts-method-inherit-rule) error + console.log("Derived:") + } +} + +// T interface +interface BaseI2 { + foo(obj: A | B):void; + foo2(obj: A ): void; + foo3(obj: A | B | C): void; +} + +// T implements +class DerivedI2 implements BaseI2 { + foo(obj: A): void { + console.log("Drived"); // (arkts-method-inherit-rule) error + } + foo2(): void { + console.log("Drived"); // (arkts-method-inherit-rule) error + } + foo3(obj: A | B): void { + console.log("Drived"); // (arkts-method-inherit-rule) error + } +} + +class Base5 { + public foo(): A|B { + console.log("base") + return new A(); + } + static foo2(){ + console.log("base") + // return new A(); + } + async foo3(): Promise { + console.log("base") + return new A(); + } + static foo4():void{ + console.log("base") + // return new A(); + } +} + +// extends +class Derived5 extends Base5 { + public foo(): A|B|C{ // (arkts-method-inherit-rule) + console.log("Derived:") + return new A(); + } + + static foo2(): A{ // (arkts-method-inherit-rule) error + console.log("Derived:") + return new A(); + } + + async foo3(): Promise { // (arkts-method-inherit-rule) + console.log("Derived:") + return new A(); + } + static foo4(): A{ // (arkts-method-inherit-rule) error + console.log("Derived:") + return new A(); + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/method_inheritance2.ets.args.json b/ets2panda/linter/test/main/method_inheritance2.ets.args.json new file mode 100755 index 00000000000..d8d3390ad9b --- /dev/null +++ b/ets2panda/linter/test/main/method_inheritance2.ets.args.json @@ -0,0 +1,19 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "mode": { + "arkts2": "" + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/method_inheritance2.ets.arkts2.json b/ets2panda/linter/test/main/method_inheritance2.ets.arkts2.json new file mode 100755 index 00000000000..30c37afc53d --- /dev/null +++ b/ets2panda/linter/test/main/method_inheritance2.ets.arkts2.json @@ -0,0 +1,148 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [ + { + "line": 17, + "column": 15, + "endLine": 17, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 20, + "column": 15, + "endLine": 20, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 23, + "column": 15, + "endLine": 23, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 27, + "column": 15, + "endLine": 27, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 28, + "column": 15, + "endLine": 28, + "endColumn": 16, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 46, + "column": 10, + "endLine": 46, + "endColumn": 16, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 49, + "column": 3, + "endLine": 49, + "endColumn": 7, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 52, + "column": 11, + "endLine": 52, + "endColumn": 21, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 66, + "column": 10, + "endLine": 66, + "endColumn": 16, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 69, + "column": 3, + "endLine": 69, + "endColumn": 7, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 72, + "column": 11, + "endLine": 72, + "endColumn": 21, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 98, + "column": 17, + "endLine": 98, + "endColumn": 22, + "problem": "MethodInheritRule", + "suggest": "", + "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", + "severity": "ERROR" + }, + { + "line": 108, + "column": 17, + "endLine": 108, + "endColumn": 29, + "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 diff --git a/ets2panda/linter/test/main/method_inheritance2.ets.json b/ets2panda/linter/test/main/method_inheritance2.ets.json new file mode 100755 index 00000000000..ca88f857e96 --- /dev/null +++ b/ets2panda/linter/test/main/method_inheritance2.ets.json @@ -0,0 +1,17 @@ +{ + "copyright": [ + "Copyright (c) 2025 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "result": [] +} \ No newline at end of file -- Gitee