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_CONCURRENT_DECORATOR,
43  CHECK_EXTEND_DECORATORS,
44  COMPONENT_STYLES_DECORATOR,
45  RESOURCE_NAME_TYPE,
46  COMPONENT_BUTTON,
47  COMPONENT_TOGGLE,
48  COMPONENT_BUILDERPARAM_DECORATOR,
49  ESMODULE,
50  CARD_ENABLE_DECORATORS,
51  CARD_LOG_TYPE_DECORATORS,
52  JSBUNDLE,
53  COMPONENT_DECORATOR_REUSEABLE,
54  STRUCT_DECORATORS,
55  STRUCT_CONTEXT_METHOD_DECORATORS,
56  CHECK_COMPONENT_EXTEND_DECORATOR,
57  CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR,
58  CLASS_TRACK_DECORATOR,
59  COMPONENT_REQUIRE_DECORATOR,
60  COMPONENT_SENDABLE_DECORATOR,
61  CLASS_MIN_TRACK_DECORATOR,
62  MIN_OBSERVED,
63  COMPONENT_NON_DECORATOR,
64  COMPONENT_DECORATOR_COMPONENT_V2,
65  OBSERVED,
66  SENDABLE,
67  TYPE,
68  COMPONENT_LOCAL_BUILDER_DECORATOR
69} from './pre_define';
70import {
71  INNER_COMPONENT_NAMES,
72  AUTOMIC_COMPONENT,
73  SINGLE_CHILD_COMPONENT,
74  SPECIFIC_CHILD_COMPONENT,
75  BUILDIN_STYLE_NAMES,
76  EXTEND_ATTRIBUTE,
77  GLOBAL_STYLE_FUNCTION,
78  STYLES_ATTRIBUTE,
79  CUSTOM_BUILDER_METHOD,
80  GLOBAL_CUSTOM_BUILDER_METHOD,
81  INNER_CUSTOM_BUILDER_METHOD,
82  INNER_STYLE_FUNCTION,
83  INNER_CUSTOM_LOCALBUILDER_METHOD
84} from './component_map';
85import {
86  LogType,
87  LogInfo,
88  componentInfo,
89  addLog,
90  hasDecorator,
91  storedFileInfo,
92  ExtendResult
93} from './utils';
94import { globalProgram, projectConfig, abilityPagesFullPath } from '../main';
95import {
96  collectExtend,
97  isExtendFunction,
98  transformLog,
99  validatorCard
100} from './process_ui_syntax';
101import {
102  isBuilderOrLocalBuilder,
103  builderConditionType
104} from './process_component_class';
105import { stateObjectCollection } from './process_component_member';
106import { collectSharedModule } from './fast_build/ark_compiler/check_shared_module';
107import constantDefine from './constant_define';
108import processStructComponentV2, { StructInfo } from './process_struct_componentV2';
109import { getMessageCollection } from './log_message_collection';
110
111export class ComponentCollection {
112  localStorageName: string = null;
113  localStorageNode: ts.Identifier | ts.ObjectLiteralExpression = null;
114  localSharedStorage: ts.Node = null;
115  entryComponentPos: number = null;
116  entryComponent: string = null;
117  previewComponent: Array<string> = [];
118  customDialogs: Set<string> = new Set([]);
119  customComponents: Set<string> = new Set([]);
120  currentClassName: string = null;
121}
122
123export class IComponentSet {
124  properties: Set<string> = new Set();
125  regulars: Set<string> = new Set();
126  states: Set<string> = new Set();
127  links: Set<string> = new Set();
128  props: Set<string> = new Set();
129  storageProps: Set<string> = new Set();
130  storageLinks: Set<string> = new Set();
131  provides: Set<string> = new Set();
132  consumes: Set<string> = new Set();
133  objectLinks: Set<string> = new Set();
134  localStorageLink: Map<string, Set<string>> = new Map();
135  localStorageProp: Map<string, Set<string>> = new Map();
136  builderParams: Set<string> = new Set();
137  builderParamData: Set<string> = new Set();
138  propData: Set<string> = new Set();
139  regularInit: Set<string> = new Set();
140  stateInit: Set<string> = new Set();
141  provideInit: Set<string> = new Set();
142  privateCollection: Set<string> = new Set();
143}
144
145export let componentCollection: ComponentCollection = new ComponentCollection();
146
147export const observedClassCollection: Set<string> = new Set();
148export const enumCollection: Set<string> = new Set();
149export const classMethodCollection: Map<string, Map<string, Set<string>>> = new Map();
150export const dollarCollection: Set<string> = new Set();
151
152export const propertyCollection: Map<string, Set<string>> = new Map();
153export const stateCollection: Map<string, Set<string>> = new Map();
154export const linkCollection: Map<string, Set<string>> = new Map();
155export const propCollection: Map<string, Set<string>> = new Map();
156export const regularCollection: Map<string, Set<string>> = new Map();
157export const storagePropCollection: Map<string, Set<string>> = new Map();
158export const storageLinkCollection: Map<string, Set<string>> = new Map();
159export const provideCollection: Map<string, Set<string>> = new Map();
160export const consumeCollection: Map<string, Set<string>> = new Map();
161export const objectLinkCollection: Map<string, Set<string>> = new Map();
162export const builderParamObjectCollection: Map<string, Set<string>> = new Map();
163export const localStorageLinkCollection: Map<string, Map<string, Set<string>>> = new Map();
164export const localStoragePropCollection: Map<string, Map<string, Set<string>>> = new Map();
165export const builderParamInitialization: Map<string, Set<string>> = new Map();
166export const propInitialization: Map<string, Set<string>> = new Map();
167export const regularInitialization: Map<string, Set<string>> = new Map();
168export const stateInitialization: Map<string, Set<string>> = new Map();
169export const provideInitialization: Map<string, Set<string>> = new Map();
170export const privateCollection: Map<string, Set<string>> = new Map();
171
172export const isStaticViewCollection: Map<string, boolean> = new Map();
173
174export const useOSFiles: Set<string> = new Set();
175export const sourcemapNamesCollection: Map<string, Map<string, string>> = new Map();
176export const originalImportNamesMap: Map<string, string> = new Map();
177
178export function validateUISyntax(source: string, content: string, filePath: string,
179  fileQuery: string, sourceFile: ts.SourceFile = null): LogInfo[] {
180  let log: LogInfo[] = [];
181  if (process.env.compileMode === 'moduleJson' ||
182    path.resolve(filePath) !== path.resolve(projectConfig.projectPath || '', 'app.ets')) {
183    componentCollection = new ComponentCollection();
184    const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery, sourceFile);
185    if (res) {
186      log = log.concat(res);
187    }
188    const allComponentNames: Set<string> =
189      new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]);
190    checkUISyntax(filePath, allComponentNames, content, log, sourceFile, fileQuery);
191    componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item));
192  }
193
194  if (projectConfig.compileMode === ESMODULE) {
195    collectSharedModule(source, filePath, sourceFile);
196  }
197
198  return log;
199}
200
201function checkComponentDecorator(source: string, filePath: string,
202  fileQuery: string, sourceFile: ts.SourceFile | null): LogInfo[] | null {
203  const log: LogInfo[] = [];
204  if (!sourceFile) {
205    sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
206  }
207  if (sourceFile && sourceFile.statements && sourceFile.statements.length) {
208    const result: DecoratorResult = {
209      entryCount: 0,
210      previewCount: 0
211    };
212    sourceFile.statements.forEach((item, index, arr) => {
213      if (isObservedClass(item)) {
214        // @ts-ignore
215        observedClassCollection.add(item.name.getText());
216      }
217      if (ts.isEnumDeclaration(item) && item.name) {
218        enumCollection.add(item.name.getText());
219      }
220      if (ts.isStructDeclaration(item)) {
221        validateStructSpec(item, result, log, sourceFile);
222      }
223      if (ts.isMissingDeclaration(item)) {
224        const decorators = ts.getAllDecorators(item);
225        for (let i = 0; i < decorators.length; i++) {
226          if (decorators[i] && /struct/.test(decorators[i].getText())) {
227            const message: string = `Please use a valid decorator.`;
228            addLog(LogType.ERROR, message, item.getStart(), log, sourceFile);
229            break;
230          }
231        }
232      }
233    });
234    if (process.env.compileTool === 'rollup') {
235      if (result.entryCount > 0) {
236        storedFileInfo.wholeFileInfo[filePath].hasEntry = true;
237      } else {
238        storedFileInfo.wholeFileInfo[filePath].hasEntry = false;
239      }
240    }
241    validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview,
242      !!projectConfig.checkEntry, log);
243  }
244
245  return log.length ? log : null;
246}
247
248function validateStructSpec(item: ts.StructDeclaration, result: DecoratorResult, log: LogInfo[],
249  sourceFile: ts.SourceFile | null): void {
250  if (item.name && ts.isIdentifier(item.name)) {
251    const componentName: string = item.name.getText();
252    componentCollection.customComponents.add(componentName);
253    const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item);
254    if (decorators && decorators.length) {
255      checkDecorators(decorators, result, item.name, log, sourceFile, item);
256    } else {
257      const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`;
258      addLog(LogType.ERROR, message, item.getStart(), log, sourceFile);
259    }
260  } else {
261    const message: string = `A struct must have a name.`;
262    addLog(LogType.ERROR, message, item.getStart(), log, sourceFile);
263  }
264}
265
266function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string,
267  fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void {
268  if (result.previewCount > 10 && (fileQuery === '?entry' || process.env.watchMode === 'true')) {
269    log.push({
270      type: LogType.ERROR,
271      message: `A page can contain at most 10 '@Preview' decorators.`,
272      fileName: fileName
273    });
274  }
275  if (result.entryCount > 1 && fileQuery === '?entry') {
276    log.push({
277      type: LogType.ERROR,
278      message: `A page can't contain more than one '@Entry' decorator`,
279      fileName: fileName
280    });
281  }
282  if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 &&
283    fileQuery === '?entry') {
284    log.push({
285      type: LogType.ERROR,
286      message: `A page which is being previewed must have one and only one '@Entry' ` +
287        `decorator, or at least one '@Preview' decorator.`,
288      fileName: fileName
289    });
290  } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry' &&
291    !abilityPagesFullPath.has(path.resolve(fileName).toLowerCase())) {
292    log.push({
293      type: LogType.ERROR,
294      message: `A page configured in '${projectConfig.pagesJsonFileName} or build-profile.json5' must have one and only one '@Entry' decorator.` +
295        `Solutions:>Please make sure that the splash page has one and only one '@Entry' decorator.`,
296      fileName: fileName
297    });
298  }
299}
300
301export function isObservedClass(node: ts.Node): boolean {
302  if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) {
303    return true;
304  }
305  return false;
306}
307
308export function isCustomDialogClass(node: ts.Node): boolean {
309  if (ts.isStructDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) {
310    return true;
311  }
312  return false;
313}
314
315interface DecoratorResult {
316  entryCount: number;
317  previewCount: number;
318}
319
320function checkDecorators(decorators: readonly ts.Decorator[], result: DecoratorResult,
321  component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile, node: ts.StructDeclaration): void {
322  const componentName: string = component.getText();
323  const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(componentName);
324  let hasInnerComponentDecorator: boolean = false;
325  decorators.forEach((element) => {
326    let name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim();
327    if (element.expression && element.expression.expression && ts.isIdentifier(element.expression.expression)) {
328      name = '@' + element.expression.expression.getText();
329    }
330    if (INNER_COMPONENT_DECORATORS.has(name)) {
331      hasInnerComponentDecorator = true;
332      switch (name) {
333        case COMPONENT_DECORATOR_ENTRY:
334          checkEntryComponent(node, log, sourceFile);
335          result.entryCount++;
336          componentCollection.entryComponent = componentName;
337          componentCollection.entryComponentPos = node.getStart();
338          collectLocalStorageName(element);
339          break;
340        case COMPONENT_DECORATOR_PREVIEW:
341          result.previewCount++;
342          componentCollection.previewComponent.push(componentName);
343          break;
344        case COMPONENT_DECORATOR_COMPONENT_V2:
345          structInfo.isComponentV2 = true;
346          break;
347        case COMPONENT_DECORATOR_COMPONENT:
348          structInfo.isComponentV1 = true;
349          break;
350        case COMPONENT_DECORATOR_CUSTOM_DIALOG:
351          componentCollection.customDialogs.add(componentName);
352          structInfo.isCustomDialog = true;
353          break;
354        case COMPONENT_DECORATOR_REUSEABLE:
355          storedFileInfo.getCurrentArkTsFile().recycleComponents.add(componentName);
356          structInfo.isReusable = true;
357          break;
358      }
359    } else {
360      validateInvalidStructDecorator(element, componentName, log, sourceFile);
361    }
362  });
363  validateStruct(hasInnerComponentDecorator, componentName, component, log, sourceFile, structInfo);
364}
365
366function validateInvalidStructDecorator(element: ts.Decorator, componentName: string, log: LogInfo[],
367  sourceFile: ts.SourceFile): void {
368  const pos: number = element.expression ? element.expression.pos : element.pos;
369  const message: string = `The struct '${componentName}' use invalid decorator.`;
370  addLog(LogType.WARN, message, pos, log, sourceFile);
371}
372
373function validateStruct(hasInnerComponentDecorator: boolean, componentName: string, component: ts.Identifier,
374  log: LogInfo[], sourceFile: ts.SourceFile, structInfo: StructInfo): void {
375  if (!hasInnerComponentDecorator) {
376    const message: string = `Decorator '@Component', '@ComponentV2', or '@CustomDialog' is missing for struct '${componentName}'.`;
377    addLog(LogType.ERROR, message, component.pos, log, sourceFile);
378  } else if (structInfo.isComponentV2 && (structInfo.isComponentV1 || structInfo.isReusable || structInfo.isCustomDialog) ) {
379    const message: string = `The struct '${componentName}' can not be decorated with '@ComponentV2' ` +
380      `and '@Component', '@Reusable', '@CustomDialog' at the same time.`;
381    addLog(LogType.ERROR, message, component.pos, log, sourceFile);
382  }
383  if (BUILDIN_STYLE_NAMES.has(componentName)) {
384    const message: string = `The struct '${componentName}' cannot have the same name ` +
385      `as the built-in attribute '${componentName}'.`;
386    addLog(LogType.ERROR, message, component.pos, log, sourceFile);
387  }
388  if (INNER_COMPONENT_NAMES.has(componentName)) {
389    const message: string = `The struct '${componentName}' cannot have the same name ` +
390      `as the built-in component '${componentName}'.`;
391    addLog(LogType.ERROR, message, component.pos, log, sourceFile);
392  }
393}
394
395function checkConcurrentDecorator(node: ts.FunctionDeclaration | ts.MethodDeclaration, log: LogInfo[],
396  sourceFile: ts.SourceFile): void {
397  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
398  if (projectConfig.compileMode === JSBUNDLE) {
399    const message: string = `@Concurrent can only be used in ESMODULE compile mode.`;
400    addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile);
401  }
402  if (ts.isMethodDeclaration(node)) {
403    const message: string = `@Concurrent can not be used on method. please use it on function declaration.`;
404    addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile);
405  }
406  if (node.asteriskToken) {
407    let hasAsync: boolean = false;
408    const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
409    const checkAsyncModifier = (modifier: ts.Modifier) : boolean => modifier.kind === ts.SyntaxKind.AsyncKeyword;
410    modifiers && (hasAsync = modifiers.some(checkAsyncModifier));
411    const funcKind: string = hasAsync ? 'Async generator' : 'Generator';
412    const message: string = `@Concurrent can not be used on ${funcKind} function declaration.`;
413    addLog(LogType.ERROR, message, decorators![0].pos, log, sourceFile);
414  }
415}
416
417function collectLocalStorageName(node: ts.Decorator): void {
418  if (node && node.expression && ts.isCallExpression(node.expression)) {
419    if (node.expression.arguments && node.expression.arguments.length) {
420      node.expression.arguments.forEach((item: ts.Node, index: number) => {
421        if (ts.isIdentifier(item) && index === 0) {
422          componentCollection.localStorageName = item.getText();
423          componentCollection.localStorageNode = item;
424        } else if (ts.isObjectLiteralExpression(item) && index === 0) {
425          componentCollection.localStorageName = null;
426          componentCollection.localStorageNode = item;
427        } else {
428          componentCollection.localSharedStorage = item;
429        }
430      });
431    }
432  } else {
433    componentCollection.localStorageName = null;
434    componentCollection.localStorageNode = null;
435  }
436}
437
438function checkUISyntax(filePath: string, allComponentNames: Set<string>, content: string,
439  log: LogInfo[], sourceFile: ts.SourceFile | null, fileQuery: string): void {
440  if (!sourceFile) {
441    sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
442  }
443  visitAllNode(sourceFile, sourceFile, allComponentNames, log, false, false, false, false, fileQuery, false, false);
444}
445
446function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set<string>,
447  log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean,
448  isComponentV2: boolean, fileQuery: string, isObservedV1Class: boolean, isSendableClass: boolean): void {
449  if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
450    structContext = true;
451    const structName: string = node.name.escapedText.toString();
452    const structInfo: StructInfo = processStructComponentV2.getOrCreateStructInfo(structName);
453    if (structInfo.isComponentV2) {
454      processStructComponentV2.parseComponentProperty(node, structInfo, log, sourceFileNode);
455      isComponentV2 = true;
456    } else {
457      collectComponentProps(node, structInfo);
458    }
459  }
460  if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
461    classContext = true;
462    [isObservedV1Class, isObservedClass, isSendableClass] = parseClassDecorator(node, sourceFileNode, log);
463  }
464  if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) {
465    methodDecoratorCollect(node);
466    if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) {
467      // ark compiler's feature
468      checkConcurrentDecorator(node, log, sourceFileNode);
469    }
470    validateFunction(node, sourceFileNode, log);
471  }
472  checkDecoratorCount(node, sourceFileNode, log);
473  checkDecorator(sourceFileNode, node, log, structContext, classContext, isObservedClass, isComponentV2,
474    isObservedV1Class, isSendableClass);
475  node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log,
476    structContext, classContext, isObservedClass, isComponentV2, fileQuery, isObservedV1Class, isSendableClass));
477  structContext = false;
478  classContext = false;
479  isObservedClass = false;
480  isObservedV1Class = false;
481  isSendableClass = false;
482}
483
484const v1ComponentDecorators: string[] = [
485  'State', 'Prop', 'Link', 'Provide', 'Consume',
486  'StorageLink', 'StorageProp', 'LocalStorageLink', 'LocalStorageProp'
487];
488const v2ComponentDecorators: string[] = [
489  'Local', 'Param', 'Event', 'Provider', 'Consumer'
490];
491function validatePropertyInStruct(structContext: boolean, decoratorNode: ts.Identifier,
492  decoratorName: string, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
493  if (structContext) {
494    const isV1Decorator: boolean = v1ComponentDecorators.includes(decoratorName);
495    const isV2Decorator: boolean = v2ComponentDecorators.includes(decoratorName);
496    if (!isV1Decorator && !isV2Decorator) {
497      return;
498    }
499    const classResult: ClassDecoratorResult = new ClassDecoratorResult();
500    const propertyNode: ts.PropertyDeclaration = getPropertyNodeByDecorator(decoratorNode);
501    if (propertyNode && propertyNode.type && globalProgram.checker) {
502      validatePropertyType(propertyNode.type, classResult);
503    }
504    let message: string;
505    if (isV1Decorator && classResult.hasObservedV2) {
506      message = `The type of the @${decoratorName} property can not be a class decorated with @ObservedV2.`;
507      addLog(LogType.ERROR, message, decoratorNode.getStart(), log, sourceFileNode);
508      return;
509    }
510    if (isV2Decorator && classResult.hasObserved) {
511      message = `The type of the @${decoratorName} property can not be a class decorated with @Observed.`;
512      addLog(LogType.ERROR, message, decoratorNode.getStart(), log, sourceFileNode);
513      return;
514    }
515  }
516}
517
518function getPropertyNodeByDecorator(decoratorNode: ts.Identifier): ts.PropertyDeclaration {
519  if (ts.isDecorator(decoratorNode.parent) && ts.isPropertyDeclaration(decoratorNode.parent.parent)) {
520    return decoratorNode.parent.parent;
521  }
522  if (ts.isCallExpression(decoratorNode.parent) && ts.isDecorator(decoratorNode.parent.parent) &&
523    ts.isPropertyDeclaration(decoratorNode.parent.parent.parent)) {
524    return decoratorNode.parent.parent.parent;
525  }
526  return undefined;
527}
528
529function validatePropertyType(node: ts.TypeNode, classResult: ClassDecoratorResult): void {
530  if (ts.isUnionTypeNode(node) && node.types && node.types.length) {
531    node.types.forEach((item: ts.TypeNode) => {
532      validatePropertyType(item, classResult);
533    });
534  }
535  if (ts.isTypeReferenceNode(node) && node.typeName) {
536    const typeNode: ts.Type = globalProgram.checker.getTypeAtLocation(node.typeName);
537    parsePropertyType(typeNode, classResult);
538  }
539}
540
541function parsePropertyType(type: ts.Type, classResult: ClassDecoratorResult): void {
542  // @ts-ignore
543  if (type && type.types && type.types.length) {
544    // @ts-ignore
545    type.types.forEach((item: ts.Type) => {
546      parsePropertyType(item, classResult);
547    });
548  }
549  if (type && type.symbol && type.symbol.valueDeclaration &&
550    ts.isClassDeclaration(type.symbol.valueDeclaration)) {
551    const result: ClassDecoratorResult = getClassDecoratorResult(type.symbol.valueDeclaration);
552    if (result.hasObserved) {
553      classResult.hasObserved = result.hasObserved;
554    }
555    if (result.hasObservedV2) {
556      classResult.hasObservedV2 = result.hasObservedV2;
557    }
558  }
559}
560
561function checkDecoratorCount(node: ts.Node, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
562  if (ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isMethodDeclaration(node)) {
563    const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
564    let innerDecoratorCount: number = 0;
565    const exludeDecorators: string[] = ['@Require', '@Once'];
566    const v1MethodDecorators: string[] = ['@Builder', '@Styles'];
567    const v1DecoratorMap: Map<string, number> = new Map<string, number>();
568    const v2DecoratorMap: Map<string, number> = new Map<string, number>();
569    let checkDecoratorCount: number = 0;
570    decorators.forEach((item: ts.Decorator) => {
571      const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, '');
572      if (!exludeDecorators.includes(decoratorName) && (constantDefine.DECORATOR_V2.includes(decoratorName) ||
573        decoratorName === '@BuilderParam')) {
574        const count: number = v2DecoratorMap.get(decoratorName) || 0;
575        v2DecoratorMap.set(decoratorName, count + 1);
576        return;
577      }
578      if (v1MethodDecorators.includes(decoratorName)) {
579        const count: number = v1DecoratorMap.get(decoratorName) || 0;
580        v1DecoratorMap.set(decoratorName, count + 1);
581        return;
582      }
583      if (decoratorName === COMPONENT_LOCAL_BUILDER_DECORATOR && decorators.length > 1) {
584        checkDecoratorCount = checkDecoratorCount + 1;
585        return;
586      }
587    });
588    const v2DecoratorMapKeys: string[] = Array.from(v2DecoratorMap.keys());
589    const v2DecoratorMapValues: number[] = Array.from(v2DecoratorMap.values());
590    const v1DecoratorMapKeys: string[] = Array.from(v1DecoratorMap.keys());
591    const v1DecoratorMapValues: number[] = Array.from(v1DecoratorMap.values());
592    innerDecoratorCount = v2DecoratorMapKeys.length + v1DecoratorMapKeys.length;
593    getMessageCollection().checkLocalBuilderDecoratorCount(node, sourceFileNode, checkDecoratorCount, log);
594    if (innerDecoratorCount > 1) {
595      const message: string = 'The member property or method can not be decorated by multiple built-in decorators.';
596      addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
597    }
598    const v2Duplicate: boolean = v2DecoratorMapValues.length &&
599      v2DecoratorMapValues.some((count: number) => count > 1);
600    const v1Duplicate: boolean = v1DecoratorMapValues.length &&
601      v1DecoratorMapValues.some((count: number) => count > 1);
602    const duplicateMessage: string = 'Duplicate decorators for method are not allowed.';
603    if (v2Duplicate || v1Duplicate) {
604      addLog(v1Duplicate ? LogType.WARN : LogType.ERROR, duplicateMessage, node.getStart(), log, sourceFileNode);
605    }
606  }
607}
608
609function methodDecoratorCollect(node: ts.MethodDeclaration | ts.FunctionDeclaration): void {
610  const extendResult: ExtendResult = { decoratorName: '', componentName: '' };
611  const builderCondition: builderConditionType = {
612    isBuilder: false,
613    isLocalBuilder: false
614  };
615  if (isBuilderOrLocalBuilder(node, builderCondition)) {
616    if (builderCondition.isBuilder) {
617      CUSTOM_BUILDER_METHOD.add(node.name.getText());
618      if (ts.isFunctionDeclaration(node)) {
619        GLOBAL_CUSTOM_BUILDER_METHOD.add(node.name.getText());
620      } else {
621        INNER_CUSTOM_BUILDER_METHOD.add(node.name.getText());
622      }
623    } else if (builderCondition.isLocalBuilder) {
624      INNER_CUSTOM_LOCALBUILDER_METHOD.add(node.name.getText());
625    }
626  } else if (ts.isFunctionDeclaration(node) && isExtendFunction(node, extendResult)) {
627    if (extendResult.decoratorName === CHECK_COMPONENT_EXTEND_DECORATOR) {
628      collectExtend(EXTEND_ATTRIBUTE, extendResult.componentName, node.name.getText());
629    }
630    if (extendResult.decoratorName === CHECK_COMPONENT_ANIMATABLE_EXTEND_DECORATOR) {
631      collectExtend(storedFileInfo.getCurrentArkTsFile().animatableExtendAttribute,
632        extendResult.componentName, node.name.getText());
633    }
634  } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) {
635    collectStyles(node);
636  }
637}
638
639function collectStyles(node: ts.FunctionLikeDeclarationBase): void {
640  if (ts.isBlock(node.body) && node.body.statements) {
641    if (ts.isFunctionDeclaration(node)) {
642      GLOBAL_STYLE_FUNCTION.set(node.name.getText(), node.body);
643    } else {
644      INNER_STYLE_FUNCTION.set(node.name.getText(), node.body);
645    }
646    STYLES_ATTRIBUTE.add(node.name.getText());
647    BUILDIN_STYLE_NAMES.add(node.name.getText());
648  }
649}
650
651function validateFunction(node: ts.MethodDeclaration | ts.FunctionDeclaration,
652  sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
653  if (ts.isFunctionDeclaration(node)) {
654    const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
655    const decoratorMap: Map<string, number> = new Map<string, number>();
656    decorators.forEach((item: ts.Decorator) => {
657      const decoratorName: string = item.getText().replace(/\([^\(\)]*\)/, '')
658        .replace(/^@/, '').trim();
659      const count: number = decoratorMap.get(decoratorName) || 0;
660      decoratorMap.set(decoratorName, count + 1);
661    });
662    const decoratorValues: number[] = Array.from(decoratorMap.values());
663    const hasDuplicate: boolean = decoratorValues.length &&
664      decoratorValues.some((count: number) => count > 1);
665    if (hasDuplicate) {
666      const message: string = 'Duplicate decorators for function are not allowed.';
667      addLog(LogType.WARN, message, node.getStart(), log, sourceFileNode);
668    }
669    const decoratorKeys: string[] = Array.from(decoratorMap.keys());
670    if (decoratorKeys.length > 1 || decoratorKeys.includes('LocalBuilder')) {
671      const message: string = 'A function can only be decorated by one of the ' +
672        `'AnimatableExtend, Builder, Extend, Styles, Concurrent and Sendable'.`;
673      addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
674    }
675  }
676}
677
678function checkDecorator(sourceFileNode: ts.SourceFile, node: ts.Node,
679  log: LogInfo[], structContext: boolean, classContext: boolean, isObservedClass: boolean,
680  isComponentV2: boolean, isObservedV1Class: boolean, isSendableClass: boolean): void {
681  if (ts.isIdentifier(node) && (ts.isDecorator(node.parent) ||
682    (ts.isCallExpression(node.parent) && ts.isDecorator(node.parent.parent)))) {
683    const decoratorName: string = node.escapedText.toString();
684    setLocalBuilderInFile(decoratorName);
685    validateStructDecorator(sourceFileNode, node, log, structContext, decoratorName, isComponentV2);
686    validateMethodDecorator(sourceFileNode, node, log, structContext, decoratorName);
687    validateClassDecorator(sourceFileNode, node, log, classContext, decoratorName, isObservedClass,
688      isObservedV1Class, isSendableClass);
689    validatePropertyInStruct(structContext, node, decoratorName, sourceFileNode, log);
690    return;
691  }
692  if (ts.isDecorator(node)) {
693    validateSingleDecorator(node, sourceFileNode, log, isComponentV2);
694  }
695}
696
697function setLocalBuilderInFile(decoratorName: string): void {
698  if (decoratorName === 'LocalBuilder') {
699    storedFileInfo.hasLocalBuilderInFile = true;
700  }
701}
702
703function validateSingleDecorator(node: ts.Decorator, sourceFileNode: ts.SourceFile,
704  log: LogInfo[], isComponentV2: boolean): void {
705  const decoratorName: string = node.getText().replace(/\([^\(\)]*\)/, '');
706  if (decoratorName === constantDefine.COMPUTED_DECORATOR && node.parent && !ts.isGetAccessor(node.parent)) {
707    const message: string = `@Computed can only decorate 'GetAccessor'.`;
708    addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
709    return;
710  }
711  const partialDecoratorCollection: string[] = [constantDefine.MONITOR_DECORATOR, COMPONENT_LOCAL_BUILDER_DECORATOR];
712  if (partialDecoratorCollection.includes(decoratorName) && node.parent &&
713    !ts.isMethodDeclaration(node.parent)) {
714    const message: string = `'${decoratorName}' can only decorate method.`;
715    addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
716    return;
717  }
718  if (isMemberForComponentV2(decoratorName, isComponentV2) && node.parent &&
719    !ts.isPropertyDeclaration(node.parent)) {
720    const message: string = `'${decoratorName}' can only decorate member property.`;
721    addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
722    return;
723  }
724}
725
726function isMemberForComponentV2(decoratorName: string, isComponentV2: boolean): boolean {
727  return constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(decoratorName) ||
728    (isComponentV2 && decoratorName === '@BuilderParam');
729}
730
731const classDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, MIN_OBSERVED];
732const classMemberDecorators: string[] = [CLASS_TRACK_DECORATOR, CLASS_MIN_TRACK_DECORATOR, TYPE,
733  constantDefine.MONITOR, constantDefine.COMPUTED];
734
735function validTypeCallback(node: ts.Identifier): boolean {
736  let isSdkPath: boolean = true;
737  if (globalProgram.checker && process.env.compileTool === 'rollup') {
738    const symbolObj: ts.Symbol = getSymbolIfAliased(node);
739    const fileName: string = symbolObj?.valueDeclaration?.getSourceFile()?.fileName;
740    isSdkPath = /@ohos.arkui.*/.test(fileName);
741  }
742  return isSdkPath;
743}
744
745function isTypeFromSdkCallback(classContext: boolean, decoratorName: string, isTypeFromSdk: boolean): boolean {
746  if (!classContext && decoratorName === TYPE && isTypeFromSdk) {
747    return true;
748  }
749  return false;
750}
751
752function validateClassDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[],
753  classContext: boolean, decoratorName: string, isObservedClass: boolean, isObservedV1Class: boolean,
754  isSendableClass: boolean): void {
755  const isTypeFromSdk: boolean = validTypeCallback(node);
756  if (!classContext && (classDecorators.includes(decoratorName) || isTypeFromSdkCallback(classContext, decoratorName, isTypeFromSdk))) {
757    const message: string = `The '@${decoratorName}' decorator can only be used in 'class'.`;
758    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
759  } else if (classContext && classMemberDecorators.includes(decoratorName)) {
760    validateMemberInClass(isObservedClass, decoratorName, node, log, sourceFileNode, isObservedV1Class, isSendableClass, isTypeFromSdk);
761  }
762}
763
764function validateMemberInClass(isObservedClass: boolean, decoratorName: string, node: ts.Identifier,
765  log: LogInfo[], sourceFileNode: ts.SourceFile, isObservedV1Class: boolean, isSendableClass: boolean, isTypeFromSdk: boolean): void {
766  if (decoratorName === CLASS_TRACK_DECORATOR) {
767    if (isObservedClass) {
768      const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with ObservedV2.`;
769      addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
770    }
771    return;
772  }
773  if (decoratorName === TYPE) {
774    if (isTypeFromSdk) {
775      validType(sourceFileNode, node, log, decoratorName, isObservedV1Class, isSendableClass);
776    }
777    return;
778  }
779  if (!isObservedClass || !isPropertyForTrace(node, decoratorName)) {
780    const info: string = decoratorName === CLASS_MIN_TRACK_DECORATOR ? 'variables' : 'method';
781    const message: string = `The '@${decoratorName}' can decorate only member ${info} within a 'class' decorated with ObservedV2.`;
782    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
783    return;
784  }
785}
786
787function validType(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[], decoratorName: string,
788  isObservedV1Class: boolean, isSendableClass: boolean): void {
789  if (isSendableClass) {
790    const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Sendable.`;
791    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
792  }
793  if (isObservedV1Class) {
794    const message: string = `The '@${decoratorName}' decorator can not be used in a 'class' decorated with Observed.`;
795    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
796  }
797  if (ts.isDecorator(node.parent?.parent) && !ts.isPropertyDeclaration(node.parent?.parent?.parent)) {
798    const message: string = `The '@${decoratorName}' can decorate only member variables in a 'class'.`;
799    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
800  }
801}
802
803function isPropertyForTrace(node: ts.Identifier, decoratorName: string): boolean {
804  if (decoratorName === CLASS_MIN_TRACK_DECORATOR && ts.isDecorator(node.parent) &&
805    !ts.isPropertyDeclaration(node.parent.parent)) {
806    return false;
807  }
808  return true;
809}
810
811class ClassDecoratorResult {
812  hasObserved: boolean = false;
813  hasObservedV2: boolean = false;
814  hasSendable: boolean = false;
815}
816
817function parseClassDecorator(node: ts.ClassDeclaration, sourceFileNode: ts.SourceFile,
818  log: LogInfo[]): [boolean, boolean, boolean] {
819  const classResult: ClassDecoratorResult = getClassDecoratorResult(node);
820  validateMutilObserved(node, classResult, sourceFileNode, log);
821  if (classResult.hasObserved || classResult.hasObservedV2) {
822    parseInheritClass(node, classResult, sourceFileNode, log);
823  }
824  return [classResult.hasObserved, classResult.hasObservedV2, classResult.hasSendable];
825}
826
827function getClassDecoratorResult(node: ts.ClassDeclaration): ClassDecoratorResult {
828  const classResult: ClassDecoratorResult = new ClassDecoratorResult();
829  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
830  decorators.forEach((item: ts.Decorator) => {
831    if (ts.isIdentifier(item.expression)) {
832      const decoratorName: string = item.expression.escapedText.toString();
833      switch (decoratorName) {
834        case MIN_OBSERVED:
835          classResult.hasObservedV2 = true;
836          break;
837        case OBSERVED:
838          classResult.hasObserved = true;
839          break;
840        case SENDABLE:
841          classResult.hasSendable = true;
842      }
843    }
844  });
845  return classResult;
846}
847
848function validateMutilObserved(node: ts.ClassDeclaration, classResult: ClassDecoratorResult,
849  sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
850  if (classResult.hasObserved && classResult.hasObservedV2) {
851    const message: string = `A class can not be decorated by '@Observed' and '@ObservedV2' at the same time.`;
852    addLog(LogType.ERROR, message, node.getStart(), log, sourceFileNode);
853  }
854}
855
856function parseInheritClass(node: ts.ClassDeclaration, childClassResult: ClassDecoratorResult,
857  sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
858  if (globalProgram.checker && process.env.compileTool === 'rollup' && node.heritageClauses) {
859    for (const heritageClause of node.heritageClauses) {
860      if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && heritageClause.types &&
861        heritageClause.types.length) {
862        getClassNode(heritageClause.types[0].expression, childClassResult, node, sourceFileNode, log);
863      }
864    }
865  }
866}
867
868function getClassNode(parentType: ts.Node, childClassResult: ClassDecoratorResult,
869  childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
870  const symbol: ts.Symbol = parentType && getSymbolIfAliased(parentType);
871  if (symbol && symbol.valueDeclaration) {
872    if (ts.isClassDeclaration(symbol.valueDeclaration)) {
873      validateInheritClassDecorator(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log);
874      return;
875    }
876    if (ts.isPropertyAssignment(symbol.valueDeclaration)) {
877      getClassNode(symbol.valueDeclaration.initializer, childClassResult, childClass, sourceFileNode, log);
878      return;
879    }
880    if (ts.isShorthandPropertyAssignment(symbol.valueDeclaration)) {
881      parseShorthandPropertyForClass(symbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log);
882      return;
883    }
884  }
885}
886
887function parseShorthandPropertyForClass(node: ts.ShorthandPropertyAssignment, childClassResult: ClassDecoratorResult,
888  childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
889  const shortSymbol: ts.Symbol = globalProgram.checker.getShorthandAssignmentValueSymbol(node);
890  if (shortSymbol && shortSymbol.valueDeclaration && ts.isClassDeclaration(shortSymbol.valueDeclaration)) {
891    validateInheritClassDecorator(shortSymbol.valueDeclaration, childClassResult, childClass, sourceFileNode, log);
892  }
893}
894
895function getSymbolIfAliased(node: ts.Node): ts.Symbol {
896  const symbol: ts.Symbol = globalProgram.checker.getSymbolAtLocation(node);
897  if (symbol && (symbol.getFlags() & ts.SymbolFlags.Alias) !== 0) {
898    return globalProgram.checker.getAliasedSymbol(symbol);
899  }
900  return symbol;
901}
902
903function validateInheritClassDecorator(parentNode: ts.ClassDeclaration, childClassResult: ClassDecoratorResult,
904  childClass: ts.ClassDeclaration, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
905  const parentClassResult: ClassDecoratorResult = getClassDecoratorResult(parentNode);
906  if (childClassResult.hasObservedV2 && parentClassResult.hasObserved) {
907    const message: string = `Because the current class is decorated by '@ObservedV2', ` +
908      `it can not inherit a class decorated by '@Observed'.`;
909    addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode);
910    return;
911  }
912  if (childClassResult.hasObserved && parentClassResult.hasObservedV2) {
913    const message: string = `Because the current class is decorated by '@Observed', ` +
914      `it can not inherit a class decorated by '@ObservedV2'.`;
915    addLog(LogType.ERROR, message, childClass.getStart(), log, sourceFileNode);
916    return;
917  }
918}
919
920function validateStructDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[],
921  structContext: boolean, decoratorName: string, isComponentV2: boolean): void {
922  const name: string = `@${decoratorName}`;
923  if (structContext) {
924    if (isComponentV2) {
925      if (constantDefine.COMPONENT_MEMBER_DECORATOR_V1.includes(name)) {
926        const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@Component'.`;
927        addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
928      }
929    } else if (constantDefine.DECORATOR_V2.includes(name)) {
930      const message: string = `The '@${decoratorName}' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`;
931      addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
932    }
933  } else if (STRUCT_DECORATORS.has(name) || constantDefine.COMPONENT_MEMBER_DECORATOR_V2.includes(name)) {
934    const message: string = `The '@${decoratorName}' decorator can only be used with 'struct'.`;
935    addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
936  }
937}
938
939function validateMethodDecorator(sourceFileNode: ts.SourceFile, node: ts.Identifier, log: LogInfo[],
940  structContext: boolean, decoratorName: string): void {
941  let message: string;
942  if (ts.isMethodDeclaration(node.parent.parent) ||
943    (ts.isDecorator(node.parent.parent) && ts.isMethodDeclaration(node.parent.parent.parent))) {
944    if (!structContext && STRUCT_CONTEXT_METHOD_DECORATORS.has(`@${decoratorName}`)) {
945      message = `The '@${decoratorName}' decorator can only be used in 'struct'.`;
946    }
947    if (CHECK_EXTEND_DECORATORS.includes(decoratorName)) {
948      message = `The '@${decoratorName}' decorator can not be a member property method of a 'class' or 'struct'.`;
949    }
950    if (message) {
951      addLog(LogType.ERROR, message, node.pos, log, sourceFileNode);
952    }
953  }
954}
955
956export function isSendableClassDeclaration(classDeclarationNode: ts.ClassDeclaration): boolean {
957  return hasDecorator(classDeclarationNode, COMPONENT_SENDABLE_DECORATOR) ||
958      (classDeclarationNode.members && classDeclarationNode.members.some((member: ts.Node) => {
959        // Check if the constructor has "use sendable" as the first statement
960        // (Sendable classes already transformed by process_ui_syntax.ts)
961        if (ts.isConstructorDeclaration(member)) {
962          if (!(member as ts.ConstructorDeclaration).body ||
963              !(member as ts.ConstructorDeclaration).body.statements) {
964            return false;
965          }
966          const constructorStatements: ts.Statement[] = (member as ts.ConstructorDeclaration).body.statements;
967          if (constructorStatements && constructorStatements[0] &&
968              ts.isExpressionStatement(constructorStatements[0])) {
969            const expression: ts.Node = (constructorStatements[0] as ts.ExpressionStatement).expression;
970            return expression && ts.isStringLiteral(expression) &&
971                (expression as ts.StringLiteral).text === 'use sendable';
972          }
973        }
974        return false;
975      }));
976}
977
978export function checkAllNode(
979  node: ts.EtsComponentExpression,
980  allComponentNames: Set<string>,
981  sourceFileNode: ts.SourceFile,
982  log: LogInfo[]
983): void {
984  if (ts.isIdentifier(node.expression)) {
985    checkNoChildComponent(node, sourceFileNode, log);
986    checkOneChildComponent(node, allComponentNames, sourceFileNode, log);
987    checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log);
988  }
989}
990
991interface ParamType {
992  name: string,
993  value: string,
994}
995
996function checkNoChildComponent(node: ts.EtsComponentExpression, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
997  const isCheckType: ParamType = { name: null, value: null};
998  if (hasChild(node, isCheckType)) {
999    const componentName: string = (node.expression as ts.Identifier).escapedText.toString();
1000    const pos: number = node.expression.getStart();
1001    const message: string = isCheckType.name === null ?
1002      `The component '${componentName}' can't have any child.` :
1003      `When the component '${componentName}' set '${isCheckType.name}' is '${isCheckType.value}'` +
1004        `, can't have any child.`;
1005    addLog(LogType.ERROR, message, pos, log, sourceFileNode);
1006  }
1007}
1008
1009function hasChild(node: ts.EtsComponentExpression, isCheckType: ParamType): boolean {
1010  const nodeName: ts.Identifier = node.expression as ts.Identifier;
1011  if ((AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) || judgeComponentType(nodeName, node, isCheckType)) &&
1012    getNextNode(node)) {
1013    return true;
1014  }
1015  return false;
1016}
1017
1018function judgeComponentType(nodeName: ts.Identifier, etsComponentExpression: ts.EtsComponentExpression,
1019  isCheckType: ParamType): boolean {
1020  return COMPONENT_TOGGLE === nodeName.escapedText.toString() &&
1021    etsComponentExpression.arguments && etsComponentExpression.arguments[0] &&
1022    ts.isObjectLiteralExpression(etsComponentExpression.arguments[0]) &&
1023    etsComponentExpression.arguments[0].getText() &&
1024    judgeToggleComponentParamType(etsComponentExpression.arguments[0].getText(), isCheckType);
1025}
1026
1027function judgeToggleComponentParamType(param: string, isCheckType: ParamType): boolean {
1028  if (param.indexOf(RESOURCE_NAME_TYPE) > -1) {
1029    isCheckType.name = RESOURCE_NAME_TYPE;
1030    const match: string[] = param.match(/\b(Checkbox|Switch|Button)\b/);
1031    if (match && match.length) {
1032      isCheckType.value = match[0];
1033      if (isCheckType.value === COMPONENT_BUTTON) {
1034        return false;
1035      }
1036      return true;
1037    }
1038  }
1039  return false;
1040}
1041
1042function getNextNode(node: ts.EtsComponentExpression): ts.Block {
1043  if (node.body && ts.isBlock(node.body)) {
1044    const statementsArray: ts.Block = node.body;
1045    return statementsArray;
1046  }
1047  return undefined;
1048}
1049
1050function checkOneChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>,
1051  sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
1052  const isCheckType: ParamType = { name: null, value: null};
1053  if (hasNonSingleChild(node, allComponentNames, isCheckType)) {
1054    const componentName: string = (node.expression as ts.Identifier).escapedText.toString();
1055    const pos: number = node.expression.getStart();
1056    const message: string = isCheckType.name === null ?
1057      `The component '${componentName}' can only have a single child component.` :
1058      `When the component '${componentName}' set '${isCheckType.name}' is ` +
1059        `'${isCheckType.value}', can only have a single child component.`;
1060    addLog(LogType.ERROR, message, pos, log, sourceFileNode);
1061  }
1062}
1063
1064function hasNonSingleChild(node: ts.EtsComponentExpression, allComponentNames: Set<string>,
1065  isCheckType: ParamType): boolean {
1066  const nodeName: ts.Identifier = node.expression as ts.Identifier;
1067  const blockNode: ts.Block = getNextNode(node);
1068  if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString()) || !judgeComponentType(nodeName, node, isCheckType) &&
1069    isCheckType.value === COMPONENT_BUTTON) {
1070    if (!blockNode) {
1071      return false;
1072    }
1073    if (blockNode && blockNode.statements) {
1074      const length: number = blockNode.statements.length;
1075      if (!length) {
1076        return false;
1077      }
1078      if (length > 3) {
1079        return true;
1080      }
1081      const childCount: number = getBlockChildrenCount(blockNode, allComponentNames);
1082      if (childCount > 1) {
1083        return true;
1084      }
1085    }
1086  }
1087  return false;
1088}
1089
1090function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set<string>): number {
1091  let maxCount: number = 0;
1092  const length: number = blockNode.statements.length;
1093  for (let i = 0; i < length; ++i) {
1094    const item: ts.Node = blockNode.statements[i];
1095    if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) &&
1096      isForEachComponent(item.expression)) {
1097      maxCount += 2;
1098    }
1099    if (ts.isIfStatement(item)) {
1100      maxCount += getIfChildrenCount(item, allComponentNames);
1101    }
1102    if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) {
1103      maxCount += 1;
1104    }
1105    if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) {
1106      let newNode = item.expression;
1107      while (newNode.expression) {
1108        if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) &&
1109          isComponent(newNode, allComponentNames)) {
1110          maxCount += 1;
1111        }
1112        newNode = newNode.expression;
1113      }
1114    }
1115    if (maxCount > 1) {
1116      break;
1117    }
1118  }
1119  return maxCount;
1120}
1121
1122function isComponent(node: ts.EtsComponentExpression | ts.CallExpression, allComponentNames: Set<string>): boolean {
1123  if (ts.isIdentifier(node.expression) &&
1124    allComponentNames.has(node.expression.escapedText.toString())) {
1125    return true;
1126  }
1127  return false;
1128}
1129
1130function isForEachComponent(node: ts.EtsComponentExpression | ts.CallExpression): boolean {
1131  if (ts.isIdentifier(node.expression)) {
1132    const componentName: string = node.expression.escapedText.toString();
1133    return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH;
1134  }
1135  return false;
1136}
1137
1138function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set<string>): number {
1139  const maxCount: number =
1140    Math.max(getStatementCount(ifNode.thenStatement, allComponentNames),
1141      getStatementCount(ifNode.elseStatement, allComponentNames));
1142  return maxCount;
1143}
1144
1145function getStatementCount(node: ts.Node, allComponentNames: Set<string>): number {
1146  let maxCount: number = 0;
1147  if (!node) {
1148    return maxCount;
1149  } else if (ts.isBlock(node)) {
1150    maxCount = getBlockChildrenCount(node, allComponentNames);
1151  } else if (ts.isIfStatement(node)) {
1152    maxCount = getIfChildrenCount(node, allComponentNames);
1153  } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
1154    isForEachComponent(node.expression)) {
1155    maxCount = 2;
1156  } else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
1157    !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) {
1158    maxCount = 1;
1159  }
1160  return maxCount;
1161}
1162
1163function checkSpecificChildComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>,
1164  sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
1165  if (hasNonspecificChild(node, allComponentNames)) {
1166    const componentName: string = (node.expression as ts.Identifier).escapedText.toString();
1167    const pos: number = node.expression.getStart();
1168    const specificChildArray: string =
1169      Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and ');
1170    const message: string =
1171      `The component '${componentName}' can only have the child component ${specificChildArray}.`;
1172    addLog(LogType.ERROR, message, pos, log, sourceFileNode);
1173  }
1174}
1175
1176function hasNonspecificChild(node: ts.EtsComponentExpression,
1177  allComponentNames: Set<string>): boolean {
1178  const nodeName: ts.Identifier = node.expression as ts.Identifier;
1179  const nodeNameString: string = nodeName.escapedText.toString();
1180  const blockNode: ts.Block = getNextNode(node);
1181  let isNonspecific: boolean = false;
1182  if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) {
1183    const specificChildSet: Set<string> = SPECIFIC_CHILD_COMPONENT.get(nodeNameString);
1184    isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames);
1185    if (isNonspecific) {
1186      return isNonspecific;
1187    }
1188  }
1189  return isNonspecific;
1190}
1191
1192function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set<string>,
1193  allComponentNames: Set<string>): boolean {
1194  if (blockNode.statements) {
1195    const length: number = blockNode.statements.length;
1196    for (let i = 0; i < length; ++i) {
1197      const item: ts.Node = blockNode.statements[i];
1198      if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) {
1199        return true;
1200      }
1201      if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) &&
1202        isForEachComponent(item.expression) &&
1203        isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) {
1204        return true;
1205      }
1206      if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) {
1207        return true;
1208      }
1209      if (ts.isExpressionStatement(item)) {
1210        let newNode: any = item.expression;
1211        while (newNode.expression) {
1212          if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) &&
1213          !isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) {
1214            const isNonspecific: boolean =
1215            isNonspecificChildNonForEach(newNode, specificChildSet);
1216            if (isNonspecific) {
1217              return isNonspecific;
1218            }
1219            if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) {
1220              ++i;
1221            }
1222          }
1223          newNode = newNode.expression;
1224        }
1225      }
1226    }
1227  }
1228  return false;
1229}
1230
1231function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set<string>,
1232  allComponentNames: Set<string>): boolean {
1233  return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) ||
1234    isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames);
1235}
1236
1237function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set<string>,
1238  allComponentNames: Set<string>): boolean {
1239  if (ts.isCallExpression(node) && node.arguments &&
1240    node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) {
1241    const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction;
1242    const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement =
1243      arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement;
1244    if (!body) {
1245      return false;
1246    }
1247    if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) {
1248      return true;
1249    }
1250    if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) {
1251      return true;
1252    }
1253    if (ts.isCallExpression(body) && isForEachComponent(body) &&
1254      isNonspecificChildForEach(body, specificChildSet, allComponentNames)) {
1255      return true;
1256    }
1257    if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) &&
1258      isComponent(body, allComponentNames) &&
1259      isNonspecificChildNonForEach(body, specificChildSet)) {
1260      return true;
1261    }
1262  }
1263  return false;
1264}
1265
1266function isNonspecificChildNonForEach(node: ts.EtsComponentExpression,
1267  specificChildSet: Set<string>): boolean {
1268  if (ts.isIdentifier(node.expression) &&
1269    !specificChildSet.has(node.expression.escapedText.toString())) {
1270    return true;
1271  }
1272  return false;
1273}
1274
1275function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set<string>,
1276  allComponentNames: Set<string>): boolean {
1277  if (!node) {
1278    return false;
1279  }
1280  if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) {
1281    return true;
1282  }
1283  if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) {
1284    return true;
1285  }
1286  if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
1287    isForEachComponent(node.expression) &&
1288    isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) {
1289    return true;
1290  }
1291  if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
1292    !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) &&
1293    isNonspecificChildNonForEach(node.expression, specificChildSet)) {
1294    return true;
1295  }
1296  return false;
1297}
1298
1299function collectComponentProps(node: ts.StructDeclaration, structInfo: StructInfo): void {
1300  const componentName: string = node.name.getText();
1301  const componentSet: IComponentSet = getComponentSet(node, true);
1302  propertyCollection.set(componentName, componentSet.properties);
1303  stateCollection.set(componentName, componentSet.states);
1304  linkCollection.set(componentName, componentSet.links);
1305  storedFileInfo.overallLinkCollection.set(componentName, componentSet.links);
1306  propCollection.set(componentName, componentSet.props);
1307  regularCollection.set(componentName, componentSet.regulars);
1308  storagePropCollection.set(componentName, componentSet.storageProps);
1309  storageLinkCollection.set(componentName, componentSet.storageLinks);
1310  provideCollection.set(componentName, componentSet.provides);
1311  consumeCollection.set(componentName, componentSet.consumes);
1312  objectLinkCollection.set(componentName, componentSet.objectLinks);
1313  storedFileInfo.overallObjectLinkCollection.set(componentName, componentSet.objectLinks);
1314  localStorageLinkCollection.set(componentName, componentSet.localStorageLink);
1315  localStoragePropCollection.set(componentName, componentSet.localStorageProp);
1316  builderParamObjectCollection.set(componentName, componentSet.builderParams);
1317  builderParamInitialization.set(componentName, componentSet.builderParamData);
1318  propInitialization.set(componentName, componentSet.propData);
1319  regularInitialization.set(componentName, componentSet.regularInit);
1320  stateInitialization.set(componentName, componentSet.stateInit);
1321  provideInitialization.set(componentName, componentSet.provideInit);
1322  privateCollection.set(componentName, componentSet.privateCollection);
1323  structInfo.updatePropsDecoratorsV1.push(
1324    ...componentSet.states, ...componentSet.props,
1325    ...componentSet.provides, ...componentSet.objectLinks
1326  );
1327  structInfo.linkDecoratorsV1.push(...componentSet.links);
1328}
1329
1330export function getComponentSet(node: ts.StructDeclaration, uiCheck: boolean = false): IComponentSet {
1331  const componentSet: IComponentSet = new IComponentSet();
1332  traversalComponentProps(node, componentSet, uiCheck);
1333  return componentSet;
1334}
1335
1336class RecordRequire {
1337  hasRequire: boolean = false;
1338  hasProp: boolean = false;
1339  hasBuilderParam: boolean = false;
1340  hasRegular: boolean = false;
1341  hasState: boolean = false;
1342  hasProvide: boolean = false;
1343}
1344
1345function traversalComponentProps(node: ts.StructDeclaration, componentSet: IComponentSet,
1346  uiCheck: boolean = false): void {
1347  let isStatic: boolean = true;
1348  if (node.members) {
1349    const currentMethodCollection: Set<string> = new Set();
1350    node.members.forEach(item => {
1351      if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
1352        const propertyName: string = item.name.getText();
1353        componentSet.properties.add(propertyName);
1354        const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item);
1355        const accessQualifierResult: AccessQualifierResult = getAccessQualifier(item, uiCheck);
1356        if (!decorators || !decorators.length) {
1357          componentSet.regulars.add(propertyName);
1358          setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR);
1359        } else {
1360          isStatic = false;
1361          let hasValidatePrivate: boolean = false;
1362          const recordRequire: RecordRequire = new RecordRequire();
1363          for (let i = 0; i < decorators.length; i++) {
1364            const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim();
1365            if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
1366              dollarCollection.add('$' + propertyName);
1367              collectionStates(decorators[i], decoratorName, propertyName,
1368                componentSet, recordRequire);
1369              setPrivateCollection(componentSet, accessQualifierResult, propertyName, decoratorName);
1370              validateAccessQualifier(item, propertyName, decoratorName, accessQualifierResult,
1371                recordRequire, uiCheck, hasValidatePrivate);
1372              hasValidatePrivate = true;
1373            }
1374          }
1375          regularAndRequire(decorators, componentSet, recordRequire, propertyName, accessQualifierResult);
1376          checkRequire(propertyName, componentSet, recordRequire);
1377        }
1378      }
1379      if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) {
1380        validateStateVariable(item);
1381        currentMethodCollection.add(item.name.getText());
1382      }
1383    });
1384    collectCurrentClassMethod(node, currentMethodCollection);
1385  }
1386  isStaticViewCollection.set(node.name.getText(), isStatic);
1387}
1388
1389function collectCurrentClassMethod(node: ts.StructDeclaration, currentMethodCollection: Set<string>): void {
1390  const componentMethodCollection: Map<string, Set<string>> = new Map();
1391  componentMethodCollection.set(node.name.getText(), currentMethodCollection);
1392  const sourceFile: ts.SourceFile = node.getSourceFile();
1393  const filePath: string = sourceFile ? sourceFile.fileName : undefined;
1394  if (filePath) {
1395    const pageMethodCollection: Map<string, Set<string>> = classMethodCollection.get(filePath);
1396    if (!pageMethodCollection) {
1397      classMethodCollection.set(filePath, componentMethodCollection);
1398    } else if (!pageMethodCollection.get(node.name.getText())) {
1399      pageMethodCollection.set(node.name.getText(), currentMethodCollection);
1400    }
1401  }
1402}
1403
1404const FORBIDDEN_PUBLIC_ACCESS: string[] = [COMPONENT_STORAGE_PROP_DECORATOR,
1405  COMPONENT_STORAGE_LINK_DECORATOR, COMPONENT_LOCAL_STORAGE_LINK_DECORATOR,
1406  COMPONENT_LOCAL_STORAGE_PROP_DECORATOR, COMPONENT_CONSUME_DECORATOR
1407];
1408const FORBIDDEN_PRIVATE_ACCESS: string[] = [COMPONENT_LINK_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR];
1409
1410function validateAccessQualifier(node: ts.PropertyDeclaration, propertyName: string,
1411  decoratorName: string, accessQualifierResult: AccessQualifierResult,
1412  recordRequire: RecordRequire, uiCheck: boolean = false, hasValidatePrivate: boolean): void {
1413  if (uiCheck) {
1414    if (accessQualifierResult.hasPublic && FORBIDDEN_PUBLIC_ACCESS.includes(decoratorName)) {
1415      transformLog.errors.push({
1416        type: LogType.WARN,
1417        message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and public.`,
1418        pos: node.getStart()
1419      });
1420    }
1421    if (accessQualifierResult.hasPrivate) {
1422      if (FORBIDDEN_PRIVATE_ACCESS.includes(decoratorName)) {
1423        transformLog.errors.push({
1424          type: LogType.WARN,
1425          message: `Property '${propertyName}' can not be decorated with both ${decoratorName} and private.`,
1426          pos: node.getStart()
1427        });
1428      }
1429      if (recordRequire.hasRequire && !hasValidatePrivate) {
1430        transformLog.errors.push({
1431          type: LogType.WARN,
1432          message: `Property '${propertyName}' can not be decorated with both @Require and private.`,
1433          pos: node.getStart()
1434        });
1435      }
1436    }
1437  }
1438}
1439
1440const SUPPORT_PRIVATE_PROPS: string[] = [COMPONENT_NON_DECORATOR, COMPONENT_STATE_DECORATOR,
1441  COMPONENT_PROP_DECORATOR, COMPONENT_PROVIDE_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR
1442];
1443
1444function setPrivateCollection(componentSet: IComponentSet, accessQualifierResult: AccessQualifierResult,
1445  propertyName: string, decoratorName: string): void {
1446  if (accessQualifierResult.hasPrivate && SUPPORT_PRIVATE_PROPS.includes(decoratorName)) {
1447    componentSet.privateCollection.add(propertyName);
1448  }
1449}
1450
1451class AccessQualifierResult {
1452  hasPrivate: boolean = false;
1453  hasPublic: boolean = false;
1454}
1455
1456function getAccessQualifier(node: ts.PropertyDeclaration, uiCheck: boolean = false): AccessQualifierResult {
1457  const modifiers: readonly ts.Modifier[] = ts.getModifiers(node);
1458  const accessQualifierResult: AccessQualifierResult = new AccessQualifierResult();
1459  if (modifiers && modifiers.length) {
1460    modifiers.forEach((item) => {
1461      if (item.kind === ts.SyntaxKind.PrivateKeyword) {
1462        accessQualifierResult.hasPrivate = true;
1463      }
1464      if (item.kind === ts.SyntaxKind.PublicKeyword) {
1465        accessQualifierResult.hasPublic = true;
1466      }
1467      if (uiCheck && item.kind === ts.SyntaxKind.ProtectedKeyword) {
1468        transformLog.errors.push({
1469          type: LogType.WARN,
1470          message: `The member attributes of a struct can not be protected.`,
1471          pos: node.getStart()
1472        });
1473      }
1474    });
1475  }
1476  return accessQualifierResult;
1477}
1478
1479function regularAndRequire(decorators: readonly ts.Decorator[], componentSet: IComponentSet,
1480  recordRequire: RecordRequire, propertyName: string, accessQualifierResult: AccessQualifierResult): void {
1481  if (decorators && decorators.length === 1 && decorators[0].getText() === COMPONENT_REQUIRE_DECORATOR) {
1482    componentSet.regulars.add(propertyName);
1483    recordRequire.hasRegular = true;
1484    setPrivateCollection(componentSet, accessQualifierResult, propertyName, COMPONENT_NON_DECORATOR);
1485  }
1486}
1487
1488function checkRequire(name: string, componentSet: IComponentSet, recordRequire: RecordRequire): void {
1489  if (recordRequire.hasRequire) {
1490    setInitValue('hasProp', 'propData', name, componentSet, recordRequire);
1491    setInitValue('hasBuilderParam', 'builderParamData', name, componentSet, recordRequire);
1492    setInitValue('hasRegular', 'regularInit', name, componentSet, recordRequire);
1493    setInitValue('hasState', 'stateInit', name, componentSet, recordRequire);
1494    setInitValue('hasProvide', 'provideInit', name, componentSet, recordRequire);
1495  }
1496}
1497
1498function setInitValue(requirekey: string, initKey: string, name: string, componentSet: IComponentSet,
1499  recordRequire: RecordRequire): void {
1500  if (recordRequire[requirekey]) {
1501    componentSet[initKey].add(name);
1502  }
1503}
1504
1505function collectionStates(node: ts.Decorator, decorator: string, name: string,
1506  componentSet: IComponentSet, recordRequire: RecordRequire): void {
1507  switch (decorator) {
1508    case COMPONENT_STATE_DECORATOR:
1509      componentSet.states.add(name);
1510      recordRequire.hasState = true;
1511      break;
1512    case COMPONENT_LINK_DECORATOR:
1513      componentSet.links.add(name);
1514      break;
1515    case COMPONENT_PROP_DECORATOR:
1516      recordRequire.hasProp = true;
1517      componentSet.props.add(name);
1518      break;
1519    case COMPONENT_STORAGE_PROP_DECORATOR:
1520      componentSet.storageProps.add(name);
1521      break;
1522    case COMPONENT_STORAGE_LINK_DECORATOR:
1523      componentSet.storageLinks.add(name);
1524      break;
1525    case COMPONENT_PROVIDE_DECORATOR:
1526      recordRequire.hasProvide = true;
1527      componentSet.provides.add(name);
1528      break;
1529    case COMPONENT_CONSUME_DECORATOR:
1530      componentSet.consumes.add(name);
1531      break;
1532    case COMPONENT_OBJECT_LINK_DECORATOR:
1533      componentSet.objectLinks.add(name);
1534      break;
1535    case COMPONENT_BUILDERPARAM_DECORATOR:
1536      recordRequire.hasBuilderParam = true;
1537      componentSet.builderParams.add(name);
1538      break;
1539    case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR :
1540      collectionlocalStorageParam(node, name, componentSet.localStorageLink);
1541      break;
1542    case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR:
1543      collectionlocalStorageParam(node, name, componentSet.localStorageProp);
1544      break;
1545    case COMPONENT_REQUIRE_DECORATOR:
1546      recordRequire.hasRequire = true;
1547      break;
1548  }
1549}
1550
1551function collectionlocalStorageParam(node: ts.Decorator, name: string,
1552  localStorage: Map<string, Set<string>>): void {
1553  const localStorageParam: Set<string> = new Set();
1554  if (node && ts.isCallExpression(node.expression) && node.expression.arguments &&
1555    node.expression.arguments.length) {
1556    localStorage.set(name, localStorageParam.add(
1557      node.expression.arguments[0].getText()));
1558  }
1559}
1560
1561export interface ReplaceResult {
1562  content: string,
1563  log: LogInfo[]
1564}
1565
1566export function sourceReplace(source: string, sourcePath: string): ReplaceResult {
1567  let content: string = source;
1568  const log: LogInfo[] = [];
1569  content = preprocessExtend(content);
1570  content = preprocessNewExtend(content);
1571  // process @system.
1572  content = processSystemApi(content, false, sourcePath);
1573  collectImportNames(content, sourcePath);
1574
1575  return {
1576    content: content,
1577    log: log
1578  };
1579}
1580
1581export function preprocessExtend(content: string, extendCollection?: Set<string>): string {
1582  const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm;
1583  return content.replace(REG_EXTEND, (item, item1, item2, item3) => {
1584    collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3);
1585    collectExtend(EXTEND_ATTRIBUTE, item2, item3);
1586    if (extendCollection) {
1587      extendCollection.add(item3);
1588    }
1589    return `@Extend(${item2})${item1}function __${item2}__${item3}(`;
1590  });
1591}
1592
1593export function preprocessNewExtend(content: string, extendCollection?: Set<string>): string {
1594  const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm;
1595  return content.replace(REG_EXTEND, (item, item1) => {
1596    if (extendCollection) {
1597      extendCollection.add(item1);
1598    }
1599    return item;
1600  });
1601}
1602
1603function replaceSystemApi(item: string, systemValue: string, moduleType: string, systemKey: string): string {
1604  // if change format, please update regexp in transformModuleSpecifier
1605  if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) {
1606    item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`;
1607  } else if (moduleType === SYSTEM_PLUGIN || moduleType === OHOS_PLUGIN) {
1608    item = `var ${systemValue} = globalThis.requireNapi('${systemKey}')`;
1609  }
1610  return item;
1611}
1612
1613function replaceLibSo(importValue: string, libSoKey: string, sourcePath: string = null): string {
1614  if (sourcePath) {
1615    useOSFiles.add(sourcePath);
1616  }
1617  // if change format, please update regexp in transformModuleSpecifier
1618  return projectConfig.bundleName && projectConfig.moduleName ?
1619    `var ${importValue} = globalThis.requireNapi("${libSoKey}", true, "${projectConfig.bundleName}/${projectConfig.moduleName}");` :
1620    `var ${importValue} = globalThis.requireNapi("${libSoKey}", true);`;
1621}
1622
1623export function processSystemApi(content: string, isProcessAllowList: boolean = false,
1624  sourcePath: string = null, isSystemModule: boolean = false): string {
1625  if (isProcessAllowList && projectConfig.compileMode === ESMODULE) {
1626    // remove the unused system api import decl like following when compile as [esmodule]
1627    // in the result_process phase
1628    // e.g. import "@ohos.application.xxx"
1629    const REG_UNUSED_SYSTEM_IMPORT: RegExp = /import(?:\s*)['"]@(system|ohos)\.(\S+)['"]/g;
1630    content = content.replace(REG_UNUSED_SYSTEM_IMPORT, '');
1631  }
1632
1633  const REG_IMPORT_DECL: RegExp = isProcessAllowList ? projectConfig.compileMode === ESMODULE ?
1634    /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]/g :
1635    /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g :
1636    /(import|export)\s+(?:(.+)|\{([\s\S]+)\})\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g;
1637
1638  const systemValueCollection: Set<string> = new Set();
1639  const processedContent: string = content.replace(REG_IMPORT_DECL, (item, item1, item2, item3, item4, item5, item6) => {
1640    const importValue: string = isProcessAllowList ? projectConfig.compileMode === ESMODULE ? item1 : item2 : item2 || item5;
1641
1642    if (isProcessAllowList) {
1643      systemValueCollection.add(importValue);
1644      if (projectConfig.compileMode !== ESMODULE) {
1645        collectSourcemapNames(sourcePath, importValue, item5);
1646        return replaceSystemApi(item, importValue, item4, item5);
1647      }
1648      collectSourcemapNames(sourcePath, importValue, item3);
1649      return replaceSystemApi(item, importValue, item2, item3);
1650    }
1651
1652    const moduleRequest: string = item4 || item6;
1653    if (/^@(system|ohos)\./.test(moduleRequest)) { // ohos/system.api
1654      // ets & ts file need compile with .d.ts, so do not replace at the phase of pre_process
1655      if (!isSystemModule) {
1656        return item;
1657      }
1658      const result: RegExpMatchArray = moduleRequest.match(/^@(system|ohos)\.(\S+)$/);
1659      const moduleType: string = result[1];
1660      const apiName: string = result[2];
1661      return replaceSystemApi(item, importValue, moduleType, apiName);
1662    } else if (/^lib(\S+)\.so$/.test(moduleRequest)) { // libxxx.so
1663      const result: RegExpMatchArray = moduleRequest.match(/^lib(\S+)\.so$/);
1664      const libSoKey: string = result[1];
1665      return replaceLibSo(importValue, libSoKey, sourcePath);
1666    }
1667    return item;
1668  });
1669  return processInnerModule(processedContent, systemValueCollection);
1670}
1671
1672function collectSourcemapNames(sourcePath: string, changedName: string, originalName: string): void {
1673  if (sourcePath == null) {
1674    return;
1675  }
1676  const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js');
1677  if (!sourcemapNamesCollection.has(cleanSourcePath)) {
1678    return;
1679  }
1680
1681  const map: Map<string, string> = sourcemapNamesCollection.get(cleanSourcePath);
1682  if (map.has(changedName)) {
1683    return;
1684  }
1685
1686  for (const entry of originalImportNamesMap.entries()) {
1687    const key: string = entry[0];
1688    const value: string = entry[1];
1689    if (value === '@ohos.' + originalName || value === '@system.' + originalName) {
1690      map.set(changedName.trim(), key);
1691      sourcemapNamesCollection.set(cleanSourcePath, map);
1692      originalImportNamesMap.delete(key);
1693      break;
1694    }
1695  }
1696}
1697
1698export function collectImportNames(content: string, sourcePath: string = null): void {
1699  const REG_IMPORT_DECL: RegExp =
1700    /(import|export)\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g;
1701
1702  const decls: string[] = content.match(REG_IMPORT_DECL);
1703  if (decls !== null) {
1704    decls.forEach(decl => {
1705      const parts: string[] = decl.split(' ');
1706      if (parts.length === 4 && parts[0] === 'import' && parts[2] === 'from' && !parts[3].includes('.so')) {
1707        originalImportNamesMap.set(parts[1], parts[3].replace(/'/g, ''));
1708      }
1709    });
1710  }
1711
1712  if (sourcePath && sourcePath !== null) {
1713    const cleanSourcePath: string = sourcePath.replace('.ets', '.js').replace('.ts', '.js');
1714    if (!sourcemapNamesCollection.has(cleanSourcePath)) {
1715      sourcemapNamesCollection.set(cleanSourcePath, new Map());
1716    }
1717  }
1718}
1719
1720function processInnerModule(content: string, systemValueCollection: Set<string>): string {
1721  systemValueCollection.forEach(element => {
1722    const target: string = element.trim() + '.default';
1723    while (content.includes(target)) {
1724      content = content.replace(target, element.trim());
1725    }
1726  });
1727  return content;
1728}
1729
1730export function resetComponentCollection(): void {
1731  componentCollection.entryComponent = null;
1732  componentCollection.entryComponentPos = null;
1733  componentCollection.previewComponent = [];
1734  stateObjectCollection.clear();
1735  builderParamInitialization.clear();
1736  propInitialization.clear();
1737  propCollection.clear();
1738  objectLinkCollection.clear();
1739  linkCollection.clear();
1740  storedFileInfo.overallLinkCollection.clear();
1741  storedFileInfo.overallObjectLinkCollection.clear();
1742  storedFileInfo.overallBuilderParamCollection.clear();
1743  regularInitialization.clear();
1744  stateInitialization.clear();
1745  provideInitialization.clear();
1746  privateCollection.clear();
1747}
1748
1749function checkEntryComponent(node: ts.StructDeclaration, log: LogInfo[], sourceFile: ts.SourceFile): void {
1750  const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
1751  if (modifiers) {
1752    for (let i = 0; i < modifiers.length; i++) {
1753      if (modifiers[i].kind === ts.SyntaxKind.ExportKeyword) {
1754        const message: string = `It's not a recommended way to export struct with @Entry decorator, ` +
1755          `which may cause ACE Engine error in component preview mode.`;
1756        addLog(LogType.WARN, message, node.getStart(), log, sourceFile);
1757        break;
1758      }
1759    }
1760  }
1761}
1762
1763function validateStateVariable(node: ts.MethodDeclaration): void {
1764  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
1765  if (decorators && decorators.length) {
1766    for (let i = 0; i < decorators.length; i++) {
1767      const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim();
1768      if (CARD_ENABLE_DECORATORS[decoratorName]) {
1769        validatorCard(transformLog.errors, CARD_LOG_TYPE_DECORATORS,
1770          decorators[i].getStart(), decoratorName);
1771      }
1772      if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
1773        transformLog.errors.push({
1774          type: LogType.ERROR,
1775          message: `'${decorators[i].getText()}' can not decorate the method.`,
1776          pos: decorators[i].getStart()
1777        });
1778      }
1779    }
1780  }
1781}
1782
1783export function getObservedPropertyCollection(className: string): Set<string> {
1784  const observedProperthCollection: Set<string> = new Set([
1785    ...stateCollection.get(className),
1786    ...linkCollection.get(className),
1787    ...propCollection.get(className),
1788    ...storageLinkCollection.get(className),
1789    ...storageLinkCollection.get(className),
1790    ...provideCollection.get(className),
1791    ...consumeCollection.get(className),
1792    ...objectLinkCollection.get(className)
1793  ]);
1794  getLocalStorageCollection(className, observedProperthCollection);
1795  return observedProperthCollection;
1796}
1797
1798export function getLocalStorageCollection(componentName: string, collection: Set<string>): void {
1799  if (localStorageLinkCollection.get(componentName)) {
1800    for (const key of localStorageLinkCollection.get(componentName).keys()) {
1801      collection.add(key);
1802    }
1803  }
1804  if (localStoragePropCollection.get(componentName)) {
1805    for (const key of localStoragePropCollection.get(componentName).keys()) {
1806      collection.add(key);
1807    }
1808  }
1809}
1810
1811export function resetValidateUiSyntax(): void {
1812  observedClassCollection.clear();
1813  enumCollection.clear();
1814  classMethodCollection.clear();
1815  dollarCollection.clear();
1816  stateCollection.clear();
1817  regularCollection.clear();
1818  storagePropCollection.clear();
1819  storageLinkCollection.clear();
1820  provideCollection.clear();
1821  consumeCollection.clear();
1822  builderParamObjectCollection.clear();
1823  localStorageLinkCollection.clear();
1824  localStoragePropCollection.clear();
1825  isStaticViewCollection.clear();
1826  useOSFiles.clear();
1827  sourcemapNamesCollection.clear();
1828  originalImportNamesMap.clear();
1829}
1830