1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts from 'typescript'; 17import path from 'path'; 18 19import { componentCollection } from './validate_ui_syntax'; 20import { processComponentClass } from './process_component_class'; 21import processImport from './process_import'; 22import { 23 PAGE_ENTRY_FUNCTION_NAME, 24 PREVIEW_COMPONENT_FUNCTION_NAME, 25 STORE_PREVIEW_COMPONENTS, 26 GET_PREVIEW_FLAG_FUNCTION_NAME, 27 COMPONENT_CONSTRUCTOR_UNDEFINED, 28 BUILD_ON, 29 COMPONENT_BUILDER_DECORATOR, 30 COMPONENT_EXTEND_DECORATOR, 31 COMPONENT_STYLES_DECORATOR, 32 RESOURCE, 33 RESOURCE_TYPE, 34 WORKER_OBJECT, 35 RESOURCE_NAME_ID, 36 RESOURCE_NAME_TYPE, 37 RESOURCE_NAME_PARAMS, 38 RESOURCE_RAWFILE, 39 ATTRIBUTE_ANIMATETO, 40 GLOBAL_CONTEXT, 41 CHECK_COMPONENT_EXTEND_DECORATOR, 42 INSTANCE, 43 SET_CONTROLLER_CTR_TYPE, 44 SET_CONTROLLER_METHOD, 45 JS_DIALOG, 46 CUSTOM_DIALOG_CONTROLLER_BUILDER 47} from './pre_define'; 48import { 49 componentInfo, 50 LogInfo, 51 LogType, 52 hasDecorator, 53 FileLog 54} from './utils'; 55import { 56 processComponentBlock, 57 bindComponentAttr 58} from './process_component_build'; 59import { 60 BUILDIN_STYLE_NAMES, 61 CUSTOM_BUILDER_METHOD, 62 EXTEND_ATTRIBUTE, 63 INNER_STYLE_FUNCTION, 64 GLOBAL_STYLE_FUNCTION, 65 INTERFACE_NODE_SET 66} from './component_map'; 67import { 68 localStorageLinkCollection, 69 localStoragePropCollection 70} from './validate_ui_syntax' 71import { 72 resources, 73 projectConfig 74} from '../main'; 75import { createCustomComponentNewExpression, createViewCreate } from './process_component_member'; 76 77export const transformLog: FileLog = new FileLog(); 78export let contextGlobal: ts.TransformationContext; 79 80export function processUISyntax(program: ts.Program, ut = false): Function { 81 return (context: ts.TransformationContext) => { 82 contextGlobal = context; 83 let pagesDir: string; 84 return (node: ts.SourceFile) => { 85 pagesDir = path.resolve(path.dirname(node.fileName)); 86 if (process.env.compiler === BUILD_ON) { 87 if (!ut && (process.env.compileMode !== 'moduleJson' && 88 path.resolve(node.fileName) === path.resolve(projectConfig.projectPath, 'app.ets') || 89 /\.ts$/.test(node.fileName))) { 90 node = ts.visitEachChild(node, processResourceNode, context); 91 return node; 92 } 93 transformLog.sourceFile = node; 94 node = createEntryNode(node, context); 95 node = ts.visitEachChild(node, processAllNodes, context); 96 GLOBAL_STYLE_FUNCTION.forEach((block, styleName) => { 97 BUILDIN_STYLE_NAMES.delete(styleName); 98 }); 99 GLOBAL_STYLE_FUNCTION.clear(); 100 const statements: ts.Statement[] = Array.from(node.statements); 101 INTERFACE_NODE_SET.forEach(item => { 102 statements.unshift(item); 103 }); 104 node = ts.factory.updateSourceFile(node, statements); 105 INTERFACE_NODE_SET.clear(); 106 return node; 107 } else { 108 return node; 109 } 110 }; 111 function processAllNodes(node: ts.Node): ts.Node { 112 if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) || 113 ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { 114 processImport(node, pagesDir, transformLog.errors); 115 } else if (ts.isStructDeclaration(node)) { 116 componentCollection.currentClassName = node.name.getText(); 117 node = processComponentClass(node, context, transformLog.errors, program); 118 componentCollection.currentClassName = null; 119 INNER_STYLE_FUNCTION.forEach((block, styleName) => { 120 BUILDIN_STYLE_NAMES.delete(styleName); 121 }); 122 INNER_STYLE_FUNCTION.clear(); 123 } else if (ts.isFunctionDeclaration(node)) { 124 if (hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { 125 node = processExtend(node, transformLog.errors); 126 } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR) && node.name && node.body && 127 ts.isBlock(node.body)) { 128 CUSTOM_BUILDER_METHOD.add(node.name.getText()); 129 node = ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers, 130 node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, 131 processComponentBlock(node.body, false, transformLog.errors)); 132 } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { 133 if (node.parameters.length === 0) { 134 node = undefined; 135 } else { 136 transformLog.errors.push({ 137 type: LogType.ERROR, 138 message: `@Styles can't have parameters.`, 139 pos: node.getStart() 140 }); 141 } 142 } 143 } else if (isResource(node)) { 144 node = processResourceData(node as ts.CallExpression); 145 } else if (isWorker(node)) { 146 node = processWorker(node as ts.NewExpression); 147 } else if (isAnimateTo(node)) { 148 node = processAnimateTo(node as ts.CallExpression); 149 } else if (isCustomDialogController(node)) { 150 node = createCustomDialogController(node.parent, node, transformLog.errors); 151 } 152 return ts.visitEachChild(node, processAllNodes, context); 153 } 154 function processResourceNode(node: ts.Node): ts.Node { 155 if (isResource(node)) { 156 node = processResourceData(node as ts.CallExpression); 157 } 158 return ts.visitEachChild(node, processResourceNode, context); 159 } 160 }; 161} 162 163function isCustomDialogController(node: ts.Expression) { 164 const tempParent: ts.Node = node.parent; 165 // @ts-ignore 166 if (!node.parent && node.original) { 167 // @ts-ignore 168 node.parent = node.original.parent; 169 } 170 if (ts.isNewExpression(node) && node.expression && ts.isIdentifier(node.expression) && 171 node.expression.escapedText.toString() === SET_CONTROLLER_CTR_TYPE) { 172 return true; 173 } else { 174 // @ts-ignore 175 node.parent = tempParent; 176 return false; 177 } 178} 179 180function createCustomDialogController(parent: ts.Expression, node: ts.NewExpression, 181 log: LogInfo[]): ts.NewExpression { 182 if (node.arguments && node.arguments.length === 1 && 183 ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) { 184 const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => { 185 if (isCustomDialogControllerPropertyAssignment(item, log)) { 186 item = processCustomDialogControllerPropertyAssignment(parent, item as ts.PropertyAssignment); 187 } 188 return item; 189 }); 190 return ts.factory.createNewExpression(node.expression, node.typeArguments, 191 [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]); 192 } 193} 194 195function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike, 196 log: LogInfo[]): boolean { 197 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 198 node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { 199 if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && 200 componentCollection.customDialogs.has(node.initializer.expression.getText())) { 201 return true; 202 } else { 203 validateCustomDialogControllerBuilderInit(node, log); 204 } 205 } 206} 207 208function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike, 209 log: LogInfo[]): void { 210 log.push({ 211 type: LogType.ERROR, 212 message: 'The builder should be initialized with a @CustomDialog Component.', 213 pos: node.getStart() 214 }); 215} 216 217function processCustomDialogControllerPropertyAssignment(parent: ts.Expression, 218 node: ts.PropertyAssignment): ts.PropertyAssignment { 219 if (ts.isCallExpression(node.initializer)) { 220 return ts.factory.updatePropertyAssignment(node, node.name, 221 processCustomDialogControllerBuilder(parent, node.initializer)); 222 } 223} 224 225function processCustomDialogControllerBuilder(parent: ts.Expression, 226 node: ts.CallExpression): ts.ArrowFunction { 227 const newExp: ts.Expression = createCustomComponentNewExpression(node); 228 const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG); 229 return createCustomComponentBuilderArrowFunction(parent, jsDialog, newExp); 230} 231 232function createCustomComponentBuilderArrowFunction(parent: ts.Expression, 233 jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction { 234 let mountNodde: ts.PropertyAccessExpression; 235 if (ts.isBinaryExpression(parent)) { 236 mountNodde = parent.left; 237 } else if (ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent)) { 238 mountNodde = ts.factory.createPropertyAccessExpression(ts.factory.createThis(), 239 parent.name as ts.Identifier); 240 } 241 return ts.factory.createArrowFunction( 242 undefined, 243 undefined, 244 [], 245 undefined, 246 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 247 ts.factory.createBlock( 248 [ 249 ts.factory.createVariableStatement( 250 undefined, 251 ts.factory.createVariableDeclarationList( 252 [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)], 253 ts.NodeFlags.Let 254 ) 255 ), 256 ts.factory.createExpressionStatement( 257 ts.factory.createCallExpression( 258 ts.factory.createPropertyAccessExpression( 259 jsDialog, 260 ts.factory.createIdentifier(SET_CONTROLLER_METHOD) 261 ), 262 undefined, 263 [mountNodde] 264 ) 265 ), 266 ts.factory.createExpressionStatement(createViewCreate(jsDialog)) 267 ], 268 true 269 ) 270 ); 271} 272 273function isResource(node: ts.Node): boolean { 274 return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && 275 (node.expression.escapedText.toString() === RESOURCE || 276 node.expression.escapedText.toString() === RESOURCE_RAWFILE) && node.arguments.length > 0; 277} 278 279function isAnimateTo(node: ts.Node): boolean { 280 return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && 281 node.expression.escapedText.toString() === ATTRIBUTE_ANIMATETO; 282} 283 284function processResourceData(node: ts.CallExpression): ts.Node { 285 if (ts.isStringLiteral(node.arguments[0])) { 286 if (node.expression.getText() === RESOURCE_RAWFILE) { 287 return createResourceParam(0, RESOURCE_TYPE.rawfile, [node.arguments[0]]); 288 } else { 289 return getResourceDataNode(node); 290 } 291 } 292 return node; 293} 294 295function getResourceDataNode(node: ts.CallExpression): ts.Node { 296 const resourceData: string[] = (node.arguments[0] as ts.StringLiteral).text.trim().split('.'); 297 if (validateResourceData(resourceData, resources, node.arguments[0].getStart())) { 298 const resourceType: number = RESOURCE_TYPE[resourceData[1]]; 299 if (resourceType === undefined) { 300 transformLog.errors.push({ 301 type: LogType.ERROR, 302 message: `The resource type ${resourceData[1]} is not supported.`, 303 pos: node.getStart() 304 }); 305 return node; 306 } 307 const resourceValue: number = resources[resourceData[0]][resourceData[1]][resourceData[2]]; 308 return createResourceParam(resourceValue, resourceType, 309 Array.from(node.arguments).slice(1)); 310 } 311 return node; 312} 313 314function createResourceParam(resourceValue: number, resourceType: number, argsArr: ts.Expression[]): 315 ts.ObjectLiteralExpression { 316 const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression( 317 [ 318 ts.factory.createPropertyAssignment( 319 ts.factory.createStringLiteral(RESOURCE_NAME_ID), 320 ts.factory.createNumericLiteral(resourceValue) 321 ), 322 ts.factory.createPropertyAssignment( 323 ts.factory.createStringLiteral(RESOURCE_NAME_TYPE), 324 ts.factory.createNumericLiteral(resourceType) 325 ), 326 ts.factory.createPropertyAssignment( 327 ts.factory.createIdentifier(RESOURCE_NAME_PARAMS), 328 ts.factory.createArrayLiteralExpression( 329 argsArr, 330 false 331 ) 332 ) 333 ], 334 false 335 ); 336 return resourceParams; 337} 338 339function validateResourceData(resourceData: string[], resources: object, pos: number): boolean { 340 if (resourceData.length !== 3) { 341 transformLog.errors.push({ 342 type: LogType.ERROR, 343 message: 'The input parameter is not supported.', 344 pos: pos 345 }); 346 } else if (!resources[resourceData[0]]) { 347 transformLog.errors.push({ 348 type: LogType.ERROR, 349 message: `Unknown resource source '${resourceData[0]}'.`, 350 pos: pos 351 }); 352 } else if (!resources[resourceData[0]][resourceData[1]]) { 353 transformLog.errors.push({ 354 type: LogType.ERROR, 355 message: `Unknown resource type '${resourceData[1]}'.`, 356 pos: pos 357 }); 358 } else if (!resources[resourceData[0]][resourceData[1]][resourceData[2]]) { 359 transformLog.errors.push({ 360 type: LogType.ERROR, 361 message: `Unknown resource name '${resourceData[2]}'.`, 362 pos: pos 363 }); 364 } else { 365 return true; 366 } 367 return false; 368} 369 370function isWorker(node: ts.Node): boolean { 371 return ts.isNewExpression(node) && ts.isPropertyAccessExpression(node.expression) && 372 ts.isIdentifier(node.expression.name) && 373 node.expression.name.escapedText.toString() === WORKER_OBJECT; 374} 375 376function processWorker(node: ts.NewExpression): ts.Node { 377 if (node.arguments.length && ts.isStringLiteral(node.arguments[0])) { 378 const args: ts.Expression[] = Array.from(node.arguments); 379 // @ts-ignore 380 const workerPath: string = node.arguments[0].text; 381 const stringNode: ts.StringLiteral = ts.factory.createStringLiteral( 382 workerPath.replace(/\.ts$/, '.js')); 383 args.splice(0, 1, stringNode); 384 return ts.factory.updateNewExpression(node, node.expression, node.typeArguments, args); 385 } 386 return node; 387} 388 389function processAnimateTo(node: ts.CallExpression): ts.CallExpression { 390 return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression( 391 ts.factory.createIdentifier(GLOBAL_CONTEXT), ts.factory.createIdentifier(ATTRIBUTE_ANIMATETO)), 392 node.typeArguments, node.arguments); 393} 394 395function processExtend(node: ts.FunctionDeclaration, log: LogInfo[]): ts.FunctionDeclaration { 396 const componentName: string = isExtendFunction(node); 397 if (componentName && node.body && node.body.statements.length) { 398 const statementArray: ts.Statement[] = []; 399 const attrSet: ts.CallExpression = node.body.statements[0].expression; 400 const changeCompName: ts.ExpressionStatement = ts.factory.createExpressionStatement(processExtendBody(attrSet)); 401 bindComponentAttr(changeCompName as ts.ExpressionStatement, 402 ts.factory.createIdentifier(componentName), statementArray, log); 403 let extendFunctionName: string; 404 if (node.name.getText().startsWith('__' + componentName + '__')) { 405 extendFunctionName = node.name.getText(); 406 } else { 407 extendFunctionName = '__' + componentName + '__' + node.name.getText(); 408 collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.escapedText.toString()); 409 } 410 return ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers, node.asteriskToken, 411 ts.factory.createIdentifier(extendFunctionName), node.typeParameters, 412 node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray)); 413 } 414} 415 416function processExtendBody(node: ts.Node): ts.Expression { 417 switch (node.kind) { 418 case ts.SyntaxKind.CallExpression: 419 return ts.factory.createCallExpression(processExtendBody(node.expression), undefined, node.arguments); 420 case ts.SyntaxKind.PropertyAccessExpression: 421 return ts.factory.createPropertyAccessExpression(processExtendBody(node.expression), node.name); 422 case ts.SyntaxKind.Identifier: 423 return ts.factory.createIdentifier(node.escapedText.toString().replace(INSTANCE, '')); 424 } 425} 426 427export function collectExtend(collectionSet: Map<string, Set<string>>, component: string, attribute: string): void { 428 if (collectionSet.has(component)) { 429 collectionSet.get(component).add(attribute); 430 } else { 431 collectionSet.set(component, new Set([attribute])); 432 } 433} 434 435export function isExtendFunction(node: ts.FunctionDeclaration): string { 436 if (node.decorators && node.decorators[0].expression && node.decorators[0].expression.expression && 437 node.decorators[0].expression.expression.escapedText.toString() === CHECK_COMPONENT_EXTEND_DECORATOR && 438 node.decorators[0].expression.arguments) { 439 return node.decorators[0].expression.arguments[0].escapedText.toString(); 440 } else { 441 return null; 442 } 443} 444 445function createEntryNode(node: ts.SourceFile, context: ts.TransformationContext): ts.SourceFile { 446 if (componentCollection.previewComponent.size === 0 || !projectConfig.isPreview) { 447 if (componentCollection.entryComponent) { 448 const entryNode: ts.ExpressionStatement = 449 createEntryFunction(componentCollection.entryComponent, context); 450 return context.factory.updateSourceFile(node, [...node.statements, entryNode]); 451 } else { 452 return node; 453 } 454 } else { 455 const statementsArray: ts.Statement = 456 createPreviewComponentFunction(componentCollection.entryComponent, context); 457 return context.factory.updateSourceFile(node, [...node.statements, statementsArray]); 458 } 459} 460 461function createEntryFunction(name: string, context: ts.TransformationContext) 462 : ts.ExpressionStatement { 463 let localStorageName: string; 464 const localStorageNum: number = localStorageLinkCollection.get(name).size + 465 localStoragePropCollection.get(name).size; 466 if (componentCollection.entryComponent === name && componentCollection.localStorageName && 467 localStorageNum) { 468 localStorageName = componentCollection.localStorageName; 469 } else if (componentCollection.entryComponent === name && !componentCollection.localStorageName 470 && localStorageNum) { 471 transformLog.errors.push({ 472 type: LogType.ERROR, 473 message: `@Entry should have a parameter, like '@Entry (storage)'.`, 474 pos: componentCollection.entryComponentPos 475 }); 476 return; 477 } 478 const newArray: ts.Expression[] = [ 479 context.factory.createStringLiteral((++componentInfo.id).toString()), 480 context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), 481 context.factory.createObjectLiteralExpression([], false) 482 ] 483 if (localStorageName) { 484 newArray.push(context.factory.createIdentifier(localStorageName)) 485 } 486 const newExpressionStatement: ts.ExpressionStatement = 487 context.factory.createExpressionStatement(context.factory.createCallExpression( 488 context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME), undefined, 489 [context.factory.createNewExpression(context.factory.createIdentifier(name), 490 undefined, newArray)])); 491 return newExpressionStatement; 492} 493 494function createPreviewComponentFunction(name: string, context: ts.TransformationContext) 495 : ts.Statement { 496 const newArray: ts.Expression[] = [ 497 context.factory.createStringLiteral((++componentInfo.id).toString()), 498 context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED), 499 context.factory.createObjectLiteralExpression([], false) 500 ]; 501 const argsArr: ts.Expression[] = []; 502 componentCollection.previewComponent.forEach(componentName => { 503 const newExpression: ts.Expression = context.factory.createNewExpression( 504 context.factory.createIdentifier(componentName), 505 undefined, 506 newArray 507 ); 508 argsArr.push(context.factory.createStringLiteral(componentName)); 509 argsArr.push(newExpression); 510 }); 511 const ifStatement: ts.Statement = context.factory.createIfStatement( 512 context.factory.createBinaryExpression( 513 context.factory.createTypeOfExpression(context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME)), 514 context.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), 515 context.factory.createStringLiteral(COMPONENT_CONSTRUCTOR_UNDEFINED) 516 ), 517 context.factory.createBlock( 518 [ 519 context.factory.createIfStatement( 520 context.factory.createCallExpression( 521 context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME), 522 undefined, 523 [] 524 ), 525 context.factory.createBlock( 526 [context.factory.createExpressionStatement(context.factory.createCallExpression( 527 context.factory.createIdentifier(PREVIEW_COMPONENT_FUNCTION_NAME), 528 undefined, 529 [] 530 ))], 531 true 532 ), 533 context.factory.createBlock( 534 [ 535 context.factory.createExpressionStatement(context.factory.createCallExpression( 536 context.factory.createIdentifier(STORE_PREVIEW_COMPONENTS), 537 undefined, 538 [ 539 context.factory.createNumericLiteral(componentCollection.previewComponent.size), 540 ...argsArr 541 ] 542 )), 543 name ? context.factory.createExpressionStatement(context.factory.createCallExpression( 544 context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME), 545 undefined, 546 [context.factory.createNewExpression( 547 context.factory.createIdentifier(name), 548 undefined, 549 newArray 550 )] 551 )) : undefined 552 ], 553 true 554 ) 555 ) 556 ], 557 true 558 ), 559 context.factory.createBlock( 560 [ 561 context.factory.createExpressionStatement(context.factory.createCallExpression( 562 context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME), 563 undefined, 564 [context.factory.createNewExpression( 565 context.factory.createIdentifier(name ? name : Array.from(componentCollection.previewComponent)[0]), 566 undefined, 567 newArray 568 )] 569 )) 570 ], 571 true 572 ) 573 ) 574 return ifStatement; 575} 576 577export function resetLog(): void { 578 transformLog.errors = []; 579} 580