import { InjectableMetadata, PluginType } from '@allianz/taly-core/schemas';
import { Logger } from '@angular-devkit/core/src/logger';
import { posix } from 'path';
import ts from 'typescript';
import { IntrospectionContext } from '../../../shared/introspection/types';
import { ensurePosixPathSeparators } from '../../../shared/metadata-utils/windows-compat';
import { findImplementedInterface } from './find-implemented-interface';
import { findInjectableClassFactory } from './find-injectable-class';
export interface injectableFileInspectionResult {
injectableClassData: InjectableMetadata;
pluginType: PluginType;
}
export const inspectInjectableClassFactory =
(introspectionContext: IntrospectionContext, projectPath: string) =>
(fileName: string): injectableFileInspectionResult | undefined => {
const { injectableClassNode, injectableClassSymbol } =
findInjectableClassFactory(introspectionContext)(fileName);
if (!injectableClassNode || !injectableClassSymbol) {
return undefined;
}
const pluginType = extractPluginType(introspectionContext.checker, injectableClassNode);
let pluginValidatorData = {};
if (pluginType === 'Validator') {
pluginValidatorData = extractPluginValidatorData(
fileName,
injectableClassNode,
introspectionContext.logger
);
}
return {
injectableClassData: {
file: posix.relative(
ensurePosixPathSeparators(projectPath),
ensurePosixPathSeparators(fileName)
),
className: injectableClassSymbol.getName(),
...pluginValidatorData
},
pluginType
};
};
function extractPluginType(checker: ts.TypeChecker, classNode: ts.ClassDeclaration) {
const baseInterfaceNode = findImplementedInterface(classNode);
if (!baseInterfaceNode) {
return PluginType.Other;
}
const baseInterfaceName = checker.getSymbolAtLocation(baseInterfaceNode.expression)?.getName();
if (baseInterfaceName === 'PluginValidator' || baseInterfaceName === 'AsyncPluginValidator') {
return PluginType.Validator;
} else if (baseInterfaceName === PluginType.HTTPInterceptor) {
return PluginType.HTTPInterceptor;
}
return PluginType.Other;
}
function extractPluginValidatorData(
fileName: string,
classNode: ts.ClassDeclaration,
logger: Logger
) {
let validatorTypeField;
let validationParam: ts.ParameterDeclaration | undefined;
let validationParamType;
classNode.members?.forEach((classElement: ts.ClassElement) => {
if (ts.isPropertyDeclaration(classElement) && classElement.name.getText() === 'type') {
const init = classElement.initializer as ts.Node;
if (ts.isStringLiteral(init)) {
validatorTypeField = init.text;
}
}
if (ts.isMethodDeclaration(classElement)) {
const validationMethods = ['validate', 'validateAsync'];
if (validationMethods.includes(classElement.name.getText())) {
validationParam = classElement.parameters[0];
}
}
});
if (!validatorTypeField) {
logger.error(`Error: "type" field is missing on validator implementation`);
}
if (validationParam) {
validationParamType = validationParam.type?.getText();
if (!validationParamType) {
logger.error(`Error: no type specified for validator method parameter`);
}
}
return {
validation: {
validatorTypeField,
validationParam: validationParamType
}
};
}