diff --git a/compiler/src/ets_checker.ts b/compiler/src/ets_checker.ts index 3119c88..613f1d2 100644 --- a/compiler/src/ets_checker.ts +++ b/compiler/src/ets_checker.ts @@ -28,9 +28,8 @@ import { COMPONENT_DECORATORS_PARAMS, COMPONENT_BUILD_FUNCTION, BIND_POPUP, - CHECKED, - RADIO, - $$ + $$, + PROPERTIES_ADD_DOUBLE_DOLLAR } from './pre_define'; import { JS_BIND_COMPONENTS } from './component_map'; import { getName } from './process_component_build'; @@ -214,34 +213,17 @@ function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile): void { function traverseBuild(node: ts.Node, index: number): void { if (ts.isExpressionStatement(node)) { let parentComponentName: string = getName(node); - if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && - index >= 1 && node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) { + if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 && + node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) { parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText; } node = node.expression; - if (node && node.body && ts.isBlock(node.body)) { + if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body)) { node.body.statements.forEach((item, indexBlock) => { traverseBuild(item, indexBlock); }); } else { - while (node) { - if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { - const argument = node.arguments; - const propertyName = node.expression.name; - if (propertyName.escapedText === BIND_POPUP || propertyName.escapedText === CHECKED && - parentComponentName === RADIO) { - argument.forEach(item => { - if (item.getText().startsWith($$)) { - while (item.expression) { - item = item.expression; - } - dollarCollection.add(item.getText()); - } - }); - } - } - node = node.expression; - } + loopNodeFindDoubleDollar(node, parentComponentName); } } else if (ts.isIfStatement(node)) { if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { @@ -257,6 +239,53 @@ function traverseBuild(node: ts.Node, index: number): void { } } +function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { + while (node) { + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { + const argument: ts.NodeArray = node.arguments; + const propertyName: ts.Identifier | ts.PrivateIdentifier= node.expression.name; + if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { + argument.forEach((item: ts.Node)=> { + doubleDollarCollection(item); + }); + } + } else if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments + && node.arguments.length) { + node.arguments.forEach((item: ts.Node) => { + if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { + item.properties.forEach((param: ts.Node)=>{ + if (isObjectPram(param, parentComponentName)) { + doubleDollarCollection(param.initializer); + } + }) + } + }) + } + node = node.expression; + } +} + +function doubleDollarCollection(item: ts.Node): void { + if (item.getText().startsWith($$)) { + while (item.expression) { + item = item.expression; + } + dollarCollection.add(item.getText()); + } +} + +function isObjectPram(param: ts.Node, parentComponentName:string): boolean { + return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && + param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); +} + +function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { + return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || + propertyName === BIND_POPUP; +} + function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean { return COMPONENT_DECORATORS_PARAMS.has(decoratorName) && // @ts-ignore diff --git a/compiler/src/pre_define.ts b/compiler/src/pre_define.ts index e5dc9f6..0bdbff7 100644 --- a/compiler/src/pre_define.ts +++ b/compiler/src/pre_define.ts @@ -195,14 +195,23 @@ export const VIEW_STACK_PROCESSOR: string = 'ViewStackProcessor'; export const BIND_POPUP: string = 'bindPopup'; export const BIND_POPUP_SET: Set = new Set(['bindPopup']); export const BIND_DRAG_SET: Set = new Set(['onDragStart', 'onItemDragStart']); + export const CHECKED: string = 'checked'; export const RADIO: string = 'Radio'; export const $$_VALUE: string = 'value'; export const $$_CHANGE_EVENT: string = 'changeEvent'; +export const TEXT_TIMER: string = 'TextTimer'; +export const FORMAT: string = 'format'; +export const IS_COUNT_DOWN: string = 'isCountDown'; +export const COUNT: string = 'count'; export const $$_THIS: string = '$$this'; export const $$_NEW_VALUE: string = 'newValue'; export const $$: string = '$$'; +export const PROPERTIES_ADD_DOUBLE_DOLLAR: Map> = new Map([ + [RADIO, new Set([CHECKED])], [TEXT_TIMER, new Set([FORMAT, COUNT, IS_COUNT_DOWN])], +]);; + export const INTERFACE_NAME_SUFFIX:string = '_Params'; export const OBSERVED_PROPERTY_ABSTRACT:string = 'ObservedPropertyAbstract'; diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index 9a21903..ccf6a5e 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -53,13 +53,13 @@ import { $$_CHANGE_EVENT, $$_THIS, $$_NEW_VALUE, - CHECKED, - RADIO, BUILDER_ATTR_NAME, BUILDER_ATTR_BIND, CUSTOM_DIALOG_CONTROLLER_BUILDER, BIND_DRAG_SET, - BIND_POPUP_SET + BIND_POPUP_SET, + $$, + PROPERTIES_ADD_DOUBLE_DOLLAR } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -359,6 +359,11 @@ function processInnerComponent(node: ts.ExpressionStatement, index: number, arr: newStatements.push(debugNode); } const etsComponentResult: EtsComponentResult = parseEtsComponentExpression(node); + if (PROPERTIES_ADD_DOUBLE_DOLLAR.has(res.identifierNode.getText()) && + etsComponentResult.etsComponentNode.arguments && etsComponentResult.etsComponentNode.arguments.length) { + etsComponentResult.etsComponentNode = processDollarEtsComponent(etsComponentResult.etsComponentNode, + res.identifierNode.getText()); + } if (etsComponentResult.etsComponentNode.body && ts.isBlock(etsComponentResult.etsComponentNode.body)) { if (res.isButton) { if (projectConfig.isPreview) { @@ -932,8 +937,7 @@ function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, statements, log, false, true, false); } lastStatement.kind = true; - } else if (!isStylesAttr && [BIND_POPUP, CHECKED].includes(propName) && - temp.arguments.length && temp.arguments[0] && temp.arguments[0].getText().match(/^\$\$(.|\n)+/)) { + } else if (isDoubleDollarToChange(isStylesAttr, identifierNode, propName, temp)) { const argumentsArr: ts.Expression[] = []; classifyArgumentsNum(temp.arguments, argumentsArr, propName, identifierNode); statements.push(ts.factory.createExpressionStatement( @@ -952,6 +956,37 @@ function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, } } +function isDoubleDollarToChange(isStylesAttr: boolean, identifierNode: ts.Identifier, + propName: string, temp: any): boolean { + return !isStylesAttr && + PROPERTIES_ADD_DOUBLE_DOLLAR.has(identifierNode.escapedText.toString()) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(identifierNode.escapedText.toString()).has(propName) || + propName === BIND_POPUP && temp.arguments.length && temp.arguments[0] ? + temp.arguments[0].getText().match(/^\$\$(.|\n)+/) !== null ? true : false + : false; +} + +function processDollarEtsComponent(node: ts.EtsComponentExpression, name: string + ): ts.EtsComponentExpression { + node.arguments.forEach((item: ts.Node, index: number) => { + if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { + item.properties.forEach((param: ts.PropertyAssignment, paramIndex: number)=>{ + if (isHaveDoubleDollar(param, name)) { + const varExp: ts.Expression = updateArgumentFor$$(param.initializer); + node.arguments[index].properties[paramIndex].initializer = generateObjectFor$$(varExp); + } + }) + } + }) + return node; +} + +function isHaveDoubleDollar(param: ts.PropertyAssignment, name: string): boolean { + return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(name).has(param.name.getText()) && param.initializer && + param.initializer.getText().startsWith($$); +} + function loopEtscomponent(node: any, isStylesAttr: boolean, isGlobalStyles: boolean): ts.Node { node.arguments.forEach((item: ts.Node, index: number) => { if (isStylesAttr && isGlobalStyles) { @@ -981,7 +1016,8 @@ function classifyArgumentsNum(args: any, argumentsArr: ts.Expression[], propName if (propName === BIND_POPUP && args.length === 2) { const varExp: ts.Expression = updateArgumentFor$$(args[0]); argumentsArr.push(generateObjectFor$$(varExp), args[1]); - } else if (propName === CHECKED && args.length === 1 && identifierNode.getText() === RADIO) { + } else if (PROPERTIES_ADD_DOUBLE_DOLLAR.has(identifierNode.getText()) && args.length === 1 && + PROPERTIES_ADD_DOUBLE_DOLLAR.get(identifierNode.getText()).has(propName)) { const varExp: ts.Expression = updateArgumentFor$$(args[0]); argumentsArr.push(varExp, createArrowFunctionFor$$(varExp)); } diff --git a/compiler/test/ut/syntacticSugar/textTimer$$.ts b/compiler/test/ut/syntacticSugar/textTimer$$.ts new file mode 100644 index 0000000..2bf656e --- /dev/null +++ b/compiler/test/ut/syntacticSugar/textTimer$$.ts @@ -0,0 +1,103 @@ +/* + * 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 = ` +let isCountDown:boolean = false +@Entry +@Component +struct TextTimerComponent { + myTimeController: TextTimerController = new TextTimerController(); + private count: number = 1000; + @State format: string = "hh:mm:ss:ms" + build() { + Column() { + TextTimer({controller: this.myTimeController, isCountDown:$$isCountDown, count:$$this.count}) + .format($$this.format) + Row(){ + Button("start") + .onClick(()=>{ + this.myTimeController.start(); + }) + Button("pause") + .onClick(()=>{ + this.myTimeController.pause(); + }) + Button("reset") + .onClick(()=>{ + this.myTimeController.reset(); + }) + } + } + } +}` + +exports.expectResult = +`let isCountDown = false; +class TextTimerComponent extends View { + constructor(compilerAssignedUniqueChildId, parent, params) { + super(compilerAssignedUniqueChildId, parent); + this.myTimeController = new TextTimerController(); + this.count = 1000; + this.__format = new ObservedPropertySimple("hh:mm:ss:ms", this, "format"); + this.updateWithValueParams(params); + } + updateWithValueParams(params) { + if (params.myTimeController !== undefined) { + this.myTimeController = params.myTimeController; + } + if (params.count !== undefined) { + this.count = params.count; + } + if (params.format !== undefined) { + this.format = params.format; + } + } + aboutToBeDeleted() { + this.__format.aboutToBeDeleted(); + SubscriberManager.Get().delete(this.id()); + } + get format() { + return this.__format.get(); + } + set format(newValue) { + this.__format.set(newValue); + } + render() { + Column.create(); + TextTimer.create({ controller: this.myTimeController, isCountDown: { value: isCountDown, changeEvent: newValue => { isCountDown = newValue; } }, count: { value: this.count, changeEvent: newValue => { this.count = newValue; } } }); + TextTimer.format(this.format, newValue => { this.format = newValue; }); + TextTimer.pop(); + Row.create(); + Button.createWithLabel("start"); + Button.onClick(() => { + this.myTimeController.start(); + }); + Button.pop(); + Button.createWithLabel("pause"); + Button.onClick(() => { + this.myTimeController.pause(); + }); + Button.pop(); + Button.createWithLabel("reset"); + Button.onClick(() => { + this.myTimeController.reset(); + }); + Button.pop(); + Row.pop(); + Column.pop(); + } +} +loadDocument(new TextTimerComponent("1", undefined, {})); +` \ No newline at end of file