1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts from 'typescript'; 17import path from 'path'; 18 19import { 20 INNER_COMPONENT_DECORATORS, 21 COMPONENT_DECORATOR_ENTRY, 22 COMPONENT_DECORATOR_PREVIEW, 23 COMPONENT_DECORATOR_COMPONENT, 24 COMPONENT_DECORATOR_CUSTOM_DIALOG, 25 NATIVE_MODULE, 26 SYSTEM_PLUGIN, 27 OHOS_PLUGIN, 28 INNER_COMPONENT_MEMBER_DECORATORS, 29 COMPONENT_FOREACH, 30 COMPONENT_LAZYFOREACH, 31 COMPONENT_STATE_DECORATOR, 32 COMPONENT_LINK_DECORATOR, 33 COMPONENT_PROP_DECORATOR, 34 COMPONENT_STORAGE_PROP_DECORATOR, 35 COMPONENT_STORAGE_LINK_DECORATOR, 36 COMPONENT_PROVIDE_DECORATOR, 37 COMPONENT_CONSUME_DECORATOR, 38 COMPONENT_OBJECT_LINK_DECORATOR, 39 COMPONENT_OBSERVED_DECORATOR, 40 COMPONENT_LOCAL_STORAGE_LINK_DECORATOR, 41 COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, 42 COMPONENT_BUILDER_DECORATOR, 43 COMPONENT_CONCURRENT_DECORATOR, 44 CHECK_EXTEND_DECORATORS, 45 COMPONENT_STYLES_DECORATOR, 46 RESOURCE_NAME_TYPE, 47 COMPONENT_BUTTON, 48 COMPONENT_TOGGLE, 49 COMPONENT_BUILDERPARAM_DECORATOR, 50 ESMODULE, 51 CARD_ENABLE_DECORATORS, 52 CARD_LOG_TYPE_DECORATORS, 53 JSBUNDLE, 54 COMPONENT_DECORATOR_REUSEABLE, 55 STRUCT_DECORATORS, 56 STRUCT_CONTEXT_METHOD_DECORATORS, 57 CHECK_COMPONENT_EXTEND_DECORATOR, 58 CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR, 59 CLASS_TRACK_DECORATOR, 60 COMPONENT_REQUIRE_DECORATOR, 61 COMPONENT_SENDABLE_DECORATOR 62} from './pre_define'; 63import { 64 INNER_COMPONENT_NAMES, 65 AUTOMIC_COMPONENT, 66 SINGLE_CHILD_COMPONENT, 67 SPECIFIC_CHILD_COMPONENT, 68 BUILDIN_STYLE_NAMES, 69 EXTEND_ATTRIBUTE, 70 GLOBAL_STYLE_FUNCTION, 71 STYLES_ATTRIBUTE, 72 CUSTOM_BUILDER_METHOD, 73 GLOBAL_CUSTOM_BUILDER_METHOD, 74 INNER_CUSTOM_BUILDER_METHOD, 75 INNER_STYLE_FUNCTION 76} from './component_map'; 77import { 78 LogType, 79 LogInfo, 80 componentInfo, 81 addLog, 82 hasDecorator, 83 storedFileInfo, 84 ExtendResult 85} from './utils'; 86import { globalProgram, projectConfig, abilityPagesFullPath } from '../main'; 87import { 88 collectExtend, 89 isExtendFunction, 90 transformLog, 91 validatorCard 92} from './process_ui_syntax'; 93import { stateObjectCollection } from './process_component_member'; 94 95export interface ComponentCollection { 96 localStorageName: string; 97 localStorageNode: ts.Identifier | ts.ObjectLiteralExpression; 98 entryComponentPos: number; 99 entryComponent: string; 100 previewComponent: Array<string>; 101 customDialogs: Set<string>; 102 customComponents: Set<string>; 103 currentClassName: string; 104} 105 106export interface IComponentSet { 107 properties: Set<string>; 108 regulars: Set<string>; 109 states: Set<string>; 110 links: Set<string>; 111 props: Set<string>; 112 storageProps: Set<string>; 113 storageLinks: Set<string>; 114 provides: Set<string>; 115 consumes: Set<string>; 116 objectLinks: Set<string>; 117 localStorageLink: Map<string, Set<string>>; 118 localStorageProp: Map<string, Set<string>>; 119 builderParams: Set<string>; 120 builderParamData: Set<string>; 121 propData: Set<string>; 122} 123 124export const componentCollection: ComponentCollection = { 125 localStorageName: null, 126 localStorageNode: null, 127 entryComponentPos: null, 128 entryComponent: null, 129 previewComponent: new Array(), 130 customDialogs: new Set([]), 131 customComponents: new Set([]), 132 currentClassName: null 133}; 134 135export const observedClassCollection: Set<string> = new Set(); 136export const enumCollection: Set<string> = new Set(); 137export const classMethodCollection: Map<string, Set<string>> = new Map(); 138export const dollarCollection: Set<string> = new Set(); 139 140export const propertyCollection: Map<string, Set<string>> = new Map(); 141export const stateCollection: Map<string, Set<string>> = new Map(); 142export const linkCollection: Map<string, Set<string>> = new Map(); 143export const propCollection: Map<string, Set<string>> = new Map(); 144export const regularCollection: Map<string, Set<string>> = new Map(); 145export const storagePropCollection: Map<string, Set<string>> = new Map(); 146export const storageLinkCollection: Map<string, Set<string>> = new Map(); 147export const provideCollection: Map<string, Set<string>> = new Map(); 148export const consumeCollection: Map<string, Set<string>> = new Map(); 149export const objectLinkCollection: Map<string, Set<string>> = new Map(); 150export const builderParamObjectCollection: Map<string, Set<string>> = new Map(); 151export const localStorageLinkCollection: Map<string, Map<string, Set<string>>> = new Map(); 152export const localStoragePropCollection: Map<string, Map<string, Set<string>>> = new Map(); 153export const builderParamInitialization: Map<string, Set<string>> = new Map(); 154export const propInitialization: Map<string, Set<string>> = new Map(); 155 156export const isStaticViewCollection: Map<string, boolean> = new Map(); 157 158export const useOSFiles: Set<string> = new Set(); 159export const sourcemapNamesCollection: Map<string, Map<string, string>> = new Map(); 160export const originalImportNamesMap: Map<string, string> = new Map(); 161 162export function validateUISyntax(source: string, content: string, filePath: string, 163 fileQuery: string, sourceFile: ts.SourceFile = null): LogInfo[] { 164 let log: LogInfo[] = []; 165 if (process.env.compileMode === 'moduleJson' || 166 path.resolve(filePath) !== path.resolve(projectConfig.projectPath || '', 'app.ets')) { 167 const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery, sourceFile); 168 if (res) { 169 log = log.concat(res); 170 } 171 const allComponentNames: Set<string> = 172 new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]); 173 checkUISyntax(filePath, allComponentNames, content, log, sourceFile, fileQuery); 174 componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item)); 175 } 176 177 return log; 178} 179 180function checkComponentDecorator(source: string, filePath: string, 181 fileQuery: string, sourceFile: ts.SourceFile | null): LogInfo[] | null { 182 const log: LogInfo[] = []; 183 if (!sourceFile) { 184 sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 185 } 186 if (sourceFile && sourceFile.statements && sourceFile.statements.length) { 187 const result: DecoratorResult = { 188 entryCount: 0, 189 previewCount: 0 190 }; 191 sourceFile.statements.forEach((item, index, arr) => { 192 if (isObservedClass(item)) { 193 // @ts-ignore 194 observedClassCollection.add(item.name.getText()); 195 } 196 if (ts.isEnumDeclaration(item) && item.name) { 197 enumCollection.add(item.name.getText()); 198 } 199 if (ts.isStructDeclaration(item)) { 200 if (item.name && ts.isIdentifier(item.name)) { 201 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 202 if (decorators && decorators.length) { 203 checkDecorators(decorators, result, item.name, log, sourceFile, item); 204 } else { 205 const message: string = `A struct should use decorator '@Component'.`; 206 addLog(LogType.WARN, message, item.getStart(), log, sourceFile); 207 } 208 } else { 209 const message: string = `A struct must have a name.`; 210 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 211 } 212 } 213 if (ts.isMissingDeclaration(item)) { 214 const decorators = ts.getAllDecorators(item); 215 for (let i = 0; i < decorators.length; i++) { 216 if (decorators[i] && /struct/.test(decorators[i].getText())) { 217 const message: string = `Please use a valid decorator.`; 218 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 219 break; 220 } 221 } 222 } 223 }); 224 if (process.env.compileTool === 'rollup') { 225 if (result.entryCount > 0) { 226 storedFileInfo.wholeFileInfo[path.resolve(sourceFile.fileName)].hasEntry = true; 227 } else { 228 storedFileInfo.wholeFileInfo[path.resolve(sourceFile.fileName)].hasEntry = false; 229 } 230 } 231 validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview, 232 !!projectConfig.checkEntry, log); 233 } 234 235 return log.length ? log : null; 236} 237 238function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string, 239 fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void { 240 if (result.previewCount > 10 && (fileQuery === '?entry' || process.env.watchMode === 'true')) { 241 log.push({ 242 type: LogType.ERROR, 243 message: `A page can contain at most 10 '@Preview' decorators.`, 244 fileName: fileName 245 }); 246 } 247 if (result.entryCount > 1 && fileQuery === '?entry') { 248 log.push({ 249 type: LogType.ERROR, 250 message: `A page can't contain more than one '@Entry' decorator`, 251 fileName: fileName 252 }); 253 } 254 if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 && 255 fileQuery === '?entry') { 256 log.push({ 257 type: LogType.ERROR, 258 message: `A page which is being previewed must have one and only one '@Entry' ` + 259 `decorator, or at least one '@Preview' decorator.`, 260 fileName: fileName 261 }); 262 } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry' && 263 !abilityPagesFullPath.includes(path.resolve(fileName).toLowerCase())) { 264 log.push({ 265 type: LogType.ERROR, 266 message: `A page configured in '${projectConfig.pagesJsonFileName}' must have one and only one '@Entry' ` + 267 `decorator.`, 268 fileName: fileName 269 }); 270 } 271} 272 273export function isObservedClass(node: ts.Node): boolean { 274 if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) { 275 return true; 276 } 277 return false; 278} 279 280export function isCustomDialogClass(node: ts.Node): boolean { 281 if (ts.isStructDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) { 282 return true; 283 } 284 return false; 285} 286 287interface DecoratorResult { 288 entryCount: number; 289 previewCount: number; 290} 291 292function checkDecorators(decorators: readonly ts.Decorator[], result: DecoratorResult, 293 component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile, node: ts.StructDeclaration): void { 294 let hasComponentDecorator: boolean = false; 295 const componentName: string = component.getText(); 296 decorators.forEach((element) => { 297 let name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim(); 298 if (element.expression && element.expression.expression && ts.isIdentifier(element.expression.expression)) { 299 name = '@' + element.expression.expression.getText(); 300 } 301 if (INNER_COMPONENT_DECORATORS.has(name)) { 302 componentCollection.customComponents.add(componentName); 303 switch (name) { 304 case COMPONENT_DECORATOR_ENTRY: 305 checkEntryComponent(node, log, sourceFile); 306 result.entryCount++; 307 componentCollection.entryComponent = componentName; 308 componentCollection.entryComponentPos = node.getStart(); 309 collectLocalStorageName(element); 310 break; 311 case COMPONENT_DECORATOR_PREVIEW: 312 result.previewCount++; 313 componentCollection.previewComponent.push(componentName); 314 break; 315 case COMPONENT_DECORATOR_COMPONENT: 316 hasComponentDecorator = true; 317 break; 318 case COMPONENT_DECORATOR_CUSTOM_DIALOG: 319 componentCollection.customDialogs.add(componentName); 320 hasComponentDecorator = true; 321 break; 322 case COMPONENT_DECORATOR_REUSEABLE: 323 storedFileInfo.getCurrentArkTsFile().recycleComponents.add(componentName); 324 hasComponentDecorator = true; 325 break; 326 } 327 } else { 328 const pos: number = element.expression ? element.expression.pos : element.pos; 329 const message: string = `The struct '${componentName}' use invalid decorator.`; 330 addLog(LogType.WARN, message, pos, log, sourceFile); 331 } 332 }); 333 if (!hasComponentDecorator) { 334 const message: string = `The struct '${componentName}' should use decorator '@Component'.`; 335 addLog(LogType.WARN, message, component.pos, log, sourceFile); 336 } 337 if (BUILDIN_STYLE_NAMES.has(componentName)) { 338 const message: string = `The struct '${componentName}' cannot have the same name ` + 339 `as the built-in attribute '${componentName}'.`; 340 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 341 } 342 if (INNER_COMPONENT_NAMES.has(componentName)) { 343 const message: string = `The struct '${componentName}' cannot have the same name ` + 344 `as the built-in component '${componentName}'.`; 345 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 346 } 347} 348 349function checkConcurrentDecorator(node: ts.FunctionDeclaration | ts.MethodDeclaration, log: LogInfo[], 350 sourceFile: ts.SourceFile): void { 351 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 352 if (projectConfig.compileMode === JSBUNDLE) { 353 const message: string = `@Concurrent can only be used in ESMODULE compile mode.`; 354 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 355 } 356 if (ts.isMethodDeclaration(node)) { 357 const message: string = `@Concurrent can not be used on method. please use it on function declaration.`; 358 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 359 } 360 if (node.asteriskToken) { 361 let hasAsync: boolean = false; 362 const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; 363 const checkAsyncModifier = (modifier: ts.Modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword; 364 modifiers && (hasAsync = modifiers.some(checkAsyncModifier)); 365 const funcKind: string = hasAsync ? 'Async generator' : 'Generator'; 366 const message: string = `@Concurrent can not be used on ${funcKind} function declaration.`; 367 addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile); 368 } 369} 370 371function collectLocalStorageName(node: ts.Decorator): void { 372 if (node && node.expression && ts.isCallExpression(node.expression)) { 373 if (node.expression.arguments && node.expression.arguments.length) { 374 node.expression.arguments.forEach((item: ts.Node, index: number) => { 375 if (ts.isIdentifier(item) && index === 0) { 376 componentCollection.localStorageName = item.getText(); 377 componentCollection.localStorageNode = item; 378 } else if (ts.isObjectLiteralExpression(item) && index === 0) { 379 componentCollection.localStorageName = null; 380 componentCollection.localStorageNode = item; 381 } 382 }); 383 } 384 } else { 385 componentCollection.localStorageName = null; 386 componentCollection.localStorageNode = null; 387 } 388} 389 390function checkUISyntax(filePath: string, allComponentNames: Set<string>, content: string, 391 log: LogInfo[], sourceFile: ts.SourceFile | null, fileQuery: string): void { 392 if (!sourceFile) { 393 sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 394 } 395 visitAllNode(sourceFile, sourceFile, allComponentNames, log, false, false, fileQuery); 396} 397 398function propertyInitializeInEntry(fileQuery: string, name: string): boolean { 399 return fileQuery === '?entry' && name === componentCollection.entryComponent; 400} 401 402function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set<string>, 403 log: LogInfo[], structContext: boolean, classContext: boolean, fileQuery: string): void { 404 if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 405 structContext = true; 406 collectComponentProps(node, propertyInitializeInEntry(fileQuery, node.name.escapedText.toString())); 407 } 408 if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 409 classContext = true; 410 if (isSendableClassDeclaration(node as ts.ClassDeclaration)) { 411 validateSendableClass(sourceFileNode, node as ts.ClassDeclaration, log); 412 } 413 } 414 if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) { 415 const extendResult: ExtendResult = { decoratorName: '', componentName: '' }; 416 if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 417 CUSTOM_BUILDER_METHOD.add(node.name.getText()); 418 if (ts.isFunctionDeclaration(node)) { 419 GLOBAL_CUSTOM_BUILDER_METHOD.add(node.name.getText()); 420 } else { 421 INNER_CUSTOM_BUILDER_METHOD.add(node.name.getText()); 422 } 423 } else if (ts.isFunctionDeclaration(node) && isExtendFunction(node, extendResult)) { 424 if (extendResult.decoratorName === CHECK_COMPONENT_EXTEND_DECORATOR) { 425 collectExtend(EXTEND_ATTRIBUTE, extendResult.componentName, node.name.getText()); 426 } 427 if (extendResult.decoratorName === CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 428 collectExtend(storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute, 429 extendResult.componentName, node.name.getText()); 430 } 431 } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) { 432 if (ts.isBlock(node.body) && node.body.statements) { 433 if (ts.isFunctionDeclaration(node)) { 434 GLOBAL_STYLE_FUNCTION.set(node.name.getText(), node.body); 435 } else { 436 INNER_STYLE_FUNCTION.set(node.name.getText(), node.body); 437 } 438 STYLES_ATTRIBUTE.add(node.name.getText()); 439 BUILDIN_STYLE_NAMES.add(node.name.getText()); 440 } 441 } 442 if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) { 443 // ark compiler's feature 444 checkConcurrentDecorator(node, log, sourceFileNode); 445 } 446 } 447 if (ts.isIdentifier(node) && (ts.isDecorator(node.parent) || 448 (ts.isCallExpression(node.parent) && ts.isDecorator(node.parent.parent)))) { 449 const decoratorName: string = node.escapedText.toString(); 450 validateStructDecorator(sourceFileNode, node, log, structContext, decoratorName); 451 validateMethodDecorator(sourceFileNode, node, log, structContext, decoratorName); 452 validateClassDecorator(sourceFileNode, node, log, classContext, decoratorName); 453 } 454 node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, 455 log, structContext, classContext, fileQuery)); 456 structContext = false; 457 classContext = false; 458} 459 460function validateClassDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 461 classContext: boolean, decoratorName: string): void { 462 if (!classContext && decoratorName === CLASS_TRACK_DECORATOR) { 463 const message: string = `The '@Track' decorator can only be used in 'class'.`; 464 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 465 } else if ('@' + decoratorName === COMPONENT_SENDABLE_DECORATOR && 466 (!node.parent || !node.parent.parent || !ts.isClassDeclaration(node.parent.parent))) { 467 const message: string = 'The \'@Sendable\' decorator can only be added to \'class\'.'; 468 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 469 } 470} 471 472function validateStructDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 473 structContext: boolean, decoratorName: string): void { 474 if (!structContext && STRUCT_DECORATORS.has(`@${decoratorName}`)) { 475 const message: string = `The '@${decoratorName}' decorator can only be used with 'struct'.`; 476 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 477 } 478} 479 480function validateMethodDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], 481 structContext: boolean, decoratorName: string): void { 482 let message: string; 483 if (ts.isMethodDeclaration(node.parent.parent) || 484 (ts.isDecorator(node.parent.parent) && ts.isMethodDeclaration(node.parent.parent.parent))) { 485 if (!structContext && STRUCT_CONTEXT_METHOD_DECORATORS.has(`@${decoratorName}`)) { 486 message = `The '@${decoratorName}' decorator can only be used in 'struct'.`; 487 } 488 if (CHECK_EXTEND_DECORATORS.includes(decoratorName)) { 489 message = `The '@${decoratorName}' decorator can not be a member property method of a 'class' or 'struct'.`; 490 } 491 if (message) { 492 addLog(LogType.ERROR, message, node.pos, log, sourceFileNode); 493 } 494 } 495} 496 497function isSendableClassDeclaration(classDeclarationNode: ts.ClassDeclaration): boolean { 498 return hasDecorator(classDeclarationNode, COMPONENT_SENDABLE_DECORATOR) || 499 (classDeclarationNode.members && classDeclarationNode.members.some((member: ts.Node) => { 500 // Check if the constructor has "use sendable" as the first statement 501 // (Sendable classes already transformed by process_ui_syntax.ts) 502 if (ts.isConstructorDeclaration(member)) { 503 if (!(member as ts.ConstructorDeclaration).body || 504 !(member as ts.ConstructorDeclaration).body.statements) { 505 return false; 506 } 507 const constructorStatements: ts.Statement[] = (member as ts.ConstructorDeclaration).body.statements; 508 if (constructorStatements && constructorStatements[0] && 509 ts.isExpressionStatement(constructorStatements[0])) { 510 const expression: ts.Node = (constructorStatements[0] as ts.ExpressionStatement).expression; 511 return expression && ts.isStringLiteral(expression) && 512 (expression as ts.StringLiteral).text === 'use sendable'; 513 } 514 } 515 return false; 516 })); 517} 518 519function isSendableTypeReference(type: ts.Type): boolean { 520 if (!type || !type.getSymbol() || !type.getSymbol().valueDeclaration) { 521 return false; 522 } 523 const valueDeclarationNode: ts.Node = type.getSymbol().valueDeclaration; 524 if (valueDeclarationNode && ts.isClassDeclaration(valueDeclarationNode)) { 525 return isSendableClassDeclaration(valueDeclarationNode as ts.ClassDeclaration); 526 } 527 return false; 528} 529 530function isSendableTypeNode(typeNode: ts.TypeNode): boolean { 531 // input typeNode should not be undefined or none, ensured by caller 532 switch (typeNode.kind) { 533 case ts.SyntaxKind.StringKeyword: 534 case ts.SyntaxKind.NumberKeyword: 535 case ts.SyntaxKind.BooleanKeyword: 536 return true; 537 case ts.SyntaxKind.TypeReference: { 538 if (globalProgram.checker) { 539 return isSendableTypeReference(globalProgram.checker.getTypeFromTypeNode(typeNode)); 540 } 541 return false; 542 } 543 default: 544 return false; 545 } 546} 547 548function validateSendableClass(sourceFileNode: ts.SourceFile, node: ts.ClassDeclaration, log: LogInfo[]): void { 549 // process parent classes 550 // extend clause must precede implements clause, so parent can only be heritageClauses[0] 551 if (node.heritageClauses && node.heritageClauses.length >= 1 && 552 node.heritageClauses[0].token && node.heritageClauses[0].token === ts.SyntaxKind.ExtendsKeyword && 553 node.heritageClauses[0].types && node.heritageClauses[0].types.length === 1) { 554 const expressionNode: ts.ExpressionWithTypeArguments = node.heritageClauses[0].types[0]; 555 if (expressionNode.expression && ts.isIdentifier(expressionNode.expression) && globalProgram.checker) { 556 if (!isSendableTypeReference(globalProgram.checker.getTypeAtLocation(expressionNode.expression))) { 557 addLog(LogType.ERROR, 'The parent class of @Sendable classes must be @Sendable class', 558 expressionNode.expression.getStart(), log, sourceFileNode); 559 } 560 } 561 } 562 563 // process all properties 564 node.members.forEach(item => { 565 if (ts.isPropertyDeclaration(item)) { 566 const propertyItem: ts.PropertyDeclaration = (item as ts.PropertyDeclaration); 567 if (propertyItem.questionToken) { 568 addLog(LogType.ERROR, 'Optional properties are not supported in @Sendable classes', 569 propertyItem.questionToken.getStart(), log, sourceFileNode); 570 } else if (propertyItem.exclamationToken) { 571 addLog(LogType.ERROR, 'Definite assignment assertions are not supported in @Sendable classes.', 572 propertyItem.exclamationToken.getStart(), log, sourceFileNode); 573 } else if (!ts.isIdentifier(propertyItem.name)) { 574 addLog(LogType.ERROR, 575 'Properties with names that are not identifiers are not supported in @Sendable classes.', 576 propertyItem.name.getStart(), log, sourceFileNode); 577 } else if (!propertyItem.type) { 578 addLog(LogType.ERROR, 579 'Property declarations without explicit types are not supported in @Sendable classes.', 580 propertyItem.name.getStart(), log, sourceFileNode); 581 } else if (!isSendableTypeNode(propertyItem.type)) { 582 addLog(LogType.ERROR, 583 'Properties with types that are not basic types (string, number, boolean) or @Sendable classes ' + 584 'are not supported in @Sendable classes.', 585 propertyItem.type.getStart(), log, sourceFileNode); 586 } 587 } else if (ts.isGetAccessorDeclaration(item) || ts.isSetAccessorDeclaration(item)) { 588 if (!ts.isIdentifier((item as ts.AccessorDeclaration).name)) { 589 addLog(LogType.ERROR, 590 'Accessors with names that are not identifiers are not supported in @Sendable classes.', 591 item.name.getStart(), log, sourceFileNode); 592 } 593 } 594 }); 595} 596 597export function checkAllNode( 598 node: ts.EtsComponentExpression, 599 allComponentNames: Set<string>, 600 sourceFileNode: ts.SourceFile, 601 log: LogInfo[] 602): void { 603 if (ts.isIdentifier(node.expression)) { 604 checkNoChildComponent(node, sourceFileNode, log); 605 checkOneChildComponent(node, allComponentNames, sourceFileNode, log); 606 checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log); 607 } 608} 609 610interface ParamType { 611 name: string, 612 value: string, 613} 614 615function checkNoChildComponent(node: ts.EtsComponentExpression, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 616 const isCheckType: ParamType = { name: null, value: null}; 617 if (hasChild(node, isCheckType)) { 618 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 619 const pos: number = node.expression.getStart(); 620 const message: string = isCheckType.name === null ? 621 `The component '${componentName}' can't have any child.` : 622 `When the component '${componentName}' set '${isCheckType.name}' is '${isCheckType.value}'` + 623 `, can't have any child.`; 624 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 625 } 626} 627 628function hasChild(node: ts.EtsComponentExpression, isCheckType: ParamType): boolean { 629 const nodeName: ts.Identifier = node.expression as ts.Identifier; 630 if ((AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) || judgeComponentType(nodeName, node, isCheckType)) && 631 getNextNode(node)) { 632 return true; 633 } 634 return false; 635} 636 637function judgeComponentType(nodeName: ts.Identifier, etsComponentExpression: ts.EtsComponentExpression, 638 isCheckType: ParamType): boolean { 639 return COMPONENT_TOGGLE === nodeName.escapedText.toString() && 640 etsComponentExpression.arguments && etsComponentExpression.arguments[0] && 641 ts.isObjectLiteralExpression(etsComponentExpression.arguments[0]) && 642 etsComponentExpression.arguments[0].getText() && 643 judgeToggleComponentParamType(etsComponentExpression.arguments[0].getText(), isCheckType); 644} 645 646function judgeToggleComponentParamType(param: string, isCheckType: ParamType): boolean { 647 if (param.indexOf(RESOURCE_NAME_TYPE) > -1) { 648 isCheckType.name = RESOURCE_NAME_TYPE; 649 const match: string[] = param.match(/\b(Checkbox|Switch|Button)\b/); 650 if (match && match.length) { 651 isCheckType.value = match[0]; 652 if (isCheckType.value === COMPONENT_BUTTON) { 653 return false; 654 } 655 return true; 656 } 657 } 658 return false; 659} 660 661function getNextNode(node: ts.EtsComponentExpression): ts.Block { 662 if (node.body && ts.isBlock(node.body)) { 663 const statementsArray: ts.Block = node.body; 664 return statementsArray; 665 } 666} 667 668function checkOneChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 669 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 670 const isCheckType: ParamType = { name: null, value: null}; 671 if (hasNonSingleChild(node, allComponentNames, isCheckType)) { 672 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 673 const pos: number = node.expression.getStart(); 674 const message: string = isCheckType.name === null ? 675 `The component '${componentName}' can only have a single child component.` : 676 `When the component '${componentName}' set '${isCheckType.name}' is ` + 677 `'${isCheckType.value}', can only have a single child component.`; 678 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 679 } 680} 681 682function hasNonSingleChild(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 683 isCheckType: ParamType): boolean { 684 const nodeName: ts.Identifier = node.expression as ts.Identifier; 685 const BlockNode: ts.Block = getNextNode(node); 686 if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString()) || !judgeComponentType(nodeName, node, isCheckType) && 687 isCheckType.value === COMPONENT_BUTTON) { 688 if (!BlockNode) { 689 return false; 690 } 691 if (BlockNode && BlockNode.statements) { 692 const length: number = BlockNode.statements.length; 693 if (!length) { 694 return false; 695 } 696 if (length > 3) { 697 return true; 698 } 699 const childCount: number = getBlockChildrenCount(BlockNode, allComponentNames); 700 if (childCount > 1) { 701 return true; 702 } 703 } 704 } 705 return false; 706} 707 708function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set<string>): number { 709 let maxCount: number = 0; 710 const length: number = blockNode.statements.length; 711 for (let i = 0; i < length; ++i) { 712 const item: ts.Node = blockNode.statements[i]; 713 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && 714 isForEachComponent(item.expression)) { 715 maxCount += 2; 716 } 717 if (ts.isIfStatement(item)) { 718 maxCount += getIfChildrenCount(item, allComponentNames); 719 } 720 if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) { 721 maxCount += 1; 722 } 723 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) { 724 let newNode: any = item.expression; 725 while (newNode.expression) { 726 if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) && 727 isComponent(newNode, allComponentNames)) { 728 maxCount += 1; 729 } 730 newNode = newNode.expression; 731 } 732 } 733 if (maxCount > 1) { 734 break; 735 } 736 } 737 return maxCount; 738} 739 740function isComponent(node: ts.EtsComponentExpression | ts.CallExpression, allComponentNames: Set<string>): boolean { 741 if (ts.isIdentifier(node.expression) && 742 allComponentNames.has(node.expression.escapedText.toString())) { 743 return true; 744 } 745 return false; 746} 747 748function isForEachComponent(node: ts.EtsComponentExpression | ts.CallExpression): boolean { 749 if (ts.isIdentifier(node.expression)) { 750 const componentName: string = node.expression.escapedText.toString(); 751 return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH; 752 } 753 return false; 754} 755 756function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set<string>): number { 757 const maxCount: number = 758 Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), 759 getStatementCount(ifNode.elseStatement, allComponentNames)); 760 return maxCount; 761} 762 763function getStatementCount(node: ts.Node, allComponentNames: Set<string>): number { 764 let maxCount: number = 0; 765 if (!node) { 766 return maxCount; 767 } else if (ts.isBlock(node)) { 768 maxCount = getBlockChildrenCount(node, allComponentNames); 769 } else if (ts.isIfStatement(node)) { 770 maxCount = getIfChildrenCount(node, allComponentNames); 771 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 772 isForEachComponent(node.expression)) { 773 maxCount = 2; 774 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 775 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { 776 maxCount = 1; 777 } 778 return maxCount; 779} 780 781function checkSpecificChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>, 782 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 783 if (hasNonspecificChild(node, allComponentNames)) { 784 const componentName: string = (node.expression as ts.Identifier).escapedText.toString(); 785 const pos: number = node.expression.getStart(); 786 const specificChildArray: string = 787 Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and '); 788 const message: string = 789 `The component '${componentName}' can only have the child component ${specificChildArray}.`; 790 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 791 } 792} 793 794function hasNonspecificChild(node: ts.EtsComponentExpression, 795 allComponentNames: Set<string>): boolean { 796 const nodeName: ts.Identifier = node.expression as ts.Identifier; 797 const nodeNameString: string = nodeName.escapedText.toString(); 798 const blockNode: ts.Block = getNextNode(node); 799 let isNonspecific: boolean = false; 800 if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { 801 const specificChildSet: Set<string> = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); 802 isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); 803 if (isNonspecific) { 804 return isNonspecific; 805 } 806 } 807 return isNonspecific; 808} 809 810function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set<string>, 811 allComponentNames: Set<string>): boolean { 812 if (blockNode.statements) { 813 const length: number = blockNode.statements.length; 814 for (let i = 0; i < length; ++i) { 815 const item: ts.Node = blockNode.statements[i]; 816 if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) { 817 return true; 818 } 819 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && 820 isForEachComponent(item.expression) && 821 isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) { 822 return true; 823 } 824 if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) { 825 return true; 826 } 827 if (ts.isExpressionStatement(item)) { 828 let newNode: any = item.expression; 829 while (newNode.expression) { 830 if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) && 831 !isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) { 832 const isNonspecific: boolean = 833 isNonspecificChildNonForEach(newNode, specificChildSet); 834 if (isNonspecific) { 835 return isNonspecific; 836 } 837 if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { 838 ++i; 839 } 840 } 841 newNode = newNode.expression; 842 } 843 } 844 } 845 } 846 return false; 847} 848 849function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set<string>, 850 allComponentNames: Set<string>): boolean { 851 return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || 852 isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); 853} 854 855function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set<string>, 856 allComponentNames: Set<string>): boolean { 857 if (ts.isCallExpression(node) && node.arguments && 858 node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { 859 const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; 860 const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement = 861 arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement; 862 if (!body) { 863 return false; 864 } 865 if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) { 866 return true; 867 } 868 if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) { 869 return true; 870 } 871 if (ts.isCallExpression(body) && isForEachComponent(body) && 872 isNonspecificChildForEach(body, specificChildSet, allComponentNames)) { 873 return true; 874 } 875 if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) && 876 isComponent(body, allComponentNames) && 877 isNonspecificChildNonForEach(body, specificChildSet)) { 878 return true; 879 } 880 } 881 return false; 882} 883 884function isNonspecificChildNonForEach(node: ts.EtsComponentExpression, 885 specificChildSet: Set<string>): boolean { 886 if (ts.isIdentifier(node.expression) && 887 !specificChildSet.has(node.expression.escapedText.toString())) { 888 return true; 889 } 890 return false; 891} 892 893function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set<string>, 894 allComponentNames: Set<string>): boolean { 895 if (!node) { 896 return false; 897 } 898 if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) { 899 return true; 900 } 901 if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) { 902 return true; 903 } 904 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 905 isForEachComponent(node.expression) && 906 isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { 907 return true; 908 } 909 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 910 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && 911 isNonspecificChildNonForEach(node.expression, specificChildSet)) { 912 return true; 913 } 914 return false; 915} 916 917function collectComponentProps(node: ts.StructDeclaration, judgeInitializeInEntry: boolean): void { 918 const componentName: string = node.name.getText(); 919 const ComponentSet: IComponentSet = getComponentSet(node, judgeInitializeInEntry); 920 propertyCollection.set(componentName, ComponentSet.properties); 921 stateCollection.set(componentName, ComponentSet.states); 922 linkCollection.set(componentName, ComponentSet.links); 923 propCollection.set(componentName, ComponentSet.props); 924 regularCollection.set(componentName, ComponentSet.regulars); 925 storagePropCollection.set(componentName, ComponentSet.storageProps); 926 storageLinkCollection.set(componentName, ComponentSet.storageLinks); 927 provideCollection.set(componentName, ComponentSet.provides); 928 consumeCollection.set(componentName, ComponentSet.consumes); 929 objectLinkCollection.set(componentName, ComponentSet.objectLinks); 930 localStorageLinkCollection.set(componentName, ComponentSet.localStorageLink); 931 localStoragePropCollection.set(componentName, ComponentSet.localStorageProp); 932 builderParamObjectCollection.set(componentName, ComponentSet.builderParams); 933 builderParamInitialization.set(componentName, ComponentSet.builderParamData); 934 propInitialization.set(componentName, ComponentSet.propData); 935} 936 937export function getComponentSet(node: ts.StructDeclaration, judgeInitializeInEntry: boolean): IComponentSet { 938 const properties: Set<string> = new Set(); 939 const states: Set<string> = new Set(); 940 const links: Set<string> = new Set(); 941 const props: Set<string> = new Set(); 942 const regulars: Set<string> = new Set(); 943 const storageProps: Set<string> = new Set(); 944 const storageLinks: Set<string> = new Set(); 945 const provides: Set<string> = new Set(); 946 const consumes: Set<string> = new Set(); 947 const objectLinks: Set<string> = new Set(); 948 const builderParams: Set<string> = new Set(); 949 const localStorageLink: Map<string, Set<string>> = new Map(); 950 const localStorageProp: Map<string, Set<string>> = new Map(); 951 const builderParamData: Set<string> = new Set(); 952 const propData: Set<string> = new Set(); 953 traversalComponentProps(node, judgeInitializeInEntry, properties, regulars, states, links, props, 954 storageProps, storageLinks, provides, consumes, objectLinks, localStorageLink, localStorageProp, 955 builderParams, builderParamData, propData); 956 return { 957 properties, regulars, states, links, props, storageProps, storageLinks, provides, consumes, 958 objectLinks, localStorageLink, localStorageProp, builderParams, builderParamData, propData 959 }; 960} 961 962class RecordRequire { 963 hasRequire: boolean = false; 964 hasProp: boolean = false; 965 hasBuilderParam: boolean = false; 966} 967 968function traversalComponentProps(node: ts.StructDeclaration, judgeInitializeInEntry: boolean, 969 properties: Set<string>, regulars: Set<string>, states: Set<string>, links: Set<string>, props: Set<string>, 970 storageProps: Set<string>, storageLinks: Set<string>, provides: Set<string>, 971 consumes: Set<string>, objectLinks: Set<string>, 972 localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>, 973 builderParams: Set<string>, builderParamData: Set<string>, propData: Set<string>): void { 974 let isStatic: boolean = true; 975 if (node.members) { 976 const currentMethodCollection: Set<string> = new Set(); 977 node.members.forEach(item => { 978 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 979 const propertyName: string = item.name.getText(); 980 properties.add(propertyName); 981 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 982 if (!decorators || !decorators.length) { 983 regulars.add(propertyName); 984 } else { 985 isStatic = false; 986 const recordRequire: RecordRequire = new RecordRequire(); 987 for (let i = 0; i < decorators.length; i++) { 988 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 989 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 990 dollarCollection.add('$' + propertyName); 991 collectionStates(decorators[i], judgeInitializeInEntry, decoratorName, propertyName, 992 states, links, props, storageProps, storageLinks, provides, consumes, objectLinks, 993 localStorageLink, localStorageProp, builderParams, recordRequire); 994 } 995 } 996 checkRequire(propertyName, builderParamData, propData, recordRequire); 997 } 998 } 999 if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) { 1000 validateStateVariable(item); 1001 currentMethodCollection.add(item.name.getText()); 1002 } 1003 }); 1004 classMethodCollection.set(node.name.getText(), currentMethodCollection); 1005 } 1006 isStaticViewCollection.set(node.name.getText(), isStatic); 1007} 1008 1009function checkRequire(name: string, builderParamData: Set<string>, 1010 propData: Set<string>, recordRequire: RecordRequire): void { 1011 if (recordRequire.hasRequire) { 1012 if (recordRequire.hasProp) { 1013 propData.add(name); 1014 } 1015 if (recordRequire.hasBuilderParam) { 1016 builderParamData.add(name); 1017 } 1018 } 1019} 1020 1021function collectionStates(node: ts.Decorator, judgeInitializeInEntry: boolean, decorator: string, name: string, 1022 states: Set<string>, links: Set<string>, props: Set<string>, storageProps: Set<string>, 1023 storageLinks: Set<string>, provides: Set<string>, consumes: Set<string>, objectLinks: Set<string>, 1024 localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>, 1025 builderParams: Set<string>, recordRequire: RecordRequire): void { 1026 switch (decorator) { 1027 case COMPONENT_STATE_DECORATOR: 1028 states.add(name); 1029 break; 1030 case COMPONENT_LINK_DECORATOR: 1031 links.add(name); 1032 break; 1033 case COMPONENT_PROP_DECORATOR: 1034 recordRequire.hasProp = true; 1035 props.add(name); 1036 break; 1037 case COMPONENT_STORAGE_PROP_DECORATOR: 1038 storageProps.add(name); 1039 break; 1040 case COMPONENT_STORAGE_LINK_DECORATOR: 1041 storageLinks.add(name); 1042 break; 1043 case COMPONENT_PROVIDE_DECORATOR: 1044 provides.add(name); 1045 break; 1046 case COMPONENT_CONSUME_DECORATOR: 1047 consumes.add(name); 1048 break; 1049 case COMPONENT_OBJECT_LINK_DECORATOR: 1050 objectLinks.add(name); 1051 break; 1052 case COMPONENT_BUILDERPARAM_DECORATOR: 1053 if (judgeInitializeInEntry) { 1054 transformLog.errors.push({ 1055 type: LogType.WARN, 1056 message: `'${name}' should be initialized in @Entry Component`, 1057 pos: node.getStart() 1058 }); 1059 } 1060 recordRequire.hasBuilderParam = true; 1061 builderParams.add(name); 1062 break; 1063 case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR : 1064 collectionlocalStorageParam(node, name, localStorageLink); 1065 break; 1066 case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: 1067 collectionlocalStorageParam(node, name, localStorageProp); 1068 break; 1069 case COMPONENT_REQUIRE_DECORATOR: 1070 recordRequire.hasRequire = true; 1071 break; 1072 } 1073} 1074 1075function collectionlocalStorageParam(node: ts.Decorator, name: string, 1076 localStorage: Map<string, Set<string>>): void { 1077 const localStorageParam: Set<string> = new Set(); 1078 if (node && ts.isCallExpression(node.expression) && node.expression.arguments && 1079 node.expression.arguments.length) { 1080 localStorage.set(name, localStorageParam.add( 1081 node.expression.arguments[0].getText())); 1082 } 1083} 1084 1085export interface ReplaceResult { 1086 content: string, 1087 log: LogInfo[] 1088} 1089 1090export function sourceReplace(source: string, sourcePath: string): ReplaceResult { 1091 let content: string = source; 1092 const log: LogInfo[] = []; 1093 content = preprocessExtend(content); 1094 content = preprocessNewExtend(content); 1095 // process @system. 1096 content = processSystemApi(content, false, sourcePath); 1097 CollectImportNames(content, sourcePath); 1098 1099 return { 1100 content: content, 1101 log: log 1102 }; 1103} 1104 1105export function preprocessExtend(content: string, extendCollection?: Set<string>): string { 1106 const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm; 1107 return content.replace(REG_EXTEND, (item, item1, item2, item3) => { 1108 collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3); 1109 collectExtend(EXTEND_ATTRIBUTE, item2, item3); 1110 if (extendCollection) { 1111 extendCollection.add(item3); 1112 } 1113 return `@Extend(${item2})${item1}function __${item2}__${item3}(`; 1114 }); 1115} 1116 1117export function preprocessNewExtend(content: string, extendCollection?: Set<string>): string { 1118 const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm; 1119 return content.replace(REG_EXTEND, (item, item1) => { 1120 if (extendCollection) { 1121 extendCollection.add(item1); 1122 } 1123 return item; 1124 }); 1125} 1126 1127function replaceSystemApi(item: string, systemValue: string, moduleType: string, systemKey: string): string { 1128 // if change format, please update regexp in transformModuleSpecifier 1129 if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { 1130 item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; 1131 } else if (moduleType === SYSTEM_PLUGIN || moduleType === OHOS_PLUGIN) { 1132 item = `var ${systemValue} = globalThis.requireNapi('${systemKey}')`; 1133 } 1134 return item; 1135} 1136 1137function replaceLibSo(importValue: string, libSoKey: string, sourcePath: string = null): string { 1138 if (sourcePath) { 1139 useOSFiles.add(sourcePath); 1140 } 1141 // if change format, please update regexp in transformModuleSpecifier 1142 return projectConfig.bundleName && projectConfig.moduleName ? 1143 `var ${importValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");` : 1144 `var ${importValue} = globalThis.requireNapi("${libSoKey}", true);`; 1145} 1146 1147export function processSystemApi(content: string, isProcessAllowList: boolean = false, 1148 sourcePath: string = null, isSystemModule: boolean = false): string { 1149 if (isProcessAllowList && projectConfig.compileMode === ESMODULE) { 1150 // remove the unused system api import decl like following when compile as [esmodule] 1151 // in the result_process phase 1152 // e.g. import "@ohos.application.xxx" 1153 const REG_UNUSED_SYSTEM_IMPORT: RegExp = /import(?:\s*)['"]@(system|ohos)\.(\S+)['"]/g; 1154 content = content.replace(REG_UNUSED_SYSTEM_IMPORT, ''); 1155 } 1156 1157 const REG_IMPORT_DECL: RegExp = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? 1158 /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]/g : 1159 /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g : 1160 /(import|export)\s+(?:(.+)|\{([\s\S]+)\})\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; 1161 1162 const systemValueCollection: Set<string> = new Set(); 1163 const processedContent: string = content.replace(REG_IMPORT_DECL, (item, item1, item2, item3, item4, item5, item6) => { 1164 const importValue: string = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? item1 : item2 : item2 || item5; 1165 1166 if (isProcessAllowList) { 1167 systemValueCollection.add(importValue); 1168 if (projectConfig.compileMode !== ESMODULE) { 1169 collectSourcemapNames(sourcePath, importValue, item5); 1170 return replaceSystemApi(item, importValue, item4, item5); 1171 } 1172 collectSourcemapNames(sourcePath, importValue, item3); 1173 return replaceSystemApi(item, importValue, item2, item3); 1174 } 1175 1176 const moduleRequest: string = item4 || item6; 1177 if (/^@(system|ohos)\./.test(moduleRequest)) { // ohos/system.api 1178 // ets & ts file need compile with .d.ts, so do not replace at the phase of pre_process 1179 if (!isSystemModule) { 1180 return item; 1181 } 1182 const result: RegExpMatchArray = moduleRequest.match(/^@(system|ohos)\.(\S+)$/); 1183 const moduleType: string = result[1]; 1184 const apiName: string = result[2]; 1185 return replaceSystemApi(item, importValue, moduleType, apiName); 1186 } else if (/^lib(\S+)\.so$/.test(moduleRequest)) { // libxxx.so 1187 const result: RegExpMatchArray = moduleRequest.match(/^lib(\S+)\.so$/); 1188 const libSoKey: string = result[1]; 1189 return replaceLibSo(importValue, libSoKey, sourcePath); 1190 } 1191 return item; 1192 }); 1193 return processInnerModule(processedContent, systemValueCollection); 1194} 1195 1196function collectSourcemapNames(sourcePath: string, changedName: string, originalName: string): void { 1197 if (sourcePath == null) { 1198 return; 1199 } 1200 const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); 1201 if (!sourcemapNamesCollection.has(cleanSourcePath)) { 1202 return; 1203 } 1204 1205 const map: Map<string, string> = sourcemapNamesCollection.get(cleanSourcePath); 1206 if (map.has(changedName)) { 1207 return; 1208 } 1209 1210 for (const entry of originalImportNamesMap.entries()) { 1211 const key: string = entry[0]; 1212 const value: string = entry[1]; 1213 if (value === '@ohos.' + originalName || value === '@system.' + originalName) { 1214 map.set(changedName.trim(), key); 1215 sourcemapNamesCollection.set(cleanSourcePath, map); 1216 originalImportNamesMap.delete(key); 1217 break; 1218 } 1219 } 1220} 1221 1222export function CollectImportNames(content: string, sourcePath: string = null): void { 1223 const REG_IMPORT_DECL: RegExp = 1224 /(import|export)\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; 1225 1226 const decls: string[] = content.match(REG_IMPORT_DECL); 1227 if (decls != undefined) { 1228 decls.forEach(decl => { 1229 const parts: string[] = decl.split(' '); 1230 if (parts.length === 4 && parts[0] === 'import' && parts[2] === 'from' && !parts[3].includes('.so')) { 1231 originalImportNamesMap.set(parts[1], parts[3].replace(/'/g, '')); 1232 } 1233 }); 1234 } 1235 1236 if (sourcePath && sourcePath != null) { 1237 const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js'); 1238 if (!sourcemapNamesCollection.has(cleanSourcePath)) { 1239 sourcemapNamesCollection.set(cleanSourcePath, new Map()); 1240 } 1241 } 1242} 1243 1244function processInnerModule(content: string, systemValueCollection: Set<string>): string { 1245 systemValueCollection.forEach(element => { 1246 const target: string = element.trim() + '.default'; 1247 while (content.includes(target)) { 1248 content = content.replace(target, element.trim()); 1249 } 1250 }); 1251 return content; 1252} 1253 1254export function resetComponentCollection() { 1255 componentCollection.entryComponent = null; 1256 componentCollection.entryComponentPos = null; 1257 componentCollection.previewComponent = new Array(); 1258 stateObjectCollection.clear(); 1259 builderParamInitialization.clear(); 1260 propInitialization.clear(); 1261 propCollection.clear(); 1262 objectLinkCollection.clear(); 1263 linkCollection.clear(); 1264} 1265 1266function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void { 1267 const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; 1268 if (modifiers) { 1269 for (let i = 0; i < modifiers.length; i++) { 1270 if (modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { 1271 const message: string = `It's not a recommended way to export struct with @Entry decorator, ` + 1272 `which may cause ACE Engine error in component preview mode.`; 1273 addLog(LogType.WARN, message, node.getStart(), log, sourceFile); 1274 break; 1275 } 1276 } 1277 } 1278} 1279 1280function validateStateVariable(node: ts.MethodDeclaration): void { 1281 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 1282 if (decorators && decorators.length) { 1283 for (let i = 0; i < decorators.length; i++) { 1284 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1285 if (CARD_ENABLE_DECORATORS[decoratorName]) { 1286 validatorCard(transformLog.errors, CARD_LOG_TYPE_DECORATORS, 1287 decorators[i].getStart(), decoratorName); 1288 } 1289 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 1290 transformLog.errors.push({ 1291 type: LogType.ERROR, 1292 message: `'${decorators[i].getText()}' can not decorate the method.`, 1293 pos: decorators[i].getStart() 1294 }); 1295 } 1296 } 1297 } 1298} 1299 1300export function getObservedPropertyCollection(className: string): Set<string> { 1301 const observedProperthCollection: Set<string> = new Set([ 1302 ...stateCollection.get(className), 1303 ...linkCollection.get(className), 1304 ...propCollection.get(className), 1305 ...storageLinkCollection.get(className), 1306 ...storageLinkCollection.get(className), 1307 ...provideCollection.get(className), 1308 ...consumeCollection.get(className), 1309 ...objectLinkCollection.get(className) 1310 ]); 1311 getLocalStorageCollection(className, observedProperthCollection); 1312 return observedProperthCollection; 1313} 1314 1315export function getLocalStorageCollection(componentName: string, collection: Set<string>): void { 1316 if (localStorageLinkCollection.get(componentName)) { 1317 for (const key of localStorageLinkCollection.get(componentName).keys()) { 1318 collection.add(key); 1319 } 1320 } 1321 if (localStoragePropCollection.get(componentName)) { 1322 for (const key of localStoragePropCollection.get(componentName).keys()) { 1323 collection.add(key); 1324 } 1325 } 1326} 1327 1328export function resetValidateUiSyntax(): void { 1329 observedClassCollection.clear(); 1330 enumCollection.clear(); 1331 classMethodCollection.clear(); 1332 dollarCollection.clear(); 1333 stateCollection.clear(); 1334 regularCollection.clear(); 1335 storagePropCollection.clear(); 1336 storageLinkCollection.clear(); 1337 provideCollection.clear(); 1338 consumeCollection.clear(); 1339 builderParamObjectCollection.clear(); 1340 localStorageLinkCollection.clear(); 1341 localStoragePropCollection.clear(); 1342 isStaticViewCollection.clear(); 1343 useOSFiles.clear(); 1344 sourcemapNamesCollection.clear(); 1345 originalImportNamesMap.clear(); 1346} 1347