* 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 { annotation, backingField, expectName } from '../../common/arkts-utils';
import { DecoratorNames, ObservedNames, StateManagementTypes, LANGUAGE_VERSION } from '../../common/predefines';
import {
BaseObservedPropertyTranslator,
IBaseObservedPropertyTranslator,
ObservedPropertyCachedTranslator,
ObservedPropertyCachedTranslatorOptions,
ObservedPropertyTranslator,
ObservedPropertyTranslatorOptions,
} from './base';
import {
collectStateManagementTypeImport,
generateThisBacking,
hasDecorator,
hasDecoratorName,
removeDecorator,
findPropertyAccessModifierFlags
} from './utils';
import { ClassScopeInfo } from '../struct-translators/utils';
import { factory } from './factory';
import { factory as uiFactory } from '../ui-factory';
import { ImportCollector } from '../../common/import-collector';
import { MetaDataCollector } from '../../common/metadata-collector';
import { FileManager } from '../../common/file-manager';
function isObservedTypeDynamic(typeNode: arkts.TypeNode | undefined): boolean {
if (!typeNode) {
return false;
}
let ident: arkts.Identifier | undefined;
if (arkts.isETSTypeReference(typeNode) &&
typeNode.part &&
typeNode.part.name &&
arkts.isIdentifier(typeNode.part.name)) {
ident = typeNode.part.name;
}
if (!ident) {
return false;
}
const typeDecl = arkts.getDecl(ident);
if (!typeDecl) {
return false;
}
const program = arkts.getProgramFromAstNode(typeDecl);
if (!program) {
return false;
}
const fileManager = FileManager.getInstance();
return fileManager.getLanguageVersionByFilePath(program.absoluteName) === LANGUAGE_VERSION.ARKTS_1_1;
}
export function getterBodyWithObservedV2TraceProperty(
this: IObservedV2TraceTranslator,
originalName: string,
newName: string
): arkts.BlockStatement {
const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.className);
ImportCollector.getInstance().collectImport(StateManagementTypes.UI_UTILS);
const observedMember: arkts.Expression = this.isStatic
? uiFactory.generateMemberExpression(classIdent.clone(), newName)
: generateThisBacking(newName);
const isTypeDynamic: boolean = isObservedTypeDynamic(this.propertyType);
const hasPropertyValue: boolean = !!this.property?.value;
let makeObservedArg: arkts.Expression;
if (isTypeDynamic) {
makeObservedArg = arkts.factory.createTSAsExpression(
arkts.factory.createTSAsExpression(observedMember, uiFactory.createTypeReferenceFromString('Any'), false),
uiFactory.createTypeReferenceFromString('object'),
false
);
} else if (this.property && !hasPropertyValue) {
makeObservedArg = arkts.factory.createTSAsExpression(observedMember, this.propertyType, false);
} else {
const isDefinitelyNonNull: boolean = this.isDefinitelyNonNull ?? !!this.property?.tsType?.definitelyNotETSNullish;
makeObservedArg = isDefinitelyNonNull && !this.isStatic && !hasPropertyValue
? arkts.factory.createTSNonNullExpression(observedMember)
: observedMember;
}
const makeObservedCall: arkts.CallExpression = arkts.factory.createCallExpression(
uiFactory.generateMemberExpression(
arkts.factory.createIdentifier(StateManagementTypes.UI_UTILS),
StateManagementTypes.MAKE_OBSERVED
),
[makeObservedArg],
undefined,
false,
false
);
const returnMember: arkts.ReturnStatement = isTypeDynamic
? arkts.factory.createReturnStatement(arkts.factory.createTSAsExpression(makeObservedCall, this.propertyType, false))
: arkts.factory.createReturnStatement(makeObservedCall);
return arkts.factory.createBlockStatement([createAddRef.bind(this)(originalName, classIdent), returnMember]);
}
function getterWithObservedV2TraceProperty(
this: IObservedV2TraceTranslator,
originalName: string,
newName: string
): arkts.MethodDefinition {
let methodModifier = this.isStatic
? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC
: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC;
let body: arkts.BlockStatement | undefined;
if (!this.isDecl) {
body = getterBodyWithObservedV2TraceProperty.bind(this)(originalName, newName);
} else {
methodModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
}
return uiFactory.createMethodDefinition({
kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET,
key: arkts.factory.createIdentifier(originalName),
function: {
body: body,
returnTypeAnnotation: this.propertyType,
modifiers: methodModifier,
flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER,
},
modifiers: methodModifier,
});
}
function setterWithObservedV2TraceProperty(
this: IObservedV2TraceTranslator,
originalName: string,
newName: string
): arkts.MethodDefinition {
let methodModifier = this.isStatic
? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC
: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC;
let body: arkts.BlockStatement | undefined;
if (!this.isDecl) {
const ifEqualsNewValue: arkts.IfStatement = setterIfEqualsNewValueWithObservedV2TraceProperty.bind(this)(
originalName,
newName
);
body = arkts.factory.createBlockStatement([ifEqualsNewValue]);
} else {
methodModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
}
const param = arkts.factory.createETSParameterExpression(
arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, this.propertyType),
false,
undefined
);
return uiFactory.createMethodDefinition({
kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET,
key: arkts.factory.createIdentifier(originalName),
function: {
body: body,
params: [param],
modifiers: methodModifier,
flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER,
},
modifiers: methodModifier,
});
}
function createAddRef(
this: IObservedV2TraceTranslator,
originalName: string,
classIdent: arkts.Identifier
): arkts.ExpressionStatement {
const metaName: string = `${StateManagementTypes.META}_${originalName}`;
const conditionalAddRef = arkts.factory.createExpressionStatement(
arkts.factory.createCallExpression(generateThisBacking(ObservedNames.CONDITIONAL_ADD_REF), [
generateThisBacking(metaName),
],
undefined,
false,
false
)
);
const metaAddRef = arkts.factory.createExpressionStatement(
arkts.factory.createCallExpression(
uiFactory.generateMemberExpression(
uiFactory.generateMemberExpression(classIdent.clone(), metaName),
ObservedNames.ADD_REF
),
[],
undefined,
false,
false
)
);
return this.isStatic ? metaAddRef : conditionalAddRef;
}
export function setterIfEqualsNewValueWithObservedV2TraceProperty(
this: IObservedV2TraceTranslator,
originalName: string,
newName: string
): arkts.IfStatement {
const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.className);
const metaName: string = `${StateManagementTypes.META}_${originalName}`;
const backingValue: arkts.Expression = generateThisBacking(newName);
const metaValue: arkts.Expression = generateThisBacking(metaName);
const staticMetaValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), metaName);
const staticBackingValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), newName);
const setNewValue = arkts.factory.createExpressionStatement(
arkts.factory.createAssignmentExpression(
this.isStatic ? staticBackingValue : backingValue,
arkts.factory.createIdentifier(ObservedNames.NEW_VALUE),
arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION
)
);
const fireChange = arkts.factory.createExpressionStatement(
arkts.factory.createCallExpression(
arkts.factory.createMemberExpression(
this.isStatic ? staticMetaValue.clone() : metaValue.clone(),
arkts.factory.createIdentifier(ObservedNames.FIRE_CHANGE),
arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
false,
false
),
[],
undefined,
false,
false
)
);
const subscribingWatches = arkts.factory.createExpressionStatement(
arkts.factory.createCallExpression(generateThisBacking(ObservedNames.EXECUATE_WATCHES), [
arkts.factory.createStringLiteral(originalName),
],
undefined,
false,
false
)
);
const consequentArr = this.isStatic ? [setNewValue, fireChange] : [setNewValue, fireChange, subscribingWatches];
return arkts.factory.createIfStatement(
arkts.factory.createBinaryExpression(
this.isStatic ? staticBackingValue.clone() : backingValue.clone(),
arkts.factory.createIdentifier(ObservedNames.NEW_VALUE),
arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL
),
arkts.factory.createBlockStatement(consequentArr)
);
}
export interface IObservedV2TraceTranslator extends IBaseObservedPropertyTranslator {
className: string;
traceDecorator: DecoratorNames.TRACE;
isTraced?: boolean;
isStatic?: boolean;
isDecl?: boolean;
isDefinitelyNonNull?: boolean;
}
* @deprecated
*/
export class ObservedV2TraceTranslator extends ObservedPropertyTranslator implements IObservedV2TraceTranslator {
traceDecorator: DecoratorNames.TRACE = DecoratorNames.TRACE;
className: string;
isTraced?: boolean;
isStatic?: boolean;
isDecl?: boolean;
propertyModifier: arkts.Es2pandaModifierFlags = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE;
protected hasBackingField: boolean = true;
protected hasMetaField: boolean = true;
protected hasGetterSetter: boolean = true;
protected isTracked: boolean = false;
constructor(options: ObservedPropertyTranslatorOptions) {
super(options);
this.className = this.classScopeInfo.className;
this.hasImplement = arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_SETTER);
this.isTraced = hasDecorator(this.property, this.traceDecorator);
this.isStatic = arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC);
this.isDecl = MetaDataCollector.getInstance().isDeclaration ||
arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE);
if (this.isStatic) {
this.propertyModifier = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
}
}
translateMember(): arkts.AstNode[] {
if (!this.isTraced) {
return [this.property];
}
return super.translateMember();
}
metaField(originalName: string, newName: string): arkts.ClassProperty {
const field = super.metaField(originalName, newName);
field.setAnnotations([
annotation(DecoratorNames.JSONSTRINGIFYIGNORE),
annotation(DecoratorNames.JSONPARSEIGNORE),
]);
return field;
}
getter(originalName: string, newName: string): arkts.MethodDefinition {
return getterWithObservedV2TraceProperty.bind(this)(originalName, newName);
}
setter(originalName: string, newName: string): arkts.MethodDefinition {
return setterWithObservedV2TraceProperty.bind(this)(originalName, newName);
}
}
export class ObservedV2TraceCachedTranslator
extends ObservedPropertyCachedTranslator
implements IObservedV2TraceTranslator
{
traceDecorator: DecoratorNames.TRACE = DecoratorNames.TRACE;
className: string;
isTraced?: boolean;
isStatic: boolean = false;
isDecl: boolean = false;
propertyModifier: arkts.Es2pandaModifierFlags = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE;
protected hasBackingField: boolean = true;
protected hasMetaField: boolean = true;
protected hasGetterSetter: boolean = true;
protected isTracked: boolean = false;
constructor(options: ObservedPropertyCachedTranslatorOptions) {
super(options);
this.className = this.propertyInfo.classInfo?.name!;
this.isTraced = !!this.propertyInfo.annotationInfo?.hasTrace;
this.isDecl = !!MetaDataCollector.getInstance().isDeclaration;
if (!!this.propertyInfo.modifiers) {
this.isStatic = (this.propertyInfo.modifiers & arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC) === arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
this.isDecl ||= (this.propertyInfo.modifiers & arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE) === arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
}
if (this.isStatic) {
this.propertyModifier = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC;
}
if (this.isDecl) {
this.propertyModifier |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE;
}
}
translateMember(): arkts.AstNode[] {
if (!this.isTraced) {
return [this.property];
}
return super.translateMember();
}
metaField(originalName: string, newName: string): arkts.ClassProperty {
const field = super.metaField(originalName, newName);
field.setAnnotations([
annotation(DecoratorNames.JSONSTRINGIFYIGNORE),
annotation(DecoratorNames.JSONPARSEIGNORE),
]);
return field;
}
getter(originalName: string, newName: string): arkts.MethodDefinition {
return getterWithObservedV2TraceProperty.bind(this)(originalName, newName);
}
setter(originalName: string, newName: string): arkts.MethodDefinition {
return setterWithObservedV2TraceProperty.bind(this)(originalName, newName);
}
}