redefining filter functions

This commit is contained in:
Martin Karkowski 2020-08-24 13:35:12 +02:00
parent 3743aeacf6
commit 0665ebd155

View File

@ -1,4 +1,4 @@
import { ClassDeclaration, Decorator, MethodDeclaration, Node, Project, PropertyDeclaration, SourceFile, Type, InterfaceDeclaration, FunctionDeclaration, ExportedDeclarations } from "ts-morph";
import { ClassDeclaration, Decorator, ExportedDeclarations, FunctionDeclaration, InterfaceDeclaration, MethodDeclaration, Node, PropertyDeclaration, SourceFile, Type } from "ts-morph";
export type TypeInformation = {
isBaseType: boolean;
@ -25,6 +25,8 @@ export type ParameterInformation = ({
name: string;
originalCode: string;
index: number;
authorDescription: string;
isOptional: boolean;
} & TypeInformation);
export type MethodInformation = {
@ -39,12 +41,24 @@ export type MethodInformation = {
head: string;
authorDescription: string;
}
export type DecoratorInformation = {
declaration: MethodDeclaration | PropertyDeclaration | ClassDeclaration,
decoratorNames: string[],
decorators: Decorator[],
decoratorSettings: { [index: string]: { [index: string]: any }}
decoratorSettings: { [index: string]: { [index: string]: any } }
}
export type ImportMapping = {
mapping: {
[index: string]: {
importSrc: string;
alias?: string;
};
};
aliasToOriginal: {
[index: string]: string;
};
}
/**
@ -203,7 +217,7 @@ function _getType(node: Node, inputType: Type, text: string) {
if (result.length > 0) {
// Update the Imported Types.
typeImports.push(... result.map(item => {
typeImports.push(...result.map(item => {
const text = item.toString();
// Regex, to extract the path of the Import.
@ -234,7 +248,7 @@ function _getType(node: Node, inputType: Type, text: string) {
typeImports.push(...defintion.typeImports)
}
})
}
}
// Define the a Simplified Subtype.
simplifiedSubType = simplifiedType
@ -350,18 +364,27 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
//
} 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()
)
);
// Use the Regex to remove the Imports from the Head
const regex = /import\(.+?\)./g
// 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');
// Flag if the Function is Performed Async. (this is achieved by retruning a promise or adding an async tag in the beginning)
const isAsync = declaration.isAsync() || returnType.baseType == 'Promise';
// Extract the Parameters of the Function
const params: ParameterInformation[] = declaration.getParameters().map((parameter, index) => {
return Object.assign(
@ -372,6 +395,10 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
originalCode: parameter.getText(),
// The Index of the Parameter
index,
// Extract the Information provided for the Parameter (If available.)
authorDescription: (parameter.getLeadingCommentRanges() || parameter.getTrailingCommentRanges()).map(comment => comment.getText()).join('\n'),
// Extract whether the Parameters is Optional or not.
isOptional: parameter.isOptional()
},
_getType(
parameter.getTypeNode(),
@ -381,15 +408,6 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
)
});
// Use the Regex to remove the Imports from the Head
const regex = /import\(.+?\)./g
// 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,
params,
@ -411,13 +429,8 @@ export function getDescription(declaration: PropertyDeclaration | InterfaceDecla
* @param reqType The requested Type of the Element
* @param caseSensitive A Flage to use casesesitivy during the checkoup
*/
export function getMatchingProperties(cl: ClassDeclaration, reqType: string, caseSensitive = true): PropertyInformation[] {
export function getMatchingProperties(cl: ClassDeclaration): PropertyInformation[] {
return cl.getProperties()
// Firstly Filter the Properties, that they match the requested Type.
.filter(prop => isPropOfType(
prop,
reqType,
caseSensitive))
// Instead of returning the Property Declaration, return the
// Property Descriptor.
.map(
@ -440,7 +453,7 @@ export function getDecorators(declaration: MethodDeclaration | PropertyDeclarati
declaration,
decoratorNames: [],
decoratorSettings: {},
decorators:[]
decorators: []
}
if (!caseSensitive) {
@ -451,7 +464,7 @@ export function getDecorators(declaration: MethodDeclaration | PropertyDeclarati
.filter(usedDecorator => {
// Get the Name of the Decorator
let nameOfDecorator = usedDecorator.getName();
// Let CaseSensitive or not
if (!caseSensitive) {
nameOfDecorator = nameOfDecorator.toLowerCase();
@ -465,7 +478,7 @@ export function getDecorators(declaration: MethodDeclaration | PropertyDeclarati
_arguments.map(a => {
// Parse the Text
const text = a.getText();
// Bad Practice. Create the Code create Function that will create the Object.
try {
ret.decoratorSettings[nameOfDecorator] = eval('() => { return ' + text + '}')();
@ -557,6 +570,49 @@ export function getModifiers(declaration: MethodDeclaration | PropertyDeclaratio
return ret;
}
/**
* Method to create a Default Filter for Methods.
* @param options
*/
export function defaultClassFilter(options: {
classDecorator: string,
classInterface: string,
}) {
return (cl: ClassDeclaration, importMapping: ImportMapping) => {
return (
options.classInterface === '' ||
isClassImplementingInterface(cl, options.classInterface, importMapping.aliasToOriginal)
) && (
options.classDecorator === '' ||
getDecorators(cl, options.classDecorator, importMapping.aliasToOriginal)
) as boolean
}
}
/**
* Default Method for filtering Methods.
*/
export function defaultMethodFilter() {
return (cl: ClassDeclaration, method: (MethodInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => {
return method.isPublic && method.isAsync;
}
}
/**
* Default Filter, to Filter Properties
* @param options The Options to use.
*/
export function defaultPropFilter(options: {
propertyType: string,
propertyDecorator: string,
}) {
return (cl: ClassDeclaration, property: (PropertyInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => {
return isPropOfType(property.declaration, options.propertyType, false) &&
property.isPublic &&
property.decoratorNames.includes(options.propertyDecorator)
}
}
/**
* Helper Function to List relevant classes with their corresponding elements
* @param sources The Source Files
@ -567,18 +623,19 @@ export function getModifiers(declaration: MethodDeclaration | PropertyDeclaratio
* @param propertyDecorator The Decorator for the Property
*/
export function analyzeClasses(sources: SourceFile[], options: {
filterClasses: (cl: ClassDeclaration, importMapping: ImportMapping) => boolean,
filterMethods: (cl: ClassDeclaration, method: (MethodInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => boolean,
filterProperties: (cl: ClassDeclaration, property: (PropertyInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => boolean,
classDecorator: string,
classInterface: string,
methodDecorator: string,
propertyType: string,
propertyDecorator: string,
}) {
const ret: {
className: string;
decorator: DecoratorInformation,
methods: (MethodInformation & DecoratorInformation)[];
properties: (PropertyInformation & DecoratorInformation) [];
methods: (MethodInformation & DecoratorInformation & ModifierInformation)[];
properties: (PropertyInformation & DecoratorInformation & ModifierInformation)[];
}[] = [];
// Iterate over the Files:
@ -588,15 +645,7 @@ export function analyzeClasses(sources: SourceFile[], options: {
const importMapping = getImportsOfFile(file);
// After all Imports has been detected => filter for all Classes that implement the provided classDecorator
const relevantClasses = file.getClasses().filter(cl => {
return (
options.classInterface === '' ||
isClassImplementingInterface(cl, options.classInterface, importMapping.aliasToOriginal)
) && (
options.classDecorator === '' ||
getDecorators(cl, options.classDecorator, importMapping.aliasToOriginal)
)
});
const relevantClasses = file.getClasses().filter(cl => options.filterClasses(cl, importMapping));
// Now after each class is known => ierate over the relevant classes
// and get their relevant Methods and Attributes.
@ -604,35 +653,42 @@ export function analyzeClasses(sources: SourceFile[], options: {
// Extract the Methods.
const sharedMethodsOfClass = relevantClass.getMethods()
.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);
// Parsed Method
const parsedMethods = sharedMethodsOfClass.map(methodObject => Object.assign(
getDescription(methodObject.declaration as MethodDeclaration) as MethodInformation,
getDecorators(
methodObject.declaration,
options.methodDecorator,
importMapping.aliasToOriginal
)
));
const parsedMethods = sharedMethodsOfClass
.map(methodObject =>
Object.assign(
getDescription(methodObject.declaration as MethodDeclaration) as MethodInformation,
getDecorators(
methodObject.declaration,
options.methodDecorator,
importMapping.aliasToOriginal
),
getModifiers(methodObject.declaration as MethodDeclaration)
)
).filter(item => options.filterMethods(
relevantClass,
item,
importMapping
));
// Get the Properties
const relevantProperties = getMatchingProperties(relevantClass, options.propertyType, false)
.map(p => Object.assign(
p,
getDecorators(
p.declaration,
options.propertyDecorator,
importMapping.aliasToOriginal,
false,
true
))
)
.filter(
property =>
property.isPublic &&
property.decoratorNames.includes(options.propertyDecorator)
const relevantProperties = getMatchingProperties(relevantClass)
.map(property =>
Object.assign(
property,
getDecorators(
property.declaration,
options.propertyDecorator,
importMapping.aliasToOriginal,
false,
true
),
getModifiers(property.declaration)
)
).filter(property => options.filterProperties(relevantClass, property, importMapping)
)
const item = {
decorator: getDecorators(
@ -749,25 +805,35 @@ export function getDeclarationByName(files: { [index: string]: SourceFile }, fil
* @param options Options, to Controll the generation.
*/
export function analyzeFiles(sourceFiles: SourceFile[], options: {
classDecorator: string,
classInterface: string,
methodDecorator: string,
propertyType: string,
filterClasses: (cl: ClassDeclaration, importMapping: ImportMapping) => boolean,
filterMethods: (cl: ClassDeclaration, method: (MethodInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => boolean,
filterProperties: (cl: ClassDeclaration, property: (PropertyInformation & DecoratorInformation & ModifierInformation), importMapping: ImportMapping) => boolean,
checkImport: (type: string) => boolean,
classDecorator: string,
methodDecorator: string,
propertyDecorator: string,
} = {
classDecorator: 'exportsElementsToDispatcher',
classInterface: '',
methodDecorator: 'exportMethodToDispatcher',
propertyType: 'nopeObservable',
propertyDecorator: 'exportPropertyToDispatcher'
}){
classDecorator: 'exportsElementsToDispatcher',
methodDecorator: 'exportMethodToDispatcher',
propertyDecorator: 'exportPropertyToDispatcher',
filterClasses: defaultClassFilter({
classDecorator: 'exportsElementsToDispatcher',
classInterface: ''
}),
filterMethods: defaultMethodFilter(),
filterProperties: defaultPropFilter({
propertyDecorator: 'exportPropertyToDispatcher',
propertyType: 'nopeObservable'
}),
checkImport: type => type !== 'nopeObservable'
}) {
const fileMapping = createFileMapping(sourceFiles);
const classes = analyzeClasses(sourceFiles, options);
const ret: IAnalyzeResult[] = []
const ret: IAnalyzeResult[] = []
// Iterate over the Classes
for (const relevantClass of classes){
for (const relevantClass of classes) {
const item: IAnalyzeResult = {
className: relevantClass.className,
@ -786,19 +852,19 @@ export function analyzeFiles(sourceFiles: SourceFile[], options: {
} = {}
// Iterate over the Properties
for (const prop of relevantClass.properties){
if (!prop.isBaseType){
for (const {identifier: type, path} of (prop.typeImports || [])){
for (const prop of relevantClass.properties) {
if (!prop.isBaseType) {
for (const { identifier, path } of (prop.typeImports || [])) {
// Only if the import isnt the Base Type, add it to the List.
if (type !== options.propertyType) {
if (options.checkImport(identifier)) {
// Check if the Imported Type has been Adden multiple Times.
if (mappingTypeToImport[type] === undefined) {
mappingTypeToImport[type] = new Set<string>();
if (mappingTypeToImport[identifier] === undefined) {
mappingTypeToImport[identifier] = new Set<string>();
}
mappingTypeToImport[type].add(path);
requiredImports.add(type);
mappingTypeToImport[identifier].add(path);
requiredImports.add(identifier);
}
}
}
@ -807,12 +873,12 @@ export function analyzeFiles(sourceFiles: SourceFile[], options: {
}
// Iterate over the Methods
for (const method of relevantClass.methods){
for (const method of relevantClass.methods) {
if (!method.returnType.isBaseType) {
for (const {identifier, path} of (method.returnType.typeImports || [])){
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) {
if (options.checkImport(identifier)) {
// Check if the Imported Type has been Adden multiple Times.
if (mappingTypeToImport[identifier] === undefined) {
mappingTypeToImport[identifier] = new Set<string>();
@ -827,17 +893,17 @@ export function analyzeFiles(sourceFiles: SourceFile[], options: {
// Iterate over the Parameters and extract
// the required elements. (If they arent base
// types).
for (const parm of method.params){
if (!parm.isBaseType){
for (const {identifier, path} of (parm.typeImports || [])){
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) {
if (options.checkImport(identifier)) {
// 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);
}
@ -866,7 +932,7 @@ export function analyzeFiles(sourceFiles: SourceFile[], options: {
item.imports.content += declarations.map(item =>
item.originalCode
).reduce(
(prev, current, idx) => item.imports.content.length === 0 && idx === 0? current : prev + '\n\n' + current, ''
(prev, current, idx) => item.imports.content.length === 0 && idx === 0 ? current : prev + '\n\n' + current, ''
);
} else if (mappingTypeToImport[reqType].size > 1) {