* 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,
hasAnnotation,
getClassPropertyAnnotationNames,
getClassPropertyName,
findDecorator,
ENV_TYPE_ARG_MAP,
isComponentBuilder,
} from '../utils';
import { AbstractUISyntaxRule } from './ui-syntax-rule';
const V1_STATE_DECORATORS: string[] = [
PresetDecorators.STATE,
PresetDecorators.PROP_REF,
PresetDecorators.LINK,
PresetDecorators.PROVIDE,
PresetDecorators.CONSUME,
PresetDecorators.OBSERVED_V1,
PresetDecorators.OBJECT_LINK,
PresetDecorators.WATCH,
PresetDecorators.TRACK,
PresetDecorators.REUSABLE_V1,
];
const V2_STATE_DECORATORS: string[] = [
PresetDecorators.LOCAL,
PresetDecorators.PARAM,
PresetDecorators.ONCE,
PresetDecorators.EVENT,
PresetDecorators.PROVIDER,
PresetDecorators.CONSUMER,
PresetDecorators.OBSERVED_V2,
PresetDecorators.TRACE,
PresetDecorators.MONITOR,
PresetDecorators.COMPUTED,
PresetDecorators.TYPE,
PresetDecorators.REUSABLE_V2,
];
const INCOMPATIBLE_DECORATORS: string[] = [
...V1_STATE_DECORATORS,
...V2_STATE_DECORATORS,
PresetDecorators.REQUIRE,
];
const ENV_TYPE_KEYS = [...ENV_TYPE_ARG_MAP.values()];
interface EnvTypeName {
currentTypeName: string;
}
class EnvDecoratorCheckRule extends AbstractUISyntaxRule {
public setup(): Record<string, string> {
return {
envOnlyInComponentOrComponentV2: `The '@Env' annotation can only be used in structs decorated with either '@Component' or '@ComponentV2'.`,
envCannotHaveDefault: `The '@Env' property cannot be specified a default value.`,
envInvalidParameter: `Invalid parameter. State variables decorated with '@Env' of '{{typeName}}' can only accept {{enumName}}.`,
envInvalidType: `The '@Env' annotation can only decorate 'WindowSizeLayoutBreakpointInfo', 'SizeInVP', 'Size', 'UIEnvWindowAvoidAreaInfoVP', 'UIEnvWindowAvoidAreaInfoPX' classes and their child classes.`,
envMultipleAnnotationsV1: `The property '{{propName}}' cannot have multiple state management annotations.`,
envMultipleAnnotationsV2: `The member property or method can not be decorated by multiple built-in annotations.`,
envOnlyInitParamInV2: `Within structs decorated with '@ComponentV2', '@Env' can only initialize variables decorated with '@Param'.`,
envOnlyInitRegularInV1: `Within structs decorated with '@Component', '@Env' can only initialize regular(non-decorated) variables.`,
customEnvOnlyInComponentOrComponentV2: `The '@CustomEnv' annotation can only be used in structs decorated with either '@Component' or '@ComponentV2'.`,
};
}
public parsed(node: arkts.AstNode): void {
if (arkts.isETSStructDeclaration(node)) {
const hasComponent = hasAnnotation(node.definition.annotations, PresetDecorators.COMPONENT_V1);
const hasComponentV2 = hasAnnotation(node.definition.annotations, PresetDecorators.COMPONENT_V2);
this.checkEnvUsagePositionInStruct(node, hasComponent, hasComponentV2);
return;
}
if (arkts.isClassProperty(node)) {
this.checkEnvInitialization(node);
}
}
private checkEnvUsagePositionInStruct(node: arkts.ETSStructDeclaration, hasComponent: boolean, hasComponentV2: boolean): void {
if (hasComponent || hasComponentV2) {
return;
}
for (const member of node.definition.body) {
if (!arkts.isClassProperty(member)) {
continue;
}
const envDecorator = findDecorator(member, PresetDecorators.ENV);
if (envDecorator) {
this.report({
node: envDecorator,
message: this.messages.envOnlyInComponentOrComponentV2,
});
}
const customEnvDecorator = findDecorator(member, PresetDecorators.CUSTOM_ENV);
if (customEnvDecorator) {
this.report({
node: customEnvDecorator,
message: this.messages.customEnvOnlyInComponentOrComponentV2,
});
}
}
}
private checkEnvInitialization(node: arkts.ClassProperty): void {
const envDecorator = findDecorator(node, PresetDecorators.ENV);
if (!envDecorator) {
return;
}
if (node.value) {
this.report({
node: envDecorator,
message: this.messages.envCannotHaveDefault,
});
}
}
private checkDecoratorCombination(node: arkts.ETSStructDeclaration, isComponentV2: boolean): void {
for (const member of node.definition.body) {
if (!arkts.isClassProperty(member)) {
continue;
}
const envDecorator = findDecorator(member, PresetDecorators.ENV);
if (!envDecorator) {
continue;
}
const annotationNames = getClassPropertyAnnotationNames(member);
const otherDecorator = annotationNames.find(name =>
name !== PresetDecorators.ENV && INCOMPATIBLE_DECORATORS.includes(name)
);
if (!otherDecorator) {
continue;
}
const messageKey = isComponentV2
? this.messages.envMultipleAnnotationsV2
: this.messages.envMultipleAnnotationsV1;
this.report({
node: envDecorator,
message: messageKey,
data: { propName: getClassPropertyName(member) || '' },
});
}
}
private checkStructPropertyDefinition(node: arkts.CallExpression): void {
if (!node.callee || !arkts.isMemberExpression(node.callee) || !isComponentBuilder(node.callee)) {
return;
}
if (!arkts.isIdentifier(node.callee.object)) {
return;
}
const structDecl = arkts.getDecl(node.callee.object);
if (!structDecl || !arkts.isClassDefinition(structDecl)) {
return;
}
const args = node.arguments;
if (args.length === 0 || !arkts.isObjectExpression(node.arguments[0])) {
return;
}
const properties = node.arguments[0].properties;
if (properties.length === 0 || !arkts.isProperty(properties[0])) {
return;
}
const paramKey = properties[0].key;
const paramValue = properties[0].value;
if (!paramKey || !paramValue || !arkts.isIdentifier(paramKey)) {
return;
}
const keyDecl = this.getPropertyFromStruct(structDecl, paramKey.name);
const valueDecl = arkts.getDecl(paramValue);
if (!keyDecl || !valueDecl) {
return;
}
if (!arkts.isClassProperty(valueDecl) || !findDecorator(valueDecl, PresetDecorators.ENV)) {
return;
}
this.checkEnvInitializationInComponent(structDecl, keyDecl, properties[0]);
}
private checkEnvInitializationInComponent(structDecl: arkts.ClassDefinition, keyDecl: arkts.ClassProperty, propertyNode: arkts.Property): void {
const annotationNames = getClassPropertyAnnotationNames(keyDecl);
const hasComponent = hasAnnotation(structDecl.annotations, PresetDecorators.COMPONENT_V1);
const hasComponentV2 = hasAnnotation(structDecl.annotations, PresetDecorators.COMPONENT_V2);
if (hasComponent) {
this.checkEnvInitInComponentV1(annotationNames, propertyNode);
}
if (hasComponentV2) {
this.checkEnvInitInComponentV2(annotationNames, propertyNode);
}
}
private checkEnvInitInComponentV1(annotationNames: string[], propertyNode: arkts.Property): void {
const hasStateDecorator = annotationNames.some(name =>
V1_STATE_DECORATORS.includes(name) || V2_STATE_DECORATORS.includes(name)
);
if (hasStateDecorator) {
this.report({
node: propertyNode,
message: this.messages.envOnlyInitRegularInV1,
});
}
}
private checkEnvInitInComponentV2(annotationNames: string[], propertyNode: arkts.Property): void {
const hasParamDecorator = annotationNames.some(name => name === PresetDecorators.PARAM);
if (!hasParamDecorator) {
this.report({
node: propertyNode,
message: this.messages.envOnlyInitParamInV2,
});
}
}
private getPropertyFromStruct(decl: arkts.ClassDefinition, propName: string): arkts.ClassProperty | undefined {
for (const member of decl.body) {
if (!arkts.isClassProperty(member) || !member.key || !arkts.isIdentifier(member.key)) {
continue;
}
if (member.key.name === propName) {
return member;
}
}
return undefined;
}
private checkEnvVariableType(node: arkts.ClassProperty): void {
const envDecorator = findDecorator(node, PresetDecorators.ENV);
if (!envDecorator) {
return;
}
const propType = node.typeAnnotation;
if (!propType) {
this.report({
node: node,
message: this.messages.envInvalidType,
});
return;
}
if (!arkts.isETSTypeReference(propType)) {
return;
}
const envTypeName: EnvTypeName = { currentTypeName: '' };
if (!this.isEnvVariableTypeValid(propType, envTypeName)) {
this.report({
node: node,
message: this.messages.envInvalidType,
});
return;
}
const expectedKey = ENV_TYPE_ARG_MAP.get(envTypeName.currentTypeName);
if (!expectedKey) {
return;
}
const envKey = this.extractEnvKey(envDecorator);
if (!envKey || !ENV_TYPE_KEYS.includes(envKey)) {
return;
}
if (ENV_TYPE_ARG_MAP.get(envTypeName.currentTypeName) !== envKey) {
this.report({
node: node,
message: this.messages.envInvalidParameter,
data: {
typeName: envTypeName.currentTypeName,
enumName: expectedKey
}
});
}
}
private isEnvVariableTypeValid(propType: arkts.ETSTypeReference, envTypeName: EnvTypeName): boolean {
if (!propType.part || !propType.part.name) {
return false;
}
let typeDecl: arkts.AstNode | undefined = undefined;
if (arkts.isIdentifier(propType.part.name)) {
typeDecl = arkts.getDecl(propType.part.name);
} else if (arkts.isTSQualifiedName(propType.part.name) && propType.part.name.right) {
typeDecl = arkts.getDecl(propType.part.name.right);
}
if (!typeDecl) {
return false;
}
if (arkts.isTSInterfaceDeclaration(typeDecl)) {
const typeName = typeDecl.id?.name;
if (!!typeName && ENV_TYPE_ARG_MAP.has(typeName)) {
envTypeName.currentTypeName = typeName;
return true;
}
return false;
}
if (arkts.isClassDefinition(typeDecl)) {
const typeName = typeDecl.ident?.name;
if (!!typeName && ENV_TYPE_ARG_MAP.has(typeName)) {
envTypeName.currentTypeName = typeName;
return true;
}
let isValidSuper: boolean = false;
let isValidImplements: boolean = false;
if (!!typeDecl.implements) {
const typeImplements = typeDecl.implements;
isValidImplements = typeImplements.some((typeImplement) => {
return typeImplement.expr &&
arkts.isETSTypeReference(typeImplement.expr) &&
this.isEnvVariableTypeValid(typeImplement.expr, envTypeName)
});
}
if (!isValidImplements && !!typeDecl.super && arkts.isETSTypeReference(typeDecl.super)) {
isValidSuper = this.isEnvVariableTypeValid(typeDecl.super, envTypeName);
}
return isValidSuper || isValidImplements;
}
return false;
}
private extractEnvKey(decorator: arkts.AnnotationUsage): string | undefined {
if (decorator.properties.length !== 1 || !arkts.isClassProperty(decorator.properties[0])) {
return undefined;
}
const value = decorator.properties[0].value;
return value?.dumpSrc();
}
}
export default EnvDecoratorCheckRule;