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