1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts, { CatchClause, Declaration, Expression, TypeChecker, VariableDeclarationList } from 'typescript'; 17import { 18 IntentEntryInfo, 19 intentEntryInfoChecker, 20 IntentLinkInfo, 21 IntentLinkInfoChecker, 22 intentMethodInfoChecker, 23 LinkIntentParamMapping, 24 IntentPageInfoChecker, 25 ParamChecker, 26 IntentEntityInfoChecker, 27 intentFormInfoChecker 28} from './intentType'; 29import { IntentLogger } from './intentLogger'; 30import path from 'path'; 31import { getNormalizedOhmUrlByFilepath } from '../ark_utils'; 32import { globalModulePaths, projectConfig } from '../../main'; 33import fs from 'fs'; 34import json5 from 'json5'; 35import { ProjectCollections } from 'arkguard'; 36import { 37 COMPONENT_USER_INTENTS_DECORATOR, 38 COMPONENT_USER_INTENTS_DECORATOR_ENTITY, 39 COMPONENT_USER_INTENTS_DECORATOR_ENTRY, 40 COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, 41 COMPONENT_USER_INTENTS_DECORATOR_METHOD, 42 COMPONENT_USER_INTENTS_DECORATOR_PAGE, 43 COMPONENT_USER_INTENTS_DECORATOR_FORM 44} from '../pre_define'; 45import { CompileEvent, createAndStartEvent, stopEvent } from '../performance'; 46import {emitLogInfo, getTransformLog, LogInfo, LogType} from '../utils'; 47import {ABILITY_SUBSYSTEM_CODE} from '../../lib/hvigor_error_code/hvigor_error_info'; 48import {resetLog, transformLog} from '../process_ui_syntax'; 49 50type StaticValue = string | number | boolean | null | undefined | StaticValue[] | { [key: string]: StaticValue }; 51 52interface methodParametersInfo { 53 functionName: string; 54 parameters: Record<string, string>, 55 args: ts.NodeArray<ts.Expression>; 56} 57 58interface schemaVerifyType { 59 type: string; 60 isEntity?: boolean; 61} 62 63class ParseIntent { 64 private checker: ts.TypeChecker; 65 public intentData: object[]; 66 private currentFilePath: string; 67 private heritageClassSet: Set<string>; 68 private updatePageIntentObj: Map<string, object[]>; 69 public isUpdateCompile: boolean = true; 70 private isInitCache: boolean = false; 71 private entityMap: Map<string, object>; 72 private entityOwnerMap: Map<string, string[]>; 73 private moduleJsonInfo: Map<string, object[]>; 74 private EntityHeritageClassSet: Set<string>; 75 private EntityExtendsMap: Map<string, string>; 76 private transformLog: LogInfo[]; 77 private currentNode: ts.Node; 78 79 constructor() { 80 this.intentData = []; 81 this.currentFilePath = ''; 82 this.heritageClassSet = new Set<string>(); 83 this.heritageClassSet.add('IntentEntity_sdk'); 84 this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); 85 this.updatePageIntentObj = new Map(); 86 this.entityMap = new Map(); 87 this.entityOwnerMap = new Map(); 88 this.moduleJsonInfo = new Map(); 89 this.EntityHeritageClassSet = new Set(); 90 this.EntityExtendsMap = new Map(); 91 } 92 93 private hasDecorator(node: ts.Node, decorators: string[]): boolean { 94 if (!node.modifiers) { 95 return false; 96 } 97 return node.modifiers.some(decorator => { 98 if (!ts.isDecorator(decorator)) { 99 return false; 100 } 101 let decoratorName: string | undefined; 102 if (ts.isCallExpression(decorator.expression)) { 103 decoratorName = `@${decorator.expression.expression.getText()}`; 104 } 105 return decoratorName !== undefined && decorators.includes(decoratorName); 106 }); 107 } 108 109 public detectInsightIntent( 110 node: ts.ClassDeclaration, metaInfo: object, filePath: string, eventOrEventFactory: CompileEvent | undefined, transformLog: LogInfo[]): ts.Node { 111 this.initInsightIntent(node, metaInfo, transformLog, filePath); 112 const eventParseIntentTime: CompileEvent | undefined = createAndStartEvent(eventOrEventFactory, 'parseIntentTime'); 113 const definedDecorators: string[] = [COMPONENT_USER_INTENTS_DECORATOR, COMPONENT_USER_INTENTS_DECORATOR_ENTRY, 114 COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, COMPONENT_USER_INTENTS_DECORATOR_PAGE, COMPONENT_USER_INTENTS_DECORATOR_ENTITY, 115 COMPONENT_USER_INTENTS_DECORATOR_FORM]; 116 if (ts.isClassDeclaration(node) && !this.hasDecorator(node, [COMPONENT_USER_INTENTS_DECORATOR_FUNCTION])) { 117 node.members.forEach((member) => { 118 if (ts.isMethodDeclaration(member) && this.hasModifier(member, ts.SyntaxKind.StaticKeyword) && 119 this.hasDecorator(member, [COMPONENT_USER_INTENTS_DECORATOR_METHOD])) { 120 const errorMessage: string = 'Methods decorated with @InsightIntentFunctionMethod must be in a class decorated with @InsightIntentFunction.'; 121 this.transformLog.push({ 122 type: LogType.ERROR, 123 message: errorMessage, 124 pos: node.getStart(), 125 code: '10110013', 126 description: 'InsightIntent Compiler Error', 127 solutions: ['Either move the method or add @InsightIntentFunction to the class'] 128 }); 129 return; 130 } 131 }); 132 } 133 if (this.hasDecorator(node, definedDecorators)) { 134 const checker: TypeChecker = metaInfo.checker; 135 this.handleIntent(node, checker, filePath, metaInfo); 136 node = this.removeDecorator(node, definedDecorators.concat(COMPONENT_USER_INTENTS_DECORATOR_METHOD)); 137 } 138 stopEvent(eventParseIntentTime); 139 return node; 140 } 141 142 private initInsightIntent(node: ts.ClassDeclaration, metaInfo: object, transformLog: LogInfo[], filePath: string): void { 143 this.transformLog = transformLog; 144 this.currentNode = node; 145 if (!this.isInitCache) { 146 if (projectConfig.cachePath) { 147 const cacheSourceMapPath: string = 148 path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // The user's intents configuration file 149 this.isUpdateCompile = fs.existsSync(cacheSourceMapPath); 150 this.isInitCache = true; 151 } else { 152 this.isUpdateCompile = false; 153 } 154 } 155 if (this.isUpdateCompile) { 156 const pkgParams: object = { 157 pkgName: metaInfo.pkgName, 158 pkgPath: metaInfo.pkgPath 159 }; 160 if (!projectConfig.pkgContextInfo) { 161 return; 162 } 163 const Logger: IntentLogger = IntentLogger.getInstance(); 164 const recordName: string = getNormalizedOhmUrlByFilepath(filePath, projectConfig, Logger, pkgParams, null); 165 if (!this.updatePageIntentObj.has(`@normalized:${recordName}`)) { 166 this.updatePageIntentObj.set(`@normalized:${recordName}`, []); 167 } 168 } 169 } 170 171 private handleIntent(node: ts.ClassDeclaration, checker: ts.TypeChecker, filepath: string, metaInfo: Object = {}): void { 172 this.checker = checker; 173 this.currentFilePath = filepath; 174 if (!filepath.endsWith('.ets')) { 175 const errorMessage: string = 'The intent decorator can only be used in .ets files.'; 176 this.transformLog.push({ 177 type: LogType.ERROR, 178 message: errorMessage, 179 pos: this.currentNode.getStart(), 180 code: '10110001', 181 description: 'InsightIntent Compiler Error', 182 solutions: ['Move it to an .ets file'] 183 }); 184 return; 185 } 186 const pkgParams: object = { 187 pkgName: metaInfo.pkgName, 188 pkgPath: metaInfo.pkgPath 189 }; 190 node.modifiers.forEach(decorator => { 191 this.handleDecorator(node, decorator, filepath, pkgParams); 192 }); 193 } 194 195 private handleDecorator(node: ts.ClassDeclaration, decorator: ts.Decorator, filepath: string, pkgParams: object): void { 196 const expr: ts.Expression = decorator.expression; 197 if (!expr || !ts.isCallExpression(expr)) { 198 return; 199 } 200 const argumentKind: ts.SyntaxKind | undefined = expr.arguments[0]?.kind; 201 if (argumentKind && argumentKind === ts.SyntaxKind.NullKeyword || argumentKind && argumentKind === ts.SyntaxKind.UndefinedKeyword) { 202 return; 203 } 204 const symbol: ts.Symbol = this.checker.getTypeAtLocation(decorator.expression.expression)?.getSymbol(); 205 const declarations: ts.Declaration[] | undefined = symbol?.getDeclarations(); 206 if (!declarations || declarations.length === 0) { 207 return; 208 } 209 const decoratorSourceFile: string = declarations[0].getSourceFile().fileName; 210 const isGlobalPathFlag: boolean = this.isGlobalPath(decoratorSourceFile); 211 if (!isGlobalPathFlag) { 212 return; 213 } 214 if (!projectConfig.pkgContextInfo) { 215 const errorMessage: string = 'Failed to generate standard OHMUrl.'; 216 this.transformLog.push({ 217 type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), 218 code: '10111027', 219 description: 'InsightIntent Compiler Error', 220 solutions: ['Set useNormalizedOHMUrl to true in build-profile.json5'] 221 }); 222 return; 223 } 224 const Logger: IntentLogger = IntentLogger.getInstance(); 225 const recordName: string = getNormalizedOhmUrlByFilepath(filepath, projectConfig, Logger, pkgParams, null); 226 const intentObj: object = { 227 'decoratorFile': `@normalized:${recordName}`, 228 'decoratorClass': node.name.text 229 }; 230 const originalDecorator: string = '@' + decorator.expression.expression.getText(); 231 if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR) { 232 this.handleLinkDecorator(intentObj, node, decorator); 233 } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_ENTRY) { 234 this.handleEntryDecorator(intentObj, node, decorator, pkgParams); 235 } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_FUNCTION) { 236 this.handleMethodDecorator(intentObj, node, decorator); 237 } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_PAGE) { 238 this.handlePageDecorator(intentObj, node, decorator, pkgParams); 239 } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_ENTITY) { 240 this.handleEntityDecorator(intentObj, node, decorator, pkgParams); 241 } else if (originalDecorator === COMPONENT_USER_INTENTS_DECORATOR_FORM) { 242 this.handleFormDecorator(intentObj, node, decorator, pkgParams); 243 } 244 } 245 246 private handleFormDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, 247 pkgParams: object): void { 248 const expr: ts.Expression = decorator.expression; 249 if (ts.isCallExpression(expr)) { 250 const args: ts.NodeArray<ts.Expression> = expr.arguments; 251 Object.assign(intentObj, { 252 'bundleName': projectConfig.bundleName, 253 'moduleName': projectConfig.moduleName, 254 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_FORM 255 }); 256 this.analyzeDecoratorArgs(args, intentObj, intentFormInfoChecker); 257 const properties: Record<string, schemaVerifyType> = this.parseClassNode(node, intentObj.intentName, COMPONENT_USER_INTENTS_DECORATOR_FORM); 258 this.processFormInfo(node, this.currentFilePath, pkgParams, intentObj); 259 this.schemaValidateSync(properties, intentObj.parameters); 260 this.createObfuscation(node); 261 if (this.isUpdateCompile) { 262 this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); 263 } 264 this.intentData.push(intentObj); 265 } else { 266 const errorMessage: string = 'Decorators must be called as functions.'; 267 this.transformLog.push({ 268 type: LogType.ERROR, 269 message: errorMessage, 270 pos: this.currentNode.getStart(), 271 code: '10110002', 272 description: 'InsightIntent Compiler Error', 273 solutions: ['Add parentheses after the decorator name'] 274 }); 275 return; 276 } 277 } 278 279 private handleEntityDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, 280 pkgParams: object): void { 281 const entityClassName: string = this.checker.getTypeAtLocation(node).getSymbol().getName(); 282 const expr: ts.Expression = decorator.expression; 283 if (ts.isCallExpression(expr)) { 284 const args: ts.NodeArray<ts.Expression> = expr.arguments; 285 Object.assign(intentObj, { 286 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTITY, 287 'className': intentObj.decoratorClass 288 }); 289 delete intentObj.decoratorClass; 290 this.analyzeDecoratorArgs(args, intentObj, IntentEntityInfoChecker); 291 const properties: Record<string, schemaVerifyType> = this.parseClassNode(node, undefined, COMPONENT_USER_INTENTS_DECORATOR_ENTITY); 292 const entityId: string = this.getEntityId(node); 293 Object.assign(properties, { 294 'entityId': entityId 295 }); 296 Object.assign(intentObj, { 297 'entityId': entityId 298 }); 299 this.schemaValidateSync(properties, intentObj.parameters); 300 this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTITY); 301 this.createObfuscation(node); 302 if (this.entityMap.has(entityClassName)) { 303 const errorMessage: string = 'Multiple @InsightIntentEntity decorators applied to the same class.'; 304 this.transformLog.push({ 305 type: LogType.ERROR, 306 message: errorMessage, 307 pos: this.currentNode.getStart(), 308 code: '10110020', 309 description: 'InsightIntent Compiler Error', 310 solutions: ['Remove duplicates'] 311 }); 312 return; 313 } else { 314 this.entityMap.set(entityClassName, intentObj); 315 } 316 } else { 317 const errorMessage: string = 'Decorators must be called as functions.'; 318 this.transformLog.push({ 319 type: LogType.ERROR, 320 message: errorMessage, 321 pos: this.currentNode.getStart(), 322 code: '10110002', 323 description: 'InsightIntent Compiler Error', 324 solutions: ['Add parentheses after the decorator name'] 325 }); 326 return; 327 } 328 } 329 330 private handlePageDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, 331 pkgParams: object): void { 332 const expr: ts.Expression = decorator.expression; 333 if (ts.isClassDeclaration(node)) { 334 const errorMessage: string = `@InsightIntentPage must be applied to a struct page.`; 335 this.transformLog.push({ 336 type: LogType.ERROR, 337 message: errorMessage, 338 pos: this.currentNode.getStart(), 339 code: '10110016', 340 description: 'InsightIntent Compiler Error', 341 solutions: ['Decorate a struct page with @InsightIntentPage'] 342 }); 343 return; 344 } 345 if (ts.isCallExpression(expr)) { 346 const args: ts.NodeArray<ts.Expression> = expr.arguments; 347 Object.assign(intentObj, { 348 'bundleName': projectConfig.bundleName, 349 'moduleName': projectConfig.moduleName, 350 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_PAGE 351 }); 352 this.analyzeDecoratorArgs(args, intentObj, IntentPageInfoChecker); 353 this.validatePagePath(intentObj, pkgParams); 354 this.createObfuscation(node); 355 if (this.isUpdateCompile) { 356 this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); 357 } 358 this.intentData.push(intentObj); 359 } else { 360 const errorMessage: string = 'Decorators must be called as functions.'; 361 this.transformLog.push({ 362 type: LogType.ERROR, 363 message: errorMessage, 364 pos: this.currentNode.getStart(), 365 code: '10110002', 366 description: 'InsightIntent Compiler Error', 367 solutions: ['Add parentheses after the decorator name'] 368 }); 369 return; 370 } 371 } 372 373 private handleMethodDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { 374 const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); 375 if (!isExported) { 376 const errorMessage: string = 'The class decorated with @InsightIntentFunction must be exported.'; 377 this.transformLog.push({ 378 type: LogType.ERROR, 379 message: errorMessage, 380 pos: this.currentNode.getStart(), 381 code: '10110014', 382 description: 'InsightIntent Compiler Error', 383 solutions: ['Add an export statement'] 384 }); 385 return; 386 } 387 const expr: ts.Expression = decorator.expression; 388 if (ts.isCallExpression(expr)) { 389 Object.assign(intentObj, { 390 'bundleName': projectConfig.bundleName, 391 'moduleName': projectConfig.moduleName, 392 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_METHOD 393 }); 394 const methodParameters: methodParametersInfo[] = this.parseClassMethods(node, COMPONENT_USER_INTENTS_DECORATOR_METHOD); 395 methodParameters?.forEach(methodDecorator => { 396 const functionName: string = methodDecorator.functionName; 397 const methodArgs: ts.NodeArray<ts.Expression> = methodDecorator.args; 398 const properties: Record<string, string> = methodDecorator.parameters; 399 const functionParamList: Array<string> = Object.keys(properties); 400 const methodObj: object = Object.assign({}, intentObj, { functionName, 'functionParamList': functionParamList }); 401 this.analyzeDecoratorArgs(methodArgs, methodObj, intentMethodInfoChecker); 402 if (this.isUpdateCompile) { 403 this.updatePageIntentObj.get(methodObj.decoratorFile).push(methodObj); 404 } 405 this.intentData.push(methodObj); 406 }); 407 this.createObfuscation(node); 408 } else { 409 const errorMessage: string = 'Decorators must be called as functions.'; 410 this.transformLog.push({ 411 type: LogType.ERROR, 412 message: errorMessage, 413 pos: this.currentNode.getStart(), 414 code: '10110002', 415 description: 'InsightIntent Compiler Error', 416 solutions: ['Add parentheses after the decorator name'] 417 }); 418 return; 419 } 420 } 421 422 private handleLinkDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { 423 const expr: ts.Expression = decorator.expression; 424 if (ts.isCallExpression(expr)) { 425 const args: ts.NodeArray<ts.Expression> = expr.arguments; 426 this.analyzeDecoratorArgs<IntentLinkInfo>(args, intentObj, IntentLinkInfoChecker); 427 Object.assign(intentObj, { 428 'bundleName': projectConfig.bundleName, 429 'moduleName': projectConfig.moduleName, 430 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR 431 }); 432 this.createObfuscation(node); 433 if (this.isUpdateCompile) { 434 this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); 435 } 436 this.intentData.push(intentObj); 437 } else { 438 const errorMessage: string = 'Decorators must be called as functions.'; 439 this.transformLog.push({ 440 type: LogType.ERROR, 441 message: errorMessage, 442 pos: this.currentNode.getStart(), 443 code: '10110002', 444 description: 'InsightIntent Compiler Error', 445 solutions: ['Add parentheses after the decorator name'] 446 }); 447 return; 448 } 449 } 450 451 private handleEntryDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, 452 pkgParams: object): void { 453 const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); 454 const isDefault: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DefaultKeyword); 455 if (!(isExported && isDefault)) { 456 const errorMessage: string = 'The class decorated with @InsightIntentEntry must be exported as default.'; 457 this.transformLog.push({ 458 type: LogType.ERROR, 459 message: errorMessage, 460 pos: this.currentNode.getStart(), 461 code: '10110019', 462 description: 'InsightIntent Compiler Error', 463 solutions: ['Use the \'export default\' syntax'] 464 }); 465 return; 466 } 467 const expr: ts.Expression = decorator.expression; 468 if (ts.isCallExpression(expr)) { 469 const args: ts.NodeArray<ts.Expression> = expr.arguments; 470 Object.assign(intentObj, { 471 'bundleName': projectConfig.bundleName, 472 'moduleName': projectConfig.moduleName, 473 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTRY 474 }); 475 this.analyzeDecoratorArgs<IntentEntryInfo>(args, intentObj, intentEntryInfoChecker); 476 const properties: Record<string, schemaVerifyType> = this.parseClassNode(node, intentObj.intentName, COMPONENT_USER_INTENTS_DECORATOR_ENTRY); 477 this.schemaValidateSync(properties, intentObj.parameters); 478 this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTRY); 479 this.createObfuscation(node); 480 this.processExecuteModeParam(intentObj); 481 if (this.isUpdateCompile) { 482 this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); 483 } 484 this.intentData.push(intentObj); 485 } else { 486 const errorMessage: string = 'Decorators must be called as functions.'; 487 this.transformLog.push({ 488 type: LogType.ERROR, 489 message: errorMessage, 490 pos: this.currentNode.getStart(), 491 code: '10110002', 492 description: 'InsightIntent Compiler Error', 493 solutions: ['Add parentheses after the decorator name'] 494 }); 495 return; 496 } 497 } 498 499 private processFormInfo(node: ts.ClassDeclaration, formClassPath: string, pkgParams: object, 500 intentObj: object): void { 501 if (this.moduleJsonInfo.size === 0 && pkgParams.pkgPath) { 502 this.readModuleJsonInfo(pkgParams); 503 } 504 const extensionAbilities: object[] = this.moduleJsonInfo.get('extensionAbilities'); 505 const bindFormInfo: object = extensionAbilities.find(extensionInfo => { 506 const formSrcEntryPath: string = path.join(pkgParams.pkgPath, 'src', 'main', extensionInfo.srcEntry); 507 return formSrcEntryPath === formClassPath && extensionInfo.type === 'form'; 508 }); 509 this.verifyFormName(bindFormInfo, intentObj); 510 const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); 511 const isDefault: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.DefaultKeyword); 512 if (!(bindFormInfo && isExported && isDefault)) { 513 const errorMessage: string = '@InsightIntentForm must be applied to formExtensionAbility.'; 514 this.transformLog.push({ 515 type: LogType.ERROR, 516 message: errorMessage, 517 pos: this.currentNode.getStart(), 518 code: '10110022', 519 description: 'InsightIntent Compiler Error', 520 solutions: ['Decorate the formExtensionAbility class with @InsightIntentForm'] 521 }); 522 return; 523 } 524 } 525 526 private verifyFormName(bindFormInfo: object, intentObj: object): void { 527 if (!bindFormInfo) { 528 return; 529 } 530 let formNameFound: boolean = false; 531 intentObj.abilityName = bindFormInfo.name; 532 bindFormInfo.metadata?.forEach(metaData => { 533 const formConfigName = `${metaData.resource.split(':').pop()}.json`; 534 const formConfigPath = path.join(projectConfig.aceProfilePath, formConfigName); 535 if (!fs.existsSync(formConfigPath)) { 536 return; 537 } 538 const formData = fs.readFileSync(formConfigPath, 'utf8'); 539 const formConfigs = JSON.parse(formData).forms; 540 if (formConfigs?.some(form => form.name === intentObj.formName)) { 541 formNameFound = true; 542 } 543 }); 544 if (!formNameFound) { 545 const errorMessage: string = 'formName in @InsightIntentForm must match the widget name registered in formExtensionAbility.'; 546 this.transformLog.push({ 547 type: LogType.ERROR, 548 message: errorMessage, 549 pos: this.currentNode.getStart(), 550 code: '10110023', 551 description: 'InsightIntent Compiler Error', 552 solutions: ['Update formName to match the registered widget name'] 553 }); 554 return; 555 } 556 } 557 558 private readModuleJsonInfo(pkgParams: object): void { 559 const moduleJsonPath: string = path.join(pkgParams.pkgPath, 'src/main', 'module.json5'); 560 if (!fs.existsSync(moduleJsonPath)) { 561 const errorMessage: string = `The module.json5 file is missing.`; 562 this.transformLog.push({ 563 type: LogType.ERROR, 564 message: errorMessage, 565 pos: this.currentNode.getStart(), 566 code: '10110024', 567 description: 'InsightIntent Compiler Error', 568 solutions: ['Check the expected paths (typically entry/src/main/config.json or module.json5) and restore the file'] 569 }); 570 return; 571 } 572 if (!projectConfig.modulePathMap) { 573 return; 574 } 575 const jsonStr: string = fs.readFileSync(moduleJsonPath, 'utf8'); 576 const obj: object = json5.parse(jsonStr); 577 if (obj.module?.abilities) { 578 this.moduleJsonInfo.set('abilities', obj.module.abilities); 579 } 580 if (obj.module?.extensionAbilities) { 581 this.moduleJsonInfo.set('extensionAbilities', obj.module.extensionAbilities); 582 } 583 } 584 585 private validatePagePath(intentObj: object, pkgParams: object): void { 586 if (pkgParams.pkgPath) { 587 const normalPagePath: string = path.join(pkgParams.pkgPath, 'src/main', intentObj.pagePath + '.ets'); 588 if (!fs.existsSync(normalPagePath)) { 589 const errorMessage: string = `PagePath in @InsightIntentPage does not match the actual page path.`; 590 this.transformLog.push({ 591 type: LogType.ERROR, 592 message: errorMessage, 593 pos: this.currentNode.getStart(), 594 code: '10110017', 595 description: 'InsightIntent Compiler Error', 596 solutions: ['Verify the file path'] 597 }); 598 return; 599 } else { 600 const Logger: IntentLogger = IntentLogger.getInstance(); 601 intentObj.pagePath = 602 '@normalized:' + getNormalizedOhmUrlByFilepath(normalPagePath, projectConfig, Logger, pkgParams, null); 603 } 604 } 605 } 606 607 private isGlobalPath(parentFilePath: string): boolean { 608 return globalModulePaths?.some(globalPath => { 609 const normalizedParent: string = path.normalize(parentFilePath).replace(/\\/g, '/'); 610 const normalizedGlobalPath: string = path.normalize(globalPath).replace(/\\/g, '/'); 611 return normalizedParent.startsWith(normalizedGlobalPath); 612 }); 613 } 614 615 private analyzeBaseClass(node: ts.ClassDeclaration, pkgParams: object, intentObj: object, 616 decoratorFlag: string): void { 617 const interfaces: ts.ExpressionWithTypeArguments[] = []; 618 if (decoratorFlag === COMPONENT_USER_INTENTS_DECORATOR_ENTRY) { 619 node.heritageClauses?.forEach(clause => { 620 if (clause.token === ts.SyntaxKind.ExtendsKeyword) { 621 interfaces.push(...clause.types); 622 } 623 }); 624 this.processEntryBaseClass(interfaces, intentObj, pkgParams); 625 } else if (decoratorFlag === COMPONENT_USER_INTENTS_DECORATOR_ENTITY) { 626 node.heritageClauses?.forEach(clause => { 627 if (clause.token === ts.SyntaxKind.ImplementsKeyword || clause.token === ts.SyntaxKind.ExtendsKeyword) { 628 interfaces.push(...clause.types); 629 } 630 }); 631 if (interfaces.length > 0) { 632 const parentNode: ts.ExpressionWithTypeArguments = interfaces[0]; 633 this.analyzeClassHeritage(parentNode, node, pkgParams, intentObj); 634 } else { 635 const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; 636 this.transformLog.push({ 637 type: LogType.ERROR, 638 message: errorMessage, 639 pos: this.currentNode.getStart(), 640 code: '10110021', 641 description: 'InsightIntent Compiler Error', 642 solutions: ['Add the implementation or inherit from a base intent entity'] 643 }); 644 return; 645 } 646 } 647 } 648 649 private processEntryBaseClass(interfaces: ts.ExpressionWithTypeArguments[], intentObj: object, 650 pkgParams: object): void { 651 if (interfaces.length > 0) { 652 const parentNode: ts.ExpressionWithTypeArguments = interfaces[0]; 653 const parentClassName: string = parentNode.expression.getText(); 654 const parentNodeSymbol: ts.Symbol = this.checker.getTypeAtLocation(parentNode).getSymbol(); 655 const parentFilePath: string = parentNodeSymbol.getDeclarations()?.[0].getSourceFile().fileName; 656 const isGlobalPathFlag: boolean = this.isGlobalPath(parentFilePath); 657 if (!(isGlobalPathFlag && parentClassName === 'InsightIntentEntryExecutor')) { 658 const errorMessage: string = `Classes decorated with @InsightIntentEntry must inherit from InsightIntentEntryExecutor.`; 659 this.transformLog.push({ 660 type: LogType.ERROR, 661 message: errorMessage, 662 pos: this.currentNode.getStart(), 663 code: '10110018', 664 description: 'InsightIntent Compiler Error', 665 solutions: ['Add the inheritance'] 666 }); 667 return; 668 } 669 const logger: IntentLogger = IntentLogger.getInstance(); 670 const parentRecordName: string = 671 getNormalizedOhmUrlByFilepath(parentFilePath, projectConfig, logger, pkgParams, null); 672 const recordPath: string = isGlobalPathFlag ? `sdk` : `@normalized:${parentRecordName}`; 673 this.collectClassInheritanceInfo(parentNode, intentObj, parentClassName, recordPath); 674 } else { 675 const errorMessage: string = `Classes decorated with @InsightIntentEntry must inherit from InsightIntentEntryExecutor.`; 676 this.transformLog.push({ 677 type: LogType.ERROR, 678 message: errorMessage, 679 pos: this.currentNode.getStart(), 680 code: '10110018', 681 description: 'InsightIntent Compiler Error', 682 solutions: ['Add the inheritance'] 683 }); 684 return; 685 } 686 } 687 688 private hasModifier(node: ts.Node, modifier: ts.SyntaxKind): boolean { 689 return (node.modifiers || []).some(m => m.kind === modifier); 690 } 691 692 private parseClassMethods(classNode: ts.ClassDeclaration, decoratorType: string): methodParametersInfo[] { 693 const methodsArr: methodParametersInfo[] = []; 694 for (const member of classNode.members) { 695 if (!ts.isMethodDeclaration(member)) { 696 continue; 697 } 698 const decorator: ts.ModifierLike = member.modifiers?.find(modifier => { 699 if (!ts.isDecorator(modifier)) { 700 return false; 701 } 702 let decoratorName: string | undefined; 703 if (ts.isCallExpression(modifier.expression)) { 704 decoratorName = `@${modifier.expression.expression.getText()}`; 705 } 706 return decoratorName === decoratorType; 707 }); 708 if (decorator && ts.isCallExpression(decorator.expression)) { 709 if (!this.hasModifier(member, ts.SyntaxKind.StaticKeyword)) { 710 const errorMessage: string = `Methods decorated with @InsightIntentFunctionMethod must be static.`; 711 this.transformLog.push({ 712 type: LogType.ERROR, 713 message: errorMessage, 714 pos: this.currentNode.getStart(), 715 code: '10110015', 716 description: 'InsightIntent Compiler Error', 717 solutions: ['Change the method to static'] 718 }); 719 return undefined; 720 } 721 let parameters: Record<string, string> = {}; 722 member.parameters.forEach(param => { 723 const paramName: string = param.name.getText(); 724 parameters[paramName] = this.checker.typeToString( 725 this.checker.getTypeAtLocation(param), 726 param, 727 ts.TypeFormatFlags.NoTruncation 728 ); 729 }); 730 const obj: methodParametersInfo = { 731 functionName: member.name.getText(), 732 parameters: parameters, 733 args: decorator.expression.arguments 734 }; 735 methodsArr.push(obj); 736 } 737 } 738 return methodsArr; 739 } 740 741 private analyzeClassHeritage( 742 parentNode: ts.ExpressionWithTypeArguments, node: ts.ClassDeclaration, pkgParams: object, intentObj: object 743 ): void { 744 const parentSymbol: ts.Symbol = this.checker.getTypeAtLocation(parentNode).getSymbol(); 745 let parentClassName: string; 746 node.heritageClauses.forEach(clause => { 747 if (clause.token === ts.SyntaxKind.ExtendsKeyword) { 748 parentClassName = parentNode.expression.getText(); 749 } else if (clause.token === ts.SyntaxKind.ImplementsKeyword) { 750 parentClassName = parentSymbol.getName(); 751 } 752 }); 753 intentObj.parentClassName = parentClassName; 754 const parentFilePath: string = parentSymbol.getDeclarations()?.[0].getSourceFile().fileName; 755 const logger: IntentLogger = IntentLogger.getInstance(); 756 const baseClassName: string = this.checker.getTypeAtLocation(node).getSymbol().getName(); 757 const baseFilePath: string = node.getSourceFile().fileName; 758 const baseRecordName: string = getNormalizedOhmUrlByFilepath(baseFilePath, projectConfig, logger, pkgParams, null); 759 const isGlobalPathFlag: boolean = this.isGlobalPath(parentFilePath); 760 const parentRecordName: string = 761 getNormalizedOhmUrlByFilepath(parentFilePath, projectConfig, logger, pkgParams, null); 762 if (isGlobalPathFlag) { 763 if (parentClassName !== 'IntentEntity') { 764 const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; 765 this.transformLog.push({ 766 type: LogType.ERROR, 767 message: errorMessage, 768 pos: this.currentNode.getStart(), 769 code: '10110021', 770 description: 'InsightIntent Compiler Error', 771 solutions: ['Add the implementation or inherit from a base intent entity'] 772 }); 773 return; 774 } 775 this.EntityHeritageClassSet.add(parentClassName + '_' + `sdk`); 776 } else { 777 this.EntityHeritageClassSet.add(parentClassName + '_' + `@normalized:${parentRecordName}`); 778 this.EntityExtendsMap.set(baseClassName, parentClassName); 779 } 780 this.heritageClassSet.add(baseClassName + '_' + `@normalized:${baseRecordName}`); 781 } 782 783 private collectClassInheritanceInfo( 784 parentNode: ts.ExpressionWithTypeArguments, intentObj: object, parentClassName: string, recordPath: string 785 ): void { 786 const ClassInheritanceInfo: object = { 787 'parentClassName': parentClassName, 788 'definitionFilePath': recordPath, 789 'generics': [] 790 }; 791 if (parentNode.typeArguments?.length > 0) { 792 parentNode.typeArguments.forEach((arg): void => { 793 this.getInheritanceInfoByTypeNode(arg, ClassInheritanceInfo, intentObj); 794 }); 795 } 796 Object.assign(intentObj, { 797 'ClassInheritanceInfo': ClassInheritanceInfo 798 }); 799 } 800 801 private getInheritanceInfoByTypeNode(arg: ts.TypeNode, ClassInheritanceInfo: object, intentObj: object): void { 802 const generic = {}; 803 const genericType: ts.Type = this.checker.getTypeAtLocation(arg); 804 let genericName: string; 805 let genericSource: string | undefined; 806 let recordGenericSource: string; 807 const genericSymbol: ts.Symbol | undefined = genericType.getSymbol(); 808 if (genericSymbol) { 809 genericName = genericSymbol.getName(); 810 genericSource = genericSymbol.declarations?.[0]?.getSourceFile().fileName; 811 recordGenericSource = path.relative(projectConfig.moduleName, genericSource).replace(/\\/g, '/'); 812 if (this.entityOwnerMap.has(intentObj.intentName)) { 813 const entityNames: string[] = this.entityOwnerMap.get(intentObj.intentName); 814 entityNames.push(genericName); 815 this.entityOwnerMap.set(intentObj.intentName, entityNames); 816 } else { 817 this.entityOwnerMap.set(intentObj.intentName, [genericName]); 818 } 819 } else { 820 genericName = this.checker.typeToString(genericType); 821 const parentTypeNode: ts.Node = arg.parent; 822 if (ts.isTypeReferenceNode(parentTypeNode)) { 823 const contextualType: ts.Type = this.checker.getTypeAtLocation(parentTypeNode); 824 const symbol: ts.Symbol = contextualType?.getSymbol(); 825 genericSource = symbol?.declarations?.[0]?.getSourceFile().fileName; 826 } 827 if (!genericSource && this.isPrimitiveType(genericType)) { 828 recordGenericSource = 'lib.es5.d.ts'; 829 } 830 } 831 Object.assign(generic, 832 { 833 'typeName': genericName, 834 'definitionFilePath': recordGenericSource 835 }); 836 ClassInheritanceInfo.generics.push(generic); 837 } 838 839 private isPrimitiveType(type: ts.Type): boolean { 840 return ( 841 (type.flags & ts.TypeFlags.StringLike) || 842 (type.flags & ts.TypeFlags.NumberLike) || 843 (type.flags & ts.TypeFlags.BooleanLike) 844 ) !== 0; 845 } 846 847 private parseClassNode(node: ts.ClassDeclaration, intentName: string, decoratorType: string): Record<string, schemaVerifyType> { 848 const mergedObject: Record<string, schemaVerifyType> = {}; 849 const type: ts.Type = this.checker.getTypeAtLocation(node); 850 const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type); 851 propertiesOfType.forEach((prop) => { 852 const objItem: Record<string, schemaVerifyType> = this.processProperty(prop, intentName, decoratorType); 853 Object.assign(mergedObject, objItem); 854 }); 855 return mergedObject; 856 } 857 858 private getEntityId(node: ts.ClassDeclaration): string { 859 let entityId: string; 860 const type: ts.Type = this.checker.getTypeAtLocation(node); 861 const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type); 862 propertiesOfType.forEach((prop) => { 863 if (prop.getName() === 'entityId') { 864 const declaration: ts.Declaration = prop.getDeclarations()?.[0]; 865 if (declaration) { 866 const initializer = ts.isIdentifier(declaration.initializer) ? 867 this.checker.getSymbolAtLocation(declaration.initializer)?.valueDeclaration?.initializer : 868 declaration.initializer; 869 entityId = initializer.text; 870 } 871 } 872 }); 873 return entityId; 874 } 875 876 private processProperty(prop: ts.Symbol, intentName: string, decoratorType: string): Record<string, schemaVerifyType> { 877 const propType: ts.Type = this.checker.getTypeOfSymbol(prop); 878 const { category } = this.getTypeCategory(propType); 879 const obj: Record<string, schemaVerifyType> = {}; 880 const propName: string = prop.getName(); 881 const entryBlackList: string[] = ['executeMode', 'context', 'windowStage', 'uiExtensionSession', 'onExecute']; 882 const formBlackList: string[] = ['context']; 883 if (decoratorType === COMPONENT_USER_INTENTS_DECORATOR_ENTRY && entryBlackList.includes(propName)) { 884 return obj; 885 } else if (decoratorType === COMPONENT_USER_INTENTS_DECORATOR_ENTRY && formBlackList.includes(propName)) { 886 return obj; 887 } 888 const tempschemaVerifyType: schemaVerifyType = { 889 type: '', 890 isEntity: false 891 }; 892 if (category === 'object') { 893 tempschemaVerifyType.type = 'object'; 894 if (this.isEntity(propType, intentName)) { 895 tempschemaVerifyType.isEntity = true; 896 } 897 } else if (category === 'array') { 898 if (this.isEntity(propType, intentName)) { 899 tempschemaVerifyType.type = 'array'; 900 tempschemaVerifyType.isEntity = true; 901 } 902 } else { 903 tempschemaVerifyType.type = this.checker.typeToString(propType); 904 } 905 Object.assign(obj, { 906 [propName]: tempschemaVerifyType 907 }); 908 return obj; 909 } 910 911 private isEntity(propType: ts.Type, intentName: string): boolean { 912 let propDeclaration: ts.Declaration; 913 let elementType: ts.Type | undefined; 914 const typeSymbol: ts.Symbol = propType.getSymbol(); 915 if (this.isArrayType(propType)) { 916 elementType = (propType as ts.TypeReference).typeArguments?.[0]; 917 propDeclaration = elementType.getSymbol()?.getDeclarations()[0]; 918 } else { 919 propDeclaration = typeSymbol.getDeclarations()?.[0]; 920 } 921 if (!propDeclaration) { 922 return false; 923 } 924 return propDeclaration.modifiers?.some(decorator => { 925 if (!ts.isDecorator(decorator)) { 926 return false; 927 } 928 let decoratorName: string | undefined; 929 if (ts.isCallExpression(decorator.expression)) { 930 decoratorName = `@${decorator.expression.expression.getText()}`; 931 } 932 if (decoratorName === '@InsightIntentEntity') { 933 const typeSymbol: ts.Symbol = propType.getSymbol(); 934 const propertyClassName: string = typeSymbol.getName(); 935 if (this.entityOwnerMap.has(intentName)) { 936 const entityNames: string[] = this.entityOwnerMap.get(intentName); 937 entityNames.push(propertyClassName); 938 this.entityOwnerMap.set(intentName, entityNames); 939 } else { 940 this.entityOwnerMap.set(intentName, [propertyClassName]); 941 } 942 return true; 943 } 944 return false; 945 }); 946 } 947 948 private getTypeCategory(type: ts.Type): { category: 'array' | 'object'; } { 949 const flags: ts.TypeFlags = type.getFlags(); 950 const valueDeclaration: ts.Declaration | undefined = type.getSymbol()?.valueDeclaration; 951 const isEnum: boolean = valueDeclaration ? ts.isEnumDeclaration(valueDeclaration) : false; 952 953 const isPrimitive: boolean = !!(flags & ts.TypeFlags.StringLike) || 954 !!(flags & ts.TypeFlags.NumberLike) || 955 !!(flags & ts.TypeFlags.BooleanLike) || 956 !!(flags & ts.TypeFlags.Null) || 957 !!(flags & ts.TypeFlags.Undefined); 958 959 const isArray: boolean = this.isArrayType(type); 960 const isObject: boolean = !isPrimitive && 961 !isArray && 962 (!!(flags & ts.TypeFlags.Object) || isEnum); 963 let category: 'array' | 'object'; 964 if (isArray) { 965 category = 'array'; 966 } else if (isObject) { 967 category = 'object'; 968 } 969 return { category }; 970 } 971 972 private isArrayType(type: ts.Type): boolean { 973 let isArray: boolean; 974 const symbol: ts.Symbol | undefined = type.getSymbol(); 975 const flags: ts.TypeFlags = type.getFlags(); 976 if (symbol) { 977 isArray = symbol.getName() === 'Array'; 978 } else { 979 isArray = !!(flags & ts.TypeFlags.Object) && 980 !!(type as ts.ObjectType).objectFlags && ts.ObjectFlags.Reference && 981 ((type as ts.TypeReference).target.getSymbol()?.getName() === 'Array'); 982 } 983 return isArray; 984 } 985 986 private removeDecorator(node: ts.ClassDeclaration, decoratorNames: string[]): ts.ClassDeclaration { 987 const filteredModifiers: ts.ModifierLike[] = node.modifiers.filter(decorator => { 988 if (!ts.isDecorator(decorator)) { 989 return true; 990 } 991 let decoratorName: string | undefined; 992 if (ts.isCallExpression(decorator.expression)) { 993 decoratorName = `@${decorator.expression.expression.getText()}`; 994 } 995 return !decoratorNames.includes(decoratorName); 996 }); 997 const updatedMembers: ts.ClassElement[] = node.members.map(member => { 998 return this.reduceMembers(member); 999 }); 1000 return ts.factory.updateClassDeclaration( 1001 node, 1002 filteredModifiers, 1003 node.name, 1004 node.typeParameters, 1005 node.heritageClauses, 1006 ts.factory.createNodeArray(updatedMembers) 1007 ); 1008 } 1009 1010 private reduceMembers(member: ts.ClassElement): ts.ClassElement { 1011 if (ts.isMethodDeclaration(member) && this.hasModifier(member, ts.SyntaxKind.StaticKeyword)) { 1012 const memberModifiers: ts.ModifierLike[] = (member.modifiers ?? []).filter(decorator => { 1013 if (!ts.isDecorator(decorator)) { 1014 return true; 1015 } 1016 let decoratorName: string | undefined; 1017 if (ts.isCallExpression(decorator.expression)) { 1018 decoratorName = `@${decorator.expression.expression.getText()}`; 1019 } 1020 return decoratorName !== COMPONENT_USER_INTENTS_DECORATOR_METHOD; 1021 }); 1022 if (memberModifiers.length !== (member.modifiers?.length ?? 0)) { 1023 return ts.factory.updateMethodDeclaration( 1024 member, 1025 memberModifiers, 1026 member.asteriskToken, 1027 member.name, 1028 member.questionToken, 1029 member.typeParameters, 1030 member.parameters, 1031 member.type, 1032 member.body! 1033 ); 1034 } 1035 } 1036 return member; 1037 } 1038 1039 private isSymbolConstant(symbol: ts.Symbol): boolean { 1040 const declaration: Declaration = symbol.valueDeclaration; 1041 1042 if (!this.isConstVariable(declaration)) { 1043 return false; 1044 } 1045 const varDecl: ts.VariableDeclaration = declaration as ts.VariableDeclaration; 1046 const initializer: Expression = varDecl.initializer; 1047 return initializer ? this.isConstantExpression(initializer) : false; 1048 } 1049 1050 private isConstVariable(node: ts.Node | undefined): node is ts.VariableDeclaration { 1051 if (!node || !ts.isVariableDeclaration(node)) { 1052 return false; 1053 } 1054 1055 const varList: VariableDeclarationList | CatchClause = node.parent; 1056 return !!varList && ts.isVariableDeclarationList(varList) && 1057 (varList.flags & ts.NodeFlags.Const) !== 0; 1058 } 1059 1060 private isConstantExpression(node: ts.Node): boolean { 1061 let flag: boolean = true; 1062 if (ts.isLiteralExpression(node) || node.kind === ts.SyntaxKind.TrueKeyword || 1063 node.kind === ts.SyntaxKind.FalseKeyword) { 1064 flag = true; 1065 } 1066 1067 if (ts.isIdentifier(node)) { 1068 const symbol: Symbol | undefined = this.checker.getSymbolAtLocation(node); 1069 flag = symbol ? this.isSymbolConstant(symbol) : false; 1070 } 1071 1072 if (ts.isArrayLiteralExpression(node)) { 1073 flag = node.elements.every(element => this.isConstantExpression(element)); 1074 } 1075 1076 if (ts.isObjectLiteralExpression(node)) { 1077 flag = node.properties.every(property => { 1078 if (ts.isPropertyAssignment(property)) { 1079 const nameIsConst: boolean = !ts.isComputedPropertyName(property.name); 1080 return nameIsConst && this.isConstantExpression(property.initializer); 1081 } 1082 1083 return false; 1084 }); 1085 } 1086 1087 if (ts.isCallExpression(node) && node.expression.getText() === '$r') { 1088 flag = node.arguments.every(node => { 1089 return ts.isStringLiteral(node); 1090 }); 1091 } 1092 if (!flag) { 1093 const errorMessage: string = `Decorator parameters must be compile-time constants.`; 1094 this.transformLog.push({ 1095 type: LogType.ERROR, 1096 message: errorMessage, 1097 pos: this.currentNode.getStart(), 1098 code: '10110000', 1099 description: 'InsightIntent Compiler Error', 1100 solutions: ['Use a fixed value (such as a string literal) instead of a variable'] 1101 }); 1102 return false; 1103 } 1104 return flag; 1105 } 1106 1107 private validateRequiredIntentLinkInfo<T>( 1108 node: ts.ObjectLiteralExpression, 1109 paramCheckFields: ParamChecker<T> 1110 ): void { 1111 const existingParams: Set<keyof T> = new Set<keyof T>(); 1112 const requiredFields: (keyof T)[] = paramCheckFields.requiredFields; 1113 const nestedCheckers: Map<string, ParamChecker<LinkIntentParamMapping>> = paramCheckFields.nestedCheckers; 1114 const allowedFields: Set<keyof T> = paramCheckFields.allowFields; 1115 const paramValidators: Record<keyof T, (v: ts.Expression) => boolean> = paramCheckFields.paramValidators; 1116 for (const prop of node.properties) { 1117 this.validateFields(prop, allowedFields, paramValidators); 1118 existingParams.add(prop.name.text); 1119 if (nestedCheckers && nestedCheckers.has(prop.name.text)) { 1120 this.validateSelfParamFields(prop, nestedCheckers); 1121 } 1122 } 1123 const missingFields: (keyof T)[] = requiredFields.filter(f => !existingParams.has(f)); 1124 if (missingFields.length > 0) { 1125 const errorMessage: string = `Required parameters are missing for the decorator.`; 1126 this.transformLog.push({ 1127 type: LogType.ERROR, 1128 message: errorMessage, 1129 pos: this.currentNode.getStart(), 1130 code: '10110003', 1131 description: 'InsightIntent Compiler Error', 1132 solutions: ['Add the required parameters as specified in the error message'] 1133 }); 1134 return; 1135 } 1136 } 1137 1138 private validateSelfParamFields(prop: ts.Node, 1139 nestedCheckers: Map<string, ParamChecker<LinkIntentParamMapping>>): void { 1140 const checker: ParamChecker<LinkIntentParamMapping> = nestedCheckers.get(prop.name.text); 1141 if (ts.isArrayLiteralExpression(prop.initializer)) { 1142 prop.initializer.elements.every(elem => { 1143 if (ts.isIdentifier(elem)) { 1144 const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(elem); 1145 const declaration: ts.Declaration = symbol?.valueDeclaration; 1146 this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(declaration.initializer, checker); 1147 } else { 1148 this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(elem, checker); 1149 } 1150 }); 1151 } else if (ts.isIdentifier(prop)) { 1152 const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(prop); 1153 const declaration: ts.Declaration = symbol?.valueDeclaration; 1154 this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(declaration.initializer, checker); 1155 } else { 1156 this.validateRequiredIntentLinkInfo<LinkIntentParamMapping>(prop, checker); 1157 } 1158 } 1159 1160 private validateFields<T>( 1161 prop: ts.Node, allowedFields: Set<keyof T>, paramValidators: Record<keyof T, (v: ts.Expression) => boolean> 1162 ): void { 1163 const paramName: keyof T = prop.name.text; 1164 if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { 1165 if (!allowedFields.has(paramName)) { 1166 const errorMessage: string = `Unsupported parameters found in the decorator.`; 1167 this.transformLog.push({ 1168 type: LogType.ERROR, 1169 message: errorMessage, 1170 pos: this.currentNode.getStart(), 1171 code: '10110005', 1172 description: 'InsightIntent Compiler Error', 1173 solutions: ['Remove any parameters that are not supported.'] 1174 }); 1175 return; 1176 } 1177 const validator: Function = paramValidators[paramName]; 1178 if (ts.isIdentifier(prop.initializer)) { 1179 const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(prop.initializer); 1180 const declaration: ts.Declaration = symbol?.valueDeclaration; 1181 if (validator && !validator(declaration?.initializer)) { 1182 const errorMessage: string = `The parameter type does not match the decorator's requirement.`; 1183 this.transformLog.push({ 1184 type: LogType.ERROR, 1185 message: errorMessage, 1186 pos: this.currentNode.getStart(), 1187 code: '10110004', 1188 description: 'InsightIntent Compiler Error', 1189 solutions: ['Adjust the type to match the expected type.'] 1190 }); 1191 return; 1192 } 1193 } else { 1194 if (validator && !validator(prop.initializer)) { 1195 const errorMessage: string = `The parameter type does not match the decorator's requirement.`; 1196 this.transformLog.push({ 1197 type: LogType.ERROR, 1198 message: errorMessage, 1199 pos: this.currentNode.getStart(), 1200 code: '10110004', 1201 description: 'InsightIntent Compiler Error', 1202 solutions: ['Adjust the type to match the expected type.'] 1203 }); 1204 return; 1205 } 1206 } 1207 } 1208 } 1209 1210 private analyzeDecoratorArgs<T>(args: ts.NodeArray<ts.Expression>, intentObj: object, 1211 paramChecker: ParamChecker<T>): void { 1212 args.forEach(arg => { 1213 if (ts.isIdentifier(arg)) { 1214 const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(arg); 1215 const declaration: ts.Declaration = symbol?.valueDeclaration; 1216 this.validateRequiredIntentLinkInfo<T>(declaration.initializer, paramChecker); 1217 } else { 1218 this.validateRequiredIntentLinkInfo<T>(arg, paramChecker); 1219 } 1220 const res: StaticValue = this.parseStaticObject(arg); 1221 Object.assign(intentObj, res); 1222 this.collectSchemaInfo(intentObj); 1223 }); 1224 } 1225 1226 private createObfuscation(classNode: ts.Node): void { 1227 ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.globalNames.add(classNode.name.text); 1228 const isExported: boolean = classNode.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); 1229 if (isExported) { 1230 ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.propertyNames.add(classNode.name.text); 1231 } 1232 classNode.members.forEach(member => { 1233 if (ts.isPropertyDeclaration(member) && member.name || ts.isFunctionDeclaration(member) || 1234 ts.isMethodDeclaration(member) || 1235 ts.isGetAccessor(member) || ts.isSetAccessor(member)) { 1236 const propName: string = member.name.getText(); 1237 ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.propertyNames.add(propName); 1238 } 1239 }); 1240 } 1241 1242 private parseStaticObject(node: ts.Node, visited: Set<ts.Node> = new Set()): StaticValue | undefined { 1243 if (visited.has(node)) { 1244 const errorMessage: string = `Circular dependencies detected in decorator parameters.`; 1245 this.transformLog.push({ 1246 type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), 1247 code: '10110006', 1248 description: 'InsightIntent Compiler Error', 1249 solutions: ['Refactor the data structure by extracting common variables or using ID references to avoid nesting'] 1250 }); 1251 return undefined; 1252 } 1253 visited.add(node); 1254 const literalValue: StaticValue | undefined = this.parseLiteralValue(node); 1255 if (literalValue !== undefined) { 1256 return literalValue; 1257 } 1258 if (ts.isIdentifier(node)) { 1259 const isStatic: boolean = this.isConstantExpression(node); 1260 if (isStatic) { 1261 const symbol: ts.Symbol | undefined = this.checker.getSymbolAtLocation(node); 1262 const declaration: ts.Declaration = symbol?.valueDeclaration; 1263 return this.parseStaticObject(declaration.initializer, visited); 1264 } 1265 } 1266 if (ts.isArrayLiteralExpression(node)) { 1267 return this.processArrayElements(node.elements); 1268 } 1269 if (ts.isObjectLiteralExpression(node)) { 1270 return this.processObjectElements(node); 1271 } 1272 if (ts.isCallExpression(node) && node.expression.getText() === '$r') { 1273 const isStatic: boolean = this.isConstantExpression(node); 1274 if (!isStatic) { 1275 return undefined; 1276 } 1277 return node.getText(); 1278 } 1279 if (ts.isPropertyAccessExpression(node)) { 1280 return this.processEnumElement(node); 1281 } 1282 const errorMessage: string = `Unsupported parameters found in the decorator.`; 1283 this.transformLog.push({ 1284 type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), 1285 code: '10110005', 1286 description: 'InsightIntent Compiler Error', 1287 solutions: ['Remove any parameters that are not supported'] 1288 }); 1289 return undefined; 1290 } 1291 1292 private parseLiteralValue(node: ts.Node): StaticValue | undefined { 1293 if (ts.isStringLiteral(node)) { 1294 return node.text; 1295 } 1296 if (ts.isNumericLiteral(node)) { 1297 return parseFloat(node.text); 1298 } 1299 if (node.kind === ts.SyntaxKind.TrueKeyword) { 1300 return true; 1301 } 1302 if (node.kind === ts.SyntaxKind.FalseKeyword) { 1303 return false; 1304 } 1305 if (node.kind === ts.SyntaxKind.NullKeyword) { 1306 return null; 1307 } 1308 if (node.kind === ts.SyntaxKind.UndefinedKeyword) { 1309 return undefined; 1310 } 1311 return undefined; 1312 } 1313 1314 private processEnumElement(node: ts.PropertyAccessExpression): string { 1315 const enumValue: string = node?.getText().split('.').pop(); 1316 const executeModeEnum: Map<string, string> = new Map(); 1317 executeModeEnum.set('UI_ABILITY_FOREGROUND', '0'); 1318 executeModeEnum.set('UI_ABILITY_BACKGROUND', '1'); 1319 executeModeEnum.set('UI_EXTENSION_ABILITY', '2'); 1320 executeModeEnum.set('SERVICE_EXTENSION_ABILITY', '3'); 1321 const paramCategoryEnum: Map<string, string> = new Map(); 1322 paramCategoryEnum.set('LINK', 'link'); 1323 paramCategoryEnum.set('WANT', 'want'); 1324 if (executeModeEnum.has(enumValue)) { 1325 return executeModeEnum.get(enumValue); 1326 } else if (paramCategoryEnum.has(enumValue)) { 1327 return paramCategoryEnum.get(enumValue); 1328 } else { 1329 const errorMessage: string = `Unsupported parameters found in the decorator.`; 1330 this.transformLog.push({ 1331 type: LogType.ERROR, 1332 message: errorMessage, 1333 pos: this.currentNode.getStart(), 1334 code: '10110005', 1335 description: 'InsightIntent Compiler Error', 1336 solutions: ['Remove any parameters that are not supported'] 1337 }); 1338 return ''; 1339 } 1340 } 1341 1342 private processObjectElements(elements: ts.ObjectLiteralExpression): { [key: string]: StaticValue } { 1343 const obj: { [key: string]: StaticValue } = {}; 1344 for (const prop of elements.properties) { 1345 if (ts.isPropertyAssignment(prop)) { 1346 const key: string = this.parsePropertyKey(prop.name); 1347 const value: StaticValue = this.parseStaticObject(prop.initializer); 1348 if (key !== undefined && value !== undefined) { 1349 obj[key] = value; 1350 } 1351 } 1352 1353 if (ts.isSpreadAssignment(prop)) { 1354 const spreadObj: StaticValue = this.parseStaticObject(prop.expression); 1355 if (typeof spreadObj === 'object' && spreadObj !== null) { 1356 Object.assign(obj, spreadObj); 1357 } 1358 } 1359 } 1360 return obj; 1361 } 1362 1363 private processArrayElements(elements: readonly ts.Node[]): StaticValue[] { 1364 const parsedElements: StaticValue[] = []; 1365 1366 elements.forEach((element) => { 1367 if (ts.isSpreadElement(element)) { 1368 const spreadValue: StaticValue = this.parseStaticObject(element.expression); 1369 if (Array.isArray(spreadValue)) { 1370 parsedElements.push(...spreadValue); 1371 } 1372 } else { 1373 const value: StaticValue = this.parseStaticObject(element); 1374 parsedElements.push(value); 1375 } 1376 }); 1377 1378 return parsedElements; 1379 } 1380 1381 private parsePropertyKey(node: ts.PropertyName): string | undefined { 1382 if (ts.isLiteralExpression(node)) { 1383 return node.text; 1384 } 1385 1386 if (ts.isIdentifier(node)) { 1387 return node.text; 1388 } 1389 return undefined; 1390 } 1391 1392 private processExecuteModeParam(intentObj: object): void { 1393 if (intentObj.executeMode) { 1394 intentObj.executeMode.forEach((item: string, index: number) => { 1395 if (item === '0') { 1396 intentObj.executeMode[index] = 'foreground'; 1397 } 1398 if (item === '1') { 1399 intentObj.executeMode[index] = 'background'; 1400 } 1401 if (item === '2') { 1402 intentObj.executeMode[index] = 'uiextension'; 1403 } 1404 if (item === '3') { 1405 intentObj.executeMode[index] = 'serviceextension'; 1406 } 1407 }); 1408 } 1409 } 1410 1411 private collectSchemaInfo(intentObj: object): void { 1412 if (intentObj.schema) { 1413 const schemaPath: string = path.join( 1414 __dirname, '../../insight_intents/schema', 1415 `${intentObj.schema}_${intentObj.intentVersion}.json` 1416 ); 1417 if (fs.existsSync(schemaPath)) { 1418 const schemaContent: string = fs.readFileSync(schemaPath, 'utf-8'); 1419 const schemaObj: object = JSON.parse(schemaContent); 1420 intentObj.parameters = schemaObj.parameters; 1421 intentObj.llmDescription = schemaObj.llmDescription; 1422 intentObj.keywords = schemaObj.keywords; 1423 intentObj.intentName = schemaObj.intentName; 1424 intentObj.result = schemaObj.result; 1425 intentObj.domain = schemaObj.domain; 1426 } 1427 } 1428 } 1429 1430 private verifyInheritanceChain(): void { 1431 this.EntityHeritageClassSet.forEach(entityClassInfo => { 1432 if (!this.heritageClassSet.has(entityClassInfo)) { 1433 const errorMessage: string = `Classes decorated with @InsightIntentEntity must implement InsightIntent.IntentEntity.`; 1434 this.transformLog.push({ 1435 type: LogType.ERROR, 1436 message: errorMessage, 1437 pos: this.currentNode.getStart(), 1438 code: '10110021', 1439 description: 'InsightIntent Compiler Error', 1440 solutions: ['Add the implementation or inherit from a base intent entity'] 1441 }); 1442 return; 1443 } 1444 }); 1445 } 1446 1447 private schemaValidationRequiredRule(schemaData: Record<string, schemaVerifyType>, schemaObj: object): void { 1448 const reqData: Map<string, boolean> = new Map(); 1449 schemaObj.required.forEach(key => reqData.set(key, true)); 1450 if (schemaObj.properties) { 1451 const paramsSchema: object = schemaObj.properties; 1452 const keyArr: string[] = Object.keys(paramsSchema); 1453 keyArr.forEach(key => { 1454 if (!schemaData[key] && reqData.get(key)) { 1455 const errorMessage: string = `A required field in the class property is missing.`; 1456 this.transformLog.push({ 1457 type: LogType.ERROR, 1458 message: errorMessage, 1459 pos: this.currentNode.getStart(), 1460 code: '10110008', 1461 description: 'InsightIntent Compiler Error', 1462 solutions: ['Add the required field as specified by the JSON Schema'] 1463 }); 1464 return; 1465 } 1466 }); 1467 } 1468 } 1469 1470 private schemaPropertiesValidation(schemaData: Record<string, schemaVerifyType>, schemaObj: object): void { 1471 if (schemaObj.properties) { 1472 Object.entries(schemaObj.properties).forEach(([key, value]) => { 1473 if ((schemaData[key]?.type && value.type !== schemaData[key].type) || 1474 value.type === 'object' && schemaData[key]?.isEntity === false) { 1475 const errorMessage: string = `The field type of the class property does not match the JSON Schema.`; 1476 this.transformLog.push({ 1477 type: LogType.ERROR, 1478 message: errorMessage, 1479 pos: this.currentNode.getStart(), 1480 code: '10110009', 1481 description: 'InsightIntent Compiler Error', 1482 solutions: ['Correct the type to match the requirement'] 1483 }); 1484 return; 1485 } 1486 }); 1487 } 1488 } 1489 1490 private schemaValidateRules(schemaData: Record<string, schemaVerifyType>, schemaObj: object): void { 1491 const schemaKeys: string[] = Object.keys(schemaData); 1492 if (schemaObj.oneOf) { 1493 let count: number = 0; 1494 const requiredOne: string[][] = schemaObj.oneOf.map(item => item.required); 1495 requiredOne.forEach(val => { 1496 const isContain: boolean = val.every((item): boolean => { 1497 return schemaKeys.includes(item); 1498 }); 1499 if (isContain) { 1500 count++; 1501 } 1502 }); 1503 if (count !== 1) { 1504 const errorMessage: string = `The class property parameter violates the oneOf/anyOf validation rules in the JSON Schema.`; 1505 this.transformLog.push({ 1506 type: LogType.ERROR, 1507 message: errorMessage, 1508 pos: this.currentNode.getStart(), 1509 code: '10110010', 1510 description: 'InsightIntent Compiler Error', 1511 solutions: ['Modify it to satisfy the rules'] 1512 }); 1513 return; 1514 } 1515 } 1516 if (schemaObj.anyOf) { 1517 let count: number = 0; 1518 const requiredAny: string[][] = schemaObj.anyOf.map(item => item.required); 1519 requiredAny.forEach(val => { 1520 const isContain: boolean = val.every((item): boolean => { 1521 return schemaKeys.includes(item); 1522 }); 1523 if (isContain) { 1524 count++; 1525 } 1526 }); 1527 if (count === 0) { 1528 const errorMessage: string = `The class property parameter violates the oneOf/anyOf validation rules in the JSON Schema.`; 1529 this.transformLog.push({ 1530 type: LogType.ERROR, 1531 message: errorMessage, 1532 pos: this.currentNode.getStart(), 1533 code: '10110010', 1534 description: 'InsightIntent Compiler Error', 1535 solutions: ['Modify it to satisfy the rules'] 1536 }); 1537 return; 1538 } 1539 } 1540 } 1541 1542 private schemaValidateSync(schemaData: Record<string, schemaVerifyType>, schemaObj: object): void { 1543 if (!schemaObj) { 1544 return; 1545 } 1546 if (schemaObj.additionalProperties === false) { 1547 this.schemaAdditionalPropertiesValidation(schemaData, schemaObj.properties); 1548 } 1549 if (schemaObj.items && schemaObj.items.type === 'array') { 1550 this.schemaValidateSync(schemaData, schemaObj.items.items); 1551 } 1552 if (schemaObj.type !== 'object') { 1553 const errorMessage: string = `The root type of the JSON Schema for Parameters must be object.`; 1554 this.transformLog.push({ 1555 type: LogType.ERROR, 1556 message: errorMessage, 1557 pos: this.currentNode.getStart(), 1558 code: '10110007', 1559 description: 'InsightIntent Compiler Error', 1560 solutions: ['Change the top-level definition to {"type":"object", ...}'] 1561 }); 1562 return; 1563 } 1564 if (schemaObj.properties) { 1565 const items: string[] = Object.keys(schemaObj.properties); 1566 if (items.length === 1 && items[0].type === 'array') { 1567 this.schemaValidateSync(schemaData, items[0].items); 1568 } else { 1569 this.schemaPropertiesValidation(schemaData, schemaObj); 1570 } 1571 } 1572 if (schemaObj.required) { 1573 this.schemaValidationRequiredRule(schemaData, schemaObj); 1574 } 1575 this.schemaValidateRules(schemaData, schemaObj); 1576 } 1577 1578 private schemaAdditionalPropertiesValidation(schemaData: Record<string, schemaVerifyType>, schemaProps: object): void { 1579 for (const key of Object.keys(schemaData)) { 1580 if (!schemaProps[key]) { 1581 const errorMessage: string = `The class property includes parameters not defined in the JSON Schema.`; 1582 this.transformLog.push({ 1583 type: LogType.ERROR, 1584 message: errorMessage, 1585 pos: this.currentNode.getStart(), 1586 code: '10110011', 1587 description: 'InsightIntent Compiler Error', 1588 solutions: ['Remove any extra parameters'] 1589 }); 1590 return; 1591 } 1592 } 1593 } 1594 1595 private processEntityOwnerMap(): void { 1596 for (const [intentName, entityClassNames] of this.entityOwnerMap.entries()) { 1597 const expandedClassNames = new Set<string>(entityClassNames); 1598 entityClassNames.forEach(className => { 1599 this.visitEntityHeritage(className, expandedClassNames); 1600 }); 1601 if (expandedClassNames.size > entityClassNames.length) { 1602 this.entityOwnerMap.set(intentName, Array.from(expandedClassNames)); 1603 } 1604 } 1605 } 1606 1607 private visitEntityHeritage(className: string, expandedClassNames: Set<string>): void { 1608 const parentClassName: string = this.EntityExtendsMap.get(className); 1609 if (parentClassName && !expandedClassNames.has(parentClassName)) { 1610 expandedClassNames.add(parentClassName); 1611 this.visitEntityHeritage(parentClassName, expandedClassNames); 1612 } 1613 } 1614 1615 private matchEntities(): void { 1616 if (this.entityMap.size === 0) { 1617 return; 1618 } 1619 this.processEntityOwnerMap(); 1620 const intentNameMappingMap: Map<string, object> = new Map(); 1621 this.intentData.forEach(data => { 1622 intentNameMappingMap.set(data.intentName, data); 1623 }); 1624 for (const [intentName, entityClassNames] of this.entityOwnerMap.entries()) { 1625 const targetIntent: object = intentNameMappingMap.get(intentName); 1626 if (!targetIntent) { 1627 continue; 1628 } 1629 const matchedEntities: object[] = []; 1630 entityClassNames.forEach(entityClassName => { 1631 if (this.entityMap.has(entityClassName)) { 1632 matchedEntities.push(this.entityMap.get(entityClassName)); 1633 } 1634 }); 1635 if (matchedEntities.length !== 0) { 1636 targetIntent.entities = matchedEntities; 1637 } 1638 } 1639 } 1640 1641 // This method writes the parsed data to a file. 1642 public writeUserIntentJsonFile(harIntentDataObj: object, share: object): void { 1643 const cachePath: string = 1644 path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file 1645 if (!projectConfig.aceProfilePath || !(fs.existsSync(cachePath) || this.intentData.length > 0 || Object.keys(harIntentDataObj).length !== 0)) { 1646 return; 1647 } 1648 const mergedData: object = this.processIntentData(harIntentDataObj); 1649 const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file 1650 try { 1651 if (Object.keys(mergedData).length > 0) { 1652 const cacheContent: object = { 1653 'extractInsightIntents': this.intentData, 1654 'entityOwnerMap': Object.fromEntries(this.entityOwnerMap.entries()), 1655 'entityMap': Object.fromEntries(this.entityMap.entries()), 1656 'heritageClassSet': Object.fromEntries(this.heritageClassSet.entries()), 1657 'entityHeritageClassSet': Object.fromEntries(this.EntityHeritageClassSet.entries()), 1658 'entityExtendsMap': Object.fromEntries(this.EntityExtendsMap.entries()) 1659 }; 1660 fs.writeFileSync(cacheSourceMapPath, JSON.stringify(mergedData, null, 2), 'utf-8'); 1661 fs.writeFileSync(cachePath, JSON.stringify(cacheContent, null, 2), 'utf-8'); 1662 } else if (fs.existsSync(cacheSourceMapPath)) { 1663 fs.unlinkSync(cacheSourceMapPath); 1664 } 1665 const normalizedPath: string = path.normalize(projectConfig.aceProfilePath); 1666 const fullPath: string = path.join(normalizedPath, '../../../module.json'); 1667 if (fs.existsSync(fullPath)) { 1668 const rawData: string = fs.readFileSync(fullPath, 'utf8'); 1669 const jsonData: object = JSON.parse(rawData); 1670 if (jsonData?.module) { 1671 jsonData.module.hasInsightIntent = Object.keys(mergedData).length > 0 ? true : undefined; 1672 } 1673 const updatedJson: string = JSON.stringify(jsonData, null, 2); 1674 fs.writeFileSync(fullPath, updatedJson, 'utf8'); 1675 } 1676 } catch (e) { 1677 const errorMessage: string = `Failed to write to the intent configuration file.`; 1678 this.transformLog.push({ 1679 type: LogType.ERROR, message: errorMessage, pos: this.currentNode.getStart(), 1680 code: '10110025', 1681 description: 'InsightIntent Compiler Error', 1682 solutions: ['Check file permissions, free disk space, or restart DevEco Studio'] 1683 }); 1684 return; 1685 } 1686 const logger = share.getLogger('etsTransform'); 1687 const hvigorLogger = share.getHvigorConsoleLogger?.(ABILITY_SUBSYSTEM_CODE); 1688 if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) { 1689 emitLogInfo(logger, getTransformLog(transformLog), true, this.currentFilePath, hvigorLogger); 1690 resetLog(); 1691 } 1692 } 1693 1694 private processUpdateEntities(cacheDataObj: object): void { 1695 const decoratorFileMapping: Set<string> = new Set(this.updatePageIntentObj.keys()); 1696 if (cacheDataObj.entityOwnerMap && Object.keys(cacheDataObj.entityOwnerMap || {}).length > 0) { 1697 const cacheEntityOwnerMap: Map<string, string[]> = cacheDataObj.entityOwnerMap as Map<string, string[]>; 1698 for (const [intentName, entityClassNames] of Object.entries(cacheEntityOwnerMap)) { 1699 if (!this.entityOwnerMap.has(intentName)) { 1700 this.entityOwnerMap.set(intentName, entityClassNames); 1701 } 1702 } 1703 } 1704 if (cacheDataObj.entityMap && Object.keys(cacheDataObj.entityMap || {}).length > 0) { 1705 const cacheEntityMap: Map<string, object> = cacheDataObj.entityMap as Map<string, object>; 1706 for (const [className, entityObj] of Object.entries(cacheEntityMap)) { 1707 if (!decoratorFileMapping.has(entityObj.decoratorFile)) { 1708 this.entityMap.set(entityObj.className, entityObj); 1709 } 1710 } 1711 } 1712 this.processUpdateHeritageVerify(cacheDataObj); 1713 this.intentData.map(userIntent => { 1714 if (userIntent.entities) { 1715 delete userIntent.entities; 1716 } 1717 }); 1718 } 1719 1720 private processUpdateHeritageVerify(cacheDataObj: object): void { 1721 const decoratorFileMapping: Set<string> = new Set(this.updatePageIntentObj.keys()); 1722 if (cacheDataObj.heritageClassSet && Object.keys(cacheDataObj.heritageClassSet || {}).length > 0) { 1723 const cacheHeritageClassSet: Set<string> = cacheDataObj.heritageClassSet as Set<string>; 1724 for (const entityPathInfo of Object.values(cacheHeritageClassSet)) { 1725 const decoratorFilePath: string = entityPathInfo.split('_').pop(); 1726 if (!decoratorFileMapping.has(decoratorFilePath)) { 1727 this.heritageClassSet.add(entityPathInfo); 1728 } 1729 } 1730 } 1731 if (cacheDataObj.entityHeritageClassSet && Object.keys(cacheDataObj.entityHeritageClassSet || {}).length > 0) { 1732 const cacheEntityExtendsMap: Set<string> = cacheDataObj.entityHeritageClassSet as Set<string>; 1733 for (const entityPathInfo of Object.values(cacheEntityExtendsMap)) { 1734 const decoratorFilePath: string = entityPathInfo.split('_').pop(); 1735 if (decoratorFileMapping.has(decoratorFilePath)) { 1736 this.EntityHeritageClassSet.add(entityPathInfo); 1737 } 1738 } 1739 } 1740 if (cacheDataObj.entityExtendsMap && Object.keys(cacheDataObj.entityExtendsMap || {}).length > 0) { 1741 const cacheEntityExtendsMap: Map<string, string> = cacheDataObj.entityExtendsMap as Map<string, string>; 1742 for (const [baseClassName, parentClassName] of Object.entries(cacheEntityExtendsMap)) { 1743 if (!this.EntityExtendsMap.has(baseClassName)) { 1744 this.EntityExtendsMap.set(baseClassName, parentClassName); 1745 } 1746 } 1747 } 1748 } 1749 1750 private processIntentData(harIntentDataObj: object): object { 1751 const cacheSourceMapPath: string = 1752 path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file 1753 const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file 1754 if (!fs.existsSync(projectConfig.aceProfilePath)) { 1755 fs.mkdirSync(projectConfig.aceProfilePath, { recursive: true }); 1756 } 1757 if (this.isUpdateCompile && fs.existsSync(cachePath)) { 1758 const cacheData: string = fs.readFileSync(cachePath, 'utf8'); 1759 const cacheDataObj: object = JSON.parse(cacheData); 1760 const insightIntents: object[] = cacheDataObj.extractInsightIntents.filter(insightIntent => { 1761 return !this.updatePageIntentObj.has(insightIntent.decoratorFile); 1762 }); 1763 this.updatePageIntentObj.forEach(insightIntent => { 1764 insightIntents.push(...insightIntent); 1765 }); 1766 this.intentData = insightIntents; 1767 this.processUpdateEntities(cacheDataObj); 1768 } 1769 this.verifyInheritanceChain(); 1770 this.matchEntities(); 1771 let writeJsonData: object = {}; 1772 if (fs.existsSync(cacheSourceMapPath)) { 1773 const originIntents: string = fs.readFileSync(cacheSourceMapPath, 'utf8'); 1774 const jsonData: object = JSON.parse(originIntents); 1775 Object.assign(jsonData, { 1776 'extractInsightIntents': this.intentData 1777 }); 1778 writeJsonData = jsonData; 1779 } else if (this.intentData.length > 0) { 1780 Object.assign(writeJsonData, { 1781 'extractInsightIntents': this.intentData 1782 }); 1783 } 1784 const mergedData: object = this.mergeHarData(writeJsonData, harIntentDataObj); 1785 this.validateIntentIntentName(mergedData); 1786 return mergedData; 1787 } 1788 1789 private mergeHarData(writeJsonData: object, harIntentDataObj: object): object { 1790 let mergedData: object = {}; 1791 if (writeJsonData) { 1792 mergedData = JSON.parse(JSON.stringify(writeJsonData)); 1793 } 1794 Object.keys(harIntentDataObj || {})?.forEach(harName => { 1795 if (harIntentDataObj[harName].extractInsightIntents) { 1796 harIntentDataObj[harName].extractInsightIntents.forEach(intentObj => { 1797 intentObj.moduleName = projectConfig.moduleName; 1798 intentObj.bundleName = projectConfig.bundleName; 1799 }); 1800 if (harIntentDataObj[harName].extractInsightIntents) { 1801 mergedData.extractInsightIntents?.push(...harIntentDataObj[harName].extractInsightIntents); 1802 } 1803 } 1804 }); 1805 return mergedData; 1806 } 1807 1808 // This method get the user's intents from the bytecode HAR package. 1809 public getHarData(): object { 1810 const harIntentDataObj: object = {}; 1811 if (fs.existsSync(projectConfig.aceBuildJson)) { 1812 const loaderJson: string = fs.readFileSync(projectConfig.aceBuildJson, 'utf8'); 1813 const { byteCodeHarInfo } = JSON.parse(loaderJson); 1814 Object.keys(byteCodeHarInfo || {})?.forEach((harName) => { 1815 const harAbcFilePath = byteCodeHarInfo[harName].abcPath as string; 1816 const harModulePath: string = harAbcFilePath.split('ets')[0]; 1817 const harSourcePath: string = path.join(harModulePath, 'src', 'main', 'resources', 'base', 'profile'); 1818 const intentDataSourcePath: string = path.join(harSourcePath, 'insight_intent.json'); 1819 let harIntentData: object = {}; 1820 if (fs.existsSync(intentDataSourcePath)) { 1821 harIntentData = JSON.parse(fs.readFileSync(intentDataSourcePath, 'utf8')) as object; 1822 } 1823 Object.assign(harIntentDataObj, { 1824 harName: harIntentData 1825 }); 1826 }); 1827 } 1828 return harIntentDataObj; 1829 } 1830 1831 private validateIntentIntentName(writeJsonData: object): void { 1832 const duplicates = new Set<string>(); 1833 writeJsonData.insightIntents?.forEach(insightIntent => { 1834 duplicates.add(insightIntent.intentName); 1835 }); 1836 writeJsonData.extractInsightIntents?.forEach(item => { 1837 if (duplicates.has(item.intentName)) { 1838 const errorMessage: string = `Duplicate intentName definitions found.`; 1839 this.transformLog.push({ 1840 type: LogType.ERROR, 1841 message: errorMessage, 1842 pos: this.currentNode.getStart(), 1843 code: '10110012', 1844 description: 'InsightIntent Compiler Error', 1845 solutions: ['Rename or remove duplicate entries'] 1846 }); 1847 return; 1848 } else if (item.intentName !== undefined) { 1849 duplicates.add(item.intentName); 1850 } 1851 }); 1852 } 1853 1854 public clear(): void { 1855 this.intentData = []; 1856 this.checker = null; 1857 this.currentFilePath = ''; 1858 this.heritageClassSet = new Set<string>(); 1859 this.heritageClassSet.add('IntentEntity_sdk'); 1860 this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); 1861 this.isInitCache = false; 1862 this.isUpdateCompile = true; 1863 this.updatePageIntentObj = new Map(); 1864 this.entityMap = new Map(); 1865 this.entityOwnerMap = new Map(); 1866 this.moduleJsonInfo = new Map(); 1867 this.EntityHeritageClassSet = new Set(); 1868 this.EntityExtendsMap = new Map(); 1869 } 1870} 1871 1872export default new ParseIntent(); 1873