/* * 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 ts, { CatchClause, Declaration, Expression, TypeChecker, VariableDeclarationList } from 'typescript'; import { IntentEntryInfo, intentEntryInfoChecker, IntentLinkInfo, IntentLinkInfoChecker, intentMethodInfoChecker, LinkIntentParamMapping, IntentPageInfoChecker, ParamChecker, IntentEntityInfoChecker, intentFormInfoChecker } from './intentType'; import { IntentLogger } from './intentLogger'; import path from 'path'; import { getNormalizedOhmUrlByFilepath } from '../ark_utils'; import { globalModulePaths, projectConfig } from '../../main'; import fs from 'fs'; import json5 from 'json5'; import { ProjectCollections } from 'arkguard'; import { COMPONENT_USER_INTENTS_DECORATOR, COMPONENT_USER_INTENTS_DECORATOR_ENTITY, COMPONENT_USER_INTENTS_DECORATOR_ENTRY, COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, COMPONENT_USER_INTENTS_DECORATOR_METHOD, COMPONENT_USER_INTENTS_DECORATOR_PAGE, COMPONENT_USER_INTENTS_DECORATOR_FORM } from '../pre_define'; import { CompileEvent, createAndStartEvent, stopEvent } from '../performance'; import {emitLogInfo, getTransformLog, LogInfo, LogType} from '../utils'; import {ABILITY_SUBSYSTEM_CODE} from '../../lib/hvigor_error_code/hvigor_error_info'; import {resetLog, transformLog} from '../process_ui_syntax'; type StaticValue = string | number | boolean | null | undefined | StaticValue[] | { [key: string]: StaticValue }; interface methodParametersInfo { functionName: string; parameters: Record, args: ts.NodeArray; } interface schemaVerifyType { type: string; isEntity?: boolean; } class ParseIntent { private checker: ts.TypeChecker; public intentData: object[]; private currentFilePath: string; private heritageClassSet: Set; private updatePageIntentObj: Map; public isUpdateCompile: boolean = true; private isInitCache: boolean = false; private entityMap: Map; private entityOwnerMap: Map; private moduleJsonInfo: Map; private EntityHeritageClassSet: Set; private EntityExtendsMap: Map; private transformLog: LogInfo[]; private currentNode: ts.Node; constructor() { this.intentData = []; this.currentFilePath = ''; this.heritageClassSet = new Set(); this.heritageClassSet.add('IntentEntity_sdk'); this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); this.updatePageIntentObj = new Map(); this.entityMap = new Map(); this.entityOwnerMap = new Map(); this.moduleJsonInfo = new Map(); this.EntityHeritageClassSet = new Set(); this.EntityExtendsMap = new Map(); } private hasDecorator(node: ts.Node, decorators: string[]): boolean { if (!node.modifiers) { return false; } return node.modifiers.some(decorator => { if (!ts.isDecorator(decorator)) { return false; } let decoratorName: string | undefined; if (ts.isCallExpression(decorator.expression)) { decoratorName = `@${decorator.expression.expression.getText()}`; } return decoratorName !== undefined && decorators.includes(decoratorName); }); } public detectInsightIntent( node: ts.ClassDeclaration, metaInfo: object, filePath: string, eventOrEventFactory: CompileEvent | undefined, transformLog: LogInfo[]): ts.Node { this.initInsightIntent(node, metaInfo, transformLog, filePath); const eventParseIntentTime: CompileEvent | undefined = createAndStartEvent(eventOrEventFactory, 'parseIntentTime'); const definedDecorators: string[] = [COMPONENT_USER_INTENTS_DECORATOR, COMPONENT_USER_INTENTS_DECORATOR_ENTRY, COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, COMPONENT_USER_INTENTS_DECORATOR_PAGE, COMPONENT_USER_INTENTS_DECORATOR_ENTITY, COMPONENT_USER_INTENTS_DECORATOR_FORM]; if (ts.isClassDeclaration(node) && !this.hasDecorator(node, [COMPONENT_USER_INTENTS_DECORATOR_FUNCTION])) { node.members.forEach((member) => { if (ts.isMethodDeclaration(member) && this.hasModifier(member, ts.SyntaxKind.StaticKeyword) && this.hasDecorator(member, [COMPONENT_USER_INTENTS_DECORATOR_METHOD])) { const errorMessage: string = 'Methods decorated with @InsightIntentFunctionMethod must be in a class decorated with @InsightIntentFunction.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: node.getStart(), code: '10110013', description: 'InsightIntent Compiler Error', solutions: ['Either move the method or add @InsightIntentFunction to the class'] }); return; } }); } if (this.hasDecorator(node, definedDecorators)) { const checker: TypeChecker = metaInfo.checker; this.handleIntent(node, checker, filePath, metaInfo); node = this.removeDecorator(node, definedDecorators.concat(COMPONENT_USER_INTENTS_DECORATOR_METHOD)); } stopEvent(eventParseIntentTime); return node; } private initInsightIntent(node: ts.ClassDeclaration, metaInfo: object, transformLog: LogInfo[], filePath: string): void { this.transformLog = transformLog; this.currentNode = node; if (!this.isInitCache) { if (projectConfig.cachePath) { const cacheSourceMapPath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // The user's intents configuration file this.isUpdateCompile = fs.existsSync(cacheSourceMapPath); this.isInitCache = true; } else { this.isUpdateCompile = false; } } if (this.isUpdateCompile) { const pkgParams: object = { pkgName: metaInfo.pkgName, pkgPath: metaInfo.pkgPath }; if (!projectConfig.pkgContextInfo) { return; } const Logger: IntentLogger = IntentLogger.getInstance(); const recordName: string = getNormalizedOhmUrlByFilepath(filePath, projectConfig, Logger, pkgParams, null); if (!this.updatePageIntentObj.has(`@normalized:${recordName}`)) { this.updatePageIntentObj.set(`@normalized:${recordName}`, []); } } } private handleIntent(node: ts.ClassDeclaration, checker: ts.TypeChecker, filepath: string, metaInfo: Object = {}): void { this.checker = checker; this.currentFilePath = filepath; if (!filepath.endsWith('.ets')) { const errorMessage: string = 'The intent decorator can only be used in .ets files.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110001', description: 'InsightIntent Compiler Error', solutions: ['Move it to an .ets file'] }); return; } const pkgParams: object = { pkgName: metaInfo.pkgName, pkgPath: metaInfo.pkgPath }; node.modifiers.forEach(decorator => { this.handleDecorator(node, decorator, filepath, pkgParams); }); } private handleDecorator(node: ts.ClassDeclaration, decorator: ts.Decorator, filepath: string, pkgParams: object): void { const expr: ts.Expression = decorator.expression; if (!expr || !ts.isCallExpression(expr)) { return; } const argumentKind: ts.SyntaxKind | undefined = expr.arguments[0]?.kind; if (argumentKind && argumentKind === ts.SyntaxKind.NullKeyword || argumentKind && argumentKind === ts.SyntaxKind.UndefinedKeyword) { return; } const symbol: ts.Symbol = this.checker.getTypeAtLocation(decorator.expression.expression)?.getSymbol(); const declarations: ts.Declaration[] | undefined = symbol?.getDeclarations(); if (!declarations || declarations.length === 0) { return; } const decoratorSourceFile: string = declarations[0].getSourceFile().fileName; const isGlobalPathFlag: boolean = this.isGlobalPath(decoratorSourceFile); if (!isGlobalPathFlag) { return; } if (!projectConfig.pkgContextInfo) { const errorMessage: string = 'Failed to generate standard OHMUrl.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10111027', description: 'InsightIntent Compiler Error', solutions: ['Set useNormalizedOHMUrl to true in build-profile.json5'] }); return; } const Logger: IntentLogger = IntentLogger.getInstance(); const recordName: string = getNormalizedOhmUrlByFilepath(filepath, projectConfig, Logger, pkgParams, null); const intentObj: object = { 'decoratorFile': `@normalized:${recordName}`, 'decoratorClass': node.name.text }; const originalDecorator: string = '@' + decorator.expression.expression.getText(); if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR) { this.handleLinkDecorator(intentObj, node, decorator); } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_ENTRY) { this.handleEntryDecorator(intentObj, node, decorator, pkgParams); } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_FUNCTION) { this.handleMethodDecorator(intentObj, node, decorator); } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_PAGE) { this.handlePageDecorator(intentObj, node, decorator, pkgParams); } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_ENTITY) { this.handleEntityDecorator(intentObj, node, decorator, pkgParams); } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_FORM) { this.handleFormDecorator(intentObj, node, decorator, pkgParams); } } private handleFormDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; Object.assign(intentObj, { 'bundleName': projectConfig.bundleName, 'moduleName': projectConfig.moduleName, 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_FORM }); this.analyzeDecoratorArgs(args, intentObj, intentFormInfoChecker); const properties: Record = this.parseClassNode(node, intentObj.intentName, COMPONENT_USER_INTENTS_DECORATOR_FORM); this.processFormInfo(node, this.currentFilePath, pkgParams, intentObj); this.schemaValidateSync(properties, intentObj.parameters); this.createObfuscation(node); if (this.isUpdateCompile) { this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); } this.intentData.push(intentObj); } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private handleEntityDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const entityClassName: string = this.checker.getTypeAtLocation(node).getSymbol().getName(); const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; Object.assign(intentObj, { 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTITY, 'className': intentObj.decoratorClass }); delete intentObj.decoratorClass; this.analyzeDecoratorArgs(args, intentObj, IntentEntityInfoChecker); const properties: Record = this.parseClassNode(node, undefined, COMPONENT_USER_INTENTS_DECORATOR_ENTITY); const entityId: string = this.getEntityId(node); Object.assign(properties, { 'entityId': entityId }); Object.assign(intentObj, { 'entityId': entityId }); this.schemaValidateSync(properties, intentObj.parameters); this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTITY); this.createObfuscation(node); if (this.entityMap.has(entityClassName)) { const errorMessage: string = 'Multiple @InsightIntentEntity decorators applied to the same class.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110020', description: 'InsightIntent Compiler Error', solutions: ['Remove duplicates'] }); return; } else { this.entityMap.set(entityClassName, intentObj); } } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private handlePageDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const expr: ts.Expression = decorator.expression; if (ts.isClassDeclaration(node)) { const errorMessage: string = `@InsightIntentPage must be applied to a struct page.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110016', description: 'InsightIntent Compiler Error', solutions: ['Decorate a struct page with @InsightIntentPage'] }); return; } if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; Object.assign(intentObj, { 'bundleName': projectConfig.bundleName, 'moduleName': projectConfig.moduleName, 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_PAGE }); this.analyzeDecoratorArgs(args, intentObj, IntentPageInfoChecker); this.validatePagePath(intentObj, pkgParams); this.createObfuscation(node); if (this.isUpdateCompile) { this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); } this.intentData.push(intentObj); } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private handleMethodDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); if (!isExported) { const errorMessage: string = 'The class decorated with @InsightIntentFunction must be exported.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110014', description: 'InsightIntent Compiler Error', solutions: ['Add an export statement'] }); return; } const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { Object.assign(intentObj, { 'bundleName': projectConfig.bundleName, 'moduleName': projectConfig.moduleName, 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_METHOD }); const methodParameters: methodParametersInfo[] = this.parseClassMethods(node, COMPONENT_USER_INTENTS_DECORATOR_METHOD); methodParameters?.forEach(methodDecorator => { const functionName: string = methodDecorator.functionName; const methodArgs: ts.NodeArray = methodDecorator.args; const properties: Record = methodDecorator.parameters; const functionParamList: Array = Object.keys(properties); const methodObj: object = Object.assign({}, intentObj, { functionName, 'functionParamList': functionParamList }); this.analyzeDecoratorArgs(methodArgs, methodObj, intentMethodInfoChecker); if (this.isUpdateCompile) { this.updatePageIntentObj.get(methodObj.decoratorFile).push(methodObj); } this.intentData.push(methodObj); }); this.createObfuscation(node); } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private handleLinkDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; this.analyzeDecoratorArgs(args, intentObj, IntentLinkInfoChecker); Object.assign(intentObj, { 'bundleName': projectConfig.bundleName, 'moduleName': projectConfig.moduleName, 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR }); this.createObfuscation(node); if (this.isUpdateCompile) { this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); } this.intentData.push(intentObj); } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private handleEntryDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); const isDefault: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DefaultKeyword); if (!(isExported && isDefault)) { const errorMessage: string = 'The class decorated with @InsightIntentEntry must be exported as default.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110019', description: 'InsightIntent Compiler Error', solutions: ['Use the \'export default\' syntax'] }); return; } const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; Object.assign(intentObj, { 'bundleName': projectConfig.bundleName, 'moduleName': projectConfig.moduleName, 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTRY }); this.analyzeDecoratorArgs(args, intentObj, intentEntryInfoChecker); const properties: Record = this.parseClassNode(node, intentObj.intentName, COMPONENT_USER_INTENTS_DECORATOR_ENTRY); this.schemaValidateSync(properties, intentObj.parameters); this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTRY); this.createObfuscation(node); this.processExecuteModeParam(intentObj); if (this.isUpdateCompile) { this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); } this.intentData.push(intentObj); } else { const errorMessage: string = 'Decorators must be called as functions.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110002', description: 'InsightIntent Compiler Error', solutions: ['Add parentheses after the decorator name'] }); return; } } private processFormInfo(node: ts.ClassDeclaration, formClassPath: string, pkgParams: object, intentObj: object): void { if (this.moduleJsonInfo.size === 0 && pkgParams.pkgPath) { this.readModuleJsonInfo(pkgParams); } const extensionAbilities: object[] = this.moduleJsonInfo.get('extensionAbilities'); const bindFormInfo: object = extensionAbilities.find(extensionInfo => { const formSrcEntryPath: string = path.join(pkgParams.pkgPath, 'src', 'main', extensionInfo.srcEntry); return formSrcEntryPath === formClassPath && extensionInfo.type === 'form'; }); this.verifyFormName(bindFormInfo, intentObj); const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); const isDefault: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DefaultKeyword); if (!(bindFormInfo && isExported && isDefault)) { const errorMessage: string = '@InsightIntentForm must be applied to formExtensionAbility.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110022', description: 'InsightIntent Compiler Error', solutions: ['Decorate the formExtensionAbility class with @InsightIntentForm'] }); return; } } private verifyFormName(bindFormInfo: object, intentObj: object): void { if (!bindFormInfo) { return; } let formNameFound: boolean = false; intentObj.abilityName = bindFormInfo.name; bindFormInfo.metadata?.forEach(metaData => { const formConfigName = `${metaData.resource.split(':').pop()}.json`; const formConfigPath = path.join(projectConfig.aceProfilePath, formConfigName); if (!fs.existsSync(formConfigPath)) { return; } const formData = fs.readFileSync(formConfigPath, 'utf8'); const formConfigs = JSON.parse(formData).forms; if (formConfigs?.some(form => form.name === intentObj.formName)) { formNameFound = true; } }); if (!formNameFound) { const errorMessage: string = 'formName in @InsightIntentForm must match the widget name registered in formExtensionAbility.'; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110023', description: 'InsightIntent Compiler Error', solutions: ['Update formName to match the registered widget name'] }); return; } } private readModuleJsonInfo(pkgParams: object): void { const moduleJsonPath: string = path.join(pkgParams.pkgPath, 'src/main', 'module.json5'); if (!fs.existsSync(moduleJsonPath)) { const errorMessage: string = `The module.json5 file is missing.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110024', description: 'InsightIntent Compiler Error', solutions: ['Check the expected paths (typically entry/src/main/config.json or module.json5) and restore the file'] }); return; } if (!projectConfig.modulePathMap) { return; } const jsonStr: string = fs.readFileSync(moduleJsonPath, 'utf8'); const obj: object = json5.parse(jsonStr); if (obj.module?.abilities) { this.moduleJsonInfo.set('abilities', obj.module.abilities); } if (obj.module?.extensionAbilities) { this.moduleJsonInfo.set('extensionAbilities', obj.module.extensionAbilities); } } private validatePagePath(intentObj: object, pkgParams: object): void { if (pkgParams.pkgPath) { const normalPagePath: string = path.join(pkgParams.pkgPath, 'src/main', intentObj.pagePath + '.ets'); if (!fs.existsSync(normalPagePath)) { const errorMessage: string = `PagePath in @InsightIntentPage does not match the actual page path.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110017', description: 'InsightIntent Compiler Error', solutions: ['Verify the file path'] }); return; } else { const Logger: IntentLogger = IntentLogger.getInstance(); intentObj.pagePath = '@normalized:' + getNormalizedOhmUrlByFilepath(normalPagePath, projectConfig, Logger, pkgParams, null); } } } private isGlobalPath(parentFilePath: string): boolean { return globalModulePaths?.some(globalPath => { const normalizedParent: string = path.normalize(parentFilePath).replace(/\\/g, '/'); const normalizedGlobalPath: string = path.normalize(globalPath).replace(/\\/g, '/'); return normalizedParent.startsWith(normalizedGlobalPath); }); } private analyzeBaseClass(node: ts.ClassDeclaration, pkgParams: object, intentObj: object, decoratorFlag: string): void { const interfaces: ts.ExpressionWithTypeArguments[] = []; if (decoratorFlag === COMPONENT_USER_INTENTS_DECORATOR_ENTRY) { node.heritageClauses?.forEach(clause => { if (clause.token === ts.SyntaxKind.ExtendsKeyword) { interfaces.push(...clause.types); } }); this.processEntryBaseClass(interfaces, intentObj, pkgParams); } else if (decoratorFlag === COMPONENT_USER_INTENTS_DECORATOR_ENTITY) { node.heritageClauses?.forEach(clause => { if (clause.token === ts.SyntaxKind.ImplementsKeyword || clause.token === ts.SyntaxKind.ExtendsKeyword) { interfaces.push(...clause.types); } }); if (interfaces.length > 0) { const parentNode: ts.ExpressionWithTypeArguments = interfaces[0]; this.analyzeClassHeritage(parentNode, node, pkgParams, intentObj); } else { const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110021', description: 'InsightIntent Compiler Error', solutions: ['Add the implementation or inherit from a base intent entity'] }); return; } } } private processEntryBaseClass(interfaces: ts.ExpressionWithTypeArguments[], intentObj: object, pkgParams: object): void { if (interfaces.length > 0) { const parentNode: ts.ExpressionWithTypeArguments = interfaces[0]; const parentClassName: string = parentNode.expression.getText(); const parentNodeSymbol: ts.Symbol = this.checker.getTypeAtLocation(parentNode).getSymbol(); const parentFilePath: string = parentNodeSymbol.getDeclarations()?.[0].getSourceFile().fileName; const isGlobalPathFlag: boolean = this.isGlobalPath(parentFilePath); if (!(isGlobalPathFlag && parentClassName === 'InsightIntentEntryExecutor')) { const errorMessage: string = `Classes decorated with @InsightIntentEntry must inherit from InsightIntentEntryExecutor.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110018', description: 'InsightIntent Compiler Error', solutions: ['Add the inheritance'] }); return; } const logger: IntentLogger = IntentLogger.getInstance(); const parentRecordName: string = getNormalizedOhmUrlByFilepath(parentFilePath, projectConfig, logger, pkgParams, null); const recordPath: string = isGlobalPathFlag ? `sdk` : `@normalized:${parentRecordName}`; this.collectClassInheritanceInfo(parentNode, intentObj, parentClassName, recordPath); } else { const errorMessage: string = `Classes decorated with @InsightIntentEntry must inherit from InsightIntentEntryExecutor.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110018', description: 'InsightIntent Compiler Error', solutions: ['Add the inheritance'] }); return; } } private hasModifier(node: ts.Node, modifier: ts.SyntaxKind): boolean { return (node.modifiers || []).some(m => m.kind === modifier); } private parseClassMethods(classNode: ts.ClassDeclaration, decoratorType: string): methodParametersInfo[] { const methodsArr: methodParametersInfo[] = []; for (const member of classNode.members) { if (!ts.isMethodDeclaration(member)) { continue; } const decorator: ts.ModifierLike = member.modifiers?.find(modifier => { if (!ts.isDecorator(modifier)) { return false; } let decoratorName: string | undefined; if (ts.isCallExpression(modifier.expression)) { decoratorName = `@${modifier.expression.expression.getText()}`; } return decoratorName === decoratorType; }); if (decorator && ts.isCallExpression(decorator.expression)) { if (!this.hasModifier(member, ts.SyntaxKind.StaticKeyword)) { const errorMessage: string = `Methods decorated with @InsightIntentFunctionMethod must be static.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110015', description: 'InsightIntent Compiler Error', solutions: ['Change the method to static'] }); return undefined; } let parameters: Record = {}; member.parameters.forEach(param => { const paramName: string = param.name.getText(); parameters[paramName] = this.checker.typeToString( this.checker.getTypeAtLocation(param), param, ts.TypeFormatFlags.NoTruncation ); }); const obj: methodParametersInfo = { functionName: member.name.getText(), parameters: parameters, args: decorator.expression.arguments }; methodsArr.push(obj); } } return methodsArr; } private analyzeClassHeritage( parentNode: ts.ExpressionWithTypeArguments, node: ts.ClassDeclaration, pkgParams: object, intentObj: object ): void { const parentSymbol: ts.Symbol = this.checker.getTypeAtLocation(parentNode).getSymbol(); let parentClassName: string; node.heritageClauses.forEach(clause => { if (clause.token === ts.SyntaxKind.ExtendsKeyword) { parentClassName = parentNode.expression.getText(); } else if (clause.token === ts.SyntaxKind.ImplementsKeyword) { parentClassName = parentSymbol.getName(); } }); intentObj.parentClassName = parentClassName; const parentFilePath: string = parentSymbol.getDeclarations()?.[0].getSourceFile().fileName; const logger: IntentLogger = IntentLogger.getInstance(); const baseClassName: string = this.checker.getTypeAtLocation(node).getSymbol().getName(); const baseFilePath: string = node.getSourceFile().fileName; const baseRecordName: string = getNormalizedOhmUrlByFilepath(baseFilePath, projectConfig, logger, pkgParams, null); const isGlobalPathFlag: boolean = this.isGlobalPath(parentFilePath); const parentRecordName: string = getNormalizedOhmUrlByFilepath(parentFilePath, projectConfig, logger, pkgParams, null); if (isGlobalPathFlag) { if (parentClassName !== 'IntentEntity') { const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110021', description: 'InsightIntent Compiler Error', solutions: ['Add the implementation or inherit from a base intent entity'] }); return; } this.EntityHeritageClassSet.add(parentClassName + '_' + `sdk`); } else { this.EntityHeritageClassSet.add(parentClassName + '_' + `@normalized:${parentRecordName}`); this.EntityExtendsMap.set(baseClassName, parentClassName); } this.heritageClassSet.add(baseClassName + '_' + `@normalized:${baseRecordName}`); } private collectClassInheritanceInfo( parentNode: ts.ExpressionWithTypeArguments, intentObj: object, parentClassName: string, recordPath: string ): void { const ClassInheritanceInfo: object = { 'parentClassName': parentClassName, 'definitionFilePath': recordPath, 'generics': [] }; if (parentNode.typeArguments?.length > 0) { parentNode.typeArguments.forEach((arg): void => { this.getInheritanceInfoByTypeNode(arg, ClassInheritanceInfo, intentObj); }); } Object.assign(intentObj, { 'ClassInheritanceInfo': ClassInheritanceInfo }); } private getInheritanceInfoByTypeNode(arg: ts.TypeNode, ClassInheritanceInfo: object, intentObj: object): void { const generic = {}; const genericType: ts.Type = this.checker.getTypeAtLocation(arg); let genericName: string; let genericSource: string | undefined; let recordGenericSource: string; const genericSymbol: ts.Symbol | undefined = genericType.getSymbol(); if (genericSymbol) { genericName = genericSymbol.getName(); genericSource = genericSymbol.declarations?.[0]?.getSourceFile().fileName; recordGenericSource = path.relative(projectConfig.moduleName, genericSource).replace(/\\/g, '/'); if (this.entityOwnerMap.has(intentObj.intentName)) { const entityNames: string[] = this.entityOwnerMap.get(intentObj.intentName); entityNames.push(genericName); this.entityOwnerMap.set(intentObj.intentName, entityNames); } else { this.entityOwnerMap.set(intentObj.intentName, [genericName]); } } else { genericName = this.checker.typeToString(genericType); const parentTypeNode: ts.Node = arg.parent; if (ts.isTypeReferenceNode(parentTypeNode)) { const contextualType: ts.Type = this.checker.getTypeAtLocation(parentTypeNode); const symbol: ts.Symbol = contextualType?.getSymbol(); genericSource = symbol?.declarations?.[0]?.getSourceFile().fileName; } if (!genericSource && this.isPrimitiveType(genericType)) { recordGenericSource = 'lib.es5.d.ts'; } } Object.assign(generic, { 'typeName': genericName, 'definitionFilePath': recordGenericSource }); ClassInheritanceInfo.generics.push(generic); } private isPrimitiveType(type: ts.Type): boolean { return ( (type.flags & ts.TypeFlags.StringLike) || (type.flags & ts.TypeFlags.NumberLike) || (type.flags & ts.TypeFlags.BooleanLike) ) !== 0; } private parseClassNode(node: ts.ClassDeclaration, intentName: string, decoratorType: string): Record { const mergedObject: Record = {}; const type: ts.Type = this.checker.getTypeAtLocation(node); const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type); propertiesOfType.forEach((prop) => { const objItem: Record = this.processProperty(prop, intentName, decoratorType); Object.assign(mergedObject, objItem); }); return mergedObject; } private getEntityId(node: ts.ClassDeclaration): string { let entityId: string; const type: ts.Type = this.checker.getTypeAtLocation(node); const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type); propertiesOfType.forEach((prop) => { if (prop.getName() === 'entityId') { const declaration: ts.Declaration = prop.getDeclarations()?.[0]; if (declaration) { const initializer = ts.isIdentifier(declaration.initializer) ? this.checker.getSymbolAtLocation(declaration.initializer)?.valueDeclaration?.initializer : declaration.initializer; entityId = initializer.text; } } }); return entityId; } private processProperty(prop: ts.Symbol, intentName: string, decoratorType: string): Record { const propType: ts.Type = this.checker.getTypeOfSymbol(prop); const { category } = this.getTypeCategory(propType); const obj: Record = {}; const propName: string = prop.getName(); const entryBlackList: string[] = ['executeMode', 'context', 'windowStage', 'uiExtensionSession', 'onExecute']; const formBlackList: string[] = ['context']; if (decoratorType === COMPONENT_USER_INTENTS_DECORATOR_ENTRY && entryBlackList.includes(propName)) { return obj; } else if (decoratorType === COMPONENT_USER_INTENTS_DECORATOR_ENTRY && formBlackList.includes(propName)) { return obj; } const tempschemaVerifyType: schemaVerifyType = { type: '', isEntity: false }; if (category === 'object') { tempschemaVerifyType.type = 'object'; if (this.isEntity(propType, intentName)) { tempschemaVerifyType.isEntity = true; } } else if (category === 'array') { if (this.isEntity(propType, intentName)) { tempschemaVerifyType.type = 'array'; tempschemaVerifyType.isEntity = true; } } else { tempschemaVerifyType.type = this.checker.typeToString(propType); } Object.assign(obj, { [propName]: tempschemaVerifyType }); return obj; } private isEntity(propType: ts.Type, intentName: string): boolean { let propDeclaration: ts.Declaration; let elementType: ts.Type | undefined; const typeSymbol: ts.Symbol = propType.getSymbol(); if (this.isArrayType(propType)) { elementType = (propType as ts.TypeReference).typeArguments?.[0]; propDeclaration = elementType.getSymbol()?.getDeclarations()[0]; } else { propDeclaration = typeSymbol.getDeclarations()?.[0]; } if (!propDeclaration) { return false; } return propDeclaration.modifiers?.some(decorator => { if (!ts.isDecorator(decorator)) { return false; } let decoratorName: string | undefined; if (ts.isCallExpression(decorator.expression)) { decoratorName = `@${decorator.expression.expression.getText()}`; } if (decoratorName === '@InsightIntentEntity') { const typeSymbol: ts.Symbol = propType.getSymbol(); const propertyClassName: string = typeSymbol.getName(); if (this.entityOwnerMap.has(intentName)) { const entityNames: string[] = this.entityOwnerMap.get(intentName); entityNames.push(propertyClassName); this.entityOwnerMap.set(intentName, entityNames); } else { this.entityOwnerMap.set(intentName, [propertyClassName]); } return true; } return false; }); } private getTypeCategory(type: ts.Type): { category: 'array' | 'object'; } { const flags: ts.TypeFlags = type.getFlags(); const valueDeclaration: ts.Declaration | undefined = type.getSymbol()?.valueDeclaration; const isEnum: boolean = valueDeclaration ? ts.isEnumDeclaration(valueDeclaration) : false; const isPrimitive: boolean = !!(flags & ts.TypeFlags.StringLike) || !!(flags & ts.TypeFlags.NumberLike) || !!(flags & ts.TypeFlags.BooleanLike) || !!(flags & ts.TypeFlags.Null) || !!(flags & ts.TypeFlags.Undefined); const isArray: boolean = this.isArrayType(type); const isObject: boolean = !isPrimitive && !isArray && (!!(flags & ts.TypeFlags.Object) || isEnum); let category: 'array' | 'object'; if (isArray) { category = 'array'; } else if (isObject) { category = 'object'; } return { category }; } private isArrayType(type: ts.Type): boolean { let isArray: boolean; const symbol: ts.Symbol | undefined = type.getSymbol(); const flags: ts.TypeFlags = type.getFlags(); if (symbol) { isArray = symbol.getName() === 'Array'; } else { isArray = !!(flags & ts.TypeFlags.Object) && !!(type as ts.ObjectType).objectFlags && ts.ObjectFlags.Reference && ((type as ts.TypeReference).target.getSymbol()?.getName() === 'Array'); } return isArray; } private removeDecorator(node: ts.ClassDeclaration, decoratorNames: string[]): ts.ClassDeclaration { const filteredModifiers: ts.ModifierLike[] = node.modifiers.filter(decorator => { if (!ts.isDecorator(decorator)) { return true; } let decoratorName: string | undefined; if (ts.isCallExpression(decorator.expression)) { decoratorName = `@${decorator.expression.expression.getText()}`; } return !decoratorNames.includes(decoratorName); }); const updatedMembers: ts.ClassElement[] = node.members.map(member => { return this.reduceMembers(member); }); return ts.factory.updateClassDeclaration( node, filteredModifiers, node.name, node.typeParameters, node.heritageClauses, ts.factory.createNodeArray(updatedMembers) ); } private reduceMembers(member: ts.ClassElement): ts.ClassElement { if (ts.isMethodDeclaration(member) && this.hasModifier(member, ts.SyntaxKind.StaticKeyword)) { const memberModifiers: ts.ModifierLike[] = (member.modifiers ?? []).filter(decorator => { if (!ts.isDecorator(decorator)) { return true; } let decoratorName: string | undefined; if (ts.isCallExpression(decorator.expression)) { decoratorName = `@${decorator.expression.expression.getText()}`; } return decoratorName !== COMPONENT_USER_INTENTS_DECORATOR_METHOD; }); if (memberModifiers.length !== (member.modifiers?.length ?? 0)) { return ts.factory.updateMethodDeclaration( member, memberModifiers, member.asteriskToken, member.name, member.questionToken, member.typeParameters, member.parameters, member.type, member.body! ); } } return member; } private isSymbolConstant(symbol: ts.Symbol): boolean { const declaration: Declaration = symbol.valueDeclaration; if (!this.isConstVariable(declaration)) { return false; } const varDecl: ts.VariableDeclaration = declaration as ts.VariableDeclaration; const initializer: Expression = varDecl.initializer; return initializer ? this.isConstantExpression(initializer) : false; } private isConstVariable(node: ts.Node | undefined): node is ts.VariableDeclaration { if (!node || !ts.isVariableDeclaration(node)) { return false; } const varList: VariableDeclarationList | CatchClause = node.parent; return !!varList && ts.isVariableDeclarationList(varList) && (varList.flags & ts.NodeFlags.Const) !== 0; } private isConstantExpression(node: ts.Node): boolean { let flag: boolean = true; if (ts.isLiteralExpression(node) || node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) { flag = true; } if (ts.isIdentifier(node)) { const symbol: Symbol | undefined = this.checker.getSymbolAtLocation(node); flag = symbol ? this.isSymbolConstant(symbol) : false; } if (ts.isArrayLiteralExpression(node)) { flag = node.elements.every(element => this.isConstantExpression(element)); } if (ts.isObjectLiteralExpression(node)) { flag = node.properties.every(property => { if (ts.isPropertyAssignment(property)) { const nameIsConst: boolean = !ts.isComputedPropertyName(property.name); return nameIsConst && this.isConstantExpression(property.initializer); } return false; }); } if (ts.isCallExpression(node) && node.expression.getText() === '$r') { flag = node.arguments.every(node => { return ts.isStringLiteral(node); }); } if (!flag) { const errorMessage: string = `Decorator parameters must be compile-time constants.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110000', description: 'InsightIntent Compiler Error', solutions: ['Use a fixed value (such as a string literal) instead of a variable'] }); return false; } return flag; } private validateRequiredIntentLinkInfo( node: ts.ObjectLiteralExpression, paramCheckFields: ParamChecker ): void { const existingParams: Set = new Set(); const requiredFields: (keyof T)[] = paramCheckFields.requiredFields; const nestedCheckers: Map> = paramCheckFields.nestedCheckers; const allowedFields: Set = paramCheckFields.allowFields; const paramValidators: Record boolean> = paramCheckFields.paramValidators; for (const prop of node.properties) { this.validateFields(prop, allowedFields, paramValidators); existingParams.add(prop.name.text); if (nestedCheckers && nestedCheckers.has(prop.name.text)) { this.validateSelfParamFields(prop, nestedCheckers); } } const missingFields: (keyof T)[] = requiredFields.filter(f => !existingParams.has(f)); if (missingFields.length > 0) { const errorMessage: string = `Required parameters are missing for the decorator.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110003', description: 'InsightIntent Compiler Error', solutions: ['Add the required parameters as specified in the error message'] }); return; } } private validateSelfParamFields(prop: ts.Node, nestedCheckers: Map>): void { const checker: ParamChecker = nestedCheckers.get(prop.name.text); if (ts.isArrayLiteralExpression(prop.initializer)) { prop.initializer.elements.every(elem => { if (ts.isIdentifier(elem)) { const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(elem); const declaration: ts.Declaration = symbol?.valueDeclaration; this.validateRequiredIntentLinkInfo(declaration.initializer, checker); } else { this.validateRequiredIntentLinkInfo(elem, checker); } }); } else if (ts.isIdentifier(prop)) { const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(prop); const declaration: ts.Declaration = symbol?.valueDeclaration; this.validateRequiredIntentLinkInfo(declaration.initializer, checker); } else { this.validateRequiredIntentLinkInfo(prop, checker); } } private validateFields( prop: ts.Node, allowedFields: Set, paramValidators: Record boolean> ): void { const paramName: keyof T = prop.name.text; if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { if (!allowedFields.has(paramName)) { const errorMessage: string = `Unsupported parameters found in the decorator.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110005', description: 'InsightIntent Compiler Error', solutions: ['Remove any parameters that are not supported.'] }); return; } const validator: Function = paramValidators[paramName]; if (ts.isIdentifier(prop.initializer)) { const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(prop.initializer); const declaration: ts.Declaration = symbol?.valueDeclaration; if (validator && !validator(declaration?.initializer)) { const errorMessage: string = `The parameter type does not match the decorator's requirement.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110004', description: 'InsightIntent Compiler Error', solutions: ['Adjust the type to match the expected type.'] }); return; } } else { if (validator && !validator(prop.initializer)) { const errorMessage: string = `The parameter type does not match the decorator's requirement.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110004', description: 'InsightIntent Compiler Error', solutions: ['Adjust the type to match the expected type.'] }); return; } } } } private analyzeDecoratorArgs(args: ts.NodeArray, intentObj: object, paramChecker: ParamChecker): void { args.forEach(arg => { if (ts.isIdentifier(arg)) { const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(arg); const declaration: ts.Declaration = symbol?.valueDeclaration; this.validateRequiredIntentLinkInfo(declaration.initializer, paramChecker); } else { this.validateRequiredIntentLinkInfo(arg, paramChecker); } const res: StaticValue = this.parseStaticObject(arg); Object.assign(intentObj, res); this.collectSchemaInfo(intentObj); }); } private createObfuscation(classNode: ts.Node): void { ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.globalNames.add(classNode.name.text); const isExported: boolean = classNode.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); if (isExported) { ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.propertyNames.add(classNode.name.text); } classNode.members.forEach(member => { if (ts.isPropertyDeclaration(member) && member.name || ts.isFunctionDeclaration(member) || ts.isMethodDeclaration(member) || ts.isGetAccessor(member) || ts.isSetAccessor(member)) { const propName: string = member.name.getText(); ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.propertyNames.add(propName); } }); } private parseStaticObject(node: ts.Node, visited: Set = new Set()): StaticValue | undefined { if (visited.has(node)) { const errorMessage: string = `Circular dependencies detected in decorator parameters.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110006', description: 'InsightIntent Compiler Error', solutions: ['Refactor the data structure by extracting common variables or using ID references to avoid nesting'] }); return undefined; } visited.add(node); const literalValue: StaticValue | undefined = this.parseLiteralValue(node); if (literalValue !== undefined) { return literalValue; } if (ts.isIdentifier(node)) { const isStatic: boolean = this.isConstantExpression(node); if (isStatic) { const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(node); const declaration: ts.Declaration = symbol?.valueDeclaration; return this.parseStaticObject(declaration.initializer, visited); } } if (ts.isArrayLiteralExpression(node)) { return this.processArrayElements(node.elements); } if (ts.isObjectLiteralExpression(node)) { return this.processObjectElements(node); } if (ts.isCallExpression(node) && node.expression.getText() === '$r') { const isStatic: boolean = this.isConstantExpression(node); if (!isStatic) { return undefined; } return node.getText(); } if (ts.isPropertyAccessExpression(node)) { return this.processEnumElement(node); } const errorMessage: string = `Unsupported parameters found in the decorator.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110005', description: 'InsightIntent Compiler Error', solutions: ['Remove any parameters that are not supported'] }); return undefined; } private parseLiteralValue(node: ts.Node): StaticValue | undefined { if (ts.isStringLiteral(node)) { return node.text; } if (ts.isNumericLiteral(node)) { return parseFloat(node.text); } if (node.kind === ts.SyntaxKind.TrueKeyword) { return true; } if (node.kind === ts.SyntaxKind.FalseKeyword) { return false; } if (node.kind === ts.SyntaxKind.NullKeyword) { return null; } if (node.kind === ts.SyntaxKind.UndefinedKeyword) { return undefined; } return undefined; } private processEnumElement(node: ts.PropertyAccessExpression): string { const enumValue: string = node?.getText().split('.').pop(); const executeModeEnum: Map = new Map(); executeModeEnum.set('UI_ABILITY_FOREGROUND', '0'); executeModeEnum.set('UI_ABILITY_BACKGROUND', '1'); executeModeEnum.set('UI_EXTENSION_ABILITY', '2'); executeModeEnum.set('SERVICE_EXTENSION_ABILITY', '3'); const paramCategoryEnum: Map = new Map(); paramCategoryEnum.set('LINK', 'link'); paramCategoryEnum.set('WANT', 'want'); if (executeModeEnum.has(enumValue)) { return executeModeEnum.get(enumValue); } else if (paramCategoryEnum.has(enumValue)) { return paramCategoryEnum.get(enumValue); } else { const errorMessage: string = `Unsupported parameters found in the decorator.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110005', description: 'InsightIntent Compiler Error', solutions: ['Remove any parameters that are not supported'] }); return ''; } } private processObjectElements(elements: ts.ObjectLiteralExpression): { [key: string]: StaticValue } { const obj: { [key: string]: StaticValue } = {}; for (const prop of elements.properties) { if (ts.isPropertyAssignment(prop)) { const key: string = this.parsePropertyKey(prop.name); const value: StaticValue = this.parseStaticObject(prop.initializer); if (key !== undefined && value !== undefined) { obj[key] = value; } } if (ts.isSpreadAssignment(prop)) { const spreadObj: StaticValue = this.parseStaticObject(prop.expression); if (typeof spreadObj === 'object' && spreadObj !== null) { Object.assign(obj, spreadObj); } } } return obj; } private processArrayElements(elements: readonly ts.Node[]): StaticValue[] { const parsedElements: StaticValue[] = []; elements.forEach((element) => { if (ts.isSpreadElement(element)) { const spreadValue: StaticValue = this.parseStaticObject(element.expression); if (Array.isArray(spreadValue)) { parsedElements.push(...spreadValue); } } else { const value: StaticValue = this.parseStaticObject(element); parsedElements.push(value); } }); return parsedElements; } private parsePropertyKey(node: ts.PropertyName): string | undefined { if (ts.isLiteralExpression(node)) { return node.text; } if (ts.isIdentifier(node)) { return node.text; } return undefined; } private processExecuteModeParam(intentObj: object): void { if (intentObj.executeMode) { intentObj.executeMode.forEach((item: string, index: number) => { if (item === '0') { intentObj.executeMode[index] = 'foreground'; } if (item === '1') { intentObj.executeMode[index] = 'background'; } if (item === '2') { intentObj.executeMode[index] = 'uiextension'; } if (item === '3') { intentObj.executeMode[index] = 'serviceextension'; } }); } } private collectSchemaInfo(intentObj: object): void { if (intentObj.schema) { const schemaPath: string = path.join( __dirname, '../../insight_intents/schema', `${intentObj.schema}_${intentObj.intentVersion}.json` ); if (fs.existsSync(schemaPath)) { const schemaContent: string = fs.readFileSync(schemaPath, 'utf-8'); const schemaObj: object = JSON.parse(schemaContent); intentObj.parameters = schemaObj.parameters; intentObj.llmDescription = schemaObj.llmDescription; intentObj.keywords = schemaObj.keywords; intentObj.intentName = schemaObj.intentName; intentObj.result = schemaObj.result; intentObj.domain = schemaObj.domain; } } } private verifyInheritanceChain(): void { this.EntityHeritageClassSet.forEach(entityClassInfo => { if (!this.heritageClassSet.has(entityClassInfo)) { const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110021', description: 'InsightIntent Compiler Error', solutions: ['Add the implementation or inherit from a base intent entity'] }); return; } }); } private schemaValidationRequiredRule(schemaData: Record, schemaObj: object): void { const reqData: Map = new Map(); schemaObj.required.forEach(key => reqData.set(key, true)); if (schemaObj.properties) { const paramsSchema: object = schemaObj.properties; const keyArr: string[] = Object.keys(paramsSchema); keyArr.forEach(key => { if (!schemaData[key] && reqData.get(key)) { const errorMessage: string = `A required field in the class property is missing.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110008', description: 'InsightIntent Compiler Error', solutions: ['Add the required field as specified by the JSON Schema'] }); return; } }); } } private schemaPropertiesValidation(schemaData: Record, schemaObj: object): void { if (schemaObj.properties) { Object.entries(schemaObj.properties).forEach(([key, value]) => { if ((schemaData[key]?.type && value.type !== schemaData[key].type) || value.type === 'object' && schemaData[key]?.isEntity === false) { const errorMessage: string = `The field type of the class property does not match the JSON Schema.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110009', description: 'InsightIntent Compiler Error', solutions: ['Correct the type to match the requirement'] }); return; } }); } } private schemaValidateRules(schemaData: Record, schemaObj: object): void { const schemaKeys: string[] = Object.keys(schemaData); if (schemaObj.oneOf) { let count: number = 0; const requiredOne: string[][] = schemaObj.oneOf.map(item => item.required); requiredOne.forEach(val => { const isContain: boolean = val.every((item): boolean => { return schemaKeys.includes(item); }); if (isContain) { count++; } }); if (count !== 1) { const errorMessage: string = `The class property parameter violates the oneOf/anyOf validation rules in the JSON Schema.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110010', description: 'InsightIntent Compiler Error', solutions: ['Modify it to satisfy the rules'] }); return; } } if (schemaObj.anyOf) { let count: number = 0; const requiredAny: string[][] = schemaObj.anyOf.map(item => item.required); requiredAny.forEach(val => { const isContain: boolean = val.every((item): boolean => { return schemaKeys.includes(item); }); if (isContain) { count++; } }); if (count === 0) { const errorMessage: string = `The class property parameter violates the oneOf/anyOf validation rules in the JSON Schema.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110010', description: 'InsightIntent Compiler Error', solutions: ['Modify it to satisfy the rules'] }); return; } } } private schemaValidateSync(schemaData: Record, schemaObj: object): void { if (!schemaObj) { return; } if (schemaObj.additionalProperties === false) { this.schemaAdditionalPropertiesValidation(schemaData, schemaObj.properties); } if (schemaObj.items && schemaObj.items.type === 'array') { this.schemaValidateSync(schemaData, schemaObj.items.items); } if (schemaObj.type !== 'object') { const errorMessage: string = `The root type of the JSON Schema for Parameters must be object.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110007', description: 'InsightIntent Compiler Error', solutions: ['Change the top-level definition to {"type":"object", ...}'] }); return; } if (schemaObj.properties) { const items: string[] = Object.keys(schemaObj.properties); if (items.length === 1 && items[0].type === 'array') { this.schemaValidateSync(schemaData, items[0].items); } else { this.schemaPropertiesValidation(schemaData, schemaObj); } } if (schemaObj.required) { this.schemaValidationRequiredRule(schemaData, schemaObj); } this.schemaValidateRules(schemaData, schemaObj); } private schemaAdditionalPropertiesValidation(schemaData: Record, schemaProps: object): void { for (const key of Object.keys(schemaData)) { if (!schemaProps[key]) { const errorMessage: string = `The class property includes parameters not defined in the JSON Schema.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110011', description: 'InsightIntent Compiler Error', solutions: ['Remove any extra parameters'] }); return; } } } private processEntityOwnerMap(): void { for (const [intentName, entityClassNames] of this.entityOwnerMap.entries()) { const expandedClassNames = new Set(entityClassNames); entityClassNames.forEach(className => { this.visitEntityHeritage(className, expandedClassNames); }); if (expandedClassNames.size > entityClassNames.length) { this.entityOwnerMap.set(intentName, Array.from(expandedClassNames)); } } } private visitEntityHeritage(className: string, expandedClassNames: Set): void { const parentClassName: string = this.EntityExtendsMap.get(className); if (parentClassName && !expandedClassNames.has(parentClassName)) { expandedClassNames.add(parentClassName); this.visitEntityHeritage(parentClassName, expandedClassNames); } } private matchEntities(): void { if (this.entityMap.size === 0) { return; } this.processEntityOwnerMap(); const intentNameMappingMap: Map = new Map(); this.intentData.forEach(data => { intentNameMappingMap.set(data.intentName, data); }); for (const [intentName, entityClassNames] of this.entityOwnerMap.entries()) { const targetIntent: object = intentNameMappingMap.get(intentName); if (!targetIntent) { continue; } const matchedEntities: object[] = []; entityClassNames.forEach(entityClassName => { if (this.entityMap.has(entityClassName)) { matchedEntities.push(this.entityMap.get(entityClassName)); } }); if (matchedEntities.length !== 0) { targetIntent.entities = matchedEntities; } } } // This method writes the parsed data to a file. public writeUserIntentJsonFile(harIntentDataObj: object, share: object): void { const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file if (!projectConfig.aceProfilePath || !(fs.existsSync(cachePath) || this.intentData.length > 0 || Object.keys(harIntentDataObj).length !== 0)) { return; } const mergedData: object = this.processIntentData(harIntentDataObj); const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file try { if (Object.keys(mergedData).length > 0) { const cacheContent: object = { 'extractInsightIntents': this.intentData, 'entityOwnerMap': Object.fromEntries(this.entityOwnerMap.entries()), 'entityMap': Object.fromEntries(this.entityMap.entries()), 'heritageClassSet': Object.fromEntries(this.heritageClassSet.entries()), 'entityHeritageClassSet': Object.fromEntries(this.EntityHeritageClassSet.entries()), 'entityExtendsMap': Object.fromEntries(this.EntityExtendsMap.entries()) }; fs.writeFileSync(cacheSourceMapPath, JSON.stringify(mergedData, null, 2), 'utf-8'); fs.writeFileSync(cachePath, JSON.stringify(cacheContent, null, 2), 'utf-8'); } else if (fs.existsSync(cacheSourceMapPath)) { fs.unlinkSync(cacheSourceMapPath); } const normalizedPath: string = path.normalize(projectConfig.aceProfilePath); const fullPath: string = path.join(normalizedPath, '../../../module.json'); if (fs.existsSync(fullPath)) { const rawData: string = fs.readFileSync(fullPath, 'utf8'); const jsonData: object = JSON.parse(rawData); if (jsonData?.module) { jsonData.module.hasInsightIntent = Object.keys(mergedData).length > 0 ? true : undefined; } const updatedJson: string = JSON.stringify(jsonData, null, 2); fs.writeFileSync(fullPath, updatedJson, 'utf8'); } } catch (e) { const errorMessage: string = `Failed to write to the intent configuration file.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110025', description: 'InsightIntent Compiler Error', solutions: ['Check file permissions, free disk space, or restart DevEco Studio'] }); return; } const logger = share.getLogger('etsTransform'); const hvigorLogger = share.getHvigorConsoleLogger?.(ABILITY_SUBSYSTEM_CODE); if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) { emitLogInfo(logger, getTransformLog(transformLog), true, this.currentFilePath, hvigorLogger); resetLog(); } } private processUpdateEntities(cacheDataObj: object): void { const decoratorFileMapping: Set = new Set(this.updatePageIntentObj.keys()); if (cacheDataObj.entityOwnerMap && Object.keys(cacheDataObj.entityOwnerMap || {}).length > 0) { const cacheEntityOwnerMap: Map = cacheDataObj.entityOwnerMap as Map; for (const [intentName, entityClassNames] of Object.entries(cacheEntityOwnerMap)) { if (!this.entityOwnerMap.has(intentName)) { this.entityOwnerMap.set(intentName, entityClassNames); } } } if (cacheDataObj.entityMap && Object.keys(cacheDataObj.entityMap || {}).length > 0) { const cacheEntityMap: Map = cacheDataObj.entityMap as Map; for (const [className, entityObj] of Object.entries(cacheEntityMap)) { if (!decoratorFileMapping.has(entityObj.decoratorFile)) { this.entityMap.set(entityObj.className, entityObj); } } } this.processUpdateHeritageVerify(cacheDataObj); this.intentData.map(userIntent => { if (userIntent.entities) { delete userIntent.entities; } }); } private processUpdateHeritageVerify(cacheDataObj: object): void { const decoratorFileMapping: Set = new Set(this.updatePageIntentObj.keys()); if (cacheDataObj.heritageClassSet && Object.keys(cacheDataObj.heritageClassSet || {}).length > 0) { const cacheHeritageClassSet: Set = cacheDataObj.heritageClassSet as Set; for (const entityPathInfo of Object.values(cacheHeritageClassSet)) { const decoratorFilePath: string = entityPathInfo.split('_').pop(); if (!decoratorFileMapping.has(decoratorFilePath)) { this.heritageClassSet.add(entityPathInfo); } } } if (cacheDataObj.entityHeritageClassSet && Object.keys(cacheDataObj.entityHeritageClassSet || {}).length > 0) { const cacheEntityExtendsMap: Set = cacheDataObj.entityHeritageClassSet as Set; for (const entityPathInfo of Object.values(cacheEntityExtendsMap)) { const decoratorFilePath: string = entityPathInfo.split('_').pop(); if (decoratorFileMapping.has(decoratorFilePath)) { this.EntityHeritageClassSet.add(entityPathInfo); } } } if (cacheDataObj.entityExtendsMap && Object.keys(cacheDataObj.entityExtendsMap || {}).length > 0) { const cacheEntityExtendsMap: Map = cacheDataObj.entityExtendsMap as Map; for (const [baseClassName, parentClassName] of Object.entries(cacheEntityExtendsMap)) { if (!this.EntityExtendsMap.has(baseClassName)) { this.EntityExtendsMap.set(baseClassName, parentClassName); } } } } private processIntentData(harIntentDataObj: object): object { const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file if (!fs.existsSync(projectConfig.aceProfilePath)) { fs.mkdirSync(projectConfig.aceProfilePath, { recursive: true }); } if (this.isUpdateCompile && fs.existsSync(cachePath)) { const cacheData: string = fs.readFileSync(cachePath, 'utf8'); const cacheDataObj: object = JSON.parse(cacheData); const insightIntents: object[] = cacheDataObj.extractInsightIntents.filter(insightIntent => { return !this.updatePageIntentObj.has(insightIntent.decoratorFile); }); this.updatePageIntentObj.forEach(insightIntent => { insightIntents.push(...insightIntent); }); this.intentData = insightIntents; this.processUpdateEntities(cacheDataObj); } this.verifyInheritanceChain(); this.matchEntities(); let writeJsonData: object = {}; if (fs.existsSync(cacheSourceMapPath)) { const originIntents: string = fs.readFileSync(cacheSourceMapPath, 'utf8'); const jsonData: object = JSON.parse(originIntents); Object.assign(jsonData, { 'extractInsightIntents': this.intentData }); writeJsonData = jsonData; } else if (this.intentData.length > 0) { Object.assign(writeJsonData, { 'extractInsightIntents': this.intentData }); } const mergedData: object = this.mergeHarData(writeJsonData, harIntentDataObj); this.validateIntentIntentName(mergedData); return mergedData; } private mergeHarData(writeJsonData: object, harIntentDataObj: object): object { let mergedData: object = {}; if (writeJsonData) { mergedData = JSON.parse(JSON.stringify(writeJsonData)); } Object.keys(harIntentDataObj || {})?.forEach(harName => { if (harIntentDataObj[harName].extractInsightIntents) { harIntentDataObj[harName].extractInsightIntents.forEach(intentObj => { intentObj.moduleName = projectConfig.moduleName; intentObj.bundleName = projectConfig.bundleName; }); if (harIntentDataObj[harName].extractInsightIntents) { mergedData.extractInsightIntents?.push(...harIntentDataObj[harName].extractInsightIntents); } } }); return mergedData; } // This method get the user's intents from the bytecode HAR package. public getHarData(): object { const harIntentDataObj: object = {}; if (fs.existsSync(projectConfig.aceBuildJson)) { const loaderJson: string = fs.readFileSync(projectConfig.aceBuildJson, 'utf8'); const { byteCodeHarInfo } = JSON.parse(loaderJson); Object.keys(byteCodeHarInfo || {})?.forEach((harName) => { const harAbcFilePath = byteCodeHarInfo[harName].abcPath as string; const harModulePath: string = harAbcFilePath.split('ets')[0]; const harSourcePath: string = path.join(harModulePath, 'src', 'main', 'resources', 'base', 'profile'); const intentDataSourcePath: string = path.join(harSourcePath, 'insight_intent.json'); let harIntentData: object = {}; if (fs.existsSync(intentDataSourcePath)) { harIntentData = JSON.parse(fs.readFileSync(intentDataSourcePath, 'utf8')) as object; } Object.assign(harIntentDataObj, { harName: harIntentData }); }); } return harIntentDataObj; } private validateIntentIntentName(writeJsonData: object): void { const duplicates = new Set(); writeJsonData.insightIntents?.forEach(insightIntent => { duplicates.add(insightIntent.intentName); }); writeJsonData.extractInsightIntents?.forEach(item => { if (duplicates.has(item.intentName)) { const errorMessage: string = `Duplicate intentName definitions found.`; this.transformLog.push({ type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), code: '10110012', description: 'InsightIntent Compiler Error', solutions: ['Rename or remove duplicate entries'] }); return; } else if (item.intentName !== undefined) { duplicates.add(item.intentName); } }); } public clear(): void { this.intentData = []; this.checker = null; this.currentFilePath = ''; this.heritageClassSet = new Set(); this.heritageClassSet.add('IntentEntity_sdk'); this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); this.isInitCache = false; this.isUpdateCompile = true; this.updatePageIntentObj = new Map(); this.entityMap = new Map(); this.entityOwnerMap = new Map(); this.moduleJsonInfo = new Map(); this.EntityHeritageClassSet = new Set(); this.EntityExtendsMap = new Map(); } } export default new ParseIntent();