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 // Find out whether the value.propertyTypeName was introduced through import. 108 const regex = new RegExp(`import[\\s\n]*?{?[\\s\n]*?${value.propertyTypeName}[,\\s\n]*?`); 109 const results = mockApi.match(regex); 110 if (results) { 111 return; 112 } 113 let temp = false; 114 importDeclarations.forEach(element => { 115 // Determine whether the external variable introduced by import contains value.propertyTypeName. 116 if ( 117 element.importPath.startsWith('\'@ohos') && 118 element.importElements.match(new RegExp(`[\\s\n]*${value.propertyTypeName}[,\\s\n]*`)) 119 ) { 120 temp = true; 121 } 122 }); 123 if (temp) { 124 return; 125 } 126 } 127 addExtraImport(extraImport, importDeclarations, sourceFile, value); 128 }); 129 return { 130 interfaceBody, 131 interfaceElementSet 132 }; 133} 134 135function assemblyInterface( 136 interfaceEntity: InterfaceEntity, 137 currentSourceInterfaceArray: InterfaceEntity[], 138 interfaceBody: string, 139 sourceFile: SourceFile, 140 interfaceElementSet: Set<string>, 141 mockApi: string, 142 interfaceName: string 143): string { 144 if (interfaceEntity.heritageClauses.length > 0) { 145 interfaceEntity.heritageClauses.forEach(value => { 146 currentSourceInterfaceArray.forEach(currentInterface => { 147 if (value.types.includes(currentInterface.interfaceName)) { 148 interfaceBody += generateHeritageInterface(currentInterface, sourceFile, interfaceElementSet, mockApi); 149 } 150 }); 151 }); 152 } 153 interfaceBody += '}\n'; 154 if (interfaceEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword)) { 155 interfaceBody += ` 156 if (!global.${interfaceName}) { 157 global.${interfaceName} = ${interfaceName};\n 158 } 159 `; 160 } 161 return interfaceBody; 162} 163 164function generateHeritageInterface( 165 interfaceEntity: InterfaceEntity, 166 sourceFile: SourceFile, 167 elements: Set<string>, 168 mockApi: string 169): string { 170 const interfaceName = interfaceEntity.interfaceName; 171 let interfaceBody = ''; 172 if (interfaceEntity.interfacePropertySignatures.length > 0) { 173 interfaceEntity.interfacePropertySignatures.forEach(value => { 174 if (!elements.has(value.propertyName)) { 175 interfaceBody += generatePropertySignatureDeclaration(interfaceName, value, sourceFile, mockApi) + '\n'; 176 } 177 }); 178 } 179 180 if (interfaceEntity.interfaceMethodSignature.size > 0) { 181 interfaceEntity.interfaceMethodSignature.forEach(value => { 182 if (!elements.has(value[0].functionName)) { 183 interfaceBody += generateCommonMethodSignature(interfaceName, value, sourceFile, mockApi) + '\n'; 184 } 185 }); 186 } 187 188 if (interfaceEntity.indexSignature.length > 0) { 189 interfaceEntity.indexSignature.forEach(value => { 190 if (elements.has(value.indexSignatureKey)) { 191 interfaceBody += generateIndexSignature(value) + '\n'; 192 } 193 }); 194 } 195 return interfaceBody; 196} 197 198/** 199 * @param extraImport 200 * @param importDeclarations 201 * @param sourceFile 202 * @param value 203 * @returns 204 */ 205export function addExtraImport( 206 extraImport: string[], 207 importDeclarations: ImportElementEntity[], 208 sourceFile: SourceFile, 209 value: PropertySignatureEntity 210): void { 211 if (extraImport && importDeclarations) { 212 const propertyTypeName = value.propertyTypeName.split('.')[0].split('|')[0].split('&')[0].replace(/"'/g, '').trim(); 213 if (propertyTypeName.includes('/')) { 214 return; 215 } 216 if (hasBeenImported(importDeclarations, propertyTypeName)) { 217 return; 218 } 219 const specialFilesList = [ 220 ...specialFiles.map(specialFile => path.join(getApiInputPath(), ...specialFile.split('/'))) 221 ]; 222 if (!specialFilesList.includes(sourceFile.fileName)) { 223 specialFilesList.unshift(sourceFile.fileName); 224 } 225 searchHasExtraImport(specialFilesList, propertyTypeName, sourceFile, extraImport); 226 } 227} 228 229/** 230 * @param specialFilesList 231 * @param propertyTypeName 232 * @param sourceFile 233 * @param extraImport 234 * @returns 235 */ 236function searchHasExtraImport( 237 specialFilesList: string[], 238 propertyTypeName: string, 239 sourceFile: SourceFile, 240 extraImport: string[] 241): void { 242 for (let i = 0; i < specialFilesList.length; i++) { 243 const specialFilePath = specialFilesList[i]; 244 if (!fs.existsSync(specialFilePath)) { 245 continue; 246 } 247 let specialFileContent = fs.readFileSync(specialFilePath, 'utf-8'); 248 const removeNoteRegx = /\/\*[\s\S]*?\*\//g; 249 specialFileContent = specialFileContent.replace(removeNoteRegx, ''); 250 const regex = new RegExp(`\\s${propertyTypeName}\\s*(<|{|=|extends)`); 251 const results = specialFileContent.match(regex); 252 if (!results) { 253 continue; 254 } 255 if (sourceFile.fileName === specialFilePath) { 256 return; 257 } 258 let specialFileRelatePath = path.relative(path.dirname(sourceFile.fileName), path.dirname(specialFilePath)); 259 if (!specialFileRelatePath.startsWith('./') && !specialFileRelatePath.startsWith('../')) { 260 specialFileRelatePath = './' + specialFileRelatePath; 261 } 262 if (!dtsFileList.includes(specialFilePath)) { 263 dtsFileList.push(specialFilePath); 264 } 265 specialFileRelatePath = specialFileRelatePath.split(path.sep).join('/'); 266 const importStr = `import { ${propertyTypeName} } from '${specialFileRelatePath}${ 267 specialFileRelatePath.endsWith('/') ? '' : '/' 268 }${path.basename(specialFilePath).replace('.d.ts', '').replace('.d.ets', '')}'\n`; 269 if (extraImport.includes(importStr)) { 270 return; 271 } 272 extraImport.push(importStr); 273 return; 274 } 275 if (propertyTypeName.includes('<') || propertyTypeName.includes('[')) { 276 return; 277 } 278 console.log(sourceFile.fileName, 'propertyTypeName', propertyTypeName); 279 return; 280} 281