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'; 17 18import { 19 COMPONENT_NON_DECORATOR, 20 COMPONENT_STATE_DECORATOR, 21 COMPONENT_PROP_DECORATOR, 22 COMPONENT_LINK_DECORATOR, 23 COMPONENT_STORAGE_LINK_DECORATOR, 24 COMPONENT_PROVIDE_DECORATOR, 25 COMPONENT_OBJECT_LINK_DECORATOR, 26 COMPONENT_CREATE_FUNCTION, 27 COMPONENT_POP_FUNCTION, 28 BASE_COMPONENT_NAME, 29 CUSTOM_COMPONENT_EARLIER_CREATE_CHILD, 30 COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, 31 CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID, 32 COMPONENT_CONSTRUCTOR_UNDEFINED, 33 CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION, 34 CUSTOM_COMPONENT_MARK_STATIC_FUNCTION, 35 COMPONENT_COMMON, 36 GENERATE_ID, 37 ELMTID, 38 STARTGETACCESSRECORDINGFOR, 39 STOPGETACCESSRECORDING, 40 BASE_COMPONENT_NAME_PU, 41 OBSERVECOMPONENTCREATION, 42 OBSERVECOMPONENTCREATION2, 43 ISINITIALRENDER, 44 UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID, 45 $$, 46 COMPONENT_RECYCLE, 47 COMPONENT_CREATE_RECYCLE, 48 RECYCLE_NODE, 49 ABOUT_TO_REUSE, 50 COMPONENT_RERENDER_FUNCTION, 51 OBSERVE_RECYCLE_COMPONENT_CREATION, 52 FUNCTION, 53 COMPONENT_IF_UNDEFINED, 54 COMPONENT_PARAMS_LAMBDA_FUNCTION, 55 COMPONENT_PARAMS_FUNCTION, 56 COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION, 57 NAME, 58 COMPONENT_CALL 59} from './pre_define'; 60import { 61 stateCollection, 62 linkCollection, 63 propCollection, 64 regularCollection, 65 storagePropCollection, 66 storageLinkCollection, 67 provideCollection, 68 consumeCollection, 69 objectLinkCollection, 70 isStaticViewCollection, 71 builderParamObjectCollection, 72 getLocalStorageCollection, 73 builderParamInitialization, 74 propInitialization 75} from './validate_ui_syntax'; 76import { 77 curPropMap, 78 createViewCreate, 79 createCustomComponentNewExpression 80} from './process_component_member'; 81import { 82 LogType, 83 LogInfo, 84 componentInfo, 85 storedFileInfo 86} from './utils'; 87import { 88 bindComponentAttr, 89 parentConditionalExpression, 90 createComponentCreationStatement, 91 createFunction, 92 ComponentAttrInfo, 93 ifRetakeId, 94 transferBuilderCall, 95 createCollectElmtIdNode, 96 createViewStackProcessorStatement, 97 BuilderParamsResult 98} from './process_component_build'; 99import { 100 partialUpdateConfig, 101 projectConfig 102} from '../main'; 103import { 104 GLOBAL_CUSTOM_BUILDER_METHOD 105} from './component_map'; 106import { 107 createReference, 108 isProperty 109} from './process_component_class'; 110 111let decoractorMap: Map<string, Map<string, Set<string>>>; 112 113export function processCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], 114 log: LogInfo[], name: string, isBuilder: boolean = false, isGlobalBuilder: boolean = false, 115 idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null): void { 116 decoractorMap = new Map( 117 [[COMPONENT_STATE_DECORATOR, stateCollection], 118 [COMPONENT_LINK_DECORATOR, linkCollection], 119 [COMPONENT_PROP_DECORATOR, propCollection], 120 [COMPONENT_NON_DECORATOR, regularCollection], 121 [COMPONENT_PROVIDE_DECORATOR, provideCollection], 122 [COMPONENT_OBJECT_LINK_DECORATOR, objectLinkCollection]]); 123 const componentNode: ts.CallExpression = getCustomComponentNode(node); 124 if (componentNode) { 125 const isRecycleComponent: boolean = isRecycle(name); 126 const hasChainCall: boolean = componentNode.parent && 127 ts.isPropertyAccessExpression(componentNode.parent); 128 let ischangeNode: boolean = false; 129 let customComponentNewExpression: ts.NewExpression = createCustomComponentNewExpression( 130 componentNode, name, isBuilder, isGlobalBuilder); 131 let argumentsArray: ts.PropertyAssignment[]; 132 const componentAttrInfo: ComponentAttrInfo = { reuseId: null }; 133 if (isHasChild(componentNode)) { 134 // @ts-ignore 135 argumentsArray = componentNode.arguments[0].properties.slice(); 136 argumentsArray.forEach((item: ts.PropertyAssignment, index: number) => { 137 if (isToChange(item, name)) { 138 ischangeNode = true; 139 const propertyAssignmentNode: ts.PropertyAssignment = ts.factory.updatePropertyAssignment( 140 item, item.name, changeNodeFromCallToArrow(item.initializer as ts.CallExpression)); 141 argumentsArray.splice(index, 1, propertyAssignmentNode); 142 } 143 }); 144 if (ischangeNode) { 145 const newNode: ts.ExpressionStatement = ts.factory.updateExpressionStatement(node, 146 ts.factory.createNewExpression(componentNode.expression, componentNode.typeArguments, 147 [ts.factory.createObjectLiteralExpression(argumentsArray, true)])); 148 customComponentNewExpression = createCustomComponentNewExpression( 149 newNode.expression as ts.CallExpression, name, isBuilder); 150 } 151 } 152 let judgeIdStart: number; 153 if (partialUpdateConfig.partialUpdateMode && idName) { 154 judgeIdStart = newStatements.length; 155 } 156 let needCommon: boolean = false; 157 if (hasChainCall) { 158 if (partialUpdateConfig.partialUpdateMode) { 159 const commomComponentNode: ts.Statement[] = [ts.factory.createExpressionStatement( 160 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 161 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))]; 162 const immutableStatements: ts.Statement[] = []; 163 bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), commomComponentNode, 164 log, true, false, immutableStatements, false, componentAttrInfo); 165 needCommon = commomComponentNode.length > 1 || immutableStatements.length > 0; 166 if (needCommon) { 167 newStatements.push(createComponentCreationStatement(componentAttributes(COMPONENT_COMMON), 168 commomComponentNode, COMPONENT_COMMON, isGlobalBuilder, false, undefined, immutableStatements, 169 builderParamsResult, isRecycleComponent)); 170 } 171 } else { 172 newStatements.push(ts.factory.createExpressionStatement( 173 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 174 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); 175 bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), newStatements, log); 176 } 177 } 178 if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { 179 newStatements.push(createRecycleComponent(isGlobalBuilder)); 180 } 181 addCustomComponent(node, newStatements, customComponentNewExpression, log, name, componentNode, 182 isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult); 183 if (hasChainCall && (!partialUpdateConfig.partialUpdateMode || needCommon)) { 184 newStatements.push(ts.factory.createExpressionStatement( 185 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 186 ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); 187 } 188 if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { 189 newStatements.push(componentAttributes(COMPONENT_RECYCLE)); 190 } 191 if (partialUpdateConfig.partialUpdateMode && idName) { 192 newStatements.splice(judgeIdStart, newStatements.length - judgeIdStart, 193 ifRetakeId(newStatements.slice(judgeIdStart), idName)); 194 } 195 } 196} 197 198export function isRecycle(componentName: string): boolean { 199 return storedFileInfo.getCurrentArkTsFile().recycleComponents.has(componentName); 200} 201 202function createRecycleComponent(isGlobalBuilder: boolean): ts.Statement { 203 return createComponentCreationStatement(componentAttributes(COMPONENT_RECYCLE), 204 [ts.factory.createExpressionStatement( 205 createFunction(ts.factory.createIdentifier(COMPONENT_RECYCLE), 206 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null)) 207 ], COMPONENT_RECYCLE, isGlobalBuilder); 208} 209 210function componentAttributes(componentName: string): ts.Statement { 211 return ts.factory.createExpressionStatement( 212 ts.factory.createCallExpression( 213 ts.factory.createPropertyAccessExpression( 214 ts.factory.createIdentifier(componentName), 215 ts.factory.createIdentifier(COMPONENT_POP_FUNCTION) 216 ), undefined, [] 217 )); 218} 219 220function isHasChild(node: ts.CallExpression): boolean { 221 return node.arguments && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && 222 node.arguments[0].properties && node.arguments[0].properties.length > 0; 223} 224 225function isToChange(item: ts.PropertyAssignment, name: string): boolean { 226 const builderParamName: Set<string> = builderParamObjectCollection.get(name); 227 if (item.initializer && ts.isCallExpression(item.initializer) && builderParamName && 228 builderParamName.has(item.name.getText()) && 229 !/\.(bind|call|apply)/.test(item.initializer.getText())) { 230 return true; 231 } 232 return false; 233} 234 235function changeNodeFromCallToArrow(node: ts.CallExpression): ts.ConditionalExpression { 236 let builderBindThis: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); 237 if (ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 238 GLOBAL_CUSTOM_BUILDER_METHOD.has(node.expression.escapedText.toString())) { 239 builderBindThis = transferBuilderCall(ts.factory.createExpressionStatement(node), node.expression.escapedText.toString()); 240 } 241 return changeNodeFromCallToArrowDetermine(node, builderBindThis); 242} 243 244function changeNodeFromCallToArrowDetermine(node: ts.CallExpression, builderBindThis: ts.ExpressionStatement): ts.ConditionalExpression { 245 if (ts.isCallExpression(node)) { 246 return ts.factory.createConditionalExpression( 247 ts.factory.createBinaryExpression( 248 ts.factory.createTypeOfExpression(node), 249 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 250 ts.factory.createStringLiteral(FUNCTION) 251 ), 252 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 253 node, 254 ts.factory.createToken(ts.SyntaxKind.ColonToken), 255 ts.factory.createArrowFunction( 256 undefined, 257 undefined, 258 [], 259 undefined, 260 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 261 ts.factory.createBlock( 262 [builderBindThis], 263 true 264 ) 265 ) 266 ); 267 } 268} 269 270function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], 271 newNode: ts.NewExpression, log: LogInfo[], name: string, componentNode: ts.CallExpression, 272 isBuilder: boolean, isGlobalBuilder: boolean, isRecycleComponent: boolean, 273 componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult): void { 274 if (ts.isNewExpression(newNode)) { 275 const propertyArray: ts.ObjectLiteralElementLike[] = []; 276 validateCustomComponentPrams(componentNode, name, propertyArray, log, isBuilder); 277 addCustomComponentStatements(node, newStatements, newNode, name, propertyArray, componentNode, 278 isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult); 279 } 280} 281 282function addCustomComponentStatements(node: ts.ExpressionStatement, newStatements: ts.Statement[], 283 newNode: ts.NewExpression, name: string, props: ts.ObjectLiteralElementLike[], 284 componentNode: ts.CallExpression, isBuilder: boolean, isGlobalBuilder: boolean, 285 isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo, 286 builderParamsResult: BuilderParamsResult): void { 287 if (!partialUpdateConfig.partialUpdateMode) { 288 const id: string = componentInfo.id.toString(); 289 newStatements.push(createFindChildById(id, name, isBuilder), createCustomComponentIfStatement(id, 290 ts.factory.updateExpressionStatement(node, createViewCreate(newNode)), 291 ts.factory.createObjectLiteralExpression(props, true), name)); 292 } else { 293 newStatements.push(createCustomComponent(newNode, name, componentNode, isGlobalBuilder, isBuilder, 294 isRecycleComponent, componentAttrInfo, builderParamsResult)); 295 } 296} 297 298function createChildElmtId(node: ts.CallExpression, name: string): ts.PropertyAssignment[] { 299 const propsAndObjectLinks: string[] = []; 300 const childParam: ts.PropertyAssignment[] = []; 301 if (propCollection.get(name)) { 302 propsAndObjectLinks.push(...propCollection.get(name)); 303 } 304 if (objectLinkCollection.get(name)) { 305 propsAndObjectLinks.push(...objectLinkCollection.get(name)); 306 } 307 if (projectConfig.optLazyForEach && storedFileInfo.processLazyForEach && stateCollection.get(name)) { 308 propsAndObjectLinks.push(...stateCollection.get(name)); 309 } 310 if (node.arguments[0].properties) { 311 node.arguments[0].properties.forEach(item => { 312 if (ts.isIdentifier(item.name) && propsAndObjectLinks.includes(item.name.escapedText.toString())) { 313 childParam.push(item); 314 } 315 }); 316 } 317 return childParam; 318} 319 320function createCustomComponent(newNode: ts.NewExpression, name: string, componentNode: ts.CallExpression, 321 isGlobalBuilder: boolean, isBuilder: boolean, isRecycleComponent: boolean, 322 componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult): ts.Block { 323 let componentParameter: ts.ObjectLiteralExpression; 324 if (componentNode.arguments && componentNode.arguments.length) { 325 componentParameter = ts.factory.createObjectLiteralExpression(createChildElmtId(componentNode, name), true); 326 } else { 327 componentParameter = ts.factory.createObjectLiteralExpression([], false); 328 } 329 const arrowArgArr: ts.ParameterDeclaration[] = [ 330 ts.factory.createParameterDeclaration(undefined, undefined, 331 ts.factory.createIdentifier(ELMTID) 332 ), 333 ts.factory.createParameterDeclaration(undefined, undefined, 334 ts.factory.createIdentifier(ISINITIALRENDER) 335 ) 336 ]; 337 const arrowBolck: ts.Statement[] = [ 338 projectConfig.optLazyForEach && storedFileInfo.processLazyForEach ? createCollectElmtIdNode() : undefined, 339 createIfCustomComponent(newNode, componentNode, componentParameter, name, isGlobalBuilder, 340 isBuilder, isRecycleComponent, componentAttrInfo) 341 ]; 342 if (isRecycleComponent) { 343 arrowArgArr.push(ts.factory.createParameterDeclaration( 344 undefined, undefined, ts.factory.createIdentifier(RECYCLE_NODE), 345 undefined, undefined, ts.factory.createNull() 346 )); 347 } else if (partialUpdateConfig.optimizeComponent && isGlobalBuilder && 348 builderParamsResult && builderParamsResult.firstParam) { 349 const paramName: ts.Identifier = builderParamsResult.firstParam.name as ts.Identifier; 350 arrowArgArr.push(ts.factory.createParameterDeclaration(undefined, undefined, 351 paramName, undefined, undefined, ts.factory.createIdentifier(`__${paramName.escapedText.toString()}__`) 352 )); 353 } 354 if (isRecycleComponent || !partialUpdateConfig.optimizeComponent) { 355 arrowBolck.unshift(createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID)); 356 arrowBolck.push(createViewStackProcessorStatement(STOPGETACCESSRECORDING)); 357 } 358 const observeArgArr: ts.Node[] = [ 359 ts.factory.createArrowFunction(undefined, undefined, arrowArgArr, undefined, 360 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 361 ts.factory.createBlock(arrowBolck, true)) 362 ]; 363 if (isRecycleComponent) { 364 componentAttrInfo.reuseId ? observeArgArr.unshift(componentAttrInfo.reuseId) : 365 observeArgArr.unshift(ts.factory.createStringLiteral(name)); 366 } else if (partialUpdateConfig.optimizeComponent) { 367 observeArgArr.push(componentPop(name)); 368 } 369 return ts.factory.createBlock( 370 [ 371 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 372 ts.factory.createPropertyAccessExpression(isGlobalBuilder ? 373 ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), 374 isRecycleComponent ? 375 ts.factory.createIdentifier(OBSERVE_RECYCLE_COMPONENT_CREATION) : 376 ts.factory.createIdentifier(partialUpdateConfig.optimizeComponent ? 377 OBSERVECOMPONENTCREATION2 : OBSERVECOMPONENTCREATION) 378 ), 379 undefined, observeArgArr as ts.Expression[])) 380 ], true); 381} 382 383function componentPop(name: string): ts.ObjectLiteralExpression { 384 return ts.factory.createObjectLiteralExpression( 385 [ts.factory.createPropertyAssignment( 386 ts.factory.createIdentifier(NAME), 387 ts.factory.createStringLiteral(name) 388 )], 389 false 390 ); 391} 392 393export function assignComponentParams(componentNode: ts.CallExpression, 394 isBuilder: boolean = false): ts.VariableStatement { 395 const isParamsLambda: boolean = true; 396 const [keyArray, valueArray]: [ts.Node[], ts.Node[]] = splitComponentParams(componentNode, isBuilder, isParamsLambda); 397 let integrateParams: boolean = false; 398 if (!keyArray.length && componentNode.arguments && componentNode.arguments.length > 0 && componentNode.arguments[0]) { 399 integrateParams = true; 400 } 401 return ts.factory.createVariableStatement( 402 undefined, 403 ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration( 404 ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION), 405 undefined, 406 undefined, 407 ts.factory.createArrowFunction( 408 undefined, 409 undefined, 410 [], 411 undefined, 412 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 413 ts.factory.createBlock( 414 [ts.factory.createReturnStatement( 415 integrateParams ? componentNode.arguments[0] : ts.factory.createObjectLiteralExpression( 416 reWriteComponentParams(keyArray, valueArray), 417 true 418 ) 419 )], 420 true 421 ) 422 ) 423 )], 424 ts.NodeFlags.Let 425 )); 426} 427 428function reWriteComponentParams(keyArray: ts.Node[], valueArray: ts.Node[]): (ts.PropertyAssignment | 429 ts.ShorthandPropertyAssignment)[] { 430 const returnProperties: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = []; 431 keyArray.forEach((item: ts.Identifier, index: number) => { 432 if (!valueArray[index]) { 433 returnProperties.push(ts.factory.createShorthandPropertyAssignment( 434 item, 435 undefined 436 )); 437 } else { 438 returnProperties.push(ts.factory.createPropertyAssignment( 439 item, 440 valueArray[index] as ts.Identifier 441 )); 442 } 443 }); 444 return returnProperties; 445} 446 447function splitComponentParams(componentNode: ts.CallExpression, isBuilder: boolean, isParamsLambda: boolean): [ts.Node[], ts.Node[]] { 448 const keyArray: ts.Node[] = []; 449 const valueArray: ts.Node[] = []; 450 if (componentNode.arguments && componentNode.arguments.length > 0 && 451 ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { 452 componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { 453 const newPropertyItem: ts.PropertyAssignment = 454 isProperty(propertyItem) ? createReference(propertyItem, [], isBuilder, isParamsLambda) : propertyItem; 455 keyArray.push(newPropertyItem.name); 456 valueArray.push(newPropertyItem.initializer); 457 }); 458 } 459 return [keyArray, valueArray]; 460} 461 462function createIfCustomComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, 463 componentParameter: ts.ObjectLiteralExpression, name: string, isGlobalBuilder: boolean, isBuilder: boolean, 464 isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo): ts.IfStatement { 465 return ts.factory.createIfStatement( 466 ts.factory.createIdentifier(ISINITIALRENDER), 467 ts.factory.createBlock( 468 [ componentParamDetachment(newNode, isRecycleComponent), 469 isRecycleComponent ? createNewRecycleComponent(newNode, componentNode, name, componentAttrInfo) : 470 createNewComponent(COMPONENT_CALL), 471 assignComponentParams(componentNode, isBuilder), 472 assignmentFunction(COMPONENT_CALL) 473 ], true), 474 ts.factory.createBlock( 475 [ts.factory.createExpressionStatement(ts.factory.createCallExpression( 476 ts.factory.createPropertyAccessExpression(isGlobalBuilder ? 477 ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), 478 ts.factory.createIdentifier(UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID) 479 ), undefined, 480 [ts.factory.createIdentifier(ELMTID), componentParameter]))], true) 481 ); 482} 483 484export function assignmentFunction(componeParamName: string): ts.ExpressionStatement { 485 return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( 486 ts.factory.createPropertyAccessExpression( 487 ts.factory.createIdentifier(componeParamName), 488 ts.factory.createIdentifier(COMPONENT_PARAMS_FUNCTION) 489 ), 490 ts.factory.createToken(ts.SyntaxKind.EqualsToken), 491 ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION) 492 )) 493} 494 495function componentParamDetachment(newNode: ts.NewExpression, isRecycleComponent: boolean): ts.VariableStatement { 496 return ts.factory.createVariableStatement( 497 undefined, 498 ts.factory.createVariableDeclarationList( 499 [ts.factory.createVariableDeclaration( 500 ts.factory.createIdentifier(COMPONENT_CALL), 501 undefined, 502 undefined, 503 isRecycleComponent ? ts.factory.createConditionalExpression( 504 ts.factory.createIdentifier(RECYCLE_NODE), 505 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 506 ts.factory.createIdentifier(RECYCLE_NODE), 507 ts.factory.createToken(ts.SyntaxKind.ColonToken), 508 newNode) : newNode 509 )], 510 ts.NodeFlags.Let 511 )); 512} 513 514function createNewComponent(componeParamName: string): ts.Statement { 515 return ts.factory.createExpressionStatement( 516 ts.factory.createCallExpression( 517 ts.factory.createPropertyAccessExpression( 518 ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), 519 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) 520 ), undefined, [ts.factory.createIdentifier(componeParamName)])); 521} 522 523function createNewRecycleComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, 524 name: string, componentAttrInfo: ComponentAttrInfo): ts.Statement { 525 let argNode: ts.Expression[] = []; 526 const componentParam: ts.PropertyAssignment[] = []; 527 if (componentNode.arguments && componentNode.arguments.length > 0 && 528 ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { 529 componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { 530 const newPropertyItem: ts.PropertyAssignment = createReference(propertyItem, [], false, false, true); 531 componentParam.push(newPropertyItem); 532 }); 533 argNode = [ts.factory.createObjectLiteralExpression(componentParam, false)]; 534 } else { 535 argNode = [ts.factory.createObjectLiteralExpression([], false)]; 536 } 537 const recycleNode: ts.CallExpression = ts.factory.createCallExpression( 538 createRecyclePropertyNode(ABOUT_TO_REUSE), undefined, argNode); 539 return ts.factory.createExpressionStatement( 540 ts.factory.createCallExpression( 541 ts.factory.createPropertyAccessExpression( 542 ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), 543 ts.factory.createIdentifier(COMPONENT_CREATE_RECYCLE) 544 ), undefined, 545 [ 546 ts.factory.createIdentifier(COMPONENT_CALL), 547 ts.factory.createBinaryExpression( 548 ts.factory.createIdentifier(RECYCLE_NODE), 549 ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), 550 ts.factory.createNull() 551 ), 552 componentAttrInfo.reuseId ? componentAttrInfo.reuseId as ts.Expression : 553 ts.factory.createStringLiteral(name), 554 ts.factory.createArrowFunction(undefined, undefined, [], undefined, 555 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 556 ts.factory.createBlock([ 557 ts.factory.createIfStatement( 558 ts.factory.createBinaryExpression( 559 ts.factory.createIdentifier(RECYCLE_NODE), 560 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 561 ts.factory.createBinaryExpression( 562 ts.factory.createTypeOfExpression( 563 createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION)), 564 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 565 ts.factory.createStringLiteral(FUNCTION) 566 )), 567 ts.factory.createBlock([ 568 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 569 createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION), 570 undefined, 571 [] 572 )) 573 ], true), 574 ts.factory.createBlock( 575 [ 576 ts.factory.createIfStatement(ts.factory.createBinaryExpression( 577 createRecyclePropertyNode(ABOUT_TO_REUSE), ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 578 ts.factory.createBinaryExpression( 579 ts.factory.createTypeOfExpression(createRecyclePropertyNode(ABOUT_TO_REUSE)), 580 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 581 ts.factory.createStringLiteral(FUNCTION) 582 )), 583 ts.factory.createBlock([ts.factory.createExpressionStatement(recycleNode)], true)), 584 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 585 createRecyclePropertyNode(COMPONENT_RERENDER_FUNCTION), undefined, [] 586 )) 587 ], 588 true 589 ) 590 )], true)) 591 ])); 592} 593 594function createRecyclePropertyNode(recycleFunctionName: string): ts.PropertyAccessExpression { 595 return ts.factory.createPropertyAccessExpression( 596 ts.factory.createIdentifier(RECYCLE_NODE), ts.factory.createIdentifier(recycleFunctionName)); 597} 598 599function validateCustomComponentPrams(node: ts.CallExpression, name: string, 600 props: ts.ObjectLiteralElementLike[], log: LogInfo[], isBuilder: boolean): void { 601 const curChildProps: Set<string> = new Set([]); 602 const nodeArguments: ts.NodeArray<ts.Expression> = node.arguments; 603 const linkSet: Set<string> = getCollectionSet(name, linkCollection); 604 if (nodeArguments && nodeArguments.length === 1 && 605 ts.isObjectLiteralExpression(nodeArguments[0])) { 606 const nodeArgument: ts.ObjectLiteralExpression = nodeArguments[0] as ts.ObjectLiteralExpression; 607 nodeArgument.properties.forEach(item => { 608 if (item.name && ts.isIdentifier(item.name)) { 609 curChildProps.add(item.name.escapedText.toString()); 610 } 611 validateStateManagement(item, name, log, isBuilder); 612 if (isNonThisProperty(item, linkSet)) { 613 if (isToChange(item as ts.PropertyAssignment, name)) { 614 item = ts.factory.updatePropertyAssignment(item as ts.PropertyAssignment, 615 item.name, changeNodeFromCallToArrow(item.initializer)); 616 } 617 props.push(item); 618 } 619 }); 620 } 621 validateInitDecorator(node, name, curChildProps, log); 622 validateMandatoryToInitViaParam(node, name, curChildProps, log); 623} 624 625function getCustomComponentNode(node: ts.ExpressionStatement): ts.CallExpression { 626 let temp: any = node.expression; 627 let child: any = null; 628 let componentNode: any = null; 629 while (temp) { 630 if (ts.isIdentifier(temp)) { 631 child = temp; 632 break; 633 } 634 temp = temp.expression; 635 } 636 if (child) { 637 let parent = child.parent; 638 while (parent) { 639 if (ts.isExpressionStatement(parent)) { 640 break; 641 } 642 if (ts.isCallExpression(parent) || ts.isEtsComponentExpression(parent)) { 643 componentNode = parent; 644 break; 645 } 646 parent = parent.parent; 647 } 648 } 649 return componentNode; 650} 651 652function getCollectionSet(name: string, collection: Map<string, Set<string>>): Set<string> { 653 if (!collection) { 654 return new Set([]); 655 } 656 return collection.get(name) || new Set([]); 657} 658 659function isThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean { 660 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 661 propertySet.has(node.name.escapedText.toString())) { 662 return true; 663 } 664 return false; 665} 666 667function isNonThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean { 668 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 669 (node.initializer.escapedText && node.initializer.escapedText.includes('$') || 670 ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && 671 node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && 672 ts.isIdentifier(node.initializer.name) && node.initializer.name.escapedText.toString().includes('$'))) { 673 return false; 674 } 675 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 676 !propertySet.has(node.name.escapedText.toString())) { 677 return true; 678 } 679 return false; 680} 681 682function validateStateManagement(node: ts.ObjectLiteralElementLike, customComponentName: string, 683 log: LogInfo[], isBuilder: boolean): void { 684 validateForbiddenToInitViaParam(node, customComponentName, log); 685 checkFromParentToChild(node, customComponentName, log, isBuilder); 686} 687 688function checkFromParentToChild(node: ts.ObjectLiteralElementLike, customComponentName: string, 689 log: LogInfo[], isBuilder: boolean): void { 690 let propertyName: string; 691 if (ts.isIdentifier(node.name)) { 692 propertyName = node.name.escapedText.toString(); 693 } 694 const curPropertyKind: string = getPropertyDecoratorKind(propertyName, customComponentName); 695 let parentPropertyName: string; 696 if (curPropertyKind) { 697 if (isInitFromParent(node)) { 698 parentPropertyName = 699 getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log); 700 let parentPropertyKind: string = curPropMap.get(parentPropertyName); 701 if (!parentPropertyKind) { 702 parentPropertyKind = COMPONENT_NON_DECORATOR; 703 } 704 if (parentPropertyKind && !isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { 705 validateIllegalInitFromParent( 706 node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log); 707 } 708 } else if (isInitFromLocal(node) && ts.isPropertyAssignment(node) && 709 curPropertyKind !== COMPONENT_OBJECT_LINK_DECORATOR) { 710 if (!isCorrectInitFormParent(COMPONENT_NON_DECORATOR, curPropertyKind)) { 711 validateIllegalInitFromParent(node, propertyName, curPropertyKind, 712 node.initializer.getText(), COMPONENT_NON_DECORATOR, log); 713 } 714 } else if (curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR && node.initializer && 715 (ts.isPropertyAccessExpression(node.initializer) || 716 ts.isElementAccessExpression(node.initializer) || ts.isIdentifier(node.initializer))) { 717 return; 718 } else { 719 parentPropertyName = 720 getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log) || propertyName; 721 const parentPropertyKind = COMPONENT_NON_DECORATOR; 722 if (!isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { 723 if (isBuilder && judgeStructAssigned$$(node)) { 724 log.push({ 725 type: LogType.WARN, 726 message: `Unrecognized property '${parentPropertyName}', make sure it can be assigned to ` + 727 `${curPropertyKind} property '${propertyName}' by yourself.`, 728 // @ts-ignore 729 pos: node.initializer ? node.initializer.getStart() : node.getStart() 730 }); 731 } else { 732 validateIllegalInitFromParent( 733 node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log, LogType.WARN); 734 } 735 } 736 } 737 } 738} 739 740function judgeStructAssigned$$(node: ts.ObjectLiteralElementLike): boolean { 741 return partialUpdateConfig.partialUpdateMode && node.initializer && 742 ts.isPropertyAccessExpression(node.initializer) && 743 node.initializer.expression && ts.isIdentifier(node.initializer.expression) && 744 node.initializer.expression.escapedText.toString() === $$; 745} 746 747function isInitFromParent(node: ts.ObjectLiteralElementLike): boolean { 748 if (ts.isPropertyAssignment(node) && node.initializer) { 749 if (ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && 750 node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && 751 ts.isIdentifier(node.initializer.name)) { 752 return true; 753 } else if (ts.isIdentifier(node.initializer) && 754 matchStartWithDollar(node.initializer.getText())) { 755 return true; 756 } 757 } 758} 759 760function isInitFromLocal(node: ts.ObjectLiteralElementLike): boolean { 761 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer) && 762 !matchStartWithDollar(node.initializer.getText())) { 763 return true; 764 } 765} 766 767function getParentPropertyName(node: ts.PropertyAssignment, curPropertyKind: string, 768 log: LogInfo[]): string { 769 const initExpression: ts.Expression = node.initializer; 770 if (!initExpression) { 771 return undefined; 772 } 773 let parentPropertyName: string = initExpression.getText(); 774 if (curPropertyKind === COMPONENT_LINK_DECORATOR) { 775 // @ts-ignore 776 const initName: ts.Identifier = initExpression.name || initExpression; 777 if (hasDollar(initExpression)) { 778 parentPropertyName = initName.getText().replace(/^\$/, ''); 779 } else { 780 parentPropertyName = initName.getText(); 781 } 782 } else { 783 if (hasDollar(initExpression)) { 784 validateNonLinkWithDollar(node, log); 785 } else { 786 // @ts-ignore 787 if (node.initializer && node.initializer.name) { 788 parentPropertyName = node.initializer.name.getText(); 789 } 790 } 791 } 792 793 return parentPropertyName; 794} 795 796function isCorrectInitFormParent(parent: string, child: string): boolean { 797 switch (child) { 798 case COMPONENT_STATE_DECORATOR: 799 case COMPONENT_PROP_DECORATOR: 800 case COMPONENT_PROVIDE_DECORATOR: 801 return true; 802 case COMPONENT_NON_DECORATOR: 803 if ([COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR, COMPONENT_LINK_DECORATOR, COMPONENT_PROP_DECORATOR, 804 COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR].includes(parent)) { 805 return true; 806 } 807 break; 808 case COMPONENT_LINK_DECORATOR: 809 case COMPONENT_OBJECT_LINK_DECORATOR: 810 return ![COMPONENT_NON_DECORATOR].includes(parent); 811 } 812 return false; 813} 814 815function getPropertyDecoratorKind(propertyName: string, customComponentName: string): string { 816 for (const item of decoractorMap.entries()) { 817 if (getCollectionSet(customComponentName, item[1]).has(propertyName)) { 818 return item[0]; 819 } 820 } 821} 822 823function createFindChildById(id: string, name: string, isBuilder: boolean = false): ts.VariableStatement { 824 return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( 825 [ts.factory.createVariableDeclaration(ts.factory.createIdentifier( 826 `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, ts.factory.createTypeReferenceNode( 827 ts.factory.createIdentifier(name)), 828 ts.factory.createConditionalExpression( 829 ts.factory.createParenthesizedExpression( 830 ts.factory.createBinaryExpression( 831 createConditionParent(isBuilder), 832 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 833 ts.factory.createPropertyAccessExpression( 834 createConditionParent(isBuilder), 835 ts.factory.createIdentifier(CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID) 836 ))), ts.factory.createToken(ts.SyntaxKind.QuestionToken), 837 ts.factory.createAsExpression(ts.factory.createCallExpression( 838 ts.factory.createPropertyAccessExpression(createConditionParent(isBuilder), 839 ts.factory.createIdentifier(`${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, 840 [isBuilder ? ts.factory.createCallExpression(ts.factory.createIdentifier(GENERATE_ID), 841 undefined, []) : ts.factory.createStringLiteral(id)]), 842 ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(name))), 843 ts.factory.createToken(ts.SyntaxKind.ColonToken), 844 ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)))], ts.NodeFlags.Let)); 845} 846 847export function createConditionParent(isBuilder: boolean): ts.ParenthesizedExpression | ts.ThisExpression { 848 return isBuilder ? ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(); 849} 850 851function createCustomComponentIfStatement(id: string, node: ts.ExpressionStatement, 852 newObjectLiteralExpression: ts.ObjectLiteralExpression, parentName: string): ts.IfStatement { 853 const viewName: string = `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`; 854 return ts.factory.createIfStatement(ts.factory.createBinaryExpression( 855 ts.factory.createIdentifier(viewName), 856 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken), 857 ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_UNDEFINED}`)), 858 ts.factory.createBlock([node], true), 859 ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( 860 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier( 861 viewName), ts.factory.createIdentifier( 862 `${COMPONENT_CONSTRUCTOR_UPDATE_PARAMS}`)), undefined, [newObjectLiteralExpression])), 863 isStaticViewCollection.get(parentName) ? createStaticIf(viewName) : undefined, 864 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 865 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(`${BASE_COMPONENT_NAME}`), 866 ts.factory.createIdentifier(`${COMPONENT_CREATE_FUNCTION}`)), undefined, 867 [ts.factory.createIdentifier(viewName)]))], true)); 868} 869 870function createStaticIf(name: string): ts.IfStatement { 871 return ts.factory.createIfStatement(ts.factory.createPrefixUnaryExpression( 872 ts.SyntaxKind.ExclamationToken, ts.factory.createCallExpression( 873 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), 874 ts.factory.createIdentifier(CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION)), undefined, [])), 875 ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( 876 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), 877 ts.factory.createIdentifier(CUSTOM_COMPONENT_MARK_STATIC_FUNCTION)), 878 undefined, []))], true), undefined); 879} 880 881function hasDollar(initExpression: ts.Expression): boolean { 882 if (ts.isPropertyAccessExpression(initExpression) && 883 matchStartWithDollar(initExpression.name.getText())) { 884 return true; 885 } else if (ts.isIdentifier(initExpression) && matchStartWithDollar(initExpression.getText())) { 886 return true; 887 } else { 888 return false; 889 } 890} 891 892function matchStartWithDollar(name: string): boolean { 893 return /^\$/.test(name); 894} 895 896function validateForbiddenToInitViaParam(node: ts.ObjectLiteralElementLike, 897 customComponentName: string, log: LogInfo[]): void { 898 const forbiddenToInitViaParamSet: Set<string> = new Set([ 899 ...getCollectionSet(customComponentName, storageLinkCollection), 900 ...getCollectionSet(customComponentName, storagePropCollection), 901 ...getCollectionSet(customComponentName, consumeCollection) 902 ]); 903 const localStorageSet: Set<string> = new Set(); 904 getLocalStorageCollection(customComponentName, localStorageSet); 905 if (isThisProperty(node, forbiddenToInitViaParamSet) || isThisProperty(node, localStorageSet)) { 906 log.push({ 907 type: LogType.ERROR, 908 message: `Property '${node.name.getText()}' in the custom component '${customComponentName}'` + 909 ` cannot initialize here (forbidden to specify).`, 910 pos: node.name.getStart() 911 }); 912 } 913} 914 915function validateMandatoryToInitViaParam(node: ts.CallExpression, customComponentName: string, 916 curChildProps: Set<string>, log: LogInfo[]): void { 917 const mandatoryToInitViaParamSet: Set<string> = new Set([ 918 ...getCollectionSet(customComponentName, linkCollection), 919 ...getCollectionSet(customComponentName, objectLinkCollection)]); 920 mandatoryToInitViaParamSet.forEach(item => { 921 if (item && !curChildProps.has(item)) { 922 log.push({ 923 type: LogType.ERROR, 924 message: `Property '${item}' in the custom component '${customComponentName}'` + 925 ` is missing (mandatory to specify).`, 926 pos: node.getStart() 927 }); 928 } 929 }); 930} 931 932function validateInitDecorator(node: ts.CallExpression, customComponentName: string, 933 curChildProps: Set<string>, log: LogInfo[]): void { 934 const mandatoryToInitViaParamSet: Set<string> = new Set([ 935 ...getCollectionSet(customComponentName, builderParamObjectCollection), 936 ...getCollectionSet(customComponentName, propCollection)]); 937 const decoratorVariable: Set<string> = new Set([ 938 ...(builderParamInitialization.get(customComponentName) || []), 939 ...(propInitialization.get(customComponentName) || [])]); 940 mandatoryToInitViaParamSet.forEach((item: string) => { 941 if (item && !curChildProps.has(item) && decoratorVariable && decoratorVariable.has(item)) { 942 log.push({ 943 type: LogType.ERROR, 944 message: `Property '${item}' must be initialized through the component constructor.`, 945 pos: node.getStart() 946 }); 947 } 948 }); 949} 950 951function validateIllegalInitFromParent(node: ts.ObjectLiteralElementLike, propertyName: string, 952 curPropertyKind: string, parentPropertyName: string, parentPropertyKind: string, 953 log: LogInfo[], inputType: LogType = undefined): void { 954 let type: LogType = LogType.ERROR; 955 if (inputType) { 956 type = inputType; 957 } else if ([COMPONENT_STATE_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR].includes( 958 parentPropertyKind) && curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR) { 959 type = LogType.WARN; 960 } 961 log.push({ 962 type: type, 963 message: `The ${parentPropertyKind} property '${parentPropertyName}' cannot be assigned to ` + 964 `the ${curPropertyKind} property '${propertyName}'.`, 965 // @ts-ignore 966 pos: node.initializer ? node.initializer.getStart() : node.getStart() 967 }); 968} 969 970function validateNonLinkWithDollar(node: ts.PropertyAssignment, log: LogInfo[]): void { 971 log.push({ 972 type: LogType.ERROR, 973 message: `Property '${node.name.getText()}' cannot initialize` + 974 ` using '$' to create a reference to a variable.`, 975 pos: node.initializer.getStart() 976 }); 977} 978