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