diff --git a/arkui-plugins/tsconfig.json b/arkui-plugins/tsconfig.json index 5ea0297ec1a3b349662239293f9eed0f506dae0e..cac711d19651dac73cfa4e85a015741313f62c72 100644 --- a/arkui-plugins/tsconfig.json +++ b/arkui-plugins/tsconfig.json @@ -23,6 +23,7 @@ "./collectors/**/*.ts", "./common/**/*.ts", "./memo-plugins/**/*.ts", + "./ui-checker/**/*.ts", "./ui-plugins/**/*.ts", "./ui-syntax-plugins/**/*.ts", "./interop-plugins/**/*.ts", @@ -30,8 +31,5 @@ "./test/ut/**/*.ts", "./test/utils/**/*.ts" ], - "exclude": [ - "./test/demo/", - "./test/local/" - ] + "exclude": ["./test/demo/", "./test/local/"] } diff --git a/arkui-plugins/ui-checker/check-builder-param.ts b/arkui-plugins/ui-checker/check-builder-param.ts new file mode 100644 index 0000000000000000000000000000000000000000..b680fb9209bbbb947e1913f47db86ffe3c370145 --- /dev/null +++ b/arkui-plugins/ui-checker/check-builder-param.ts @@ -0,0 +1,64 @@ +import * as arkts from '@koalaui/libarkts'; +import { CallExpressionMetadata, isBuilderParamProperty, reportDiagnostic, ReportLevel } from './utils'; + +/** + * 校验规则:自定义组件尾随闭包调用的场景,struct 声明中只能有1个BuilderParam + * 校验等级:error + */ +export function checkBuilderParam(callExpression: arkts.CallExpression, metadata: CallExpressionMetadata) { + // 只处理自定义组件 CallExpression的场景 + if (!metadata.isCustomComponentCall) { + return; + } + + // 只处理尾随闭包调用场景 + if (!callExpression.isTrailingCall) { + return; + } + + // 获取struct 节点信息 + const structDeclaration = arkts.getPeerDecl(metadata.structPeer); + if ( + !structDeclaration || + !arkts.isClassDefinition(structDeclaration) || + !arkts.classDefinitionIsFromStructConst(structDeclaration) || + !structDeclaration.ident + ) { + return; + } + + // 从struct 中获取被@BuilderParam 修饰的属性个数 + const properties: Array<{ property: arkts.ClassProperty, argc: number }> = []; + try { + for (const member of structDeclaration.body) { + if (isBuilderParamProperty(member)) { + properties.push({ + property: member, + argc: getBuilderParamTypeArgc(member) + }) + } + } + } catch (error) { + // todo 补充@BuilderParam 修饰的不是函数类型需要报错。ArkTS1.1 不报错。 + } + + // 如果@BuilderParam个数超过1个或者@BuilderParam修饰的函数类型有参数,那么就报错 + if (properties.length > 1 || (properties.length === 1 && properties[0].argc > 0)) { + const structName = structDeclaration.ident.name; + reportDiagnostic({ + position: arkts.getStartPosition(structDeclaration), + level: ReportLevel.ERROR, + message: `In the trailing lambda case, '${structName}' must have one and only one property decorated with @BuilderParam, and its @BuilderParam expects no parameter.`, + }) + } +} + +function getBuilderParamTypeArgc(property: arkts.ClassProperty): number { + if ( + !property.typeAnnotation || + !arkts.isETSFunctionType(property.typeAnnotation) + ) { + throw new Error("The type of @BuilderParam property is not a function."); + } + return property.typeAnnotation.params.length; +} diff --git a/arkui-plugins/ui-checker/utils.ts b/arkui-plugins/ui-checker/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..482410461cdfdae226f90c82018d71adca6ad238 --- /dev/null +++ b/arkui-plugins/ui-checker/utils.ts @@ -0,0 +1,148 @@ +import * as arkts from '@koalaui/libarkts'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { ProjectConfig } from '../common/plugin-context'; + +export const enum ReportLevel { + ERROR = "error", + WARN = "warn", +} + +export interface DiagnosticOptions { + level: ReportLevel; + position: arkts.SourcePosition; + message: string; + args?: string[]; +} + +export interface SuggestionOptions { + code: string; + range: [start: arkts.SourcePosition, end: arkts.SourcePosition]; + message: string; + args?: string[]; +} + +export function reportDiagnostic(diagnostic: DiagnosticOptions, suggestion?: SuggestionOptions): void { + const diagnosticArgs = diagnostic.args ?? []; + const diagnosticType = convert(diagnostic.level); + const diagnosticKind = arkts.DiagnosticKind.create(diagnostic.message, diagnosticType); + if (!suggestion) { + arkts.Diagnostic.logDiagnostic(diagnosticKind, diagnostic.position, ...diagnosticArgs); + } else { + const suggestionArgs = suggestion.args ?? []; + const diagnosticInfo = arkts.DiagnosticInfo.create(diagnosticKind, ...diagnosticArgs); + const suggestionKind = arkts.DiagnosticKind.create(suggestion.message, arkts.PluginDiagnosticType.ES2PANDA_PLUGIN_SUGGESTION); + const suggestionInfo = arkts.SuggestionInfo.create(suggestionKind, suggestion.code, ...suggestionArgs); + const [startPosition, endPosition] = suggestion.range; + const suggestionRange = arkts.SourceRange.create(startPosition, endPosition); + arkts.Diagnostic.logDiagnosticWithSuggestion(diagnosticInfo, suggestionInfo, suggestionRange); + } +} + +const BASE_RESOURCE_PATH = 'src/main/resources/base'; +const ETS_MODULE_PATH = 'src/main/ets'; + +export function getMainPages(projectConfig: ProjectConfig): string[] { + const { moduleRootPath, aceModuleJsonPath } = projectConfig; + if (!aceModuleJsonPath) { + throw new Error('The aceModuleJsonPath config is empty.'); + } + const moduleConfig = readJSON<{ module: { pages: string } }>(aceModuleJsonPath); + if (!moduleConfig || !moduleConfig.module || !moduleConfig.module.pages) { + return []; + } + const pagesPath = moduleConfig.module.pages; + const matcher = /\$(?[_A-Za-z]+):(?[_A-Za-z]+)/.exec(pagesPath); + if (matcher && matcher.groups) { + const { directory, filename } = matcher.groups; + const mainPagesPath = path.resolve(moduleRootPath, BASE_RESOURCE_PATH, directory, `${filename}.json`); + const mainPages = readJSON<{ src: string[] }>(mainPagesPath); + if (!mainPages || !mainPages.src || !Array.isArray(mainPages.src)) { + return []; + } + return mainPages.src.map((page) => path.resolve(moduleRootPath, ETS_MODULE_PATH, `${page}.ets`)); + } else { + return []; + } +} + +export function getSourceFile(node: arkts.AstNode): string { + return arkts.getProgramFromAstNode(node).absName; +} + +export function readJSON(path: string): T | undefined { + if (!fs.existsSync(path)) { + return undefined; + } + const content = fs.readFileSync(path).toString(); + if (!content) { + return undefined; + } + return JSON.parse(content) as T; +} + +export function format(content: string, placeholders: object): string { + return Object.entries(placeholders).reduce((content, [placehoderName, placehoderValue]) => { + return content.replace(`{{${placehoderName}}}`, placehoderValue); + }, content); +} + +function convert(level: "error" | "warn" | undefined): arkts.PluginDiagnosticType { + return level === "error" ? arkts.PluginDiagnosticType.ES2PANDA_PLUGIN_ERROR : arkts.PluginDiagnosticType.ES2PANDA_PLUGIN_WARNING; +} + +export const PresetAnnotations = { + BUILDER_PARAM: "BuilderParam", + COMPONENT_V2: "ComponentV2", + COMPONENT_V1: "Component", + LINK: "link" +}; + +export function isBuilderParamProperty(node: arkts.AstNode): node is arkts.ClassProperty { + if (!arkts.isClassProperty(node)) { + return false; + } + return !!getAnnotationByName(node.annotations, PresetAnnotations.BUILDER_PARAM); +} + +export function hasAnnotation(annotations: readonly arkts.AnnotationUsage[], name: string): boolean { + return !!getAnnotationByName(annotations, name); +} + +export function getAnnotationByName(annotations: readonly arkts.AnnotationUsage[], name: string): arkts.AnnotationUsage | undefined { + return annotations.find((annotation: arkts.AnnotationUsage): boolean => { + if (getAnnotationName(annotation) !== name) { + return false; + } + const annotationDeclaration = arkts.getDecl(annotation.expr!); + if (!annotationDeclaration) { + return false; + } + return isArkUIModule(annotationDeclaration); + }); +} + +export function getAnnotationName(annotation: arkts.AnnotationUsage): string | undefined { + return annotation.expr && arkts.isIdentifier(annotation.expr) ? annotation.expr.name : undefined; +} + +const ARKUI_MODULE_PREFIXES: string[] = ['arkui.', '@ohos.arkui', '@kit.ArkUI']; + +export function isArkUIModule(node: arkts.AstNode): boolean { + const program = arkts.getProgramFromAstNode(node); + const moduleName = program.moduleName; + for (const modulePrefix of ARKUI_MODULE_PREFIXES) { + if (moduleName.startsWith(modulePrefix)) { + return true; + } + } + return false; +} + + +export interface CallExpressionMetadata { + isCustomComponentCall: boolean; + structPeer: any; + parentStructAnnotations: string[]; + structAnnotations: string[]; +} \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts index a914891f9a65984a9b399a34ac34dbb40f4000cf..f80b12db3bd24c2fb8510d0db2502f066415b641 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getIdentifierName, getAnnotationUsage, PresetDecorators, BUILD_NAME } from '../utils'; +import { getIdentifierName, PresetDecorators, BUILD_NAME, isStructClassDeclaration, getAnnotationUsageByName } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; const STATEMENT_LENGTH: number = 1; @@ -28,36 +28,42 @@ class BuildRootNodeRule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.AstNode): void { - if (!arkts.isStructDeclaration(node)) { + public checked(node: arkts.AstNode): void { + if (!isStructClassDeclaration(node)) { return; } - const entryDecoratorUsage = getAnnotationUsage(node, PresetDecorators.ENTRY); - node.definition.body.forEach((member) => { - if (!arkts.isMethodDefinition(member) || getIdentifierName(member.name) !== BUILD_NAME) { + const definition = node.definition!; + const entryAnnotation = getAnnotationUsageByName(definition.annotations, PresetDecorators.ENTRY); + definition.body.forEach((member) => { + if ( + !arkts.isMethodDefinition(member) || + member.kind !== arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD || + getIdentifierName(member.name) !== BUILD_NAME + ) { return; } - const blockStatement = member.scriptFunction.body; - const buildNode = member.scriptFunction.id; - if (!blockStatement || !arkts.isBlockStatement(blockStatement) || !buildNode) { + const scriptFunction = member.scriptFunction; + const scriptFunctionID = scriptFunction.id; + if (!scriptFunctionID) { + return; + } + const blockStatement = scriptFunction.body; + if (!blockStatement || !arkts.isBlockStatement(blockStatement)) { return; } const statements = blockStatement.statements; - let buildCount = 0; - // rule1: The 'build' method cannot have more than one root node. if (statements.length > STATEMENT_LENGTH) { - if (!this.isBuildOneRoot(statements, buildCount)) { + if (!this.isBuildOneRoot(statements, 0)) { this.report({ - node: buildNode, - message: entryDecoratorUsage ? this.messages.invalidEntryBuildRoot : this.messages.invalidBuildRoot, + node: scriptFunctionID, + message: entryAnnotation ? this.messages.invalidEntryBuildRoot : this.messages.invalidBuildRoot, }); } } - // rule2: its 'build' function can have only one root node, which must be a container component. - if (!statements.length || !entryDecoratorUsage) { + if (!statements.length || !entryAnnotation) { return; } - this.validateContainerInBuild(statements, buildNode); + this.validateContainerInBuild(statements, scriptFunctionID); }); } diff --git a/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts b/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts index e3d8f3446e02765464ef4d452751bcf75a36ce80..c03975ee30a6a0337f383ce7ea3ec4ec5f1cc662 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts @@ -16,9 +16,13 @@ import * as arkts from '@koalaui/libarkts'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; import { - forbiddenUseStateType, - getAnnotationUsage, getClassPropertyAnnotationNames, getClassPropertyName, - getClassPropertyType, PresetDecorators + getAnnotationUsageByName, + getClassPropertyName, + getClassPropertyType, + getIdentifierName, + isPresetAnnotation, + isStructClassDeclaration, + PresetDecorators } from '../utils'; const forbiddenUseStateTypeForDecorators: string[] = [ @@ -35,6 +39,32 @@ const forbiddenUseStateTypeForDecorators: string[] = [ PresetDecorators.LOCAL_STORAGE_LINK, ]; +export const forbiddenUseStateType: string[] = [ + 'Scroller', + 'SwiperScroller', + 'VideoController', + 'WebController', + 'CustomDialogController', + 'SwiperController', + 'TabsController', + 'CalendarController', + 'AbilityController', + 'XComponentController', + 'CanvasRenderingContext2D', + 'CanvasGradient', + 'ImageBitmap', + 'ImageData', + 'Path2D', + 'RenderingContextSettings', + 'OffscreenCanvasRenderingContext2D', + 'PatternLockController', + 'TextAreaController', + 'TextInputController', + 'TextTimerController', + 'SearchController', + 'RichEditorController', +]; + class CheckDecoratedPropertyTypeRule extends AbstractUISyntaxRule { public setup(): Record { return { @@ -42,51 +72,36 @@ class CheckDecoratedPropertyTypeRule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.StructDeclaration): void { - if (!arkts.isStructDeclaration(node)) { - return; - } - if (!node.definition) { + public checked(node: arkts.AstNode): void { + if (!isStructClassDeclaration(node) || !node.definition) { return; } - const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - if (!componentDecorator) { + const annotation = getAnnotationUsageByName(node.definition.annotations, PresetDecorators.COMPONENT_V1); + if (!annotation) { return; } node.definition.body.forEach(member => { - this.checkDecoratedPropertyType(member, forbiddenUseStateTypeForDecorators, forbiddenUseStateType); + if (!arkts.isClassProperty(member) || !member.key) { + return; + } + const propertyName = getClassPropertyName(member); + const propertyType = getClassPropertyType(member); + if (!propertyName || !propertyType) { + return; + } + const annotationNames = member.annotations + .filter(annotation => isPresetAnnotation(annotation)) + .map(annotation => getIdentifierName(annotation.expr!)); + const forbiddenAnnotationName = annotationNames.find(annotationName => forbiddenUseStateTypeForDecorators.includes(annotationName)); + if (forbiddenAnnotationName && forbiddenUseStateType.includes(propertyType)) { + this.report({ + node: member.key, + message: this.messages.invalidDecoratedPropertyType, + data: { decoratorName: forbiddenAnnotationName, propertyName, propertyType }, + }); + } }); } - - private checkDecoratedPropertyType( - member: arkts.AstNode, - annoList: string[], - typeList: string[] - ): void { - if (!arkts.isClassProperty(member)) { - return; - } - const propertyName = getClassPropertyName(member); - const propertyType = getClassPropertyType(member); - if (!propertyName || !propertyType) { - return; - } - const propertyAnnotationNames: string[] = getClassPropertyAnnotationNames(member); - const decoratorName: string | undefined = - propertyAnnotationNames.find((annotation) => annoList.includes(annotation)); - const isType: boolean = typeList.includes(propertyType); - if (!member.key) { - return; - } - const errorNode = member.key; - if (decoratorName && isType) { - this.report({ - node: errorNode, - message: this.messages.invalidDecoratedPropertyType, - data: { decoratorName, propertyName, propertyType }, - }); - } - } } export default CheckDecoratedPropertyTypeRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts b/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts index 05e71cf2a59e22892af3c0892fc632374f6c01e9..394626c9539bbe80d6cccc094523bfe975e01b74 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts @@ -20,6 +20,8 @@ import { getClassPropertyAnnotationNames, PresetDecorators, getAnnotationName, + isFromPresetModules, + isStructClassDeclaration, } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; @@ -46,11 +48,8 @@ const decoratorsFilter: string[] = [ PresetDecorators.LOCAL_STORAGE_LINK, PresetDecorators.LOCAL_STORAGE_PROP, PresetDecorators.BUILDER_PARAM, ]; +const COMPONENT_INSTANTIATE = "$_instantiate"; class ConstructParameterRule extends AbstractUISyntaxRule { - private propertyMap: Map> = new Map(); - private regularVariableList: string[] = []; - private builderFunctionList: string[] = []; - public setup(): Record { return { constructParameter: `The '{{initializer}}' property '{{initializerName}}' cannot be assigned to the '{{parameter}}' property '{{parameterName}}'.`, @@ -60,258 +59,63 @@ class ConstructParameterRule extends AbstractUISyntaxRule { } public beforeTransform(): void { - this.propertyMap = new Map(); - this.regularVariableList = []; - this.builderFunctionList = []; - } - public parsed(node: arkts.StructDeclaration): void { - this.initList(node); - this.initPropertyMap(node); - this.checkConstructParameter(node); } - private getPropertyAnnotationName(node: arkts.AstNode, propertyName: string): string { - while (!arkts.isStructDeclaration(node)) { - if (!node.parent) { - return ''; - } - node = node.parent; - } - let annotationNames: string[] = []; - node.definition.body.forEach((item) => { - if (arkts.isClassProperty(item) && getClassPropertyName(item) === propertyName) { - annotationNames = getClassPropertyAnnotationNames(item); - } - if (arkts.isMethodDefinition(item) && getIdentifierName(item.name) === propertyName) { - annotationNames = item.scriptFunction.annotations.map((annotation) => - getAnnotationName(annotation) - ); - } - }); - if (annotationNames.length === 0) { - return PresetDecorators.REGULAR; - } - const annotationName = annotationNames.find((item) => { return decoratorsFilter.includes(item) }); - if (annotationName) { - return annotationName; - } - return ''; - } - // Define a function to add property data to the property map - private addProperty( - structName: string, - propertyName: string, - annotationName: string - ): void { - if (!this.propertyMap.has(structName)) { - this.propertyMap.set(structName, new Map()); - } - const structProperties = this.propertyMap.get(structName); - if (!structProperties) { - return; - } - structProperties.set(propertyName, annotationName); - } - private collectBuilderFunctions(member: arkts.AstNode): void { - if (!arkts.isFunctionDeclaration(member) || !member.annotations) { + public checked(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node)) { return; } - member.annotations.forEach(annotation => { - if (annotation.expr && getIdentifierName(annotation.expr) === PresetDecorators.BUILDER && - member.scriptFunction.id) { - this.builderFunctionList.push(member.scriptFunction.id.name); - } - }); - } - private collectRegularVariables(member: arkts.AstNode): void { - if (!arkts.isVariableDeclaration(member) || !member.declarators) { - return; - } - member.getChildren().forEach((item) => { - if (!arkts.isVariableDeclarator(item) || !item.name || - (item.initializer && arkts.isArrowFunctionExpression(item.initializer))) { - return; - } - this.regularVariableList.push(item.name.name); - }); - } + const expression = node.expression; - private initList(node: arkts.AstNode): void { - // Record variables and functions that @builder decorate - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + // 只有自定义组件的场景才需要处理 + if ( + !arkts.isMemberExpression(expression) || + !arkts.isIdentifier(expression.property) || + !arkts.isIdentifier(expression.object) || + expression.property.name !== COMPONENT_INSTANTIATE + ) { return; } - node.getChildren().forEach((member) => { - this.collectBuilderFunctions(member); - this.collectRegularVariables(member); - }); - } - - private recordRestrictedDecorators( - item: arkts.AstNode, - structName: string, - ): void { - if (!arkts.isClassProperty(item) || !item.key) { + const componentDeclaration = arkts.getDecl(expression.object); + if (!componentDeclaration || !arkts.isClassDefinition(componentDeclaration) || !arkts.classDefinitionIsFromStructConst(componentDeclaration)) { return; } - let propertyName: string = getIdentifierName(item.key); - // If there is no decorator, it is a regular type - if (item.annotations.length === 0) { - let annotationName: string = PresetDecorators.REGULAR; - this.addProperty(structName, propertyName, annotationName); - } - // Iterate through the decorator of the property, and when the decorator is in the disallowAssignedDecorators array, the property is recorded - item.annotations.forEach((annotation) => { - if (!annotation.expr) { - return; - } - let annotationName: string = getIdentifierName(annotation.expr); - if (disallowAssignedDecorators.includes(annotationName)) { - this.addProperty(structName, propertyName, annotationName); - } - }); - } - private initPropertyMap(node: arkts.AstNode): void { - // Iterate through the root node ahead of time, noting the structure name, variable name, and corresponding decorator type - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + // 自定义组件有构造入参的场景才需要处理 + if (node.arguments.length <= 1) { return; } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident) { - return; - } - let structName: string = member.definition.ident?.name ?? ''; - if (structName === '') { - return; - } - member.definition?.body?.forEach((item) => { - this.recordRestrictedDecorators(item, structName); - }); - }); - } - - private reportRegularVariableError( - property: arkts.AstNode, - childType: string, - childName: string, - ): void { - if (!arkts.isProperty(property) || !property.value) { - return; - } - if (childType !== PresetDecorators.LINK) { - return; - } - if (arkts.isIdentifier(property.value) && this.regularVariableList.includes(property.value.name)) { - this.context.report({ - node: property, - message: this.messages.constructParameter, - data: { - initializer: PresetDecorators.REGULAR, - initializerName: property.value.name, - parameter: `@${childType}`, - parameterName: childName, - }, - }); - } - } - - private reportBuilderError( - property: arkts.AstNode, - childType: string, - childName: string, - ): void { - if (!arkts.isProperty(property) || !property.value) { + const constructorParameter = node.arguments[1]; + if (!arkts.isObjectExpression(constructorParameter)) { return; } - let isBuilderInStruct: boolean = false; - if (arkts.isMemberExpression(property.value) && arkts.isIdentifier(property.value.property)) { - const parentName = property.value.property.name; - const parentType: string = this.getPropertyAnnotationName(property, parentName); - if (parentType === '') { + constructorParameter.properties.forEach((property) => { + if ( + !arkts.isProperty(property) || + !property.key || + !arkts.isIdentifier(property.key) || + !property.value || + !arkts.isMemberExpression(property.value) || + !arkts.isThisExpression(property.value.object) || + !arkts.isIdentifier(property.value.property) + ) { return; } - isBuilderInStruct = parentType === PresetDecorators.BUILDER; - } - let isBuilder: boolean = false; - if (arkts.isIdentifier(property.value)) { - isBuilder = this.builderFunctionList.includes(property.value.name); - if (this.builderFunctionList.includes(property.value.name) && childType !== PresetDecorators.BUILDER_PARAM) { - this.context.report({ - node: property, - message: this.messages.initializerIsBuilder, - data: { - initializerName: property.value.name, - parameterName: childName, - }, - }); - } - } - if (childType === PresetDecorators.BUILDER_PARAM && !isBuilder && !isBuilderInStruct) { - this.context.report({ - node: property, - message: this.messages.parameterIsBuilderParam, - data: { - parameterName: childName, - }, - }); - } - } - private checkConstructParameter(node: arkts.AstNode): void { - if (!arkts.isIdentifier(node) || !this.propertyMap.has(getIdentifierName(node))) { - return; - } - let structName: string = getIdentifierName(node); - if (!node.parent) { - return; - } - let parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Gets all the properties recorded by the struct - const childPropertyName: Map = this.propertyMap.get(structName)!; - parentNode.arguments.forEach((member) => { - member.getChildren().forEach((property) => { - if (!arkts.isProperty(property) || !property.key) { - return; - } - const childName = getIdentifierName(property.key); - if (!childPropertyName.has(childName) || !property.value) { - return; - } - const childType: string = childPropertyName.get(childName)!; - this.reportRegularVariableError(property, childType, childName); - this.reportBuilderError(property, childType, childName); - if (!arkts.isMemberExpression(property.value) || !arkts.isThisExpression(property.value.object)) { - return; - } - const parentName = getIdentifierName(property.value.property); - const parentType: string = this.getPropertyAnnotationName(node, parentName); - if (parentType === '') { - return; - } - if (restrictedDecoratorInitializations.has(parentType) && - restrictedDecoratorInitializations.get(parentType)!.includes(childType)) { - this.context.report({ - node: property, - message: this.messages.constructParameter, - data: { - initializer: parentType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${parentType}`, - initializerName: parentName, - parameter: childType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${childType}`, - parameterName: childName, - }, - }); - } - }); + const keyDeclaration = arkts.getDecl(property.key); + const valueDeclaration = arkts.getDecl(property.value.property); + console.log("key", keyDeclaration?.parent?.dumpJson()); + console.log("value", valueDeclaration?.dumpJson()); + }); } + + } export default ConstructParameterRule; + diff --git a/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts b/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts index 79d42a4d670b9a99bb6626fd74100fa3b9ab64ad..652d86515383172513a921017288264d6b499ffb 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { PresetDecorators } from '../utils'; +import { getAnnotationUsagesByName, PresetDecorators } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; class ObservedObservedV2Rule extends AbstractUISyntaxRule { @@ -24,20 +24,19 @@ class ObservedObservedV2Rule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.AstNode): void { - if (!arkts.isClassDeclaration(node)) { + public checked(node: arkts.AstNode): void { + if (!arkts.isClassDeclaration(node) || !node.definition) { return; } - const hasObservedDecorator = node.definition?.annotations?.find(annotations => annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V1); - const hasObservedV2Decorator = node.definition?.annotations?.find(annotations => annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V2); - // If the current class is decorated by @Observed and @ObservedV2, an error is reported - if (hasObservedDecorator && hasObservedV2Decorator) { + + const [observedAnnotationV1, observedAnnotationV2] = getAnnotationUsagesByName(node.definition.annotations, [ + PresetDecorators.OBSERVED_V1, + PresetDecorators.OBSERVED_V2, + ]); + + if (observedAnnotationV1 && observedAnnotationV2) { this.report({ - node: hasObservedDecorator, + node: observedAnnotationV1, message: this.messages.conflictingDecorators, }); } diff --git a/arkui-plugins/ui-syntax-plugins/utils/index.ts b/arkui-plugins/ui-syntax-plugins/utils/index.ts index b3cd62688fd3947a0f28ab26ccff86225a6f8a39..e19e0c9463e28643d6fb2977a84f7dcc6c947b43 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -29,7 +29,7 @@ export const EXCLUDE_EXTERNAL_SOURCE_PREFIXES: Array = [ 'global', 'arkui', /@arkts\..*/, - /@ohos\.*/, + /@ohos\..*/, /@system\..*/, /@koalaui\./, /ability\..*/, @@ -51,32 +51,6 @@ export const PresetType = { BIGINT: 'bigint', }; -export const forbiddenUseStateType: string[] = [ - 'Scroller', - 'SwiperScroller', - 'VideoController', - 'WebController', - 'CustomDialogController', - 'SwiperController', - 'TabsController', - 'CalendarController', - 'AbilityController', - 'XComponentController', - 'CanvasRenderingContext2D', - 'CanvasGradient', - 'ImageBitmap', - 'ImageData', - 'Path2D', - 'RenderingContextSettings', - 'OffscreenCanvasRenderingContext2D', - 'PatternLockController', - 'TextAreaController', - 'TextInputController', - 'TextTimerController', - 'SearchController', - 'RichEditorController', -]; - export const PresetDecorators = { TOGGLE: 'Toggle', BUILDER_PARAM: 'BuilderParam', @@ -368,7 +342,25 @@ export function tracePerformance any>(name: string } as T; } -const ANNOTATION_PRESET_MODULE_PREFIXES: string[] = ['arkui.', '@ohos.', '@kit.ArkUI']; +const ANNOTATION_PRESET_MODULE_PREFIXES: string[] = ['arkui.', '@ohos.', '@kit.']; + +export function isPresetAnnotation(annotation: arkts.AnnotationUsage, annotationName?: string): boolean { + if (!annotation.expr || !arkts.isIdentifier(annotation.expr)) { + return false; + } + if (annotationName && annotation.expr.name !== annotationName) { + return false; + } + const annotationDeclaration = arkts.getDecl(annotation.expr); + if (!annotationDeclaration) { + return false; + } + const program = arkts.getProgramFromAstNode(annotationDeclaration); + if (!isFromPresetModules(program.moduleName)) { + return false; + } + return true; +} export function isFromPresetModules(moduleName: string): boolean { for (const presetModulePrefix of ANNOTATION_PRESET_MODULE_PREFIXES) { @@ -390,20 +382,7 @@ export function getAnnotationUsageByName( annotations: readonly arkts.AnnotationUsage[], annotationName: string ): arkts.AnnotationUsage | undefined { - return annotations.find((annotation: arkts.AnnotationUsage): boolean => { - if (!annotation.expr || !arkts.isIdentifier(annotation.expr) || annotation.expr.name !== annotationName) { - return false; - } - const annotationDeclaration = arkts.getDecl(annotation.expr); - if (!annotationDeclaration) { - return false; - } - const program = arkts.getProgramFromAstNode(annotationDeclaration); - if (!isFromPresetModules(program.moduleName)) { - return false; - } - return true; - }); + return annotations.find((annotation: arkts.AnnotationUsage): boolean => isPresetAnnotation(annotation, annotationName)); } export function isStructClassDeclaration(node: arkts.AstNode): node is arkts.ClassDeclaration {