* 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 * as arkts from '@koalaui/libarkts';
import {
getIdentifierName,
getClassPropertyName,
getClassPropertyAnnotationNames,
PresetDecorators,
getAnnotationName,
} from '../utils';
import { AbstractUISyntaxRule } from './ui-syntax-rule';
const disallowAssignedDecorators: string[] = [
PresetDecorators.REGULAR, PresetDecorators.LINK, PresetDecorators.OBJECT_LINK,
PresetDecorators.BUILDER_PARAM, PresetDecorators.BUILDER, PresetDecorators.STATE,
PresetDecorators.PROP_REF, PresetDecorators.PROVIDE, PresetDecorators.CONSUME,
PresetDecorators.BUILDER,
];
const restrictedDecoratorInitializations: Map<string, string[]> = new Map([
[PresetDecorators.REGULAR, [PresetDecorators.OBJECT_LINK, PresetDecorators.LINK]],
[PresetDecorators.PROVIDE, [PresetDecorators.REGULAR]],
[PresetDecorators.CONSUME, [PresetDecorators.REGULAR]],
[PresetDecorators.STORAGE_PROP_REF, [PresetDecorators.REGULAR]],
[PresetDecorators.VARIABLE, [PresetDecorators.LINK]],
[PresetDecorators.LOCAL_STORAGE_LINK, [PresetDecorators.REGULAR]],
]);
const decoratorsFilter: string[] = [
PresetDecorators.PROVIDE, PresetDecorators.CONSUME, PresetDecorators.STORAGE_PROP_REF,
PresetDecorators.LOCAL_STORAGE_LINK, PresetDecorators.BUILDER_PARAM,
];
class ConstructParameterRule extends AbstractUISyntaxRule {
private propertyMap: Map<string, Map<string, string>> = new Map();
private regularVariableList: string[] = [];
private builderFunctionList: string[] = [];
public setup(): Record<string, string> {
return {
constructParameter: `The '{{initializer}}' property '{{initializerName}}' cannot be assigned to the '{{parameter}}' property '{{parameterName}}'.`,
initializerIsBuilder: `'@Builder' function '{{initializerName}}' can only initialize '@BuilderParam' attribute.`,
parameterIsBuilderParam: `'@BuilderParam' attribute '{{parameterName}}' can only initialized by '@Builder' function or '@Builder' method in struct.`,
};
}
public beforeTransform(): void {
this.propertyMap = new Map();
this.regularVariableList = [];
this.builderFunctionList = [];
}
public parsed(node: arkts.AstNode): void {
this.initList(node);
this.initPropertyMap(node);
this.checkConstructParameter(node);
}
private getPropertyAnnotationName(node: arkts.AstNode, propertyName: string): string {
while (!arkts.isETSStructDeclaration(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) === propertyName) {
annotationNames = item.function!.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 '';
}
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) {
return;
}
member.annotations.forEach(annotation => {
if (annotation.expr && getIdentifierName(annotation.expr) === PresetDecorators.BUILDER &&
member.function!.id) {
this.builderFunctionList.push(member.function!.id.name);
}
});
}
private collectRegularVariables(member: arkts.AstNode): void {
if (!arkts.isVariableDeclaration(member) || !member.declarators) {
return;
}
member.getChildren().forEach((item) => {
if (!arkts.isVariableDeclarator(item) || !item.id ||
(item.init && arkts.isArrowFunctionExpression(item.init))) {
return;
}
this.regularVariableList.push((item.id as arkts.Identifier).name);
});
}
private initList(node: arkts.AstNode): void {
if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) {
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) {
return;
}
let propertyName: string = getIdentifierName(item.key);
if (item.annotations.length === 0) {
let annotationName: string = PresetDecorators.REGULAR;
this.addProperty(structName, propertyName, annotationName);
}
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 {
if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) {
return;
}
node.getChildren().forEach((member) => {
if (!arkts.isETSStructDeclaration(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.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) {
return;
}
const propertyValue: arkts.Expression = property.value;
if (arkts.isArrowFunctionExpression(propertyValue)) {
return;
}
let isBuilderInStruct: boolean = false;
if (arkts.isMemberExpression(propertyValue) && arkts.isIdentifier(propertyValue.property)) {
const parentName = propertyValue.property.name;
const parentType: string = this.getPropertyAnnotationName(property, parentName);
if (parentType === '') {
return;
}
isBuilderInStruct = parentType === PresetDecorators.BUILDER;
}
let isBuilder: boolean = false;
if (arkts.isIdentifier(propertyValue)) {
isBuilder = this.builderFunctionList.includes(propertyValue.name);
if (this.builderFunctionList.includes(propertyValue.name) && childType !== PresetDecorators.BUILDER_PARAM) {
this.report({
node: property,
message: this.messages.initializerIsBuilder,
data: {
initializerName: propertyValue.name,
parameterName: childName,
},
});
}
}
if (childType === PresetDecorators.BUILDER_PARAM && !isBuilder && !isBuilderInStruct) {
this.report({
node: property,
message: this.messages.parameterIsBuilderParam,
level: 'warn',
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;
}
const childPropertyName: Map<string, string> = 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 as arkts.AstNode);
const parentType: string = this.getPropertyAnnotationName(node, parentName);
if (parentType === '') {
return;
}
if (restrictedDecoratorInitializations.has(parentType) &&
restrictedDecoratorInitializations.get(parentType)!.includes(childType)) {
this.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,
},
});
}
});
});
}
}
export default ConstructParameterRule;