From a68d2beb7a3f0492a23d68ffc65e96bf8a574531 Mon Sep 17 00:00:00 2001 From: Yenan Date: Tue, 24 Jun 2025 10:24:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=A8=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E8=B0=83=E7=94=A8extend=E5=87=BD=E6=95=B0=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E4=B8=8D=E6=8B=A6=E6=88=AA=E8=BF=90=E8=A1=8C=E9=97=AA=E9=80=80?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yenan --- compiler/src/process_component_build.ts | 68 ++++++++++++++++++++++++- compiler/src/process_ui_syntax.ts | 5 ++ compiler/src/validate_ui_syntax.ts | 30 ++++++++++- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index 1a9cabc42..1fd878ec8 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -167,7 +167,8 @@ import { componentCollection, builderParamObjectCollection, checkAllNode, - enumCollection + enumCollection, + extendFromSourceFile } from './validate_ui_syntax'; import { processCustomComponent, @@ -193,7 +194,8 @@ import { contextGlobal, validatorCard, builderTypeParameter, - resourceFileName + resourceFileName, + currentSourceFile } from './process_ui_syntax'; import { regularCollection, getSymbolIfAliased } from './validate_ui_syntax'; import { contextStackPushOrPop } from './process_component_class'; @@ -203,6 +205,7 @@ import { concatenateEtsOptions, getExternalComponentPaths } from './external_component_map'; +import { extendCollection } from './ets_checker'; export function processComponentBuild(node: ts.MethodDeclaration, log: LogInfo[]): ts.MethodDeclaration { @@ -852,10 +855,55 @@ function processInnerComponent(node: ts.ExpressionStatement, innerCompStatements } } +/** + * Check the inner component's called attributes, and check if the attribute is defined + * by '@Extend' decorated function, then check if the function exists on current sourceFile + * @param {ts.CallExpression} node + * @param {Set} currentSourceExtend + * @param {ts.SourceFile} currentSource + * @param {LogInfo[]} log + * @param {typeName} typeName + * @return {*} {void} + */ +function validateAttrsNames(node: ts.CallExpression, currentSourceExtend: Set, + currentSource: ts.SourceFile, log: LogInfo[], typeName: string): void { + if (!ts.isPropertyAccessExpression(node.expression)) { + return; + } + const newNode: ts.PropertyAccessExpression = node.expression; + const currentAttr: string = newNode.name.getText(); + if (extendCollection.has(currentAttr) && !currentSourceExtend.has(currentAttr)) { + validateInvalidExtend(newNode, currentSource, log, typeName); + } + if (ts.isCallExpression(newNode.expression)) { + validateAttrsNames(newNode.expression, currentSourceExtend, + currentSource, log, typeName); + } +} + +function validateInvalidExtend(node: ts.PropertyAccessExpression, currentSource: ts.SourceFile, + log: LogInfo[], typeName:string): void { + log.push({ + type: LogType.ERROR, + message: `Property '${node.name.getText()}' does not exist on type '${typeName}'.`, + pos: node.getEnd(), + code: '10905361', + fileName: currentSource.fileName + }); +} + function processNormalComponent(node: ts.ExpressionStatement, nameResult: NameResult, innerCompStatements: ts.Statement[], log: LogInfo[], parent: string = undefined, isBuilder: boolean = false, isGlobalBuilder: boolean = false, isTransition: boolean = false, idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + // check the component's attributes first + if (ts.isCallExpression(node.expression)) { + const currentSource: ts.SourceFile = currentSourceFile; + const currentSourceExtend: Set = extendFromSourceFile.has(currentSource.fileName) ? + new Set(Array.from(extendFromSourceFile.get(currentSource.fileName).keys())) : new Set(); + const typeName: string = getName(node) + 'Attribute'; + validateAttrsNames(node.expression, currentSourceExtend, currentSource, log, typeName); + } const newStatements: ts.Statement[] = []; if (addElmtIdNode()) { newStatements.push(createCollectElmtIdNode()); @@ -1178,6 +1226,14 @@ function createInitRenderStatement(node: ts.Statement, function processItemComponent(node: ts.ExpressionStatement, nameResult: NameResult, innerCompStatements: ts.Statement[], log: LogInfo[], parent: string = undefined, isGlobalBuilder: boolean = false, idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + // check the component's attributes first + if (ts.isCallExpression(node.expression)) { + const currentSource: ts.SourceFile = currentSourceFile; + const currentSourceExtend: Set = extendFromSourceFile.has(currentSource.fileName) ? + new Set(Array.from(extendFromSourceFile.get(currentSource.fileName).keys())) : new Set(); + const typeName: string = getName(node) + 'Attribute'; + validateAttrsNames(node.expression, currentSourceExtend, currentSource, log, typeName); + } const itemRenderInnerStatements: ts.Statement[] = []; const immutableStatements: ts.Statement[] = []; const deepItemRenderInnerStatements: ts.Statement[] = []; @@ -1477,6 +1533,14 @@ function processTabAndNav(node: ts.ExpressionStatement, innerCompStatements: ts. nameResult: NameResult, log: LogInfo[], parent: string = undefined, isGlobalBuilder: boolean = false, idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null, isInRepeatTemplate: boolean = false): void { + // check the component's attributes first + if (ts.isCallExpression(node.expression)) { + const currentSource: ts.SourceFile = currentSourceFile; + const currentSourceExtend: Set = extendFromSourceFile.has(currentSource.fileName) ? + new Set(Array.from(extendFromSourceFile.get(currentSource.fileName).keys())) : new Set(); + const typeName: string = getName(node) + 'Attribute'; + validateAttrsNames(node.expression, currentSourceExtend, currentSource, log, typeName); + } const name: string = nameResult.name; const tabContentComp: ts.EtsComponentExpression = getEtsComponentExpression(node); const tabContentBody: ts.Block = tabContentComp.body; diff --git a/compiler/src/process_ui_syntax.ts b/compiler/src/process_ui_syntax.ts index 16202d2da..ac5511f8b 100644 --- a/compiler/src/process_ui_syntax.ts +++ b/compiler/src/process_ui_syntax.ts @@ -178,6 +178,9 @@ export let resourceFileName: string = ''; export const builderTypeParameter: { params: string[] } = { params: [] }; import parseIntent from './userIntents_parser/parseUserIntents'; +// declare the current sourcefile which is being transformed +export let currentSourceFile: ts.SourceFile; + export function processUISyntax(program: ts.Program, ut = false, parentEvent?: CompileEvent, filePath: string = '', share: object = null, metaInfo: Object = {}): Function { let entryNodeKey: ts.Expression; @@ -193,6 +196,7 @@ export function processUISyntax(program: ts.Program, ut = false, eventProcessUISyntax = createAndStartEvent(parentEvent, 'processUISyntax'); pagesDir = path.resolve(path.dirname(node.fileName)); resourceFileName = path.resolve(node.fileName); + currentSourceFile = node; pageFile = path.resolve(filePath !== '' ? filePath : node.fileName); if (process.env.compiler === BUILD_ON || process.env.compileTool === 'rollup') { const fileHash = share?.getHashByFilePath ? share?.getHashByFilePath(pageFile) : ''; @@ -2020,6 +2024,7 @@ export function validatorCard(log: LogInfo[], type: number, pos: number, export function resetProcessUiSyntax(): void { transformLog = new createAstNodeUtils.FileLog(); contextGlobal = undefined; + currentSourceFile = undefined; } function createSharedStorageWithRoute(context: ts.TransformationContext, name: string, cardRelativePath: string, diff --git a/compiler/src/validate_ui_syntax.ts b/compiler/src/validate_ui_syntax.ts index 4543facef..7127c8736 100644 --- a/compiler/src/validate_ui_syntax.ts +++ b/compiler/src/validate_ui_syntax.ts @@ -179,6 +179,9 @@ export const provideInitialization: Map> = new Map(); export const privateCollection: Map> = new Map(); export const regularStaticCollection: Map> = new Map(); +// Create a map, key: ts.sourceFile, value is a map which's key and value are both string +export const extendFromSourceFile: Map> = new Map(); + export const isStaticViewCollection: Map = new Map(); export const useOSFiles: Set = new Set(); @@ -487,7 +490,7 @@ function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponent [isObservedV1Class, isObservedClass, isSendableClass] = parseClassDecorator(node, sourceFileNode, log); } if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) { - methodDecoratorCollect(node); + methodDecoratorCollect(node, sourceFileNode); if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) { // ark compiler's feature checkConcurrentDecorator(node, log, sourceFileNode); @@ -643,7 +646,8 @@ function findDuplicateDecoratorMethod(mapKeys: string[], mapValues: number[]): s return output; } -export function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionDeclaration): void { +export function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionDeclaration, + sourceFileNode: ts.SourceFile): void { const extendResult: ExtendResult = { decoratorName: '', componentName: '' }; const builderCondition: builderConditionType = { isBuilder: false, @@ -663,6 +667,8 @@ export function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionD } else if (ts.isFunctionDeclaration(node) && isExtendFunction(node, extendResult)) { if (extendResult.decoratorName === CHECK_COMPONENT_EXTEND_DECORATOR) { collectExtend(EXTEND_ATTRIBUTE, extendResult.componentName, node.name.getText()); + collectExtendBySourceFile(sourceFileNode, extendFromSourceFile, + node.name.getText(), extendResult.componentName); } if (extendResult.decoratorName === CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { collectExtend(storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute, @@ -673,6 +679,25 @@ export function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionD } } + +/** + * Collect extend functions in each sourceFile + * Each function stores the valid component name + * @param {ts.SourceFile} sourceFileNode + * @param {Map>} extendFromSourceFile + * @param {string} functionName extend function name + * @param {string} compName + */ +function collectExtendBySourceFile(sourceFileNode: ts.SourceFile, + extendFromSourceFile: Map>, functionName: string, + compName: string): void { + if (extendFromSourceFile.has(sourceFileNode.fileName)) { + extendFromSourceFile.get(sourceFileNode.fileName).set(functionName, compName); + } else { + extendFromSourceFile.set(sourceFileNode.fileName, new Map([[functionName, compName]])); + } +} + /** * Checker whether the function's name is the same with common attributes * or the component's private attributes @@ -1888,6 +1913,7 @@ export function resetComponentCollection(): void { provideInitialization.clear(); privateCollection.clear(); regularStaticCollection.clear(); + extendFromSourceFile.clear(); } function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void { -- Gitee