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 type { TypeAliasEntity, TypeAliasTypeEntity } from '../declaration-node/typeAliasDeclaration'; 17import { firstCharacterToUppercase, getOhosInterfacesDir } from '../common/commonUtils'; 18import path from 'path'; 19import fs from 'fs'; 20import type { SourceFile } from 'typescript'; 21 22interface SubstepParseProps { 23 importFileContent: string, 24 realImportPath: string, 25 extraImport: string[], 26 properties: string 27} 28 29const interceptIndex = 2; 30 31/** 32 * generate type alias 33 * @param typeAliasEntity 34 * @param isInner 35 * @param sourceFile 36 * @param extraImport 37 * @returns 38 */ 39export function generateTypeAliasDeclaration( 40 typeAliasEntity: TypeAliasEntity, isInner: boolean, sourceFile: SourceFile, extraImport: string[], mockApi: string 41): string { 42 let typeAliasName = ''; 43 if (!isInner) { 44 typeAliasName += `export const ${typeAliasEntity.typeAliasName} = `; 45 } else { 46 typeAliasName += `const ${typeAliasEntity.typeAliasName} = `; 47 } 48 49 let typeAliasValue = ''; 50 51 const typeAliasTypeElements = typeAliasEntity.typeAliasTypeElements; 52 if (sourceFile.fileName.endsWith('@ohos.abilityAccessCtrl.d.ts') && typeAliasEntity.typeAliasName === 'PermissionRequestResult') { 53 return typeAliasName + 'new _PermissionRequestResult();'; 54 } 55 56 if (typeAliasTypeElements) { 57 typeAliasValue += parseImportExpression(typeAliasTypeElements, sourceFile, extraImport, mockApi); 58 } 59 60 if (!typeAliasValue) { 61 typeAliasValue += `'[PC Preview] unknown ${typeAliasEntity.typeAliasName}'`; 62 } 63 return typeAliasName + typeAliasValue + ';'; 64} 65 66function getImportFileFullPath(typeName: string): string { 67 const importRelatePathTmp = typeName.match(/\('[^'()]+'\)/); 68 if (!importRelatePathTmp) { 69 return ''; 70 } 71 const importRelatePath = importRelatePathTmp[0].substring(interceptIndex, importRelatePathTmp[0].length - interceptIndex); 72 const tmpRealPath = getOhosInterfacesDir() + importRelatePath.replace('../api', '').replace(/\//g, path.sep); 73 if (fs.existsSync(tmpRealPath + '.d.ts')) { 74 return tmpRealPath + '.d.ts'; 75 } 76 77 if (fs.existsSync(tmpRealPath + '.d.ets')) { 78 return tmpRealPath + '.d.ets'; 79 } 80 console.warn(`Can not find import \'${importRelatePath}\'`); 81 return ''; 82} 83 84function pathToImportPath(currentFilePath: string, importFilePath: string): string { 85 const currentFilePathSteps = currentFilePath.replace(/.d.e?ts/, '').split('/'); 86 const importFilePathSteps = importFilePath.replace(/.d.e?ts/, '').split(path.sep); 87 const importFilePathStepsLength = importFilePathSteps.length; 88 importFilePathSteps[importFilePathStepsLength - 1] = importFilePathSteps[importFilePathStepsLength - 1] 89 .replace('@', '').replace(/\./g, '_'); 90 let differStepIndex: number; 91 for (differStepIndex = 0; differStepIndex < currentFilePathSteps.length; differStepIndex++) { 92 if (currentFilePathSteps[differStepIndex] !== importFilePathSteps[differStepIndex]) { 93 break; 94 } 95 } 96 const currentFileDifferPathSteps = currentFilePathSteps.slice(differStepIndex); 97 const importFileDifferPathSteps = importFilePathSteps.slice(differStepIndex); 98 if (currentFileDifferPathSteps.length === importFileDifferPathSteps.length && currentFileDifferPathSteps.length === 1) { 99 return `./${path.basename(importFilePath)}`; 100 } else { 101 const steps = []; 102 for (let i = 0; i < currentFileDifferPathSteps.length - 1; i++) { 103 steps.push('..'); 104 } 105 const fullSteps = steps.concat(importFileDifferPathSteps); 106 return fullSteps.join('/'); 107 } 108} 109 110function parseImportExpression( 111 typeAliasTypeElements: TypeAliasTypeEntity[], sourceFile: SourceFile, extraImport: string[], mockApi: string 112): string { 113 for (let i = 0; i < typeAliasTypeElements.length; i++) { 114 const typeAliasTypeElement = typeAliasTypeElements[i]; 115 const typeName = typeAliasTypeElement.typeName; 116 if (!typeName) { 117 continue; 118 } 119 if (!typeName?.trim().startsWith('import(')) { 120 let name = typeName.trim(); 121 if (name.includes('.')) { 122 name = name.trim().split('.')[0]; 123 } 124 if (mockApi.includes(`import { ${name} `) || mockApi.includes(` as ${name.trim()} `) || 125 mockApi.includes(`import ${name} `)) { 126 return typeName.trim(); 127 } 128 continue; 129 } 130 const splitTypeName = typeName.split(')'); 131 const propertiesIndex = 1; 132 let properties = splitTypeName[propertiesIndex]; 133 if (properties.includes('<') && properties.includes('>')) { 134 properties = properties.replace(/<.*?>/sg, ''); 135 } 136 const importPath = getImportFileFullPath(typeName); 137 const realImportPath = pathToImportPath(sourceFile.fileName, importPath); 138 if (!importPath) { 139 continue; 140 } 141 const importFileContent = fs.readFileSync(importPath, 'utf-8'); 142 if (properties.startsWith('.default')) { 143 const result = substepParseImportExpression({importFileContent, realImportPath, extraImport, properties}); 144 if (result !== '') { 145 return result; 146 } 147 } else { 148 const moduleName = properties.replace('.', '').split('.')[0]; 149 const importStr = `import {${moduleName} as _${moduleName}} from '${realImportPath}';\n`; 150 !extraImport.includes(importStr) && extraImport.push(importStr); 151 return `_${properties.replace('.', '')}`; 152 } 153 } 154 return ''; 155} 156 157/** 158 * generate type alias 159 * @param props 160 * @returns 161 */ 162function substepParseImportExpression(props:SubstepParseProps): string { 163 let result = props.importFileContent.match(/export\sdefault\sclass\s[a-zA-Z]+/); 164 if (result) { 165 const defaultModuleName = '_' + result[0].replace(/export\sdefault\sclass\s/, ''); 166 const importStr = `import ${defaultModuleName} from '${props.realImportPath}';\n`; 167 !props.extraImport.includes(importStr) && props.extraImport.push(importStr); 168 return `${defaultModuleName}${props.properties.replace('.default', '')}`; 169 } 170 result = props.importFileContent.match(/export\sdefault\s[a-zA-Z]+;/); 171 if (result) { 172 const moduleName = result[0].replace(/export\sdefault\s/, '').replace(';', ''); 173 const mockFunctionName = `mock${firstCharacterToUppercase(moduleName)}`; 174 const importStr = `import {${mockFunctionName}} from '${props.realImportPath}';\n`; 175 !props.extraImport.includes(importStr) && props.extraImport.push(importStr); 176 return `${mockFunctionName}()${props.properties.replace('.default', '')}`; 177 } 178 return ''; 179} 180 181