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