* Copyright (c) 2026 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 { PresetDecorators, findDecorator } from '../utils';
import { AbstractUISyntaxRule } from './ui-syntax-rule';
const V1Decorators: string[] = [
PresetDecorators.STATE,
PresetDecorators.PROP_REF,
PresetDecorators.PROVIDE,
PresetDecorators.LINK,
PresetDecorators.CONSUME,
PresetDecorators.STORAGE_LINK,
PresetDecorators.LOCAL_STORAGE_LINK,
PresetDecorators.STORAGE_PROP_REF,
PresetDecorators.LOCAL_STORAGE_PROP_REF,
];
class StructV1DecoratorFunctionRule extends AbstractUISyntaxRule {
private typeAliasMap: Map<string, arkts.TypeNode> = new Map();
public setup(): Record<string, string> {
return {
IllegalFunction: `The V1 decorator '{{decorateNameProxy}}' cannot be applied to a Function-type variable '{{propertyName}}'`,
};
}
public parsed(node: arkts.AstNode): void {
if (arkts.isTSTypeAliasDeclaration(node) && node.id && node.typeAnnotation) {
this.typeAliasMap.set(node.id.name, node.typeAnnotation);
return;
}
if (!arkts.isETSStructDeclaration(node)) {
return;
}
node.definition.body.forEach(member => {
if (arkts.isClassProperty(member)) {
this.reportIllegalFunctionError(member);
}
});
}
private hasDecoratorWithFunctionType(
member: arkts.ClassProperty,
decorators: string[]
): boolean {
const hasTargetDecorator = member.annotations.some((annotation: arkts.AnnotationUsage) => {
return (
annotation.expr &&
arkts.isIdentifier(annotation.expr) &&
decorators.includes(annotation.expr.name)
);
});
if (!hasTargetDecorator) {
return false;
}
if (member.typeAnnotation) {
if (this.isFunctionType(member.typeAnnotation, new Set<string>())) {
return true;
}
}
return false;
}
private isFunctionType(typeNode: arkts.TypeNode, visited: Set<string>): boolean {
if (arkts.isETSFunctionType(typeNode)) {
return true;
}
if (this.isFunctionTypeReference(typeNode, visited)) {
return true;
}
if (arkts.isETSUnionType(typeNode)) {
return this.isUnionOfTypeOnlyFunction(typeNode, visited);
}
return false;
}
private isFunctionTypeReference(typeNode: arkts.TypeNode, visited: Set<string>): boolean {
if (!arkts.isETSTypeReference(typeNode)) {
return false;
}
const ref = typeNode as arkts.ETSTypeReference;
if (!ref.part || !arkts.isETSTypeReferencePart(ref.part)) {
return false;
}
const part = ref.part as arkts.ETSTypeReferencePart;
if (!part.name || !arkts.isIdentifier(part.name)) {
return false;
}
const typeName = part.name.name;
if (typeName === 'Function') {
return true;
}
const aliasedType = this.typeAliasMap.get(typeName);
if (!aliasedType || visited.has(typeName)) {
return false;
}
visited.add(typeName);
return this.isFunctionType(aliasedType, visited);
}
private isUnionOfTypeOnlyFunction(unionType: arkts.ETSUnionType, visited: Set<string>): boolean {
const types = unionType.types;
for (const type of types) {
if (!this.isFunctionType(type, new Set(visited))) {
return false;
}
}
return types.length > 0;
}
private reportIllegalFunctionError(member: arkts.ClassProperty): void {
if (member.key && arkts.isIdentifier(member.key)) {
const propertyName = member.key.name.toString();
const decorateNameProxy = V1Decorators.find(decoratorName => {
return findDecorator(member, decoratorName);
});
if (this.hasDecoratorWithFunctionType(member, V1Decorators) && decorateNameProxy) {
this.report({
node: member,
message: this.messages.IllegalFunction,
data: {
decorateNameProxy: decorateNameProxy,
propertyName: propertyName,
},
});
}
}
}
}
export default StructV1DecoratorFunctionRule;