/* * 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. */ import ts from 'typescript'; import { COMPONENT_STATE_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_LINK_DECORATOR, COMPONENT_PROP_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_CONSUME_DECORATOR, SYNCHED_PROPERTY_NESED_OBJECT, SYNCHED_PROPERTY_SIMPLE_TWO_WAY, SYNCHED_PROPERTY_SIMPLE_ONE_WAY, OBSERVED_PROPERTY_OBJECT, OBSERVED_PROPERTY_SIMPLE, COMPONENT_BUILD_FUNCTION, BASE_COMPONENT_NAME, CREATE_CONSTRUCTOR_PARAMS, COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, COMPONENT_CONSTRUCTOR_DELETE_PARAMS, COMPONENT_DECORATOR_PREVIEW, CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER, ABOUT_TO_BE_DELETE_FUNCTION_ID, CREATE_CONSTRUCTOR_GET_FUNCTION, CREATE_CONSTRUCTOR_DELETE_FUNCTION, FOREACH_OBSERVED_OBJECT, FOREACH_GET_RAW_OBJECT, COMPONENT_BUILDER_DECORATOR, COMPONENT_TRANSITION_FUNCTION, COMPONENT_CREATE_FUNCTION, GEOMETRY_VIEW, COMPONENT_STYLES_DECORATOR, STYLES, INTERFACE_NAME_SUFFIX, OBSERVED_PROPERTY_ABSTRACT, COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, COMPONENT_CONSTRUCTOR_LOCALSTORAGE, COMPONENT_SET_AND_LINK, COMPONENT_SET_AND_PROP, COMPONENT_CONSTRUCTOR_UNDEFINED } from './pre_define'; import { BUILDIN_STYLE_NAMES, CUSTOM_BUILDER_METHOD, INNER_STYLE_FUNCTION, INTERFACE_NODE_SET, STYLES_ATTRIBUTE } from './component_map'; import { componentCollection, linkCollection, localStorageLinkCollection, localStoragePropCollection } from './validate_ui_syntax'; import { addConstructor, getInitConstructor } from './process_component_constructor'; import { ControllerType, processMemberVariableDecorators, UpdateResult, stateObjectCollection, curPropMap, decoratorParamSet, isSimpleType } from './process_component_member'; import { processComponentBuild, processComponentBlock } from './process_component_build'; import { LogType, LogInfo, hasDecorator } from './utils'; export function processComponentClass(node: ts.StructDeclaration, context: ts.TransformationContext, log: LogInfo[], program: ts.Program): ts.ClassDeclaration { validateInheritClass(node, log); const memberNode: ts.ClassElement[] = processMembers(node.members, node.name, context, log, program, checkPreview(node)); return ts.factory.createClassDeclaration(undefined, node.modifiers, node.name, node.typeParameters, updateHeritageClauses(node), memberNode); } function checkPreview(node: ts.ClassDeclaration) { let hasPreview: boolean = false; if (node && node.decorators) { for (let i = 0; i < node.decorators.length; i++) { const name: string = node.decorators[i].getText().replace(/\([^\(\)]*\)/, '').trim(); if (name === COMPONENT_DECORATOR_PREVIEW) { hasPreview = true; break; } } } return hasPreview; } type BuildCount = { count: number; } function processMembers(members: ts.NodeArray, parentComponentName: ts.Identifier, context: ts.TransformationContext, log: LogInfo[], program: ts.Program, hasPreview: boolean): ts.ClassElement[] { const buildCount: BuildCount = { count: 0 }; let ctorNode: any = getInitConstructor(members, parentComponentName); const newMembers: ts.ClassElement[] = []; const watchMap: Map = new Map(); const updateParamsStatements: ts.Statement[] = []; const deleteParamsStatements: ts.PropertyDeclaration[] = []; const checkController: ControllerType = { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()) }; const interfaceNode = ts.factory.createInterfaceDeclaration(undefined, undefined, parentComponentName.getText() + INTERFACE_NAME_SUFFIX, undefined, undefined, []); members.forEach((item: ts.ClassElement) => { let updateItem: ts.ClassElement; if (ts.isPropertyDeclaration(item)) { addPropertyMember(item, newMembers, program, parentComponentName.getText(), log); const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item, ctorNode, watchMap, checkController, log, program, context, hasPreview, interfaceNode); if (result.isItemUpdate()) { updateItem = result.getProperity(); } else { updateItem = item; } if (result.getVariableGet()) { newMembers.push(result.getVariableGet()); } if (result.getVariableSet()) { newMembers.push(result.getVariableSet()); } if (result.isCtorUpdate()) { ctorNode = result.getCtor(); } if (result.getUpdateParams()) { updateParamsStatements.push(result.getUpdateParams()); } if (result.isDeleteParams()) { deleteParamsStatements.push(item); } if (result.getControllerSet()) { newMembers.push(result.getControllerSet()); } } if (ts.isMethodDeclaration(item) && item.name) { updateItem = processComponentMethod(item, parentComponentName, context, log, buildCount); } if (updateItem) { newMembers.push(updateItem); } }); INTERFACE_NODE_SET.add(interfaceNode); validateBuildMethodCount(buildCount, parentComponentName, log); validateHasController(parentComponentName, checkController, log); newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements)); newMembers.unshift(addUpdateParamsFunc(updateParamsStatements, parentComponentName)); newMembers.unshift(addConstructor(ctorNode, watchMap, parentComponentName)); return newMembers; } function addPropertyMember(item: ts.ClassElement, newMembers: ts.ClassElement[], program: ts.Program, parentComponentName: string, log: LogInfo[]): void { const propertyItem: ts.PropertyDeclaration = item as ts.PropertyDeclaration; let decoratorName: string; let updatePropertyItem: ts.PropertyDeclaration; const type: ts.TypeNode = propertyItem.type; if (!propertyItem.decorators || propertyItem.decorators.length === 0) { updatePropertyItem = createPropertyDeclaration(propertyItem, type, true); newMembers.push(updatePropertyItem); } else if (propertyItem.decorators) { for (let i = 0; i < propertyItem.decorators.length; i++) { let newType: ts.TypeNode; decoratorName = propertyItem.decorators[i].getText().replace(/\(.*\)$/, '').trim(); let isLocalStorage: boolean = false; switch (decoratorName) { case COMPONENT_STATE_DECORATOR: case COMPONENT_PROVIDE_DECORATOR: newType = ts.factory.createTypeReferenceNode(isSimpleType(type, program, log) ? OBSERVED_PROPERTY_SIMPLE : OBSERVED_PROPERTY_OBJECT, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); break; case COMPONENT_LINK_DECORATOR: case COMPONENT_CONSUME_DECORATOR: newType = ts.factory.createTypeReferenceNode(isSimpleType(type, program, log) ? SYNCHED_PROPERTY_SIMPLE_TWO_WAY : SYNCHED_PROPERTY_SIMPLE_ONE_WAY, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); break; case COMPONENT_PROP_DECORATOR: newType = ts.factory.createTypeReferenceNode(SYNCHED_PROPERTY_SIMPLE_ONE_WAY, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); break; case COMPONENT_OBJECT_LINK_DECORATOR: newType = ts.factory.createTypeReferenceNode(SYNCHED_PROPERTY_NESED_OBJECT, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); break; case COMPONENT_STORAGE_PROP_DECORATOR: case COMPONENT_STORAGE_LINK_DECORATOR: newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); break; case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR: case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [type || ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)]); isLocalStorage = true; break; } updatePropertyItem = createPropertyDeclaration(propertyItem, newType, false, isLocalStorage, parentComponentName); if (updatePropertyItem) { newMembers.push(updatePropertyItem); } } } } function createPropertyDeclaration(propertyItem: ts.PropertyDeclaration, newType: ts.TypeNode | undefined, normalVar: boolean, isLocalStorage: boolean = false, parentComponentName: string = null): ts.PropertyDeclaration { if (typeof newType === undefined) { return undefined; } let prefix: string = ''; if (!normalVar) { prefix = '__'; } const privateM: ts.ModifierToken = ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword); return ts.factory.updatePropertyDeclaration(propertyItem, undefined, propertyItem.modifiers || [privateM], prefix + propertyItem.name.getText(), propertyItem.questionToken, newType, isLocalStorage ? createLocalStroageCallExpression(propertyItem, propertyItem.name.getText(), parentComponentName) : undefined); } function createLocalStroageCallExpression(node: ts.PropertyDeclaration, name: string, parentComponentName: string): ts.CallExpression { const localStorageLink: Set = localStorageLinkCollection.get(parentComponentName).get(name); const localStorageProp: Set = localStoragePropCollection.get(parentComponentName).get(name); return ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression( ts.factory.createThis(), ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_LOCALSTORAGE}_`) ), ts.factory.createIdentifier(localStorageLink && !localStorageProp ? COMPONENT_SET_AND_LINK : COMPONENT_SET_AND_PROP) ), [node.type], [ ts.factory.createStringLiteral(localStorageLink && !localStorageProp ? Array.from(localStorageLink)[0] : !localStorageLink && localStorageProp ? Array.from(localStorageProp)[0] : COMPONENT_CONSTRUCTOR_UNDEFINED), ts.factory.createNumericLiteral(node.initializer ? node.initializer.getText() : COMPONENT_CONSTRUCTOR_UNDEFINED), ts.factory.createThis(), ts.factory.createStringLiteral(name || COMPONENT_CONSTRUCTOR_UNDEFINED) ] ); } function processComponentMethod(node: ts.MethodDeclaration, parentComponentName: ts.Identifier, context: ts.TransformationContext, log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration { let updateItem: ts.MethodDeclaration = node; const name: string = node.name.getText(); if (name === COMPONENT_BUILD_FUNCTION) { buildCount.count = buildCount.count + 1; updateItem = processBuildMember(node, context, log); curPropMap.clear(); } else if (node.body && ts.isBlock(node.body)) { if (name === COMPONENT_TRANSITION_FUNCTION) { updateItem = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, processComponentBlock(node.body, false, log, true)); } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { CUSTOM_BUILDER_METHOD.add(name); updateItem = ts.factory.updateMethodDeclaration(node, undefined, node.modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, processComponentBlock(node.body, false, log)); } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { if (node.parameters && node.parameters.length === 0) { if (ts.isBlock(node.body) && node.body.statements && node.body.statements.length) { INNER_STYLE_FUNCTION.set(name, node.body); STYLES_ATTRIBUTE.add(name); BUILDIN_STYLE_NAMES.add(name); decoratorParamSet.add(STYLES); } } else { log.push({ type: LogType.ERROR, message: `@Styles can't have parameters.`, pos: node.getStart() }); } return; } } return updateItem; } function processBuildMember(node: ts.MethodDeclaration, context: ts.TransformationContext, log: LogInfo[]): ts.MethodDeclaration { if (node.parameters.length) { log.push({ type: LogType.ERROR, message: `The 'build' method can not have arguments.`, pos: node.getStart() }); } const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); return ts.visitNode(buildNode, visitBuild); function visitBuild(node: ts.Node): ts.Node { if (isGeometryView(node)) { node = processGeometryView(node as ts.ExpressionStatement, log); } if (isProperty(node)) { node = createReference(node as ts.PropertyAssignment); } if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && stateObjectCollection.has(node.name.escapedText.toString()) && node.parent && ts.isCallExpression(node.parent) && ts.isPropertyAccessExpression(node.parent.expression) && node.parent.expression.name.escapedText.toString() !== FOREACH_GET_RAW_OBJECT) { return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]); } return ts.visitEachChild(node, visitBuild, context); } } function isGeometryView(node: ts.Node): boolean { if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { const call: ts.CallExpression = node.expression; const exp: ts.Expression = call.expression; const args: ts.NodeArray = call.arguments; if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression) && exp.expression.escapedText.toString() === GEOMETRY_VIEW && ts.isIdentifier(exp.name) && exp.name.escapedText.toString() === COMPONENT_CREATE_FUNCTION && args && args.length === 1 && (ts.isArrowFunction(args[0]) || ts.isFunctionExpression(args[0]))) { return true; } } return false; } function processGeometryView(node: ts.ExpressionStatement, log: LogInfo[]): ts.ExpressionStatement { const exp: ts.CallExpression = node.expression as ts.CallExpression; const arg: ts.ArrowFunction | ts.FunctionExpression = exp.arguments[0] as ts.ArrowFunction | ts.FunctionExpression; return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(exp, exp.expression, undefined, [ts.factory.createArrowFunction(undefined, undefined, arg.parameters, undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), getGeometryReaderFunctionBlock(arg, log))])); } function getGeometryReaderFunctionBlock(node: ts.ArrowFunction | ts.FunctionExpression, log: LogInfo[]): ts.Block { let blockNode: ts.Block; if (ts.isBlock(node.body)) { blockNode = node.body; } else if (ts.isArrowFunction(node) && ts.isCallExpression(node.body)) { blockNode = ts.factory.createBlock([ts.factory.createExpressionStatement(node.body)]); } return processComponentBlock(blockNode, false, log); } function updateHeritageClauses(node: ts.StructDeclaration): ts.NodeArray { const result:ts.HeritageClause[] = []; const heritageClause:ts.HeritageClause = ts.factory.createHeritageClause( ts.SyntaxKind.ExtendsKeyword, [ts.factory.createExpressionWithTypeArguments( ts.factory.createIdentifier(BASE_COMPONENT_NAME), [])]); if (node.heritageClauses) { result.push(...node.heritageClauses); } result.push(heritageClause); return ts.factory.createNodeArray(result); } export function isProperty(node: ts.Node): Boolean { if (judgmentParentType(node)) { if (node.parent.parent.expression && ts.isIdentifier(node.parent.parent.expression) && !BUILDIN_STYLE_NAMES.has(node.parent.parent.expression.escapedText.toString()) && componentCollection.customComponents.has( node.parent.parent.expression.escapedText.toString())) { return true; } else if (ts.isPropertyAccessExpression(node.parent.parent.expression) && ts.isIdentifier(node.parent.parent.expression.expression) && componentCollection.customComponents.has( node.parent.parent.expression.name.escapedText.toString())) { return true; } } return false; } function judgmentParentType(node: ts.Node): boolean { return ts.isPropertyAssignment(node) && node.name && ts.isIdentifier(node.name) && node.parent && ts.isObjectLiteralExpression(node.parent) && node.parent.parent && (ts.isCallExpression(node.parent.parent) || ts.isEtsComponentExpression(node.parent.parent)); } export function createReference(node: ts.PropertyAssignment): ts.PropertyAssignment { const linkParentComponent: string[] = getParentNode(node, linkCollection).slice(1); const propertyName: ts.Identifier = node.name as ts.Identifier; let initText: string; const LINK_REG: RegExp = /^\$/g; const initExpression: ts.Expression = node.initializer; if (ts.isIdentifier(initExpression) && initExpression.escapedText.toString().match(LINK_REG)) { initText = initExpression.escapedText.toString().replace(LINK_REG, ''); } else if (ts.isPropertyAccessExpression(initExpression) && initExpression.expression && initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(initExpression.name) && initExpression.name.escapedText.toString().match(LINK_REG)) { initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); } else if (isMatchInitExpression(initExpression) && initExpression.name.escapedText.toString().match(LINK_REG) && linkParentComponent.includes(propertyName.escapedText.toString())) { initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); } if (initText) { node = addDoubleUnderline(node, propertyName, initText); } return node; } function isMatchInitExpression(initExpression: ts.Expression): boolean { return ts.isPropertyAccessExpression(initExpression) && initExpression.expression && initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(initExpression.name); } function addDoubleUnderline(node: ts.PropertyAssignment, propertyName: ts.Identifier, initText: string): ts.PropertyAssignment { return ts.factory.updatePropertyAssignment(node, propertyName, ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(`__${initText}`))); } function getParentNode(node: ts.PropertyAssignment, collection: Map>): string[] { const grandparentNode: ts.NewExpression = node.parent.parent as ts.NewExpression; const grandparentExpression: ts.Identifier | ts.PropertyAccessExpression = grandparentNode.expression as ts.Identifier | ts.PropertyAccessExpression; let parentComponent: Set = new Set(); let grandparentName: string; if (ts.isIdentifier(grandparentExpression)) { grandparentName = grandparentExpression.escapedText.toString(); parentComponent = collection.get(grandparentName); } else if (ts.isPropertyAccessExpression(grandparentExpression)) { grandparentName = grandparentExpression.name.escapedText.toString(); parentComponent = collection.get(grandparentName); } else { // ignore } if (!parentComponent) { parentComponent = new Set(); } return [grandparentName, ...parentComponent]; } function addUpdateParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): ts.MethodDeclaration { return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements, parentComponentName); } function addDeleteParamsFunc(statements: ts.PropertyDeclaration[]): ts.MethodDeclaration { const deleteStatements: ts.ExpressionStatement[] = []; statements.forEach((statement: ts.PropertyDeclaration) => { const name: ts.Identifier = statement.name as ts.Identifier; const paramsStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement( ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(`__${name.escapedText.toString()}`)), ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_DELETE_PARAMS)), undefined, [])); deleteStatements.push(paramsStatement); }); const defaultStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement(ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier(CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER), ts.factory.createIdentifier(CREATE_CONSTRUCTOR_GET_FUNCTION)), undefined, []), ts.factory.createIdentifier(CREATE_CONSTRUCTOR_DELETE_FUNCTION)), undefined, [ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( ts.factory.createThis(), ts.factory.createIdentifier(ABOUT_TO_BE_DELETE_FUNCTION_ID)), undefined, [])])); deleteStatements.push(defaultStatement); const deleteParamsMethod: ts.MethodDeclaration = createParamsInitBlock(COMPONENT_CONSTRUCTOR_DELETE_PARAMS, deleteStatements); return deleteParamsMethod; } function createParamsInitBlock(express: string, statements: ts.Statement[], parentComponentName?: ts.Identifier): ts.MethodDeclaration { const methodDeclaration: ts.MethodDeclaration = ts.factory.createMethodDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier(express), undefined, undefined, [ts.factory.createParameterDeclaration(undefined, undefined, undefined, express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), undefined, express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : ts.factory.createTypeReferenceNode( ts.factory.createIdentifier(parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), undefined)], undefined, ts.factory.createBlock(statements, true)); return methodDeclaration; } function validateBuildMethodCount(buildCount: BuildCount, parentComponentName: ts.Identifier, log: LogInfo[]): void { if (buildCount.count !== 1) { log.push({ type: LogType.ERROR, message: `struct '${parentComponentName.getText()}' must be at least or at most one 'build' method.`, pos: parentComponentName.getStart() }); } } function validateInheritClass(node: ts.StructDeclaration, log: LogInfo[]): void { if (node.heritageClauses) { log.push({ type: LogType.ERROR, message: '@Component should not be inherit other Classes.', pos: node.heritageClauses.pos }); } } function validateHasController(componentName: ts.Identifier, checkController: ControllerType, log: LogInfo[]): void { if (!checkController.hasController) { log.push({ type: LogType.ERROR, message: '@CustomDialog component should have a property of the CustomDialogController type.', pos: componentName.pos }); } }