1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts from 'typescript'; 17import path from 'path'; 18 19import { 20 INNER_COMPONENT_DECORATORS, 21 COMPONENT_DECORATOR_ENTRY, 22 COMPONENT_DECORATOR_PREVIEW, 23 COMPONENT_DECORATOR_COMPONENT, 24 COMPONENT_DECORATOR_CUSTOM_DIALOG, 25 NATIVE_MODULE, 26 SYSTEM_PLUGIN, 27 OHOS_PLUGIN, 28 INNER_COMPONENT_MEMBER_DECORATORS, 29 COMPONENT_FOREACH, 30 COMPONENT_LAZYFOREACH, 31 COMPONENT_STATE_DECORATOR, 32 COMPONENT_LINK_DECORATOR, 33 COMPONENT_PROP_DECORATOR, 34 COMPONENT_STORAGE_PROP_DECORATOR, 35 COMPONENT_STORAGE_LINK_DECORATOR, 36 COMPONENT_PROVIDE_DECORATOR, 37 COMPONENT_CONSUME_DECORATOR, 38 COMPONENT_OBJECT_LINK_DECORATOR, 39 COMPONENT_OBSERVED_DECORATOR, 40 COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, 41 COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, 42 COMPONENT_CONCURRENT_DECORATOR, 43 CHECK_EXTEND_DECORATORS, 44 COMPONENT_STYLES_DECORATOR, 45 RESOURCE_NAME_TYPE, 46 COMPONENT_BUTTON, 47 COMPONENT_TOGGLE, 48 COMPONENT_BUILDERPARAM_DECORATOR, 49 ESMODULE, 50 CARD_ENABLE_DECORATORS, 51 CARD_LOG_TYPE_DECORATORS, 52 JSBUNDLE, 53 COMPONENT_DECORATOR_REUSEABLE, 54 STRUCT_DECORATORS, 55 STRUCT_CONTEXT_METHOD_DECORATORS, 56 CHECK_COMPONENT_EXTEND_DECORATOR, 57 CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR, 58 CLASS_TRACK_DECORATOR, 59 COMPONENT_REQUIRE_DECORATOR, 60 COMPONENT_SENDABLE_DECORATOR, 61 CLASS_MIN_TRACK_DECORATOR, 62 MIN_OBSERVED, 63 COMPONENT_NON_DECORATOR, 64 COMPONENT_DECORATOR_COMPONENT_V2, 65 OBSERVED, 66 SENDABLE, 67 TYPE, 68 COMPONENT_LOCAL_BUILDER_DECORATOR 69} from './pre_define'; 70import { 71 INNER_COMPONENT_NAMES, 72 AUTOMIC_COMPONENT, 73 SINGLE_CHILD_COMPONENT, 74 SPECIFIC_CHILD_COMPONENT, 75 BUILDIN_STYLE_NAMES, 76 EXTEND_ATTRIBUTE, 77 GLOBAL_STYLE_FUNCTION, 78 STYLES_ATTRIBUTE, 79 CUSTOM_BUILDER_METHOD, 80 GLOBAL_CUSTOM_BUILDER_METHOD, 81 INNER_CUSTOM_BUILDER_METHOD, 82 INNER_STYLE_FUNCTION, 83 INNER_CUSTOM_LOCALBUILDER_METHOD 84} from './component_map'; 85import { 86 LogType, 87 LogInfo, 88 componentInfo, 89 addLog, 90 hasDecorator, 91 storedFileInfo, 92 ExtendResult 93} from './utils'; 94import { globalProgram, projectConfig, abilityPagesFullPath } from '../main'; 95import { 96 collectExtend, 97 isExtendFunction, 98 transformLog, 99 validatorCard 100} from './process_ui_syntax'; 101import { 102 isBuilderOrLocalBuilder, 103 builderConditionType 104} from './process_component_class'; 105import { stateObjectCollection } from './process_component_member'; 106import { collectSharedModule } from './fast_build/ark_compiler/check_shared_module'; 107import constantDefine from './constant_define'; 108import processStructComponentV2, { StructInfo } from './process_struct_componentV2'; 109import { getMessageCollection } from './log_message_collection'; 110 111export class ComponentCollection { 112 localStorageName: string = null; 113 localStorageNode: ts.Identifier | ts.ObjectLiteralExpression = null; 114 localSharedStorage: ts.Node = null; 115 entryComponentPos: number = null; 116 entryComponent: string = null; 117 previewComponent: Array<string> = []; 118 customDialogs: Set<string> = new Set([]); 119 customComponents: Set<string> = new Set([]); 120 currentClassName: string = null; 121} 122 123export class IComponentSet { 124 properties: Set<string> = new Set(); 125 regulars: Set<string> = new Set(); 126 states: Set<string> = new Set(); 127 links: Set<string> = new Set(); 128 props: Set<string> = new Set(); 129 storageProps: Set<string> = new Set(); 130 storageLinks: Set<string> = new Set(); 131 provides: Set<string> = new Set(); 132 consumes: Set<string> = new Set(); 133 objectLinks: Set<string> = new Set(); 134 localStorageLink: Map<string, Set<string>> = new Map(); 135 localStorageProp: Map<string, Set<string>> = new Map(); 136 builderParams: Set<string> = new Set(); 137 builderParamData: Set<string> = new Set(); 138 propData: Set<string> = new Set(); 139 regularInit: Set<string> = new Set(); 140 stateInit: Set<string> = new Set(); 141 provideInit: Set<string> = new Set(); 142 privateCollection: Set<string> = new Set(); 143} 144 145export let componentCollection: ComponentCollection = new ComponentCollection(); 146 147export const observedClassCollection: Set<string> = new Set(); 148export const enumCollection: Set<string> = new Set(); 149export const classMethodCollection: Map<string, Map<string, Set<string>>> = new Map(); 150export const dollarCollection: Set<string> = new Set(); 151 152export const propertyCollection: Map<string, Set<string>> = new Map(); 153export const stateCollection: Map<string, Set<string>> = new Map(); 154export const linkCollection: Map<string, Set<string>> = new Map(); 155export const propCollection: Map<string, Set<string>> = new Map(); 156export const regularCollection: Map<string, Set<string>> = new Map(); 157export const storagePropCollection: Map<string, Set<string>> = new Map(); 158export const storageLinkCollection: Map<string, Set<string>> = new Map(); 159export const provideCollection: Map<string, Set<string>> = new Map(); 160export const consumeCollection: Map<string, Set<string>> = new Map(); 161export const objectLinkCollection: Map<string, Set<string>> = new Map(); 162export const builderParamObjectCollection: Map<string, Set<string>> = new Map(); 163export const localStorageLinkCollection: Map<string, Map<string, Set<string>>> = new Map(); 164export const localStoragePropCollection: Map<string, Map<string, Set<string>>> = new Map(); 165export const builderParamInitialization: Map<string, Set<string>> = new Map(); 166export const propInitialization: Map<string, Set<string>> = new Map(); 167export const regularInitialization: Map<string, Set<string>> = new Map(); 168export const stateInitialization: Map<string, Set<string>> = new Map(); 169export const provideInitialization: Map<string, Set<string>> = new Map(); 170export const privateCollection: Map<string, Set<string>> = new Map(); 171 172export const isStaticViewCollection: Map<string, boolean> = new Map(); 173 174export const useOSFiles: Set<string> = new Set(); 175export const sourcemapNamesCollection: Map<string, Map<string, string>> = new Map(); 176export const originalImportNamesMap: Map<string, string> = new Map(); 177 178export function validateUISyntax(source: string, content: string, filePath: string, 179 fileQuery: string, sourceFile: ts.SourceFile = null): LogInfo[] { 180 let log: LogInfo[] = []; 181 if (process.env.compileMode === 'moduleJson' || 182 path.resolve(filePath) !== path.resolve(projectConfig.projectPath || '', 'app.ets')) { 183 componentCollection = new ComponentCollection(); 184 const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery, sourceFile); 185 if (res) { 186 log = log.concat(res); 187 } 188 const allComponentNames: Set<string> = 189 new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]); 190 checkUISyntax(filePath, allComponentNames, content, log, sourceFile, fileQuery); 191 componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item)); 192 } 193 194 if (projectConfig.compileMode === ESMODULE) { 195 collectSharedModule(source, filePath, sourceFile); 196 } 197 198 return log; 199} 200 201function checkComponentDecorator(source: string, filePath: string, 202 fileQuery: string, sourceFile: ts.SourceFile | null): LogInfo[] | null { 203 const log: LogInfo[] = []; 204 if (!sourceFile) { 205 sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 206 } 207 if (sourceFile && sourceFile.statements && sourceFile.statements.length) { 208 const result: DecoratorResult = { 209 entryCount: 0, 210 previewCount: 0 211 }; 212 sourceFile.statements.forEach((item, index, arr) => { 213 if (isObservedClass(item)) { 214 // @ts-ignore 215 observedClassCollection.add(item.name.getText()); 216 } 217 if (ts.isEnumDeclaration(item) && item.name) { 218 enumCollection.add(item.name.getText()); 219 } 220 if (ts.isStructDeclaration(item)) { 221 validateStructSpec(item, result, log, sourceFile); 222 } 223 if (ts.isMissingDeclaration(item)) { 224 const decorators = ts.getAllDecorators(item); 225 for (let i = 0; i < decorators.length; i++) { 226 if (decorators[i] && /struct/.test(decorators[i].getText())) { 227 const message: string = `Please use a valid decorator.`; 228 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 229 break; 230 } 231 } 232 } 233 }); 234 if (process.env.compileTool === 'rollup') { 235 if (result.entryCount > 0) { 236 storedFileInfo.wholeFileInfo[filePath].hasEntry = true; 237 } else { 238 storedFileInfo.wholeFileInfo[filePath].hasEntry = false; 239 } 240 } 241 validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview, 242 !!projectConfig.checkEntry, log); 243 } 244 245 return log.length ? log : null; 246} 247 248function validateStructSpec(item: ts.StructDeclaration, result: DecoratorResult, log: LogInfo[], 249 sourceFile: ts.SourceFile | null): void { 250 if (item.name && ts.isIdentifier(item.name)) { 251 const componentName: string = item.name.getText(); 252 componentCollection.customComponents.add(componentName); 253 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 254 if (decorators && decorators.length) { 255 checkDecorators(decorators, result, item.name, log, sourceFile, item); 256 } else { 257 const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`; 258 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 259 } 260 } else { 261 const message: string = `A struct must have a name.`; 262 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 263 } 264} 265 266function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string, 267 fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void { 268 if (result.previewCount > 10 && (fileQuery === '?entry' || process.env.watchMode === 'true')) { 269 log.push({ 270 type: LogType.ERROR, 271 message: `A page can contain at most 10 '@Preview' decorators.`, 272 fileName: fileName 273 }); 274 } 275 if (result.entryCount > 1 && fileQuery === '?entry') { 276 log.push({ 277 type: LogType.ERROR, 278 message: `A page can't contain more than one '@Entry' decorator`, 279 fileName: fileName 280 }); 281 } 282 if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 && 283 fileQuery === '?entry') { 284 log.push({ 285 type: LogType.ERROR, 286 message: `A page which is being previewed must have one and only one '@Entry' ` + 287 `decorator, or at least one '@Preview' decorator.`, 288 fileName: fileName 289 }); 290 } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry' && 291 !abilityPagesFullPath.has(path.resolve(fileName).toLowerCase())) { 292 log.push({ 293 type: LogType.ERROR, 294 message: `A page configured in '${projectConfig.pagesJsonFileName} or build-profile.json5' must have one and only one '@Entry' decorator.` + 295 `Solutions:>Please make sure that the splash page has one and only one '@Entry' decorator.`, 296 fileName: fileName 297 }); 298 } 299} 300 301export function isObservedClass(node: ts.Node): boolean { 302 if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) { 303 return true; 304 } 305 return false; 306} 307 308export function isCustomDialogClass(node: ts.Node): boolean { 309 if (ts.isStructDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) { 310 return true; 311 } 312 return false; 313} 314 315interface DecoratorResult { 316 entryCount: number; 317 previewCount: number; 318} 319 320function checkDecorators(decorators: readonly ts.Decorator[], result: DecoratorResult, 321 component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile, node: ts.StructDeclaration): void { 322 const componentName: string = component.getText(); 323 const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(componentName); 324 let hasInnerComponentDecorator: boolean = false; 325 decorators.forEach((element) => { 326 let name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim(); 327 if (element.expression && element.expression.expression && ts.isIdentifier(element.expression.expression)) { 328 name = '@' + element.expression.expression.getText(); 329 } 330 if (INNER_COMPONENT_DECORATORS.has(name)) { 331 hasInnerComponentDecorator = true; 332 switch (name) { 333 case COMPONENT_DECORATOR_ENTRY: 334 checkEntryComponent(node, log, sourceFile); 335 result.entryCount++; 336 componentCollection.entryComponent = componentName; 337 componentCollection.entryComponentPos = node.getStart(); 338 collectLocalStorageName(element); 339 break; 340 case COMPONENT_DECORATOR_PREVIEW: 341 result.previewCount++; 342 componentCollection.previewComponent.push(componentName); 343 break; 344 case COMPONENT_DECORATOR_COMPONENT_V2: 345 structInfo.isComponentV2 = true; 346 break; 347 case COMPONENT_DECORATOR_COMPONENT: 348 structInfo.isComponentV1 = true; 349 break; 350 case COMPONENT_DECORATOR_CUSTOM_DIALOG: 351 componentCollection.customDialogs.add(componentName); 352 structInfo.isCustomDialog = true; 353 break; 354 case COMPONENT_DECORATOR_REUSEABLE: 355 storedFileInfo.getCurrentArkTsFile().recycleComponents.add(componentName); 356 structInfo.isReusable = true; 357 break; 358 } 359 } else { 360 validateInvalidStructDecorator(element, componentName, log, sourceFile); 361 } 362 }); 363 validateStruct(hasInnerComponentDecorator, componentName, component, log, sourceFile, structInfo); 364} 365 366function validateInvalidStructDecorator(element: ts.Decorator, componentName: string, log: LogInfo[], 367 sourceFile: ts.SourceFile): void { 368 const pos: number = element.expression ? element.expression.pos : element.pos; 369 const message: string = `The struct '${componentName}' use invalid decorator.`; 370 addLog(LogType.WARN, message, pos, log, sourceFile); 371} 372 373function validateStruct(hasInnerComponentDecorator: boolean, componentName: string, component: ts.Identifier, 374 log: LogInfo[], sourceFile: ts.SourceFile, structInfo: StructInfo): void { 375 if (!hasInnerComponentDecorator) { 376 const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`; 377 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 378 } else if (structInfo.isComponentV2 && (structInfo.isComponentV1 || structInfo.isReusable || structInfo.isCustomDialog) ) { 379 const message: string = `The struct '${componentName}' can not be decorated with '@ComponentV2' ` + 380 `and '@Component', '@Reusable', '@CustomDialog' at the same time.`; 381 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 382 } 383 if (BUILDIN_STYLE_NAMES.has(componentName)) { 384 const message: string = `The struct '${componentName}' cannot have the same name ` + 385 `as the built-in attribute '${componentName}'.`; 386 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 387 } 388 if (INNER_COMPONENT_NAMES.has(componentName)) { 389 const message: string = `The struct '${componentName}' cannot have the same name ` + 390 `as the built-in component '${componentName}'.`; 391 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 392 } 393} 394 395function checkConcurrentDecorator(node: ts.FunctionDeclaration | ts.MethodDeclaration, log: LogInfo[], 396 sourceFile: ts.SourceFile): void { 397 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 398 if (projectConfig.compileMode === JSBUNDLE) { 399 const message: string = `@Concurrent can only be used in ESMODULE compile mode.`; 400 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 401 } 402 if (ts.isMethodDeclaration(node)) { 403 const message: string = `@Concurrent can not be used on method. please use it on function declaration.`; 404 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 405 } 406 if (node.asteriskToken) { 407 let hasAsync: boolean = false; 408 const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; 409 const checkAsyncModifier = (modifier: ts.Modifier) : boolean => modifier.kind === ts.SyntaxKind.AsyncKeyword; 410 modifiers && (hasAsync = modifiers.some(checkAsyncModifier)); 411 const funcKind: string = hasAsync ? 'Async generator' : 'Generator'; 412 const message: string = `@Concurrent can not be used on ${funcKind} function declaration.`; 413 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 414 } 415} 416 417function collectLocalStorageName(node: ts.Decorator): void { 418 if (node && node.expression && ts.isCallExpression(node.expression)) { 419 if (node.expression.arguments && node.expression.arguments.length) { 420 node.expression.arguments.forEach((item: ts.Node, index: number) => { 421 if (ts.isIdentifier(item) && index === 0) { 422 componentCollection.localStorageName = item.getText(); 423 componentCollection.localStorageNode = item; 424 } else if (ts.isObjectLiteralExpression(item) && index === 0) { 425 componentCollection.localStorageName = null; 426 componentCollection.localStorageNode = item; 427 } else { 428 componentCollection.localSharedStorage = item; 429 } 430 }); 431 } 432 } else { 433 componentCollection.localStorageName = null; 434 componentCollection.localStorageNode = null; 435 } 436} 437 438function checkUISyntax(filePath: string, allComponentNames: Set<string>, content: string, 439 log: LogInfo[], sourceFile: ts.SourceFile | null, fileQuery: string): void { 440 if (!sourceFile) { 441 sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 442 } 443 visitAllNode(sourceFile, sourceFile, allComponentNames, log, false, false, false, false, fileQuery, false, false); 444} 445 446function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set<string>, 447 log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean, 448 isComponentV2: boolean, fileQuery: string, isObservedV1Class: boolean, isSendableClass: boolean): void { 449 if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 450 structContext = true; 451 const structName: string = node.name.escapedText.toString(); 452 const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(structName); 453 if (structInfo.isComponentV2) { 454 processStructComponentV2.parseComponentProperty(node, structInfo, log, sourceFileNode); 455 isComponentV2 = true; 456 } else { 457 collectComponentProps(node, structInfo); 458 } 459 } 460 if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 461 classContext = true; 462 [isObservedV1Class, isObservedClass, isSendableClass] = parseClassDecorator(node, sourceFileNode, log); 463 } 464 if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) { 465 methodDecoratorCollect(node); 466 if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) { 467 // ark compiler's feature 468 checkConcurrentDecorator(node, log, sourceFileNode); 469 } 470 validateFunction(node, sourceFileNode, log); 471 } 472 checkDecoratorCount(node, sourceFileNode, log); 473 checkDecorator(sourceFileNode, node, log, structContext, classContext, isObservedClass, isComponentV2, 474 isObservedV1Class, isSendableClass); 475 node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log, 476 structContext, classContext, isObservedClass, isComponentV2, fileQuery, isObservedV1Class, isSendableClass)); 477 structContext = false; 478 classContext = false; 479 isObservedClass = false; 480 isObservedV1Class = false; 481 isSendableClass = false; 482} 483 484const v1ComponentDecorators: string[] = [ 485 'State', 'Prop', 'Link', 'Provide', 'Consume', 486 'StorageLink', 'StorageProp', 'LocalStorageLink', 'LocalStorageProp' 487]; 488const v2ComponentDecorators: string[] = [ 489 'Local', 'Param', 'Event', 'Provider', 'Consumer' 490]; 491function validatePropertyInStruct(structContext: boolean, decoratorNode: ts.Identifier, 492 decoratorName: string, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 493 if (structContext) { 494 const isV1Decorator: boolean = v1ComponentDecorators.includes(decoratorName); 495 const isV2Decorator: boolean = v2ComponentDecorators.includes(decoratorName); 496 if (!isV1Decorator && !isV2Decorator) { 497 return; 498 } 499 const classResult: ClassDecoratorResult = new ClassDecoratorResult(); 500 const propertyNode: ts.PropertyDeclaration = getPropertyNodeByDecorator(decoratorNode); 501 if (propertyNode && propertyNode.type && globalProgram.checker) { 502 validatePropertyType(propertyNode.type, classResult); 503 } 504 let message: string; 505 if (isV1Decorator && classResult.hasObservedV2) { 506 message = `The type of the @${decoratorName} property can not be a class decorated with @ObservedV2.`; 507 addLog(LogType.ERROR, message, decoratorNode.getStart(), log, sourceFileNode); 508 return; 509 } 510 if (isV2Decorator && classResult.hasObserved) { 511 message = `The type of the @${decoratorName} property can not be a class decorated with @Observed.`; 512 addLog(LogType.ERROR, message, decoratorNode.getStart(), log, sourceFileNode); 513 return; 514 } 515 } 516} 517 518function getPropertyNodeByDecorator(decoratorNode: ts.Identifier): ts.PropertyDeclaration { 519 if (ts.isDecorator(decoratorNode.parent) && ts.isPropertyDeclaration(decoratorNode.parent.parent)) { 520 return decoratorNode.parent.parent; 521 } 522 if (ts.isCallExpression(decoratorNode.parent) && ts.isDecorator(decoratorNode.parent.parent) && 523 ts.isPropertyDeclaration(decoratorNode.parent.parent.parent)) { 524 return decoratorNode.parent.parent.parent; 525 } 526 return undefined; 527} 528 529function validatePropertyType(node: ts.TypeNode, classResult: ClassDecoratorResult): void { 530 if (ts.isUnionTypeNode(node) && node.types && node.types.length) { 531 node.types.forEach((item: ts.TypeNode) => { 532 validatePropertyType(item, classResult); 533 }); 534 } 535 if (ts.isTypeReferenceNode(node) && node.typeName) { 536 const typeNode: ts.Type = globalProgram.checker.getTypeAtLocation(node.typeName); 537 parsePropertyType(typeNode, classResult); 538 } 539} 540 541function parsePropertyType(type: ts.Type, classResult: ClassDecoratorResult): void { 542 // @ts-ignore 543 if (type && type.types && type.types.length) { 544 // @ts-ignore 545 type.types.forEach((item: ts.Type) => { 546 parsePropertyType(item, classResult); 547 }); 548 } 549 if (type && type.symbol && type.symbol.valueDeclaration && 550 ts.isClassDeclaration(type.symbol.valueDeclaration)) { 551 const result: ClassDecoratorResult = getClassDecoratorResult(type.symbol.valueDeclaration); 552 if (result.hasObserved) { 553 classResult.hasObserved = result.hasObserved; 554 } 555 if (result.hasObservedV2) { 556 classResult.hasObservedV2 = result.hasObservedV2; 557 } 558 } 559} 560 561function checkDecoratorCount(node: ts.Node, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 562 if (ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isMethodDeclaration(node)) { 563 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 564 let innerDecoratorCount: number = 0; 565 const exludeDecorators: string[] = ['@Require', '@Once']; 566 const v1MethodDecorators: string[] = ['@Builder', '@Styles']; 567 const v1DecoratorMap: Map<string, number> = new Map<string, number>(); 568 const v2DecoratorMap: Map<string, number> = new Map<string, number>(); 569 let checkDecoratorCount: number = 0; 570 decorators.forEach((item: ts.Decorator) => { 571 const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, ''); 572 if (!exludeDecorators.includes(decoratorName) && (constantDefine.DECORATOR_V2.includes(decoratorName) || 573 decoratorName === '@BuilderParam')) { 574 const count: number = v2DecoratorMap.get(decoratorName) || 0; 575 v2DecoratorMap.set(decoratorName, count + 1); 576 return; 577 } 578 if (v1MethodDecorators.includes(decoratorName)) { 579 const count: number = v1DecoratorMap.get(decoratorName) || 0; 580 v1DecoratorMap.set(decoratorName, count + 1); 581 return; 582 } 583 if (decoratorName === COMPONENT_LOCAL_BUILDER_DECORATOR && decorators.length > 1) { 584 checkDecoratorCount = checkDecoratorCount + 1; 585 return; 586 } 587 }); 588 const v2DecoratorMapKeys: string[] = Array.from(v2DecoratorMap.keys()); 589 const v2DecoratorMapValues: number[] = Array.from(v2DecoratorMap.values()); 590 const v1DecoratorMapKeys: string[] = Array.from(v1DecoratorMap.keys()); 591 const v1DecoratorMapValues: number[] = Array.from(v1DecoratorMap.values()); 592 innerDecoratorCount = v2DecoratorMapKeys.length + v1DecoratorMapKeys.length; 593 getMessageCollection().checkLocalBuilderDecoratorCount(node, sourceFileNode, checkDecoratorCount, log); 594 if (innerDecoratorCount > 1) { 595 const message: string = 'The member property or method can not be decorated by multiple built-in decorators.'; 596 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 597 } 598 const v2Duplicate: boolean = v2DecoratorMapValues.length && 599 v2DecoratorMapValues.some((count: number) => count > 1); 600 const v1Duplicate: boolean = v1DecoratorMapValues.length && 601 v1DecoratorMapValues.some((count: number) => count > 1); 602 const duplicateMessage: string = 'Duplicate decorators for method are not allowed.'; 603 if (v2Duplicate || v1Duplicate) { 604 addLog(v1Duplicate ? LogType.WARN : LogType.ERROR, duplicateMessage, node.getStart(), log, sourceFileNode); 605 } 606 } 607} 608 609function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionDeclaration): void { 610 const extendResult: ExtendResult = { decoratorName: '', componentName: '' }; 611 const builderCondition: builderConditionType = { 612 isBuilder: false, 613 isLocalBuilder: false 614 }; 615 if (isBuilderOrLocalBuilder(node, builderCondition)) { 616 if (builderCondition.isBuilder) { 617 CUSTOM_BUILDER_METHOD.add(node.name.getText()); 618 if (ts.isFunctionDeclaration(node)) { 619 GLOBAL_CUSTOM_BUILDER_METHOD.add(node.name.getText()); 620 } else { 621 INNER_CUSTOM_BUILDER_METHOD.add(node.name.getText()); 622 } 623 } else if (builderCondition.isLocalBuilder) { 624 INNER_CUSTOM_LOCALBUILDER_METHOD.add(node.name.getText()); 625 } 626 } else if (ts.isFunctionDeclaration(node) && isExtendFunction(node, extendResult)) { 627 if (extendResult.decoratorName === CHECK_COMPONENT_EXTEND_DECORATOR) { 628 collectExtend(EXTEND_ATTRIBUTE, extendResult.componentName, node.name.getText()); 629 } 630 if (extendResult.decoratorName === CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 631 collectExtend(storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute, 632 extendResult.componentName, node.name.getText()); 633 } 634 } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { 635 collectStyles(node); 636 } 637} 638 639function collectStyles(node: ts.FunctionLikeDeclarationBase): void { 640 if (ts.isBlock(node.body) && node.body.statements) { 641 if (ts.isFunctionDeclaration(node)) { 642 GLOBAL_STYLE_FUNCTION.set(node.name.getText(), node.body); 643 } else { 644 INNER_STYLE_FUNCTION.set(node.name.getText(), node.body); 645 } 646 STYLES_ATTRIBUTE.add(node.name.getText()); 647 BUILDIN_STYLE_NAMES.add(node.name.getText()); 648 } 649} 650 651function validateFunction(node: ts.MethodDeclaration | ts.FunctionDeclaration, 652 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 653 if (ts.isFunctionDeclaration(node)) { 654 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 655 const decoratorMap: Map<string, number> = new Map<string, number>(); 656 decorators.forEach((item: ts.Decorator) => { 657 const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, '') 658 .replace(/^@/, '').trim(); 659 const count: number = decoratorMap.get(decoratorName) || 0; 660 decoratorMap.set(decoratorName, count + 1); 661 }); 662 const decoratorValues: number[] = Array.from(decoratorMap.values()); 663 const hasDuplicate: boolean = decoratorValues.length && 664 decoratorValues.some((count: number) => count > 1); 665 if (hasDuplicate) { 666 const message: string = 'Duplicate decorators for function are not allowed.'; 667 addLog(LogType.WARN, message, node.getStart(), log, sourceFileNode); 668 } 669 const decoratorKeys: string[] = Array.from(decoratorMap.keys()); 670 if (decoratorKeys.length > 1 || decoratorKeys.includes('LocalBuilder')) { 671 const message: string = 'A function can only be decorated by one of the ' + 672 `'AnimatableExtend, Builder, Extend, Styles, Concurrent and Sendable'.`; 673 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 674 } 675 } 676} 677 678function checkDecorator(sourceFileNode: ts.SourceFile, node: ts.Node, 679 log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean, 680 isComponentV2: boolean, isObservedV1Class: boolean, isSendableClass: boolean): void { 681 if (ts.isIdentifier(node) && (ts.isDecorator(node.parent) || 682 (ts.isCallExpression(node.parent) && ts.isDecorator(node.parent.parent)))) { 683 const decoratorName: string = node.escapedText.toString(); 684 setLocalBuilderInFile(decoratorName); 685 validateStructDecorator(sourceFileNode, node, log, structContext, decoratorName, isComponentV2); 686 validateMethodDecorator(sourceFileNode, node, log, structContext, decoratorName); 687 validateClassDecorator(sourceFileNode, node, log, classContext, decoratorName, isObservedClass, 688 isObservedV1Class, isSendableClass); 689 validatePropertyInStruct(structContext, node, decoratorName, sourceFileNode, log); 690 return; 691 } 692 if (ts.isDecorator(node)) { 693 validateSingleDecorator(node, sourceFileNode, log, isComponentV2); 694 } 695} 696 697function setLocalBuilderInFile(decoratorName: string): void { 698 if (decoratorName === 'LocalBuilder') { 699 storedFileInfo.hasLocalBuilderInFile = true; 700 } 701} 702 703function validateSingleDecorator(node: ts.Decorator, sourceFileNode: ts.SourceFile, 704 log: LogInfo[], isComponentV2: boolean): void { 705 const decoratorName: string = node.getText().replace(/\([^\(\)]*\)/, ''); 706 if (decoratorName === constantDefine.COMPUTED_DECORATOR && node.parent && !ts.isGetAccessor(node.parent)) { 707 const message: string = `@Computed can only decorate 'GetAccessor'.`; 708 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 709 return; 710 } 711 const partialDecoratorCollection: string[] = [constantDefine.MONITOR_DECORATOR, COMPONENT_LOCAL_BUILDER_DECORATOR]; 712 if (partialDecoratorCollection.includes(decoratorName) && node.parent && 713 !ts.isMethodDeclaration(node.parent)) { 714 const message: string = `'${decoratorName}' can only decorate method.`; 715 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 716 return; 717 } 718 if (isMemberForComponentV2(decoratorName, isComponentV2) && node.parent && 719 !ts.isPropertyDeclaration(node.parent)) { 720 const message: string = `'${decoratorName}' can only decorate member property.`; 721 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 722 return; 723 } 724} 725 726function isMemberForComponentV2(decoratorName: string, isComponentV2: boolean): boolean { 727 return constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(decoratorName) || 728 (isComponentV2 && decoratorName === '@BuilderParam'); 729} 730 731const classDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, MIN_OBSERVED]; 732const classMemberDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, TYPE, 733 constantDefine.MONITOR, constantDefine.COMPUTED]; 734 735function validTypeCallback(node: ts.Identifier): boolean { 736 let isSdkPath: boolean = true; 737 if (globalProgram.checker && process.env.compileTool === 'rollup') { 738 const symbolObj: ts.Symbol = getSymbolIfAliased(node); 739 const fileName: string = symbolObj?.valueDeclaration?.getSourceFile()?.fileName; 740 isSdkPath = /@ohos.arkui.*/.test(fileName); 741 } 742 return isSdkPath; 743} 744 745function isTypeFromSdkCallback(classContext: boolean, decoratorName: string, isTypeFromSdk: boolean): boolean { 746 if (!classContext && decoratorName === TYPE && isTypeFromSdk) { 747 return true; 748 } 749 return false; 750} 751 752function validateClassDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 753 classContext: boolean, decoratorName: string, isObservedClass: boolean, isObservedV1Class: boolean, 754 isSendableClass: boolean): void { 755 const isTypeFromSdk: boolean = validTypeCallback(node); 756 if (!classContext && (classDecorators.includes(decoratorName) || isTypeFromSdkCallback(classContext, decoratorName, isTypeFromSdk))) { 757 const message: string = `The '@${decoratorName}' decorator can only be used in 'class'.`; 758 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 759 } else if (classContext && classMemberDecorators.includes(decoratorName)) { 760 validateMemberInClass(isObservedClass, decoratorName, node, log, sourceFileNode, isObservedV1Class, isSendableClass, isTypeFromSdk); 761 } 762} 763 764function validateMemberInClass(isObservedClass: boolean, decoratorName: string, node: ts.Identifier, 765 log: LogInfo[], sourceFileNode: ts.SourceFile, isObservedV1Class: boolean, isSendableClass: boolean, isTypeFromSdk: boolean): void { 766 if (decoratorName === CLASS_TRACK_DECORATOR) { 767 if (isObservedClass) { 768 const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with ObservedV2.`; 769 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 770 } 771 return; 772 } 773 if (decoratorName === TYPE) { 774 if (isTypeFromSdk) { 775 validType(sourceFileNode, node, log, decoratorName, isObservedV1Class, isSendableClass); 776 } 777 return; 778 } 779 if (!isObservedClass || !isPropertyForTrace(node, decoratorName)) { 780 const info: string = decoratorName === CLASS_MIN_TRACK_DECORATOR ? 'variables' : 'method'; 781 const message: string = `The '@${decoratorName}' can decorate only member ${info} within a 'class' decorated with ObservedV2.`; 782 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 783 return; 784 } 785} 786 787function validType(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], decoratorName: string, 788 isObservedV1Class: boolean, isSendableClass: boolean): void { 789 if (isSendableClass) { 790 const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Sendable.`; 791 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 792 } 793 if (isObservedV1Class) { 794 const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Observed.`; 795 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 796 } 797 if (ts.isDecorator(node.parent?.parent) && !ts.isPropertyDeclaration(node.parent?.parent?.parent)) { 798 const message: string = `The '@${decoratorName}' can decorate only member variables in a 'class'.`; 799 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 800 } 801} 802 803function isPropertyForTrace(node: ts.Identifier, decoratorName: string): boolean { 804 if (decoratorName === CLASS_MIN_TRACK_DECORATOR && ts.isDecorator(node.parent) && 805 !ts.isPropertyDeclaration(node.parent.parent)) { 806 return false; 807 } 808 return true; 809} 810 811class ClassDecoratorResult { 812 hasObserved: boolean = false; 813 hasObservedV2: boolean = false; 814 hasSendable: boolean = false; 815} 816 817function parseClassDecorator(node: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, 818 log: LogInfo[]): [boolean, boolean, boolean] { 819 const classResult: ClassDecoratorResult = getClassDecoratorResult(node); 820 validateMutilObserved(node, classResult, sourceFileNode, log); 821 if (classResult.hasObserved || classResult.hasObservedV2) { 822 parseInheritClass(node, classResult, sourceFileNode, log); 823 } 824 return [classResult.hasObserved, classResult.hasObservedV2, classResult.hasSendable]; 825} 826 827function getClassDecoratorResult(node: ts.ClassDeclaration): ClassDecoratorResult { 828 const classResult: ClassDecoratorResult = new ClassDecoratorResult(); 829 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 830 decorators.forEach((item: ts.Decorator) => { 831 if (ts.isIdentifier(item.expression)) { 832 const decoratorName: string = item.expression.escapedText.toString(); 833 switch (decoratorName) { 834 case MIN_OBSERVED: 835 classResult.hasObservedV2 = true; 836 break; 837 case OBSERVED: 838 classResult.hasObserved = true; 839 break; 840 case SENDABLE: 841 classResult.hasSendable = true; 842 } 843 } 844 }); 845 return classResult; 846} 847 848function validateMutilObserved(node: ts.ClassDeclaration, classResult: ClassDecoratorResult, 849 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 850 if (classResult.hasObserved && classResult.hasObservedV2) { 851 const message: string = `A class can not be decorated by '@Observed' and '@ObservedV2' at the same time.`; 852 addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode); 853 } 854} 855 856function parseInheritClass(node: ts.ClassDeclaration, childClassResult: ClassDecoratorResult, 857 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 858 if (globalProgram.checker && process.env.compileTool === 'rollup' && node.heritageClauses) { 859 for (const heritageClause of node.heritageClauses) { 860 if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && heritageClause.types && 861 heritageClause.types.length) { 862 getClassNode(heritageClause.types[0].expression, childClassResult, node, sourceFileNode, log); 863 } 864 } 865 } 866} 867 868function getClassNode(parentType: ts.Node, childClassResult: ClassDecoratorResult, 869 childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 870 const symbol: ts.Symbol = parentType && getSymbolIfAliased(parentType); 871 if (symbol && symbol.valueDeclaration) { 872 if (ts.isClassDeclaration(symbol.valueDeclaration)) { 873 validateInheritClassDecorator(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); 874 return; 875 } 876 if (ts.isPropertyAssignment(symbol.valueDeclaration)) { 877 getClassNode(symbol.valueDeclaration.initializer, childClassResult, childClass, sourceFileNode, log); 878 return; 879 } 880 if (ts.isShorthandPropertyAssignment(symbol.valueDeclaration)) { 881 parseShorthandPropertyForClass(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); 882 return; 883 } 884 } 885} 886 887function parseShorthandPropertyForClass(node: ts.ShorthandPropertyAssignment, childClassResult: ClassDecoratorResult, 888 childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 889 const shortSymbol: ts.Symbol = globalProgram.checker.getShorthandAssignmentValueSymbol(node); 890 if (shortSymbol && shortSymbol.valueDeclaration && ts.isClassDeclaration(shortSymbol.valueDeclaration)) { 891 validateInheritClassDecorator(shortSymbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log); 892 } 893} 894 895function getSymbolIfAliased(node: ts.Node): ts.Symbol { 896 const symbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(node); 897 if (symbol && (symbol.getFlags() & ts.SymbolFlags.Alias) !== 0) { 898 return globalProgram.checker.getAliasedSymbol(symbol); 899 } 900 return symbol; 901} 902 903function validateInheritClassDecorator(parentNode: ts.ClassDeclaration, childClassResult: ClassDecoratorResult, 904 childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 905 const parentClassResult: ClassDecoratorResult = getClassDecoratorResult(parentNode); 906 if (childClassResult.hasObservedV2 && parentClassResult.hasObserved) { 907 const message: string = `Because the current class is decorated by '@ObservedV2', ` + 908 `it can not inherit a class decorated by '@Observed'.`; 909 addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode); 910 return; 911 } 912 if (childClassResult.hasObserved && parentClassResult.hasObservedV2) { 913 const message: string = `Because the current class is decorated by '@Observed', ` + 914 `it can not inherit a class decorated by '@ObservedV2'.`; 915 addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode); 916 return; 917 } 918} 919 920function validateStructDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 921 structContext: boolean, decoratorName: string, isComponentV2: boolean): void { 922 const name: string = `@${decoratorName}`; 923 if (structContext) { 924 if (isComponentV2) { 925 if (constantDefine.COMPONENT_MEMBER_DECORATOR_V1.includes(name)) { 926 const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@Component'.`; 927 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 928 } 929 } else if (constantDefine.DECORATOR_V2.includes(name)) { 930 const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`; 931 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 932 } 933 } else if (STRUCT_DECORATORS.has(name) || constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(name)) { 934 const message: string = `The '@${decoratorName}' decorator can only be used with 'struct'.`; 935 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 936 } 937} 938 939function validateMethodDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 940 structContext: boolean, decoratorName: string): void { 941 let message: string; 942 if (ts.isMethodDeclaration(node.parent.parent) || 943 (ts.isDecorator(node.parent.parent) && ts.isMethodDeclaration(node.parent.parent.parent))) { 944 if (!structContext && STRUCT_CONTEXT_METHOD_DECORATORS.has(`@${decoratorName}`)) { 945 message = `The '@${decoratorName}' decorator can only be used in 'struct'.`; 946 } 947 if (CHECK_EXTEND_DECORATORS.includes(decoratorName)) { 948 message = `The '@${decoratorName}' decorator can not be a member property method of a 'class' or 'struct'.`; 949 } 950 if (message) { 951 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 952 } 953 } 954} 955 956export function isSendableClassDeclaration(classDeclarationNode: ts.ClassDeclaration): boolean { 957 return hasDecorator(classDeclarationNode, COMPONENT_SENDABLE_DECORATOR) || 958 (classDeclarationNode.members && classDeclarationNode.members.some((member: ts.Node) => { 959 // Check if the constructor has "use sendable" as the first statement 960 // (Sendable classes already transformed by process_ui_syntax.ts) 961 if (ts.isConstructorDeclaration(member)) { 962 if (!(member as ts.ConstructorDeclaration).body || 963 !(member as ts.ConstructorDeclaration).body.statements) { 964 return false; 965 } 966 const constructorStatements: ts.Statement[] = (member as ts.ConstructorDeclaration).body.statements; 967 if (constructorStatements && constructorStatements[0] && 968 ts.isExpressionStatement(constructorStatements[0])) { 969 const expression: ts.Node = (constructorStatements[0] as ts.ExpressionStatement).expression; 970 return expression && ts.isStringLiteral(expression) && 971 (expression as ts.StringLiteral).text === 'use sendable'; 972 } 973 } 974 return false; 975 })); 976} 977 978export function checkAllNode( 979 node: ts.EtsComponentExpression, 980 allComponentNames: Set<string>, 981 sourceFileNode: ts.SourceFile, 982 log: LogInfo[] 983): void { 984 if (ts.isIdentifier(node.expression)) { 985 checkNoChildComponent(node, sourceFileNode, log); 986 checkOneChildComponent(node, allComponentNames, sourceFileNode, log); 987 checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log); 988 } 989} 990 991interface ParamType { 992 name: string, 993 value: string, 994} 995 996function checkNoChildComponent(node: ts.EtsComponentExpression, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 997 const isCheckType: ParamType = { name: null, value: null}; 998 if (hasChild(node, isCheckType)) { 999 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 1000 const pos: number = node.expression.getStart(); 1001 const message: string = isCheckType.name === null ? 1002 `The component '${componentName}' can't have any child.` : 1003 `When the component '${componentName}' set '${isCheckType.name}' is '${isCheckType.value}'` + 1004 `, can't have any child.`; 1005 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 1006 } 1007} 1008 1009function hasChild(node: ts.EtsComponentExpression, isCheckType: ParamType): boolean { 1010 const nodeName: ts.Identifier = node.expression as ts.Identifier; 1011 if ((AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) || judgeComponentType(nodeName, node, isCheckType)) && 1012 getNextNode(node)) { 1013 return true; 1014 } 1015 return false; 1016} 1017 1018function judgeComponentType(nodeName: ts.Identifier, etsComponentExpression: ts.EtsComponentExpression, 1019 isCheckType: ParamType): boolean { 1020 return COMPONENT_TOGGLE === nodeName.escapedText.toString() && 1021 etsComponentExpression.arguments && etsComponentExpression.arguments[0] && 1022 ts.isObjectLiteralExpression(etsComponentExpression.arguments[0]) && 1023 etsComponentExpression.arguments[0].getText() && 1024 judgeToggleComponentParamType(etsComponentExpression.arguments[0].getText(), isCheckType); 1025} 1026 1027function judgeToggleComponentParamType(param: string, isCheckType: ParamType): boolean { 1028 if (param.indexOf(RESOURCE_NAME_TYPE) > -1) { 1029 isCheckType.name = RESOURCE_NAME_TYPE; 1030 const match: string[] = param.match(/\b(Checkbox|Switch|Button)\b/); 1031 if (match && match.length) { 1032 isCheckType.value = match[0]; 1033 if (isCheckType.value === COMPONENT_BUTTON) { 1034 return false; 1035 } 1036 return true; 1037 } 1038 } 1039 return false; 1040} 1041 1042function getNextNode(node: ts.EtsComponentExpression): ts.Block { 1043 if (node.body && ts.isBlock(node.body)) { 1044 const statementsArray: ts.Block = node.body; 1045 return statementsArray; 1046 } 1047 return undefined; 1048} 1049 1050function checkOneChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 1051 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 1052 const isCheckType: ParamType = { name: null, value: null}; 1053 if (hasNonSingleChild(node, allComponentNames, isCheckType)) { 1054 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 1055 const pos: number = node.expression.getStart(); 1056 const message: string = isCheckType.name === null ? 1057 `The component '${componentName}' can only have a single child component.` : 1058 `When the component '${componentName}' set '${isCheckType.name}' is ` + 1059 `'${isCheckType.value}', can only have a single child component.`; 1060 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 1061 } 1062} 1063 1064function hasNonSingleChild(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 1065 isCheckType: ParamType): boolean { 1066 const nodeName: ts.Identifier = node.expression as ts.Identifier; 1067 const blockNode: ts.Block = getNextNode(node); 1068 if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString()) || !judgeComponentType(nodeName, node, isCheckType) && 1069 isCheckType.value === COMPONENT_BUTTON) { 1070 if (!blockNode) { 1071 return false; 1072 } 1073 if (blockNode && blockNode.statements) { 1074 const length: number = blockNode.statements.length; 1075 if (!length) { 1076 return false; 1077 } 1078 if (length > 3) { 1079 return true; 1080 } 1081 const childCount: number = getBlockChildrenCount(blockNode, allComponentNames); 1082 if (childCount > 1) { 1083 return true; 1084 } 1085 } 1086 } 1087 return false; 1088} 1089 1090function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set<string>): number { 1091 let maxCount: number = 0; 1092 const length: number = blockNode.statements.length; 1093 for (let i = 0; i < length; ++i) { 1094 const item: ts.Node = blockNode.statements[i]; 1095 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && 1096 isForEachComponent(item.expression)) { 1097 maxCount += 2; 1098 } 1099 if (ts.isIfStatement(item)) { 1100 maxCount += getIfChildrenCount(item, allComponentNames); 1101 } 1102 if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) { 1103 maxCount += 1; 1104 } 1105 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) { 1106 let newNode = item.expression; 1107 while (newNode.expression) { 1108 if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) && 1109 isComponent(newNode, allComponentNames)) { 1110 maxCount += 1; 1111 } 1112 newNode = newNode.expression; 1113 } 1114 } 1115 if (maxCount > 1) { 1116 break; 1117 } 1118 } 1119 return maxCount; 1120} 1121 1122function isComponent(node: ts.EtsComponentExpression | ts.CallExpression, allComponentNames: Set<string>): boolean { 1123 if (ts.isIdentifier(node.expression) && 1124 allComponentNames.has(node.expression.escapedText.toString())) { 1125 return true; 1126 } 1127 return false; 1128} 1129 1130function isForEachComponent(node: ts.EtsComponentExpression | ts.CallExpression): boolean { 1131 if (ts.isIdentifier(node.expression)) { 1132 const componentName: string = node.expression.escapedText.toString(); 1133 return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH; 1134 } 1135 return false; 1136} 1137 1138function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set<string>): number { 1139 const maxCount: number = 1140 Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), 1141 getStatementCount(ifNode.elseStatement, allComponentNames)); 1142 return maxCount; 1143} 1144 1145function getStatementCount(node: ts.Node, allComponentNames: Set<string>): number { 1146 let maxCount: number = 0; 1147 if (!node) { 1148 return maxCount; 1149 } else if (ts.isBlock(node)) { 1150 maxCount = getBlockChildrenCount(node, allComponentNames); 1151 } else if (ts.isIfStatement(node)) { 1152 maxCount = getIfChildrenCount(node, allComponentNames); 1153 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 1154 isForEachComponent(node.expression)) { 1155 maxCount = 2; 1156 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 1157 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { 1158 maxCount = 1; 1159 } 1160 return maxCount; 1161} 1162 1163function checkSpecificChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 1164 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 1165 if (hasNonspecificChild(node, allComponentNames)) { 1166 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 1167 const pos: number = node.expression.getStart(); 1168 const specificChildArray: string = 1169 Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and '); 1170 const message: string = 1171 `The component '${componentName}' can only have the child component ${specificChildArray}.`; 1172 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 1173 } 1174} 1175 1176function hasNonspecificChild(node: ts.EtsComponentExpression, 1177 allComponentNames: Set<string>): boolean { 1178 const nodeName: ts.Identifier = node.expression as ts.Identifier; 1179 const nodeNameString: string = nodeName.escapedText.toString(); 1180 const blockNode: ts.Block = getNextNode(node); 1181 let isNonspecific: boolean = false; 1182 if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { 1183 const specificChildSet: Set<string> = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); 1184 isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); 1185 if (isNonspecific) { 1186 return isNonspecific; 1187 } 1188 } 1189 return isNonspecific; 1190} 1191 1192function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set<string>, 1193 allComponentNames: Set<string>): boolean { 1194 if (blockNode.statements) { 1195 const length: number = blockNode.statements.length; 1196 for (let i = 0; i < length; ++i) { 1197 const item: ts.Node = blockNode.statements[i]; 1198 if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) { 1199 return true; 1200 } 1201 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && 1202 isForEachComponent(item.expression) && 1203 isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) { 1204 return true; 1205 } 1206 if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) { 1207 return true; 1208 } 1209 if (ts.isExpressionStatement(item)) { 1210 let newNode: any = item.expression; 1211 while (newNode.expression) { 1212 if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) && 1213 !isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) { 1214 const isNonspecific: boolean = 1215 isNonspecificChildNonForEach(newNode, specificChildSet); 1216 if (isNonspecific) { 1217 return isNonspecific; 1218 } 1219 if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { 1220 ++i; 1221 } 1222 } 1223 newNode = newNode.expression; 1224 } 1225 } 1226 } 1227 } 1228 return false; 1229} 1230 1231function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set<string>, 1232 allComponentNames: Set<string>): boolean { 1233 return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || 1234 isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); 1235} 1236 1237function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set<string>, 1238 allComponentNames: Set<string>): boolean { 1239 if (ts.isCallExpression(node) && node.arguments && 1240 node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { 1241 const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; 1242 const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement = 1243 arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement; 1244 if (!body) { 1245 return false; 1246 } 1247 if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) { 1248 return true; 1249 } 1250 if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) { 1251 return true; 1252 } 1253 if (ts.isCallExpression(body) && isForEachComponent(body) && 1254 isNonspecificChildForEach(body, specificChildSet, allComponentNames)) { 1255 return true; 1256 } 1257 if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) && 1258 isComponent(body, allComponentNames) && 1259 isNonspecificChildNonForEach(body, specificChildSet)) { 1260 return true; 1261 } 1262 } 1263 return false; 1264} 1265 1266function isNonspecificChildNonForEach(node: ts.EtsComponentExpression, 1267 specificChildSet: Set<string>): boolean { 1268 if (ts.isIdentifier(node.expression) && 1269 !specificChildSet.has(node.expression.escapedText.toString())) { 1270 return true; 1271 } 1272 return false; 1273} 1274 1275function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set<string>, 1276 allComponentNames: Set<string>): boolean { 1277 if (!node) { 1278 return false; 1279 } 1280 if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) { 1281 return true; 1282 } 1283 if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) { 1284 return true; 1285 } 1286 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 1287 isForEachComponent(node.expression) && 1288 isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { 1289 return true; 1290 } 1291 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 1292 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && 1293 isNonspecificChildNonForEach(node.expression, specificChildSet)) { 1294 return true; 1295 } 1296 return false; 1297} 1298 1299function collectComponentProps(node: ts.StructDeclaration, structInfo: StructInfo): void { 1300 const componentName: string = node.name.getText(); 1301 const componentSet: IComponentSet = getComponentSet(node, true); 1302 propertyCollection.set(componentName, componentSet.properties); 1303 stateCollection.set(componentName, componentSet.states); 1304 linkCollection.set(componentName, componentSet.links); 1305 storedFileInfo.overallLinkCollection.set(componentName, componentSet.links); 1306 propCollection.set(componentName, componentSet.props); 1307 regularCollection.set(componentName, componentSet.regulars); 1308 storagePropCollection.set(componentName, componentSet.storageProps); 1309 storageLinkCollection.set(componentName, componentSet.storageLinks); 1310 provideCollection.set(componentName, componentSet.provides); 1311 consumeCollection.set(componentName, componentSet.consumes); 1312 objectLinkCollection.set(componentName, componentSet.objectLinks); 1313 storedFileInfo.overallObjectLinkCollection.set(componentName, componentSet.objectLinks); 1314 localStorageLinkCollection.set(componentName, componentSet.localStorageLink); 1315 localStoragePropCollection.set(componentName, componentSet.localStorageProp); 1316 builderParamObjectCollection.set(componentName, componentSet.builderParams); 1317 builderParamInitialization.set(componentName, componentSet.builderParamData); 1318 propInitialization.set(componentName, componentSet.propData); 1319 regularInitialization.set(componentName, componentSet.regularInit); 1320 stateInitialization.set(componentName, componentSet.stateInit); 1321 provideInitialization.set(componentName, componentSet.provideInit); 1322 privateCollection.set(componentName, componentSet.privateCollection); 1323 structInfo.updatePropsDecoratorsV1.push( 1324 ...componentSet.states, ...componentSet.props, 1325 ...componentSet.provides, ...componentSet.objectLinks 1326 ); 1327 structInfo.linkDecoratorsV1.push(...componentSet.links); 1328} 1329 1330export function getComponentSet(node: ts.StructDeclaration, uiCheck: boolean = false): IComponentSet { 1331 const componentSet: IComponentSet = new IComponentSet(); 1332 traversalComponentProps(node, componentSet, uiCheck); 1333 return componentSet; 1334} 1335 1336class RecordRequire { 1337 hasRequire: boolean = false; 1338 hasProp: boolean = false; 1339 hasBuilderParam: boolean = false; 1340 hasRegular: boolean = false; 1341 hasState: boolean = false; 1342 hasProvide: boolean = false; 1343} 1344 1345function traversalComponentProps(node: ts.StructDeclaration, componentSet: IComponentSet, 1346 uiCheck: boolean = false): void { 1347 let isStatic: boolean = true; 1348 if (node.members) { 1349 const currentMethodCollection: Set<string> = new Set(); 1350 node.members.forEach(item => { 1351 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 1352 const propertyName: string = item.name.getText(); 1353 componentSet.properties.add(propertyName); 1354 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 1355 const accessQualifierResult: AccessQualifierResult = getAccessQualifier(item, uiCheck); 1356 if (!decorators || !decorators.length) { 1357 componentSet.regulars.add(propertyName); 1358 setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR); 1359 } else { 1360 isStatic = false; 1361 let hasValidatePrivate: boolean = false; 1362 const recordRequire: RecordRequire = new RecordRequire(); 1363 for (let i = 0; i < decorators.length; i++) { 1364 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1365 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 1366 dollarCollection.add('$' + propertyName); 1367 collectionStates(decorators[i], decoratorName, propertyName, 1368 componentSet, recordRequire); 1369 setPrivateCollection(componentSet, accessQualifierResult, propertyName, decoratorName); 1370 validateAccessQualifier(item, propertyName, decoratorName, accessQualifierResult, 1371 recordRequire, uiCheck, hasValidatePrivate); 1372 hasValidatePrivate = true; 1373 } 1374 } 1375 regularAndRequire(decorators, componentSet, recordRequire, propertyName, accessQualifierResult); 1376 checkRequire(propertyName, componentSet, recordRequire); 1377 } 1378 } 1379 if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) { 1380 validateStateVariable(item); 1381 currentMethodCollection.add(item.name.getText()); 1382 } 1383 }); 1384 collectCurrentClassMethod(node, currentMethodCollection); 1385 } 1386 isStaticViewCollection.set(node.name.getText(), isStatic); 1387} 1388 1389function collectCurrentClassMethod(node: ts.StructDeclaration, currentMethodCollection: Set<string>): void { 1390 const componentMethodCollection: Map<string, Set<string>> = new Map(); 1391 componentMethodCollection.set(node.name.getText(), currentMethodCollection); 1392 const sourceFile: ts.SourceFile = node.getSourceFile(); 1393 const filePath: string = sourceFile ? sourceFile.fileName : undefined; 1394 if (filePath) { 1395 const pageMethodCollection: Map<string, Set<string>> = classMethodCollection.get(filePath); 1396 if (!pageMethodCollection) { 1397 classMethodCollection.set(filePath, componentMethodCollection); 1398 } else if (!pageMethodCollection.get(node.name.getText())) { 1399 pageMethodCollection.set(node.name.getText(), currentMethodCollection); 1400 } 1401 } 1402} 1403 1404const FORBIDDEN_PUBLIC_ACCESS: string[] = [COMPONENT_STORAGE_PROP_DECORATOR, 1405 COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, 1406 COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, COMPONENT_CONSUME_DECORATOR 1407]; 1408const FORBIDDEN_PRIVATE_ACCESS: string[] = [COMPONENT_LINK_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]; 1409 1410function validateAccessQualifier(node: ts.PropertyDeclaration, propertyName: string, 1411 decoratorName: string, accessQualifierResult: AccessQualifierResult, 1412 recordRequire: RecordRequire, uiCheck: boolean = false, hasValidatePrivate: boolean): void { 1413 if (uiCheck) { 1414 if (accessQualifierResult.hasPublic && FORBIDDEN_PUBLIC_ACCESS.includes(decoratorName)) { 1415 transformLog.errors.push({ 1416 type: LogType.WARN, 1417 message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and public.`, 1418 pos: node.getStart() 1419 }); 1420 } 1421 if (accessQualifierResult.hasPrivate) { 1422 if (FORBIDDEN_PRIVATE_ACCESS.includes(decoratorName)) { 1423 transformLog.errors.push({ 1424 type: LogType.WARN, 1425 message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and private.`, 1426 pos: node.getStart() 1427 }); 1428 } 1429 if (recordRequire.hasRequire && !hasValidatePrivate) { 1430 transformLog.errors.push({ 1431 type: LogType.WARN, 1432 message: `Property '${propertyName}' can not be decorated with both @Require and private.`, 1433 pos: node.getStart() 1434 }); 1435 } 1436 } 1437 } 1438} 1439 1440const SUPPORT_PRIVATE_PROPS: string[] = [COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR, 1441 COMPONENT_PROP_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR 1442]; 1443 1444function setPrivateCollection(componentSet: IComponentSet, accessQualifierResult: AccessQualifierResult, 1445 propertyName: string, decoratorName: string): void { 1446 if (accessQualifierResult.hasPrivate && SUPPORT_PRIVATE_PROPS.includes(decoratorName)) { 1447 componentSet.privateCollection.add(propertyName); 1448 } 1449} 1450 1451class AccessQualifierResult { 1452 hasPrivate: boolean = false; 1453 hasPublic: boolean = false; 1454} 1455 1456function getAccessQualifier(node: ts.PropertyDeclaration, uiCheck: boolean = false): AccessQualifierResult { 1457 const modifiers: readonly ts.Modifier[] = ts.getModifiers(node); 1458 const accessQualifierResult: AccessQualifierResult = new AccessQualifierResult(); 1459 if (modifiers && modifiers.length) { 1460 modifiers.forEach((item) => { 1461 if (item.kind === ts.SyntaxKind.PrivateKeyword) { 1462 accessQualifierResult.hasPrivate = true; 1463 } 1464 if (item.kind === ts.SyntaxKind.PublicKeyword) { 1465 accessQualifierResult.hasPublic = true; 1466 } 1467 if (uiCheck && item.kind === ts.SyntaxKind.ProtectedKeyword) { 1468 transformLog.errors.push({ 1469 type: LogType.WARN, 1470 message: `The member attributes of a struct can not be protected.`, 1471 pos: node.getStart() 1472 }); 1473 } 1474 }); 1475 } 1476 return accessQualifierResult; 1477} 1478 1479function regularAndRequire(decorators: readonly ts.Decorator[], componentSet: IComponentSet, 1480 recordRequire: RecordRequire, propertyName: string, accessQualifierResult: AccessQualifierResult): void { 1481 if (decorators && decorators.length === 1 && decorators[0].getText() === COMPONENT_REQUIRE_DECORATOR) { 1482 componentSet.regulars.add(propertyName); 1483 recordRequire.hasRegular = true; 1484 setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR); 1485 } 1486} 1487 1488function checkRequire(name: string, componentSet: IComponentSet, recordRequire: RecordRequire): void { 1489 if (recordRequire.hasRequire) { 1490 setInitValue('hasProp', 'propData', name, componentSet, recordRequire); 1491 setInitValue('hasBuilderParam', 'builderParamData', name, componentSet, recordRequire); 1492 setInitValue('hasRegular', 'regularInit', name, componentSet, recordRequire); 1493 setInitValue('hasState', 'stateInit', name, componentSet, recordRequire); 1494 setInitValue('hasProvide', 'provideInit', name, componentSet, recordRequire); 1495 } 1496} 1497 1498function setInitValue(requirekey: string, initKey: string, name: string, componentSet: IComponentSet, 1499 recordRequire: RecordRequire): void { 1500 if (recordRequire[requirekey]) { 1501 componentSet[initKey].add(name); 1502 } 1503} 1504 1505function collectionStates(node: ts.Decorator, decorator: string, name: string, 1506 componentSet: IComponentSet, recordRequire: RecordRequire): void { 1507 switch (decorator) { 1508 case COMPONENT_STATE_DECORATOR: 1509 componentSet.states.add(name); 1510 recordRequire.hasState = true; 1511 break; 1512 case COMPONENT_LINK_DECORATOR: 1513 componentSet.links.add(name); 1514 break; 1515 case COMPONENT_PROP_DECORATOR: 1516 recordRequire.hasProp = true; 1517 componentSet.props.add(name); 1518 break; 1519 case COMPONENT_STORAGE_PROP_DECORATOR: 1520 componentSet.storageProps.add(name); 1521 break; 1522 case COMPONENT_STORAGE_LINK_DECORATOR: 1523 componentSet.storageLinks.add(name); 1524 break; 1525 case COMPONENT_PROVIDE_DECORATOR: 1526 recordRequire.hasProvide = true; 1527 componentSet.provides.add(name); 1528 break; 1529 case COMPONENT_CONSUME_DECORATOR: 1530 componentSet.consumes.add(name); 1531 break; 1532 case COMPONENT_OBJECT_LINK_DECORATOR: 1533 componentSet.objectLinks.add(name); 1534 break; 1535 case COMPONENT_BUILDERPARAM_DECORATOR: 1536 recordRequire.hasBuilderParam = true; 1537 componentSet.builderParams.add(name); 1538 break; 1539 case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR : 1540 collectionlocalStorageParam(node, name, componentSet.localStorageLink); 1541 break; 1542 case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: 1543 collectionlocalStorageParam(node, name, componentSet.localStorageProp); 1544 break; 1545 case COMPONENT_REQUIRE_DECORATOR: 1546 recordRequire.hasRequire = true; 1547 break; 1548 } 1549} 1550 1551function collectionlocalStorageParam(node: ts.Decorator, name: string, 1552 localStorage: Map<string, Set<string>>): void { 1553 const localStorageParam: Set<string> = new Set(); 1554 if (node && ts.isCallExpression(node.expression) && node.expression.arguments && 1555 node.expression.arguments.length) { 1556 localStorage.set(name, localStorageParam.add( 1557 node.expression.arguments[0].getText())); 1558 } 1559} 1560 1561export interface ReplaceResult { 1562 content: string, 1563 log: LogInfo[] 1564} 1565 1566export function sourceReplace(source: string, sourcePath: string): ReplaceResult { 1567 let content: string = source; 1568 const log: LogInfo[] = []; 1569 content = preprocessExtend(content); 1570 content = preprocessNewExtend(content); 1571 // process @system. 1572 content = processSystemApi(content, false, sourcePath); 1573 collectImportNames(content, sourcePath); 1574 1575 return { 1576 content: content, 1577 log: log 1578 }; 1579} 1580 1581export function preprocessExtend(content: string, extendCollection?: Set<string>): string { 1582 const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm; 1583 return content.replace(REG_EXTEND, (item, item1, item2, item3) => { 1584 collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3); 1585 collectExtend(EXTEND_ATTRIBUTE, item2, item3); 1586 if (extendCollection) { 1587 extendCollection.add(item3); 1588 } 1589 return `@Extend(${item2})${item1}function __${item2}__${item3}(`; 1590 }); 1591} 1592 1593export function preprocessNewExtend(content: string, extendCollection?: Set<string>): string { 1594 const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm; 1595 return content.replace(REG_EXTEND, (item, item1) => { 1596 if (extendCollection) { 1597 extendCollection.add(item1); 1598 } 1599 return item; 1600 }); 1601} 1602 1603function replaceSystemApi(item: string, systemValue: string, moduleType: string, systemKey: string): string { 1604 // if change format, please update regexp in transformModuleSpecifier 1605 if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { 1606 item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; 1607 } else if (moduleType === SYSTEM_PLUGIN || moduleType === OHOS_PLUGIN) { 1608 item = `var ${systemValue} = globalThis.requireNapi('${systemKey}')`; 1609 } 1610 return item; 1611} 1612 1613function replaceLibSo(importValue: string, libSoKey: string, sourcePath: string = null): string { 1614 if (sourcePath) { 1615 useOSFiles.add(sourcePath); 1616 } 1617 // if change format, please update regexp in transformModuleSpecifier 1618 return projectConfig.bundleName && projectConfig.moduleName ? 1619 `var ${importValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");` : 1620 `var ${importValue} = globalThis.requireNapi("${libSoKey}", true);`; 1621} 1622 1623export function processSystemApi(content: string, isProcessAllowList: boolean = false, 1624 sourcePath: string = null, isSystemModule: boolean = false): string { 1625 if (isProcessAllowList && projectConfig.compileMode === ESMODULE) { 1626 // remove the unused system api import decl like following when compile as [esmodule] 1627 // in the result_process phase 1628 // e.g. import "@ohos.application.xxx" 1629 const REG_UNUSED_SYSTEM_IMPORT: RegExp = /import(?:\s*)['"]@(system|ohos)\.(\S+)['"]/g; 1630 content = content.replace(REG_UNUSED_SYSTEM_IMPORT, ''); 1631 } 1632 1633 const REG_IMPORT_DECL: RegExp = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? 1634 /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]/g : 1635 /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g : 1636 /(import|export)\s+(?:(.+)|\{([\s\S]+)\})\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; 1637 1638 const systemValueCollection: Set<string> = new Set(); 1639 const processedContent: string = content.replace(REG_IMPORT_DECL, (item, item1, item2, item3, item4, item5, item6) => { 1640 const importValue: string = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? item1 : item2 : item2 || item5; 1641 1642 if (isProcessAllowList) { 1643 systemValueCollection.add(importValue); 1644 if (projectConfig.compileMode !== ESMODULE) { 1645 collectSourcemapNames(sourcePath, importValue, item5); 1646 return replaceSystemApi(item, importValue, item4, item5); 1647 } 1648 collectSourcemapNames(sourcePath, importValue, item3); 1649 return replaceSystemApi(item, importValue, item2, item3); 1650 } 1651 1652 const moduleRequest: string = item4 || item6; 1653 if (/^@(system|ohos)\./.test(moduleRequest)) { // ohos/system.api 1654 // ets & ts file need compile with .d.ts, so do not replace at the phase of pre_process 1655 if (!isSystemModule) { 1656 return item; 1657 } 1658 const result: RegExpMatchArray = moduleRequest.match(/^@(system|ohos)\.(\S+)$/); 1659 const moduleType: string = result[1]; 1660 const apiName: string = result[2]; 1661 return replaceSystemApi(item, importValue, moduleType, apiName); 1662 } else if (/^lib(\S+)\.so$/.test(moduleRequest)) { // libxxx.so 1663 const result: RegExpMatchArray = moduleRequest.match(/^lib(\S+)\.so$/); 1664 const libSoKey: string = result[1]; 1665 return replaceLibSo(importValue, libSoKey, sourcePath); 1666 } 1667 return item; 1668 }); 1669 return processInnerModule(processedContent, systemValueCollection); 1670} 1671 1672function collectSourcemapNames(sourcePath: string, changedName: string, originalName: string): void { 1673 if (sourcePath == null) { 1674 return; 1675 } 1676 const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); 1677 if (!sourcemapNamesCollection.has(cleanSourcePath)) { 1678 return; 1679 } 1680 1681 const map: Map<string, string> = sourcemapNamesCollection.get(cleanSourcePath); 1682 if (map.has(changedName)) { 1683 return; 1684 } 1685 1686 for (const entry of originalImportNamesMap.entries()) { 1687 const key: string = entry[0]; 1688 const value: string = entry[1]; 1689 if (value === '@ohos.' + originalName || value === '@system.' + originalName) { 1690 map.set(changedName.trim(), key); 1691 sourcemapNamesCollection.set(cleanSourcePath, map); 1692 originalImportNamesMap.delete(key); 1693 break; 1694 } 1695 } 1696} 1697 1698export function collectImportNames(content: string, sourcePath: string = null): void { 1699 const REG_IMPORT_DECL: RegExp = 1700 /(import|export)\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; 1701 1702 const decls: string[] = content.match(REG_IMPORT_DECL); 1703 if (decls !== null) { 1704 decls.forEach(decl => { 1705 const parts: string[] = decl.split(' '); 1706 if (parts.length === 4 && parts[0] === 'import' && parts[2] === 'from' && !parts[3].includes('.so')) { 1707 originalImportNamesMap.set(parts[1], parts[3].replace(/'/g, '')); 1708 } 1709 }); 1710 } 1711 1712 if (sourcePath && sourcePath !== null) { 1713 const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); 1714 if (!sourcemapNamesCollection.has(cleanSourcePath)) { 1715 sourcemapNamesCollection.set(cleanSourcePath, new Map()); 1716 } 1717 } 1718} 1719 1720function processInnerModule(content: string, systemValueCollection: Set<string>): string { 1721 systemValueCollection.forEach(element => { 1722 const target: string = element.trim() + '.default'; 1723 while (content.includes(target)) { 1724 content = content.replace(target, element.trim()); 1725 } 1726 }); 1727 return content; 1728} 1729 1730export function resetComponentCollection(): void { 1731 componentCollection.entryComponent = null; 1732 componentCollection.entryComponentPos = null; 1733 componentCollection.previewComponent = []; 1734 stateObjectCollection.clear(); 1735 builderParamInitialization.clear(); 1736 propInitialization.clear(); 1737 propCollection.clear(); 1738 objectLinkCollection.clear(); 1739 linkCollection.clear(); 1740 storedFileInfo.overallLinkCollection.clear(); 1741 storedFileInfo.overallObjectLinkCollection.clear(); 1742 storedFileInfo.overallBuilderParamCollection.clear(); 1743 regularInitialization.clear(); 1744 stateInitialization.clear(); 1745 provideInitialization.clear(); 1746 privateCollection.clear(); 1747} 1748 1749function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void { 1750 const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; 1751 if (modifiers) { 1752 for (let i = 0; i < modifiers.length; i++) { 1753 if (modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { 1754 const message: string = `It's not a recommended way to export struct with @Entry decorator, ` + 1755 `which may cause ACE Engine error in component preview mode.`; 1756 addLog(LogType.WARN, message, node.getStart(), log, sourceFile); 1757 break; 1758 } 1759 } 1760 } 1761} 1762 1763function validateStateVariable(node: ts.MethodDeclaration): void { 1764 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 1765 if (decorators && decorators.length) { 1766 for (let i = 0; i < decorators.length; i++) { 1767 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1768 if (CARD_ENABLE_DECORATORS[decoratorName]) { 1769 validatorCard(transformLog.errors, CARD_LOG_TYPE_DECORATORS, 1770 decorators[i].getStart(), decoratorName); 1771 } 1772 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 1773 transformLog.errors.push({ 1774 type: LogType.ERROR, 1775 message: `'${decorators[i].getText()}' can not decorate the method.`, 1776 pos: decorators[i].getStart() 1777 }); 1778 } 1779 } 1780 } 1781} 1782 1783export function getObservedPropertyCollection(className: string): Set<string> { 1784 const observedProperthCollection: Set<string> = new Set([ 1785 ...stateCollection.get(className), 1786 ...linkCollection.get(className), 1787 ...propCollection.get(className), 1788 ...storageLinkCollection.get(className), 1789 ...storageLinkCollection.get(className), 1790 ...provideCollection.get(className), 1791 ...consumeCollection.get(className), 1792 ...objectLinkCollection.get(className) 1793 ]); 1794 getLocalStorageCollection(className, observedProperthCollection); 1795 return observedProperthCollection; 1796} 1797 1798export function getLocalStorageCollection(componentName: string, collection: Set<string>): void { 1799 if (localStorageLinkCollection.get(componentName)) { 1800 for (const key of localStorageLinkCollection.get(componentName).keys()) { 1801 collection.add(key); 1802 } 1803 } 1804 if (localStoragePropCollection.get(componentName)) { 1805 for (const key of localStoragePropCollection.get(componentName).keys()) { 1806 collection.add(key); 1807 } 1808 } 1809} 1810 1811export function resetValidateUiSyntax(): void { 1812 observedClassCollection.clear(); 1813 enumCollection.clear(); 1814 classMethodCollection.clear(); 1815 dollarCollection.clear(); 1816 stateCollection.clear(); 1817 regularCollection.clear(); 1818 storagePropCollection.clear(); 1819 storageLinkCollection.clear(); 1820 provideCollection.clear(); 1821 consumeCollection.clear(); 1822 builderParamObjectCollection.clear(); 1823 localStorageLinkCollection.clear(); 1824 localStoragePropCollection.clear(); 1825 isStaticViewCollection.clear(); 1826 useOSFiles.clear(); 1827 sourcemapNamesCollection.clear(); 1828 originalImportNamesMap.clear(); 1829} 1830