diff --git a/ets2panda/linter/rule-config.json b/ets2panda/linter/rule-config.json index 611a033828ee351a8d883852ab8fb3785bf19ed0..3fbe347bc36283fd0abecbc8e0ef8a4c6579d21d 100644 --- a/ets2panda/linter/rule-config.json +++ b/ets2panda/linter/rule-config.json @@ -58,6 +58,7 @@ "arkts-limited-stdlib-no-setTransferList", "arkts-builtin-object-getOwnPropertyNames", "arkts-no-class-omit-interface-optional-prop", + "arkts-class-no-signature-distinct-with-object-public-api", "arkts-no-sparse-array", "arkts-no-enum-prop-as-type", "arkts-no-ts-like-smart-type", diff --git a/ets2panda/linter/src/lib/CookBookMsg.ts b/ets2panda/linter/src/lib/CookBookMsg.ts index f0b8e41253982f18ac37463e1e3174e0224759f0..91e93ccd90054d8d6906187c883d2ba9d6185db7 100644 --- a/ets2panda/linter/src/lib/CookBookMsg.ts +++ b/ets2panda/linter/src/lib/CookBookMsg.ts @@ -257,6 +257,8 @@ cookBookTag[212] = 'The index expression must be zero or positive value.(arkts-a cookBookTag[213] = 'Class cannot have static codeblocks. (arkts-class-lazy-import)'; cookBookTag[214] = 'Objects have no constructor property in ArkTS1.2 (arkts-obj-no-constructor)'; cookBookTag[215] = 'Array bound not checked. (arkts-runtime-array-check)'; +cookBookTag[216] = + 'The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)'; cookBookTag[222] = 'Import for side-effect only is prohibited.(arkts-no-side-effect-import)'; cookBookTag[232] = 'Lazy import is not supported(arkts-no-lazy-import)'; cookBookTag[233] = 'Dynamic import is not supported(arkts-no-dynamic-import)'; diff --git a/ets2panda/linter/src/lib/FaultAttrs.ts b/ets2panda/linter/src/lib/FaultAttrs.ts index 8ace468f161fd5f32f2aac72700714920a6452b3..a1e9166e2af8c51e9658e2857b4236b27aa7a7d5 100644 --- a/ets2panda/linter/src/lib/FaultAttrs.ts +++ b/ets2panda/linter/src/lib/FaultAttrs.ts @@ -170,6 +170,7 @@ faultsAttrs[FaultID.IndexNegative] = new FaultAttributes(212); faultsAttrs[FaultID.NoStaticOnClass] = new FaultAttributes(213); faultsAttrs[FaultID.NoConstructorOnClass] = new FaultAttributes(214); faultsAttrs[FaultID.RuntimeArrayCheck] = new FaultAttributes(215); +faultsAttrs[FaultID.NoSignatureDistinctWithObjectPublicApi] = new FaultAttributes(216); faultsAttrs[FaultID.NoSideEffectImport] = new FaultAttributes(222); faultsAttrs[FaultID.ImportLazyIdentifier] = new FaultAttributes(232); faultsAttrs[FaultID.DynamicImport] = new FaultAttributes(233); diff --git a/ets2panda/linter/src/lib/FaultDesc.ts b/ets2panda/linter/src/lib/FaultDesc.ts index d812eab32339364d3f430dad380008fcffea5ec2..50a4e2e08c64e7b9ec08994a1852bcce04b8ccd0 100644 --- a/ets2panda/linter/src/lib/FaultDesc.ts +++ b/ets2panda/linter/src/lib/FaultDesc.ts @@ -71,6 +71,8 @@ faultDesc[FaultID.PropertyAccessByIndex] = 'property access by index'; faultDesc[FaultID.NoStaticOnClass] = 'No static blocks on classes'; faultDesc[FaultID.NoConstructorOnClass] = 'No constructor field on object'; faultDesc[FaultID.RuntimeArrayCheck] = 'Array bound not checked'; +faultDesc[FaultID.NoSignatureDistinctWithObjectPublicApi] = + 'Method’s signature in a class/interface must match the public interface of the object.'; faultDesc[FaultID.JsxElement] = 'JSX Elements'; faultDesc[FaultID.EnumMemberNonConstInit] = 'Enum members with non-constant initializer'; faultDesc[FaultID.ImplementsClass] = 'Class type mentioned in "implements" clause'; diff --git a/ets2panda/linter/src/lib/Problems.ts b/ets2panda/linter/src/lib/Problems.ts index dd700b4ed503bf045a326a84be4b9149e4628771..0257546343b873b21b8d0e4b4e7e9541ca2cda7a 100644 --- a/ets2panda/linter/src/lib/Problems.ts +++ b/ets2panda/linter/src/lib/Problems.ts @@ -71,6 +71,7 @@ export enum FaultID { NoStaticOnClass, NoConstructorOnClass, RuntimeArrayCheck, + NoSignatureDistinctWithObjectPublicApi, MethodReassignment, MultipleStaticBlocks, ThisType, diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 1e0dd75815109e868dd9b34384f35e41c0523c98..39d017b0a6e8c4e1dd647ab36ab4d27db346146c 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -76,7 +76,8 @@ import { LIMITED_STD_REFLECT_API, MODULE_IMPORTS, ARKTSUTILS_MODULES, - ARKTSUTILS_LOCKS_MEMBER + ARKTSUTILS_LOCKS_MEMBER, + OBJECT_PUBLIC_API_METHOD_SIGNATURES } from './utils/consts/LimitedStdAPI'; import { SupportedStdCallApiChecker } from './utils/functions/SupportedStdCallAPI'; import { identiferUseInValueContext } from './utils/functions/identiferUseInValueContext'; @@ -939,6 +940,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } this.countDeclarationsWithDuplicateName(interfaceNode.name, interfaceNode); this.handleLocalDeclarationOfClassAndIface(interfaceNode); + this.checkObjectPublicApiMethods(interfaceNode); } private handleTryStatement(node: ts.TryStatement): void { @@ -3317,6 +3319,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { this.handleSdkMethod(tsClassDecl); this.handleNotsLikeSmartType(tsClassDecl); this.handleLocalDeclarationOfClassAndIface(tsClassDecl); + this.checkObjectPublicApiMethods(tsClassDecl); } private static findFinalExpression(typeNode: ts.TypeNode): ts.Node { @@ -3821,6 +3824,31 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { this.handleNoDeprecatedApi(tsMethodDecl); } + private checkObjectPublicApiMethods(node: ts.ClassDeclaration | ts.InterfaceDeclaration): void { + if (!this.options.arkts2) { + return; + } + for (const member of node.members) { + if (!((ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && ts.isIdentifier(member.name))) { + continue; + } + const methodName = member.name.text; + const expectedSignature = OBJECT_PUBLIC_API_METHOD_SIGNATURES.get(methodName); + if (!expectedSignature) { + continue; + } + const methodType = this.tsTypeChecker.getTypeAtLocation(member); + const signature = TsUtils.getFunctionalTypeSignature(methodType); + if (!signature) { + continue; + } + const actualSignature = this.tsTypeChecker.signatureToString(signature); + if (actualSignature !== expectedSignature) { + this.incrementCounters(member, FaultID.NoSignatureDistinctWithObjectPublicApi); + } + } + } + private handleLimitedVoidFunction(node: ts.FunctionLikeDeclaration): void { const typeNode = node.type; if (!typeNode || !ts.isUnionTypeNode(typeNode)) { diff --git a/ets2panda/linter/src/lib/utils/consts/LimitedStdAPI.ts b/ets2panda/linter/src/lib/utils/consts/LimitedStdAPI.ts index 3149a493a1a8b608a3dafec14c6a44298cf12594..b7aa45b5f2e005be1e1015797a2e607c7e13f88d 100644 --- a/ets2panda/linter/src/lib/utils/consts/LimitedStdAPI.ts +++ b/ets2panda/linter/src/lib/utils/consts/LimitedStdAPI.ts @@ -162,3 +162,12 @@ export const MODULE_IMPORTS: Record = { export const ARKTSUTILS_MODULES = ['@arkts.utils', '@kit.ArkTS']; export const ARKTSUTILS_LOCKS_MEMBER = 'locks'; + +export const OBJECT_PUBLIC_API_METHOD_SIGNATURES = new Map([ + ['toString', '(): string'], + ['toLocaleString', '(): string'], + ['valueOf', '(): Object'], + ['hasOwnProperty', '(v: PropertyKey): boolean'], + ['isPrototypeOf', '(v: Object): boolean'], + ['propertyIsEnumerable', '(v: PropertyKey): boolean'] +]); diff --git a/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets new file mode 100644 index 0000000000000000000000000000000000000000..b02afc3dcc5cd3da3d98c26d6860dac141cc00f5 --- /dev/null +++ b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets @@ -0,0 +1,106 @@ +/* + * 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 A1 { + toString(): string { // valid + return "ok"; + } + + toLocaleString(): string { // valid + return "ok"; + } + + valueOf(): number { // error + return 789; + } + + hasOwnProperty(): boolean { // error + return false; + } + + isPrototypeOf(v: Object): boolean { // valid + return true; + } + + propertyIsEnumerable(v: PropertyKey): boolean { // valid + return false; + } +} + +class A2 { + customFunc(): void {} // valid + + toString(): number {return 123;} // error + + toLocaleString(): number { // error + return 456; + } + + valueOf(): Object { // valid + return {}; + } + + hasOwnProperty(v: PropertyKey): boolean { // valid + return true; + } + + isPrototypeOf(v: string): boolean { // error + return false; + } + + propertyIsEnumerable(v: PropertyKey): string { // error + return "not boolean"; + } +} + +class A3 { + toString(): string { return "ok"; } // valid + valueOf(): number { return 123; } // error +} + +class A4 { + customFunc(): void {} // valid + valueOf(): number { return 123; } // error + toString(): string { return "ok"; } // valid +} + +interface I1 { + toString(): string; // valid + toLocaleString(): number; // error + valueOf(): number; // error + customFunc(): boolean; // valid + hasOwnProperty(v: PropertyKey): boolean; // valid + isPrototypeOf(v: Object): boolean; // valid + propertyIsEnumerable(v: PropertyKey): boolean; // valid +} + +interface I2 { + toString(): boolean; // error + toLocaleString(): string; // valid + valueOf(): Object; // valid + hasOwnProperty(): boolean; // error + isPrototypeOf(v: string): boolean; // error + propertyIsEnumerable(v: PropertyKey): string; // error +} + +interface I3 { + toString(): string; // valid + valueOf(): number; // error +} + +interface I4 { + valueOf(): number; // error + toString(): string; // valid +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.args.json b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..1b80aa9e7367c4d206bb53f8fc43c77fc24045d7 --- /dev/null +++ b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.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/arkts_class_no_signature_public_obj_api.ets.arkts2.json b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..e8e7a47f64702c8c8786488292f3e5a4d1122a17 --- /dev/null +++ b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.arkts2.json @@ -0,0 +1,238 @@ +{ + "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": 25, + "column": 3, + "endLine": 27, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 29, + "column": 3, + "endLine": 31, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 26, + "column": 12, + "endLine": 26, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 45, + "column": 3, + "endLine": 45, + "endColumn": 35, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 47, + "column": 3, + "endLine": 49, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 59, + "column": 3, + "endLine": 61, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 63, + "column": 3, + "endLine": 65, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 45, + "column": 30, + "endLine": 45, + "endColumn": 33, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 48, + "column": 12, + "endLine": 48, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 52, + "column": 12, + "endLine": 52, + "endColumn": 13, + "problem": "ObjectLiteralNoContextType", + "suggest": "", + "rule": "Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)", + "severity": "ERROR" + }, + { + "line": 70, + "column": 3, + "endLine": 70, + "endColumn": 36, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 70, + "column": 30, + "endLine": 70, + "endColumn": 33, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 75, + "column": 3, + "endLine": 75, + "endColumn": 36, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 75, + "column": 30, + "endLine": 75, + "endColumn": 33, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 81, + "column": 3, + "endLine": 81, + "endColumn": 28, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 82, + "column": 3, + "endLine": 82, + "endColumn": 21, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 90, + "column": 3, + "endLine": 90, + "endColumn": 23, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 93, + "column": 3, + "endLine": 93, + "endColumn": 29, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 94, + "column": 3, + "endLine": 94, + "endColumn": 37, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 95, + "column": 3, + "endLine": 95, + "endColumn": 48, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 100, + "column": 3, + "endLine": 100, + "endColumn": 21, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, + { + "line": 104, + "column": 3, + "endLine": 104, + "endColumn": 21, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.json b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..84b96b7788932300593bd70afba0c4fea845f3f0 --- /dev/null +++ b/ets2panda/linter/test/main/arkts_class_no_signature_public_obj_api.ets.json @@ -0,0 +1,28 @@ +{ + "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": 52, + "column": 12, + "endLine": 52, + "endColumn": 13, + "problem": "ObjectLiteralNoContextType", + "suggest": "", + "rule": "Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json b/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json index c9d8ad036e52826cfdf4c6248996088543d98a98..83d6fc71f384b00cb2e6096e35feb2751a820125 100644 --- a/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json +++ b/ets2panda/linter/test/main/method_inheritance.ets.arkts2.json @@ -294,6 +294,16 @@ "rule": "Overridden method parameters and return types must respect type inheritance principles (arkts-method-inherit-rule)", "severity": "ERROR" }, + { + "line": 224, + "column": 3, + "endLine": 226, + "endColumn": 4, + "problem": "NoSignatureDistinctWithObjectPublicApi", + "suggest": "", + "rule": "The signature of a method in a class/interface cannot be different from the public interface in an object. (arkts-class-no-signature-distinct-with-object-public-api)", + "severity": "ERROR" + }, { "line": 266, "column": 9,