• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}