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 regularInitialization, 76 stateInitialization, 77 provideInitialization, 78 privateCollection, 79 componentCollection 80} from './validate_ui_syntax'; 81import { 82 curPropMap, 83 createViewCreate, 84 createCustomComponentNewExpression, 85 isLocalStorageParameter, 86 isBasicType 87} from './process_component_member'; 88import { 89 LogType, 90 LogInfo, 91 componentInfo, 92 storedFileInfo 93} from './utils'; 94import { 95 bindComponentAttr, 96 parentConditionalExpression, 97 createComponentCreationStatement, 98 createFunction, 99 ComponentAttrInfo, 100 ifRetakeId, 101 transferBuilderCall, 102 createCollectElmtIdNode, 103 createViewStackProcessorStatement, 104 BuilderParamsResult 105} from './process_component_build'; 106import { 107 partialUpdateConfig, 108 projectConfig, 109 globalProgram 110} from '../main'; 111import { 112 GLOBAL_CUSTOM_BUILDER_METHOD 113} from './component_map'; 114import { 115 createReference, 116 isProperty 117} from './process_component_class'; 118import processStructComponentV2, { StructInfo, ParamDecoratorInfo } from './process_struct_componentV2'; 119import constantDefine from './constant_define'; 120import createAstNodeUtils from './create_ast_node_utils'; 121 122let decoractorMap: Map<string, Map<string, Set<string>>>; 123 124export function processCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], 125 log: LogInfo[], name: string, isBuilder: boolean = false, isGlobalBuilder: boolean = false, 126 idName: ts.Expression = undefined, builderParamsResult: BuilderParamsResult = null): void { 127 decoractorMap = new Map( 128 [[COMPONENT_STATE_DECORATOR, stateCollection], 129 [COMPONENT_LINK_DECORATOR, linkCollection], 130 [COMPONENT_PROP_DECORATOR, propCollection], 131 [COMPONENT_NON_DECORATOR, regularCollection], 132 [COMPONENT_PROVIDE_DECORATOR, provideCollection], 133 [COMPONENT_OBJECT_LINK_DECORATOR, objectLinkCollection]]); 134 const componentNode: ts.CallExpression = getCustomComponentNode(node); 135 if (componentNode) { 136 const isRecycleComponent: boolean = isRecycle(name); 137 const hasChainCall: boolean = componentNode.parent && 138 ts.isPropertyAccessExpression(componentNode.parent); 139 let ischangeNode: boolean = false; 140 let customComponentNewExpression: ts.NewExpression = createCustomComponentNewExpression( 141 componentNode, name, isBuilder, isGlobalBuilder); 142 let argumentsArray: ts.PropertyAssignment[]; 143 const componentAttrInfo: ComponentAttrInfo = { reuseId: null, hasIdAttr: false, attrCount: 0 }; 144 if (isHasChild(componentNode)) { 145 // @ts-ignore 146 argumentsArray = componentNode.arguments[0].properties.slice(); 147 argumentsArray.forEach((item: ts.PropertyAssignment, index: number) => { 148 if (isToChange(item, name)) { 149 ischangeNode = true; 150 const propertyAssignmentNode: ts.PropertyAssignment = ts.factory.updatePropertyAssignment( 151 item, item.name, changeNodeFromCallToArrow(item.initializer as ts.CallExpression)); 152 argumentsArray.splice(index, 1, propertyAssignmentNode); 153 } 154 }); 155 if (ischangeNode) { 156 const newNode: ts.ExpressionStatement = ts.factory.updateExpressionStatement(node, 157 ts.factory.createNewExpression(componentNode.expression, componentNode.typeArguments, 158 [ts.factory.createObjectLiteralExpression(argumentsArray, true)])); 159 customComponentNewExpression = createCustomComponentNewExpression( 160 newNode.expression as ts.CallExpression, name, isBuilder); 161 } 162 } 163 let judgeIdStart: number; 164 if (partialUpdateConfig.partialUpdateMode && idName) { 165 judgeIdStart = newStatements.length; 166 } 167 let needCommon: boolean = false; 168 if (hasChainCall) { 169 if (partialUpdateConfig.partialUpdateMode) { 170 const commomComponentNode: ts.Statement[] = [ts.factory.createExpressionStatement( 171 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 172 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))]; 173 const immutableStatements: ts.Statement[] = []; 174 bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), commomComponentNode, 175 log, true, false, immutableStatements, false, componentAttrInfo); 176 needCommon = commomComponentNode.length > 1 || immutableStatements.length > 0; 177 if (componentAttrInfo.hasIdAttr && componentAttrInfo.attrCount === 1) { 178 commomComponentNode[0] = createCommonIdAttrNode(); 179 } 180 if (needCommon) { 181 newStatements.push(createComponentCreationStatement(componentAttributes(COMPONENT_COMMON), 182 commomComponentNode, COMPONENT_COMMON, isGlobalBuilder, false, undefined, immutableStatements, 183 builderParamsResult, isRecycleComponent)); 184 } 185 } else { 186 newStatements.push(ts.factory.createExpressionStatement( 187 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 188 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); 189 bindComponentAttr(node, ts.factory.createIdentifier(COMPONENT_COMMON), newStatements, log); 190 } 191 } 192 if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { 193 newStatements.push(createRecycleComponent(isGlobalBuilder)); 194 } 195 addCustomComponent(node, newStatements, customComponentNewExpression, log, name, componentNode, 196 isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult); 197 if (hasChainCall && (!partialUpdateConfig.partialUpdateMode || needCommon)) { 198 newStatements.push(ts.factory.createExpressionStatement( 199 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 200 ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); 201 } 202 if (isRecycleComponent && partialUpdateConfig.partialUpdateMode) { 203 newStatements.push(componentAttributes(COMPONENT_RECYCLE)); 204 } 205 if (partialUpdateConfig.partialUpdateMode && idName) { 206 newStatements.splice(judgeIdStart, newStatements.length - judgeIdStart, 207 ifRetakeId(newStatements.slice(judgeIdStart), idName)); 208 } 209 } 210} 211 212function createCommonIdAttrNode(): ts.ExpressionStatement { 213 return ts.factory.createExpressionStatement( 214 createFunction(ts.factory.createIdentifier(COMPONENT_COMMON), 215 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), 216 // @ts-ignore 217 [ts.factory.createTrue()])); 218} 219 220export function isRecycle(componentName: string): boolean { 221 return storedFileInfo.getCurrentArkTsFile().recycleComponents.has(componentName); 222} 223 224function createRecycleComponent(isGlobalBuilder: boolean): ts.Statement { 225 return createComponentCreationStatement(componentAttributes(COMPONENT_RECYCLE), 226 [ts.factory.createExpressionStatement( 227 createFunction(ts.factory.createIdentifier(COMPONENT_RECYCLE), 228 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null)) 229 ], COMPONENT_RECYCLE, isGlobalBuilder); 230} 231 232function componentAttributes(componentName: string): ts.Statement { 233 return ts.factory.createExpressionStatement( 234 ts.factory.createCallExpression( 235 ts.factory.createPropertyAccessExpression( 236 ts.factory.createIdentifier(componentName), 237 ts.factory.createIdentifier(COMPONENT_POP_FUNCTION) 238 ), undefined, [] 239 )); 240} 241 242function isHasChild(node: ts.CallExpression): boolean { 243 return node.arguments && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && 244 node.arguments[0].properties && node.arguments[0].properties.length > 0; 245} 246 247function isToChange(item: ts.PropertyAssignment, name: string): boolean { 248 const builderParamName: Set<string> = builderParamObjectCollection.get(name); 249 if (item.initializer && ts.isCallExpression(item.initializer) && builderParamName && 250 builderParamName.has(item.name.getText()) && 251 !/\.(bind|call|apply)/.test(item.initializer.getText())) { 252 return true; 253 } 254 return false; 255} 256 257function changeNodeFromCallToArrow(node: ts.CallExpression): ts.ConditionalExpression { 258 let builderBindThis: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); 259 if (ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 260 GLOBAL_CUSTOM_BUILDER_METHOD.has(node.expression.escapedText.toString())) { 261 builderBindThis = transferBuilderCall(ts.factory.createExpressionStatement(node), node.expression.escapedText.toString()); 262 } 263 return changeNodeFromCallToArrowDetermine(node, builderBindThis); 264} 265 266function changeNodeFromCallToArrowDetermine(node: ts.CallExpression, builderBindThis: ts.ExpressionStatement): ts.ConditionalExpression { 267 if (ts.isCallExpression(node)) { 268 return ts.factory.createConditionalExpression( 269 ts.factory.createBinaryExpression( 270 ts.factory.createTypeOfExpression(node), 271 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 272 ts.factory.createStringLiteral(FUNCTION) 273 ), 274 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 275 node, 276 ts.factory.createToken(ts.SyntaxKind.ColonToken), 277 ts.factory.createArrowFunction( 278 undefined, 279 undefined, 280 [], 281 undefined, 282 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 283 ts.factory.createBlock( 284 [builderBindThis], 285 true 286 ) 287 ) 288 ); 289 } 290 return undefined; 291} 292 293function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], 294 newNode: ts.NewExpression, log: LogInfo[], name: string, componentNode: ts.CallExpression, 295 isBuilder: boolean, isGlobalBuilder: boolean, isRecycleComponent: boolean, 296 componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult): void { 297 if (ts.isNewExpression(newNode)) { 298 const propertyArray: ts.ObjectLiteralElementLike[] = []; 299 validateCustomComponentPrams(componentNode, name, propertyArray, log, isBuilder); 300 addCustomComponentStatements(node, newStatements, newNode, name, propertyArray, componentNode, 301 isBuilder, isGlobalBuilder, isRecycleComponent, componentAttrInfo, builderParamsResult, log); 302 } 303} 304 305function addCustomComponentStatements(node: ts.ExpressionStatement, newStatements: ts.Statement[], 306 newNode: ts.NewExpression, name: string, props: ts.ObjectLiteralElementLike[], 307 componentNode: ts.CallExpression, isBuilder: boolean, isGlobalBuilder: boolean, 308 isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo, 309 builderParamsResult: BuilderParamsResult, log: LogInfo[]): void { 310 if (!partialUpdateConfig.partialUpdateMode) { 311 const id: string = componentInfo.id.toString(); 312 newStatements.push(createFindChildById(id, name, isBuilder), createCustomComponentIfStatement(id, 313 ts.factory.updateExpressionStatement(node, createViewCreate(newNode)), 314 ts.factory.createObjectLiteralExpression(props, true), name)); 315 } else { 316 newStatements.push(createCustomComponent(newNode, name, componentNode, isGlobalBuilder, isBuilder, 317 isRecycleComponent, componentAttrInfo, builderParamsResult, log)); 318 } 319} 320 321function createChildElmtId(node: ts.CallExpression, name: string, log: LogInfo[]): ts.PropertyAssignment[] { 322 const childParam: ts.PropertyAssignment[] = []; 323 const propsAndObjectLinks: string[] = []; 324 if (propCollection.get(name)) { 325 propsAndObjectLinks.push(...propCollection.get(name)); 326 } 327 if (objectLinkCollection.get(name)) { 328 propsAndObjectLinks.push(...objectLinkCollection.get(name)); 329 } 330 if (projectConfig.optLazyForEach && storedFileInfo.processLazyForEach && stateCollection.get(name)) { 331 propsAndObjectLinks.push(...stateCollection.get(name)); 332 } 333 parseChildProperties(name, node, childParam, propsAndObjectLinks, log); 334 return childParam; 335} 336 337class ChildAndParentComponentInfo { 338 childStructInfo: StructInfo; 339 parentStructInfo: StructInfo; 340 paramDecoratorMap: Map<string, ParamDecoratorInfo>; 341 updatePropsDecoratorsV2: string[]; 342 propsAndObjectLinks: string[]; 343 childName: string; 344 forbiddenInitPropsV2: string[]; 345 updatePropsForV1Parent: string[]; 346 updatePropsForV2Parent: string[]; 347 constructor(childName: string, childNode: ts.CallExpression, propsAndObjectLinks: string[]) { 348 this.childName = childName; 349 this.propsAndObjectLinks = propsAndObjectLinks; 350 this.childStructInfo = processStructComponentV2.getAliasStructInfo(childNode) || 351 processStructComponentV2.getOrCreateStructInfo(childName); 352 this.paramDecoratorMap = this.childStructInfo.paramDecoratorMap; 353 this.updatePropsDecoratorsV2 = [...this.childStructInfo.eventDecoratorSet, ...this.paramDecoratorMap.keys()]; 354 this.parentStructInfo = componentCollection.currentClassName ? 355 processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName) : 356 new StructInfo(); 357 this.forbiddenInitPropsV2 = [...this.childStructInfo.localDecoratorSet, 358 ...this.childStructInfo.providerDecoratorSet, ...this.childStructInfo.consumerDecoratorSet, 359 ...this.childStructInfo.regularSet]; 360 this.updatePropsForV1Parent = getUpdatePropsForV1Parent(); 361 this.updatePropsForV2Parent = [...this.parentStructInfo.localDecoratorSet, 362 ...this.parentStructInfo.paramDecoratorMap.keys(), ...this.parentStructInfo.providerDecoratorSet, 363 ...this.parentStructInfo.consumerDecoratorSet]; 364 } 365} 366 367function getUpdatePropsForV1Parent(): string[] { 368 const propertiesMapArr: Array<Map<string, Set<string>>> = [ 369 stateCollection, linkCollection, propCollection, 370 provideCollection, consumeCollection, objectLinkCollection, 371 storagePropCollection, storageLinkCollection 372 ]; 373 const updatePropsForParent: string[] = []; 374 if (componentCollection.currentClassName) { 375 const localStorageSet: Set<string> = new Set(); 376 getLocalStorageCollection(componentCollection.currentClassName, localStorageSet); 377 updatePropsForParent.push(...localStorageSet); 378 propertiesMapArr.forEach((item: Map<string, Set<string>>) => { 379 const value: Set<string> = item.get(componentCollection.currentClassName); 380 if (value) { 381 updatePropsForParent.push(...value); 382 } 383 }); 384 } 385 return updatePropsForParent; 386} 387 388function parseChildProperties(childName: string, node: ts.CallExpression, 389 childParam: ts.PropertyAssignment[], propsAndObjectLinks: string[], log: LogInfo[]): void { 390 const childAndParentComponentInfo: ChildAndParentComponentInfo = 391 new ChildAndParentComponentInfo(childName, node, propsAndObjectLinks); 392 if (node.arguments[0].properties) { 393 node.arguments[0].properties.forEach((item: ts.PropertyAssignment) => { 394 if (ts.isIdentifier(item.name)) { 395 const itemName: string = item.name.escapedText.toString(); 396 validateChildProperty(item, itemName, childParam, log, childAndParentComponentInfo); 397 } 398 }); 399 } 400} 401 402function validateChildProperty(item: ts.PropertyAssignment, itemName: string, 403 childParam: ts.PropertyAssignment[], log: LogInfo[], info: ChildAndParentComponentInfo): void { 404 if (info.childStructInfo.isComponentV2) { 405 if (info.forbiddenInitPropsV2.includes(itemName)) { 406 log.push({ 407 type: LogType.ERROR, 408 message: `Property '${itemName}' in the custom component '${info.childName}'` + 409 ` cannot be initialized here (forbidden to specify).`, 410 pos: item.getStart() 411 }); 412 return; 413 } 414 if (info.paramDecoratorMap.has(itemName)) { 415 childParam.push(item); 416 } 417 if (isForbiddenAssignToComponentV2(item, itemName, info)) { 418 log.push({ 419 type: LogType.ERROR, 420 message: `Property '${itemName}' in the @ComponentV2 component '${info.childName}' are not allowed to be assigned values here.`, 421 pos: item.getStart() 422 }); 423 } 424 } else { 425 if (info.propsAndObjectLinks.includes(itemName)) { 426 childParam.push(item); 427 } 428 if (isForbiddenAssignToComponentV1(item, itemName, info)) { 429 log.push({ 430 type: LogType.ERROR, 431 message: `Property '${itemName}' in the @Component component '${info.childName}' are not allowed to be assigned values here.`, 432 pos: item.getStart() 433 }); 434 } 435 } 436} 437 438function isForbiddenAssignToComponentV1(item: ts.PropertyAssignment, itemName: string, 439 info: ChildAndParentComponentInfo): boolean { 440 if (info.parentStructInfo.isComponentV2 && info.childStructInfo.updatePropsDecoratorsV1.includes(itemName) && 441 isObervedProperty(item.initializer, info, false) && globalProgram.checker) { 442 const type: ts.Type = globalProgram.checker.getTypeAtLocation(item.initializer); 443 return isForbiddenTypeToComponentV1(type); 444 } 445 return false; 446} 447 448function isForbiddenTypeToComponentV1(type: ts.Type): boolean { 449 // @ts-ignore 450 if (type.types && type.types.length) { 451 // @ts-ignore 452 return !type.types.some((item: ts.Type) => { 453 return !isForbiddenTypeToComponentV1(item); 454 }); 455 } 456 const allowedTypes: string[] = ['Set', 'Map', 'Date', 'Array']; 457 const name: string = type?.getSymbol()?.getName(); 458 if (name && allowedTypes.includes(name)) { 459 return true; 460 } 461 return false; 462} 463 464function isForbiddenAssignToComponentV2(item: ts.PropertyAssignment, itemName: string, 465 info: ChildAndParentComponentInfo): boolean { 466 if (!info.parentStructInfo.isComponentV2 && info.updatePropsDecoratorsV2.includes(itemName) && 467 isObervedProperty(item.initializer, info) && globalProgram.strictChecker) { 468 const type: ts.Type = globalProgram.strictChecker.getTypeAtLocation(item.initializer); 469 return !isAllowedTypeToComponentV2(type); 470 } 471 return false; 472} 473 474function isObervedProperty(value: ts.Expression, info: ChildAndParentComponentInfo, 475 isV1Parent: boolean = true): boolean { 476 if (value && ts.isPropertyAccessExpression(value) && value.expression.kind === ts.SyntaxKind.ThisKeyword && 477 ts.isIdentifier(value.name)) { 478 const propertyName: string = value.name.escapedText.toString(); 479 return isV1Parent ? info.updatePropsForV1Parent.includes(propertyName) : 480 info.updatePropsForV2Parent.includes(propertyName); 481 } 482 return false; 483} 484 485function isAllowedTypeToComponentV2(type: ts.Type): boolean { 486 if (type) { 487 // @ts-ignore 488 if (type.types && type.types.length) { 489 // @ts-ignore 490 return type.types.some((item: ts.Type) => { 491 return isAllowedTypeToComponentV2(item); 492 }); 493 } 494 // string|number|boolean|enum|null|undefined 495 if (isAllowedTypeForBasic(type.flags)) { 496 return true; 497 } 498 } 499 return false; 500} 501 502function isAllowedTypeForBasic(flags: ts.TypeFlags): boolean { 503 if (isBasicType(flags) || (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))) { 504 return true; 505 } 506 return false; 507} 508 509function validateInitParam(childName: string, curChildProps: Set<string>, 510 node: ts.CallExpression, log: LogInfo[], parentStructInfo: StructInfo): void { 511 const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(node) || 512 processStructComponentV2.getOrCreateStructInfo(childName); 513 const paramDecoratorMap: Map<string, ParamDecoratorInfo> = childStructInfo.paramDecoratorMap; 514 if (childStructInfo.isComponentV2) { 515 const needInitParam: string[] = []; 516 for (const item of paramDecoratorMap) { 517 if (item[1].hasRequire) { 518 needInitParam.push(item[0]); 519 } 520 } 521 needInitParam.forEach((paramName: string) => { 522 if (!curChildProps.has(paramName)) { 523 log.push({ 524 type: LogType.ERROR, 525 message: `Property '${paramName}' must be initialized through the component constructor.`, 526 pos: node.getStart() 527 }); 528 } 529 }); 530 } else if (parentStructInfo.isComponentV2 && childStructInfo.linkDecoratorsV1.length) { 531 log.push({ 532 type: LogType.ERROR, 533 message: 'The @ComponentV2 struct must not contain any @Component with an @Link decorated variable', 534 pos: node.getStart() 535 }); 536 } 537} 538 539function createCustomComponent(newNode: ts.NewExpression, name: string, componentNode: ts.CallExpression, 540 isGlobalBuilder: boolean, isBuilder: boolean, isRecycleComponent: boolean, 541 componentAttrInfo: ComponentAttrInfo, builderParamsResult: BuilderParamsResult, log: LogInfo[]): ts.Block { 542 let componentParameter: ts.ObjectLiteralExpression; 543 if (componentNode.arguments && componentNode.arguments.length) { 544 componentParameter = ts.factory.createObjectLiteralExpression(createChildElmtId(componentNode, name, log), true); 545 } else { 546 componentParameter = ts.factory.createObjectLiteralExpression([], false); 547 } 548 const arrowArgArr: ts.ParameterDeclaration[] = [ 549 ts.factory.createParameterDeclaration(undefined, undefined, 550 ts.factory.createIdentifier(ELMTID) 551 ), 552 ts.factory.createParameterDeclaration(undefined, undefined, 553 ts.factory.createIdentifier(ISINITIALRENDER) 554 ) 555 ]; 556 const arrowBolck: ts.Statement[] = [ 557 projectConfig.optLazyForEach && storedFileInfo.processLazyForEach ? createCollectElmtIdNode() : undefined, 558 createIfCustomComponent(newNode, componentNode, componentParameter, name, isGlobalBuilder, 559 isBuilder, isRecycleComponent, componentAttrInfo, log) 560 ]; 561 if (isRecycleComponent) { 562 arrowArgArr.push(ts.factory.createParameterDeclaration( 563 undefined, undefined, ts.factory.createIdentifier(RECYCLE_NODE), 564 undefined, undefined, ts.factory.createNull() 565 )); 566 } else if (partialUpdateConfig.optimizeComponent && isGlobalBuilder && 567 builderParamsResult && builderParamsResult.firstParam) { 568 const paramName: ts.Identifier = builderParamsResult.firstParam.name as ts.Identifier; 569 arrowArgArr.push(ts.factory.createParameterDeclaration(undefined, undefined, 570 paramName, undefined, undefined, ts.factory.createIdentifier(`__${paramName.escapedText.toString()}__`) 571 )); 572 } 573 if (isRecycleComponent || !partialUpdateConfig.optimizeComponent) { 574 arrowBolck.unshift(createViewStackProcessorStatement(STARTGETACCESSRECORDINGFOR, ELMTID)); 575 arrowBolck.push(createViewStackProcessorStatement(STOPGETACCESSRECORDING)); 576 } 577 const observeArgArr: ts.Node[] = [ 578 ts.factory.createArrowFunction(undefined, undefined, arrowArgArr, undefined, 579 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 580 ts.factory.createBlock(arrowBolck, true)) 581 ]; 582 if (isRecycleComponent) { 583 componentAttrInfo.reuseId ? observeArgArr.unshift(componentAttrInfo.reuseId) : 584 observeArgArr.unshift(ts.factory.createStringLiteral(name)); 585 } else if (partialUpdateConfig.optimizeComponent) { 586 observeArgArr.push(componentPop(name)); 587 } 588 return ts.factory.createBlock( 589 [ 590 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 591 ts.factory.createPropertyAccessExpression(isGlobalBuilder ? 592 ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), 593 isRecycleComponent ? 594 ts.factory.createIdentifier(OBSERVE_RECYCLE_COMPONENT_CREATION) : 595 ts.factory.createIdentifier(partialUpdateConfig.optimizeComponent ? 596 OBSERVECOMPONENTCREATION2 : OBSERVECOMPONENTCREATION) 597 ), 598 undefined, observeArgArr as ts.Expression[])) 599 ], true); 600} 601 602function componentPop(name: string): ts.ObjectLiteralExpression { 603 return ts.factory.createObjectLiteralExpression( 604 [ts.factory.createPropertyAssignment( 605 ts.factory.createIdentifier(NAME), 606 ts.factory.createStringLiteral(name) 607 )], 608 false 609 ); 610} 611 612export function assignComponentParams(componentNode: ts.CallExpression, 613 isBuilder: boolean = false): ts.VariableStatement { 614 const isParamsLambda: boolean = true; 615 const [keyArray, valueArray]: [ts.Node[], ts.Node[]] = splitComponentParams(componentNode, isBuilder, isParamsLambda); 616 let integrateParams: boolean = false; 617 if (!keyArray.length && componentNode.arguments && componentNode.arguments.length > 0 && componentNode.arguments[0]) { 618 integrateParams = true; 619 } 620 return ts.factory.createVariableStatement( 621 undefined, 622 ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration( 623 ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION), 624 undefined, 625 undefined, 626 ts.factory.createArrowFunction( 627 undefined, 628 undefined, 629 [], 630 undefined, 631 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 632 ts.factory.createBlock( 633 [ts.factory.createReturnStatement( 634 integrateParams ? paramsLambdaCallBack(componentNode) : ts.factory.createObjectLiteralExpression( 635 reWriteComponentParams(keyArray, valueArray), 636 true 637 ) 638 )], 639 true 640 ) 641 ) 642 )], 643 ts.NodeFlags.Let 644 )); 645} 646 647function paramsLambdaCallBack(componentNode: ts.CallExpression): ts.Expression { 648 if (partialUpdateConfig.partialUpdateMode && componentNode.arguments.length === 1 && 649 isLocalStorageParameter(componentNode)) { 650 return ts.factory.createObjectLiteralExpression([], true); 651 } else { 652 return componentNode.arguments[0]; 653 } 654} 655 656function reWriteComponentParams(keyArray: ts.Node[], valueArray: ts.Node[]): (ts.PropertyAssignment | 657 ts.ShorthandPropertyAssignment)[] { 658 const returnProperties: (ts.PropertyAssignment | ts.ShorthandPropertyAssignment)[] = []; 659 keyArray.forEach((item: ts.Identifier, index: number) => { 660 if (!valueArray[index]) { 661 returnProperties.push(ts.factory.createShorthandPropertyAssignment( 662 item, 663 undefined 664 )); 665 } else { 666 returnProperties.push(ts.factory.createPropertyAssignment( 667 item, 668 valueArray[index] as ts.Identifier 669 )); 670 } 671 }); 672 return returnProperties; 673} 674 675function splitComponentParams(componentNode: ts.CallExpression, isBuilder: boolean, isParamsLambda: boolean): [ts.Node[], ts.Node[]] { 676 const keyArray: ts.Node[] = []; 677 const valueArray: ts.Node[] = []; 678 if (componentNode.arguments && componentNode.arguments.length > 0 && 679 ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { 680 componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { 681 const newPropertyItem: ts.PropertyAssignment = 682 isProperty(propertyItem) ? createReference(propertyItem, [], isBuilder, isParamsLambda) : propertyItem; 683 keyArray.push(newPropertyItem.name); 684 valueArray.push(newPropertyItem.initializer); 685 }); 686 } 687 return [keyArray, valueArray]; 688} 689 690function createIfCustomComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, 691 componentParameter: ts.ObjectLiteralExpression, name: string, isGlobalBuilder: boolean, isBuilder: boolean, 692 isRecycleComponent: boolean, componentAttrInfo: ComponentAttrInfo, log: LogInfo[]): ts.IfStatement { 693 return ts.factory.createIfStatement( 694 ts.factory.createIdentifier(ISINITIALRENDER), 695 ts.factory.createBlock( 696 [componentParamDetachment(newNode, isRecycleComponent, name, log, componentNode), 697 isRecycleComponent ? createNewRecycleComponent(newNode, componentNode, name, componentAttrInfo) : 698 createNewComponent(COMPONENT_CALL, name, componentNode), 699 assignComponentParams(componentNode, isBuilder), 700 assignmentFunction(COMPONENT_CALL) 701 ], true), 702 ts.factory.createBlock( 703 [ts.factory.createExpressionStatement(ts.factory.createCallExpression( 704 ts.factory.createPropertyAccessExpression(isGlobalBuilder ? 705 ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(), 706 ts.factory.createIdentifier(UPDATE_STATE_VARS_OF_CHIND_BY_ELMTID) 707 ), undefined, 708 [ts.factory.createIdentifier(ELMTID), componentParameter]))], true) 709 ); 710} 711 712export function assignmentFunction(componeParamName: string): ts.ExpressionStatement { 713 return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( 714 ts.factory.createPropertyAccessExpression( 715 ts.factory.createIdentifier(componeParamName), 716 ts.factory.createIdentifier(COMPONENT_PARAMS_FUNCTION) 717 ), 718 ts.factory.createToken(ts.SyntaxKind.EqualsToken), 719 ts.factory.createIdentifier(COMPONENT_PARAMS_LAMBDA_FUNCTION) 720 )); 721} 722 723function traverseChildComponentArgs(childParam: ts.Expression[], name: string, log: LogInfo[], 724 componentNode: ts.CallExpression): ts.Expression[] { 725 const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(componentNode) || 726 processStructComponentV2.getOrCreateStructInfo(name); 727 if (!childStructInfo.isComponentV2) { 728 return childParam; 729 } 730 const objectLiteralIndex: number = 2; 731 if (childParam.length > objectLiteralIndex && ts.isObjectLiteralExpression(childParam[1]) && 732 childParam[1].properties) { 733 const newProperties: ts.PropertyAssignment[] = []; 734 childParam[1].properties.forEach((item: ts.PropertyAssignment) => { 735 if (item.name && ts.isIdentifier(item.name)) { 736 const itemName: string = item.name.escapedText.toString(); 737 updatePropertyAssignment(newProperties, itemName, item, childStructInfo, log); 738 } 739 }); 740 if (newProperties.length) { 741 return getNewArgsForCustomComponent(childParam, newProperties); 742 } 743 } 744 return childParam; 745} 746 747function getNewArgsForCustomComponent(childParam: ts.Expression[], 748 newProperties: ts.PropertyAssignment[]): ts.Expression[] { 749 const newArr: ts.Expression[] = []; 750 const newObjectLiteralNode: ts.ObjectLiteralExpression = 751 ts.factory.updateObjectLiteralExpression(childParam[1], [...childParam[1].properties, ...newProperties]); 752 childParam.forEach((item: ts.Expression, index: number) => { 753 if (index === 1) { 754 newArr.push(newObjectLiteralNode); 755 } else { 756 newArr.push(item); 757 } 758 }); 759 return newArr; 760} 761 762function updatePropertyAssignment(newProperties: ts.PropertyAssignment[], 763 itemName: string, item: ts.PropertyAssignment, childStructInfo: StructInfo, log: LogInfo[]): void { 764 if (isDoubleNonNullExpression(item.initializer)) { 765 if (isLeftHandExpression(item.initializer.expression.expression)) { 766 const result: Record<string, boolean> = { hasQuestionToken: false }; 767 traverseExpressionNode(item.initializer.expression.expression, result); 768 if (result.hasQuestionToken) { 769 log.push({ 770 type: LogType.ERROR, 771 message: `The optional character can not be used in the initial value of property '${itemName}'.`, 772 pos: item.getStart() 773 }); 774 return; 775 } 776 if (childStructInfo.paramDecoratorMap.has(itemName) && 777 childStructInfo.eventDecoratorSet.has('$' + itemName)) { 778 newProperties.push(createUpdateTwoWayNode(itemName, item.initializer.expression.expression)); 779 return; 780 } 781 log.push({ 782 type: LogType.ERROR, 783 message: 'When the two-way binding syntax is used, ' + 784 `the variable '${itemName}' must be decorated with @Param, ` + 785 `and the @Event variable '$` + `${itemName}' ` + `must be defined in the ${childStructInfo.structName}.`, 786 pos: item.getStart() 787 }); 788 return; 789 } 790 log.push({ 791 type: LogType.ERROR, 792 message: 'When the two-way binding syntax is used, ' + 793 `the initial value of property '${itemName}' must be a variable.`, 794 pos: item.getStart() 795 }); 796 return; 797 } 798} 799 800function createUpdateTwoWayNode(itemName: string, leftHandExpression: ts.Expression): ts.PropertyAssignment { 801 return ts.factory.createPropertyAssignment( 802 ts.factory.createIdentifier('$' + itemName), 803 ts.factory.createArrowFunction(undefined, undefined, 804 [createAstNodeUtils.createParameterDeclaration('value')], undefined, 805 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 806 ts.factory.createBlock([ 807 ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( 808 leftHandExpression, ts.factory.createToken(ts.SyntaxKind.EqualsToken), 809 ts.factory.createIdentifier('value') 810 )) 811 ], false) 812 ) 813 ); 814} 815 816function isDoubleNonNullExpression(node: ts.Expression): boolean { 817 return node && ts.isNonNullExpression(node) && ts.isNonNullExpression(node.expression); 818} 819 820function isLeftHandExpression(node: ts.Expression): boolean { 821 return node && (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)); 822} 823 824function traverseExpressionNode(node: ts.Node, result: Record<string, boolean>): void { 825 if (ts.isOptionalChain(node) && !ts.isNonNullExpression(node) && node.questionDotToken) { 826 result.hasQuestionToken = true; 827 } 828 if (!result.hasQuestionToken) { 829 node.getChildren().forEach((item: ts.Node) => traverseExpressionNode(item, result)); 830 } 831} 832 833function componentParamDetachment(newNode: ts.NewExpression, isRecycleComponent: boolean, 834 name: string, log: LogInfo[], componentNode: ts.CallExpression): ts.VariableStatement { 835 const paramsArray: ts.Expression[] = newNode.arguments.length ? newNode.arguments : []; 836 const updateNewNode = ts.factory.updateNewExpression(newNode, newNode.expression, 837 newNode.typeArguments, traverseChildComponentArgs(paramsArray, name, log, componentNode)); 838 return ts.factory.createVariableStatement( 839 undefined, 840 ts.factory.createVariableDeclarationList( 841 [ts.factory.createVariableDeclaration( 842 ts.factory.createIdentifier(COMPONENT_CALL), 843 undefined, 844 undefined, 845 isRecycleComponent ? ts.factory.createConditionalExpression( 846 ts.factory.createIdentifier(RECYCLE_NODE), 847 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 848 ts.factory.createIdentifier(RECYCLE_NODE), 849 ts.factory.createToken(ts.SyntaxKind.ColonToken), 850 newNode) : updateNewNode 851 )], 852 ts.NodeFlags.Let 853 )); 854} 855 856function createNewComponent(componeParamName: string, name: string, 857 componentNode: ts.CallExpression): ts.Statement { 858 const childStructInfo: StructInfo = processStructComponentV2.getAliasStructInfo(componentNode) || 859 processStructComponentV2.getOrCreateStructInfo(name); 860 return ts.factory.createExpressionStatement( 861 ts.factory.createCallExpression( 862 ts.factory.createPropertyAccessExpression( 863 ts.factory.createIdentifier( 864 childStructInfo.isComponentV2 ? constantDefine.STRUCT_PARENT : BASE_COMPONENT_NAME_PU), 865 ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) 866 ), undefined, [ts.factory.createIdentifier(componeParamName)])); 867} 868 869function createNewRecycleComponent(newNode: ts.NewExpression, componentNode: ts.CallExpression, 870 name: string, componentAttrInfo: ComponentAttrInfo): ts.Statement { 871 let argNode: ts.Expression[] = []; 872 const componentParam: ts.PropertyAssignment[] = []; 873 if (componentNode.arguments && componentNode.arguments.length > 0 && 874 ts.isObjectLiteralExpression(componentNode.arguments[0]) && componentNode.arguments[0].properties) { 875 componentNode.arguments[0].properties.forEach((propertyItem: ts.PropertyAssignment) => { 876 const newPropertyItem: ts.PropertyAssignment = isProperty(propertyItem) ? 877 createReference(propertyItem, [], false, false, true) : propertyItem; 878 componentParam.push(newPropertyItem); 879 }); 880 argNode = [ts.factory.createObjectLiteralExpression(componentParam, false)]; 881 } else { 882 argNode = [ts.factory.createObjectLiteralExpression([], false)]; 883 } 884 const recycleNode: ts.CallExpression = ts.factory.createCallExpression( 885 createRecyclePropertyNode(ABOUT_TO_REUSE), undefined, argNode); 886 return ts.factory.createExpressionStatement( 887 ts.factory.createCallExpression( 888 ts.factory.createPropertyAccessExpression( 889 ts.factory.createIdentifier(BASE_COMPONENT_NAME_PU), 890 ts.factory.createIdentifier(COMPONENT_CREATE_RECYCLE) 891 ), undefined, 892 [ 893 ts.factory.createIdentifier(COMPONENT_CALL), 894 ts.factory.createBinaryExpression( 895 ts.factory.createIdentifier(RECYCLE_NODE), 896 ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), 897 ts.factory.createNull() 898 ), 899 componentAttrInfo.reuseId ? componentAttrInfo.reuseId as ts.Expression : 900 ts.factory.createStringLiteral(name), 901 ts.factory.createArrowFunction(undefined, undefined, [], undefined, 902 ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), 903 ts.factory.createBlock([ 904 ts.factory.createIfStatement( 905 ts.factory.createBinaryExpression( 906 ts.factory.createIdentifier(RECYCLE_NODE), 907 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 908 ts.factory.createBinaryExpression( 909 ts.factory.createTypeOfExpression( 910 createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION)), 911 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 912 ts.factory.createStringLiteral(FUNCTION) 913 )), 914 ts.factory.createBlock([ 915 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 916 createRecyclePropertyNode(COMPONENT_ABOUTTOREUSEINTERNAL_FUNCTION), 917 undefined, 918 [] 919 )) 920 ], true), 921 ts.factory.createBlock( 922 [ 923 ts.factory.createIfStatement(ts.factory.createBinaryExpression( 924 createRecyclePropertyNode(ABOUT_TO_REUSE), ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 925 ts.factory.createBinaryExpression( 926 ts.factory.createTypeOfExpression(createRecyclePropertyNode(ABOUT_TO_REUSE)), 927 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), 928 ts.factory.createStringLiteral(FUNCTION) 929 )), 930 ts.factory.createBlock([ts.factory.createExpressionStatement(recycleNode)], true)), 931 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 932 createRecyclePropertyNode(COMPONENT_RERENDER_FUNCTION), undefined, [] 933 )) 934 ], 935 true 936 ) 937 )], true)) 938 ])); 939} 940 941function createRecyclePropertyNode(recycleFunctionName: string): ts.PropertyAccessExpression { 942 return ts.factory.createPropertyAccessExpression( 943 ts.factory.createIdentifier(RECYCLE_NODE), ts.factory.createIdentifier(recycleFunctionName)); 944} 945 946function validateCustomComponentPrams(node: ts.CallExpression, name: string, 947 props: ts.ObjectLiteralElementLike[], log: LogInfo[], isBuilder: boolean): void { 948 const curChildProps: Set<string> = new Set([]); 949 const nodeArguments: ts.NodeArray<ts.Expression> = node.arguments; 950 const linkSet: Set<string> = getCollectionSet(name, linkCollection); 951 if (nodeArguments && nodeArguments.length >= 1 && 952 ts.isObjectLiteralExpression(nodeArguments[0])) { 953 const nodeArgument: ts.ObjectLiteralExpression = nodeArguments[0] as ts.ObjectLiteralExpression; 954 nodeArgument.properties.forEach(item => { 955 if (item.name && ts.isIdentifier(item.name)) { 956 curChildProps.add(item.name.escapedText.toString()); 957 } 958 validateStateManagement(item, name, log, isBuilder); 959 if (isNonThisProperty(item, linkSet)) { 960 if (isToChange(item as ts.PropertyAssignment, name)) { 961 item = ts.factory.updatePropertyAssignment(item as ts.PropertyAssignment, 962 item.name, changeNodeFromCallToArrow(item.initializer)); 963 } 964 props.push(item); 965 } 966 }); 967 } 968 const parentStructInfo: StructInfo = componentCollection.currentClassName ? 969 processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName) : 970 new StructInfo(); 971 validateInitDecorator(node, name, curChildProps, log); 972 validateMandatoryToInitViaParam(node, name, curChildProps, log, parentStructInfo); 973 validateInitParam(name, curChildProps, node, log, parentStructInfo); 974} 975 976function getCustomComponentNode(node: ts.ExpressionStatement): ts.CallExpression { 977 let temp: any = node.expression; 978 let child: any = null; 979 let componentNode: any = null; 980 while (temp) { 981 if (ts.isIdentifier(temp)) { 982 child = temp; 983 break; 984 } 985 temp = temp.expression; 986 } 987 if (child) { 988 let parent = child.parent; 989 while (parent) { 990 if (ts.isExpressionStatement(parent)) { 991 break; 992 } 993 if (ts.isCallExpression(parent) || ts.isEtsComponentExpression(parent)) { 994 componentNode = parent; 995 break; 996 } 997 parent = parent.parent; 998 } 999 } 1000 return componentNode; 1001} 1002 1003function getCollectionSet(name: string, collection: Map<string, Set<string>>): Set<string> { 1004 if (!collection) { 1005 return new Set([]); 1006 } 1007 return collection.get(name) || new Set([]); 1008} 1009 1010function isThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean { 1011 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 1012 propertySet.has(node.name.escapedText.toString())) { 1013 return true; 1014 } 1015 return false; 1016} 1017 1018function isNonThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean { 1019 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 1020 (node.initializer.escapedText && node.initializer.escapedText.includes('$') || 1021 ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && 1022 node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && 1023 ts.isIdentifier(node.initializer.name) && node.initializer.name.escapedText.toString().includes('$'))) { 1024 return false; 1025 } 1026 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && 1027 !propertySet.has(node.name.escapedText.toString())) { 1028 return true; 1029 } 1030 return false; 1031} 1032 1033function validateStateManagement(node: ts.ObjectLiteralElementLike, customComponentName: string, 1034 log: LogInfo[], isBuilder: boolean): void { 1035 validateForbiddenToInitViaParam(node, customComponentName, log); 1036 if (componentCollection.currentClassName) { 1037 const parentStructInfo: StructInfo = 1038 processStructComponentV2.getOrCreateStructInfo(componentCollection.currentClassName); 1039 if (parentStructInfo.isComponentV2) { 1040 return; 1041 } 1042 } 1043 checkFromParentToChild(node, customComponentName, log, isBuilder); 1044} 1045 1046function checkFromParentToChild(node: ts.ObjectLiteralElementLike, customComponentName: string, 1047 log: LogInfo[], isBuilder: boolean): void { 1048 let propertyName: string; 1049 if (ts.isIdentifier(node.name)) { 1050 propertyName = node.name.escapedText.toString(); 1051 } 1052 const curPropertyKind: string = getPropertyDecoratorKind(propertyName, customComponentName); 1053 let parentPropertyName: string; 1054 if (curPropertyKind) { 1055 if (isInitFromParent(node)) { 1056 parentPropertyName = 1057 getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log); 1058 let parentPropertyKind: string = curPropMap.get(parentPropertyName); 1059 if (!parentPropertyKind) { 1060 parentPropertyKind = COMPONENT_NON_DECORATOR; 1061 } 1062 if (parentPropertyKind && !isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { 1063 validateIllegalInitFromParent( 1064 node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log); 1065 } 1066 } else if (isInitFromLocal(node) && ts.isPropertyAssignment(node) && 1067 curPropertyKind !== COMPONENT_OBJECT_LINK_DECORATOR) { 1068 if (!isCorrectInitFormParent(COMPONENT_NON_DECORATOR, curPropertyKind)) { 1069 validateIllegalInitFromParent(node, propertyName, curPropertyKind, 1070 node.initializer.getText(), COMPONENT_NON_DECORATOR, log); 1071 } 1072 } else if (curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR && node.initializer && 1073 (ts.isPropertyAccessExpression(node.initializer) || 1074 ts.isElementAccessExpression(node.initializer) || ts.isIdentifier(node.initializer))) { 1075 return; 1076 } else { 1077 parentPropertyName = 1078 getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log) || propertyName; 1079 const parentPropertyKind = COMPONENT_NON_DECORATOR; 1080 if (!isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) { 1081 if (isBuilder && judgeStructAssignedDollar(node)) { 1082 log.push({ 1083 type: LogType.WARN, 1084 message: `Unrecognized property '${parentPropertyName}', make sure it can be assigned to ` + 1085 `${curPropertyKind} property '${propertyName}' by yourself.`, 1086 // @ts-ignore 1087 pos: node.initializer ? node.initializer.getStart() : node.getStart() 1088 }); 1089 } else { 1090 validateIllegalInitFromParent( 1091 node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log, LogType.WARN); 1092 } 1093 } 1094 } 1095 } 1096} 1097 1098function judgeStructAssignedDollar(node: ts.ObjectLiteralElementLike): boolean { 1099 return partialUpdateConfig.partialUpdateMode && node.initializer && 1100 ts.isPropertyAccessExpression(node.initializer) && 1101 node.initializer.expression && ts.isIdentifier(node.initializer.expression) && 1102 node.initializer.expression.escapedText.toString() === $$; 1103} 1104 1105function isInitFromParent(node: ts.ObjectLiteralElementLike): boolean { 1106 if (ts.isPropertyAssignment(node) && node.initializer) { 1107 if (ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression && 1108 node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword && 1109 ts.isIdentifier(node.initializer.name)) { 1110 return true; 1111 } else if (ts.isIdentifier(node.initializer) && 1112 matchStartWithDollar(node.initializer.getText())) { 1113 return true; 1114 } 1115 } 1116 return false; 1117} 1118 1119function isInitFromLocal(node: ts.ObjectLiteralElementLike): boolean { 1120 if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer) && 1121 !matchStartWithDollar(node.initializer.getText())) { 1122 return true; 1123 } 1124 return false; 1125} 1126 1127function getParentPropertyName(node: ts.PropertyAssignment, curPropertyKind: string, 1128 log: LogInfo[]): string { 1129 const initExpression: ts.Expression = node.initializer; 1130 if (!initExpression) { 1131 return undefined; 1132 } 1133 let parentPropertyName: string = initExpression.getText(); 1134 if (curPropertyKind === COMPONENT_LINK_DECORATOR) { 1135 // @ts-ignore 1136 const initName: ts.Identifier = initExpression.name || initExpression; 1137 if (hasDollar(initExpression)) { 1138 parentPropertyName = initName.getText().replace(/^\$/, ''); 1139 } else { 1140 parentPropertyName = initName.getText(); 1141 } 1142 } else { 1143 if (hasDollar(initExpression)) { 1144 validateNonLinkWithDollar(node, log); 1145 } else { 1146 // @ts-ignore 1147 if (node.initializer && node.initializer.name) { 1148 parentPropertyName = node.initializer.name.getText(); 1149 } 1150 } 1151 } 1152 1153 return parentPropertyName; 1154} 1155 1156function isCorrectInitFormParent(parent: string, child: string): boolean { 1157 switch (child) { 1158 case COMPONENT_STATE_DECORATOR: 1159 case COMPONENT_PROP_DECORATOR: 1160 case COMPONENT_PROVIDE_DECORATOR: 1161 return true; 1162 case COMPONENT_NON_DECORATOR: 1163 if ([COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR, COMPONENT_LINK_DECORATOR, COMPONENT_PROP_DECORATOR, 1164 COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR].includes(parent)) { 1165 return true; 1166 } 1167 break; 1168 case COMPONENT_LINK_DECORATOR: 1169 case COMPONENT_OBJECT_LINK_DECORATOR: 1170 return ![COMPONENT_NON_DECORATOR].includes(parent); 1171 } 1172 return false; 1173} 1174 1175function getPropertyDecoratorKind(propertyName: string, customComponentName: string): string { 1176 for (const item of decoractorMap.entries()) { 1177 if (getCollectionSet(customComponentName, item[1]).has(propertyName)) { 1178 return item[0]; 1179 } 1180 } 1181 return undefined; 1182} 1183 1184function createFindChildById(id: string, name: string, isBuilder: boolean = false): ts.VariableStatement { 1185 return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( 1186 [ts.factory.createVariableDeclaration(ts.factory.createIdentifier( 1187 `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, ts.factory.createTypeReferenceNode( 1188 ts.factory.createIdentifier(name)), 1189 ts.factory.createConditionalExpression( 1190 ts.factory.createParenthesizedExpression( 1191 ts.factory.createBinaryExpression( 1192 createConditionParent(isBuilder), 1193 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 1194 ts.factory.createPropertyAccessExpression( 1195 createConditionParent(isBuilder), 1196 ts.factory.createIdentifier(CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID) 1197 ))), ts.factory.createToken(ts.SyntaxKind.QuestionToken), 1198 ts.factory.createAsExpression(ts.factory.createCallExpression( 1199 ts.factory.createPropertyAccessExpression(createConditionParent(isBuilder), 1200 ts.factory.createIdentifier(`${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, 1201 [isBuilder ? ts.factory.createCallExpression(ts.factory.createIdentifier(GENERATE_ID), 1202 undefined, []) : ts.factory.createStringLiteral(id)]), 1203 ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(name))), 1204 ts.factory.createToken(ts.SyntaxKind.ColonToken), 1205 ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED)))], ts.NodeFlags.Let)); 1206} 1207 1208export function createConditionParent(isBuilder: boolean): ts.ParenthesizedExpression | ts.ThisExpression { 1209 return isBuilder ? ts.factory.createParenthesizedExpression(parentConditionalExpression()) : ts.factory.createThis(); 1210} 1211 1212function createCustomComponentIfStatement(id: string, node: ts.ExpressionStatement, 1213 newObjectLiteralExpression: ts.ObjectLiteralExpression, parentName: string): ts.IfStatement { 1214 const viewName: string = `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`; 1215 return ts.factory.createIfStatement(ts.factory.createBinaryExpression( 1216 ts.factory.createIdentifier(viewName), 1217 ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken), 1218 ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_UNDEFINED}`)), 1219 ts.factory.createBlock([node], true), 1220 ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( 1221 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier( 1222 viewName), ts.factory.createIdentifier( 1223 `${COMPONENT_CONSTRUCTOR_UPDATE_PARAMS}`)), undefined, [newObjectLiteralExpression])), 1224 isStaticViewCollection.get(parentName) ? createStaticIf(viewName) : undefined, 1225 ts.factory.createExpressionStatement(ts.factory.createCallExpression( 1226 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(`${BASE_COMPONENT_NAME}`), 1227 ts.factory.createIdentifier(`${COMPONENT_CREATE_FUNCTION}`)), undefined, 1228 [ts.factory.createIdentifier(viewName)]))], true)); 1229} 1230 1231function createStaticIf(name: string): ts.IfStatement { 1232 return ts.factory.createIfStatement(ts.factory.createPrefixUnaryExpression( 1233 ts.SyntaxKind.ExclamationToken, ts.factory.createCallExpression( 1234 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), 1235 ts.factory.createIdentifier(CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION)), undefined, [])), 1236 ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression( 1237 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), 1238 ts.factory.createIdentifier(CUSTOM_COMPONENT_MARK_STATIC_FUNCTION)), 1239 undefined, []))], true), undefined); 1240} 1241 1242function hasDollar(initExpression: ts.Expression): boolean { 1243 if (ts.isPropertyAccessExpression(initExpression) && 1244 matchStartWithDollar(initExpression.name.getText())) { 1245 return true; 1246 } else if (ts.isIdentifier(initExpression) && matchStartWithDollar(initExpression.getText())) { 1247 return true; 1248 } else { 1249 return false; 1250 } 1251} 1252 1253function matchStartWithDollar(name: string): boolean { 1254 return /^\$/.test(name); 1255} 1256 1257function validateForbiddenToInitViaParam(node: ts.ObjectLiteralElementLike, 1258 customComponentName: string, log: LogInfo[]): void { 1259 const forbiddenToInitViaParamSet: Set<string> = new Set([ 1260 ...getCollectionSet(customComponentName, storageLinkCollection), 1261 ...getCollectionSet(customComponentName, storagePropCollection), 1262 ...getCollectionSet(customComponentName, consumeCollection) 1263 ]); 1264 const localStorageSet: Set<string> = new Set(); 1265 getLocalStorageCollection(customComponentName, localStorageSet); 1266 if (isThisProperty(node, forbiddenToInitViaParamSet) || isThisProperty(node, localStorageSet)) { 1267 log.push({ 1268 type: LogType.ERROR, 1269 message: `Property '${node.name.getText()}' in the custom component '${customComponentName}'` + 1270 ` cannot be initialized here (forbidden to specify).`, 1271 pos: node.name.getStart() 1272 }); 1273 } 1274} 1275 1276function validateMandatoryToInitViaParam(node: ts.CallExpression, customComponentName: string, 1277 curChildProps: Set<string>, log: LogInfo[], parentStructInfo: StructInfo): void { 1278 let mandatoryToInitViaParamSet: Set<string>; 1279 if (projectConfig.compileMode === 'esmodule' && process.env.compileTool === 'rollup' && node.expression) { 1280 const overAll: string[] = [ 1281 ...getCollectionSet(node.expression.getText(), storedFileInfo.overallObjectLinkCollection)]; 1282 if (!parentStructInfo.isComponentV2) { 1283 overAll.unshift(...getCollectionSet(node.expression.getText(), storedFileInfo.overallLinkCollection)); 1284 } 1285 mandatoryToInitViaParamSet = new Set(overAll); 1286 customComponentName = node.expression.getText(); 1287 } else { 1288 const arr: string[] = [...getCollectionSet(customComponentName, objectLinkCollection)]; 1289 if (!parentStructInfo.isComponentV2) { 1290 arr.unshift(...getCollectionSet(customComponentName, linkCollection)); 1291 } 1292 mandatoryToInitViaParamSet = new Set(arr); 1293 } 1294 mandatoryToInitViaParamSet.forEach(item => { 1295 if (item && !curChildProps.has(item)) { 1296 log.push({ 1297 type: LogType.ERROR, 1298 message: `Property '${item}' in the custom component '${customComponentName}'` + 1299 ` is missing (mandatory to specify).`, 1300 pos: node.getStart() 1301 }); 1302 } 1303 }); 1304} 1305 1306function validateInitDecorator(node: ts.CallExpression, customComponentName: string, 1307 curChildProps: Set<string>, log: LogInfo[]): void { 1308 const mandatoryToInitViaParamSet: Set<string> = new Set([ 1309 ...getCollectionSet(customComponentName, builderParamObjectCollection), 1310 ...getCollectionSet(customComponentName, propCollection), 1311 ...getCollectionSet(customComponentName, regularCollection), 1312 ...getCollectionSet(customComponentName, stateCollection), 1313 ...getCollectionSet(customComponentName, provideCollection) 1314 ]); 1315 const decoratorVariable: Set<string> = new Set([ 1316 ...(builderParamInitialization.get(customComponentName) || []), 1317 ...(propInitialization.get(customComponentName) || []), 1318 ...(regularInitialization.get(customComponentName) || []), 1319 ...(stateInitialization.get(customComponentName) || []), 1320 ...(provideInitialization.get(customComponentName) || []) 1321 ]); 1322 mandatoryToInitViaParamSet.forEach((item: string) => { 1323 if (item && !curChildProps.has(item) && decoratorVariable && decoratorVariable.has(item)) { 1324 log.push({ 1325 type: LogType.ERROR, 1326 message: `Property '${item}' must be initialized through the component constructor.`, 1327 pos: node.getStart() 1328 }); 1329 } 1330 }); 1331 const privateParamSet: Set<string> = privateCollection.get(customComponentName) || new Set([]); 1332 curChildProps.forEach((item: string) => { 1333 if (privateParamSet.has(item)) { 1334 log.push({ 1335 type: LogType.WARN, 1336 message: `Property '${item}' is private and can not be initialized through the component constructor.`, 1337 pos: node.getStart() 1338 }); 1339 } 1340 }); 1341} 1342 1343function validateIllegalInitFromParent(node: ts.ObjectLiteralElementLike, propertyName: string, 1344 curPropertyKind: string, parentPropertyName: string, parentPropertyKind: string, 1345 log: LogInfo[], inputType: LogType = undefined): void { 1346 let type: LogType = LogType.ERROR; 1347 if (inputType) { 1348 type = inputType; 1349 } else if ([COMPONENT_STATE_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR].includes( 1350 parentPropertyKind) && curPropertyKind === COMPONENT_OBJECT_LINK_DECORATOR) { 1351 type = LogType.WARN; 1352 } 1353 log.push({ 1354 type: type, 1355 message: `The ${parentPropertyKind} property '${parentPropertyName}' cannot be assigned to ` + 1356 `the ${curPropertyKind} property '${propertyName}'.`, 1357 // @ts-ignore 1358 pos: node.initializer ? node.initializer.getStart() : node.getStart() 1359 }); 1360} 1361 1362function validateNonLinkWithDollar(node: ts.PropertyAssignment, log: LogInfo[]): void { 1363 log.push({ 1364 type: LogType.ERROR, 1365 message: `Property '${node.name.getText()}' cannot initialize` + 1366 ` using '$' to create a reference to a variable.`, 1367 pos: node.initializer.getStart() 1368 }); 1369} 1370