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 STYLES, 43 VALIDATE_MODULE, 44 COMPONENT_BUILDER_DECORATOR, 45 COMPONENT_BUILDERPARAM_DECORATOR 46} from './pre_define'; 47import { 48 INNER_COMPONENT_NAMES, 49 AUTOMIC_COMPONENT, 50 SINGLE_CHILD_COMPONENT, 51 SPECIFIC_CHILD_COMPONENT, 52 BUILDIN_STYLE_NAMES, 53 EXTEND_ATTRIBUTE, 54 GLOBAL_STYLE_FUNCTION, 55 STYLES_ATTRIBUTE, 56 CUSTOM_BUILDER_METHOD 57} from './component_map'; 58import { 59 LogType, 60 LogInfo, 61 componentInfo, 62 addLog, 63 hasDecorator 64} from './utils'; 65import { projectConfig } from '../main'; 66import { 67 collectExtend, 68 isExtendFunction, 69 transformLog 70} from './process_ui_syntax'; 71import { importModuleCollection } from './ets_checker'; 72import { builderParamObjectCollection } from './process_component_member'; 73 74export interface ComponentCollection { 75 localStorageName: string; 76 entryComponentPos: number; 77 entryComponent: string; 78 previewComponent: Set<string>; 79 customDialogs: Set<string>; 80 customComponents: Set<string>; 81 currentClassName: string; 82} 83 84export interface IComponentSet { 85 properties: Set<string>; 86 regulars: Set<string>; 87 states: Set<string>; 88 links: Set<string>; 89 props: Set<string>; 90 storageProps: Set<string>; 91 storageLinks: Set<string>; 92 provides: Set<string>; 93 consumes: Set<string>; 94 objectLinks: Set<string>; 95 localStorageLink: Map<string, Set<string>>; 96 localStorageProp: Map<string, Set<string>>; 97 builderParams: Set<string>; 98} 99 100export const componentCollection: ComponentCollection = { 101 localStorageName: null, 102 entryComponentPos: null, 103 entryComponent: null, 104 previewComponent: new Set([]), 105 customDialogs: new Set([]), 106 customComponents: new Set([]), 107 currentClassName: null 108}; 109 110export const observedClassCollection: Set<string> = new Set(); 111export const enumCollection: Set<string> = new Set(); 112export const classMethodCollection: Map<string, Set<string>> = new Map(); 113export const dollarCollection: Set<string> = new Set(); 114 115export const propertyCollection: Map<string, Set<string>> = new Map(); 116export const stateCollection: Map<string, Set<string>> = new Map(); 117export const linkCollection: Map<string, Set<string>> = new Map(); 118export const propCollection: Map<string, Set<string>> = new Map(); 119export const regularCollection: Map<string, Set<string>> = new Map(); 120export const storagePropCollection: Map<string, Set<string>> = new Map(); 121export const storageLinkCollection: Map<string, Set<string>> = new Map(); 122export const provideCollection: Map<string, Set<string>> = new Map(); 123export const consumeCollection: Map<string, Set<string>> = new Map(); 124export const objectLinkCollection: Map<string, Set<string>> = new Map(); 125export const localStorageLinkCollection: Map<string, Map<string, Set<string>>> = new Map(); 126export const localStoragePropCollection: Map<string, Map<string, Set<string>>> = new Map(); 127 128export const isStaticViewCollection: Map<string, boolean> = new Map(); 129 130export const moduleCollection: Set<string> = new Set(); 131export const useOSFiles: Set<string> = new Set(); 132 133export function validateUISyntax(source: string, content: string, filePath: string, 134 fileQuery: string): LogInfo[] { 135 let log: LogInfo[] = []; 136 if (process.env.compileMode === 'moduleJson' || 137 path.resolve(filePath) !== path.resolve(projectConfig.projectPath || '', 'app.ets')) { 138 const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery); 139 if (res) { 140 log = log.concat(res); 141 } 142 const allComponentNames: Set<string> = 143 new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]); 144 checkUISyntax(filePath, allComponentNames, content, log); 145 componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item)); 146 } 147 148 return log; 149} 150 151function checkComponentDecorator(source: string, filePath: string, 152 fileQuery: string): LogInfo[] | null { 153 const log: LogInfo[] = []; 154 const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, source, 155 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 156 if (sourceFile && sourceFile.statements && sourceFile.statements.length) { 157 const result: DecoratorResult = { 158 entryCount: 0, 159 previewCount: 0 160 }; 161 sourceFile.statements.forEach((item, index, arr) => { 162 if (isObservedClass(item)) { 163 // @ts-ignore 164 observedClassCollection.add(item.name.getText()); 165 } 166 if (ts.isEnumDeclaration(item) && item.name) { 167 enumCollection.add(item.name.getText()); 168 } 169 if (ts.isStructDeclaration(item)) { 170 if (item.name && ts.isIdentifier(item.name)) { 171 if (item.decorators && item.decorators.length) { 172 checkDecorators(item.decorators, result, item.name, log, sourceFile, item); 173 } else { 174 const message: string = `A struct should use decorator '@Component'.`; 175 addLog(LogType.WARN, message, item.getStart(), log, sourceFile); 176 } 177 } else { 178 const message: string = `A struct must have a name.`; 179 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 180 } 181 } 182 if (ts.isMissingDeclaration(item)) { 183 const decorators: ts.NodeArray<ts.Decorator> = item.decorators; 184 for (let i = 0; i < decorators.length; i++) { 185 if (decorators[i] && /struct/.test(decorators[i].getText())) { 186 const message: string = `Please use a valid decorator.`; 187 addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); 188 break; 189 } 190 } 191 } 192 if (ts.isFunctionDeclaration(item) && item.decorators && item.decorators.length === 1 && 193 item.decorators[0].expression && item.decorators[0].expression.getText() === STYLES) { 194 if (ts.isBlock(item.body) && item.body.statements && item.body.statements.length) { 195 STYLES_ATTRIBUTE.add(item.name.getText()); 196 GLOBAL_STYLE_FUNCTION.set(item.name.getText(), item.body); 197 BUILDIN_STYLE_NAMES.add(item.name.getText()); 198 } 199 } 200 }); 201 validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview, 202 !!projectConfig.checkEntry, log); 203 } 204 205 return log.length ? log : null; 206} 207 208function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string, 209 fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void { 210 if (result.previewCount > 10 && fileQuery === '?entry') { 211 log.push({ 212 type: LogType.ERROR, 213 message: `A page can contain at most 10 '@Preview' decorators.`, 214 fileName: fileName 215 }); 216 } 217 if (result.entryCount > 1 && fileQuery === '?entry') { 218 log.push({ 219 type: LogType.ERROR, 220 message: `A page can't contain more than one '@Entry' decorator`, 221 fileName: fileName 222 }); 223 } 224 if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 && 225 fileQuery === '?entry') { 226 log.push({ 227 type: LogType.ERROR, 228 message: `A page which is being previewed must have one and only one '@Entry' ` 229 + `decorator, or at least one '@Preview' decorator.`, 230 fileName: fileName 231 }); 232 } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry') { 233 log.push({ 234 type: LogType.ERROR, 235 message: `A page configured in '${projectConfig.pagesJsonFileName}' must have one and only one '@Entry' ` 236 + `decorator.`, 237 fileName: fileName 238 }); 239 } 240} 241 242export function isObservedClass(node: ts.Node): boolean { 243 if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) { 244 return true; 245 } 246 return false; 247} 248 249export function isCustomDialogClass(node: ts.Node): boolean { 250 if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) { 251 return true; 252 } 253 return false; 254} 255 256interface DecoratorResult { 257 entryCount: number; 258 previewCount: number; 259} 260 261function checkDecorators(decorators: ts.NodeArray<ts.Decorator>, result: DecoratorResult, 262 component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile, node: ts.StructDeclaration): void { 263 let hasComponentDecorator: boolean = false; 264 const componentName: string = component.getText(); 265 decorators.forEach((element) => { 266 const name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim(); 267 if (INNER_COMPONENT_DECORATORS.has(name)) { 268 componentCollection.customComponents.add(componentName); 269 switch (name) { 270 case COMPONENT_DECORATOR_ENTRY: 271 checkEntryComponent(node, log, sourceFile); 272 result.entryCount++; 273 componentCollection.entryComponent = componentName; 274 collectLocalStorageName(element); 275 break; 276 case COMPONENT_DECORATOR_PREVIEW: 277 result.previewCount++; 278 componentCollection.previewComponent.add(componentName); 279 break; 280 case COMPONENT_DECORATOR_COMPONENT: 281 hasComponentDecorator = true; 282 break; 283 case COMPONENT_DECORATOR_CUSTOM_DIALOG: 284 componentCollection.customDialogs.add(componentName); 285 hasComponentDecorator = true; 286 break; 287 } 288 } else { 289 const pos: number = element.expression ? element.expression.pos : element.pos; 290 const message: string = `The struct '${componentName}' use invalid decorator.`; 291 addLog(LogType.WARN, message, pos, log, sourceFile); 292 } 293 }); 294 if (!hasComponentDecorator) { 295 const message: string = `The struct '${componentName}' should use decorator '@Component'.`; 296 addLog(LogType.WARN, message, component.pos, log, sourceFile); 297 } 298 if (BUILDIN_STYLE_NAMES.has(componentName)) { 299 const message: string = `The struct '${componentName}' cannot have the same name ` + 300 `as the built-in attribute '${componentName}'.`; 301 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 302 } 303 if (INNER_COMPONENT_NAMES.has(componentName)) { 304 const message: string = `The struct '${componentName}' cannot have the same name ` + 305 `as the built-in component '${componentName}'.`; 306 addLog(LogType.ERROR, message, component.pos, log, sourceFile); 307 } 308} 309 310function collectLocalStorageName(node: ts.Decorator): void { 311 if (node && node.expression && ts.isCallExpression(node.expression)) { 312 componentCollection.entryComponentPos = node.expression.pos; 313 if (node.expression.arguments && node.expression.arguments.length) { 314 node.expression.arguments.forEach((item: ts.Node, index: number) => { 315 if (ts.isIdentifier(item) && index === 0) { 316 componentCollection.localStorageName = item.getText(); 317 } 318 }); 319 } 320 } else { 321 componentCollection.localStorageName = null; 322 } 323} 324 325function checkUISyntax(filePath: string, allComponentNames: Set<string>, content: string, 326 log: LogInfo[]): void { 327 const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, content, 328 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 329 visitAllNode(sourceFile, sourceFile, allComponentNames, log); 330} 331 332function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set<string>, 333 log: LogInfo[]) { 334 checkAllNode(node, allComponentNames, sourceFileNode, log); 335 if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 336 collectComponentProps(node); 337 } 338 if (ts.isMethodDeclaration(node) && hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 339 CUSTOM_BUILDER_METHOD.add(node.name.getText()); 340 } 341 if (ts.isFunctionDeclaration(node) && isExtendFunction(node)) { 342 const componentName: string = isExtendFunction(node); 343 collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.getText()); 344 } 345 node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log)); 346} 347 348function checkAllNode(node: ts.Node, allComponentNames: Set<string>, sourceFileNode: ts.SourceFile, 349 log: LogInfo[]): void { 350 if (ts.isExpressionStatement(node) && node.expression && ts.isIdentifier(node.expression) && 351 allComponentNames.has(node.expression.escapedText.toString())) { 352 const pos: number = node.expression.getStart(); 353 const message: string = 354 `The component name must be followed by parentheses, like '${node.expression.getText()}()'.`; 355 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 356 } 357 checkNoChildComponent(node, sourceFileNode, log); 358 checkOneChildComponent(node, allComponentNames, sourceFileNode, log); 359 checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log); 360} 361 362function checkNoChildComponent(node: ts.Node, sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 363 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 364 ts.isIdentifier(node.expression.expression) && hasChild(node)) { 365 const componentName: string = node.expression.expression.escapedText.toString(); 366 const pos: number = node.expression.expression.getStart(); 367 const message: string = `The component '${componentName}' can't have any child.`; 368 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 369 } 370} 371 372function hasChild(node: ts.ExpressionStatement): boolean { 373 const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression; 374 const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier; 375 if (AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) && getNextNode(etsComponentExpression)) { 376 return true; 377 } 378 return false; 379} 380 381function getNextNode(node: ts.EtsComponentExpression): ts.Block { 382 if (node.body && ts.isBlock(node.body)) { 383 const statementsArray: ts.Block = node.body; 384 return statementsArray; 385 } 386} 387 388function checkOneChildComponent(node: ts.Node, allComponentNames: Set<string>, 389 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 390 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 391 ts.isIdentifier(node.expression.expression) && hasNonSingleChild(node, allComponentNames)) { 392 const componentName: string = node.expression.expression.escapedText.toString(); 393 const pos: number = node.expression.expression.getStart(); 394 const message: string = 395 `The component '${componentName}' can only have a single child component.`; 396 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 397 } 398} 399 400function hasNonSingleChild(node: ts.ExpressionStatement, allComponentNames: Set<string>): boolean { 401 const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression; 402 const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier; 403 const BlockNode: ts.Block = getNextNode(etsComponentExpression); 404 if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString())) { 405 if (!BlockNode) { 406 return false; 407 } 408 if (BlockNode && BlockNode.statements) { 409 const length: number = BlockNode.statements.length; 410 if (!length) { 411 return false; 412 } 413 if (length > 3) { 414 return true; 415 } 416 const childCount: number = getBlockChildrenCount(BlockNode, allComponentNames); 417 if (childCount > 1) { 418 return true; 419 } 420 } 421 } 422 return false; 423} 424 425function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set<string>): number { 426 let maxCount: number = 0; 427 const length: number = blockNode.statements.length; 428 for (let i = 0; i < length; ++i) { 429 const item: ts.Node = blockNode.statements[i]; 430 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && 431 isForEachComponent(item.expression)) { 432 maxCount += 2; 433 } 434 if (ts.isIfStatement(item)) { 435 maxCount += getIfChildrenCount(item, allComponentNames); 436 } 437 if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) { 438 maxCount += 1; 439 } 440 if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) { 441 let newNode: any = item.expression; 442 while (newNode.expression) { 443 if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) && 444 isComponent(newNode, allComponentNames)) { 445 maxCount += 1; 446 } 447 newNode = newNode.expression; 448 } 449 } 450 if (maxCount > 1) { 451 break; 452 } 453 } 454 return maxCount; 455} 456 457function isComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>): boolean { 458 if (ts.isIdentifier(node.expression) && 459 allComponentNames.has(node.expression.escapedText.toString())) { 460 return true; 461 } 462 return false; 463} 464 465function isForEachComponent(node: ts.CallExpression): boolean { 466 if (ts.isIdentifier(node.expression)) { 467 const componentName: string = node.expression.escapedText.toString(); 468 return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH; 469 } 470 return false; 471} 472 473function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set<string>): number { 474 const maxCount: number = 475 Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), 476 getStatementCount(ifNode.elseStatement, allComponentNames)); 477 return maxCount; 478} 479 480function getStatementCount(node: ts.Node, allComponentNames: Set<string>): number { 481 let maxCount: number = 0; 482 if (!node) { 483 return maxCount; 484 } else if (ts.isBlock(node)) { 485 maxCount = getBlockChildrenCount(node, allComponentNames); 486 } else if (ts.isIfStatement(node)) { 487 maxCount = getIfChildrenCount(node, allComponentNames); 488 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 489 isForEachComponent(node.expression)) { 490 maxCount = 2; 491 } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 492 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { 493 maxCount = 1; 494 } 495 return maxCount; 496} 497 498function checkSpecificChildComponent(node: ts.Node, allComponentNames: Set<string>, 499 sourceFileNode: ts.SourceFile, log: LogInfo[]): void { 500 if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && 501 ts.isIdentifier(node.expression.expression) && hasNonspecificChild(node, allComponentNames)) { 502 const componentName: string = node.expression.expression.escapedText.toString(); 503 const pos: number = node.expression.expression.getStart(); 504 const specificChildArray: string = 505 Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and '); 506 const message: string = 507 `The component '${componentName}' can only have the child component ${specificChildArray}.`; 508 addLog(LogType.ERROR, message, pos, log, sourceFileNode); 509 } 510} 511 512function hasNonspecificChild(node: ts.ExpressionStatement, 513 allComponentNames: Set<string>): boolean { 514 const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression; 515 const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier; 516 const nodeNameString: string = nodeName.escapedText.toString(); 517 const blockNode: ts.Block = getNextNode(etsComponentExpression); 518 let isNonspecific: boolean = false; 519 if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { 520 const specificChildSet: Set<string> = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); 521 isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); 522 if (isNonspecific) { 523 return isNonspecific; 524 } 525 } 526 return isNonspecific; 527} 528 529function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set<string>, 530 allComponentNames: Set<string>): boolean { 531 if (blockNode.statements) { 532 const length: number = blockNode.statements.length; 533 for (let i = 0; i < length; ++i) { 534 const item: ts.Node = blockNode.statements[i]; 535 if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) { 536 return true; 537 } 538 if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression) && 539 isForEachComponent(item.expression) && 540 isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) { 541 return true; 542 } 543 if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) { 544 return true; 545 } 546 if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) { 547 let newNode: any = item.expression; 548 while (newNode.expression) { 549 if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) && 550 !isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) { 551 const isNonspecific: boolean = 552 isNonspecificChildNonForEach(item.expression, specificChildSet); 553 if (isNonspecific) { 554 return isNonspecific; 555 } 556 if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { 557 ++i; 558 } 559 } 560 newNode = newNode.expression; 561 } 562 } 563 } 564 } 565 return false; 566} 567 568function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set<string>, 569 allComponentNames: Set<string>): boolean { 570 return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || 571 isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); 572} 573 574function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set<string>, 575 allComponentNames: Set<string>): boolean { 576 if (ts.isEtsComponentExpression(node) && node.arguments && 577 node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { 578 const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; 579 const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement = 580 arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement; 581 if (!body) { 582 return false; 583 } 584 if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) { 585 return true; 586 } 587 if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) { 588 return true; 589 } 590 if (ts.isCallExpression(body) && isForEachComponent(body) && 591 isNonspecificChildForEach(body, specificChildSet, allComponentNames)) { 592 return true; 593 } 594 if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) && 595 isComponent(body, allComponentNames) && 596 isNonspecificChildNonForEach(body, specificChildSet)) { 597 return true; 598 } 599 } 600 return false; 601} 602 603function isNonspecificChildNonForEach(node: ts.EtsComponentExpression, 604 specificChildSet: Set<string>): boolean { 605 if (ts.isIdentifier(node.expression) && 606 !specificChildSet.has(node.expression.escapedText.toString())) { 607 return true; 608 } 609 return false; 610} 611 612function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set<string>, 613 allComponentNames: Set<string>): boolean { 614 if (!node) { 615 return false; 616 } 617 if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) { 618 return true; 619 } 620 if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) { 621 return true; 622 } 623 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 624 isForEachComponent(node.expression) && 625 isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { 626 return true; 627 } 628 if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) && 629 !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && 630 isNonspecificChildNonForEach(node.expression, specificChildSet)) { 631 return true; 632 } 633 return false; 634} 635 636function collectComponentProps(node: ts.StructDeclaration): void { 637 const componentName: string = node.name.getText(); 638 const ComponentSet: IComponentSet = getComponentSet(node); 639 propertyCollection.set(componentName, ComponentSet.properties); 640 stateCollection.set(componentName, ComponentSet.states); 641 linkCollection.set(componentName, ComponentSet.links); 642 propCollection.set(componentName, ComponentSet.props); 643 regularCollection.set(componentName, ComponentSet.regulars); 644 storagePropCollection.set(componentName, ComponentSet.storageProps); 645 storageLinkCollection.set(componentName, ComponentSet.storageLinks); 646 provideCollection.set(componentName, ComponentSet.provides); 647 consumeCollection.set(componentName, ComponentSet.consumes); 648 objectLinkCollection.set(componentName, ComponentSet.objectLinks); 649 localStorageLinkCollection.set(componentName, ComponentSet.localStorageLink); 650 localStoragePropCollection.set(componentName, ComponentSet.localStorageProp); 651 builderParamObjectCollection.set(componentName, ComponentSet.builderParams); 652} 653 654export function getComponentSet(node: ts.StructDeclaration): IComponentSet { 655 const properties: Set<string> = new Set(); 656 const states: Set<string> = new Set(); 657 const links: Set<string> = new Set(); 658 const props: Set<string> = new Set(); 659 const regulars: Set<string> = new Set(); 660 const storageProps: Set<string> = new Set(); 661 const storageLinks: Set<string> = new Set(); 662 const provides: Set<string> = new Set(); 663 const consumes: Set<string> = new Set(); 664 const objectLinks: Set<string> = new Set(); 665 const localStorageLink: Map<string, Set<string>> = new Map(); 666 const localStorageProp: Map<string, Set<string>> = new Map(); 667 const builderParams: Set<string> = new Set(); 668 traversalComponentProps(node, properties, regulars, states, links, props, storageProps, 669 storageLinks, provides, consumes, objectLinks, localStorageLink, localStorageProp, builderParams); 670 return { 671 properties, regulars, states, links, props, storageProps, storageLinks, provides, 672 consumes, objectLinks, localStorageLink, localStorageProp, builderParams 673 }; 674} 675 676function traversalComponentProps(node: ts.StructDeclaration, properties: Set<string>, 677 regulars: Set<string>, states: Set<string>, links: Set<string>, props: Set<string>, 678 storageProps: Set<string>, storageLinks: Set<string>, provides: Set<string>, 679 consumes: Set<string>, objectLinks: Set<string>, 680 localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>, 681 builderParams: Set<string>): void { 682 let isStatic: boolean = true; 683 if (node.members) { 684 const currentMethodCollection: Set<string> = new Set(); 685 node.members.forEach(item => { 686 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 687 const propertyName: string = item.name.getText(); 688 properties.add(propertyName); 689 if (!item.decorators || !item.decorators.length) { 690 regulars.add(propertyName); 691 } else { 692 isStatic = false; 693 for (let i = 0; i < item.decorators.length; i++) { 694 const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim(); 695 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 696 dollarCollection.add('$' + propertyName); 697 collectionStates(item.decorators[i], decoratorName, propertyName, states, links, props, storageProps, 698 storageLinks, provides, consumes, objectLinks, localStorageLink, localStorageProp, builderParams); 699 } 700 } 701 } 702 } 703 if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) { 704 validateStateVariable(item); 705 currentMethodCollection.add(item.name.getText()); 706 } 707 }); 708 classMethodCollection.set(node.name.getText(), currentMethodCollection); 709 } 710 isStaticViewCollection.set(node.name.getText(), isStatic); 711} 712 713function collectionStates(node: ts.Decorator, decorator: string, name: string, 714 states: Set<string>, links: Set<string>, props: Set<string>, storageProps: Set<string>, 715 storageLinks: Set<string>, provides: Set<string>, consumes: Set<string>, objectLinks: Set<string>, 716 localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>, 717 builderParams: Set<string>): void { 718 switch (decorator) { 719 case COMPONENT_STATE_DECORATOR: 720 states.add(name); 721 break; 722 case COMPONENT_LINK_DECORATOR: 723 links.add(name); 724 break; 725 case COMPONENT_PROP_DECORATOR: 726 props.add(name); 727 break; 728 case COMPONENT_STORAGE_PROP_DECORATOR: 729 storageProps.add(name); 730 break; 731 case COMPONENT_STORAGE_LINK_DECORATOR: 732 storageLinks.add(name); 733 break; 734 case COMPONENT_PROVIDE_DECORATOR: 735 provides.add(name); 736 break; 737 case COMPONENT_CONSUME_DECORATOR: 738 consumes.add(name); 739 break; 740 case COMPONENT_OBJECT_LINK_DECORATOR: 741 objectLinks.add(name); 742 break; 743 case COMPONENT_BUILDERPARAM_DECORATOR: 744 builderParams.add(name); 745 break; 746 case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR : 747 collectionlocalStorageParam(node, name, localStorageLink); 748 break; 749 case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR: 750 collectionlocalStorageParam(node, name, localStorageProp); 751 break; 752 } 753} 754 755function collectionlocalStorageParam(node: ts.Decorator, name: string, 756 localStorage: Map<string, Set<string>>): void { 757 const localStorageParam: Set<string> = new Set(); 758 if (node && ts.isCallExpression(node.expression) && node.expression.arguments && 759 node.expression.arguments.length && ts.isStringLiteral(node.expression.arguments[0])) { 760 localStorage.set(name, localStorageParam.add( 761 node.expression.arguments[0].getText().replace(/\"|'/g, ''))); 762 } 763} 764 765export interface ReplaceResult { 766 content: string, 767 log: LogInfo[] 768} 769 770export function sourceReplace(source: string, sourcePath: string): ReplaceResult { 771 let content: string = source; 772 const log: LogInfo[] = []; 773 content = preprocessExtend(content); 774 content = preprocessNewExtend(content); 775 // process @system. 776 content = processSystemApi(content, false, sourcePath); 777 778 return { 779 content: content, 780 log: log 781 }; 782} 783 784export function preprocessExtend(content: string, extendCollection?: Set<string>): string { 785 const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm; 786 return content.replace(REG_EXTEND, (item, item1, item2, item3) => { 787 collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3); 788 collectExtend(EXTEND_ATTRIBUTE, item2, item3); 789 if (extendCollection) { 790 extendCollection.add(item3); 791 } 792 return `@Extend(${item2})${item1}function __${item2}__${item3}(`; 793 }); 794} 795 796export function preprocessNewExtend(content: string, extendCollection?: Set<string>): string { 797 const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm; 798 return content.replace(REG_EXTEND, (item, item1) => { 799 if (extendCollection) { 800 extendCollection.add(item1); 801 } 802 return item; 803 }); 804} 805 806export function processSystemApi(content: string, isProcessAllowList: boolean = false, 807 sourcePath: string = null, isSystemModule: boolean = false): string { 808 let REG_SYSTEM: RegExp; 809 if (isProcessAllowList) { 810 REG_SYSTEM = 811 /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g; 812 } else { 813 REG_SYSTEM = 814 /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)/g; 815 } 816 const REG_LIB_SO: RegExp = 817 /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; 818 const systemValueCollection: Set<string> = new Set(); 819 const newContent: string = content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { 820 const libSoValue: string = item1 || item3; 821 const libSoKey: string = item2 || item4; 822 if (sourcePath) { 823 useOSFiles.add(sourcePath); 824 } 825 return `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`; 826 }).replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6, item7) => { 827 let moduleType: string = item2 || item5; 828 let systemKey: string = item3 || item6; 829 let systemValue: string = item1 || item4; 830 if (!VALIDATE_MODULE.includes(systemValue)) { 831 importModuleCollection.add(systemValue); 832 } 833 if (!isProcessAllowList && !isSystemModule) { 834 return item; 835 } else if (isProcessAllowList) { 836 systemValue = item2; 837 moduleType = item4; 838 systemKey = item5; 839 systemValueCollection.add(systemValue); 840 } 841 moduleCollection.add(`${moduleType}.${systemKey}`); 842 if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { 843 item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; 844 } else if (moduleType === SYSTEM_PLUGIN) { 845 item = `var ${systemValue} = isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + 846 `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; 847 } else if (moduleType === OHOS_PLUGIN) { 848 item = `var ${systemValue} = globalThis.requireNapi('${systemKey}') || ` + 849 `(isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + 850 `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ` + 851 `? globalThis.systemplugin.${systemKey} : undefined)`; 852 } 853 return item; 854 }); 855 return processInnerModule(newContent, systemValueCollection); 856} 857 858function processInnerModule(content: string, systemValueCollection: Set<string>): string { 859 systemValueCollection.forEach(element => { 860 const target: string = element.trim() + '.default'; 861 while (content.includes(target)) { 862 content = content.replace(target, element.trim()); 863 } 864 }); 865 return content; 866} 867 868function validateStateVariable(node: ts.MethodDeclaration): void { 869 if (node.decorators && node.decorators.length) { 870 for (let i = 0; i < node.decorators.length; i++) { 871 const decoratorName: string = node.decorators[i].getText().replace(/\(.*\)$/,'').trim(); 872 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 873 transformLog.errors.push({ 874 type: LogType.ERROR, 875 message: `'${node.decorators[i].getText()}' can not decorate the method.`, 876 pos: node.decorators[i].getStart() 877 }); 878 } 879 } 880 } 881} 882 883const VALIDATE_MODULE_REG: RegExp = new RegExp('^(' + VALIDATE_MODULE.join('|') + ')'); 884function validateAllowListModule(moduleType: string, systemKey: string): boolean { 885 return moduleType === 'ohos' && VALIDATE_MODULE_REG.test(systemKey); 886} 887 888export function resetComponentCollection() { 889 componentCollection.entryComponent = null; 890 componentCollection.entryComponentPos = null; 891 componentCollection.previewComponent = new Set([]); 892} 893 894function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void { 895 if (node.modifiers) { 896 for (let i = 0; i < node.modifiers.length; i++) { 897 if (node.modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { 898 const message: string = `It's not a recommended way to export struct with @Entry decorator, ` + 899 `which may cause ACE Engine error in component preview mode.`; 900 addLog(LogType.WARN, message, node.getStart(), log, sourceFile); 901 break; 902 } 903 } 904 } 905}