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