diff --git a/automock/mock-generate/src/declaration-node/sourceFileElementsAssemply.ts b/automock/mock-generate/src/declaration-node/sourceFileElementsAssemply.ts index aeb2ce0..30568e5 100644 --- a/automock/mock-generate/src/declaration-node/sourceFileElementsAssemply.ts +++ b/automock/mock-generate/src/declaration-node/sourceFileElementsAssemply.ts @@ -53,6 +53,7 @@ export function getSourceFileAssembly(sourceFile: SourceFile, fileName: string): let exportAssignment: Array = []; const staticMethods: Array> = []; const exportDeclarations: Array = []; + const functionDeclarations: Array = []; sourceFile.forEachChild(node => { if (isImportDeclaration(node)) { @@ -85,7 +86,10 @@ export function getSourceFileAssembly(sourceFile: SourceFile, fileName: string): enumDeclarations.push(getEnumDeclaration(node, sourceFile)); } else if (isExportDeclaration(node)) { exportDeclarations.push(sourceFile.text.substring(node.pos, node.end).trimStart().trimEnd()); - } else { + } else if (isFunctionDeclaration(node)){ + functionDeclarations.push(getFunctionDeclaration(node, sourceFile)); + } + else { if (node.kind !== SyntaxKind.EndOfFileToken && !isFunctionDeclaration(node) && !isVariableStatement(node)) { console.log('--------------------------- uncaught sourceFile type start -----------------------'); console.log('fileName: ' + fileName); @@ -104,7 +108,8 @@ export function getSourceFileAssembly(sourceFile: SourceFile, fileName: string): enumDeclarations: enumDeclarations, exportAssignment: exportAssignment, staticMethods: staticMethods, - exportDeclarations: exportDeclarations + exportDeclarations: exportDeclarations, + functionDeclarations: functionDeclarations, }; } @@ -169,5 +174,6 @@ export interface SourceFileEntity { enumDeclarations: Array, exportAssignment: Array, staticMethods: Array>, - exportDeclarations: Array + exportDeclarations: Array, + functionDeclarations: Array } diff --git a/automock/mock-generate/src/generate/generateCommonUtil.ts b/automock/mock-generate/src/generate/generateCommonUtil.ts index b65bd3c..85d34b0 100644 --- a/automock/mock-generate/src/generate/generateCommonUtil.ts +++ b/automock/mock-generate/src/generate/generateCommonUtil.ts @@ -261,7 +261,7 @@ function getImportTypeAliasNameFromImportElements(importElementEntity: ImportEle if (typeName === 'Want') { typeName = 'mockWant().Want'; } else if (typeName === 'InputMethodExtensionContext') { - typeName = 'mockInputmethodextensioncontext().InputMethodExtensionContext'; + typeName = 'mockInputMethodExtensionContext().InputMethodExtensionContext'; } return typeName; } diff --git a/automock/mock-generate/src/generate/generateExportFunction.ts b/automock/mock-generate/src/generate/generateExportFunction.ts new file mode 100644 index 0000000..1975bd8 --- /dev/null +++ b/automock/mock-generate/src/generate/generateExportFunction.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { SourceFile } from 'typescript'; +import { SyntaxKind } from 'typescript'; +import type { FunctionEntity } from '../declaration-node/functionDeclaration'; +import { getCallbackStatement, getReturnStatement } from './generateCommonUtil'; + +/** + * generate function + * @param rootName + * @param functionArray + * @param sourceFile + * @param mockApi + * @returns + */ +export function generateExportFunction(functionEntity: FunctionEntity, sourceFile: SourceFile, mockApi: string): string { + let functionBody = ''; + functionBody = `global.${functionEntity.functionName} = function (...args) {`; + functionBody += `console.warn('The ${functionEntity.functionName} interface in the Previewer is a mocked implementation and may behave differently than on a real device.');\n`; + + const args = functionEntity.args; + const len = args.length; + if (args.length > 0 && args[len - 1].paramName.toLowerCase().includes('callback')) { + functionBody += getCallbackStatement(mockApi); + } + if (functionEntity.returnType.returnKind !== SyntaxKind.VoidKeyword) { + functionBody += getReturnStatement(functionEntity.returnType, sourceFile); + } + functionBody += '}'; + return functionBody; +} diff --git a/automock/mock-generate/src/generate/generateMockJsFile.ts b/automock/mock-generate/src/generate/generateMockJsFile.ts index eb57d50..c6d7a32 100644 --- a/automock/mock-generate/src/generate/generateMockJsFile.ts +++ b/automock/mock-generate/src/generate/generateMockJsFile.ts @@ -29,6 +29,7 @@ import { generateModuleDeclaration } from './generateModuleDeclaration'; import { generateStaticFunction } from './generateStaticFunction'; import { addToSystemIndexArray } from './generateSystemIndex'; import { generateTypeAliasDeclaration } from './generateTypeAlias'; +import { generateExportFunction } from './generateExportFunction'; /** * generate mock file string @@ -54,7 +55,7 @@ export function generateSourceFileElements(rootName: string, sourceFileEntity: S if (sourceFileEntity.moduleDeclarations.length > 0) { sourceFileEntity.moduleDeclarations.forEach(value => { - mockApi += generateModuleDeclaration('', value, sourceFile, fileName, mockApi) + '\n'; + mockApi += generateModuleDeclaration('', value, sourceFile, fileName, mockApi, extraImport) + '\n'; }); } @@ -84,11 +85,17 @@ export function generateSourceFileElements(rootName: string, sourceFileEntity: S if (sourceFileEntity.typeAliasDeclarations.length > 0) { sourceFileEntity.typeAliasDeclarations.forEach(value => { - mockApi += generateTypeAliasDeclaration(value, false) + '\n'; + mockApi += generateTypeAliasDeclaration(value, false, sourceFile, extraImport) + '\n'; mockFunctionElements.push({ elementName: value.typeAliasName, type: 'typeAlias' }); }); } + if (sourceFileEntity.functionDeclarations.length > 0) { + sourceFileEntity.functionDeclarations.forEach(value => { + mockApi += generateExportFunction(value, sourceFile, mockApi) + '\n'; + }); + } + if (sourceFileEntity.moduleDeclarations.length === 0 && (fileName.startsWith('ohos_') || fileName.startsWith('system_') || fileName.startsWith('webgl'))) { const mockNameArr = fileName.split('_'); const mockName = mockNameArr[mockNameArr.length - 1]; @@ -157,6 +164,9 @@ export function generateSourceFileElements(rootName: string, sourceFileEntity: S * generate import definition * @param importEntity * @param sourceFileName + * @param heritageClausesArray + * @param currentFilePath + * @param dependsSourceFileList * @returns */ export function generateImportDeclaration( diff --git a/automock/mock-generate/src/generate/generateModuleDeclaration.ts b/automock/mock-generate/src/generate/generateModuleDeclaration.ts index a61de49..4967044 100644 --- a/automock/mock-generate/src/generate/generateModuleDeclaration.ts +++ b/automock/mock-generate/src/generate/generateModuleDeclaration.ts @@ -39,9 +39,11 @@ import { generateVariableStatementDelcatation } from './generateVariableStatemen * @param moduleEntity * @param sourceFile * @param filename + * @param extraImport * @returns */ -export function generateModuleDeclaration(rootName: string, moduleEntity: ModuleBlockEntity, sourceFile: SourceFile, filename: string, mockApi: string): string { +export function generateModuleDeclaration(rootName: string, moduleEntity: ModuleBlockEntity, sourceFile: SourceFile, + filename: string, mockApi: string, extraImport: string[]): string { let moduleName = moduleEntity.moduleName.replace(/["']/g, ''); let moduleBody = `export function mock${firstCharacterToUppercase(moduleName)}() {\n`; if (!(moduleEntity.exportModifiers.includes(SyntaxKind.DeclareKeyword) && @@ -82,7 +84,7 @@ export function generateModuleDeclaration(rootName: string, moduleEntity: Module if (moduleEntity.typeAliasDeclarations.length > 0) { moduleEntity.typeAliasDeclarations.forEach(value => { - outBody += generateTypeAliasDeclaration(value, true) + '\n'; + outBody += generateTypeAliasDeclaration(value, true, sourceFile, extraImport) + '\n'; }); } @@ -178,7 +180,6 @@ export function generateModuleDeclaration(rootName: string, moduleEntity: Module /** * generate inner module for declare module * @param moduleEntity - * @param sourceFile * @returns */ function generateInnerDeclareModule(moduleEntity: ModuleBlockEntity): string { @@ -196,9 +197,10 @@ function generateInnerDeclareModule(moduleEntity: ModuleBlockEntity): string { * generate inner module * @param moduleEntity * @param sourceFile + * @param extraImport * @returns */ -function generateInnerModule(moduleEntity: ModuleBlockEntity, sourceFile: SourceFile): string { +function generateInnerModule(moduleEntity: ModuleBlockEntity, sourceFile: SourceFile, extraImport: string[]): string { const moduleName = moduleEntity.moduleName; let innerModuleBody = `const ${moduleName} = (()=> {`; @@ -210,7 +212,7 @@ function generateInnerModule(moduleEntity: ModuleBlockEntity, sourceFile: Source if (moduleEntity.typeAliasDeclarations.length > 0) { moduleEntity.typeAliasDeclarations.forEach(value => { - innerModuleBody += generateTypeAliasDeclaration(value, true) + '\n'; + innerModuleBody += generateTypeAliasDeclaration(value, true, sourceFile, extraImport) + '\n'; }); } diff --git a/automock/mock-generate/src/generate/generateTypeAlias.ts b/automock/mock-generate/src/generate/generateTypeAlias.ts index e8f5562..132a922 100644 --- a/automock/mock-generate/src/generate/generateTypeAlias.ts +++ b/automock/mock-generate/src/generate/generateTypeAlias.ts @@ -13,22 +13,131 @@ * limitations under the License. */ -import type { TypeAliasEntity } from '../declaration-node/typeAliasDeclaration'; +import type { TypeAliasEntity, TypeAliasTypeEntity } from '../declaration-node/typeAliasDeclaration'; +import { firstCharacterToUppercase, getOhosInterfacesDir } from '../common/commonUtils'; +import path from 'path'; +import fs from 'fs'; +import type { SourceFile } from 'typescript'; /** * generate type alias * @param typeAliasEntity * @param isInner + * @param sourceFile + * @param extraImport * @returns */ -export function generateTypeAliasDeclaration(typeAliasEntity: TypeAliasEntity, isInner: boolean): string { +export function generateTypeAliasDeclaration( + typeAliasEntity: TypeAliasEntity, isInner: boolean, sourceFile: SourceFile, extraImport: string[] +): string { let typeAliasName = ''; if (!isInner) { typeAliasName += `export const ${typeAliasEntity.typeAliasName} = `; } else { typeAliasName += `const ${typeAliasEntity.typeAliasName} = `; } + let typeAliasValue = ''; - typeAliasValue += `'[PC Preview] unknown ${typeAliasEntity.typeAliasName}'`; + + const typeAliasTypeElements = typeAliasEntity.typeAliasTypeElements; + + if (typeAliasTypeElements) { + typeAliasValue += parseImportExpression(typeAliasTypeElements, sourceFile, extraImport); + } + + if (!typeAliasValue) { + typeAliasValue += `'[PC Preview] unknown ${typeAliasEntity.typeAliasName}'`; + } return typeAliasName + typeAliasValue + ';'; } + +function getImportFileFullPath(typeName: string): string { + const importRelatePathTmp = typeName.match(/\('[^'()]+'\)/); + if (!importRelatePathTmp) { + return ''; + } + const importRelatePath = importRelatePathTmp[0].substring(2, importRelatePathTmp[0].length - 2); + const tmpRealPath = getOhosInterfacesDir() + importRelatePath.replace('../api', '').replace(/\//g, path.sep); + if (fs.existsSync(tmpRealPath + '.d.ts')) { + return tmpRealPath + '.d.ts'; + } + + if (fs.existsSync(tmpRealPath + '.d.ets')) { + return tmpRealPath + '.d.ets'; + } + console.warn(`Can not find import \'${importRelatePath}\'`); + return ''; +} + +function pathToImportPath(currentFilePath: string, importFilePath: string): string { + const currentFilePathSteps = currentFilePath.replace(/.d.e?ts/, '').split('/'); + const importFilePathSteps = importFilePath.replace(/.d.e?ts/, '').split(path.sep); + const importFilePathStepsLength = importFilePathSteps.length; + importFilePathSteps[importFilePathStepsLength - 1] = importFilePathSteps[importFilePathStepsLength - 1] + .replace('@', '').replace(/\./g, '_'); + let differStepIndex: number; + for (differStepIndex = 0; differStepIndex < currentFilePathSteps.length; differStepIndex++) { + if (currentFilePathSteps[differStepIndex] !== importFilePathSteps[differStepIndex]) { + break; + } + } + const currentFileDifferPathSteps = currentFilePathSteps.slice(differStepIndex); + const importFileDifferPathSteps = importFilePathSteps.slice(differStepIndex); + if (currentFileDifferPathSteps.length === importFileDifferPathSteps.length + && currentFileDifferPathSteps.length === 1) { + return `./${path.basename(importFilePath)}`; + } else { + const steps = []; + for (let i = 0; i < currentFileDifferPathSteps.length - 1; i++) { + steps.push('..'); + } + const fullSteps = steps.concat(importFileDifferPathSteps); + return fullSteps.join('/'); + } +} + +function parseImportExpression( + typeAliasTypeElements: TypeAliasTypeEntity[], sourceFile: SourceFile, extraImport: string[] +): string { + for (let i = 0; i < typeAliasTypeElements.length; i++) { + const typeAliasTypeElement = typeAliasTypeElements[i]; + const typeName = typeAliasTypeElement.typeName; + if (!typeName?.trim().startsWith('import(')) { + continue; + } + const splitTypeName = typeName.split(')'); + const propertiesIndex = 1; + const properties = splitTypeName[propertiesIndex]; + const importPath = getImportFileFullPath(typeName); + const realImportPath = pathToImportPath(sourceFile.fileName, importPath); + if (!importPath) { + continue; + } + const importFileContent = fs.readFileSync(importPath, 'utf-8'); + if (properties.startsWith('.default')) { + let result = importFileContent.match(/export\sdefault\sclass\s[a-zA-Z]+/); + if (result) { + const defaultModuleName = '_' + result[0].replace(/export\sdefault\sclass\s/, ''); + const importStr = `import ${defaultModuleName} from '${realImportPath}';\n`; + !extraImport.includes(importStr) && extraImport.push(importStr); + return `${defaultModuleName}${properties.replace('.default', '')}`; + } + result = importFileContent.match(/export\sdefault\s[a-zA-Z]+;/); + if (result) { + const moduleName = result[0] + .replace(/export\sdefault\s/, '') + .replace(';', ''); + const mockFunctionName = `mock${firstCharacterToUppercase(moduleName)}`; + const importStr = `import {${mockFunctionName}} from '${realImportPath}';\n`; + !extraImport.includes(importStr) && extraImport.push(importStr); + return `${mockFunctionName}()${properties.replace('.default', '')}`; + } + } else { + const moduleName = properties.replace('.', '').split('.')[0]; + const importStr = `import {${moduleName} as _${moduleName}} from '${realImportPath}';\n`; + !extraImport.includes(importStr) && extraImport.push(importStr); + return `_${properties.replace('.', '')}`; + } + } + return ''; +}