From 296a36bfb87fd66037cf4d5b0a7d14c79a968017 Mon Sep 17 00:00:00 2001 From: Utku Enes GURSEL Date: Mon, 14 Jul 2025 07:20:42 +0300 Subject: [PATCH] fix array-type-immutable rule Issue: ICLYGZ Description: Fix issues on array-type-immutable rule Signed-off-by: Utku Enes GURSEL --- ets2panda/linter/.gitignore | 3 +- ets2panda/linter/src/lib/TypeScriptLinter.ts | 69 +++++++++++++++++-- .../linter/src/lib/autofixes/Autofixer.ts | 16 ++--- .../lib/utils/consts/ArktsWhiteApiPaths.ts | 2 +- .../src/lib/utils/consts/BuiltinWhiteList.ts | 16 +---- .../test/main/arkts-array-type-immutable.ets | 18 ++++- ...arkts-array-type-immutable.ets.arkts2.json | 22 +++++- 7 files changed, 113 insertions(+), 33 deletions(-) diff --git a/ets2panda/linter/.gitignore b/ets2panda/linter/.gitignore index a84c3073e0..26590e3ca2 100644 --- a/ets2panda/linter/.gitignore +++ b/ets2panda/linter/.gitignore @@ -5,4 +5,5 @@ dist node_modules package-lock.json panda-tslinter-1.0.0.tgz -coverage/ \ No newline at end of file +coverage/ +test/local diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 666921f942..efa5e995bb 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -1869,6 +1869,12 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { this.handleMissingReturnType(arrowFunc); } } + if (!ts.isBlock(arrowFunc.body)) { + const contextRetType = this.tsTypeChecker.getContextualType(arrowFunc.body); + if (contextRetType) { + this.checkAssignmentMatching(arrowFunc.body, contextRetType, arrowFunc.body, true); + } + } this.checkDefaultParamBeforeRequired(arrowFunc); this.handleLimitedVoidFunction(arrowFunc); } @@ -1876,8 +1882,8 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { private handleFunctionDeclaration(node: ts.Node): void { // early exit via exception if cancellation was requested this.options.cancellationToken?.throwIfCancellationRequested(); - const tsFunctionDeclaration = node as ts.FunctionDeclaration; + if (!tsFunctionDeclaration.type) { this.handleMissingReturnType(tsFunctionDeclaration); } @@ -7341,10 +7347,46 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } } + isExprReturnedFromAsyncFunction(rhsExpr: ts.Expression | undefined, lhsType: ts.Type): ts.Type | undefined { + void this; + if (!rhsExpr) { + return undefined; + } + + const enclosingFunction = ts.findAncestor(rhsExpr, ts.isFunctionLike); + const isReturnExpr = ts.isReturnStatement(rhsExpr.parent) || ts.isArrowFunction(rhsExpr.parent); + if (!enclosingFunction) { + return undefined; + } + if (!isReturnExpr) { + return undefined; + } + + if (!TsUtils.hasModifier(enclosingFunction.modifiers, ts.SyntaxKind.AsyncKeyword)) { + return undefined; + } + + const lhsPromiseLikeType = lhsType.isUnion() && lhsType.types.find(TsUtils.isStdPromiseLikeType); + if (!lhsPromiseLikeType) { + return undefined; + } + + if (!TsUtils.isTypeReference(lhsPromiseLikeType) || !lhsPromiseLikeType.typeArguments?.length) { + return undefined; + } + return lhsPromiseLikeType.typeArguments[0]; + } + private handleArrayTypeImmutable(node: ts.Node, lhsType: ts.Type, rhsType: ts.Type, rhsExpr?: ts.Expression): void { if (!this.options.arkts2) { return; } + + const possibleLhsType = this.isExprReturnedFromAsyncFunction(rhsExpr, lhsType); + if (possibleLhsType) { + lhsType = possibleLhsType; + } + const isArray = this.tsUtils.isArray(lhsType) && this.tsUtils.isArray(rhsType); const isTuple = this.tsUtils.isOrDerivedFrom(lhsType, TsUtils.isTuple) && this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple); @@ -7357,18 +7399,31 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { return; } - if (ts.isAsExpression(node) && ts.isArrayLiteralExpression(node.expression)) { - node.expression.elements.forEach((elem) => { - if (elem.kind === ts.SyntaxKind.FalseKeyword || elem.kind === ts.SyntaxKind.TrueKeyword) { - lhsTypeStr = rhsTypeStr.replace(elem.getText(), 'boolean'); - } - }); + const possibleLhsTypeStr = this.checkLhsTypeString(node, rhsTypeStr); + if (possibleLhsTypeStr) { + lhsTypeStr = possibleLhsTypeStr; } + if (lhsTypeStr !== rhsTypeStr) { this.incrementCounters(node, FaultID.ArrayTypeImmutable); } } + private checkLhsTypeString(node: ts.Node, rhsTypeStr: string): string | undefined { + void this; + if (!ts.isAsExpression(node) || !ts.isArrayLiteralExpression(node.expression)) { + return undefined; + } + let lhsTypeStr: string | undefined; + node.expression.elements.forEach((elem) => { + if (elem.kind === ts.SyntaxKind.FalseKeyword || elem.kind === ts.SyntaxKind.TrueKeyword) { + lhsTypeStr = rhsTypeStr.replace(elem.getText(), 'boolean'); + } + }); + + return lhsTypeStr; + } + private isSubtypeByBaseTypesList(baseType: ts.Type, actualType: ts.Type): boolean { if (this.isTypeAssignable(actualType, baseType)) { return true; diff --git a/ets2panda/linter/src/lib/autofixes/Autofixer.ts b/ets2panda/linter/src/lib/autofixes/Autofixer.ts index 243071ad18..f95376ab59 100644 --- a/ets2panda/linter/src/lib/autofixes/Autofixer.ts +++ b/ets2panda/linter/src/lib/autofixes/Autofixer.ts @@ -4187,7 +4187,7 @@ export class Autofixer { return [{ start: newExpr.getStart(), end: newExpr.getEnd(), replacementText }]; } - + fixAppStorageCallExpression(callExpr: ts.CallExpression): Autofix[] | undefined { const varDecl = Autofixer.findParentVariableDeclaration(callExpr); if (!varDecl || varDecl.type) { @@ -4776,13 +4776,13 @@ export class Autofixer { `${expr.getText()}${callExpr.questionDotToken.getText()}unsafeCall` : `${expr.getText()}.unsafeCall`; - return [{ - start: expr.getStart(), - end: hasOptionalChain ? - callExpr.questionDotToken.getEnd() : - expr.getEnd(), - replacementText - }]; + return [ + { + start: expr.getStart(), + end: hasOptionalChain ? callExpr.questionDotToken.getEnd() : expr.getEnd(), + replacementText + } + ]; } private static createBuiltInTypeInitializer(type: ts.TypeReferenceNode): ts.Expression | undefined { diff --git a/ets2panda/linter/src/lib/utils/consts/ArktsWhiteApiPaths.ts b/ets2panda/linter/src/lib/utils/consts/ArktsWhiteApiPaths.ts index 113c4bdd79..c945fcd33c 100755 --- a/ets2panda/linter/src/lib/utils/consts/ArktsWhiteApiPaths.ts +++ b/ets2panda/linter/src/lib/utils/consts/ArktsWhiteApiPaths.ts @@ -14,4 +14,4 @@ */ export const ARKTS_WHITE_API_PATH_TEXTSTYLE = 'component/styled_string.d.ts'; -export const COMMON_UNION_MEMBER_ACCESS_WHITELIST = new Set(['ArrayBufferLike', 'IteratorResult']); \ No newline at end of file +export const COMMON_UNION_MEMBER_ACCESS_WHITELIST = new Set(['ArrayBufferLike', 'IteratorResult']); diff --git a/ets2panda/linter/src/lib/utils/consts/BuiltinWhiteList.ts b/ets2panda/linter/src/lib/utils/consts/BuiltinWhiteList.ts index db5078a676..297b5fba00 100644 --- a/ets2panda/linter/src/lib/utils/consts/BuiltinWhiteList.ts +++ b/ets2panda/linter/src/lib/utils/consts/BuiltinWhiteList.ts @@ -48,18 +48,6 @@ export const BUILTIN_DISABLE_CALLSIGNATURE = [ export const BUILTIN_CONSTRUCTORS = ['Boolean', 'Number', 'Object', 'String']; +export const COLLECTION_TYPES = new Set(['Map', 'Set', 'WeakMap', 'WeakSet']); -export const COLLECTION_TYPES = new Set([ - 'Map', - 'Set', - 'WeakMap', - 'WeakSet' -]); - -export const COLLECTION_METHODS = new Set([ - 'add', - 'delete', - 'get', - 'has', - 'set' -]); \ No newline at end of file +export const COLLECTION_METHODS = new Set(['add', 'delete', 'get', 'has', 'set']); diff --git a/ets2panda/linter/test/main/arkts-array-type-immutable.ets b/ets2panda/linter/test/main/arkts-array-type-immutable.ets index 8f805d8229..3c018c68ab 100644 --- a/ets2panda/linter/test/main/arkts-array-type-immutable.ets +++ b/ets2panda/linter/test/main/arkts-array-type-immutable.ets @@ -153,4 +153,20 @@ if (handler.apply) handler.apply(objA, objA, []); let readonlyArr: ReadonlyArray = []; let arr66 = new Array(); -readonlyArr = arr66; //error \ No newline at end of file +readonlyArr = arr66; //error + +let stringArray: string[] = [] +let correctArr: (string | number)[] = [] + +const Foo = (): string[] => stringArray; + +const FooBar = (): (string | number)[] => stringArray; +const Baz = (): (string | number)[] => correctArr; + +async function Foo_a(): Promise<(string| number)[]> { + return stringArray; +} + +async function Foo_b(): Promise<(string| number)[]> { + return correctArr; +} diff --git a/ets2panda/linter/test/main/arkts-array-type-immutable.ets.arkts2.json b/ets2panda/linter/test/main/arkts-array-type-immutable.ets.arkts2.json index e2441885c3..74eb906e2f 100644 --- a/ets2panda/linter/test/main/arkts-array-type-immutable.ets.arkts2.json +++ b/ets2panda/linter/test/main/arkts-array-type-immutable.ets.arkts2.json @@ -733,6 +733,26 @@ "suggest": "", "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", "severity": "ERROR" + }, + { + "line": 163, + "column": 43, + "endLine": 163, + "endColumn": 54, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" + }, + { + "line": 167, + "column": 5, + "endLine": 167, + "endColumn": 24, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" } ] -} \ No newline at end of file +} -- Gitee