* 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_LINK,
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, CurrentProcessFile} from '../utils';
import {ABILITY_SUBSYSTEM_CODE} from '../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<string, string>,
args: ts.NodeArray<ts.Expression>;
}
interface schemaVerifyType {
type: string;
isEntity?: boolean;
}
class ParseIntent {
private checker: ts.TypeChecker;
public intentData: object[];
private currentFilePath: string;
private heritageClassSet: Set<string>;
private updateIntentObj: Map<string, object[]>;
private allCompiledSourceFiles: Map<string, string>;
public isUpdateCompile: boolean = true;
private isInitCache: boolean = false;
private entityMap: Map<string, object>;
private entityOwnerMap: Map<string, string[]>;
private moduleJsonInfo: Map<string, object[]>;
private EntityHeritageClassSet: Set<string>;
private EntityExtendsMap: Map<string, string>;
private transformLog: LogInfo[];
private currentNode: ts.Node;
constructor() {
this.intentData = [];
this.currentFilePath = '';
this.heritageClassSet = new Set<string>();
this.heritageClassSet.add('IntentEntity_sdk');
this.heritageClassSet.add('InsightIntent.AppIntentEntity_sdk');
this.heritageClassSet.add('insightIntent.AppIntentEntity_sdk');
this.heritageClassSet.add('InsightIntentEntryExecutor_sdk');
this.updateIntentObj = new Map();
this.allCompiledSourceFiles = 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 recordCompiledFile(filePath: string, metaInfo: object): void {
if (!projectConfig.pkgContextInfo || !metaInfo?.pkgName || !metaInfo?.pkgPath) {
return;
}
const pkgParams: object = {
pkgName: metaInfo.pkgName,
pkgPath: metaInfo.pkgPath
};
const Logger: IntentLogger = IntentLogger.getInstance();
const recordName: string = getNormalizedOhmUrlByFilepath(filePath, projectConfig, Logger, pkgParams, null);
const decoratorFile = `@normalized:${recordName}`;
this.allCompiledSourceFiles.set(decoratorFile, filePath);
}
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_LINK, 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 = CurrentProcessFile.getChecker();
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');
this.isUpdateCompile = fs.existsSync(cacheSourceMapPath);
this.isInitCache = true;
} else {
this.isUpdateCompile = false;
}
}
if (projectConfig.pkgContextInfo && metaInfo?.pkgName && metaInfo?.pkgPath) {
const pkgParams: object = {
pkgName: metaInfo.pkgName,
pkgPath: metaInfo.pkgPath
};
const Logger: IntentLogger = IntentLogger.getInstance();
const recordName: string = getNormalizedOhmUrlByFilepath(filePath, projectConfig, Logger, pkgParams, null);
const decoratorFile = `@normalized:${recordName}`;
this.allCompiledSourceFiles.set(decoratorFile, filePath);
if (this.isUpdateCompile) {
if (!this.updateIntentObj.has(decoratorFile)) {
this.updateIntentObj.set(decoratorFile, []);
}
}
}
}
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;
}
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 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;
}
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_LINK) {
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<ts.Expression> = 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<string, schemaVerifyType> = 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.updateIntentObj.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<ts.Expression> = 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<string, schemaVerifyType> = this.parseClassNode(node, undefined, COMPONENT_USER_INTENTS_DECORATOR_ENTITY);
const entityId: string = this.getEntityId(node);
const currentClassType = this.checker.getTypeAtLocation(node);
const baseTypes = this.checker.getBaseTypes(currentClassType as ts.InterfaceType);
let parentClassName = 'None';
if (baseTypes && baseTypes.length > 0) {
const parentSymbol = baseTypes[0].getSymbol();
if (parentSymbol) {
parentClassName = parentSymbol.getName();
}
}
if (!entityId && entityId !== '' && parentClassName == "AppIntentEntity" ) {
const errorMessage: string = `The entityId is not defined in class ${entityClassName}.`;
this.transformLog.push({
type: LogType.ERROR,
message: errorMessage,
pos: this.currentNode.getStart(),
code: '10110008',
description: 'InsightIntent Compiler Error',
solutions: ['Add the required parameters as specified in the error message']
});
}
Object.assign(properties, {
'entityId': entityId
});
Object.assign(intentObj, {
'entityId': entityId
});
this.schemaValidateSync(properties, intentObj.parameters);
if (intentObj.supportedQueryProperties && intentObj.supportedQueryProperties.length !==0) {
this.validateSupportedQuery(properties, intentObj.parameters, intentObj.supportedQueryProperties)
}
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<ts.Expression> = 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.updateIntentObj.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<ts.Expression> = methodDecorator.args;
const properties: Record<string, string> = methodDecorator.parameters;
const functionParamList: Array<string> = Object.keys(properties);
const methodObj: object = Object.assign({}, intentObj, { functionName, 'functionParamList': functionParamList });
this.analyzeDecoratorArgs(methodArgs, methodObj, intentMethodInfoChecker);
if (this.isUpdateCompile) {
this.updateIntentObj.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<ts.Expression> = expr.arguments;
this.analyzeDecoratorArgs<IntentLinkInfo>(args, intentObj, IntentLinkInfoChecker);
Object.assign(intentObj, {
'bundleName': projectConfig.bundleName,
'moduleName': projectConfig.moduleName,
'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_LINK
});
this.createObfuscation(node);
if (this.isUpdateCompile) {
this.updateIntentObj.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<ts.Expression> = expr.arguments;
Object.assign(intentObj, {
'bundleName': projectConfig.bundleName,
'moduleName': projectConfig.moduleName,
'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTRY
});
this.analyzeDecoratorArgs<IntentEntryInfo>(args, intentObj, intentEntryInfoChecker);
const properties: Record<string, schemaVerifyType> = 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.updateIntentObj.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<string, string> = {};
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 isInheritFromIntentEntity(symbol: ts.Symbol, pkgParams: object, visitedSymbols: Set<ts.Symbol> = new Set()): boolean {
if (!symbol) {
return false;
}
if (visitedSymbols.has(symbol)) {
return false;
}
visitedSymbols.add(symbol);
const symbolName: string = symbol.getName();
if (symbolName === 'IntentEntity' || symbolName === 'AppIntentEntity') {
return true;
}
return false;
}
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 (!this.isInheritFromIntentEntity(parentSymbol, pkgParams)) {
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<string, schemaVerifyType> {
const mergedObject: Record<string, schemaVerifyType> = {};
const type: ts.Type = this.checker.getTypeAtLocation(node);
const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type);
propertiesOfType.forEach((prop) => {
const objItem: Record<string, schemaVerifyType> = 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<string, schemaVerifyType> {
const propType: ts.Type = this.checker.getNonNullableType(this.checker.getTypeOfSymbol(prop));
const { category } = this.getTypeCategory(propType);
const obj: Record<string, schemaVerifyType> = {};
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_FORM && 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<T>(
node: ts.ObjectLiteralExpression,
paramCheckFields: ParamChecker<T>
): void {
const existingParams: Set<keyof T> = new Set<keyof T>();
const requiredFields: (keyof T)[] = paramCheckFields.requiredFields;
const nestedCheckers: Map<string, ParamChecker<LinkIntentParamMapping>> = paramCheckFields.nestedCheckers;
const allowedFields: Set<keyof T> = paramCheckFields.allowFields;
const paramValidators: Record<keyof T, (v: ts.Expression) => 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<string, ParamChecker<LinkIntentParamMapping>>): void {
const checker: ParamChecker<LinkIntentParamMapping> = 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<LinkIntentParamMapping>(declaration.initializer, checker);
} else {
this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(elem, checker);
}
});
} else if (ts.isIdentifier(prop)) {
const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(prop);
const declaration: ts.Declaration = symbol?.valueDeclaration;
this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(declaration.initializer, checker);
} else {
this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(prop, checker);
}
}
private validateFields<T>(
prop: ts.Node, allowedFields: Set<keyof T>, paramValidators: Record<keyof T, (v: ts.Expression) => 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<T>(args: ts.NodeArray<ts.Expression>, intentObj: object,
paramChecker: ParamChecker<T>): 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<T>(declaration.initializer, paramChecker);
} else {
this.validateRequiredIntentLinkInfo<T>(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<ts.Node> = 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<string, string> = 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<string, string> = 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<string, schemaVerifyType>, schemaObj: object): void {
const reqData: Map<string, boolean> = 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<string, schemaVerifyType>, 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<string, schemaVerifyType>, 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<string, schemaVerifyType>, 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 validateSupportedQuery(schemaData: Record<string, schemaVerifyType>, schemaObj: object, supportedValue: string[],): void {
const properties = schemaObj.properties;
const propertyKeys = new Set(Object.keys(properties));
for (const field of supportedValue) {
if (typeof field === 'string' && !propertyKeys.has(field)) {
const errorMessage: string = `The supportedQueryProperties field '${field}' is not defined in properties.`;
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;
}
}
const supportedQueryPropertiesFieldSet = new Set(
supportedValue.filter((field): field is string => typeof field === 'string')
);
if (supportedQueryPropertiesFieldSet.size === 0) {
return;
}
for (const key of Object.keys(properties)) {
if (supportedQueryPropertiesFieldSet.has(key) && !schemaData[key]) {
const errorMessage: string = `The field '${key}' in supportedQueryProperties is not a property of the class.`;
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 schemaAdditionalPropertiesValidation(schemaData: Record<string, schemaVerifyType>, 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<string>(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<string>): 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<string, object> = 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;
}
}
}
public writeUserIntentJsonFile(harIntentDataObj: object, share: object): void {
const cachePath: string =
path.join(projectConfig.cachePath, 'insight_compile_cache.json');
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');
try {
if (Object.keys(mergedData).length > 0) {
const intentFiles = new Set<string>();
this.intentData.forEach(intent => {
if (intent.decoratorFile) {
intentFiles.add(intent.decoratorFile);
}
});
const sourceFilePathMap: Record<string, string> = {};
for (const [decoratorFile, filePath] of this.allCompiledSourceFiles.entries()) {
if (intentFiles.has(decoratorFile)) {
sourceFilePathMap[decoratorFile] = filePath;
}
}
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()),
'sourceFilePathMap': sourceFilePathMap
};
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.`;
transformLog.errors.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, deletedFileSet?: Set<string>): void {
const decoratorFileMapping: Set<string> = new Set(this.updateIntentObj.keys());
if (cacheDataObj.entityOwnerMap && Object.keys(cacheDataObj.entityOwnerMap || {}).length > 0) {
const cacheEntityOwnerMap: Map<string, string[]> = cacheDataObj.entityOwnerMap as Map<string, string[]>;
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<string, object> = cacheDataObj.entityMap as Map<string, object>;
for (const [className, entityObj] of Object.entries(cacheEntityMap)) {
if (deletedFileSet && deletedFileSet.has(entityObj.decoratorFile)) {
continue;
}
if (!decoratorFileMapping.has(entityObj.decoratorFile)) {
this.entityMap.set(entityObj.className, entityObj);
}
}
}
this.processUpdateHeritageVerify(cacheDataObj, deletedFileSet);
this.intentData.map(userIntent => {
if (userIntent.entities) {
delete userIntent.entities;
}
});
}
private processUpdateHeritageVerify(cacheDataObj: object, deletedFileSet?: Set<string>): void {
const decoratorFileMapping: Set<string> = new Set(this.updateIntentObj.keys());
if (cacheDataObj.heritageClassSet && Object.keys(cacheDataObj.heritageClassSet || {}).length > 0) {
const cacheHeritageClassSet: Set<string> = cacheDataObj.heritageClassSet as Set<string>;
for (const entityPathInfo of Object.values(cacheHeritageClassSet)) {
const decoratorFilePath: string = entityPathInfo.split('_').pop();
if (deletedFileSet && deletedFileSet.has(decoratorFilePath)) {
continue;
}
if (!decoratorFileMapping.has(decoratorFilePath)) {
this.heritageClassSet.add(entityPathInfo);
}
}
}
if (cacheDataObj.entityHeritageClassSet && Object.keys(cacheDataObj.entityHeritageClassSet || {}).length > 0) {
const cacheEntityExtendsMap: Set<string> = cacheDataObj.entityHeritageClassSet as Set<string>;
for (const entityPathInfo of Object.values(cacheEntityExtendsMap)) {
const decoratorFilePath: string = entityPathInfo.split('_').pop();
if (deletedFileSet && deletedFileSet.has(decoratorFilePath)) {
continue;
}
if (decoratorFileMapping.has(decoratorFilePath)) {
this.EntityHeritageClassSet.add(entityPathInfo);
}
}
}
if (cacheDataObj.entityExtendsMap && Object.keys(cacheDataObj.entityExtendsMap || {}).length > 0) {
const cacheEntityExtendsMap: Map<string, string> = cacheDataObj.entityExtendsMap as Map<string, string>;
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');
const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json');
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 cachedSourceFilePathMap: Record<string, string> = cacheDataObj.sourceFilePathMap || {};
const deletedFiles: string[] = this.detectDeletedFiles(cachedSourceFilePathMap);
const deletedFileSet = new Set(deletedFiles);
const insightIntents: object[] = cacheDataObj.extractInsightIntents.filter(insightIntent => {
if (this.updateIntentObj.has(insightIntent.decoratorFile)) {
return false;
}
if (deletedFileSet.has(insightIntent.decoratorFile)) {
return false;
}
return true;
});
this.updateIntentObj.forEach(insightIntent => {
insightIntents.push(...insightIntent);
});
this.intentData = insightIntents;
this.processUpdateEntities(cacheDataObj, deletedFileSet);
const mergedFiles: string[] = [];
for (const [decoratorFile, filePath] of Object.entries(cachedSourceFilePathMap)) {
if (!this.allCompiledSourceFiles.has(decoratorFile)) {
this.allCompiledSourceFiles.set(decoratorFile, filePath);
mergedFiles.push(decoratorFile);
}
}
}
this.verifyInheritanceChain();
this.matchEntities();
this.intentData.sort((a, b) => a.decoratorFile.localeCompare(b.decoratorFile));
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 detectDeletedFiles(
cachedSourceFilePathMap: Record<string, string>
): string[] {
const deletedFiles: string[] = [];
const cachedFiles = Object.keys(cachedSourceFilePathMap);
for (const decoratorFile of cachedFiles) {
if (this.updateIntentObj.has(decoratorFile)) {
continue;
}
if (this.allCompiledSourceFiles.has(decoratorFile)) {
deletedFiles.push(decoratorFile);
continue;
}
const sourceFilePath = cachedSourceFilePathMap[decoratorFile];
if (sourceFilePath) {
if (!fs.existsSync(sourceFilePath)) {
deletedFiles.push(decoratorFile);
}
continue;
}
}
return deletedFiles;
}
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;
}
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;
}
if (Object.keys(harIntentData).length !== 0) {
Object.assign(harIntentDataObj, {
harName: harIntentData
});
}
});
}
return harIntentDataObj;
}
private validateIntentIntentName(writeJsonData: object): void {
const duplicates = new Set<string>();
writeJsonData.insightIntents?.forEach(insightIntent => {
duplicates.add(insightIntent.intentName);
});
writeJsonData.extractInsightIntents?.forEach(item => {
if (duplicates.has(item.intentName)) {
const errorMessage: string = `Duplicate intentName definitions found.`;
transformLog.errors.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<string>();
this.heritageClassSet.add('IntentEntity_sdk');
this.heritageClassSet.add('InsightIntent.AppIntentEntity_sdk');
this.heritageClassSet.add('insightIntent.AppIntentEntity_sdk');
this.heritageClassSet.add('InsightIntentEntryExecutor_sdk');
this.isInitCache = false;
this.isUpdateCompile = true;
this.updateIntentObj = new Map();
this.allCompiledSourceFiles = 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();