creating functionality to write the results into files.

This commit is contained in:
Martin Karkowski 2020-08-24 09:37:48 +02:00
parent 2926dcf46a
commit 5418c837d3
2 changed files with 88 additions and 127 deletions

View File

@ -37,6 +37,7 @@ export type MethodInformation = {
isImplementation: boolean;
returnType: TypeInformation;
head: string;
authorDescription: string;
}
export type DecoratorInformation = {
@ -46,6 +47,25 @@ export type DecoratorInformation = {
decoratorSettings: { [index: string]: { [index: string]: any }}
}
/**
* Interface for the Analyzing Result
*/
export interface IAnalyzeResult {
// Name of the Class
className: string,
// Decorators of the Class
classDecorator: DecoratorInformation,
// Methods of the Class
methods: (MethodInformation & DecoratorInformation)[],
// Properties of the Class
properties: (PropertyInformation & DecoratorInformation)[],
// Imports of the Class (contians external Files)
imports: {
content: string,
required: boolean,
}
}
export type PropertyInformation = ModifierInformation & TypeInformation & { name: string; declaration: PropertyDeclaration; };
/**
@ -363,7 +383,12 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
// Use the Regex to remove the Imports from the Head
const regex = /import\(.+?\)./g
const head = declaration.getType().getText().replace(regex, '');
// Define the Head
let head = declaration.getType().getText().replace(regex, '');
head = head.slice(0, head.length - ('=> '+ returnType.simplifiedType).length)
// Extract the Description of the Author.
const authorDescription = declaration.getLeadingCommentRanges().map(comment => comment.getText()).join('\n');
return {
declaration,
@ -374,7 +399,8 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
isAsync,
isGenerator,
isImplementation,
returnType
returnType,
authorDescription
}
}
}
@ -717,25 +743,6 @@ export function getDeclarationByName(files: { [index: string]: SourceFile }, fil
throw Error('Declaration "' + filePath + '" not found');
}
/**
* Interface for the Analyzing Result
*/
export interface IAnalyzeResult {
// Name of the Class
className: string,
// Decorators of the Class
classDecorator: DecoratorInformation,
// Methods of the Class
methods: (MethodInformation & DecoratorInformation)[],
// Properties of the Class
properties: (PropertyInformation & DecoratorInformation)[],
// Imports of the Class (contians external Files)
imports: {
content: string,
required: boolean,
}
}
/**
* Function, that will create the imported Type classes based on source-Files.
* @param sourceFiles

View File

@ -1,120 +1,74 @@
import { SourceFile } from "ts-morph";
import { createFileMapping, analyzeClasses, getDeclarationByName } from "./analyzeTypescriptFiles";
import { Project } from "ts-morph";
import { analyzeFiles } from "./analyzeTypescriptFiles";
import * as handlebars from 'handlebars';
import { readFile, copyFile } from "fs/promises";
import { join } from 'path';
import { createFile, createPath } from "./fileHelpers";
/**
* Function, that will create the imported Type classes based on source-Files.
* @param sourceFiles
* @param options Options, to Controll the generation.
* Generate the Client Templates.
* @param options
*/
export function transformClasses(sourceFiles: SourceFile[], options: {
classDecorator: string,
classInterface: string,
methodDecorator: string,
propertyType: string,
propertyDecorator: string,
} = {
classDecorator: 'exportsElementsToDispatcher',
classInterface: '',
methodDecorator: 'exportMethodToDispatcher',
propertyType: 'nopeObservable',
propertyDecorator: 'exportPropertyToDispatcher'
}){
const fileMapping = createFileMapping(sourceFiles);
const classes = analyzeClasses(sourceFiles, options);
export async function generateClientTemplate(options: {
pathToTemplate: string,
outputDir: string,
inputDir: string,
tsConfigFilePath: string,
}) {
// Create the Output dir (if it doenst exists)
await createPath(options.outputDir);
// Firstly copy the nopeDispatcher
await copyFile(
join(__dirname,'..','..','..','lib','dispatcher','nopeDispatcher.ts'),
join(options.outputDir,'nopeDispatcher.ts')
);
// Iterate over the Classes
for (const relevantClass of classes){
// Function to Determine new project files.
const project = new Project({
tsConfigFilePath: options.tsConfigFilePath,
addFilesFromTsConfig: false,
});
const requiredImports = new Set<string>();
const mappingTypeToImport: {
[index: string]: Set<string>
} = {}
project.addSourceFilesAtPaths(options.inputDir);
// Iterate over the Properties
for (const prop of relevantClass.properties){
if (!prop.isBaseType){
for (const {identifier: type, path} of (prop.typeImports || [])){
// Readin the Source-Files.
const sourceFiles = project.getSourceFiles();
// Only if the import isnt the Base Type, add it to the List.
if (type !== options.propertyType) {
// Check if the Imported Type has been Adden multiple Times.
if (mappingTypeToImport[type] === undefined) {
mappingTypeToImport[type] = new Set<string>();
}
// Generate the Files
const result = analyzeFiles(sourceFiles);
mappingTypeToImport[type].add(path);
requiredImports.add(type);
}
}
}
// load the File.
const template = await readFile(options.pathToTemplate, {
encoding: 'utf-8'
})
// Renderfuncting
const render = handlebars.compile(template);
// Generate the Files.
const files: {
name: string,
content: string,
}[] = result.map(item => {
item.className = item.className + 'ClientInterface'
return {
name: item.className + '.ts',
content: render(item)
}
});
// Iterate over the Methods
for (const method of relevantClass.methods){
if (!method.returnType.isBaseType) {
for (const {identifier, path} of (method.returnType.typeImports || [])){
// Only if the import isnt the Base Type, add it to the List.
if (identifier !== options.propertyType) {
// Check if the Imported Type has been Adden multiple Times.
if (mappingTypeToImport[identifier] === undefined) {
mappingTypeToImport[identifier] = new Set<string>();
}
mappingTypeToImport[identifier].add(path);
requiredImports.add(identifier);
}
}
}
for (const parm of method.params){
if (!parm.isBaseType){
for (const {identifier, path} of (parm.typeImports || [])){
// Only if the import isnt the Base Type, add it to the List.
if (identifier !== options.propertyType) {
// Check if the Imported Type has been Adden multiple Times.
if (mappingTypeToImport[identifier] === undefined) {
mappingTypeToImport[identifier] = new Set<string>();
}
mappingTypeToImport[identifier].add(path);
requiredImports.add(identifier);
}
}
}
}
}
let fileWithTypesForClass = `// Automatic extracted Interfaces for the Class "` + relevantClass.className + `"\n// Generated on: ` + (new Date()).toISOString();
// List containing the already used Types
const importedBaseTypes = new Set<string>();
// Iterate over the Imports
for (const reqType of requiredImports) {
if (mappingTypeToImport[reqType] && mappingTypeToImport[reqType].size === 1) {
// Extract the Path of the File
const pathToFile = Array.from(mappingTypeToImport[reqType].values())[0] + '.ts';
const declarations = getDeclarationByName(fileMapping, pathToFile, reqType).filter(item => !importedBaseTypes.has(item.baseType));
declarations.map(item => importedBaseTypes.add(item.baseType));
fileWithTypesForClass += declarations.map(item =>
item.originalCode
).reduce(
(prev, current, idx) => prev + '\n\n' + current, ''
);
} else if (mappingTypeToImport[reqType].size > 1) {
// Multiple Items are using the Same Name.
}
}
console.log(fileWithTypesForClass)
for (const file of files){
await createFile(
// Generate the Path.
join(options.outputDir,'clients',file.name),
file.content
)
}
// Compile the Template and parse the Code.
return true;
}