adding unified getDescriptor-Function, creating new Helper-Functions

This commit is contained in:
Martin Karkowski 2020-08-23 09:22:49 +02:00
parent 123495b654
commit 7adb65fef2

View File

@ -1,4 +1,4 @@
import { ClassDeclaration, Decorator, MethodDeclaration, Node, Project, PropertyDeclaration, SourceFile, Type } from "ts-morph"; import { ClassDeclaration, Decorator, MethodDeclaration, Node, Project, PropertyDeclaration, SourceFile, Type, InterfaceDeclaration, FunctionDeclaration, ExportedDeclarations } from "ts-morph";
export type TypeInformation = { export type TypeInformation = {
isBaseType: boolean; isBaseType: boolean;
@ -7,7 +7,7 @@ export type TypeInformation = {
simplifiedSubType?: string; simplifiedSubType?: string;
typeImports?: { typeImports?: {
path: string; path: string;
type: string; identifier: string;
}[]; }[];
originalCode: string; originalCode: string;
} }
@ -145,13 +145,14 @@ export function getImplementedInterface(cl: ClassDeclaration, aliasToOriginal: {
* @param inputType The type * @param inputType The type
* @param text The textual representation. * @param text The textual representation.
*/ */
export function getType(node: Node, inputType: Type, text: string) { function _getType(node: Node, inputType: Type, text: string) {
// Define return Properties. // Define return Properties.
let baseType = ''; let baseType = '';
let simplifiedType = ''; let simplifiedType = '';
let simplifiedSubType = ''; let simplifiedSubType = '';
let typeImports: { path: string, type: string }[] = []; let typeImports: { path: string, identifier: string }[] = [];
let originalCode = inputType.getText();
// Test if the Type is a Base-Type. // Test if the Type is a Base-Type.
if ((inputType.compilerType as any).intrinsicName) { if ((inputType.compilerType as any).intrinsicName) {
@ -165,16 +166,14 @@ export function getType(node: Node, inputType: Type, text: string) {
// Regex to extrat the Imports with the corresponding type. // Regex to extrat the Imports with the corresponding type.
const externalTypes = /import\(.+?\)\.\w+/g; const externalTypes = /import\(.+?\)\.\w+/g;
const result = externalTypes.exec(text); const result = [...text.matchAll(externalTypes)];
// Use the Regex to remove the Imports // Use the Regex to remove the Imports
const regex = /import\(.+?\)./g const regex = /import\(.+?\)./g
simplifiedType = text.replace(regex, ''); simplifiedType = text.replace(regex, '');
// Only if the type isnt a function the result will be present: // Only if the type isnt a function the result will be present:
if (result) { if (result.length > 0) {
// Update the Imported Types. // Update the Imported Types.
typeImports = result.map(item => { typeImports = result.map(item => {
@ -184,27 +183,30 @@ export function getType(node: Node, inputType: Type, text: string) {
const regex = /(?<=")(.*)(?=")/g; const regex = /(?<=")(.*)(?=")/g;
const path = regex.exec(text)[0].toString(); const path = regex.exec(text)[0].toString();
// Get the corresponding Typ-Identifier // Get the corresponding Typ-Identifier
const type = text.split(').')[1]; const identifier = text.split(').')[1];
return { return {
path, path,
type identifier
}; };
}); });
} else if (Node.isFunctionDeclaration(node) || Node.isFunctionTypeNode(node) || Node.isFunctionLikeDeclaration(node)) { } else if (Node.isFunctionDeclaration(node) || Node.isFunctionTypeNode(node) || Node.isFunctionLikeDeclaration(node)) {
baseType = "function" baseType = "function"
} }
// Define the a Simplified Subtype. // Define the a Simplified Subtype.
simplifiedSubType = simplifiedType.slice(baseType.length + '<'.length, simplifiedType.length - 1); simplifiedSubType = simplifiedType
if (simplifiedType.includes(baseType + '<') && simplifiedType[simplifiedType.length - 1] === '>') {
simplifiedSubType = simplifiedType.slice(baseType.length + '<'.length, simplifiedType.length - 1);
}
} }
// Define a Partial element of the Return-Value. // Define a Partial element of the Return-Value.
const ret: TypeInformation = { const ret: TypeInformation = {
isBaseType: !!(inputType.compilerType as any).intrinsicName, isBaseType: !!(inputType.compilerType as any).intrinsicName,
baseType, baseType,
originalCode: inputType.getText() originalCode
} }
// Add the Additional elements if required. // Add the Additional elements if required.
@ -227,36 +229,130 @@ export function getType(node: Node, inputType: Type, text: string) {
*/ */
export function isPropOfType(prop: PropertyDeclaration, reqType: string, caseSensitive = true) { export function isPropOfType(prop: PropertyDeclaration, reqType: string, caseSensitive = true) {
if (!caseSensitive) { if (!caseSensitive) {
return reqType.toLowerCase() === getPropertyDescription(prop).baseType.toLowerCase(); return reqType.toLowerCase() === (getDescription(prop) as PropertyInformation).baseType.toLowerCase();
} else { } else {
return reqType === getPropertyDescription(prop).baseType; return reqType === (getDescription(prop) as PropertyInformation).baseType;
} }
} }
/** export function getDescription(declaration: PropertyDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration | ExportedDeclarations): PropertyInformation | MethodInformation | TypeInformation {
* Function to generate a Property description.
* @param prop The Property Declaration.
*/
export function getPropertyDescription(prop: PropertyDeclaration): PropertyInformation {
return Object.assign( let typeInformation: TypeInformation;
// Use the Modifiers
getModifiers(prop), if (Node.isPropertyDeclaration(declaration)) {
Object.assign( typeInformation = _getType(
// the Type Description declaration.getTypeNode(),
getType( declaration.getType(),
prop.getTypeNode(), declaration.getType().getText()
prop.getType(), );
prop.getText()
), return Object.assign(
{ // Use the Modifiers
// And use the Propertiy Name. getModifiers(declaration),
name: prop.getName(), // Provide the Type Information.
// Keep the declartion object as well Object.assign(
declaration: prop // the Type Description
typeInformation,
{
// And use the Propertiy Name.
name: declaration.getName(),
// Keep the declartion object as well
declaration
}
)
);
} else if (Node.isInterfaceDeclaration(declaration)) {
const typeImports: {
path: string,
identifier: string
}[] = [];
declaration.getProperties().map(p => {
let text = p.getType().getText();
const externalTypesRegex = /import\(.+?\)\.\w+/g;
const externalTypes = [...text.matchAll(externalTypesRegex)];
for (const externalType of externalTypes) {
const section = externalType.toString();
const regexType = /import\(.+?\)./g;
const identifier = section.replace(regexType, '');
const regexPath = /(?<=import\(").+?(?="\).)/g;
const path = [...section.matchAll(regexPath)][0].toString();
typeImports.push({
path,
identifier
})
} }
});
// // Define a Partial element of the Return-Value.
const ret: TypeInformation = {
isBaseType: false,
baseType: declaration.getName(),
originalCode: declaration.getText(),
typeImports,
}
// // Add the Additional elements if required.
// if (!ret.isBaseType) {
// ret.simplifiedType = simplifiedType;
// ret.simplifiedSubType = simplifiedSubType;
// ret.typeImports = typeImports;
// }
// // Return the Type.
return ret;
} else if (Node.isFunctionDeclaration(declaration)) {
} else if (Node.isMethodDeclaration(declaration)) {
const isAbstract = declaration.isAbstract();
const isAsync = declaration.isAsync();
const name = declaration.getName();
const isGenerator = declaration.isGenerator();
const isImplementation = declaration.isImplementation();
const retTypeObj = declaration.getReturnType();
const returnType = _getType(
declaration.getReturnTypeNode(),
retTypeObj,
retTypeObj.getText()
) )
); // Extract the Parameters of the Function
const params: ParameterInformation[] = declaration.getParameters().map((parameter, index) => {
return Object.assign(
{
// Name of the parameter
name: parameter.getName(),
// The Originale Code
originalCode: parameter.getText(),
// The Index of the Parameter
index,
},
_getType(
parameter.getTypeNode(),
parameter.getType(),
parameter.getText()
)
)
});
return {
declaration,
params,
isAbstract,
name,
isAsync,
isGenerator,
isImplementation,
returnType
}
}
} }
/** /**
@ -275,7 +371,7 @@ export function getMatchingProperties(cl: ClassDeclaration, reqType: string, cas
// Instead of returning the Property Declaration, return the // Instead of returning the Property Declaration, return the
// Property Descriptor. // Property Descriptor.
.map( .map(
propertyDeclaration => getPropertyDescription(propertyDeclaration) propertyDeclaration => getDescription(propertyDeclaration) as PropertyInformation
); );
} }
@ -351,57 +447,6 @@ export function isPropertyInjectedWith(prop: PropertyDeclaration, decorator: str
return decorators.decorators.length > 0; return decorators.decorators.length > 0;
} }
/**
* Helper Function to extract the Information of a Method.
* @param declaration Declartion of the Method
*/
export function getMethodInfo(declaration: MethodDeclaration): MethodInformation {
const isAbstract = declaration.isAbstract();
const isAsync = declaration.isAsync();
const name = declaration.getName();
const isGenerator = declaration.isGenerator();
const isImplementation = declaration.isImplementation();
const retTypeObj = declaration.getReturnType();
const returnType = getType(
declaration.getReturnTypeNode(),
retTypeObj,
retTypeObj.getText()
)
// Extract the Parameters of the Function
const params: ParameterInformation[] = declaration.getParameters().map((parameter, index) => {
return Object.assign(
{
// Name of the parameter
name: parameter.getName(),
// The Originale Code
originalCode: parameter.getText(),
// The Index of the Parameter
index,
},
getType(
parameter.getTypeNode(),
parameter.getType(),
parameter.getText()
)
)
});
return {
declaration,
params,
isAbstract,
name,
isAsync,
isGenerator,
isImplementation,
returnType
}
}
/** /**
* Function to extrac the Modifiers of a declaration. * Function to extrac the Modifiers of a declaration.
* @param declaration The Declartion of the Class * @param declaration The Declartion of the Class
@ -459,7 +504,13 @@ export function getModifiers(declaration: MethodDeclaration | PropertyDeclaratio
* @param propertyType The requrired Type for the Property * @param propertyType The requrired Type for the Property
* @param propertyDecorator The Decorator for the Property * @param propertyDecorator The Decorator for the Property
*/ */
export function analyzeClasses(sources: SourceFile[], classDecorator: string, classInterface: string, methodDecorator: string, propertyType: string, propertyDecorator: string,) { export function analyzeClasses(sources: SourceFile[], options: {
classDecorator: string,
classInterface: string,
methodDecorator: string,
propertyType: string,
propertyDecorator: string,
}) {
const ret: { const ret: {
className: string; className: string;
@ -476,11 +527,11 @@ export function analyzeClasses(sources: SourceFile[], classDecorator: string, cl
// After all Imports has been detected => filter for all Classes that implement the provided classDecorator // After all Imports has been detected => filter for all Classes that implement the provided classDecorator
const relevantClasses = file.getClasses().filter(cl => { const relevantClasses = file.getClasses().filter(cl => {
return ( return (
classInterface === '' || options.classInterface === '' ||
isClassImplementingInterface(cl, classInterface, importMapping.aliasToOriginal) isClassImplementingInterface(cl, options.classInterface, importMapping.aliasToOriginal)
) && ( ) && (
classDecorator === '' || options.classDecorator === '' ||
getDecorators(cl, classDecorator, importMapping.aliasToOriginal) getDecorators(cl, options.classDecorator, importMapping.aliasToOriginal)
) )
}); });
@ -489,17 +540,22 @@ export function analyzeClasses(sources: SourceFile[], classDecorator: string, cl
for (const relevantClass of relevantClasses) { for (const relevantClass of relevantClasses) {
// Extract the Methods. // Extract the Methods.
const sharedMethodsOfClass = relevantClass.getMethods() const sharedMethodsOfClass = relevantClass.getMethods()
.map(method => getDecorators(method, methodDecorator, importMapping.aliasToOriginal)) .map(method => getDecorators(method, options.methodDecorator, importMapping.aliasToOriginal))
.filter(methodObject => methodObject.decorators.length > 0 && getModifiers(methodObject.declaration as MethodDeclaration).isPublic); .filter(methodObject => methodObject.decorators.length > 0 && getModifiers(methodObject.declaration as MethodDeclaration).isPublic);
// Parsed Method // Parsed Method
const parsedMethods = sharedMethodsOfClass.map(methodObject => getMethodInfo(methodObject.declaration as MethodDeclaration)); const parsedMethods = sharedMethodsOfClass.map(methodObject => getDescription(methodObject.declaration as MethodDeclaration) as MethodInformation);
// Get the Properties // Get the Properties
const relevantProperties = getMatchingProperties(relevantClass, propertyType, false) const relevantProperties = getMatchingProperties(relevantClass, options.propertyType, false)
.filter(property => .filter(property => property.isPublic &&
property.isPublic && getDecorators(
getDecorators(property.declaration, propertyDecorator, importMapping.aliasToOriginal, false, false) property.declaration,
options.propertyDecorator,
importMapping.aliasToOriginal,
false,
false
)
); );
const item = { const item = {
@ -513,4 +569,95 @@ export function analyzeClasses(sources: SourceFile[], classDecorator: string, cl
} }
return ret; return ret;
}
/**
* Create a Mapping of Files. for simpler access.
* @param files
*/
export function createFileMapping(files: SourceFile[]) {
// Define the Return type.
const ret: {
[index: string]: SourceFile
} = {};
for (const file of files) {
ret[file.getFilePath()] = file;
}
return ret;
}
/**
* Function to extrac the Declaration by the Name of the Element
* @param files A File Mapping
* @param filePath The Relevant File
* @param identifier The Identifier of the Element, on which the Description should be extracted
* @param types Internal Object, used for recursion.
*/
export function getDeclarationByName(files: { [index: string]: SourceFile }, filePath: string, identifier: string, types: { [index: string]: TypeInformation } = {}) {
// Make shur the File is named correctly.
if (!filePath.endsWith('.ts')) {
filePath = filePath + '.ts';
}
// Iterate over all files, to finde the correct one.
const file = files[filePath];
if (file) {
// Get all Declarations
const declarations = file.getExportedDeclarations();
// Get the Imports of the File.
const importMapping = getImportsOfFile(file);
// If the idenfifiert is know, go on otherwise throw an Error.
if (declarations.has(identifier)) {
const exportedDeclarations = declarations.get(identifier);
const typesToTest = exportedDeclarations.map(e => getDescription(e) as PropertyInformation | TypeInformation);
const recursiveTest = new Array<{
path: string, identifier: string
}>();
// Iterate as long, as there are elements to test
while (typesToTest.length > 0) {
// Get the Type.
const type = typesToTest.pop();
// Flag, indicating, whether the element has been imported or not
const isImported = importMapping.aliasToOriginal[type.baseType] !== undefined || importMapping.mapping[type.baseType] !== undefined;
// Flag, showing whether the element imports other Items or not.
const hasImport = type.typeImports && type.typeImports.length > 0;
// If the Element isnt imported => simply add the item Type
if (!isImported && types[type.originalCode] === undefined) {
types[type.originalCode] = type;
} else if (isImported && types[type.originalCode] === undefined) {
recursiveTest.push({
path: importMapping.mapping[type.baseType].importSrc,
identifier: type.baseType
})
}
if (hasImport) {
type.typeImports.map(({ path, identifier }) => {
// Push the Elements to the Reverse Test.
recursiveTest.push({
path,
identifier
})
});
}
}
// Call this Function recursively.
for (const rec of recursiveTest) {
getDeclarationByName(files, rec.path, rec.identifier, types)
}
return Object.getOwnPropertyNames(types).map(key => types[key]);
}
}
throw Error('Declaration "' + filePath + '" not found');
} }