1/*
2 * Copyright (c) 2023 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 {
17  isClassDeclaration,
18  isEnumDeclaration,
19  isExportAssignment,
20  isExportDeclaration,
21  isFunctionDeclaration,
22  isImportDeclaration,
23  isInterfaceDeclaration,
24  isModuleDeclaration,
25  isTypeAliasDeclaration,
26  isVariableStatement,
27  SyntaxKind
28} from 'typescript';
29import type { Node, SourceFile, ClassDeclaration, FunctionDeclaration } from 'typescript';
30import { getClassDeclaration } from './classDeclaration';
31import type { ClassEntity } from './classDeclaration';
32import { getEnumDeclaration } from './enumDeclaration';
33import type { EnumEntity } from './enumDeclaration';
34import { getFunctionDeclaration } from './functionDeclaration';
35import type { FunctionEntity } from './functionDeclaration';
36import { getExportAssignment, getImportDeclaration } from './importAndExportDeclaration';
37import type { ImportElementEntity } from './importAndExportDeclaration';
38import { getInterfaceDeclaration } from './interfaceDeclaration';
39import type { InterfaceEntity } from './interfaceDeclaration';
40import type { StaticMethodEntity } from './methodDeclaration';
41import { getModuleDeclaration } from './moduleDeclaration';
42import type { ModuleBlockEntity } from './moduleDeclaration';
43import { getTypeAliasDeclaration } from './typeAliasDeclaration';
44import type { TypeAliasEntity } from './typeAliasDeclaration';
45import { getVariableStatementDeclaration } from './variableStatementResolve';
46import type { StatementEntity } from './variableStatementResolve';
47
48interface SubstepClassParams {
49  node: ClassDeclaration;
50  sourceFile: SourceFile;
51  classDeclarations: Array<ClassEntity>;
52  staticMethods: Array<Array<StaticMethodEntity>>;
53}
54
55interface SubstepFuntionParams {
56  node: FunctionDeclaration;
57  sourceFile: SourceFile;
58  functionDeclarations: Map<string, Array<FunctionEntity>>;
59}
60
61export interface SourceFileEntity {
62  importDeclarations: Array<ImportElementEntity>;
63  moduleDeclarations: Array<ModuleBlockEntity>;
64  typeAliasDeclarations: Array<TypeAliasEntity>;
65  classDeclarations: Array<ClassEntity>;
66  interfaceDeclarations: Array<InterfaceEntity>;
67  enumDeclarations: Array<EnumEntity>;
68  exportAssignment: Array<string>;
69  staticMethods: Array<Array<StaticMethodEntity>>;
70  exportDeclarations: Array<string>;
71  functionDeclarations: Map<string, Array<FunctionEntity>>;
72}
73
74/**
75 * assembly all sourceFile node info
76 * @param sourceFile
77 * @param fileName
78 * @returns
79 */
80export function getSourceFileAssembly(sourceFile: SourceFile, fileName: string): SourceFileEntity {
81  const importDeclarations: Array<ImportElementEntity> = [];
82  const moduleDeclarations: Array<ModuleBlockEntity> = [];
83  const typeAliasDeclarations: Array<TypeAliasEntity> = [];
84  let classDeclarations: Array<ClassEntity> = [];
85  const interfaceDeclarations: Array<InterfaceEntity> = [];
86  const enumDeclarations: Array<EnumEntity> = [];
87  let exportAssignment: Array<string> = [];
88  let staticMethods: Array<Array<StaticMethodEntity>> = [];
89  const exportDeclarations: Array<string> = [];
90  let functionDeclarations: Map<string, Array<FunctionEntity>> = new Map<string, Array<FunctionEntity>>();
91  sourceFile.forEachChild(node => {
92    if (isImportDeclaration(node)) {
93      importDeclarations.push(getImportDeclaration(node, sourceFile));
94    } else if (isModuleDeclaration(node)) {
95      moduleDeclarations.push(getModuleDeclaration(node, sourceFile, fileName));
96    } else if (isTypeAliasDeclaration(node)) {
97      typeAliasDeclarations.push(getTypeAliasDeclaration(node, sourceFile));
98    } else if (isClassDeclaration(node)) {
99      const substepClassBack = substepClass({ node, sourceFile, classDeclarations, staticMethods });
100      classDeclarations = substepClassBack.classDeclarations;
101      staticMethods = substepClassBack.staticMethods;
102    } else if (isInterfaceDeclaration(node)) {
103      interfaceDeclarations.push(getInterfaceDeclaration(node, sourceFile));
104    } else if (isExportAssignment(node)) {
105      exportAssignment = getExportAssignment(node, sourceFile);
106    } else if (isEnumDeclaration(node)) {
107      enumDeclarations.push(getEnumDeclaration(node, sourceFile));
108    } else if (isExportDeclaration(node)) {
109      exportDeclarations.push(sourceFile.text.substring(node.pos, node.end).trim());
110    } else if (isFunctionDeclaration(node)) {
111      const classParams = substepFunction({ node, sourceFile, functionDeclarations });
112      functionDeclarations = classParams.functionDeclarations;
113    } else {
114      substepConsole(node, fileName);
115    }
116  });
117  return {
118    importDeclarations,
119    moduleDeclarations,
120    typeAliasDeclarations,
121    classDeclarations,
122    interfaceDeclarations,
123    enumDeclarations,
124    exportAssignment,
125    staticMethods,
126    exportDeclarations,
127    functionDeclarations
128  };
129}
130
131/**
132 * get default export class
133 * @param sourceFile
134 * @returns
135 */
136export function getDefaultExportClassDeclaration(sourceFile: SourceFile): Array<ClassEntity> {
137  const defaultExportClass: Array<ClassEntity> = [];
138  sourceFile.forEachChild(node => {
139    if (isClassDeclaration(node)) {
140      defaultExportClass.push(getClassDeclaration(node, sourceFile));
141    }
142  });
143  return defaultExportClass;
144}
145
146/**
147 * get sourceFile const variable statement
148 * @param sourceFile
149 * @returns
150 */
151export function getSourceFileVariableStatements(sourceFile: SourceFile): Array<Array<StatementEntity>> {
152  const variableStatements: Array<Array<StatementEntity>> = [];
153  sourceFile.forEachChild(node => {
154    if (isVariableStatement(node)) {
155      variableStatements.push(getVariableStatementDeclaration(node, sourceFile));
156    }
157  });
158  return variableStatements;
159}
160
161/**
162 * get sourcefile functions
163 * @param sourceFile
164 * @returns
165 */
166export function getSourceFileFunctions(sourceFile: SourceFile): Map<string, Array<FunctionEntity>> {
167  const functionDeclarations: Map<string, Array<FunctionEntity>> = new Map<string, Array<FunctionEntity>>();
168  sourceFile.forEachChild(node => {
169    if (isFunctionDeclaration(node)) {
170      const functionEntity = getFunctionDeclaration(node, sourceFile);
171      if (functionDeclarations.get(functionEntity.functionName) !== undefined) {
172        functionDeclarations.get(functionEntity.functionName)?.push(functionEntity);
173      } else {
174        const functionArray: Array<FunctionEntity> = [];
175        functionArray.push(functionEntity);
176        functionDeclarations.set(functionEntity.functionName, functionArray);
177      }
178    }
179  });
180  return functionDeclarations;
181}
182
183/**
184 * assembly some sourceFile node info
185 * @param substepClassParams
186 * @returns
187 */
188function substepClass(substepClassParams: SubstepClassParams): SubstepClassParams {
189  let isDefaultExportClass = false;
190  if (substepClassParams.node.modifiers !== undefined) {
191    substepClassParams.node.modifiers.forEach(value => {
192      if (value.kind === SyntaxKind.DefaultKeyword) {
193        isDefaultExportClass = true;
194      }
195    });
196  }
197  if (isDefaultExportClass) {
198    const classDeclarationEntity = getClassDeclaration(substepClassParams.node, substepClassParams.sourceFile);
199    substepClassParams.classDeclarations.push(classDeclarationEntity);
200    if (classDeclarationEntity.staticMethods.length > 0) {
201      substepClassParams.staticMethods.push(classDeclarationEntity.staticMethods);
202    }
203  }
204  return substepClassParams;
205}
206
207/**
208 * assembly some sourceFile node info
209 * @param substepClassParams
210 * @returns
211 */
212function substepFunction(substepClassParams: SubstepFuntionParams): SubstepFuntionParams {
213  const functionEntity = getFunctionDeclaration(substepClassParams.node, substepClassParams.sourceFile);
214  if (substepClassParams.functionDeclarations.get(functionEntity.functionName) !== undefined) {
215    substepClassParams.functionDeclarations.get(functionEntity.functionName)?.push(functionEntity);
216  } else {
217    const functionArray: Array<FunctionEntity> = [];
218    functionArray.push(functionEntity);
219    substepClassParams.functionDeclarations.set(functionEntity.functionName, functionArray);
220  }
221  return substepClassParams;
222}
223
224/**
225 * assembly some sourceFile node info
226 * @param substepClassParams
227 * @returns
228 */
229function substepConsole(node: Node, fileName: string): void {
230  if (node.kind !== SyntaxKind.EndOfFileToken && !isFunctionDeclaration(node) && !isVariableStatement(node)) {
231    console.log('--------------------------- uncaught sourceFile type start -----------------------');
232    console.log('fileName: ' + fileName);
233    console.log(node);
234    console.log('--------------------------- uncaught sourceFile type end -----------------------');
235  }
236}
237