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 = {
|
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');
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user