gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
592 lines (591 loc) • 24.6 kB
JavaScript
import { relationshipOptions, validations } from "../built-in-options/index.js";
import deduplicate from "../utils/array-utils.js";
import logger from "../utils/objects/logger.js";
const { BUILT_IN_ENTITY } = relationshipOptions;
const { Validations: { PATTERN, REQUIRED, UNIQUE }, } = validations;
export const buildJDLAstBuilderVisitor = (runtime) => {
const BaseJDLCSTVisitor = runtime.parser.getBaseCstVisitorConstructor();
class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
constructor() {
super();
this.validateVisitor();
}
prog(context) {
const ast = {
applications: [],
deployments: [],
constants: {},
entities: [],
relationships: [],
enums: [],
options: {},
useOptions: [],
};
if (context.constantDeclaration) {
const constants = context.constantDeclaration.map(this.visit, this);
constants.forEach(currConst => {
ast.constants[currConst.name] = currConst.value;
});
}
if (context.applicationDeclaration) {
ast.applications = context.applicationDeclaration.map(this.visit, this);
}
if (context.deploymentDeclaration) {
ast.deployments = context.deploymentDeclaration.map(this.visit, this);
}
if (context.entityDeclaration) {
ast.entities = context.entityDeclaration.map(this.visit, this);
}
if (context.relationDeclaration) {
ast.relationships = context.relationDeclaration.flatMap(this.visit, this);
}
if (context.enumDeclaration) {
ast.enums = context.enumDeclaration.map(this.visit, this);
}
if (context.unaryOptionDeclaration) {
context.unaryOptionDeclaration.map(this.visit, this).forEach((option) => {
if (!ast.options[option.optionName]) {
ast.options[option.optionName] = {};
}
const astResult = ast.options[option.optionName];
const { entityList, excludedEntityList } = getOptionEntityAndExcludedEntityLists(astResult, option);
astResult.list = entityList;
astResult.excluded = excludedEntityList;
});
}
if (context.binaryOptionDeclaration) {
context.binaryOptionDeclaration.map(this.visit, this).forEach((option) => {
if (option.optionName === 'paginate') {
// TODO drop for v9
logger.warn('The paginate option is deprecated, please use pagination instead.');
option.optionName = 'pagination';
}
const newOption = !ast.options[option.optionName];
if (newOption) {
ast.options[option.optionName] = {};
}
const optionValuesMap = ast.options[option.optionName];
if (!optionValuesMap[option.optionValue]) {
optionValuesMap[option.optionValue] = { list: [], excluded: [] };
}
const astResult = optionValuesMap[option.optionValue];
const { entityList, excludedEntityList } = getOptionEntityAndExcludedEntityLists(astResult, option);
astResult.list = entityList;
astResult.excluded = excludedEntityList;
});
}
if (context.useOptionDeclaration) {
ast.useOptions = context.useOptionDeclaration.map(this.visit, this);
}
return ast;
}
constantDeclaration(context) {
return {
name: context.NAME[0].image,
value: context.INTEGER ? context.INTEGER[0].image : context.DECIMAL?.[0].image,
};
}
entityDeclaration(context) {
const annotations = [];
if (context.annotationDeclaration) {
context.annotationDeclaration.forEach(contextObject => {
annotations.push(this.visit(contextObject));
});
}
let documentation = null;
if (context.JAVADOC) {
documentation = trimComment(context.JAVADOC[0].image);
}
const name = context.NAME[0].image;
let tableName;
if (context.entityTableNameDeclaration) {
tableName = this.visit(context.entityTableNameDeclaration);
}
let body = [];
if (context.entityBody) {
body = this.visit(context.entityBody);
}
return {
annotations,
name,
tableName,
body,
documentation,
};
}
annotationDeclaration(context) {
const optionName = context.option[0].image;
if (!context.value) {
return { optionName, type: 'UNARY' };
}
const { image: valueImage } = context.value[0];
const { tokenType } = context.value[0];
let optionValue;
switch (tokenType.name) {
case 'INTEGER':
optionValue = parseInt(valueImage, 10);
break;
case 'DECIMAL':
optionValue = parseFloat(valueImage);
break;
case 'TRUE':
optionValue = true;
break;
case 'FALSE':
optionValue = false;
break;
default:
optionValue = valueImage.replace(/"/g, '');
}
return { optionName, optionValue, type: 'BINARY' };
}
entityTableNameDeclaration(context) {
return context.NAME[0].image;
}
entityBody(context) {
if (!context.fieldDeclaration) {
return [];
}
return context.fieldDeclaration.map(element => this.visit(element));
}
fieldDeclaration(context) {
const annotations = [];
if (context.annotationDeclaration) {
context.annotationDeclaration.forEach(contextObject => {
annotations.push(this.visit(contextObject));
});
}
// filter actual comment as the comment rule may be empty
const comment = context.JAVADOC ? trimComment(context.JAVADOC[0].image) : null;
let validations = [];
if (context.validation) {
validations = context.validation.map(element => this.visit(element));
}
return {
name: context.NAME[0].image,
// context.type is an array with a single item.
// in that case:
// this.visit(context.type) is equivalent to this.visit(context.type[0])
type: this.visit(context.type),
validations,
documentation: comment,
annotations,
};
}
type(context) {
return context.NAME[0].image;
}
validation(context) {
// only one of these alternatives can exist at the same time.
if (context.REQUIRED) {
return {
key: REQUIRED,
value: '',
};
}
if (context.UNIQUE) {
return {
key: UNIQUE,
value: '',
};
}
if (context.minMaxValidation) {
return this.visit(context.minMaxValidation);
}
return this.visit(context.pattern);
}
minMaxValidation(context) {
if (context.NAME) {
return {
key: context.MIN_MAX_KEYWORD[0].image,
value: context.NAME[0].image,
constant: true,
};
}
return {
key: context.MIN_MAX_KEYWORD[0].image,
value: context.INTEGER ? context.INTEGER[0].image : context.DECIMAL[0].image,
};
}
pattern(context) {
const patternImage = context.REGEX[0].image;
return {
key: PATTERN,
value: patternImage.substring(1, patternImage.length - 1),
};
}
relationDeclaration(context) {
const cardinality = this.visit(context.relationshipType);
const relationshipBodies = context.relationshipBody.map(this.visit, this);
relationshipBodies.forEach(relationshipBody => {
relationshipBody.cardinality = cardinality;
});
return relationshipBodies;
}
relationshipType(context) {
return context.RELATIONSHIP_TYPE[0].image;
}
relationshipBody(context) {
const optionsForTheSourceSide = context.annotationOnSourceSide ? context.annotationOnSourceSide.map(this.visit, this) : [];
const optionsForTheDestinationSide = context.annotationOnDestinationSide
? context.annotationOnDestinationSide.map(this.visit, this)
: [];
const from = this.visit(context.from);
const to = this.visit(context.to);
const relationshipOptions = [];
if (context.relationshipOptions) {
this.visit(context.relationshipOptions).forEach((option) => relationshipOptions.push(option));
}
return {
from,
to,
options: {
global: relationshipOptions,
source: optionsForTheSourceSide,
destination: optionsForTheDestinationSide,
},
};
}
relationshipSide(context) {
const documentation = this.visit(context.comment);
const name = context.NAME[0].image;
const required = !!context.REQUIRED;
let injectedField = null;
if (context.injectedField) {
injectedField = context.injectedField[0].image;
if (context.injectedFieldParam) {
injectedField += `(${context.injectedFieldParam[0].image})`;
}
}
const ast = {
name,
injectedField,
documentation,
required,
};
if (!injectedField) {
delete ast.required;
}
return ast;
}
relationshipOptions(context) {
return context.relationshipOption.map(this.visit, this).reduce((final, current) => [...final, current], []);
}
relationshipOption(context) {
if (context.BUILT_IN_ENTITY) {
return { optionName: BUILT_IN_ENTITY, type: 'UNARY' };
}
/* istanbul ignore next */
throw new Error(`No valid relationship option found, expected '${context.BUILT_IN_ENTITY}'.`);
}
enumDeclaration(context) {
const name = context.NAME[0].image;
const values = this.visit(context.enumPropList);
let documentation = null;
if (context.JAVADOC) {
documentation = trimComment(context.JAVADOC[0].image);
}
return { name, values, documentation };
}
enumPropList(context) {
return context.enumProp.map(this.visit, this);
}
enumProp(context) {
const prop = {
key: context.enumPropKey[0].image,
};
if (context.JAVADOC) {
prop.comment = trimComment(context.JAVADOC[0].image);
}
if (context.enumPropValue) {
prop.value = context.enumPropValue[0].image;
}
if (context.enumPropValueWithQuotes) {
prop.value = context.enumPropValueWithQuotes[0].image.replace(/"/g, '');
}
return prop;
}
entityList(context) {
let entityList = [];
if (context.NAME) {
entityList = context.NAME.map(nameToken => nameToken.image);
}
const entityOnlyListContainsAll = entityList.length === 1 && entityList[0] === 'all';
if (context.STAR || entityOnlyListContainsAll) {
entityList = ['*'];
}
if (context.method) {
entityList.push(context.method[0].image);
}
if (context.methodPath) {
entityList.push(context.methodPath[0].image);
}
return deduplicate(entityList);
}
exclusion(context) {
return context.NAME.map(nameToken => nameToken.image, this);
}
unaryOptionDeclaration(context) {
return getUnaryOptionFromContext(context, this);
}
binaryOptionDeclaration(context) {
return getBinaryOptionFromContext(context, this);
}
useOptionDeclaration(context) {
return getSpecialUnaryOptionDeclaration(context, this);
}
filterDef(context) {
let entityList = [];
if (context.NAME) {
entityList = context.NAME.map(nameToken => nameToken.image, this);
}
const entityOnlyListContainsAll = entityList.length === 1 && entityList[0] === 'all';
if (context.STAR || entityOnlyListContainsAll) {
entityList = ['*'];
}
return deduplicate(entityList);
}
comment(context) {
if (context.JAVADOC) {
return trimComment(context.JAVADOC[0].image);
}
return null;
}
deploymentDeclaration(context) {
const config = {};
if (context.deploymentConfigDeclaration) {
const configProps = context.deploymentConfigDeclaration.map(this.visit, this);
configProps.forEach(configProp => {
config[configProp.key] = configProp.value;
});
}
return config;
}
deploymentConfigDeclaration(context) {
const key = context.DEPLOYMENT_KEY[0].image;
const value = this.visit(context.deploymentConfigValue);
return { key, value };
}
deploymentConfigValue(context) {
return this.configValue(context);
}
applicationDeclaration(context) {
return this.visit(context.applicationSubDeclaration);
}
applicationSubDeclaration(context) {
const applicationSubDeclaration = {
config: {},
namespaceConfigs: {},
entitiesOptions: { entityList: [], excluded: [] },
options: {},
useOptions: [],
};
if (context.applicationSubConfig) {
// Apparently the pegjs grammar only returned the last config
applicationSubDeclaration.config = this.visit(context.applicationSubConfig[context.applicationSubConfig.length - 1]);
}
if (context.applicationSubNamespaceConfig) {
const { namespace, config } = this.visit(context.applicationSubNamespaceConfig[context.applicationSubNamespaceConfig.length - 1]);
applicationSubDeclaration.namespaceConfigs[namespace] = config;
}
if (context.applicationSubEntities) {
// Apparently the pegjs grammar only returned the last entities
applicationSubDeclaration.entitiesOptions = this.visit(context.applicationSubEntities[context.applicationSubEntities.length - 1]);
}
if (context.unaryOptionDeclaration) {
context.unaryOptionDeclaration.map(this.visit, this).forEach(option => {
if (!applicationSubDeclaration.options[option.optionName]) {
applicationSubDeclaration.options[option.optionName] = {};
}
const astResult = applicationSubDeclaration.options[option.optionName];
const { entityList, excludedEntityList } = getOptionEntityAndExcludedEntityLists(astResult, option);
astResult.list = entityList;
astResult.excluded = excludedEntityList;
});
}
if (context.binaryOptionDeclaration) {
context.binaryOptionDeclaration.map(this.visit, this).forEach(option => {
if (option.optionName === 'paginate') {
// TODO drop for v9
logger.warn('The paginate option is deprecated, please use pagination instead.');
option.optionName = 'pagination';
}
if (!applicationSubDeclaration.options[option.optionName]) {
applicationSubDeclaration.options[option.optionName] = {};
}
const optionValuesMap = applicationSubDeclaration.options[option.optionName];
if (!optionValuesMap[option.optionValue]) {
optionValuesMap[option.optionValue] = { list: [], excluded: [] };
}
const astResult = optionValuesMap[option.optionValue];
const { entityList, excludedEntityList } = getOptionEntityAndExcludedEntityLists(astResult, option);
astResult.list = entityList;
astResult.excluded = excludedEntityList;
});
}
if (context.useOptionDeclaration) {
context.useOptionDeclaration.map(this.visit, this).forEach(option => {
applicationSubDeclaration.useOptions.push(option);
});
}
return applicationSubDeclaration;
}
applicationSubNamespaceConfig(context) {
const config = {};
const namespace = context.namespace[0].image;
if (context.applicationNamespaceConfigDeclaration) {
const configProps = context.applicationNamespaceConfigDeclaration.map(this.visit, this);
configProps.forEach(configProp => {
config[configProp.key] = configProp.value;
});
}
return { namespace, config };
}
applicationNamespaceConfigDeclaration(context) {
const key = context.NAME[0].image;
const value = this.visit(context.namespaceConfigValue);
return { key, value };
}
namespaceConfigValue(context) {
if (context.qualifiedName) {
return this.visit(context.qualifiedName);
}
if (context.list) {
return this.visit(context.list);
}
if (context.quotedList) {
return this.visit(context.quotedList);
}
if (context.INTEGER) {
return context.INTEGER[0].image;
}
if (context.STRING) {
const stringImage = context.STRING[0].image;
return stringImage.substring(1, stringImage.length - 1);
}
if (context.BOOLEAN) {
return context.BOOLEAN[0].image === 'true';
}
/* istanbul ignore next */
throw new Error('No valid config value was found, expected a qualified name, a list, an integer, a string or a boolean.');
}
applicationSubConfig(context) {
const config = {};
if (context.applicationConfigDeclaration) {
const configProps = context.applicationConfigDeclaration.map(this.visit, this);
configProps.forEach(configProp => {
config[configProp.key] = configProp.value;
});
}
return config;
}
applicationSubEntities(context) {
return getEntityListFromContext(context, this);
}
applicationConfigDeclaration(context) {
const key = context.CONFIG_KEY[0].image;
const value = this.visit(context.configValue);
return { key, value };
}
configValue(context) {
if (context.qualifiedName) {
return this.visit(context.qualifiedName);
}
if (context.list) {
return this.visit(context.list);
}
if (context.quotedList) {
return this.visit(context.quotedList);
}
if (context.INTEGER) {
return context.INTEGER[0].image;
}
if (context.STRING) {
const stringImage = context.STRING[0].image;
return stringImage.substring(1, stringImage.length - 1);
}
if (context.BOOLEAN) {
return context.BOOLEAN[0].image === 'true';
}
/* istanbul ignore next */
throw new Error('No valid config value was found, expected a qualified name, a list, an integer, a string or a boolean.');
}
qualifiedName(context) {
return context.NAME.map(namePart => namePart.image, this).join('.');
}
list(context) {
if (!context.NAME) {
return [];
}
return context.NAME.map(namePart => namePart.image, this);
}
quotedList(context) {
if (!context.STRING) {
return [];
}
return context.STRING.map(namePart => namePart.image.slice(1, -1), this);
}
}
return new JDLAstBuilderVisitor();
};
function getOptionEntityAndExcludedEntityLists(astResult, option) {
const { list, excluded } = astResult;
let entityList = Array.isArray(list) ? list : [];
entityList = deduplicate(entityList.concat(option.list));
let excludedEntityList = Array.isArray(excluded) ? excluded : [];
if (option.excluded) {
excludedEntityList = deduplicate(excludedEntityList.concat(option.excluded));
}
return { entityList, excludedEntityList };
}
function getEntityListFromContext(context, visitor) {
const entityList = visitor.visit(context.filterDef);
let excluded = [];
if (context.exclusion) {
excluded = visitor.visit(context.exclusion);
}
const result = { entityList, excluded };
if (context.UNARY_OPTION) {
result.optionName = context.UNARY_OPTION[0].image;
}
return result;
}
function getUnaryOptionFromContext(context, visitor) {
const entityList = visitor.visit(context.filterDef);
let excluded = [];
if (context.exclusion) {
excluded = visitor.visit(context.exclusion);
}
return { optionName: context.UNARY_OPTION[0].image, list: entityList, excluded };
}
function getBinaryOptionFromContext(context, visitor) {
const entityListWithOptionValue = visitor.visit(context.entityList);
const optionValue = entityListWithOptionValue[entityListWithOptionValue.length - 1];
const list = entityListWithOptionValue.slice(0, entityListWithOptionValue.length - 1);
let excluded = [];
if (context.exclusion) {
excluded = visitor.visit(context.exclusion);
}
return {
optionName: context.BINARY_OPTION[0].image,
optionValue,
list,
excluded,
};
}
function getSpecialUnaryOptionDeclaration(context, visitor) {
const optionValues = context.NAME.map(name => name.image);
const list = visitor.visit(context.filterDef);
let excluded = [];
if (context.exclusion) {
excluded = visitor.visit(context.exclusion);
}
return {
optionValues,
list,
excluded,
};
}
function trimComment(comment) {
return comment.replace(/^\/[*]+[ ]*/, '').replace(/[ ]*[*]+\/$/, '');
}