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