From 359a2007d65283ca38f67867ff7a8c7f1556584d Mon Sep 17 00:00:00 2001 From: zhangzezhong Date: Fri, 6 Jun 2025 19:24:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=84=8F=E5=9B=BE=E6=A1=86=E6=9E=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9EForm=E8=A3=85=E9=A5=B0=E5=99=A8=E4=B8=8EEntity?= =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhangzezhong --- compiler/src/pre_define.ts | 2 + .../src/userIntents_parser/intentLogger.ts | 26 +- compiler/src/userIntents_parser/intentType.ts | 49 +- .../userIntents_parser/parseUserIntents.ts | 481 ++++++++++++++---- .../@InsightIntentEntity.js.sample | 26 + .../@InsightIntentEntity.ets | 44 ++ 6 files changed, 514 insertions(+), 114 deletions(-) create mode 100644 compiler/test/transform_ut/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.js.sample create mode 100644 compiler/test/transform_ut/application/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.ets diff --git a/compiler/src/pre_define.ts b/compiler/src/pre_define.ts index 7b041ad59..82109acda 100644 --- a/compiler/src/pre_define.ts +++ b/compiler/src/pre_define.ts @@ -98,6 +98,8 @@ export const COMPONENT_USER_INTENTS_DECORATOR_ENTRY: string = '@InsightIntentEnt export const COMPONENT_USER_INTENTS_DECORATOR_FUNCTION: string = '@InsightIntentFunction'; export const COMPONENT_USER_INTENTS_DECORATOR_METHOD: string = '@InsightIntentFunctionMethod'; export const COMPONENT_USER_INTENTS_DECORATOR_PAGE: string = '@InsightIntentPage'; +export const COMPONENT_USER_INTENTS_DECORATOR_ENTITY: string = '@InsightIntentEntity'; +export const COMPONENT_USER_INTENTS_DECORATOR_FORM: string = '@InsightIntentForm'; export const CHECK_COMPONENT_EXTEND_DECORATOR: string = 'Extend'; export const STRUCT_CONTEXT_METHOD_DECORATORS: Set = new Set([COMPONENT_BUILDER_DECORATOR, COMPONENT_STYLES_DECORATOR, COMPONENT_LOCAL_BUILDER_DECORATOR]); diff --git a/compiler/src/userIntents_parser/intentLogger.ts b/compiler/src/userIntents_parser/intentLogger.ts index 40848c1c8..8eb995cf3 100644 --- a/compiler/src/userIntents_parser/intentLogger.ts +++ b/compiler/src/userIntents_parser/intentLogger.ts @@ -86,26 +86,26 @@ export class IntentLogger { } export const ENTRYPATH_ERROR: LogData = LogDataFactory.newInstance('1001', '[InsightIntent] IntentDecorator needs to be in the .ets file'); -export const DECORATOR_STATEMENT_ERROR: LogData = LogDataFactory.newInstance('1002', '[InsightIntent] decorator is not CallExpression'); +export const DECORATOR_STATEMENT_ERROR: LogData = LogDataFactory.newInstance('1002', '[InsightIntent] Decorator is not CallExpression'); export const DYNAMIC_PARAM_ERROR: LogData = LogDataFactory.newInstance('1003', '[InsightIntent] Dynamic variable cannot be resolved'); -export const DISALLOWED_PARAM_ERROR: LogData = LogDataFactory.newInstance('1004', '[InsightIntent] param is disallowed'); -export const UNSUPPORTED_PARSE_ERROR: LogData = LogDataFactory.newInstance('1005', '[InsightIntent] unsupported parameter type cannot be parsed'); -export const INCORRECT_PARAM_TYPE_ERROR: LogData = LogDataFactory.newInstance('1006', '[InsightIntent] param parsing occurs error param type'); -export const REQUIRED_PARAM_DISMISS_ERROR: LogData = LogDataFactory.newInstance('1007', '[InsightIntent] decorator args missing required param'); -export const INTERNAL_ERROR: LogData = LogDataFactory.newInstance('1008', '[InsightIntent] internal error'); +export const DISALLOWED_PARAM_ERROR: LogData = LogDataFactory.newInstance('1004', '[InsightIntent] Param is disallowed'); +export const UNSUPPORTED_PARSE_ERROR: LogData = LogDataFactory.newInstance('1005', '[InsightIntent] Unsupported parameter type cannot be parsed'); +export const INCORRECT_PARAM_TYPE_ERROR: LogData = LogDataFactory.newInstance('1006', '[InsightIntent] Param parsing occurs error param type'); +export const REQUIRED_PARAM_DISMISS_ERROR: LogData = LogDataFactory.newInstance('1007', '[InsightIntent] Decorator args missing required param'); +export const INTERNAL_ERROR: LogData = LogDataFactory.newInstance('1008', '[InsightIntent] Internal error'); export const SCHEMA_VALIDATE_ONE_OF_ERROR: LogData = LogDataFactory.newInstance('1009', '[InsightIntent] Not meeting the one of schema verification rules'); export const SCHEMA_VALIDATE_ANY_OF_ERROR: LogData = LogDataFactory.newInstance('1010', '[InsightIntent] Not meeting the any of schema verification rules'); -export const SCHEMA_VALIDATE_TYPE_ERROR: LogData = LogDataFactory.newInstance('1011', '[InsightIntent] schema verification parameter type error'); +export const SCHEMA_VALIDATE_TYPE_ERROR: LogData = LogDataFactory.newInstance('1011', '[InsightIntent] Schema verification parameter type error'); export const SCHEMA_VALIDATE_REQUIRED_ERROR: LogData = LogDataFactory.newInstance('1012', - '[InsightIntent] schema verification required parameter does not exist'); + '[InsightIntent] Schema verification required parameter does not exist'); export const SCHEMA_ROOT_TYPE_MISMATCH_ERROR: LogData = LogDataFactory.newInstance('1013', '[InsightIntent] Schema root type must be \'object\''); export const INVALID_BASE_CLASS_ERROR: LogData = LogDataFactory.newInstance('1014', - '[InsightIntent] decorated with @InsightIntentEntry has an invalid inheritance hierarchy.'); + '[InsightIntent] Invalid inheritance hierarchy'); export const PARAM_CIRCULAR_REFERENCE_ERROR: LogData = LogDataFactory.newInstance('1015', '[InsightIntent] Circular reference detected in param'); -export const INVALID_PAGEPATH_ERROR: LogData = LogDataFactory.newInstance('1016', - '[InsightIntent] @InsightIntentPage Resolved \'pagePath\' path not found in project directory'); +export const INCORRECT_PARAM_ERROR: LogData = LogDataFactory.newInstance('1016', + '[InsightIntent] Decorator param validate fail'); export const DECORATOR_ILLEGAL_USE: LogData = LogDataFactory.newInstance('1017', - '[InsightIntent] @InsightIntentFunctionMethod must be declared under the @InsightIntentFunction decorator'); + '[InsightIntent] Decorator illegal use'); export const DECORATOR_DUPLICATE_INTENTNAME: LogData = LogDataFactory.newInstance('1018', - '[InsightIntent] user intents has duplicate intentName param'); + '[InsightIntent] User intents has duplicate intentName param'); diff --git a/compiler/src/userIntents_parser/intentType.ts b/compiler/src/userIntents_parser/intentType.ts index bfe00c16e..4e6f7ddc0 100644 --- a/compiler/src/userIntents_parser/intentType.ts +++ b/compiler/src/userIntents_parser/intentType.ts @@ -60,6 +60,15 @@ export interface IntentPageInfo extends IntentInfo { navDestinationName: string; } +export interface IntentEntityDecoratorInfo { + entityCategory: string; + parameters?: object; +} + +export interface FormIntentDecoratorInfo extends IntentInfo { + formName: string; +} + export class ParamChecker { private _requiredFields: (keyof T)[]; private _allowFields: Set; @@ -193,7 +202,7 @@ IntentLinkInfoChecker.paramValidators = { intentVersion: validateRequiredString, displayName: validateRequiredString, displayDescription: validateOptionalString, - schema: validateRequiredString, + schema: validateOptionalString, icon: validateIcon, llmDescription: validateOptionalString, uri: validateRequiredString @@ -201,7 +210,7 @@ IntentLinkInfoChecker.paramValidators = { IntentLinkInfoChecker.nestedCheckers = new Map([['paramMappings', IntentLinkParamsChecker]]); export const intentEntryInfoChecker: ParamChecker = new ParamChecker(); -intentEntryInfoChecker.requiredFields = [...BASE_REQUIRED as Array, 'abilityName', 'executeMode']; +intentEntryInfoChecker.requiredFields = [...BASE_REQUIRED as Array, 'abilityName']; intentEntryInfoChecker.allowFields = new Set([...BASE_ALLOW as Array, 'abilityName', 'executeMode']); intentEntryInfoChecker.paramValidators = { example: validateOptionalString, @@ -209,7 +218,7 @@ intentEntryInfoChecker.paramValidators = { parameters: validateParameters, icon: validateIcon, keywords: validateKeywords, - schema: validateRequiredString, + schema: validateOptionalString, abilityName: validateRequiredString, displayDescription: validateRequiredString, displayName: validateRequiredString, @@ -243,7 +252,7 @@ intentMethodInfoChecker.paramValidators = { parameters: validateParameters, icon: validateIcon, keywords: validateKeywords, - schema: validateRequiredString, + schema: validateOptionalString, intentName: validateRequiredString, domain: validateRequiredString, intentVersion: validateRequiredString, @@ -268,7 +277,7 @@ IntentPageInfoChecker.paramValidators = { parameters: validateParameters, icon: validateIcon, keywords: validateKeywords, - schema: validateRequiredString, + schema: validateOptionalString, intentName: validateRequiredString, domain: validateRequiredString, intentVersion: validateRequiredString, @@ -280,3 +289,33 @@ IntentPageInfoChecker.paramValidators = { navigationId: validateOptionalString, navDestinationName: validateOptionalString }; + +export const IntentEntityInfoChecker: ParamChecker = new ParamChecker(); +IntentEntityInfoChecker.requiredFields = ['entityCategory']; +IntentEntityInfoChecker.allowFields = new Set(['entityCategory', 'parameters']); + +IntentEntityInfoChecker.paramValidators = { + entityCategory: validateOptionalString, + parameters: validateParameters +}; + +export const intentFormInfoChecker = new ParamChecker(); +intentFormInfoChecker.requiredFields = [...BASE_REQUIRED as Array, 'formName']; +intentFormInfoChecker.allowFields = new Set([ + ...BASE_ALLOW as Array, 'formName' +]); +intentFormInfoChecker.paramValidators = { + formName: validateOptionalString, + example: validateOptionalString, + result: validateParameters, + parameters: validateParameters, + icon: validateIcon, + keywords: validateKeywords, + schema: validateRequiredString, + intentName: validateRequiredString, + domain: validateRequiredString, + intentVersion: validateRequiredString, + displayName: validateRequiredString, + displayDescription: validateOptionalString, + llmDescription: validateOptionalString +}; diff --git a/compiler/src/userIntents_parser/parseUserIntents.ts b/compiler/src/userIntents_parser/parseUserIntents.ts index 081f3234c..3f1d3abb0 100644 --- a/compiler/src/userIntents_parser/parseUserIntents.ts +++ b/compiler/src/userIntents_parser/parseUserIntents.ts @@ -21,7 +21,8 @@ import { IntentLinkInfoChecker, intentMethodInfoChecker, LinkIntentParamMapping, IntentPageInfoChecker, - ParamChecker + ParamChecker, IntentEntityInfoChecker, + intentFormInfoChecker } from './intentType'; import { DECORATOR_DUPLICATE_INTENTNAME, @@ -29,11 +30,11 @@ import { DECORATOR_STATEMENT_ERROR, DISALLOWED_PARAM_ERROR, DYNAMIC_PARAM_ERROR, - ENTRYPATH_ERROR, + ENTRYPATH_ERROR, INCORRECT_PARAM_ERROR, INCORRECT_PARAM_TYPE_ERROR, IntentLogger, INTERNAL_ERROR, - INVALID_BASE_CLASS_ERROR, INVALID_PAGEPATH_ERROR, + INVALID_BASE_CLASS_ERROR, PARAM_CIRCULAR_REFERENCE_ERROR, REQUIRED_PARAM_DISMISS_ERROR, SCHEMA_ROOT_TYPE_MISMATCH_ERROR, @@ -47,13 +48,16 @@ import path from 'path'; import { getNormalizedOhmUrlByFilepath } from '../ark_utils'; import { globalModulePaths, projectConfig } from '../../main'; import fs from 'fs'; +import json5 from 'json5'; import { ProjectCollections } from 'arkguard'; import { COMPONENT_USER_INTENTS_DECORATOR, + COMPONENT_USER_INTENTS_DECORATOR_ENTITY, COMPONENT_USER_INTENTS_DECORATOR_ENTRY, COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, COMPONENT_USER_INTENTS_DECORATOR_METHOD, - COMPONENT_USER_INTENTS_DECORATOR_PAGE + COMPONENT_USER_INTENTS_DECORATOR_PAGE, + COMPONENT_USER_INTENTS_DECORATOR_FORM } from '../pre_define'; import { CompileEvent, @@ -74,8 +78,12 @@ class ParseIntent { this.intentData = []; this.currentFilePath = ''; this.heritageClassSet = new Set(); + this.heritageClassSet.add('IntentEntity_sdk'); this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); this.updatePageIntentObj = new Map(); + this.entityMap = new Map(); + this.entityOwnerMap = new Map(); + this.moduleJsonInfo = new Map(); } checker: ts.TypeChecker; @@ -85,6 +93,9 @@ class ParseIntent { updatePageIntentObj: Map; isUpdateCompile: boolean = false; isInitCache : boolean = false; + entityMap : Map; + entityOwnerMap : Map; + moduleJsonInfo: Map; hasDecorator(node: ts.Node, decorators: string[]): boolean { if (!node.modifiers) { @@ -121,12 +132,14 @@ class ParseIntent { } } const definedDecorators: string[] = [COMPONENT_USER_INTENTS_DECORATOR, COMPONENT_USER_INTENTS_DECORATOR_ENTRY, - COMPONENT_USER_INTENTS_DECORATOR_FUNCTION, COMPONENT_USER_INTENTS_DECORATOR_PAGE]; + COMPONENT_USER_INTENTS_DECORATOR_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])) { - throw Error(`${DECORATOR_ILLEGAL_USE.toString()}, className: ${node.name.getText()}`); + throw Error(`${DECORATOR_ILLEGAL_USE.toString()}, @InsightIntentFunctionMethod must be declared under the @InsightIntentFunction decorator,` + + `className: ${node.name.getText()}`); } }); } @@ -154,7 +167,7 @@ class ParseIntent { if (!expr || !ts.isCallExpression(expr)) { return; } - const argumentKind: ts.SyntaxKin | undefined = expr.arguments[0]?.kind; + const argumentKind: ts.SyntaxKind | undefined = expr.arguments[0]?.kind; if (argumentKind && argumentKind === ts.SyntaxKind.NullKeyword || argumentKind && argumentKind === ts.SyntaxKind.UndefinedKeyword) { return; } @@ -164,12 +177,8 @@ class ParseIntent { return; } const decoratorSourceFile: string = declarations[0].getSourceFile().fileName; - const isGlobalPath: boolean = globalModulePaths?.some(globalPath => { - const normalizedParent: string = path.normalize(decoratorSourceFile).replace(/\\/g, '/'); - const normalizedGlobalPath: string = path.normalize(globalPath).replace(/\\/g, '/'); - return normalizedParent.startsWith(normalizedGlobalPath); - }); - if (!isGlobalPath) { + const isGlobalPathFlag: boolean = this.isGlobalPath(decoratorSourceFile); + if (!isGlobalPathFlag) { return; } const Logger: IntentLogger = IntentLogger.getInstance(); @@ -187,12 +196,78 @@ class ParseIntent { 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); } }); } - handlePageDecorator(intentObj: object, node: ts.Node, decorator: ts.Decorator, pkgParams: object): void { + handleFormDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { + const expr: ts.Expression = decorator.expression; + if (ts.isCallExpression(expr)) { + const args: ts.NodeArray = expr.arguments; + Object.assign(intentObj, { + 'bundleName': projectConfig.bundleName, + 'moduleName': projectConfig.moduleName, + 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_FORM + }); + this.analyzeDecoratorArgs(args, intentObj, intentFormInfoChecker); + const properties: Record = this.parseClassNode(node, intentObj.intentName); + this.processFormInfo(node, this.currentFilePath, pkgParams, intentObj); + this.schemaValidateSync(properties, intentObj.parameters); + this.createObfuscation(node); + if (this.isUpdateCompile) { + this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); + } + this.intentData.push(intentObj); + } else { + throw Error(`${DECORATOR_STATEMENT_ERROR.toString()}, invalidDecoratorPath: ${this.currentFilePath}`); + } + } + + handleEntityDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { + const entityClassName: string = this.checker.getTypeAtLocation(node).getSymbol().getName(); + const expr: ts.Expression = decorator.expression; + if (ts.isCallExpression(expr)) { + const args: ts.NodeArray = expr.arguments; + Object.assign(intentObj, { + 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTITY, + 'className': intentObj.decoratorClass + }); + delete intentObj.decoratorClass; + this.analyzeDecoratorArgs(args, intentObj, IntentEntityInfoChecker); + const properties: Record = this.parseClassNode(node, undefined); + const entityId: string = this.getEntityId(node); + Object.assign(properties, { + 'entityId': entityId + }); + Object.assign(intentObj, { + 'entityId': entityId + }); + this.schemaValidateSync(properties, intentObj.parameters); + this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTITY); + this.createObfuscation(node); + if (this.isUpdateCompile) { + this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); + } + if (this.entityMap.has(entityClassName)) { + throw Error(`${DECORATOR_ILLEGAL_USE}, a class can be decorated with at most one @InsightIntentEntity`); + } else { + this.entityMap.set(entityClassName, intentObj); + } + } else { + throw Error(`${DECORATOR_STATEMENT_ERROR.toString()}, invalidDecoratorPath: ${this.currentFilePath}`); + } + } + + handlePageDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const expr: ts.Expression = decorator.expression; + if (ts.isClassDeclaration(node)) { + throw Error(`${DECORATOR_ILLEGAL_USE.toString()}, @InsightIntentPage must be decorated on struct` + + `, className: ${node.name.getText()}`); + } if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; Object.assign(intentObj, { @@ -201,8 +276,11 @@ class ParseIntent { 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_PAGE }); this.analyzeDecoratorArgs(args, intentObj, IntentPageInfoChecker); + if (intentObj.uiAbility) { + this.validateAbility(intentObj.uiAbility, pkgParams, true); + } + this.validatePagePath(intentObj, pkgParams); this.createObfuscation(node); - this.transformPagePath(intentObj, pkgParams); if (this.isUpdateCompile) { this.updatePageIntentObj.get(intentObj.decoratorFile).push(intentObj); } @@ -212,7 +290,12 @@ class ParseIntent { } } - handleMethodDecorator(intentObj: object, node: ts.Node, decorator: ts.Decorator): void { + handleMethodDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { + const isExported: boolean = node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword); + if (!isExported) { + throw Error(`${DECORATOR_ILLEGAL_USE.toString()}, classes with @InsightIntentFunction must be exported` + + `className: ${node.name.getText()}`); + } const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { Object.assign(intentObj, { @@ -246,7 +329,7 @@ class ParseIntent { } } - handleLinkDecorator(intentObj: object, node: ts.Node, decorator: ts.Decorator): void { + handleLinkDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator): void { const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; @@ -266,7 +349,7 @@ class ParseIntent { } } - handleEntryDecorator(intentObj: object, node: ts.Node, decorator: ts.Decorator, pkgParams: object): void { + handleEntryDecorator(intentObj: object, node: ts.ClassDeclaration, decorator: ts.Decorator, pkgParams: object): void { const expr: ts.Expression = decorator.expression; if (ts.isCallExpression(expr)) { const args: ts.NodeArray = expr.arguments; @@ -276,9 +359,9 @@ class ParseIntent { 'decoratorType': COMPONENT_USER_INTENTS_DECORATOR_ENTRY }); this.analyzeDecoratorArgs(args, intentObj, intentEntryInfoChecker); - const properties: Record = this.parseClassNode(node); + const properties: Record = this.parseClassNode(node, intentObj.intentName); this.schemaValidateSync(properties, intentObj.parameters); - this.analyzeBaseClass(node, pkgParams, intentObj); + this.analyzeBaseClass(node, pkgParams, intentObj, COMPONENT_USER_INTENTS_DECORATOR_ENTRY); this.createObfuscation(node); this.processExecuteModeParam(intentObj); if (this.isUpdateCompile) { @@ -290,11 +373,85 @@ class ParseIntent { } } - transformPagePath(intentObj: object, pkgParams: object): void { + validateAbility(abilityName: string, pkgParams: object, isUIAbility: boolean): void { + if (this.moduleJsonInfo.size === 0 && pkgParams.pkgPath) { + this.readModuleJsonInfo(pkgParams); + } + const abilities: object[] = this.moduleJsonInfo.get('abilities'); + const extensionAbilities: object[] = this.moduleJsonInfo.get('extensionAbilities'); + const hasUIAbility: object = abilities.find(abilityInfo => { + return abilityInfo.name === abilityName; + }); + const hasExtensionAbility: object = extensionAbilities.find(abilityInfo => { + return abilityInfo.name === abilityName; + }); + if (!hasUIAbility && isUIAbility) { + throw Error(`${INCORRECT_PARAM_ERROR}, @InsightIntentPage param uiAbility must match the name of a UIAbility defined`); + } + if (!(hasUIAbility && hasExtensionAbility) && !isUIAbility) { + throw Error(`${INCORRECT_PARAM_ERROR}, @InsightIntentEntry param abilityName must match the name of an Ability defined`); + } + } + + 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'; + }); + let verifyFormNameFlag: boolean = false; + if (bindFormInfo) { + intentObj.abilityName = bindFormInfo.name; + bindFormInfo.metadata?.forEach(metaData => { + const formConfigName: string = metaData.resource.split(':').pop() + '.json'; + const formConfigPath: string = path.join(projectConfig.aceProfilePath, formConfigName); + if (fs.existsSync(formConfigPath)) { + const formData: string = fs.readFileSync(formConfigPath, 'utf8'); + const formDataObj: object = JSON.parse(formData); + const formConfig: object = formDataObj.forms?.find(data => { + return data.name === intentObj.formName; + }); + if (formConfig) { + verifyFormNameFlag = true; + } + } + }); + } + if (!verifyFormNameFlag) { + throw Error(`${INCORRECT_PARAM_ERROR}, @InsightIntentForm param formName must match the card name`); + } + 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)) { + throw Error(`${DECORATOR_ILLEGAL_USE.toString()}, @InsightIntentForm must be decorated on a formExtensionAbility`); + } + } + + readModuleJsonInfo(pkgParams: object) { + const moduleJsonPath: string = path.join(pkgParams.pkgPath, 'src/main', 'module.json5'); + if (fs.existsSync(moduleJsonPath)) { + 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); + } + } else { + throw Error(`${INTERNAL_ERROR.toString()}, module.json5 not found, expect moduleJsonPath: ${moduleJsonPath}`); + } + } + + 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)) { - throw Error(`${INVALID_PAGEPATH_ERROR.toString()}, ${normalPagePath} does not exist, invalidDecoratorPath: ${this.currentFilePath}`); + throw Error(`${INCORRECT_PARAM_ERROR.toString()}, @InsightIntentPage pagePath incorrect,` + + `${normalPagePath} does not exist, invalidDecoratorPath: ${this.currentFilePath}`); } else { const Logger: IntentLogger = IntentLogger.getInstance(); intentObj.pagePath = '@normalized:' + getNormalizedOhmUrlByFilepath(normalPagePath, projectConfig, Logger, pkgParams, null); @@ -302,21 +459,57 @@ class ParseIntent { } } - analyzeBaseClass(node: ts.ClassDeclaration, pkgParams: object, intentObj: object): void { + 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); + }); + } + + analyzeBaseClass(node: ts.ClassDeclaration, pkgParams: object, intentObj: object, decoratorFlag: string): void { const interfaces: ts.ExpressionWithTypeArguments[] = []; - node.heritageClauses?.forEach(clause => { - if (clause.token === ts.SyntaxKind.ImplementsKeyword || clause.token === ts.SyntaxKind.ExtendsKeyword) { - interfaces.push(...clause.types); + 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 { + throw Error(`${INVALID_BASE_CLASS_ERROR.toString()},` + + `decorated with @InsightIntentEntity must be ultimately inherited to insightIntent.IntentEntity, invalidDecoratorPath: ${this.currentFilePath}`); } - }); + } + } + + processEntryBaseClass(interfaces: ts.ExpressionWithTypeArguments[], intentObj: object, pkgParams: object): void { if (interfaces.length > 0) { const parentNode: ts.ExpressionWithTypeArguments = interfaces[0]; - if (parentNode) { - this.analyzeClassHeritage(parentNode, node, pkgParams, intentObj); + 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')) { + throw Error(`${INVALID_BASE_CLASS_ERROR.toString()},` + + `decorated with @InsightIntentEntry must be inherited to InsightIntentEntryExecutor, invalidDecoratorPath: ${this.currentFilePath}`); } + 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 { - throw Error(`${INVALID_BASE_CLASS_ERROR.toString()}, - The custom intent must be ultimately inherited to InsightIntentEntryExecutor, invalidDecoratorPath: ${this.currentFilePath}`); + throw Error(`${INVALID_BASE_CLASS_ERROR.toString()},` + + `decorated with @InsightIntentEntry must inherited to InsightIntentEntryExecutor, invalidDecoratorPath: ${this.currentFilePath}`); } } @@ -330,7 +523,7 @@ class ParseIntent { if (!ts.isMethodDeclaration(member) || !this.hasModifier(member, ts.SyntaxKind.StaticKeyword)) { continue; } - const decorator: boolean = member.modifiers?.find(m => { + const decorator: ts.ModifierLike = member.modifiers?.find(m => { if (!ts.isDecorator(m)) { return false; } @@ -341,7 +534,7 @@ class ParseIntent { return decoratorName === decoratorType; }); if (decorator && ts.isCallExpression(decorator.expression)) { - const parameters: Record = {}; + let parameters: Record = {}; member.parameters.forEach(param => { const paramName: string = param.name.getText(); parameters[paramName] = this.checker.typeToString( @@ -373,26 +566,20 @@ class ParseIntent { parentClassName = parentSymbol.getName(); } }); + intentObj.parentClassName = parentClassName; const parentFilePath: string = parentSymbol.getDeclarations()?.[0].getSourceFile().fileName; const logger: IntentLogger = IntentLogger.getInstance(); - const parentRecordName: string = getNormalizedOhmUrlByFilepath(parentFilePath, projectConfig, logger, pkgParams, null); 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 isGlobalPath: boolean = globalModulePaths?.some(globalPath => { - const normalizedParent: string = path.normalize(parentFilePath).replace(/\\/g, '/'); - const normalizedGlobalPath: string = path.normalize(globalPath).replace(/\\/g, '/'); - return normalizedParent.startsWith(normalizedGlobalPath); - }); - const recordPath: string = isGlobalPath ? `sdk` : `@normalized:${parentRecordName}`; - if (isGlobalPath) { - if (parentClassName !== 'InsightIntentEntryExecutor') { - throw Error(`${INVALID_BASE_CLASS_ERROR.toString()}, - The custom intent must be ultimately inherited to InsightIntentEntryExecutor, invalidDecoratorPath: ${this.currentFilePath}`); + const isGlobalPathFlag: boolean = this.isGlobalPath(parentFilePath); + if (isGlobalPathFlag) { + if (parentClassName !== 'IntentEntity') { + throw Error(`${INVALID_BASE_CLASS_ERROR.toString()},` + + `decorated with @InsightIntentEntry must be ultimately inherited to insightIntent.IntentEntity, invalidDecoratorPath: ${this.currentFilePath}`); } } this.heritageClassSet.add(baseClassName + '_' + `@normalized:${baseRecordName}`); - this.collectClassInheritanceInfo(parentNode, intentObj, parentClassName, recordPath); } collectClassInheritanceInfo( @@ -452,39 +639,89 @@ class ParseIntent { ) !== 0; } - parseClassNode(node: ts.ClassDeclaration): Record { + parseClassNode(node: ts.ClassDeclaration, intentName: string): Record { const mergedObject: Record = {}; - node.members - .filter((member): member is ts.PropertyDeclaration => { - return ts.isPropertyDeclaration(member) && - member.parent === node; - }) - .forEach(propertyNode => { - const propSymbol = this.checker.getSymbolAtLocation(propertyNode.name); - if (!propSymbol) { - return; - } - const objItem: Record = this.processProperty(propSymbol); - Object.assign(mergedObject, objItem); - }); + const type: ts.Type = this.checker.getTypeAtLocation(node); + const propertiesOfType: ts.Symbol[] = this.checker.getPropertiesOfType(type); + propertiesOfType.forEach((prop) => { + const objItem: Record = this.processProperty(prop, intentName); + Object.assign(mergedObject, objItem); + }); return mergedObject; } - processProperty(prop: ts.Symbol): Record { + 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; + } + + processProperty(prop: ts.Symbol, intentName: string): Record { const propType: ts.Type = this.checker.getTypeOfSymbol(prop); const {category} = this.getTypeCategory(propType); const obj: Record = {}; const propName: string = prop.getName(); if (category === 'object') { - obj[propName] = 'object'; + if (this.isEntity(propType, intentName)) { + obj[propName] = 'object'; + } } else if (category === 'array') { - obj[propName] = 'array'; + if (this.isEntity(propType, intentName)) { + obj[propName] = 'array'; + } } else { obj[propName] = this.checker.typeToString(propType); } return obj; } + isEntity(propType: ts.Type, intentName: string): boolean { + let propDeclaration: ts.Declaration; + let elementType: ts.Type | undefined; + const typeSymbol: ts.Symbol = propType.getSymbol(); + const propertyClassName: string = typeSymbol.getName(); + 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') { + 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; + } + }); + } + getTypeCategory(type: ts.Type): { category: 'array' | 'object'; } { const flags: ts.TypeFlags = type.getFlags(); @@ -494,15 +731,7 @@ class ParseIntent { !!(flags & ts.TypeFlags.Null) || !!(flags & ts.TypeFlags.Undefined); - let isArray: boolean; - const symbol: ts.Symbol | undefined = type.getSymbol(); - 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'); - } + const isArray: boolean = this.isArrayType(type); const isObject: boolean = !isPrimitive && !isArray && !!(flags & ts.TypeFlags.Object); @@ -515,6 +744,20 @@ class ParseIntent { return {category}; } + 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; + } + removeDecorator(node: ts.ClassDeclaration, decoratorNames: string[]): ts.ClassDeclaration { const filteredModifiers: ts.ModifierLike[] = node.modifiers.filter(decorator => { if (!ts.isDecorator(decorator)) { @@ -708,10 +951,10 @@ class ParseIntent { } createObfuscation(classNode: ts.Node): void { - ProjectCollections.projectWhiteListManager?.fileWhiteListInfo.fileKeepInfo.arkUIKeepInfo.globalNames.add(classNode.symbol.name); + 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.symbol.name); + 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) || @@ -854,7 +1097,7 @@ class ParseIntent { intentObj.executeMode[index] = 'uiextension'; } if (item === '3') { - intentObj.executeMode[index] = 'serviceextension'; + intentObj.executeMode[index] = 'seviceextension'; } }); } @@ -873,6 +1116,7 @@ class ParseIntent { intentObj.llmDescription = schemaObj.llmDescription; intentObj.keywords = schemaObj.keywords; intentObj.intentName = schemaObj.intentName; + intentObj.result = schemaObj.result; } } } @@ -884,8 +1128,9 @@ class ParseIntent { const definitionFilePath: string = element.ClassInheritanceInfo.definitionFilePath; const verifiedString: string = parentClassName + '_' + definitionFilePath; if (!this.heritageClassSet.has(verifiedString)) { - throw Error(`${INVALID_BASE_CLASS_ERROR.toString()} - , Subclass must inherit from a class decorated with @InsightIntentEntry, invalidDecoratorPath: ${this.currentFilePath}\``); + throw Error(`${INVALID_BASE_CLASS_ERROR.toString()}` + + `, Classes decorated with @InsightIntentEntity must extend a base class that is also decorated with @InsightIntentEntity` + + `, invalidDecoratorPath: ${this.currentFilePath}`); } } }); @@ -984,12 +1229,68 @@ class ParseIntent { } } + matchEntities(): void { + const intentIndex = new Map(); + this.intentData.forEach(intent => { + intentIndex.set(intent.intentName, intent); + }); + + for (const [intentName, entityIds] of this.entityOwnerMap.entries()) { + const targetIntent: object = intentIndex.get(intentName); + if (!targetIntent) { + continue; + } + + let matchedEntities = null; + entityIds.forEach(id => { + if (this.entityMap.has(id)) { + if (matchedEntities === null) { + matchedEntities = []; + } + matchedEntities.push(this.entityMap.get(id)); + } + }); + + if (matchedEntities) { + targetIntent.entities = matchedEntities; + } + } + } + writeUserIntentJsonFile(): void { + const writeJsonData: object = this.processIntentData(); + const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file + const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file + try { + if (this.intentData.length > 0) { + fs.writeFileSync(cacheSourceMapPath, JSON.stringify(writeJsonData, null, 2), 'utf-8'); + fs.writeFileSync(cachePath, JSON.stringify({'extractInsightIntents': this.intentData}, 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 = this.intentData.length > 0 ? true : undefined; + } + const updatedJson: string = JSON.stringify(jsonData, null, 2); + fs.writeFileSync(fullPath, updatedJson, 'utf8'); + } + } catch (e) { + throw Error(`${INTERNAL_ERROR}, writeFile failed: ${e}`); + } + } + + processIntentData(): object { + this.matchEntities(); if (!projectConfig.aceProfilePath) { throw Error(`${INTERNAL_ERROR.toString()}, aceProfilePath not found, invalidDecoratorPath: ${this.currentFilePath}`); } - const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); - const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); + const cacheSourceMapPath: string = path.join(projectConfig.aceProfilePath, 'insight_intent.json'); // The user's intents configuration file + const cachePath: string = path.join(projectConfig.cachePath, 'insight_compile_cache.json'); // Compiled cache file if (!fs.existsSync(projectConfig.aceProfilePath)) { fs.mkdirSync(projectConfig.aceProfilePath, {recursive: true}); } @@ -1018,23 +1319,7 @@ class ParseIntent { }); } this.validateIntentIntentName(writeJsonData); - if (this.intentData.length > 0) { - fs.writeFileSync(cacheSourceMapPath, JSON.stringify(writeJsonData, null, 2), 'utf-8'); - fs.writeFileSync(cachePath, JSON.stringify({'extractInsightIntents': this.intentData}, 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 = this.intentData.length > 0 ? true : undefined; - } - const updatedJson: string = JSON.stringify(jsonData, null, 2); - fs.writeFileSync(fullPath, updatedJson, 'utf8'); - } + return writeJsonData; } validateIntentIntentName(writeJsonData: object): void { @@ -1045,7 +1330,7 @@ class ParseIntent { writeJsonData.extractInsightIntents.forEach(item => { if (duplicates.has(item.intentName)) { throw Error(`${DECORATOR_DUPLICATE_INTENTNAME.toString()}, value : ${item.intentName}`); - } else { + } else if (item.intentName !== undefined) { duplicates.add(item.intentName); } }); @@ -1056,10 +1341,14 @@ class ParseIntent { this.checker = null; this.currentFilePath = ''; this.heritageClassSet = new Set(); + this.heritageClassSet.add('IntentEntity_sdk'); this.heritageClassSet.add('InsightIntentEntryExecutor_sdk'); this.isInitCache = false; this.isUpdateCompile = false; this.updatePageIntentObj = new Map(); + this.entityMap = new Map(); + this.entityOwnerMap = new Map(); + this.moduleJsonInfo = new Map(); } } diff --git a/compiler/test/transform_ut/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.js.sample b/compiler/test/transform_ut/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.js.sample new file mode 100644 index 000000000..1896d0476 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.js.sample @@ -0,0 +1,26 @@ +/* + * 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. + */ + +let __generate__Id = 0; +function generateId() { + return "@InsightIntentEntity_" + ++__generate__Id; +} +export class ArtistClassDef { + constructor() { + this.entityId = "id"; + this.name = ''; + } +} +//# sourceMappingURL=@InsightIntentEntity.js.map \ No newline at end of file diff --git a/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.ets b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.ets new file mode 100644 index 000000000..b6184f987 --- /dev/null +++ b/compiler/test/transform_ut/application/entry/src/main/ets/pages/utForInsightDecorator/@InsightIntentEntity/@InsightIntentEntity.ets @@ -0,0 +1,44 @@ +/* + * 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. + */ + +@InsightIntentEntity({ + entityCategory: 'artist entity category', + parameters: { + "$id": "/schemas/ArtistClassDef", + "type": "object", + "description": "Information about the artist", + "properties": { + "country": { + "type": "string", + "description": "The artist's country of origin", + "default": "zh" + }, + "city": { + "type": "string", + "description": "The artist's city of origin" + }, + "name": { + "type": "string", + "description": "The name of the artist", + "minLength": 1 + } + }, + "required": ["name"] + } +}) +export class ArtistClassDef implements insightIntent.IntentEntity { + entityId: string = "id"; + name: string = '' +} \ No newline at end of file -- Gitee