diff --git a/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts b/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts index 9fd0a38200a833df66b5463a4156654f4f283c59..7c5158240596c8ee01086c30a209b5767d382f99 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/IRInference.ts @@ -79,6 +79,7 @@ import { ValueUtil } from './ValueUtil'; import { ArkFile } from '../model/ArkFile'; import { AbstractTypeExpr, KeyofTypeExpr, TypeQueryExpr } from '../base/TypeExpr'; import { ArkBaseModel } from '../model/ArkBaseModel'; +import { SdkUtils } from './SdkUtils'; const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'IRInference'); @@ -398,7 +399,11 @@ export class IRInference { } public static inferArgTypeWithSdk(sdkType: ClassType, scene: Scene, argType: Type): void { - if (!scene.getProjectSdkMap().has(sdkType.getClassSignature().getDeclaringFileSignature().getProjectName())) { + const sdkProjectName = sdkType.getClassSignature().getDeclaringFileSignature().getProjectName(); + const className = sdkType.getClassSignature().getClassName(); + + // When leftOp is local with Function annotation, the rightOp is a lambda function, which should be inferred as method later. + if (!scene.getProjectSdkMap().has(sdkProjectName) || (sdkProjectName === SdkUtils.BUILT_IN_NAME && className === 'Function')) { return; } if (argType instanceof UnionType) { diff --git a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts index b5854a3205aa3483eb2b4ec8e8ea9a054d65c2fd..b2caf8ad565416adffbaf4c23ac64be609ff038a 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts @@ -288,15 +288,18 @@ export class TypeInference { ) { return; } - const fieldRef = stmtDef.inferType(arkMethod); - stmt.replaceDef(stmtDef, fieldRef); + this.processRef(stmtDef, stmt, arkMethod, false); } } - private static processRef(use: AbstractRef | ArkInstanceFieldRef, stmt: Stmt, arkMethod: ArkMethod): void { + private static processRef(use: AbstractRef | ArkInstanceFieldRef, stmt: Stmt, arkMethod: ArkMethod, replaceUse: boolean = true): void { const fieldRef = use.inferType(arkMethod); if (fieldRef instanceof ArkStaticFieldRef && stmt instanceof ArkAssignStmt) { - stmt.replaceUse(use, fieldRef); + if (replaceUse) { + stmt.replaceUse(use, fieldRef); + } else { + stmt.replaceDef(use, fieldRef); + } } else if (use instanceof ArkInstanceFieldRef && fieldRef instanceof ArkArrayRef && stmt instanceof ArkAssignStmt) { const index = fieldRef.getIndex(); if (index instanceof Constant && index.getType() instanceof StringType) { @@ -305,7 +308,11 @@ export class TypeInference { fieldRef.setIndex(local); } } - stmt.replaceUse(use, fieldRef); + if (replaceUse) { + stmt.replaceUse(use, fieldRef); + } else { + stmt.replaceDef(use, fieldRef); + } } } diff --git a/ets2panda/linter/arkanalyzer/src/core/common/ValueUtil.ts b/ets2panda/linter/arkanalyzer/src/core/common/ValueUtil.ts index fd33a451da8b10230640a43bd642442539c76f64..b2b9b6cc3ff8cc610cf42ad890f9f4080c4dc382 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/ValueUtil.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/ValueUtil.ts @@ -62,7 +62,7 @@ export class ValueUtil { public static createConst(str: string): Constant { const n = Number(str); if (!isNaN(n)) { - return this.getOrCreateNumberConst(n); + return this.getOrCreateNumberConst(str); } return new StringConstant(str); } diff --git a/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts b/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts index 54284012979954f1ee7fdf446e5cf3c2ea2753bb..7b1c4a6e5adc3d0341893d1ec8355cb0642f8333 100644 --- a/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts +++ b/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts @@ -121,6 +121,18 @@ export class Cfg { } } + public setBlocks(blocks: Set, resetStmtToBlock: boolean = true): void { + this.blocks = blocks; + if (resetStmtToBlock) { + this.stmtToBlock.clear(); + for (const block of this.blocks) { + for (const stmt of block.getStmts()) { + this.stmtToBlock.set(stmt, block); + } + } + } + } + public getBlocks(): Set { return this.blocks; } diff --git a/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts b/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts index 478d0a7121bc5a7753f791936c3e07bbef8e7df2..ca81f85119bbb7c16b4dcac1b22e249c768a3a30 100644 --- a/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts +++ b/ets2panda/linter/arkanalyzer/src/core/graph/builder/CfgBuilder.ts @@ -1114,7 +1114,6 @@ export class CfgBuilder { arkIRTransformer ); - const currBlockId = this.blocks.length; this.linkBasicBlocks(blockBuilderToCfgBlock); this.adjustBlocks( blockBuilderToCfgBlock, @@ -1128,7 +1127,7 @@ export class CfgBuilder { const trapBuilder = new TrapBuilder(); const traps = trapBuilder.buildTraps(blockBuilderToCfgBlock, blockBuildersBeforeTry, arkIRTransformer, basicBlockSet); - const cfg = this.createCfg(blockBuilderToCfgBlock, basicBlockSet, currBlockId); + const cfg = this.createCfg(blockBuilderToCfgBlock, basicBlockSet); return { cfg, locals: arkIRTransformer.getLocals(), @@ -1239,20 +1238,11 @@ export class CfgBuilder { conditionalBuilder.rebuildBlocksContainConditionalOperator(basicBlockSet, ModelUtils.isArkUIBuilderMethod(this.declaringMethod)); } - private createCfg(blockBuilderToCfgBlock: Map, basicBlockSet: Set, prevBlockId: number): Cfg { - let currBlockId = prevBlockId; - for (const blockBuilder of this.blocks) { - if (blockBuilder.id === -1) { - blockBuilder.id = currBlockId++; - const block = blockBuilderToCfgBlock.get(blockBuilder) as BasicBlock; - block.setId(blockBuilder.id); - } - } - + private createCfg(blockBuilderToCfgBlock: Map, basicBlockSet: Set): Cfg { const cfg = new Cfg(); const startingBasicBlock = blockBuilderToCfgBlock.get(this.blocks[0])!; cfg.setStartingStmt(startingBasicBlock.getStmts()[0]); - currBlockId = 0; + let currBlockId = 0; for (const basicBlock of basicBlockSet) { basicBlock.setId(currBlockId++); cfg.addBlock(basicBlock); @@ -1260,9 +1250,45 @@ export class CfgBuilder { for (const stmt of cfg.getStmts()) { stmt.setCfg(cfg); } + this.topologicalSortBlock(cfg); return cfg; } + private topologicalSortBlock(cfg: Cfg): void { + function dfs(block: BasicBlock): void { + if (visited[block.getId()]) { + return; + } + + visited[block.getId()] = true; + result.add(block); + + for (const succ of block.getSuccessors() || []) { + dfs(succ); + } + } + + const startingBlock = cfg.getStartingBlock(); + if (!startingBlock) { + return; + } + const blocks = cfg.getBlocks(); + const visited: boolean[] = new Array(blocks.size).fill(false); + const result: Set = new Set(); + + dfs(startingBlock); + + // handle rest blocks haven't visted, which should be with no predecessorBlocks or the rest block in a block circle + for (const block of blocks) { + if (!visited[block.getId()]) { + dfs(block); + } + } + if (result.size === blocks.size) { + cfg.setBlocks(result, false); + } + } + private linkBasicBlocks(blockBuilderToCfgBlock: Map): void { for (const [blockBuilder, cfgBlock] of blockBuilderToCfgBlock) { for (const successorBlockBuilder of blockBuilder.nexts) { diff --git a/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts index b3b3c6d09a622dfb58e7c36f7238f9600aa5b204..90636ec1d022126aa1dfe52cf326e2e7966dcf33 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts @@ -31,6 +31,7 @@ import { ArkParameterRef, ArkTypeOfExpr, ArkUnopExpr, + ArrayType, BooleanType, CallGraph, ClassSignature, @@ -39,13 +40,13 @@ import { CONSTRUCTOR_NAME, DVFGBuilder, FileSignature, + FullPosition, GlobalRef, INSTANCE_INIT_METHOD_NAME, LexicalEnvType, Local, MethodSignature, NAME_DELIMITER, - NAME_PREFIX, NamespaceSignature, NormalBinaryOperator, Scene, @@ -54,6 +55,7 @@ import { TEMP_LOCAL_PREFIX, Type, UnaryOperator, + UnionType, UnknownType, Value, } from 'arkanalyzer/lib'; @@ -62,7 +64,7 @@ import { BaseChecker, BaseMetaData } from '../BaseChecker'; import { Defects, MatcherCallback, Rule, RuleFix, Utils } from '../../Index'; import { IssueReport } from '../../model/Defects'; import { DVFG, DVFGNode } from 'arkanalyzer/lib/VFG/DVFG'; -import { CALL_DEPTH_LIMIT, getGlobalLocalsInDefaultMethod, getLineAndColumn, GlobalCallGraphHelper } from './Utils'; +import { CALL_DEPTH_LIMIT, getLineAndColumn, GlobalCallGraphHelper } from './Utils'; import { Language } from 'arkanalyzer/lib/core/model/ArkFile'; import { NullConstant, NumberConstant, StringConstant, UndefinedConstant } from 'arkanalyzer/lib/core/base/Constant'; import { @@ -74,6 +76,7 @@ import { AstTreeUtils, EnumValueType, NumberType, + ts, TupleType, UnclearReferenceType, } from 'arkanalyzer'; @@ -102,6 +105,7 @@ enum NumberCategory { enum RuleCategory { SDKIntType = 'sdk-api-num2int', NumericLiteral = 'arkts-numeric-semantic', + ArrayIndex = 'arkts-array-index-expr-type', } enum IssueReason { @@ -109,6 +113,7 @@ enum IssueReason { UsedWithOtherType = 'not-only-used-as-int-or-long', CannotFindAll = 'cannot-find-all', RelatedWithNonETS2 = 'related-with-non-ets2', + ActuallyIntConstant = 'actually-int-constant', Other = 'other', } @@ -206,14 +211,29 @@ export class NumericSemanticCheck implements BaseChecker { // 场景2:需要检查整型字面量出现的stmt,该stmt为sink点。场景2在场景1之后执行,优先让SDK调用来决定变量的类型为int、long、number,剩余的场景2处理,避免issue之间的冲突 if (target.isGenerated()) { // statInit、instInit等方法不进行检查,不主动对类属性的类型进行检查,因为类属性的使用范围很广,很难找全,仅对涉及的1/2这种进行告警,自动修复为1.0/2.0 - this.checkFieldInitializerWithDivision(target); - return; + try { + this.checkFieldInitializerWithDivision(target); + } catch (e) { + logger.error(`Error checking init method with numeric literal, method: ${target.getSignature().toString()}, error: ${e}`); + } + } else { + for (const stmt of stmts) { + try { + this.checkStmtContainsNumericLiteral(stmt); + } catch (e) { + logger.error( + `Error checking stmt with numeric literal, stmt: ${stmt.toString()}, method: ${target.getSignature().toString()}, error: ${e}` + ); + } + } } + + // 场景3:需要检查array的index,该stmt为sink点 for (const stmt of stmts) { try { - this.checkStmtContainsNumericLiteral(stmt); + this.checkArrayIndexInStmt(stmt); } catch (e) { - logger.error(`Error checking stmt with numeric literal, stmt: ${stmt.toString()}, method: ${target.getSignature().toString()}, error: ${e}`); + logger.error(`Error checking array index in stmt: ${stmt.toString()}, method: ${target.getSignature().toString()}, error: ${e}`); } } } @@ -348,6 +368,114 @@ export class NumericSemanticCheck implements BaseChecker { }); } + private checkArrayIndexInStmt(stmt: Stmt): void { + const res = new Map(); + this.callDepth = 0; + const index = this.getIndexValue(stmt); + if (index === null) { + return; + } + // 对于index为1.0、2.0这种number constant,需要告警并自动修复成1、2 + if (index instanceof NumberConstant && this.isFloatActuallyInt(index)) { + this.addIssueReport(RuleCategory.ArrayIndex, NumberCategory.number, IssueReason.ActuallyIntConstant, true, stmt, index); + return; + } + const issueReason = this.checkValueOnlyUsedAsIntLong(stmt, index, res, NumberCategory.int); + if (issueReason !== IssueReason.OnlyUsedAsIntLong) { + // 若index原先非int,则获取的数组元素应该是undefined,不可以对其进行强转int,否则对原始代码的语义有修改 + this.addIssueReport(RuleCategory.ArrayIndex, NumberCategory.number, issueReason, false, stmt, index); + } + res.forEach((issueInfo, local) => { + if (this.shouldIgnoreLocal(local)) { + return; + } + const declaringStmt = local.getDeclaringStmt(); + if (declaringStmt !== null) { + this.addIssueReport(RuleCategory.ArrayIndex, issueInfo.numberCategory, issueInfo.issueReason, true, declaringStmt, local); + } + }); + this.classFieldRes.forEach((fieldInfo, field) => { + if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong) { + // 如果能明确判断出field是int,则添加类型注解int,其他找不全的场景不变 + this.addIssueReport(RuleCategory.ArrayIndex, NumberCategory.int, fieldInfo.issueReason, true, undefined, undefined, field); + } + }); + } + + private getFieldRefActualArrayRef(stmt: Stmt): ArkInstanceFieldRef | null { + const fieldRef = stmt.getFieldRef(); + if (fieldRef === undefined || !(fieldRef instanceof ArkInstanceFieldRef)) { + return null; + } + const fieldBaseType = fieldRef.getBase().getType(); + if (!(fieldBaseType instanceof UnionType)) { + return null; + } + let containArray = false; + for (const t of fieldBaseType.getTypes()) { + if (t instanceof ArrayType) { + containArray = true; + break; + } + } + if (!containArray) { + return null; + } + const fieldName = fieldRef.getFieldName(); + if (fieldName === 'length') { + return null; + } + return fieldRef; + } + + private getActualIndexPosInStmt(stmt: Stmt): FullPosition { + // 处理array定义时为unionType,例如string[] | undefined,array的index访问语句被错误表示成ArkInstanceFieldRef的场景 + // 由于IR表示在此处的能力限制,此处判断unionType中包含arrayType,且fieldRef的名字非length,取第4个操作数的位置为实际index的位置 + const fieldRef = this.getFieldRefActualArrayRef(stmt); + if (!fieldRef) { + return FullPosition.DEFAULT; + } + const positions = stmt.getOperandOriginalPositions(); + if (!positions || positions.length !== 4) { + return FullPosition.DEFAULT; + } + return positions[3]; + } + + private getIndexValue(stmt: Stmt): Value | null { + // 处理stmt的uses中的array的index访问语句 + const arrayRef = stmt.getArrayRef(); + if (arrayRef !== undefined) { + return arrayRef.getIndex(); + } + + // 处理array的index访问出现在赋值语句左边的情况 + if (stmt.getDef() instanceof ArkArrayRef) { + return (stmt.getDef() as ArkArrayRef).getIndex(); + } + + // 处理array定义时为unionType,例如string[] | undefined,array的index访问语句被错误表示成ArkInstanceFieldRef的场景 + // 由于IR表示在此处的能力限制,此处判断unionType中包含arrayType,且fieldRef的名字非length,且在local或global中能找到同名的情况 + const methodBody = stmt.getCfg().getDeclaringMethod().getBody(); + if (methodBody === undefined) { + return null; + } + + const fieldRef = this.getFieldRefActualArrayRef(stmt); + if (!fieldRef) { + return null; + } + let index = methodBody.getLocals().get(fieldRef.getFieldName()); + if (index !== undefined) { + return index; + } + const global = methodBody.getUsedGlobals()?.get(fieldRef.getFieldName()); + if (global === undefined || !(global instanceof GlobalRef)) { + return null; + } + return global.getRef(); + } + private isLocalAssigned2Array(local: Local): boolean { if (!local.getName().startsWith(TEMP_LOCAL_PREFIX)) { return false; @@ -406,6 +534,10 @@ export class NumericSemanticCheck implements BaseChecker { if (s instanceof ArkAssignStmt && s.getRightOp() instanceof Local && s.getRightOp() === local) { const leftOp = s.getLeftOp(); if (leftOp instanceof Local) { + if (hasChecked.has(leftOp)) { + // 对于a = a语句,此处必须判断,否则会死循环 + return; + } this.checkAllLocalsAroundLocal(s, leftOp, hasChecked, numberCategory); } } @@ -702,6 +834,42 @@ export class NumericSemanticCheck implements BaseChecker { return constant.getValue().includes('.'); } + // 判断number constant是否为1.0、2.0这种可以转成1、2的整型形式 + private isFloatActuallyInt(constant: NumberConstant): boolean { + const parts = constant.getValue().split('.'); + if (parts.length !== 2) { + return false; + } + return /^0+$/.test(parts[1]); + } + + // 根据local的类型初步判断结果 + // 有些场景直接返回检查结果,不再继续检查,例如:类型为枚举、未知类型、与number无关的复杂类型等 + // 有些场景需要继续根据local的使用进行判断,例如:与number有关的类型等 + private checkResWithLocalType(local: Local, stmt: Stmt): IssueReason | null { + const localType = local.getType(); + if (!Utils.isNearlyNumberType(localType) && !(localType instanceof BooleanType)) { + // 对于联合类型仅包含number和null、undefined,可以认为是OK的,需要进一步根据local的使用情况进行判断 + // 对于return a || b, arkanalyzer会认为return op是boolean类型,其实是a的类型或b的类型,此处应该是number,需要正常继续解析表达式a || b + if (localType instanceof UnknownType || localType instanceof UnclearReferenceType) { + // 类型推导失败为unknownType或UnclearReferenceType + if (stmt instanceof ArkAssignStmt && stmt.getRightOp() instanceof ArkArrayRef && (stmt.getRightOp() as ArkArrayRef).getIndex() === local) { + // class field初始化为函数指针,导致匿名函数中使用到的闭包变量未识别,其类型为unknownType,需要继续进行查找 + return null; + } + return IssueReason.CannotFindAll; + } + if (localType instanceof EnumValueType) { + // local是枚举类型的值,无法改变枚举类型的定义,当做number使用 + return IssueReason.UsedWithOtherType; + } + // 剩余情况有aliasType、classType、函数指针、genericType等复杂场景,不再继续判断,直接返回UsedWithOtherType + logger.trace(`Local type is not number, local: ${local.getName()}, local type: ${local.getType().getTypeString()}`); + return IssueReason.UsedWithOtherType; + } + return null; + } + private isLocalOnlyUsedAsIntLong(stmt: Stmt, local: Local, hasChecked: Map, numberCategory: NumberCategory): IssueReason { const currentInfo = hasChecked.get(local); // hasChecked map中已有此local,若原先为int,现在为long则使用long替换,其余情况不改动,直接返回,避免死循环 @@ -712,10 +880,10 @@ export class NumericSemanticCheck implements BaseChecker { return IssueReason.OnlyUsedAsIntLong; } // 在之前的语句检测中已查找过此local并生成相应的issue,直接根据issue的内容返回结果,如果issue中是int,检查的是long,则结果为long - const currentIssue = this.getLocalIssueFromIssueList(local); + const currentIssue = this.getLocalIssueFromIssueList(local, stmt); if (currentIssue && currentIssue.fix instanceof RuleFix) { const issueReason = this.getIssueReasonFromDefectInfo(currentIssue.defect); - const issueCategory = this.getNumberCategoryFromLocalFixInfo(currentIssue.fix as RuleFix); + const issueCategory = this.getNumberCategoryFromFixInfo(currentIssue.fix as RuleFix); if (issueReason !== null && issueCategory !== null) { if (issueReason !== IssueReason.OnlyUsedAsIntLong) { hasChecked.set(local, { issueReason: issueReason, numberCategory: numberCategory }); @@ -738,24 +906,10 @@ export class NumericSemanticCheck implements BaseChecker { // 先将value加入map中,默认设置成false,避免后续递归查找阶段出现死循环,最后再根据查找结果绝对是否重新设置成true hasChecked.set(local, { issueReason: IssueReason.Other, numberCategory: numberCategory }); - // 正常情况不会走到此分支,除非类型为any、联合类型等复杂类型,或类型推导失败为unknownType,保守处理返回false,不转int - // 对于联合类型仅包含number和null、undefined,可以认为是OK的 - // 对于return a || b, arkanalyzer会认为return op是boolean类型,其实是a的类型或b的类型,此处应该是number,需要正常继续解析表达式a || b - const localType = local.getType(); - if (!Utils.isNearlyNumberType(localType) && !(localType instanceof BooleanType)) { - if (localType instanceof UnknownType || localType instanceof UnclearReferenceType) { - hasChecked.set(local, { issueReason: IssueReason.CannotFindAll, numberCategory: numberCategory }); - return IssueReason.CannotFindAll; - } - if (localType instanceof EnumValueType) { - // local是枚举类型的值,无法改变枚举类型的定义,当做number使用 - hasChecked.set(local, { issueReason: IssueReason.UsedWithOtherType, numberCategory: numberCategory }); - return IssueReason.UsedWithOtherType; - } - // 剩余情况有aliasType、classType、函数指针、genericType等复杂场景,不再继续判断,直接返回UsedWithOtherType - logger.trace(`Local type is not number, local: ${local.getName()}, local type: ${local.getType().getTypeString()}`); - hasChecked.set(local, { issueReason: IssueReason.UsedWithOtherType, numberCategory: numberCategory }); - return IssueReason.UsedWithOtherType; + const resWithLocalType = this.checkResWithLocalType(local, stmt); + if (resWithLocalType) { + hasChecked.set(local, { issueReason: resWithLocalType, numberCategory: numberCategory }); + return resWithLocalType; } let checkStmts: Stmt[] = []; @@ -763,7 +917,11 @@ export class NumericSemanticCheck implements BaseChecker { if (declaringStmt === null) { // 无定义语句的local可能来自于全局变量或import变量,需要根据import信息查找其原始local // 也可能是内层匿名类中使用到的外层函数中的变量,在内存类属性初始化时无定义语句 - const newLocal = this.getLocalFromOuterMethod(local) ?? this.getLocalFromGlobal(local, stmt) ?? this.getLocalFromImportInfo(local); + const declaringMethod = stmt.getCfg().getDeclaringMethod(); + const newLocal = + this.getLocalFromOuterMethod(local, declaringMethod) ?? + this.getLocalFromGlobal(local, declaringMethod) ?? + this.getLocalFromImportInfo(local, declaringMethod); if (newLocal === null) { // local非来自于import,确实是缺少定义语句,或者是从非1.2文件import,直接返回false,因为就算是能确认local仅当做int使用,也找不到定义语句去修改类型注解为int,所以后续检查都没有意义 logger.error(`Missing declaring stmt, local: ${local.getName()}`); @@ -798,7 +956,7 @@ export class NumericSemanticCheck implements BaseChecker { checkStmts.push(s); } }); - // usedStmts中不会记录local为leftOp的stmt,此处需要补充 + // usedStmts中不会记录local为leftOp的stmt,在此处进行补充 declaringStmt .getCfg() .getStmts() @@ -810,52 +968,50 @@ export class NumericSemanticCheck implements BaseChecker { }); for (const s of checkStmts) { - if (s instanceof ArkAssignStmt && s.getLeftOp() === local) { - const checkRightOp = this.checkValueOnlyUsedAsIntLong(s, s.getRightOp(), hasChecked, numberCategory); - if (checkRightOp !== IssueReason.OnlyUsedAsIntLong) { - hasChecked.set(local, { issueReason: checkRightOp, numberCategory: numberCategory }); - return checkRightOp; - } - continue; + const res = this.checkRelatedStmtForLocal(s, local, hasChecked, numberCategory); + if (res.issueReason !== IssueReason.OnlyUsedAsIntLong) { + hasChecked.set(local, res); + return res.issueReason; } - // 当前检查的local位于赋值语句的右边,若参与除法运算则看做double类型使用,若作为SDK入参依据SDK定义,其余运算、赋值等处理不会影响其自身从int -> number,所以不处理 - if (s instanceof ArkAssignStmt && s.getLeftOp() !== local) { - const rightOp = s.getRightOp(); - if (rightOp instanceof ArkNormalBinopExpr && rightOp.getOperator() === NormalBinaryOperator.Division) { - hasChecked.set(local, { issueReason: IssueReason.UsedWithOtherType, numberCategory: numberCategory }); - return IssueReason.UsedWithOtherType; - } - if (rightOp instanceof AbstractInvokeExpr) { - const res = this.checkLocalUsedAsSDKArg(rightOp, local, hasChecked); - if (res !== null && res.issueReason !== IssueReason.OnlyUsedAsIntLong) { - hasChecked.set(local, res); - return res.issueReason; - } - } - continue; + } + hasChecked.set(local, { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory: numberCategory }); + return IssueReason.OnlyUsedAsIntLong; + } + + private checkRelatedStmtForLocal(stmt: Stmt, local: Local, hasChecked: Map, numberCategory: NumberCategory): IssueInfo { + if (stmt instanceof ArkAssignStmt && stmt.getLeftOp() === local) { + const issueReason = this.checkValueOnlyUsedAsIntLong(stmt, stmt.getRightOp(), hasChecked, numberCategory); + return { issueReason, numberCategory }; + } + // 当前检查的local位于赋值语句的右边,若参与除法运算则看做double类型使用,若作为SDK入参依据SDK定义,其余运算、赋值等处理不会影响其自身从int -> number,所以不处理 + if (stmt instanceof ArkAssignStmt && stmt.getLeftOp() !== local) { + const rightOp = stmt.getRightOp(); + if (rightOp instanceof ArkNormalBinopExpr && rightOp.getOperator() === NormalBinaryOperator.Division) { + return { issueReason: IssueReason.UsedWithOtherType, numberCategory }; } - if (s instanceof ArkInvokeStmt) { - // 函数调用语句,local作为实参或base,除作为SDK入参之外,其余场景不会影响其值的变化,不会导致int被重新赋值为number使用 - const res = this.checkLocalUsedAsSDKArg(s.getInvokeExpr(), local, hasChecked); - if (res !== null && res.issueReason !== IssueReason.OnlyUsedAsIntLong) { - hasChecked.set(local, res); - return res.issueReason; + if (rightOp instanceof AbstractInvokeExpr) { + const res = this.checkLocalUsedAsSDKArg(rightOp, local, hasChecked); + if (res !== null) { + return res; } - continue; } - if (s instanceof ArkReturnStmt) { - // return语句,local作为返回值,不会影响其值的变化,不会导致int被重新赋值为number使用 - continue; - } - if (s instanceof ArkIfStmt) { - // 条件判断语句,local作为condition expr的op1或op2,进行二元条件判断,不会影响其值的变化,不会导致int被重新赋值为number使用 - continue; + return { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory }; + } + if (stmt instanceof ArkInvokeStmt) { + // 函数调用语句,local作为实参或base,除作为SDK入参之外,其余场景不会影响其值的变化,不会导致int被重新赋值为number使用 + const res = this.checkLocalUsedAsSDKArg(stmt.getInvokeExpr(), local, hasChecked); + if (res !== null) { + return res; } - logger.error(`Need to check new type of stmt: ${s.toString()}, method: ${s.getCfg().getDeclaringMethod().getSignature().toString()}`); - return IssueReason.Other; + return { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory }; } - hasChecked.set(local, { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory: numberCategory }); - return IssueReason.OnlyUsedAsIntLong; + if (stmt instanceof ArkReturnStmt || stmt instanceof ArkIfStmt) { + // return语句,local作为返回值,不会影响其值的变化,不会导致int被重新赋值为number使用 + // 条件判断语句,local作为condition expr的op1或op2,进行二元条件判断,不会影响其值的变化,不会导致int被重新赋值为number使用 + return { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory }; + } + logger.error(`Need to check new type of stmt: ${stmt.toString()}, method: ${stmt.getCfg().getDeclaringMethod().getSignature().toString()}`); + return { issueReason: IssueReason.Other, numberCategory }; } // 判断local是否是SDK invoke expr的入参,且其类型是int或long,否则返回null @@ -897,8 +1053,8 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getLocalFromGlobal(local: Local, stmt: Stmt): Local | null { - const defaultMethod = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getDefaultClass().getDefaultArkMethod(); + private getLocalFromGlobal(local: Local, method: ArkMethod): Local | null { + const defaultMethod = method.getDeclaringArkFile().getDefaultClass().getDefaultArkMethod(); if (!defaultMethod) { return null; } @@ -909,12 +1065,8 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getLocalFromImportInfo(local: Local): Local | null { - const usedStmts = local.getUsedStmts(); - if (usedStmts.length < 1) { - return null; - } - const importInfo = usedStmts[0].getCfg().getDeclaringMethod().getDeclaringArkFile().getImportInfoBy(local.getName()); + private getLocalFromImportInfo(local: Local, method: ArkMethod): Local | null { + const importInfo = method.getDeclaringArkFile().getImportInfoBy(local.getName()); if (importInfo === undefined) { return null; } @@ -933,16 +1085,25 @@ export class NumericSemanticCheck implements BaseChecker { } // 对于method中的let obj: Obj = {aa: a}的对象字面量,其中使用到a变量为method中的local或global,当前ArkAnalyzer未能对其进行识别和表示,此处手动查找 - private getLocalFromOuterMethod(local: Local): Local | null { - const usedStmts = local.getUsedStmts(); - if (usedStmts.length < 1) { - return null; + // 对于class的field为lambda函数定义的函数指针的场景,lambda函数中使用到的外层的变量,当前ArkAnalyzer未能对其进行识别和表示,此处手动查找 + private getLocalFromOuterMethod(local: Local, method: ArkMethod): Local | null { + const outerMethod = method.getOuterMethod(); + if (outerMethod) { + const newLocal = outerMethod.getBody()?.getLocals().get(local.getName()); + if (newLocal) { + if (newLocal.getDeclaringStmt()) { + return newLocal; + } else { + return this.getLocalFromOuterMethod(newLocal, outerMethod); + } + } } - const clazz = usedStmts[0].getCfg().getDeclaringMethod().getDeclaringArkClass(); - return this.findLocalFromOuterObject(local, clazz); + + const clazz = method.getDeclaringArkClass(); + return this.findLocalFromOuterClass(local, clazz); } - private findLocalFromOuterObject(local: Local, objectClass: ArkClass): Local | null { + private findLocalFromOuterClass(local: Local, objectClass: ArkClass): Local | null { if (objectClass.getCategory() !== ClassCategory.INTERFACE && objectClass.getCategory() !== ClassCategory.OBJECT) { // 此查找仅涉及对象字面量中直接使用变量的场景,其余场景不涉及 return null; @@ -971,7 +1132,7 @@ export class NumericSemanticCheck implements BaseChecker { if (declaringStmt) { return newLocal; } - return this.getLocalFromOuterMethod(newLocal); + return this.getLocalFromOuterMethod(newLocal, outerMethod); } const globalRef = outerMethod.getBody()?.getUsedGlobals()?.get(local.getName()); if (globalRef && globalRef instanceof GlobalRef) { @@ -982,7 +1143,7 @@ export class NumericSemanticCheck implements BaseChecker { return null; } if (outerClass.isAnonymousClass()) { - return this.findLocalFromOuterObject(local, outerClass); + return this.findLocalFromOuterClass(local, outerClass); } return null; } @@ -1067,6 +1228,10 @@ export class NumericSemanticCheck implements BaseChecker { if (expr.getOperator() === UnaryOperator.Neg || expr.getOperator() === UnaryOperator.BitwiseNot) { return this.checkValueOnlyUsedAsIntLong(stmt, expr.getOp(), hasChecked, numberCategory); } + if (expr.getOperator() === UnaryOperator.LogicalNot) { + // let a = 1; let b = !a,不会导致a产生int到number的变化 + return IssueReason.OnlyUsedAsIntLong; + } logger.error(`Need to handle new type of unary operator: ${expr.getOperator().toString()}`); return IssueReason.Other; } @@ -1439,7 +1604,7 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getNumberCategoryFromFieldFixInfo(fix: RuleFix): NumberCategory | null { + private getNumberCategoryFromFixInfo(fix: RuleFix): NumberCategory | null { const fixText = fix.text; let match = fix.text.match(/^([^=;]+:[^=;]+)([\s\S]*)$/); if (match === null || match.length < 2) { @@ -1457,20 +1622,6 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getNumberCategoryFromLocalFixInfo(fix: RuleFix): NumberCategory | null { - const fixText = fix.text; - if (fixText.startsWith(`: ${NumberCategory.int}`)) { - return NumberCategory.int; - } - if (fixText.startsWith(`: ${NumberCategory.long}`)) { - return NumberCategory.long; - } - if (fixText.startsWith(`: ${NumberCategory.number}`)) { - return NumberCategory.number; - } - return null; - } - private getFieldIssueFromIssueList(field: ArkField): IssueReport | null { const position: WarnInfo = { line: field.getOriginPosition().getLineNo(), @@ -1487,12 +1638,8 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getLocalIssueFromIssueList(local: Local): IssueReport | null { - const declaringStmt = local.getDeclaringStmt(); - if (declaringStmt === null) { - return null; - } - const position = getLineAndColumn(declaringStmt, local, true); + private getLocalIssueFromIssueList(local: Local, stmt: Stmt): IssueReport | null { + const position = getLineAndColumn(stmt, local, true); const fixKeyPrefix = position.line + '%' + position.startCol + '%' + position.endCol + '%'; for (const issue of this.issues) { if (issue.defect.fixKey.startsWith(fixKeyPrefix)) { @@ -1502,24 +1649,28 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private addIssueReport( - ruleCategory: RuleCategory, - numberCategory: NumberCategory, - reason: IssueReason, - couldAutofix: boolean, - issueStmt?: Stmt, - value?: Value, - field?: ArkField, - usedStmt?: Stmt - ): void { - const severity = this.rule.alert ?? this.metaData.severity; - let warnInfo: WarnInfo; + private getWarnInfo(field?: ArkField, issueStmt?: Stmt, value?: Value): WarnInfo | null { + let warnInfo: WarnInfo | null = null; + if (field === undefined) { if (issueStmt && value) { warnInfo = getLineAndColumn(issueStmt, value, true); + if (warnInfo.line === -1) { + // 可能是因为获取array index时,array是联合类型导致index未推导成功,checker里面额外去body里找local替换index + // 但是获取index的position信息时,需要使用原始的index去stmt中查找位置 + const actualPosition = this.getActualIndexPosInStmt(issueStmt); + const originPath = issueStmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath(); + warnInfo = { + line: actualPosition.getFirstLine(), + startCol: actualPosition.getFirstCol(), + endLine: actualPosition.getLastLine(), + endCol: actualPosition.getLastCol(), + filePath: originPath, + }; + } } else { logger.error('Missing stmt or value when adding issue.'); - return; + return warnInfo; } } else { warnInfo = { @@ -1537,46 +1688,97 @@ export class NumericSemanticCheck implements BaseChecker { } else { logger.error(`failed to get position info`); } - return; + return null; } - let problem: string; - let desc: string; + return warnInfo; + } + + private getProblem(ruleCategory: RuleCategory, reason: IssueReason): string | null { + if (ruleCategory === RuleCategory.SDKIntType) { + return 'SDKIntType-' + reason; + } + if (ruleCategory === RuleCategory.NumericLiteral) { + return 'NumericLiteral-' + reason; + } + if (ruleCategory === RuleCategory.ArrayIndex) { + return 'IndexIntType-' + reason; + } + logger.error(`Have not support rule ${ruleCategory} yet.`); + return null; + } + + private getDesc( + ruleCategory: RuleCategory, + reason: IssueReason, + numberCategory: NumberCategory, + couldAutofix: boolean, + issueStmt?: Stmt, + usedStmt?: Stmt + ): string | null { if (ruleCategory === RuleCategory.SDKIntType) { - problem = 'SDKIntType-' + reason; if (reason === IssueReason.OnlyUsedAsIntLong) { if (usedStmt) { - desc = `It has relationship with the arg of SDK API in ${this.getUsedStmtDesc(usedStmt, issueStmt)} and only used as ${numberCategory}, should be defined as ${numberCategory} (${ruleCategory})`; - } else { - logger.error('Missing used stmt when getting issue description'); - return; + return `It has relationship with the arg of SDK API in ${this.getUsedStmtDesc(usedStmt, issueStmt)} and only used as ${numberCategory}, should be defined as ${numberCategory} (${ruleCategory})`; } - } else { - desc = `The arg of SDK API should be ${numberCategory} here (${ruleCategory})`; + logger.error('Missing used stmt when getting issue description'); + return null; } - } else if (ruleCategory === RuleCategory.NumericLiteral) { - problem = 'NumericLiteral-' + reason; + return `The arg of SDK API should be ${numberCategory} here (${ruleCategory})`; + } + if (ruleCategory === RuleCategory.NumericLiteral) { if (reason === IssueReason.OnlyUsedAsIntLong) { - desc = `It is used as ${NumberCategory.int} (${ruleCategory})`; - } else { - desc = `It is used as ${NumberCategory.number} (${ruleCategory})`; + return `It is used as ${NumberCategory.int} (${ruleCategory})`; } - } else { - logger.error(`Have not support rule ${ruleCategory} yet.`); + return `It is used as ${NumberCategory.number} (${ruleCategory})`; + } + if (ruleCategory === RuleCategory.ArrayIndex) { + if (reason === IssueReason.OnlyUsedAsIntLong) { + return `It is used as ${NumberCategory.int} (${ruleCategory})`; + } + if (reason === IssueReason.ActuallyIntConstant) { + return `The number constant could be changed to int constant (${ruleCategory})`; + } + if (couldAutofix) { + return `It is used as ${NumberCategory.number} (${ruleCategory})`; + } + return `The array index is used as ${NumberCategory.number}, please check if it's ok (${ruleCategory})`; + } + logger.error(`Have not support rule ${ruleCategory} yet.`); + return null; + } + + private addIssueReport( + ruleCategory: RuleCategory, + numberCategory: NumberCategory, + reason: IssueReason, + couldAutofix: boolean, + issueStmt?: Stmt, + value?: Value, + field?: ArkField, + usedStmt?: Stmt + ): void { + const severity = this.rule.alert ?? this.metaData.severity; + let warnInfo = this.getWarnInfo(field, issueStmt, value); + let problem = this.getProblem(ruleCategory, reason); + let desc = this.getDesc(ruleCategory, reason, numberCategory, couldAutofix, issueStmt, usedStmt); + if (!warnInfo || !problem || !desc) { return; } - // 添加新的issue之前需要检查一下已有issue,避免2个issue之间冲突,一个issue要改为int,一个issue要改为long + // 添加新的issue之前需要检查一下已有issue,避免重复issue,或2个issue之间冲突,一个issue要改为int,一个issue要改为long let currentIssue: IssueReport | null = null; let issueCategory: NumberCategory | null = null; if (field !== undefined) { currentIssue = this.getFieldIssueFromIssueList(field); if (currentIssue) { - issueCategory = this.getNumberCategoryFromFieldFixInfo(currentIssue.fix as RuleFix); + issueCategory = this.getNumberCategoryFromFixInfo(currentIssue.fix as RuleFix); } } else if (value instanceof Local) { - currentIssue = this.getLocalIssueFromIssueList(value); - if (currentIssue) { - issueCategory = this.getNumberCategoryFromLocalFixInfo(currentIssue.fix as RuleFix); + if (issueStmt) { + currentIssue = this.getLocalIssueFromIssueList(value, issueStmt); + if (currentIssue && currentIssue.fix) { + issueCategory = this.getNumberCategoryFromFixInfo(currentIssue.fix as RuleFix); + } } } if (currentIssue && issueCategory) { @@ -1593,6 +1795,9 @@ export class NumericSemanticCheck implements BaseChecker { // 已有的issue已经足够进行自动修复处理,无需重复添加 return; } + } else { + // 已有的issue对非int进行修改,无需重复添加 + return; } } } @@ -1612,28 +1817,47 @@ export class NumericSemanticCheck implements BaseChecker { couldAutofix ); - if (couldAutofix) { - let autofix: RuleFix | null = null; - if (ruleCategory === RuleCategory.SDKIntType) { - autofix = this.generateSDKRuleFix(warnInfo, reason, numberCategory, issueStmt, value, field); + if (!couldAutofix) { + this.issues.push(new IssueReport(defects, undefined)); + return; + } + if (ruleCategory === RuleCategory.SDKIntType) { + const autofix = this.generateSDKRuleFix(warnInfo, reason, numberCategory, issueStmt, value, field); + if (autofix === null) { + // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue + return; + } else { + this.issues.push(new IssueReport(defects, autofix)); + } + return; + } + if (ruleCategory === RuleCategory.NumericLiteral) { + const autofix = this.generateNumericLiteralRuleFix(warnInfo, reason, issueStmt, value, field); + if (autofix === null) { + // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue + return; + } + this.issues.push(new IssueReport(defects, autofix)); + return; + } + if (ruleCategory === RuleCategory.ArrayIndex) { + if (reason === IssueReason.ActuallyIntConstant && issueStmt && value instanceof NumberConstant) { + const autofix = this.generateIntConstantIndexRuleFix(warnInfo, issueStmt, value); if (autofix === null) { defects.fixable = false; this.issues.push(new IssueReport(defects, undefined)); } else { this.issues.push(new IssueReport(defects, autofix)); } - return; - } - if (ruleCategory === RuleCategory.NumericLiteral) { - autofix = this.generateNumericLiteralRuleFix(warnInfo, reason, issueStmt, value, field); + } else { + const autofix = this.generateNumericLiteralRuleFix(warnInfo, reason, issueStmt, value, field); if (autofix === null) { // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue return; } this.issues.push(new IssueReport(defects, autofix)); } - } else { - this.issues.push(new IssueReport(defects, undefined)); + return; } } @@ -1647,69 +1871,149 @@ export class NumericSemanticCheck implements BaseChecker { return `line ${line}`; } - private generateSDKRuleFix( - warnInfo: WarnInfo, - issueReason: IssueReason, - numberCategory: NumberCategory, - stmt?: Stmt, - value?: Value, - field?: ArkField - ): RuleFix | null { + private getSourceFile(field?: ArkField, issueStmt?: Stmt): ts.SourceFile | null { let arkFile: ArkFile; if (field) { arkFile = field.getDeclaringArkClass().getDeclaringArkFile(); - } else if (stmt) { - arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); + } else if (issueStmt) { + arkFile = issueStmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); } else { logger.error('Missing both issue stmt and field when generating auto fix info.'); return null; } - const sourceFile = AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode()); - if (field) { - // warnInfo中对于field的endCol与startCol一样,均为filed首列位置,包含修饰符位置,这里autofix采用整行替换方式进行 - const range = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.startCol); - if (range === null) { - logger.error('Failed to getting range info of issue file when generating auto fix info.'); - return null; - } - const valueString = FixUtils.getSourceWithRange(sourceFile, range); - if (valueString === null) { + return AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode()); + } + + private generateRuleFixForLocalDefine(sourceFile: ts.SourceFile, warnInfo: WarnInfo, numberCategory: NumberCategory): RuleFix | null { + // warnInfo中对于变量声明语句的位置信息只包括变量名,不包括变量声明时的类型注解位置 + // 此处先获取变量名后到行尾的字符串信息,判断是替换‘: number’ 或增加 ‘: int’ + const localRange = FixUtils.getRangeWithAst(sourceFile, { + startLine: warnInfo.line, + startCol: warnInfo.startCol, + endLine: warnInfo.line, + endCol: warnInfo.endCol, + }); + const restRange = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.endCol); + if (!localRange || !restRange) { + logger.error('Failed to getting range info of issue file when generating auto fix info.'); + return null; + } + const restString = FixUtils.getSourceWithRange(sourceFile, restRange); + if (!restString) { + logger.error('Failed to getting text of the fix range info when generating auto fix info.'); + return null; + } + + // 场景1:变量或函数入参,无类型注解的场景,直接在localString后面添加': int' + if (!restString.trimStart().startsWith(':')) { + let ruleFix = new RuleFix(); + ruleFix.range = localRange; + const localString = FixUtils.getSourceWithRange(sourceFile, ruleFix.range); + if (!localString) { logger.error('Failed to getting text of the fix range info when generating auto fix info.'); return null; } - const fixedText = this.generateFixedTextForFieldDefine(valueString, numberCategory); - if (fixedText === null) { - logger.error('Failed to get fix text when generating auto fix info.'); - return null; - } - const ruleFix = new RuleFix(); - ruleFix.range = range; - ruleFix.text = fixedText; + ruleFix.text = `${localString}: ${numberCategory}`; return ruleFix; } + // 场景2:变量或函数入参,有类型注解的场景,需要将类型注解替换成新的类型 + const match = restString.match(/^(\s*:[^,)=;]+)([\s\S]*)$/); + if (match === null || match.length < 3) { + return null; + } + // 如果需要替换成number,但是已经存在类型注解number,则返回null,不需要告警和自动修复 + if (match[1].includes(numberCategory)) { + return null; + } + let ruleFix = new RuleFix(); + ruleFix.range = [localRange[0], localRange[1] + match[1].length]; + const localString = FixUtils.getSourceWithRange(sourceFile, ruleFix.range); + if (!localString) { + logger.error('Failed to getting text of the fix range info when generating auto fix info.'); + return null; + } + const parts = localString.split(':'); + if (parts.length !== 2) { + logger.error('Failed to getting text of the fix range info when generating auto fix info.'); + return null; + } + if (!parts[1].includes(NumberCategory.number)) { + // 原码含有类型注解但是其类型中不含number,无法进行替换 + return null; + } + ruleFix.text = `${parts[0].trimEnd()}: ${parts[1].trimStart().replace(NumberCategory.number, numberCategory)}`; + return ruleFix; + } + + private generateRuleFixForFieldDefine(sourceFile: ts.SourceFile, warnInfo: WarnInfo, numberCategory: NumberCategory): RuleFix | null { + // warnInfo中对于field的endCol与startCol一样,均为filed首列位置,包含修饰符位置,这里autofix采用整行替换方式进行 + const fullRange = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.startCol); + if (fullRange === null) { + logger.error('Failed to getting range info of issue file when generating auto fix info.'); + return null; + } + const fullValueString = FixUtils.getSourceWithRange(sourceFile, fullRange); + if (fullValueString === null) { + logger.error('Failed to getting text of the fix range info when generating auto fix info.'); + return null; + } - if (issueReason === IssueReason.OnlyUsedAsIntLong) { - // warnInfo中对于变量声明语句的位置信息只包括变量名,不包括变量声明时的类型注解位置,此处获取变量名后到行尾的字符串信息,替换‘: number’ 或增加 ‘: int’ - const range = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.endCol); - if (range === null) { - logger.error('Failed to getting range info of issue file when generating auto fix info.'); + const ruleFix = new RuleFix(); + // 场景1:对于类属性private a: number 或 private a: number = xxx, fullValueString为private开始到行尾的内容,需要替换为private a: int + let match = fullValueString.match(/^([^=;]+:[^=;]+)([\s\S]*)$/); + if (match !== null && match.length > 2) { + if (match[1].includes(numberCategory)) { + // 判断field是否已经有正确的类型注解 return null; } - const valueString = FixUtils.getSourceWithRange(sourceFile, range); - if (valueString === null) { + ruleFix.range = [fullRange[0], fullRange[0] + match[1].length]; + const originalText = FixUtils.getSourceWithRange(sourceFile, ruleFix.range); + if (!originalText) { logger.error('Failed to getting text of the fix range info when generating auto fix info.'); return null; } - const fixedText = this.generateFixedTextForVariableDefine(valueString, numberCategory); - if (fixedText === null) { - logger.error('Failed to get fix text when generating auto fix info.'); + if (!originalText.includes(NumberCategory.number)) { + // 原码含有类型注解但是其类型中不含number,无法进行替换 return null; } - const ruleFix = new RuleFix(); - ruleFix.range = range; - ruleFix.text = fixedText; + ruleFix.text = originalText.replace(NumberCategory.number, numberCategory); + return ruleFix; + } + // 场景2:对于private a = 123,originalText为private开始到行尾的内容,需要替换为private a: int = 123 + match = fullValueString.match(/^([^=;]+)([\s\S]*)$/); + if (match !== null && match.length > 2) { + ruleFix.range = [fullRange[0], fullRange[0] + match[1].trimEnd().length]; + const originalText = FixUtils.getSourceWithRange(sourceFile, ruleFix.range); + if (!originalText) { + logger.error('Failed to getting text of the fix range info when generating auto fix info.'); + return null; + } + ruleFix.text = `${originalText}: ${numberCategory}`; return ruleFix; } + // 正常情况下不会走到此处,因为field一定有类型注解或初始化值来确定其类型 + return null; + } + + private generateSDKRuleFix( + warnInfo: WarnInfo, + issueReason: IssueReason, + numberCategory: NumberCategory, + issueStmt?: Stmt, + value?: Value, + field?: ArkField + ): RuleFix | null { + const sourceFile = this.getSourceFile(field, issueStmt); + if (!sourceFile) { + return null; + } + if (field) { + return this.generateRuleFixForFieldDefine(sourceFile, warnInfo, numberCategory); + } + + if (issueReason === IssueReason.OnlyUsedAsIntLong) { + return this.generateRuleFixForLocalDefine(sourceFile, warnInfo, numberCategory); + } // 强转场景,获取到对应位置信息,在其后添加'.toInt()'或'.toLong()' let endLine = warnInfo.line; if (warnInfo.endLine !== undefined) { @@ -1773,47 +2077,46 @@ export class NumericSemanticCheck implements BaseChecker { } } + private generateIntConstantIndexRuleFix(warnInfo: WarnInfo, issueStmt: Stmt, constant: NumberConstant): RuleFix | null { + if (!this.isFloatActuallyInt(constant)) { + return null; + } + const sourceFile = this.getSourceFile(undefined, issueStmt); + if (!sourceFile) { + return null; + } + const range = FixUtils.getRangeWithAst(sourceFile, { + startLine: warnInfo.line, + startCol: warnInfo.startCol, + endLine: warnInfo.line, + endCol: warnInfo.endCol, + }); + if (range === null) { + logger.error('Failed to getting range info of issue file when generating auto fix info.'); + return null; + } + const ruleFix = new RuleFix(); + ruleFix.range = range; + const parts = constant.getValue().split('.'); + if (parts.length !== 2) { + return null; + } + ruleFix.text = parts[0]; + return ruleFix; + } + private generateNumericLiteralRuleFix(warnInfo: WarnInfo, issueReason: IssueReason, issueStmt?: Stmt, value?: Value, field?: ArkField): RuleFix | null { - let arkFile: ArkFile; - if (field) { - arkFile = field.getDeclaringArkClass().getDeclaringArkFile(); - } else if (issueStmt) { - arkFile = issueStmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); - } else { - logger.error('Missing both issue stmt and field when generating auto fix info.'); + const sourceFile = this.getSourceFile(field, issueStmt); + if (!sourceFile) { return null; } - const sourceFile = AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode()); if (field) { - // warnInfo中对于field的endCol与startCol一样,均为filed首列位置,包含修饰符位置,这里autofix采用整行替换方式进行 - const range = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.startCol); - if (range === null) { - logger.error('Failed to getting range info of issue file when generating auto fix info.'); - return null; - } - const valueString = FixUtils.getSourceWithRange(sourceFile, range); - if (valueString === null) { - logger.error('Failed to getting text of the fix range info when generating auto fix info.'); - return null; - } - let fixedText: string | null = null; if (issueReason === IssueReason.OnlyUsedAsIntLong) { - fixedText = this.generateFixedTextForFieldDefine(valueString, NumberCategory.int); + return this.generateRuleFixForFieldDefine(sourceFile, warnInfo, NumberCategory.int); } else { - if (this.isFieldDefineAlreadyWithNumberType(valueString)) { - return null; - } - fixedText = this.generateFixedTextForFieldDefine(valueString, NumberCategory.number); + return this.generateRuleFixForFieldDefine(sourceFile, warnInfo, NumberCategory.number); } - if (fixedText === null) { - logger.error('Failed to get fix text when generating auto fix info.'); - return null; - } - const ruleFix = new RuleFix(); - ruleFix.range = range; - ruleFix.text = fixedText; - return ruleFix; } if (value instanceof NumberConstant) { @@ -1844,91 +2147,9 @@ export class NumericSemanticCheck implements BaseChecker { } // 非整型字面量 // warnInfo中对于变量声明语句的位置信息只包括变量名,不包括变量声明时的类型注解位置,此处获取变量名后到行尾的字符串信息,替换‘: number’ 或增加 ‘: int’ - const range = FixUtils.getLineRangeWithStartCol(sourceFile, warnInfo.line, warnInfo.endCol); - if (range === null) { - logger.error('Failed to getting range info of issue file when generating auto fix info.'); - return null; - } - const valueString = FixUtils.getSourceWithRange(sourceFile, range); - if (valueString === null) { - logger.error('Failed to getting text of the fix range info when generating auto fix info.'); - return null; - } - - let fixedText: string | null = null; if (issueReason === IssueReason.OnlyUsedAsIntLong) { - fixedText = this.generateFixedTextForVariableDefine(valueString, NumberCategory.int); - } else { - if (this.isVariableDefineAlreadyWithNumberType(valueString)) { - // 类型注解已经有number,无需进行自动修复 - return null; - } - fixedText = this.generateFixedTextForVariableDefine(valueString, NumberCategory.number); - } - if (fixedText === null) { - logger.error('Failed to get fix text when generating auto fix info.'); - return null; - } - const ruleFix = new RuleFix(); - ruleFix.range = range; - ruleFix.text = fixedText; - return ruleFix; - } - - private generateFixedTextForFieldDefine(originalText: string, numberCategory: NumberCategory): string | null { - // 对于类属性private a: number 或 private a, originalText为private开始到行尾的内容,需要替换为private a: int - let newTypeStr: string = numberCategory; - let match = originalText.match(/^([^=;]+:[^=;]+)([\s\S]*)$/); - if (match !== null && match.length > 2) { - return match[1].replace(NumberCategory.number, newTypeStr) + match[2]; - } - // 对于private a = 123,originalText为private开始到行尾的内容,需要替换为private a: int = 123 - match = originalText.match(/^([^=;]+)([\s\S]*)$/); - if (match !== null && match.length > 2) { - return `${match[1].trimEnd()}: ${newTypeStr} ${match[2]}`; - } - return null; - } - - private generateFixedTextForVariableDefine(originalText: string, numberCategory: NumberCategory): string | null { - // 对于let a = xxx, originalText为' = xxx,',需要替换成': int = xxx' - // 对于let a: number | null = xxx, originalText为': number | null = xxx,',需要替换成': int | null = xxx' - // 对于foo(a: number, b: string)场景, originalText为‘: number, b: string)’,需要替换为foo(a: int, b: string) - // 场景1:变量或类属性定义或函数入参,无类型注解的场景,直接在originalText前面添加': int' - let newTypeStr: string = numberCategory; - if (!originalText.trimStart().startsWith(':')) { - if (originalText.startsWith(';') || originalText.startsWith(FixUtils.getTextEof(originalText))) { - return `: ${newTypeStr}${originalText}`; - } - return `: ${newTypeStr} ${originalText.trimStart()}`; - } - // 场景2:变量或类属性定义或函数入参,有类型注解的场景 - const match = originalText.match(/^(\s*:[^,)=;]+)([\s\S]*)$/); - if (match === null || match.length < 3) { - return null; - } - const newAnnotation = match[1].replace('number', newTypeStr); - return newAnnotation + match[2]; - } - - // 传入的源码片段为变量的声明语句中紧跟变量名的部分 - // 对于let a: number | null = xxx, 为': number | null = xxx,', - private isVariableDefineAlreadyWithNumberType(sourceCode: string): boolean { - if (!sourceCode.trimStart().startsWith(':')) { - return false; - } - const match = sourceCode.match(/\s*:\s*([^,)=;]+)([\s\S]*)$/); - if (match === null || match.length < 2) { - return false; - } - return match[1].includes(NumberCategory.number); - } - - private isFieldDefineAlreadyWithNumberType(sourceCode: string): boolean { - let match = sourceCode.match(/^([^=;]+:[^=;]+)([\s\S]*)$/); - if (match === null || match.length < 2) { - return false; + return this.generateRuleFixForLocalDefine(sourceFile, warnInfo, NumberCategory.int); } - return match[1].includes(NumberCategory.number); + return this.generateRuleFixForLocalDefine(sourceFile, warnInfo, NumberCategory.number); } } diff --git a/ets2panda/linter/rule-config.json b/ets2panda/linter/rule-config.json index e2bf187f7622f58703b9a379f52bcf27756fd9ba..ac4230ae61513dfbe0ef105fa68d3d66f6a89ab4 100644 --- a/ets2panda/linter/rule-config.json +++ b/ets2panda/linter/rule-config.json @@ -34,7 +34,6 @@ "arkts-no-debugger", "arkts-no-arguments-obj", "arkts-no-tagged-templates", - "arkts-array-index-expr-type", "arkts-switch-expr", "arkts-case-expr", "arkts-array-index-negative", @@ -143,7 +142,10 @@ "arkui-buildernode-no-nestingbuildersupported", "arkui-sdk-common-deprecated-api", "arkui-sdk-common-whitelist-api", - "arkui-sdk-common-behaviorchange-api" + "arkui-sdk-common-behaviorchange-api", + "arkui-persistent-prop-serialization", + "arkui-persistent-props-serialization", + "arkui-persistencev2-connect-serialization" ], "builtin": [ "arkts-builtin-thisArgs", diff --git a/ets2panda/linter/src/lib/CookBookMsg.ts b/ets2panda/linter/src/lib/CookBookMsg.ts index eaeedd2ab284a5cb24da950e4bf78e5c6ee9d16a..0536594a90edf1c2348a8ae1e0525b76c171fa41 100644 --- a/ets2panda/linter/src/lib/CookBookMsg.ts +++ b/ets2panda/linter/src/lib/CookBookMsg.ts @@ -415,6 +415,12 @@ cookBookTag[381] = 'The code block passed to stateStyles needs to be an arrow function (arkui-statestyles-block-need-arrow-func)'; cookBookTag[382] = 'Promiseconstructor only supports using resolve (undefined) (arkts-promise-with-void-type-need-undefined-as-resolve-arg)'; +cookBookTag[391] = + 'The class of the second parameter passed to the "persistProp" method must be a primitive type or Date type, or implement the "toJson" and "fromJson" methods (arkui-persistent-prop-serialization)'; +cookBookTag[392] = + 'The class of the "defaultValue" parameter in the literal passed to the "persistProps" method must be a primitive type or Date type, or implement the "toJson" and "fromJson" methods (arkui-persistent-props-serialization)'; +cookBookTag[393] = + 'When calling the "globalConnect" and "connect" methods, the parameter list of the methods needs to include "toJson" and "fromJson" (arkui-persistencev2-connect-serialization)'; cookBookTag[399] = 'ArkUI deprecated api check (arkui-no-deprecated-api)'; cookBookTag[400] = 'ArkUI sdk common deprecated api check (arkui-sdk-common-deprecated-api)'; cookBookTag[401] = 'ArkUI sdk common whitelist api check (arkui-sdk-common-whitelist-api)'; diff --git a/ets2panda/linter/src/lib/FaultAttrs.ts b/ets2panda/linter/src/lib/FaultAttrs.ts index 8a6fe17151ba2697323a744587e0c8528e7700ca..a3de17fb3f126dbcff91abe370cd94f3bfa42b00 100644 --- a/ets2panda/linter/src/lib/FaultAttrs.ts +++ b/ets2panda/linter/src/lib/FaultAttrs.ts @@ -292,6 +292,9 @@ faultsAttrs[FaultID.NondecimalBigint] = new FaultAttributes(377); faultsAttrs[FaultID.UnsupportOperator] = new FaultAttributes(378); faultsAttrs[FaultID.StateStylesBlockNeedArrowFunc] = new FaultAttributes(381); faultsAttrs[FaultID.PromiseVoidNeedResolveArg] = new FaultAttributes(382); +faultsAttrs[FaultID.PersistentPropNeedImplementMethod] = new FaultAttributes(391); +faultsAttrs[FaultID.PersistentPropsNeedImplementMethod] = new FaultAttributes(392); +faultsAttrs[FaultID.PersistenceV2ConnectNeedAddParam] = new FaultAttributes(393); faultsAttrs[FaultID.NoDeprecatedApi] = new FaultAttributes(399); faultsAttrs[FaultID.SdkCommonApiDeprecated] = new FaultAttributes(400); faultsAttrs[FaultID.SdkCommonApiWhiteList] = new FaultAttributes(401); diff --git a/ets2panda/linter/src/lib/FaultDesc.ts b/ets2panda/linter/src/lib/FaultDesc.ts index 66ca12b4baa37f2fcfff618ab5cf7d1edcb97728..f6cd393f2ca68fdcc7b129f549c784cd0fadf550 100644 --- a/ets2panda/linter/src/lib/FaultDesc.ts +++ b/ets2panda/linter/src/lib/FaultDesc.ts @@ -286,3 +286,6 @@ faultDesc[FaultID.NoESObjectSupport] = 'ESObject type cannot be used'; faultDesc[FaultID.SdkCommonApiDeprecated] = 'ArkUI sdk common deprecated api check'; faultDesc[FaultID.SdkCommonApiWhiteList] = 'ArkUI sdk common whiteList api check'; faultDesc[FaultID.SdkCommonApiBehaviorChange] = 'ArkUI sdk common behavior change api check'; +faultDesc[FaultID.PersistentPropNeedImplementMethod] = 'Serialization needs class to implement the specific methods'; +faultDesc[FaultID.PersistentPropsNeedImplementMethod] = 'Serialization needs class to implement the specific methods'; +faultDesc[FaultID.PersistenceV2ConnectNeedAddParam] = 'Serialization needs class to implement the specific methods'; diff --git a/ets2panda/linter/src/lib/Problems.ts b/ets2panda/linter/src/lib/Problems.ts index 0c2e3338051df3e0eafc13bd6390cd43fc06db24..60422d36c586b99d79146f09aa26b2d5d53a6515 100644 --- a/ets2panda/linter/src/lib/Problems.ts +++ b/ets2panda/linter/src/lib/Problems.ts @@ -285,6 +285,9 @@ export enum FaultID { SdkCommonApiDeprecated, SdkCommonApiWhiteList, SdkCommonApiBehaviorChange, + PersistentPropNeedImplementMethod, + PersistentPropsNeedImplementMethod, + PersistenceV2ConnectNeedAddParam, // this should always be last enum LAST_ID } diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 3b7a5a6a4e2315ee296a9fa5d53c907eb0ca2421..5e7a8387f8fb3cf67c95614c17de1089929f0fd8 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -122,7 +122,15 @@ import { VIRTUAL_SCROLL_IDENTIFIER, BUILDERNODE_D_TS, BuilderNodeFunctionName, - NESTING_BUILDER_SUPPORTED + NESTING_BUILDER_SUPPORTED, + COMMON_TS_ETS_API_D_TS, + UI_STATE_MANAGEMENT_D_TS, + PERSIST_PROP_FUNC_NAME, + PERSIST_PROPS_FUNC_NAME, + GLOBAL_CONNECT_FUNC_NAME, + CONNECT_FUNC_NAME, + serializationTypeFlags, + serializationTypeName } from './utils/consts/ArkuiConstants'; import { arkuiImportList } from './utils/consts/ArkuiImportList'; import type { IdentifierAndArguments, ForbidenAPICheckResult } from './utils/consts/InteropAPI'; @@ -4237,7 +4245,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { if (!this.isTypeSameOrWider(baseParamType, derivedParamType)) { this.incrementCounters(derivedParams[i], FaultID.MethodInheritRule); - } + } } } @@ -4367,7 +4375,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } if (this.checkTypeInheritance(derivedType, baseType, false)) { - return true; + return true; } const baseTypeSet = new Set(this.flattenUnionTypes(baseType)); @@ -4403,7 +4411,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } } - if(this.checkTypeInheritance(fromType, toType)) { + if (this.checkTypeInheritance(fromType, toType)) { return true; } @@ -4418,11 +4426,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { }); } - private checkTypeInheritance( - sourceType: ts.Type, - targetType: ts.Type, - isSouceTotaqrget: boolean = true - ): boolean { + private checkTypeInheritance(sourceType: ts.Type, targetType: ts.Type, isSouceTotaqrget: boolean = true): boolean { // Early return if either type lacks symbol information if (!sourceType.symbol || !targetType.symbol) { return false; @@ -4434,7 +4438,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { // Get inheritance chain and check for relationship const inheritanceChain = this.getTypeInheritanceChain(typeToGetChain); - return inheritanceChain.some(t => { + return inheritanceChain.some((t) => { return t.symbol === typeToCheck.symbol; }); } @@ -4444,12 +4448,14 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { const declarations = type.symbol?.getDeclarations() || []; for (const declaration of declarations) { - if ((!ts.isClassDeclaration(declaration) && !ts.isInterfaceDeclaration(declaration)) || - !declaration.heritageClauses) { + if ( + !ts.isClassDeclaration(declaration) && !ts.isInterfaceDeclaration(declaration) || + !declaration.heritageClauses + ) { continue; } - const heritageClauses = declaration.heritageClauses.filter(clause => { + const heritageClauses = declaration.heritageClauses.filter((clause) => { return clause.token === ts.SyntaxKind.ExtendsKeyword || clause.token === ts.SyntaxKind.ImplementsKeyword; }); @@ -5493,52 +5499,146 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { } } - private handleCallExpression(node: ts.Node): void { - const tsCallExpr = node as ts.CallExpression; - this.checkSdkAbilityLifecycleMonitor(tsCallExpr); - this.handleCallExpressionForUI(tsCallExpr); - this.handleBuiltinCtorCallSignature(tsCallExpr); - this.handleSdkConstructorIfaceForCallExpression(tsCallExpr); - if (this.options.arkts2 && tsCallExpr.typeArguments !== undefined) { - this.handleSdkPropertyAccessByIndex(tsCallExpr); + private handleCallExpression(callExpr: ts.CallExpression): void { + this.checkSdkAbilityLifecycleMonitor(callExpr); + this.handleCallExpressionForUI(callExpr); + this.handleBuiltinCtorCallSignature(callExpr); + this.handleSdkConstructorIfaceForCallExpression(callExpr); + if (this.options.arkts2 && callExpr.typeArguments !== undefined) { + this.handleSdkPropertyAccessByIndex(callExpr); } - const calleeSym = this.tsUtils.trueSymbolAtLocation(tsCallExpr.expression); - const callSignature = this.tsTypeChecker.getResolvedSignature(tsCallExpr); - this.handleImportCall(tsCallExpr); - this.handleRequireCall(tsCallExpr); + const calleeSym = this.tsUtils.trueSymbolAtLocation(callExpr.expression); + const callSignature = this.tsTypeChecker.getResolvedSignature(callExpr); + this.handleImportCall(callExpr); + this.handleRequireCall(callExpr); if (calleeSym !== undefined) { - this.processCalleeSym(calleeSym, tsCallExpr); + this.processCalleeSym(calleeSym, callExpr); } if (callSignature !== undefined) { if (!this.tsUtils.isLibrarySymbol(calleeSym)) { - this.handleStructIdentAndUndefinedInArgs(tsCallExpr, callSignature); - this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); + this.handleStructIdentAndUndefinedInArgs(callExpr, callSignature); + this.handleGenericCallWithNoTypeArgs(callExpr, callSignature); } else if (this.options.arkts2) { - this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); + this.handleGenericCallWithNoTypeArgs(callExpr, callSignature); } - this.handleNotsLikeSmartTypeOnCallExpression(tsCallExpr, callSignature); + this.handleNotsLikeSmartTypeOnCallExpression(callExpr, callSignature); } - this.handleInteropForCallExpression(tsCallExpr); - this.handleLibraryTypeCall(tsCallExpr); + this.handleInteropForCallExpression(callExpr); + this.handleLibraryTypeCall(callExpr); if ( - ts.isPropertyAccessExpression(tsCallExpr.expression) && - this.tsUtils.hasEsObjectType(tsCallExpr.expression.expression) + ts.isPropertyAccessExpression(callExpr.expression) && + this.tsUtils.hasEsObjectType(callExpr.expression.expression) ) { const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; - this.incrementCounters(node, faultId); + this.incrementCounters(callExpr, faultId); + } + this.handleLimitedVoidWithCall(callExpr); + this.fixJsImportCallExpression(callExpr); + this.handleInteropForCallJSExpression(callExpr, calleeSym, callSignature); + this.handleNoTsLikeFunctionCall(callExpr); + this.handleObjectLiteralInFunctionArgs(callExpr); + this.handleSdkGlobalApi(callExpr); + this.handleObjectLiteralAssignmentToClass(callExpr); + this.checkRestrictedAPICall(callExpr); + this.handleNoDeprecatedApi(callExpr); + this.handleFunctionReturnThisCall(callExpr); + this.handlePromiseTupleGeneric(callExpr); + this.checkArgumentTypeOfCallExpr(callExpr, callSignature); + this.handleTupleGeneric(callExpr); + } + + private checkArgumentTypeOfCallExpr(callExpr: ts.CallExpression, signature: ts.Signature | undefined): void { + if (!this.options.arkts2) { + return; + } + if (!signature) { + return; + } + + const args = callExpr.arguments; + if (args.length === 0) { + return; + } + + for (const [idx, arg] of args.entries()) { + this.isArgumentAndParameterMatch(signature, arg, idx); + } + } + + private isArgumentAndParameterMatch(signature: ts.Signature, arg: ts.Expression, idx: number): void { + if (!ts.isPropertyAccessExpression(arg)) { + return; + } + + let rootObject = arg.expression; + + while (ts.isPropertyAccessExpression(rootObject)) { + rootObject = rootObject.expression; + } + + if (rootObject.kind !== ts.SyntaxKind.ThisKeyword) { + return; + } + + const param = signature.parameters.at(idx); + if (!param) { + return; + } + const paramDecl = param.getDeclarations(); + if (!paramDecl || paramDecl.length === 0) { + return; + } + + const paramFirstDecl = paramDecl[0]; + if (!ts.isParameter(paramFirstDecl)) { + return; + } + + const paramTypeNode = paramFirstDecl.type; + const argumentType = this.tsTypeChecker.getTypeAtLocation(arg); + if (!paramTypeNode) { + return; + } + + if (!paramTypeNode) { + return; + } + + const argumentTypeString = this.tsTypeChecker.typeToString(argumentType); + if (ts.isUnionTypeNode(paramTypeNode)) { + this.checkUnionTypesMatching(arg, paramTypeNode, argumentTypeString); + } else { + this.checkSingleTypeMatching(paramTypeNode, arg, argumentTypeString); + } + } + + private checkSingleTypeMatching(paramTypeNode: ts.TypeNode, arg: ts.Node, argumentTypeString: string): void { + const paramType = this.tsTypeChecker.getTypeFromTypeNode(paramTypeNode); + const paramTypeString = this.tsTypeChecker.typeToString(paramType); + if (TsUtils.isIgnoredTypeForParameterType(paramTypeString, paramType)) { + return; + } + + if (argumentTypeString !== paramTypeString) { + this.incrementCounters(arg, FaultID.StructuralIdentity); + } + } + + private checkUnionTypesMatching(arg: ts.Node, paramTypeNode: ts.UnionTypeNode, argumentTypeString: string): void { + let notMatching = true; + for (const type of paramTypeNode.types) { + const paramType = this.tsTypeChecker.getTypeFromTypeNode(type); + const paramTypeString = this.tsTypeChecker.typeToString(paramType); + notMatching = !TsUtils.isIgnoredTypeForParameterType(paramTypeString, paramType); + + if (argumentTypeString === paramTypeString) { + notMatching = false; + } + } + + if (notMatching) { + this.incrementCounters(arg, FaultID.StructuralIdentity); } - this.handleLimitedVoidWithCall(tsCallExpr); - this.fixJsImportCallExpression(tsCallExpr); - this.handleInteropForCallJSExpression(tsCallExpr, calleeSym, callSignature); - this.handleNoTsLikeFunctionCall(tsCallExpr); - this.handleObjectLiteralInFunctionArgs(tsCallExpr); - this.handleSdkGlobalApi(tsCallExpr); - this.handleObjectLiteralAssignmentToClass(tsCallExpr); - this.checkRestrictedAPICall(tsCallExpr); - this.handleNoDeprecatedApi(tsCallExpr); - this.handleFunctionReturnThisCall(tsCallExpr); - this.handlePromiseTupleGeneric(tsCallExpr); - this.handleTupleGeneric(tsCallExpr); } private handleTupleGeneric(callExpr: ts.CallExpression): void { @@ -5575,6 +5675,7 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { this.handleStateStyles(node); this.handleCallExpressionForRepeat(node); this.handleNodeForWrappedBuilder(node); + this.handleCallExpressionForSerialization(node); } handleNoTsLikeFunctionCall(callExpr: ts.CallExpression): void { @@ -14581,4 +14682,122 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { node.right.text === '0' ); } + + private handleCallExpressionForSerialization(node: ts.CallExpression): void { + if (!this.options.arkts2) { + return; + } + + const propertyAccess = node.expression; + if (!ts.isPropertyAccessExpression(propertyAccess)) { + return; + } + + const persistentClass = propertyAccess.expression; + if (!ts.isIdentifier(persistentClass)) { + return; + } + + switch (persistentClass.getText()) { + case StorageTypeName.PersistentStorage: + if (this.isDeclInTargetFile(persistentClass, COMMON_TS_ETS_API_D_TS)) { + this.handleCallExpressionForPersistentStorage(node, propertyAccess); + } + break; + case StorageTypeName.PersistenceV2: + if (this.isDeclInTargetFile(persistentClass, UI_STATE_MANAGEMENT_D_TS)) { + this.handleCallExpressionForPersistenceV2(node, propertyAccess); + } + break; + default: + } + } + + private handleCallExpressionForPersistentStorage( + callExpr: ts.CallExpression, + propertyAccess: ts.PropertyAccessExpression + ): void { + const funcName = propertyAccess.name.getText(); + + switch (funcName) { + case PERSIST_PROP_FUNC_NAME: + if (!this.checkPersistPropForSerialization(callExpr)) { + this.incrementCounters(callExpr, FaultID.PersistentPropNeedImplementMethod); + } + break; + case PERSIST_PROPS_FUNC_NAME: + if (!this.checkPersistPropsForSerialization(callExpr)) { + this.incrementCounters(callExpr, FaultID.PersistentPropsNeedImplementMethod); + } + break; + default: + } + } + + private checkPersistPropForSerialization(callExpr: ts.CallExpression): boolean { + const arg = callExpr.arguments?.[1]; + return !arg || this.checkArgumentForSerialization(arg); + } + + private checkPersistPropsForSerialization(callExpr: ts.CallExpression): boolean { + const arg = callExpr.arguments?.[0]; + if (!arg || !ts.isArrayLiteralExpression(arg)) { + return true; + } + + const literals = arg.elements; + let serializable: boolean = true; + for (const literal of literals) { + if (!ts.isObjectLiteralExpression(literal)) { + continue; + } + const property = literal.properties?.[1]; + if (!property || !ts.isPropertyAssignment(property)) { + continue; + } + if (!this.checkArgumentForSerialization(property.initializer)) { + serializable = false; + break; + } + } + + return serializable; + } + + private checkArgumentForSerialization(arg: ts.Node): boolean { + const type = this.tsTypeChecker.getTypeAtLocation(arg); + + if (type.isUnion()) { + if ( + type.types.some((type) => { + return !this.isSpecificTypeOfSerialization(type); + }) + ) { + return false; + } + return true; + } + + return this.isSpecificTypeOfSerialization(type); + } + + private isSpecificTypeOfSerialization(type: ts.Type): boolean { + const typeName = this.tsTypeChecker.typeToString(type); + return serializationTypeFlags.has(type.flags) || serializationTypeName.has(typeName); + } + + private handleCallExpressionForPersistenceV2( + callExpr: ts.CallExpression, + propertyAccess: ts.PropertyAccessExpression + ): void { + const funcName = propertyAccess.name.getText(); + if (funcName !== GLOBAL_CONNECT_FUNC_NAME && funcName !== CONNECT_FUNC_NAME) { + return; + } + + const errorMsg = + `When calling the "${funcName}" method, the parameter list of the methods needs to include ` + + '"toJson" and "fromJson" (arkui-persistencev2-connect-serialization)'; + this.incrementCounters(callExpr, FaultID.PersistenceV2ConnectNeedAddParam, undefined, errorMsg); + } } diff --git a/ets2panda/linter/src/lib/autofixes/Autofixer.ts b/ets2panda/linter/src/lib/autofixes/Autofixer.ts index c321277ede6d057cefdb8d548a49814a3926a8d6..91ff39cad5048eb004ed3b31e8dd8177e508178d 100644 --- a/ets2panda/linter/src/lib/autofixes/Autofixer.ts +++ b/ets2panda/linter/src/lib/autofixes/Autofixer.ts @@ -42,7 +42,8 @@ import { PROVIDE_ALLOW_OVERRIDE_PROPERTY_NAME, NEW_PROP_DECORATOR_SUFFIX, VIRTUAL_SCROLL_IDENTIFIER, - DISABLE_VIRTUAL_SCROLL_IDENTIFIER + DISABLE_VIRTUAL_SCROLL_IDENTIFIER, + USE_STATIC_STATEMENT } from '../utils/consts/ArkuiConstants'; import { ES_VALUE } from '../utils/consts/ESObject'; import type { IncrementDecrementNodeInfo } from '../utils/consts/InteropAPI'; @@ -3693,7 +3694,7 @@ export class Autofixer { fixInterfaceImport( interfacesNeedToImport: Set, interfacesAlreadyImported: Set, - sourceFile: ts.SourceFile + file: ts.SourceFile ): Autofix[] { const importSpecifiers: ts.ImportSpecifier[] = []; interfacesNeedToImport.forEach((interfaceName) => { @@ -3710,25 +3711,33 @@ export class Autofixer { undefined ); - const leadingComments = ts.getLeadingCommentRanges(sourceFile.getFullText(), 0); + const leadingComments = ts.getLeadingCommentRanges(file.getFullText(), 0); let annotationEndLine = 0; let annotationEndPos = 0; if (leadingComments && leadingComments.length > 0) { annotationEndPos = leadingComments[leadingComments.length - 1].end; - annotationEndLine = sourceFile.getLineAndCharacterOfPosition(annotationEndPos).line; + annotationEndLine = file.getLineAndCharacterOfPosition(annotationEndPos).line; } + const stmt = file?.statements[0]; + const isUseStaticAtStart = stmt ? Autofixer.checkUseStaticAtStart(stmt) : false; + annotationEndLine = isUseStaticAtStart ? file.getLineAndCharacterOfPosition(stmt.end).line : annotationEndLine; + annotationEndPos = isUseStaticAtStart ? stmt.end : annotationEndPos; + let text = Autofixer.formatImportStatement( - this.printer.printNode(ts.EmitHint.Unspecified, importDeclaration, sourceFile) + this.printer.printNode(ts.EmitHint.Unspecified, importDeclaration, file) ); if (annotationEndPos !== 0) { - text = this.getNewLine() + this.getNewLine() + text; + text = this.getNewLine() + (isUseStaticAtStart ? '' : this.getNewLine()) + text; } - const codeStartLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.getStart()).line; + const codeStartLine = isUseStaticAtStart ? + annotationEndLine + 1 : + file.getLineAndCharacterOfPosition(file.getStart()).line; for (let i = 2; i > codeStartLine - annotationEndLine; i--) { text = text + this.getNewLine(); } + return [{ start: annotationEndPos, end: annotationEndPos, replacementText: text }]; } @@ -3750,6 +3759,10 @@ export class Autofixer { }); } + private static checkUseStaticAtStart(stmt: ts.Statement): boolean { + return stmt.getText().trim().replace(/^'|'$/g, '').endsWith(USE_STATIC_STATEMENT); + } + fixStylesDecoratorGlobal( funcDecl: ts.FunctionDeclaration, calls: ts.Identifier[], diff --git a/ets2panda/linter/src/lib/utils/TsUtils.ts b/ets2panda/linter/src/lib/utils/TsUtils.ts index fbc408c87e9af7c1444b9687e3de92829d240512..1eea1826ef462274b8813ba8b2dd1429d51b203b 100644 --- a/ets2panda/linter/src/lib/utils/TsUtils.ts +++ b/ets2panda/linter/src/lib/utils/TsUtils.ts @@ -48,6 +48,7 @@ import { ETS_MODULE, PATH_SEPARATOR, VALID_OHM_COMPONENTS_MODULE_PATH } from './ import { EXTNAME_ETS, EXTNAME_JS, EXTNAME_D_ETS } from './consts/ExtensionName'; import { CONCAT_ARRAY, STRING_ERROR_LITERAL } from './consts/Literals'; import { INT_MIN, INT_MAX } from './consts/NumericalConstants'; +import { IGNORE_TYPE_LIST } from './consts/TypesToBeIgnored'; export const PROMISE_METHODS = new Set(['all', 'race', 'any', 'resolve', 'allSettled']); export const PROMISE_METHODS_WITH_NO_TUPLE_SUPPORT = new Set(['all', 'race', 'any', 'allSettled']); @@ -3917,4 +3918,16 @@ export class TsUtils { return (typeArguments[0].flags & ts.TypeFlags.Number) !== 0; } + + static isIgnoredTypeForParameterType(typeString: string, type: ts.Type): boolean { + if (TsUtils.isAnyType(type)) { + return true; + } + + return ( + IGNORE_TYPE_LIST.findIndex((ignored_type) => { + return typeString.includes(ignored_type); + }) !== -1 + ); + } } diff --git a/ets2panda/linter/src/lib/utils/consts/ArkuiConstants.ts b/ets2panda/linter/src/lib/utils/consts/ArkuiConstants.ts index 40063205dac9d0f9b4ed214aec4b8922450b3a67..5b17c7dad52474dde94fcbec64d87153541edd6d 100644 --- a/ets2panda/linter/src/lib/utils/consts/ArkuiConstants.ts +++ b/ets2panda/linter/src/lib/utils/consts/ArkuiConstants.ts @@ -13,6 +13,8 @@ * limitations under the License. */ +import * as ts from 'typescript'; + export const DOUBLE_DOLLAR_IDENTIFIER = '$$'; export const THIS_IDENTIFIER = 'this'; export const ATTRIBUTE_SUFFIX = 'Attribute'; @@ -42,7 +44,9 @@ export enum CustomInterfaceName { export enum StorageTypeName { LocalStorage = 'LocalStorage', - AppStorage = 'AppStorage' + AppStorage = 'AppStorage', + PersistentStorage = 'PersistentStorage', + PersistenceV2 = 'PersistenceV2' } export enum PropDecoratorName { @@ -85,6 +89,74 @@ export const skipImportDecoratorName: Set = new Set([ 'LocalStorageProp' ]); +export const serializationTypeFlags: Set = new Set([ + ts.TypeFlags.String, + ts.TypeFlags.Number, + ts.TypeFlags.Boolean, + ts.TypeFlags.StringLiteral, + ts.TypeFlags.NumberLiteral, + ts.TypeFlags.BooleanLiteral, + ts.TypeFlags.Undefined, + ts.TypeFlags.Null +]); + +export const serializationTypeName: Set = new Set([ + 'Date', + 'number', + 'boolean', + 'string', + 'undefined', + 'null', + 'Date[]', + 'number[]', + 'boolean[]', + 'string[]', + 'undefined[]', + 'null[]', + 'Set', + 'Set', + 'Set', + 'Set', + 'Set', + 'Set', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map', + 'Map' +]); + export const customLayoutFunctionName: Set = new Set(['onMeasureSize', 'onPlaceChildren']); export const ENTRY_DECORATOR_NAME = 'Entry'; @@ -104,3 +176,12 @@ export const STATE_MANAGEMENT_MODULE = '@ohos.arkui.StateManagement'; export const BUILDERNODE_D_TS = 'BuilderNode.d.ts'; export const NESTING_BUILDER_SUPPORTED = 'nestingBuilderSupported'; + +export const COMMON_TS_ETS_API_D_TS = 'common_ts_ets_api.d.ts'; +export const UI_STATE_MANAGEMENT_D_TS = '@ohos.arkui.StateManagement.d.ts'; +export const PERSIST_PROP_FUNC_NAME = 'persistProp'; +export const PERSIST_PROPS_FUNC_NAME = 'persistProps'; +export const GLOBAL_CONNECT_FUNC_NAME = 'globalConnect'; +export const CONNECT_FUNC_NAME = 'connect'; + +export const USE_STATIC_STATEMENT = 'use static'; diff --git a/ets2panda/linter/src/lib/utils/consts/TypesToBeIgnored.ts b/ets2panda/linter/src/lib/utils/consts/TypesToBeIgnored.ts new file mode 100644 index 0000000000000000000000000000000000000000..06b56ad53c8ca0351745034ce98cf18c73b1e32d --- /dev/null +++ b/ets2panda/linter/src/lib/utils/consts/TypesToBeIgnored.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export const IGNORE_TYPE_LIST: string[] = ['any[]', 'Promise', 'never', 'never[]', '{}', 'undefined[]']; diff --git a/ets2panda/linter/test/main/call_expression_matching_argument.ets b/ets2panda/linter/test/main/call_expression_matching_argument.ets new file mode 100644 index 0000000000000000000000000000000000000000..08c34d7682b9a6c397ff28078f1fc35f2f4c584b --- /dev/null +++ b/ets2panda/linter/test/main/call_expression_matching_argument.ets @@ -0,0 +1,70 @@ +/* + * 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. + */ + +interface A { + value: string; +} + +interface Outer { + inner: A +} + +interface No { + inner: string +} + +class Test { + sv1: number = 1 + sv2: string = "some Value"; + sv3: boolean = true; + sv4: A = { value: "something" } + sv5: Outer = { inner: sv4 } + sv6: No = { inner: sv2 } + sv7: Array = [sv2, sv2, sv2]; + + someMethod() { + func(this.sv2) //error + func(this.sv4) //valid + func(this.sv6.inner) // error + func(this.sv5.inner) //valid + func2(this.sv2) //valid + func2(this.sv4) //valid + func3(this.sv2) //valid + func4(this.sv2, this.sv1) //valid + func5(this.sv7) //valid + } +} + + +function func(param: A) { + console.log(A); +} + +function func2(param: A | any) { + console.log(param); +} + +function func3(param: string | Promise) { + console.log(param); +} + +function func4(param: string, param2: any[]) { + console.log(param, ...param2); +} + +function func5(param: Array) { + param.forEach(elem => console.log(elem)) +} + diff --git a/ets2panda/linter/test/main/call_expression_matching_argument.ets.args.json b/ets2panda/linter/test/main/call_expression_matching_argument.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..bc4d2071daf6e9354e711c3b74b6be2b56659066 --- /dev/null +++ b/ets2panda/linter/test/main/call_expression_matching_argument.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": "" + } +} diff --git a/ets2panda/linter/test/main/call_expression_matching_argument.ets.arkts2.json b/ets2panda/linter/test/main/call_expression_matching_argument.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..00147aa39226a33dc4ae27c875ec669c70be8045 --- /dev/null +++ b/ets2panda/linter/test/main/call_expression_matching_argument.ets.arkts2.json @@ -0,0 +1,58 @@ +{ + "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": 38, + "column": 14, + "endLine": 38, + "endColumn": 22, + "problem": "StructuralIdentity", + "suggest": "", + "rule": "Structural typing is not supported (arkts-no-structural-typing)", + "severity": "ERROR" + }, + { + "line": 40, + "column": 14, + "endLine": 40, + "endColumn": 28, + "problem": "StructuralIdentity", + "suggest": "", + "rule": "Structural typing is not supported (arkts-no-structural-typing)", + "severity": "ERROR" + }, + { + "line": 55, + "column": 27, + "endLine": 55, + "endColumn": 30, + "problem": "AnyType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)", + "severity": "ERROR" + }, + { + "line": 63, + "column": 39, + "endLine": 63, + "endColumn": 42, + "problem": "AnyType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/call_expression_matching_argument.ets.json b/ets2panda/linter/test/main/call_expression_matching_argument.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..f864fd1cc3f376222369e712dbcee0ed60b91641 --- /dev/null +++ b/ets2panda/linter/test/main/call_expression_matching_argument.ets.json @@ -0,0 +1,38 @@ +{ + "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": 55, + "column": 27, + "endLine": 55, + "endColumn": 30, + "problem": "AnyType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)", + "severity": "ERROR" + }, + { + "line": 63, + "column": 39, + "endLine": 63, + "endColumn": 42, + "problem": "AnyType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets b/ets2panda/linter/test/main/interface_import_6.ets new file mode 100644 index 0000000000000000000000000000000000000000..881ce6f547293ab3c1f848db7110a638a395f6b5 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets @@ -0,0 +1,24 @@ +/* + * 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. + */ + +'use static' +import { Component } from '@kit.ArkUI'; + +@Entry +@Component +struct Index { + build() { + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.args.json b/ets2panda/linter/test/main/interface_import_6.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..ef3938e967322a0c7551d84c7b6d280de94144c8 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets.args.json @@ -0,0 +1,21 @@ +{ + "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": "", + "autofix": "--arkts-2", + "migrate": "--arkts-2" + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.arkts2.json b/ets2panda/linter/test/main/interface_import_6.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..f1ac486b52d8c95fb776d58d1c2ece99587b9350 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets.arkts2.json @@ -0,0 +1,38 @@ +{ + "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": 1, + "endLine": 17, + "endColumn": 40, + "problem": "ImportAfterStatement", + "suggest": "", + "rule": "\"import\" statements after other statements are not allowed (arkts-no-misplaced-imports)", + "severity": "ERROR" + }, + { + "line": 19, + "column": 2, + "endLine": 19, + "endColumn": 7, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Entry\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.autofix.json b/ets2panda/linter/test/main/interface_import_6.ets.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..2b63a553ae7dc31a08d2a9ee40d58b06a45934a5 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets.autofix.json @@ -0,0 +1,49 @@ +{ + "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": 1, + "endLine": 17, + "endColumn": 40, + "problem": "ImportAfterStatement", + "suggest": "", + "rule": "\"import\" statements after other statements are not allowed (arkts-no-misplaced-imports)", + "severity": "ERROR" + }, + { + "line": 19, + "column": 2, + "endLine": 19, + "endColumn": 7, + "problem": "UIInterfaceImport", + "autofix": [ + { + "start": 617, + "end": 617, + "replacementText": "\nimport { Entry } from '@kit.ArkUI';\n", + "line": 19, + "column": 2, + "endLine": 19, + "endColumn": 7 + } + ], + "suggest": "", + "rule": "The ArkUI interface \"Entry\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.json b/ets2panda/linter/test/main/interface_import_6.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..0cdb08cc16ad1879ae83bcc575e2f6feb7e672d0 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.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": 17, + "column": 1, + "endLine": 17, + "endColumn": 40, + "problem": "ImportAfterStatement", + "suggest": "", + "rule": "\"import\" statements after other statements are not allowed (arkts-no-misplaced-imports)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.migrate.ets b/ets2panda/linter/test/main/interface_import_6.ets.migrate.ets new file mode 100644 index 0000000000000000000000000000000000000000..12599d4583df8e7fe2e9943190b127aace0c7dd7 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets.migrate.ets @@ -0,0 +1,26 @@ +/* + * 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. + */ + +'use static' +import { Entry } from '@kit.ArkUI'; + +import { Component } from '@kit.ArkUI'; + +@Entry +@Component +struct Index { + build() { + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/interface_import_6.ets.migrate.json b/ets2panda/linter/test/main/interface_import_6.ets.migrate.json new file mode 100644 index 0000000000000000000000000000000000000000..93115465bb027e2868e67d2c0ed6c9b73278a496 --- /dev/null +++ b/ets2panda/linter/test/main/interface_import_6.ets.migrate.json @@ -0,0 +1,38 @@ +{ + "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": 1, + "endLine": 17, + "endColumn": 36, + "problem": "ImportAfterStatement", + "suggest": "", + "rule": "\"import\" statements after other statements are not allowed (arkts-no-misplaced-imports)", + "severity": "ERROR" + }, + { + "line": 19, + "column": 1, + "endLine": 19, + "endColumn": 40, + "problem": "ImportAfterStatement", + "suggest": "", + "rule": "\"import\" statements after other statements are not allowed (arkts-no-misplaced-imports)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_1.ets b/ets2panda/linter/test/main/persist_serial_1.ets new file mode 100644 index 0000000000000000000000000000000000000000..72a51f4537e70825aac96c0813e56c554555ac68 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_1.ets @@ -0,0 +1,70 @@ +/* + * 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. + */ + +import { PersistentStorage } from './ui_modules/common_ts_ets_api'; + +class MyClassA {} + +function MyGet1() { + if (1 > 0) { + return 1 + } else { + return 2 + } +} + +function MyGet2() { + if (1 > 0) { + return 0 + } else { + return new MyClassA() + } +} + +const a1 = new MyClassA(); +const a2 = new MyClassA(); +const myArr1: Array = new Array(1, 2); +const mySet1: Set = new Set([1, 2, 3]); +const myMap1: Map = new Map([["jack", 3], ["tom", 2]]); +const myMap2: Map = new Map([[1, a1], [2, a2]]); + +PersistentStorage.persistProp('PropA', 47); +PersistentStorage.persistProp('PropA', true); +PersistentStorage.persistProp('PropA', "Jack"); +PersistentStorage.persistProp('PropA', null); +PersistentStorage.persistProp('PropA', undefined); +PersistentStorage.persistProp('PropA', new Date()); +PersistentStorage.persistProp('PropA', a1); // error +PersistentStorage.persistProp('PropA', new MyClassA()); // error +PersistentStorage.persistProp('PropA', MyGet1()); +PersistentStorage.persistProp('PropA', MyGet2()); // error +PersistentStorage.persistProp('PropA', 1 < 0 ? 1 : new MyClassA()); // error + +PersistentStorage.persistProp('PropA', new Array(1, 2)); +PersistentStorage.persistProp('PropA', new Array("jack", "tom")); +PersistentStorage.persistProp('PropA', new Array(myArr1)); // error +PersistentStorage.persistProp('PropA', new Array(new MyClassA())); // error +PersistentStorage.persistProp('PropA', new Array(a1)); // error + +PersistentStorage.persistProp('PropA', new Set([1, 2])); +PersistentStorage.persistProp('PropA', new Set(["jack", "tom"])); +PersistentStorage.persistProp('PropA', mySet1); +PersistentStorage.persistProp('PropA', new Set([new MyClassA()])); // error +PersistentStorage.persistProp('PropA', new Set([a1])); // error + +PersistentStorage.persistProp('PropA', new Map([["jack", 3], ["tom", 2]])); +PersistentStorage.persistProp('PropA', new Map([[1, a1], [2, a2]])); // error +PersistentStorage.persistProp('PropA', myMap1); +PersistentStorage.persistProp('PropA', myMap2); // error diff --git a/ets2panda/linter/test/main/persist_serial_1.ets.args.json b/ets2panda/linter/test/main/persist_serial_1.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..ec9992d92461d66e16b80975e33f95872c06af54 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_1.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/persist_serial_1.ets.arkts2.json b/ets2panda/linter/test/main/persist_serial_1.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..5a82da735eddf67b7d90486d5d305cda9d8052ae --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_1.ets.arkts2.json @@ -0,0 +1,278 @@ +{ + "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": 38, + "column": 31, + "endLine": 38, + "endColumn": 46, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 39, + "column": 29, + "endLine": 39, + "endColumn": 47, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 40, + "column": 37, + "endLine": 40, + "endColumn": 71, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 41, + "column": 39, + "endLine": 41, + "endColumn": 66, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 49, + "column": 1, + "endLine": 49, + "endColumn": 43, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 50, + "column": 1, + "endLine": 50, + "endColumn": 55, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 52, + "column": 1, + "endLine": 52, + "endColumn": 49, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 53, + "column": 1, + "endLine": 53, + "endColumn": 67, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 55, + "column": 40, + "endLine": 55, + "endColumn": 55, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 56, + "column": 40, + "endLine": 56, + "endColumn": 64, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 57, + "column": 1, + "endLine": 57, + "endColumn": 58, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 57, + "column": 40, + "endLine": 57, + "endColumn": 57, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 58, + "column": 1, + "endLine": 58, + "endColumn": 66, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 58, + "column": 40, + "endLine": 58, + "endColumn": 65, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 59, + "column": 1, + "endLine": 59, + "endColumn": 54, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 59, + "column": 40, + "endLine": 59, + "endColumn": 53, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 61, + "column": 40, + "endLine": 61, + "endColumn": 55, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 62, + "column": 40, + "endLine": 62, + "endColumn": 64, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 64, + "column": 1, + "endLine": 64, + "endColumn": 66, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 64, + "column": 40, + "endLine": 64, + "endColumn": 65, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 65, + "column": 1, + "endLine": 65, + "endColumn": 54, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 65, + "column": 40, + "endLine": 65, + "endColumn": 53, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 67, + "column": 40, + "endLine": 67, + "endColumn": 74, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 68, + "column": 1, + "endLine": 68, + "endColumn": 68, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + }, + { + "line": 68, + "column": 40, + "endLine": 68, + "endColumn": 67, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 70, + "column": 1, + "endLine": 70, + "endColumn": 47, + "problem": "PersistentPropNeedImplementMethod", + "suggest": "", + "rule": "The class of the second parameter passed to the \"persistProp\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-prop-serialization)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_1.ets.json b/ets2panda/linter/test/main/persist_serial_1.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..ca88f857e960b437dcf767c0ac40be998c8f1236 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_1.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 diff --git a/ets2panda/linter/test/main/persist_serial_2.ets b/ets2panda/linter/test/main/persist_serial_2.ets new file mode 100644 index 0000000000000000000000000000000000000000..5f0214c5673ff0dca84f14772571116851161114 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_2.ets @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import { PersistentStorage } from './ui_modules/common_ts_ets_api'; + +class MyClassA {} + +function MyGet1() { + if (1 > 0) { + return 1 + } else { + return 2 + } +} + +function MyGet2() { + if (1 > 0) { + return 0 + } else { + return new MyClassA() + } +} + +const a1 = new MyClassA(); +const a2 = new MyClassA(); +const myArr1: Array = new Array(1, 2); +const mySet1: Set = new Set([1, 2, 3]); +const myMap1: Map = new Map([["jack", 3], ["tom", 2]]); +const myMap2: Map = new Map([[1, a1], [2, a2]]); + +PersistentStorage.persistProps([{ key: 'highScore', defaultValue: '0' }, { key: 'wightScore', defaultValue: '1' }]); +PersistentStorage.persistProps([{ key: 'highScore', defaultValue: 0 }, { key: 'wightScore', defaultValue: 1 }]); +PersistentStorage.persistProps([{ key: 'highScore', defaultValue: false }, { key: 'wightScore', defaultValue: true }]); + +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new MyClassA() }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: 1 < 0 ? 1 : new MyClassA() }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: MyGet1() }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: MyGet2() }]); // error + +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Array(1, 2) }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Array("jack", "tom") }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Array(myArr1) }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Array(new MyClassA()) }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Array(a1) }]); // error + +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Set([1, 2]) }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Set(["jack", "tom"]) }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: mySet1 }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Set([new MyClassA()]) }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Set([a1]) }]); // error + +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Map([["jack", 3], ["tom", 2]]) }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: new Map([[1, a1], [2, a2]]) }]); // error +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: myMap1 }]); +PersistentStorage.persistProps([{ key: 'PropA', defaultValue: '0' }, { key: 'PropB', defaultValue: myMap2 }]); // error \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_2.ets.args.json b/ets2panda/linter/test/main/persist_serial_2.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..ec9992d92461d66e16b80975e33f95872c06af54 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_2.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/persist_serial_2.ets.arkts2.json b/ets2panda/linter/test/main/persist_serial_2.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..16144bf46db661eee43d885cd407b12c36a11353 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_2.ets.arkts2.json @@ -0,0 +1,268 @@ +{ + "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": 38, + "column": 31, + "endLine": 38, + "endColumn": 46, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 39, + "column": 29, + "endLine": 39, + "endColumn": 47, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 40, + "column": 37, + "endLine": 40, + "endColumn": 71, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 41, + "column": 39, + "endLine": 41, + "endColumn": 66, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 47, + "column": 1, + "endLine": 47, + "endColumn": 118, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 48, + "column": 1, + "endLine": 48, + "endColumn": 130, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 50, + "column": 1, + "endLine": 50, + "endColumn": 112, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 52, + "column": 100, + "endLine": 52, + "endColumn": 115, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 53, + "column": 100, + "endLine": 53, + "endColumn": 124, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 54, + "column": 1, + "endLine": 54, + "endColumn": 121, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 54, + "column": 100, + "endLine": 54, + "endColumn": 117, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 55, + "column": 1, + "endLine": 55, + "endColumn": 129, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 55, + "column": 100, + "endLine": 55, + "endColumn": 125, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 56, + "column": 1, + "endLine": 56, + "endColumn": 117, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 56, + "column": 100, + "endLine": 56, + "endColumn": 113, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 58, + "column": 100, + "endLine": 58, + "endColumn": 115, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 59, + "column": 100, + "endLine": 59, + "endColumn": 124, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 61, + "column": 1, + "endLine": 61, + "endColumn": 129, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 61, + "column": 100, + "endLine": 61, + "endColumn": 125, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 62, + "column": 1, + "endLine": 62, + "endColumn": 117, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 62, + "column": 100, + "endLine": 62, + "endColumn": 113, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 64, + "column": 100, + "endLine": 64, + "endColumn": 134, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 65, + "column": 1, + "endLine": 65, + "endColumn": 131, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + }, + { + "line": 65, + "column": 100, + "endLine": 65, + "endColumn": 127, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 67, + "column": 1, + "endLine": 67, + "endColumn": 110, + "problem": "PersistentPropsNeedImplementMethod", + "suggest": "", + "rule": "The class of the \"defaultValue\" parameter in the literal passed to the \"persistProps\" method must be a primitive type or Date type, or implement the \"toJson\" and \"fromJson\" methods (arkui-persistent-props-serialization)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_2.ets.json b/ets2panda/linter/test/main/persist_serial_2.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..ca88f857e960b437dcf767c0ac40be998c8f1236 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_2.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 diff --git a/ets2panda/linter/test/main/persist_serial_3.ets b/ets2panda/linter/test/main/persist_serial_3.ets new file mode 100644 index 0000000000000000000000000000000000000000..f314b869406c1ed70ab17236974b8cd2cbe9b32b --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_3.ets @@ -0,0 +1,49 @@ +/* + * 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. + */ + +import { ConnectOptions, PersistenceV2, Type } from './ui_modules/@kit.ArkUI'; +import { contextConstant } from '@kit.AbilityKit'; + +PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {}) + +@ObservedV2 +class SampleChild { + @Trace childId: number = 0; + groupId: number = 1; +} + +@ObservedV2 +class Sample { + @Type(SampleChild) + @Trace father: SampleChild = new SampleChild(); +} + +@Entry +@ComponentV2 +struct TestCase2 { + @Local p1: Sample | undefined = PersistenceV2.globalConnect({type: Sample, defaultCreator:() => new Sample()}); // error + + @Local p2: Sample | undefined = PersistenceV2.globalConnect({type: Sample, key: 'global1', defaultCreator:() => new Sample(), areaMode: contextConstant.AreaMode.EL1}); // error + + options: ConnectOptions = {type: Sample, key: 'global2', defaultCreator:() => new Sample()}; + @Local p3: Sample | undefined = PersistenceV2.globalConnect(this.options); // error + + @Local p4: Sample | undefined = PersistenceV2.globalConnect({type: Sample, key: 'global1', defaultCreator:() => new Sample(), areaMode: 3}); // error + + @Local p5: Sample | undefined = PersistenceV2.connect(Sample, () => new Sample()); // error + + build() { + } +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_3.ets.args.json b/ets2panda/linter/test/main/persist_serial_3.ets.args.json new file mode 100644 index 0000000000000000000000000000000000000000..ec9992d92461d66e16b80975e33f95872c06af54 --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_3.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/persist_serial_3.ets.arkts2.json b/ets2panda/linter/test/main/persist_serial_3.ets.arkts2.json new file mode 100644 index 0000000000000000000000000000000000000000..151971eb234021de3a50c2110398f41caac4855e --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_3.ets.arkts2.json @@ -0,0 +1,248 @@ +{ + "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": 16, + "column": 41, + "endLine": 16, + "endColumn": 45, + "problem": "InvalidIdentifier", + "suggest": "", + "rule": "This keyword cannot be used as identifiers (arkts-invalid-identifier)", + "severity": "ERROR" + }, + { + "line": 36, + "column": 35, + "endLine": 36, + "endColumn": 113, + "problem": "PersistenceV2ConnectNeedAddParam", + "suggest": "", + "rule": "When calling the \"globalConnect\" method, the parameter list of the methods needs to include \"toJson\" and \"fromJson\" (arkui-persistencev2-connect-serialization)", + "severity": "ERROR" + }, + { + "line": 36, + "column": 35, + "endLine": 36, + "endColumn": 113, + "problem": "LimitedVoidType", + "suggest": "", + "rule": "Type \"void\" has no instances.(arkts-limited-void-type)", + "severity": "ERROR" + }, + { + "line": 38, + "column": 35, + "endLine": 38, + "endColumn": 169, + "problem": "PersistenceV2ConnectNeedAddParam", + "suggest": "", + "rule": "When calling the \"globalConnect\" method, the parameter list of the methods needs to include \"toJson\" and \"fromJson\" (arkui-persistencev2-connect-serialization)", + "severity": "ERROR" + }, + { + "line": 38, + "column": 35, + "endLine": 38, + "endColumn": 169, + "problem": "LimitedVoidType", + "suggest": "", + "rule": "Type \"void\" has no instances.(arkts-limited-void-type)", + "severity": "ERROR" + }, + { + "line": 40, + "column": 44, + "endLine": 40, + "endColumn": 50, + "problem": "ClassAsObjectError", + "suggest": "", + "rule": "Classes cannot be used as objects (arkts-no-classes-as-obj)", + "severity": "ERROR" + }, + { + "line": 41, + "column": 35, + "endLine": 41, + "endColumn": 76, + "problem": "PersistenceV2ConnectNeedAddParam", + "suggest": "", + "rule": "When calling the \"globalConnect\" method, the parameter list of the methods needs to include \"toJson\" and \"fromJson\" (arkui-persistencev2-connect-serialization)", + "severity": "ERROR" + }, + { + "line": 41, + "column": 35, + "endLine": 41, + "endColumn": 76, + "problem": "LimitedVoidType", + "suggest": "", + "rule": "Type \"void\" has no instances.(arkts-limited-void-type)", + "severity": "ERROR" + }, + { + "line": 43, + "column": 35, + "endLine": 43, + "endColumn": 142, + "problem": "PersistenceV2ConnectNeedAddParam", + "suggest": "", + "rule": "When calling the \"globalConnect\" method, the parameter list of the methods needs to include \"toJson\" and \"fromJson\" (arkui-persistencev2-connect-serialization)", + "severity": "ERROR" + }, + { + "line": 43, + "column": 35, + "endLine": 43, + "endColumn": 142, + "problem": "LimitedVoidType", + "suggest": "", + "rule": "Type \"void\" has no instances.(arkts-limited-void-type)", + "severity": "ERROR" + }, + { + "line": 45, + "column": 35, + "endLine": 45, + "endColumn": 84, + "problem": "PersistenceV2ConnectNeedAddParam", + "suggest": "", + "rule": "When calling the \"connect\" method, the parameter list of the methods needs to include \"toJson\" and \"fromJson\" (arkui-persistencev2-connect-serialization)", + "severity": "ERROR" + }, + { + "line": 45, + "column": 35, + "endLine": 45, + "endColumn": 84, + "problem": "LimitedVoidType", + "suggest": "", + "rule": "Type \"void\" has no instances.(arkts-limited-void-type)", + "severity": "ERROR" + }, + { + "line": 21, + "column": 2, + "endLine": 21, + "endColumn": 12, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"ObservedV2\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 23, + "column": 4, + "endLine": 23, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Trace\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 27, + "column": 2, + "endLine": 27, + "endColumn": 12, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"ObservedV2\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 30, + "column": 4, + "endLine": 30, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Trace\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 33, + "column": 2, + "endLine": 33, + "endColumn": 7, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Entry\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 34, + "column": 2, + "endLine": 34, + "endColumn": 13, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"ComponentV2\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 36, + "column": 4, + "endLine": 36, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Local\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 38, + "column": 4, + "endLine": 38, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Local\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 41, + "column": 4, + "endLine": 41, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Local\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 43, + "column": 4, + "endLine": 43, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Local\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + }, + { + "line": 45, + "column": 4, + "endLine": 45, + "endColumn": 9, + "problem": "UIInterfaceImport", + "suggest": "", + "rule": "The ArkUI interface \"Local\" should be imported before it is used (arkui-modular-interface)", + "severity": "ERROR" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/persist_serial_3.ets.json b/ets2panda/linter/test/main/persist_serial_3.ets.json new file mode 100644 index 0000000000000000000000000000000000000000..097b66ff3c436ef1e2f9b8373703c5bb650894de --- /dev/null +++ b/ets2panda/linter/test/main/persist_serial_3.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": 40, + "column": 44, + "endLine": 40, + "endColumn": 50, + "problem": "ClassAsObject", + "suggest": "", + "rule": "Classes cannot be used as objects (arkts-no-classes-as-obj)", + "severity": "WARNING" + } + ] +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/ui_modules/@kit.ArkUI.d.ts b/ets2panda/linter/test/main/ui_modules/@kit.ArkUI.d.ts index cf71890e70f66a55e52e5cc7d90ca8f1207ca921..638b3a1404338d3dc8cd16d181fe002e024cbbce 100644 --- a/ets2panda/linter/test/main/ui_modules/@kit.ArkUI.d.ts +++ b/ets2panda/linter/test/main/ui_modules/@kit.ArkUI.d.ts @@ -14,4 +14,5 @@ */ import { BuilderNode } from './@ohos.arkui.node'; -export { BuilderNode }; \ No newline at end of file +import { PersistenceV2 } from './@ohos.arkui.StateManagement'; +export { BuilderNode, PersistenceV2 }; \ No newline at end of file diff --git a/ets2panda/linter/test/main/ui_modules/@ohos.arkui.StateManagement.d.ts b/ets2panda/linter/test/main/ui_modules/@ohos.arkui.StateManagement.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ba4dbfcb73b09a4e861b21440ed15c3af639ff7 --- /dev/null +++ b/ets2panda/linter/test/main/ui_modules/@ohos.arkui.StateManagement.d.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export declare class PersistenceV2 extends AppStorageV2{ + static globalConnect(): void; +} + +declare class AppStorageV2 { + static connect(): void; +} \ No newline at end of file diff --git a/ets2panda/linter/test/main/ui_modules/common_ts_ets_api.d.ts b/ets2panda/linter/test/main/ui_modules/common_ts_ets_api.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..2634f5bd9faa4449476f8844e42819c4eaaf1876 --- /dev/null +++ b/ets2panda/linter/test/main/ui_modules/common_ts_ets_api.d.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export declare class PersistentStorage { + static persistProp(): void; + static PersistProps(): void; +} \ No newline at end of file