Skip to content

Commit

Permalink
jdl: allow namespaced options
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed Oct 23, 2023
1 parent 7ad87c2 commit 29b3fc1
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 36 deletions.
90 changes: 77 additions & 13 deletions jdl/integration-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import { parseFromContent, parseFromFiles } from './readers/jdl-reader.js';
import DocumentParser from './converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.js';
import exportToJDL from './exporters/jdl-exporter.js';
import { basicHelpers as helpers } from '../test/support/index.mjs';
import { convert } from './converters/jdl-to-json/jdl-without-application-to-json-converter.js';
import { convert as convertWithoutApplication } from './converters/jdl-to-json/jdl-without-application-to-json-converter.js';
import { ApplicationWithEntities, createImporterFromContent } from './jdl-importer.js';

const { MONOLITH } = applicationTypes;
const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -62,18 +63,18 @@ describe('jdl - integration tests', () => {

context('when parsing entities JDL', () => {
const applicationName = 'jhipster';
const jdl = `
context('with bidirectional relationship', () => {
let result: Map<any, any[]>;
const jdl = `
entity A {}
entity B {}
relationship ManyToOne {
A to B
}
`;
context('with bidirectional relationship', () => {
let result: Map<any, any[]>;

beforeEach(() => {
result = convert({
result = convertWithoutApplication({
applicationName,
databaseType: 'sql',
jdlObject: DocumentParser.parseFromConfigurationObject({
Expand All @@ -84,8 +85,8 @@ relationship ManyToOne {
});

it('should add relationship at both sides', () => {
jestExpect(result.get('jhipster')![0].relationships.length).toBe(1);
jestExpect(result.get('jhipster')![1].relationships.length).toBe(1);
jestExpect(result.get(applicationName)![0].relationships.length).toBe(1);
jestExpect(result.get(applicationName)![1].relationships.length).toBe(1);
});

it('should result matching', () => {
Expand Down Expand Up @@ -155,7 +156,7 @@ relationship ManyToOne {
`;

beforeEach(() => {
result = convert({
result = convertWithoutApplication({
applicationName,
databaseType: 'sql',
jdlObject: DocumentParser.parseFromConfigurationObject({
Expand All @@ -166,8 +167,8 @@ relationship ManyToOne {
});

it('should add relationship at one side', () => {
jestExpect(result.get('jhipster')![0].relationships.length).toBe(1);
jestExpect(result.get('jhipster')![1].relationships.length).toBe(0);
jestExpect(result.get(applicationName)![0].relationships.length).toBe(1);
jestExpect(result.get(applicationName)![1].relationships.length).toBe(0);
});

it('should result matching', () => {
Expand Down Expand Up @@ -231,7 +232,7 @@ relationship ManyToOne {
`;

beforeEach(() => {
result = convert({
result = convertWithoutApplication({
applicationName,
databaseType: 'sql',
jdlObject: DocumentParser.parseFromConfigurationObject({
Expand All @@ -242,8 +243,8 @@ relationship ManyToOne {
});

it('should add relationship at both sides', () => {
jestExpect(result.get('jhipster')![0].relationships.length).toBe(1);
jestExpect(result.get('jhipster')![1].relationships.length).toBe(1);
jestExpect(result.get(applicationName)![0].relationships.length).toBe(1);
jestExpect(result.get(applicationName)![1].relationships.length).toBe(1);
});

it('should result matching', () => {
Expand Down Expand Up @@ -303,6 +304,69 @@ Map {
},
],
}
`);
});
});
});

context('when parsing JDL with blueprint configs', () => {
const applicationName = 'jhipster';

context('without blueprint', () => {
const jdl = `
application {
config {
baseName jhipster
foo:stringConfig stringValue
}
}
`;

it('should throw error', () => {
const importer = createImporterFromContent(jdl);
jestExpect(() => importer.import()).toThrowError({ message: 'Blueprint config foo:stringConfig requires the blueprint foo' });
});
});

context('with blueprint', () => {
let result: Record<string, ApplicationWithEntities>;
const jdl = `
application {
config {
baseName jhipster
blueprints [foo]
foo:stringConfig stringValue
foo:trueConfig true
foo:falseConfig false
foo:listConfig [item]
foo:integerConfig 123
}
}
`;

beforeEach(() => {
const importer = createImporterFromContent(jdl);
const importState = importer.import();
result = importState.exportedApplicationsWithEntities;
});

it('should result matching', () => {
jestExpect(result[applicationName].config).toMatchInlineSnapshot(`
{
"baseName": "jhipster",
"blueprints": [
{
"name": "generator-jhipster-foo",
},
],
"generator-jhipster-foo:falseConfig": false,
"generator-jhipster-foo:integerConfig": 123,
"generator-jhipster-foo:listConfig": [
"item",
],
"generator-jhipster-foo:stringConfig": "stringValue",
"generator-jhipster-foo:trueConfig": true,
}
`);
});
});
Expand Down
18 changes: 16 additions & 2 deletions jdl/models/jdl-application-configuration-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function createApplicationConfigurationFromObject(configurationOb
const configuration = new JDLApplicationConfiguration();
Object.keys(configurationObject).forEach(optionName => {
const optionValue = configurationObject[optionName];
if (!applicationDefinition.doesOptionExist(optionName)) {
if (!applicationDefinition.doesOptionExist(optionName) && !optionName.includes(':')) {
logger.debug(`Unrecognized application option name and value: '${optionName}' and '${optionValue}'.`);
return;
}
Expand All @@ -41,7 +41,21 @@ export default function createApplicationConfigurationFromObject(configurationOb
}

function createJDLConfigurationOption(name, value) {
const type = applicationDefinition.getTypeForOption(name);
let type = applicationDefinition.getTypeForOption(name);
if (type === 'unknown') {
if (typeof value === 'boolean') {
type = 'boolean';
} else if (/^\d*$/.test(value)) {
value = parseInt(value, 10);
type = 'integer';
} else if (Array.isArray(value)) {
type = 'list';
} else if (typeof value === 'string') {
type = 'string';
} else {
throw new Error(`Unknown value type for option ${name}`);
}
}
switch (type) {
case 'string':
return new StringJDLApplicationConfigurationOption(name, value, applicationDefinition.shouldTheValueBeQuoted(name));
Expand Down
4 changes: 2 additions & 2 deletions jdl/models/jdl-application-configuration-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
*/

export default class JDLApplicationConfigurationOption {
name: any;
name: string;
value: any;

/**
* Creates a new application option.
* @param {String} name - the option's name.
* @param {any} value - the option's value, can be virtually anything: a String, an Int, a boolean...
*/
constructor(name, value) {
constructor(name: string, value) {
this.name = name;
this.value = value;
}
Expand Down
11 changes: 6 additions & 5 deletions jdl/models/jdl-application-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,25 @@
*/

import ApplicationOptions from '../jhipster/application-options.js';
import JDLApplicationConfigurationOption from './jdl-application-configuration-option.js';

const { OptionNames } = ApplicationOptions;

export default class JDLApplicationConfiguration {
options: any;
options: Record<string, JDLApplicationConfigurationOption>;

constructor() {
this.options = {};
}

hasOption(optionName) {
hasOption(optionName: string) {
if (!optionName) {
return false;
}
return optionName in this.options;
}

getOption(optionName) {
getOption(optionName: string) {
if (!optionName) {
throw new Error('An option name has to be passed to get the option.');
}
Expand All @@ -45,14 +46,14 @@ export default class JDLApplicationConfiguration {
return this.options[optionName];
}

setOption(option) {
setOption(option: JDLApplicationConfigurationOption) {
if (!option) {
throw new Error('An option has to be passed to set an option.');
}
this.options[option.name] = option;
}

forEachOption(passedFunction) {
forEachOption(passedFunction: (option: JDLApplicationConfigurationOption) => void) {
if (!passedFunction) {
return;
}
Expand Down
5 changes: 4 additions & 1 deletion jdl/models/jdl-application-definition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { jhipsterOptionTypes, jhipsterOptionValues, jhipsterQuotedOptionNames } from '../jhipster/application-options.js';

export type JDLApplicationOptionValue = string | number | boolean | undefined | never[] | Record<string, string>;
export type JDLApplicationOptionTypeValue = 'string' | 'integer' | 'boolean' | 'list';
export type JDLApplicationOptionTypeValue = 'string' | 'integer' | 'boolean' | 'list' | 'unknown';
export type JDLApplicationOptionType = { type: JDLApplicationOptionTypeValue };

export default class JDLApplicationDefinition {
Expand All @@ -18,6 +18,9 @@ export default class JDLApplicationDefinition {
if (!optionName) {
throw new Error('A name has to be passed to get the option type.');
}
if (optionName.includes(':')) {
return 'unknown';
}
if (!this.optionTypes[optionName]) {
throw new Error(`Unrecognised application option name: ${optionName}.`);
}
Expand Down
8 changes: 5 additions & 3 deletions jdl/models/jdl-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
*/

import createApplicationConfigurationFromObject from './jdl-application-configuration-factory.js';
import JDLApplicationConfigurationOption from './jdl-application-configuration-option.js';
import JDLApplicationConfiguration from './jdl-application-configuration.js';
import JDLApplicationEntities from './jdl-application-entities.js';
import JDLOptions from './jdl-options.js';

export default class JDLApplication {
config: any;
config: JDLApplicationConfiguration;
entityNames: any;
options: any;

Expand Down Expand Up @@ -51,10 +53,10 @@ export default class JDLApplication {
return undefined;
}
const option = this.config.getOption(optionName);
return option.getValue();
return option!.getValue();
}

forEachConfigurationOption(passedFunction) {
forEachConfigurationOption(passedFunction: (option: JDLApplicationConfigurationOption) => void) {
this.config.forEachOption(passedFunction);
}

Expand Down
2 changes: 1 addition & 1 deletion jdl/models/jdl-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export default class JDLObject {
return this.options.getOptionsForName(optionName);
}

forEachOption(passedFunction) {
forEachOption(passedFunction: (option: any) => void) {
if (!passedFunction) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion jdl/models/list-jdl-application-configuration-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class ListJDLApplicationConfigurationOption extends JDLApplicatio
super(name, new Set(value));
}

getValue() {
getValue(): any[] {
return Array.from(this.value);
}

Expand Down
1 change: 1 addition & 0 deletions jdl/parsing/lexer/application-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const {
const applicationConfigCategoryToken = createTokenFromConfig({ name: 'CONFIG_KEY', pattern: Lexer.NA });

const applicationConfigTokens: Pick<ITokenConfig, 'name' | 'pattern'>[] = [
{ name: 'BLUEPRINT_CONFIGS', pattern: /\w*:\w*/ },
{ name: 'BASE_NAME', pattern: BASE_NAME },
{ name: 'BLUEPRINTS', pattern: BLUEPRINTS },
{ name: 'BLUEPRINT', pattern: BLUEPRINT },
Expand Down
2 changes: 1 addition & 1 deletion jdl/parsing/lexer/token-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function createTokenFromConfig(config: ITokenConfig) {
// library with a separate lexing phase.
// See: https://github.com/SAP/chevrotain/blob/master/examples/lexer/keywords_vs_identifiers/keywords_vs_identifiers.js
// a Concise way to resolve the problem without manually adding the "longer_alt" property dozens of times.
if (isString(config.pattern) && namePattern.test(config.pattern)) {
if ((isString(config.pattern) && namePattern.test(config.pattern)) || config.name === 'BLUEPRINT_CONFIGS') {
config.longer_alt = NAME;
if (!config.categories) {
config.categories = [];
Expand Down
10 changes: 9 additions & 1 deletion jdl/parsing/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ const JWT_SECRET_KEY_PATTERN = /^\S+$/;
const REMEMBER_ME_KEY_PATTERN = /^\S+$/;
const NUMERIC = /^\d$/;
const BASIC_NPM_PACKAGE_NAME_PATTERN = /^(@[a-z0-9-][a-z0-9-._]*\/)?[a-z0-9-][a-z0-9-._]*$/;
const BLUEPRINT_CONFIGS_PATTERN = /^((\d*)|([A-Za-z][A-Za-z0-9]*))$/;

export type JDLValidatorOptionType = 'BOOLEAN' | 'INTEGER' | 'list' | 'NAME' | 'qualifiedName' | 'STRING';
export type JDLValidatorOptionType = 'BOOLEAN' | 'INTEGER' | 'list' | 'NAME' | 'qualifiedName' | 'STRING' | 'UNKNOWN';

export type JDLValidatorOption = {
type: JDLValidatorOptionType;
Expand All @@ -61,6 +62,11 @@ export type JDLValidatorOption = {
};

const configPropsValidations: Record<string, JDLValidatorOption> = {
BLUEPRINT_CONFIGS: {
type: 'UNKNOWN',
pattern: BLUEPRINT_CONFIGS_PATTERN,
msg: 'blueprint configs',
},
APPLICATION_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
Expand Down Expand Up @@ -369,6 +375,8 @@ class JDLSyntaxValidatorVisitor extends BaseJDLCSTVisitorWithDefaults {

checkExpectedValueType(expected, actual) {
switch (expected) {
case 'UNKNOWN':
return true;
case 'NAME':
if (
actual.name !== 'qualifiedName' &&
Expand Down
3 changes: 3 additions & 0 deletions jdl/validators/entity-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export default class EntityValidator extends Validator {

validate(jdlEntity) {
super.validate(jdlEntity);
}

validateBusiness(jdlEntity) {
checkForReservedClassName(jdlEntity);
}
}
Expand Down
2 changes: 1 addition & 1 deletion jdl/validators/enum-validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('jdl - EnumValidator', () => {
context('with a reserved class name as name', () => {
it('should fail', () => {
expect(() => {
validator.validate(new JDLEnum({ name: 'Catch' }));
validator.validateBusiness(new JDLEnum({ name: 'Catch' }));
}).to.throw(/^The enum name 'Catch' is reserved keyword and can not be used as enum class name\.$/);
});
});
Expand Down
3 changes: 3 additions & 0 deletions jdl/validators/enum-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default class EnumValidator extends Validator {

validate(jdlEnum) {
super.validate(jdlEnum);
}

validateBusiness(jdlEnum) {
checkForReservedClassName(jdlEnum);
}
}
Expand Down
Loading

0 comments on commit 29b3fc1

Please sign in to comment.