• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 fs from 'fs';
17import path from 'path';
18import type { SourceFile } from 'typescript';
19import { SyntaxKind } from 'typescript';
20import type { InterfaceEntity } from '../declaration-node/interfaceDeclaration';
21import { generateCommonMethodSignature } from './generateCommonMethodSignature';
22import { generateIndexSignature } from './generateIndexSignature';
23import { generatePropertySignatureDeclaration } from './generatePropertySignatureDeclaration';
24import { dtsFileList, getApiInputPath, hasBeenImported, specialFiles } from '../common/commonUtils';
25import type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration';
26import type { PropertySignatureEntity } from '../declaration-node/propertySignatureDeclaration';
27
28/**
29 * generate interface
30 * @param interfaceEntity
31 * @param sourceFile
32 * @param isSourceFile
33 * @returns
34 */
35export function generateInterfaceDeclaration(
36  interfaceEntity: InterfaceEntity,
37  sourceFile: SourceFile,
38  isSourceFile: boolean,
39  mockApi: string,
40  currentSourceInterfaceArray: InterfaceEntity[],
41  importDeclarations?: ImportElementEntity[],
42  extraImport?: string[]
43): string {
44  const interfaceName = interfaceEntity.interfaceName;
45  let interfaceBody = '';
46  let interfaceElementSet = new Set<string>();
47  if (interfaceEntity.exportModifiers.length > 0 || isSourceFile) {
48    interfaceBody += `export const ${interfaceName} = { \n`;
49  } else {
50    interfaceBody += `const ${interfaceName} = { \n`;
51  }
52  if (interfaceEntity.interfacePropertySignatures.length > 0) {
53    const isAddExtraImportReturn = isNeedAddExtraImport(interfaceEntity, interfaceBody, interfaceName, mockApi, sourceFile,
54      interfaceElementSet, extraImport, importDeclarations);
55    interfaceElementSet = isAddExtraImportReturn.interfaceElementSet;
56    interfaceBody = isAddExtraImportReturn.interfaceBody;
57  }
58  if (interfaceEntity.interfaceMethodSignature.size > 0) {
59    interfaceEntity.interfaceMethodSignature.forEach(value => {
60      interfaceBody += generateCommonMethodSignature(interfaceName, value, sourceFile, mockApi) + '\n';
61      interfaceElementSet.add(value[0].functionName);
62    });
63  }
64  if (extraImport.length > 0) {
65    for (let i = 0; i < extraImport.length; i++) {
66      if (mockApi.includes(extraImport[i])) {
67        extraImport.splice(i, 1);
68      }
69    }
70  }
71  if (interfaceEntity.indexSignature.length > 0) {
72    interfaceEntity.indexSignature.forEach(value => {
73      interfaceBody += generateIndexSignature(value) + '\n';
74      interfaceElementSet.add(value.indexSignatureKey);
75    });
76  }
77  interfaceBody = assemblyInterface(interfaceEntity, currentSourceInterfaceArray, interfaceBody,
78    sourceFile, interfaceElementSet, mockApi, interfaceName);
79  return interfaceBody;
80}
81
82/**
83 * @param interfaceEntity
84 * @param interfaceBody
85 * @param interfaceName
86 * @param mockApi
87 * @param sourceFile
88 * @param interfaceElementSet
89 * @param extraImport
90 * @param importDeclarations
91 * @returns
92 */
93function isNeedAddExtraImport(
94  interfaceEntity: InterfaceEntity,
95  interfaceBody: string,
96  interfaceName: string,
97  mockApi: string,
98  sourceFile:SourceFile,
99  interfaceElementSet:Set<string>,
100  extraImport:string[],
101  importDeclarations:ImportElementEntity[]
102) : {interfaceBody: string, interfaceElementSet: Set<string>} {
103  interfaceEntity.interfacePropertySignatures.forEach(value => {
104    interfaceBody += generatePropertySignatureDeclaration(interfaceName, value, sourceFile, mockApi) + '\n';
105    interfaceElementSet.add(value.propertyName);
106    if (!value.propertyTypeName.includes(' ')) {
107      const regex = new RegExp(`import[\\s\n]*?{?[\\s\n]*?${value.propertyTypeName}[,\\s\n]*?`);
108      const results = mockApi.match(regex);
109      if (results) {
110        return;
111      }
112      let temp = false;
113      importDeclarations.forEach(element => {
114        if (
115          element.importPath.startsWith('\'@ohos') &&
116            element.importElements.match(new RegExp(`[\\s\n]*${value.propertyTypeName}[,\\s\n]*`))
117        ) {
118          temp = true;
119        }
120      });
121      if (temp) {
122        return;
123      }
124    }
125    addExtraImport(extraImport, importDeclarations, sourceFile, value);
126  });
127  return {
128    interfaceBody,
129    interfaceElementSet
130  };
131}
132
133function assemblyInterface(
134  interfaceEntity: InterfaceEntity,
135  currentSourceInterfaceArray: InterfaceEntity[],
136  interfaceBody: string,
137  sourceFile: SourceFile,
138  interfaceElementSet: Set<string>,
139  mockApi: string,
140  interfaceName: string
141) :string {
142  if (interfaceEntity.heritageClauses.length > 0) {
143    interfaceEntity.heritageClauses.forEach(value => {
144      currentSourceInterfaceArray.forEach(currentInterface => {
145        if (value.types.includes(currentInterface.interfaceName)) {
146          interfaceBody += generateHeritageInterface(currentInterface, sourceFile, interfaceElementSet, mockApi);
147        }
148      });
149    });
150  }
151  interfaceBody += '}\n';
152  if (interfaceEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword)) {
153    interfaceBody += `
154      if (!global.${interfaceName}) {
155        global.${interfaceName} = ${interfaceName};\n
156      }
157    `;
158  }
159  return interfaceBody;
160}
161
162function generateHeritageInterface(interfaceEntity: InterfaceEntity, sourceFile: SourceFile, elements: Set<string>, mockApi: string): string {
163  const interfaceName = interfaceEntity.interfaceName;
164  let interfaceBody = '';
165  if (interfaceEntity.interfacePropertySignatures.length > 0) {
166    interfaceEntity.interfacePropertySignatures.forEach(value => {
167      if (!elements.has(value.propertyName)) {
168        interfaceBody += generatePropertySignatureDeclaration(interfaceName, value, sourceFile, mockApi) + '\n';
169      }
170    });
171  }
172
173  if (interfaceEntity.interfaceMethodSignature.size > 0) {
174    interfaceEntity.interfaceMethodSignature.forEach(value => {
175      if (!elements.has(value[0].functionName)) {
176        interfaceBody += generateCommonMethodSignature(interfaceName, value, sourceFile, mockApi) + '\n';
177      }
178    });
179  }
180
181  if (interfaceEntity.indexSignature.length > 0) {
182    interfaceEntity.indexSignature.forEach(value => {
183      if (elements.has(value.indexSignatureKey)) {
184        interfaceBody += generateIndexSignature(value) + '\n';
185      }
186    });
187  }
188  return interfaceBody;
189}
190
191/**
192 * @param extraImport
193 * @param importDeclarations
194 * @param sourceFile
195 * @param value
196 * @returns
197 */
198export function addExtraImport(
199  extraImport: string[], importDeclarations: ImportElementEntity[], sourceFile: SourceFile, value: PropertySignatureEntity
200): void {
201  if (extraImport && importDeclarations) {
202    const propertyTypeName = value.propertyTypeName.split('.')[0].split('|')[0].split('&')[0].replace(/"'/g, '').trim();
203    if (propertyTypeName.includes('/')) {
204      return;
205    }
206    if (hasBeenImported(importDeclarations, propertyTypeName)) {
207      return;
208    }
209    const specialFilesList = [...specialFiles.map(specialFile => path.join(getApiInputPath(), ...specialFile.split('/')))];
210    if (!specialFilesList.includes(sourceFile.fileName)) {
211      specialFilesList.unshift(sourceFile.fileName);
212    }
213    searchHasExtraImport(specialFilesList, propertyTypeName, sourceFile, extraImport);
214  }
215}
216
217/**
218 * @param specialFilesList
219 * @param propertyTypeName
220 * @param sourceFile
221 * @param extraImport
222 * @returns
223 */
224function searchHasExtraImport(specialFilesList: string[], propertyTypeName: string, sourceFile: SourceFile, extraImport: string[]): void {
225  for (let i = 0; i < specialFilesList.length; i++) {
226    const specialFilePath = specialFilesList[i];
227    if (!fs.existsSync(specialFilePath)) {
228      continue;
229    }
230    let specialFileContent = fs.readFileSync(specialFilePath, 'utf-8');
231    const removeNoteRegx = /\/\*[\s\S]*?\*\//g;
232    specialFileContent = specialFileContent.replace(removeNoteRegx, '');
233    const regex = new RegExp(`\\s${propertyTypeName}\\s*(<|{|=|extends)`);
234    const results = specialFileContent.match(regex);
235    if (!results) {
236      continue;
237    }
238    if (sourceFile.fileName === specialFilePath) {
239      return;
240    }
241    let specialFileRelatePath = path.relative(path.dirname(sourceFile.fileName), path.dirname(specialFilePath));
242    if (!specialFileRelatePath.startsWith('./') && !specialFileRelatePath.startsWith('../')) {
243      specialFileRelatePath = './' + specialFileRelatePath;
244    }
245    if (!dtsFileList.includes(specialFilePath)) {
246      dtsFileList.push(specialFilePath);
247    }
248    specialFileRelatePath = specialFileRelatePath.split(path.sep).join('/');
249    const importStr = `import { ${propertyTypeName} } from '${
250      specialFileRelatePath}${
251      specialFileRelatePath.endsWith('/') ? '' : '/'}${
252      path.basename(specialFilePath).replace('.d.ts', '').replace('.d.ets', '')}'\n`;
253    if (extraImport.includes(importStr)) {
254      return;
255    }
256    extraImport.push(importStr);
257    return;
258  }
259  if (propertyTypeName.includes('<') || propertyTypeName.includes('[')) {
260    return;
261  }
262  console.log(sourceFile.fileName, 'propertyTypeName', propertyTypeName);
263  return;
264}
265