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