diff --git a/compiler/src/component_map.ts b/compiler/src/component_map.ts index a88790e07f396895c7d61b264623e48361b1dca4..19083b5038139c9f7c63fae8ee12eb6a27412eec 100644 --- a/compiler/src/component_map.ts +++ b/compiler/src/component_map.ts @@ -91,6 +91,9 @@ export const JS_BIND_COMPONENTS: Set = new Set([ export const NEEDPOP_COMPONENT: Set = new Set(['Blank', 'Search']); +export const CUSTOM_BUILDER_PROPERTIES: Set = new Set(['bindPopup', 'bindMenu', 'bindContextMenu', 'title', + 'menus', 'toolBar', 'tabBar']); + (function initComponent() { Object.keys(COMPONENT_MAP).forEach((componentName) => { INNER_COMPONENT_NAMES.add(componentName); diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index ea2b141bc62a671b7b97d64755b37a988f80b578..c66da1729311e8e337c1a34b8fe566497a15dc52 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -52,7 +52,10 @@ import { $$_VALUE, $$_CHANGE_EVENT, $$_THIS, - $$_NEW_VALUE + $$_NEW_VALUE, + BUILDER_ATTR_NAME, + BUILDER_ATTR_BIND, + CUSTOM_DIALOG_CONTROLLER_BUILDER } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -66,7 +69,8 @@ import { NEEDPOP_COMPONENT, INNER_STYLE_FUNCTION, GLOBAL_STYLE_FUNCTION, - COMMON_ATTRS + COMMON_ATTRS, + CUSTOM_BUILDER_PROPERTIES } from './component_map'; import { componentCollection } from './validate_ui_syntax'; import { processCustomComponent } from './process_custom_component'; @@ -516,6 +520,16 @@ export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: const statements: ts.Statement[] = []; const lastStatement: AnimationInfo = { statement: null, kind: false }; while (temp && ts.isCallExpression(temp) && temp.expression) { + if (temp.expression && (validatePropertyAccessExpressionWithCustomBuilder(temp.expression) || + validateIdentifierWithCustomBuilder(temp.expression))) { + let propertyName: string = ''; + if (ts.isIdentifier(temp.expression)) { + propertyName = temp.expression.escapedText.toString(); + } else if (ts.isPropertyAccessExpression(temp.expression)) { + propertyName = temp.expression.name.escapedText.toString(); + } + temp = propertyName === BIND_POPUP ? processBindPopupBuilder(temp) : processCustomBuilderProperty(temp); + } if (ts.isPropertyAccessExpression(temp.expression) && temp.expression.name && ts.isIdentifier(temp.expression.name)) { addComponentAttr(temp, temp.expression.name, lastStatement, statements, identifierNode, log, @@ -538,6 +552,122 @@ export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: } } +function processCustomBuilderProperty(node: ts.CallExpression): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.Expression | ts.Identifier, index: number) => { + if (index === 0 && (ts.isPropertyAccessExpression(argument) || ts.isCallExpression(argument) || + ts.isIdentifier(argument))) { + newArguments.push(parseBuilderNode(argument)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function parseBuilderNode(node: ts.Node): ts.ObjectLiteralExpression { + if (isPropertyAccessExpressionNode(node)) { + return processPropertyBuilder(node as ts.PropertyAccessExpression); + } else if (ts.isIdentifier(node) && CUSTOM_BUILDER_METHOD.has(node.escapedText.toString())) { + return processIdentifierBuilder(node); + } else if (ts.isCallExpression(node)) { + return getParsedBuilderAttrArgumentWithParams(node); + } +} + +function isPropertyAccessExpressionNode(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.expression && + node.expression.kind === ts.SyntaxKind.ThisKeyword && node.name && ts.isIdentifier(node.name) && + CUSTOM_BUILDER_METHOD.has(node.name.escapedText.toString()); +} + +function processBindPopupBuilder(node: ts.CallExpression): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.ObjectLiteralExpression, index: number) => { + if (index === 1) { + // @ts-ignore + newArguments.push(processBindPopupBuilderProperty(argument)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function processBindPopupBuilderProperty(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { + const newProperties: ts.PropertyAssignment[] = []; + node.properties.forEach((property: ts.PropertyAssignment, index: number) => { + if (index === 0) { + if (property.name && ts.isIdentifier(property.name) && + property.name.escapedText.toString() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { + newProperties.push(ts.factory.updatePropertyAssignment(property, property.name, + parseBuilderNode(property.initializer))); + } else { + newProperties.push(property); + } + } else { + newProperties.push(property); + } + }); + return ts.factory.updateObjectLiteralExpression(node, newProperties); +} + +function processPropertyBuilder(node: ts.PropertyAccessExpression): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ) + ) + ]); +} + +function processIdentifierBuilder(node: ts.Identifier): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + node + ) + ]); +} + +function getParsedBuilderAttrArgumentWithParams(node: ts.CallExpression): + ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(node)], + true + ) + ) + ) + ]); +} + +function validatePropertyAccessExpressionWithCustomBuilder(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.name && + ts.isIdentifier(node.name) && CUSTOM_BUILDER_PROPERTIES.has(node.name.escapedText.toString()); +} + +function validateIdentifierWithCustomBuilder(node: ts.Node): boolean { + return ts.isIdentifier(node) && CUSTOM_BUILDER_PROPERTIES.has(node.escapedText.toString()); +} + function createArrowFunctionFor$$($$varExp: ts.Expression): ts.ArrowFunction { return ts.factory.createArrowFunction( undefined, undefined, @@ -561,8 +691,8 @@ function createArrowFunctionFor$$($$varExp: ts.Expression): ts.ArrowFunction { function updateArgumentFor$$(argument: any): ts.Expression { if (ts.isElementAccessExpression(argument)) { - return ts.factory.updateElementAccessExpression - (argument, updateArgumentFor$$(argument.expression), argument.argumentExpression); + return ts.factory.updateElementAccessExpression( + argument, updateArgumentFor$$(argument.expression), argument.argumentExpression); } else if (ts.isIdentifier(argument)) { props.push(argument.getText()); if (argument.getText() === $$_THIS) { @@ -571,8 +701,8 @@ function updateArgumentFor$$(argument: any): ts.Expression { return ts.factory.createIdentifier(argument.getText().replace(/\$\$/, '')); } } else if (ts.isPropertyAccessExpression(argument)) { - return ts.factory.updatePropertyAccessExpression - (argument, updateArgumentFor$$(argument.expression), argument.name); + return ts.factory.updatePropertyAccessExpression( + argument, updateArgumentFor$$(argument.expression), argument.name); } } @@ -706,8 +836,8 @@ function traverseStateStylesAttr(temp: any, statements: ts.Statement[], } else if (ts.isObjectLiteralExpression(item.initializer) && item.initializer.properties.length === 1 && ts.isPropertyAssignment(item.initializer.properties[0])) { - bindComponentAttr(ts.factory.createExpressionStatement - (item.initializer.properties[0].initializer), identifierNode, statements, log, false, true); + bindComponentAttr(ts.factory.createExpressionStatement( + item.initializer.properties[0].initializer), identifierNode, statements, log, false, true); } else { validateStateStyleSyntax(temp, log); } diff --git a/compiler/src/process_component_class.ts b/compiler/src/process_component_class.ts index ee4a482747c5ff1f0b326a82965ea01866cf7e80..1732d6c3ac1d63758de0d783481ce8c4c3a506f7 100644 --- a/compiler/src/process_component_class.ts +++ b/compiler/src/process_component_class.ts @@ -47,8 +47,6 @@ import { COMPONENT_TRANSITION_FUNCTION, COMPONENT_CREATE_FUNCTION, GEOMETRY_VIEW, - BUILDER_ATTR_NAME, - BUILDER_ATTR_BIND, COMPONENT_STYLES_DECORATOR, STYLES, INTERFACE_NAME_SUFFIX, @@ -290,8 +288,7 @@ function processBuildMember(node: ts.MethodDeclaration, context: ts.Transformati }); } const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); - const firstParseBuildNode = ts.visitNode(buildNode, visitBuild); - return ts.visitNode(firstParseBuildNode, visitBuildSecond); + return ts.visitNode(buildNode, visitBuild); function visitBuild(node: ts.Node): ts.Node { if (isGeometryView(node)) { node = processGeometryView(node as ts.ExpressionStatement, log); @@ -309,90 +306,6 @@ function processBuildMember(node: ts.MethodDeclaration, context: ts.Transformati } return ts.visitEachChild(node, visitBuild, context); } - function visitBuildSecond(node: ts.Node): ts.Node { - if (isCustomComponentNode(node) || isCustomBuilderNode(node)) { - return node; - } - if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)) && - validateBuilderFunctionNode(node)) { - return getParsedBuilderAttrArgument(node); - } - return ts.visitEachChild(node, visitBuildSecond, context); - } -} - -function validateBuilderFunctionNode(node: ts.PropertyAccessExpression | ts.Identifier): boolean { - if ((ts.isPropertyAccessExpression(node) && node.expression && node.name && - node.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(node.name) && - CUSTOM_BUILDER_METHOD.has(node.name.escapedText.toString()) || - ts.isIdentifier(node) && CUSTOM_BUILDER_METHOD.has(node.escapedText.toString())) && - !(ts.isPropertyAccessExpression(node) && validateBuilderParam(node) || - ts.isIdentifier(node) && node.parent && ts.isPropertyAccessExpression(node.parent) && - validateBuilderParam(node.parent))) { - return true; - } else { - return false; - } -} - -function validateBuilderParam(node: ts.PropertyAccessExpression): boolean { - if (node.parent && ts.isCallExpression(node.parent) && node.parent.expression === node) { - return true; - } else { - return false; - } -} - -function getParsedBuilderAttrArgument(node: ts.PropertyAccessExpression | ts.Identifier): - ts.ObjectLiteralExpression { - let newObjectNode: ts.ObjectLiteralExpression = null; - if (ts.isPropertyAccessExpression(node)) { - newObjectNode = ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(BUILDER_ATTR_NAME), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - node, - ts.factory.createIdentifier(BUILDER_ATTR_BIND) - ), - undefined, - [ts.factory.createThis()] - ) - ) - ]); - } else if (ts.isIdentifier(node)) { - newObjectNode = ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(BUILDER_ATTR_NAME), - node - ) - ]); - } - return newObjectNode; -} - -function isCustomComponentNode(node:ts.NewExpression | ts.ExpressionStatement): boolean { - if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.escapedText - && componentCollection.customComponents.has(node.expression.escapedText.toString()) || - // @ts-ignore - ts.isExpressionStatement(node) && node.expression && node.expression.expression && - // @ts-ignore - node.expression.expression.expression && node.expression.expression.expression.escapedText && - // @ts-ignore - node.expression.expression.expression.escapedText.toString().startsWith( - CUSTOM_COMPONENT_EARLIER_CREATE_CHILD)) { - return true; - } else { - return false; - } -} - -function isCustomBuilderNode(node: ts.ExpressionStatement): boolean { - return ts.isExpressionStatement(node) && node.expression && - // @ts-ignore - node.expression.expression && node.expression.expression.escapedText && - // @ts-ignore - CUSTOM_BUILDER_METHOD.has(node.expression.expression.escapedText.toString()); } function isGeometryView(node: ts.Node): boolean { diff --git a/compiler/src/validate_ui_syntax.ts b/compiler/src/validate_ui_syntax.ts index 148178cb25abb75e3ef2a6f7d820688fe23850a3..04ee994fe395d1b1d57b3e575d167bf39c792bf1 100644 --- a/compiler/src/validate_ui_syntax.ts +++ b/compiler/src/validate_ui_syntax.ts @@ -41,10 +41,10 @@ import { COMPONENT_CONSTRUCTOR_ID, COMPONENT_CONSTRUCTOR_PARENT, COMPONENT_CONSTRUCTOR_PARAMS, - COMPONENT_EXTEND_DECORATOR, COMPONENT_OBSERVED_DECORATOR, STYLES, - VALIDATE_MODULE + VALIDATE_MODULE, + COMPONENT_BUILDER_DECORATOR } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -54,7 +54,8 @@ import { BUILDIN_STYLE_NAMES, EXTEND_ATTRIBUTE, GLOBAL_STYLE_FUNCTION, - STYLES_ATTRIBUTE + STYLES_ATTRIBUTE, + CUSTOM_BUILDER_METHOD } from './component_map'; import { LogType, @@ -324,6 +325,9 @@ function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponent if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { collectComponentProps(node); } + if (ts.isMethodDeclaration(node) && hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { + CUSTOM_BUILDER_METHOD.add(node.name.getText()); + } node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log)); } diff --git a/compiler/test/ut/builder/builderBindPopu.ts b/compiler/test/ut/builder/builderBindPopu.ts new file mode 100644 index 0000000000000000000000000000000000000000..8869428d4e883084d9d80ff8b6881d92a96ac37d --- /dev/null +++ b/compiler/test/ut/builder/builderBindPopu.ts @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 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. + */ + +exports.source = ` + +@Entry +@Component +struct Banner { +@Builder textBuilder() { + Text("文本") + .fontSize(30) +} +@Builder NavigationTitle(label:string) { + Column() { + Text(label) + .width(10) + .bindMenu(this.textBuilder) + } +} + build() { + Column() { + Text("111") + .bindMenu(this.NavigationTitle("111")) + } + } +} +` +exports.expectResult = +`class Banner extends View { + constructor(compilerAssignedUniqueChildId, parent, params) { + super(compilerAssignedUniqueChildId, parent); + this.updateWithValueParams(params); + } + updateWithValueParams(params) { + } + aboutToBeDeleted() { + SubscriberManager.Get().delete(this.id()); + } + textBuilder() { + Text.create("文本"); + Text.fontSize(30); + Text.pop(); + } + NavigationTitle(label) { + Column.create(); + Text.create(label); + Text.width(10); + Text.bindMenu({ builder: this.textBuilder.bind(this) }); + Text.pop(); + Column.pop(); + } + render() { + Column.create(); + Text.create("111"); + Text.bindMenu({ builder: () => { + this.NavigationTitle("111"); + } }); + Text.pop(); + Column.pop(); + } +} +loadDocument(new Banner("1", undefined, {})); +`