adding unified getDescriptor-Function, creating new Helper-Functions
This commit is contained in:
parent
123495b654
commit
7adb65fef2
@ -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 = {
|
||||
isBaseType: boolean;
|
||||
@ -7,7 +7,7 @@ export type TypeInformation = {
|
||||
simplifiedSubType?: string;
|
||||
typeImports?: {
|
||||
path: string;
|
||||
type: string;
|
||||
identifier: string;
|
||||
}[];
|
||||
originalCode: string;
|
||||
}
|
||||
@ -145,13 +145,14 @@ export function getImplementedInterface(cl: ClassDeclaration, aliasToOriginal: {
|
||||
* @param inputType The type
|
||||
* @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.
|
||||
let baseType = '';
|
||||
let simplifiedType = '';
|
||||
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.
|
||||
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.
|
||||
const externalTypes = /import\(.+?\)\.\w+/g;
|
||||
const result = externalTypes.exec(text);
|
||||
const result = [...text.matchAll(externalTypes)];
|
||||
|
||||
// Use the Regex to remove the Imports
|
||||
const regex = /import\(.+?\)./g
|
||||
simplifiedType = text.replace(regex, '');
|
||||
|
||||
// Only if the type isnt a function the result will be present:
|
||||
if (result) {
|
||||
|
||||
|
||||
if (result.length > 0) {
|
||||
|
||||
// Update the Imported Types.
|
||||
typeImports = result.map(item => {
|
||||
@ -184,27 +183,30 @@ export function getType(node: Node, inputType: Type, text: string) {
|
||||
const regex = /(?<=")(.*)(?=")/g;
|
||||
const path = regex.exec(text)[0].toString();
|
||||
// Get the corresponding Typ-Identifier
|
||||
const type = text.split(').')[1];
|
||||
const identifier = text.split(').')[1];
|
||||
|
||||
return {
|
||||
path,
|
||||
type
|
||||
identifier
|
||||
};
|
||||
});
|
||||
|
||||
} else if (Node.isFunctionDeclaration(node) || Node.isFunctionTypeNode(node) || Node.isFunctionLikeDeclaration(node)) {
|
||||
baseType = "function"
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
const ret: TypeInformation = {
|
||||
isBaseType: !!(inputType.compilerType as any).intrinsicName,
|
||||
baseType,
|
||||
originalCode: inputType.getText()
|
||||
originalCode
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!caseSensitive) {
|
||||
return reqType.toLowerCase() === getPropertyDescription(prop).baseType.toLowerCase();
|
||||
return reqType.toLowerCase() === (getDescription(prop) as PropertyInformation).baseType.toLowerCase();
|
||||
} else {
|
||||
return reqType === getPropertyDescription(prop).baseType;
|
||||
return reqType === (getDescription(prop) as PropertyInformation).baseType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate a Property description.
|
||||
* @param prop The Property Declaration.
|
||||
*/
|
||||
export function getPropertyDescription(prop: PropertyDeclaration): PropertyInformation {
|
||||
export function getDescription(declaration: PropertyDeclaration | InterfaceDeclaration | FunctionDeclaration | MethodDeclaration | ExportedDeclarations): PropertyInformation | MethodInformation | TypeInformation {
|
||||
|
||||
return Object.assign(
|
||||
// Use the Modifiers
|
||||
getModifiers(prop),
|
||||
Object.assign(
|
||||
// the Type Description
|
||||
getType(
|
||||
prop.getTypeNode(),
|
||||
prop.getType(),
|
||||
prop.getText()
|
||||
),
|
||||
{
|
||||
// And use the Propertiy Name.
|
||||
name: prop.getName(),
|
||||
// Keep the declartion object as well
|
||||
declaration: prop
|
||||
let typeInformation: TypeInformation;
|
||||
|
||||
if (Node.isPropertyDeclaration(declaration)) {
|
||||
typeInformation = _getType(
|
||||
declaration.getTypeNode(),
|
||||
declaration.getType(),
|
||||
declaration.getType().getText()
|
||||
);
|
||||
|
||||
return Object.assign(
|
||||
// Use the Modifiers
|
||||
getModifiers(declaration),
|
||||
// Provide the Type Information.
|
||||
Object.assign(
|
||||
// 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
|
||||
// Property Descriptor.
|
||||
.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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 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: {
|
||||
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
|
||||
const relevantClasses = file.getClasses().filter(cl => {
|
||||
return (
|
||||
classInterface === '' ||
|
||||
isClassImplementingInterface(cl, classInterface, importMapping.aliasToOriginal)
|
||||
options.classInterface === '' ||
|
||||
isClassImplementingInterface(cl, options.classInterface, importMapping.aliasToOriginal)
|
||||
) && (
|
||||
classDecorator === '' ||
|
||||
getDecorators(cl, classDecorator, importMapping.aliasToOriginal)
|
||||
options.classDecorator === '' ||
|
||||
getDecorators(cl, options.classDecorator, importMapping.aliasToOriginal)
|
||||
)
|
||||
});
|
||||
|
||||
@ -489,17 +540,22 @@ export function analyzeClasses(sources: SourceFile[], classDecorator: string, cl
|
||||
for (const relevantClass of relevantClasses) {
|
||||
// Extract the Methods.
|
||||
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);
|
||||
|
||||
// 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
|
||||
const relevantProperties = getMatchingProperties(relevantClass, propertyType, false)
|
||||
.filter(property =>
|
||||
property.isPublic &&
|
||||
getDecorators(property.declaration, propertyDecorator, importMapping.aliasToOriginal, false, false)
|
||||
const relevantProperties = getMatchingProperties(relevantClass, options.propertyType, false)
|
||||
.filter(property => property.isPublic &&
|
||||
getDecorators(
|
||||
property.declaration,
|
||||
options.propertyDecorator,
|
||||
importMapping.aliasToOriginal,
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
const item = {
|
||||
@ -513,4 +569,95 @@ export function analyzeClasses(sources: SourceFile[], classDecorator: string, cl
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
Loading…
Reference in New Issue
Block a user