generator-pyhipster
Version:
Python (Flask) + Angular/React/Vue in one handy generator
597 lines (552 loc) • 17.4 kB
JavaScript
/**
* Copyright 2013-2022 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-useless-escape */
const _ = require('lodash');
const matchesToken = require('chevrotain').tokenMatcher;
const JDLParser = require('./jdl-parser');
const LexerTokens = require('./lexer/lexer').tokens;
const checkConfigKeys = require('./self-checks/parsing-system-checker').checkConfigKeys;
const CONSTANT_PATTERN = /^[A-Z_]+$/;
const ENTITY_NAME_PATTERN = /^[A-Z][A-Za-z0-9]*$/;
const TYPE_NAME_PATTERN = /^[A-Z][A-Za-z0-9]*$/;
const ENUM_NAME_PATTERN = /^[A-Z][A-Za-z0-9]*$/;
const ENUM_PROP_NAME_PATTERN = /^[A-Z][A-Za-z0-9_]*$/;
const ENUM_PROP_VALUE_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
const METHOD_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-_]*$/;
const JHI_PREFIX_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-_]*$/;
const PACKAGE_NAME_PATTERN = /^[a-z_][a-z0-9_]*$/;
const ALPHABETIC = /^[A-Za-z]+$/;
const ALPHABETIC_LOWER = /^[a-z]+$/;
const ALPHANUMERIC = /^[A-Za-z][A-Za-z0-9]*$/;
const ALPHANUMERIC_DASH = /^[A-Za-z][A-Za-z0-9-]*$/;
const ALPHABETIC_DASH_LOWER = /^[a-z][a-z-]*$/;
const ALPHANUMERIC_SPACE = /^"?[A-Za-z][A-Za-z0-9- ]*"?$/;
const ALPHANUMERIC_UNDERSCORE = /^[A-Za-z][A-Za-z0-9_]*$/;
const LANGUAGE_PATTERN = /^[a-z]+(-[A-Za-z0-9]+)*$/;
const PATH_PATTERN = /^"([^\/]+).*"$/;
// const PASSWORD_PATTERN = /^(.+)$/;
const REPONAME_PATTERN = /^"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:\/?#[\]@!$&'()*+,;=]+|[a-zA-Z0-9]+)"$/;
const KUBERNETES_STORAGE_CLASS_NAME = /^"[A-Za-z]*"$/;
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 configPropsValidations = {
APPLICATION_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
msg: 'applicationType property',
},
AUTHENTICATION_TYPE: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'authenticationType property',
},
BASE_NAME: {
type: 'NAME',
pattern: ALPHANUMERIC_UNDERSCORE,
msg: 'baseName property',
},
BLUEPRINT: {
type: 'NAME',
pattern: BASIC_NPM_PACKAGE_NAME_PATTERN,
msg: 'blueprint property',
},
BLUEPRINTS: {
type: 'list',
pattern: BASIC_NPM_PACKAGE_NAME_PATTERN,
msg: 'blueprints property',
},
BUILD_TOOL: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'buildTool property',
},
CACHE_PROVIDER: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'cacheProvider property',
},
CLIENT_FRAMEWORK: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'clientFramework property',
},
CLIENT_THEME: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'clientTheme property',
},
CLIENT_THEME_VARIANT: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'clientThemeVariant property',
},
WITH_ADMIN_UI: { type: 'BOOLEAN' },
CLIENT_PACKAGE_MANAGER: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'clientPackageManager property',
},
CREATION_TIMESTAMP: {
type: 'INTEGER',
pattern: NUMERIC,
msg: 'creationTimestamp property',
},
DATABASE_TYPE: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'databaseType property',
},
DEV_DATABASE_TYPE: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'devDatabaseType property',
},
ENTITY_SUFFIX: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'entitySuffix property',
},
DTO_SUFFIX: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'dtoSuffix property',
},
EMBEDDABLE_LAUNCH_SCRIPT: { type: 'BOOLEAN' },
ENABLE_HIBERNATE_CACHE: { type: 'BOOLEAN' },
ENABLE_SWAGGER_CODEGEN: { type: 'BOOLEAN' },
ENABLE_TRANSLATION: { type: 'BOOLEAN' },
FRONT_END_BUILDER: {
type: 'NAME',
pattern: ALPHABETIC,
msg: 'frontendBuilder property',
},
JHIPSTER_VERSION: { type: 'STRING' },
JHI_PREFIX: {
type: 'NAME',
pattern: JHI_PREFIX_NAME_PATTERN,
msg: 'jhiPrefix property',
},
JWT_SECRET_KEY: {
type: 'STRING',
pattern: JWT_SECRET_KEY_PATTERN,
msg: 'JWT secret key property',
},
LANGUAGES: {
type: 'list',
pattern: LANGUAGE_PATTERN,
msg: 'languages property',
},
MESSAGE_BROKER: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'messageBroker property',
},
NATIVE_LANGUAGE: {
type: 'NAME',
pattern: LANGUAGE_PATTERN,
msg: 'nativeLanguage property',
},
OTHER_MODULES: {
type: 'list',
pattern: BASIC_NPM_PACKAGE_NAME_PATTERN,
msg: 'otherModules property',
},
PACKAGE_NAME: {
type: 'qualifiedName',
pattern: PACKAGE_NAME_PATTERN,
msg: 'packageName property',
},
PROD_DATABASE_TYPE: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'prodDatabaseType property',
},
REACTIVE: { type: 'BOOLEAN' },
REMEMBER_ME_KEY: {
type: 'STRING',
pattern: REMEMBER_ME_KEY_PATTERN,
msg: 'rememberMeKey property',
},
SEARCH_ENGINE: {
type: 'NAME',
pattern: ALPHANUMERIC,
msg: 'searchEngine property',
},
SERVER_PORT: { type: 'INTEGER' },
SERVICE_DISCOVERY_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
msg: 'serviceDiscoveryType property',
},
SKIP_CLIENT: { type: 'BOOLEAN' },
SKIP_SERVER: { type: 'BOOLEAN' },
SKIP_USER_MANAGEMENT: { type: 'BOOLEAN' },
TEST_FRAMEWORKS: {
type: 'list',
pattern: ALPHANUMERIC,
msg: 'testFrameworks property',
},
WEBSOCKET: {
type: 'NAME',
pattern: ALPHANUMERIC_DASH,
msg: 'websocket property',
},
ENABLE_GRADLE_ENTERPRISE: { type: 'BOOLEAN' },
GRADLE_ENTERPRISE_HOST: {
type: 'STRING',
pattern: JWT_SECRET_KEY_PATTERN,
msg: 'gradleEnterpriseHost property',
},
};
const deploymentConfigPropsValidations = {
DEPLOYMENT_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_DASH_LOWER,
msg: 'deploymentType property',
},
GATEWAY_TYPE: {
type: 'NAME',
pattern: ALPHABETIC,
msg: 'gatewayType property',
},
MONITORING: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
msg: 'monitoring property',
},
DIRECTORY_PATH: {
type: 'STRING',
pattern: PATH_PATTERN,
msg: 'directoryPath property',
},
APPS_FOLDERS: {
type: 'list',
pattern: ALPHANUMERIC_UNDERSCORE,
msg: 'appsFolders property',
},
CLUSTERED_DB_APPS: {
type: 'list',
pattern: ALPHANUMERIC,
msg: 'clusteredDbApps property',
},
// This is not secure, need to find a better way
/* ADMIN_PASSWORD: {
type: 'STRING',
pattern: PASSWORD_PATTERN,
msg: 'adminPassword property'
}, */
SERVICE_DISCOVERY_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
msg: 'serviceDiscoveryType property',
},
DOCKER_REPOSITORY_NAME: {
type: 'STRING',
pattern: REPONAME_PATTERN,
msg: 'dockerRepositoryName property',
},
DOCKER_PUSH_COMMAND: {
type: 'STRING',
pattern: ALPHANUMERIC_SPACE,
msg: 'dockerPushCommand property',
},
KUBERNETES_NAMESPACE: {
type: 'NAME',
pattern: ALPHANUMERIC_DASH,
msg: 'kubernetesNamespace property',
},
KUBERNETES_SERVICE_TYPE: {
type: 'NAME',
pattern: ALPHABETIC,
msg: 'kubernetesServiceType property',
},
KUBERNETES_STORAGE_CLASS_NAME: {
type: 'STRING',
pattern: KUBERNETES_STORAGE_CLASS_NAME,
msg: 'kubernetesStorageClassName property',
},
KUBERNETES_USE_DYNAMIC_STORAGE: { type: 'BOOLEAN' },
INGRESS_DOMAIN: {
type: 'STRING',
pattern: REPONAME_PATTERN,
msg: 'ingressDomain property',
},
INGRESS_TYPE: {
type: 'NAME',
pattern: ALPHABETIC,
msg: 'ingressType property',
},
ISTIO: {
type: 'BOOLEAN',
msg: 'istio property',
},
OPENSHIFT_NAMESPACE: {
type: 'NAME',
pattern: ALPHANUMERIC_DASH,
msg: 'openshiftNamespace property',
},
REGISTRY_REPLICAS: { type: 'INTEGER' },
STORAGE_TYPE: {
type: 'NAME',
pattern: ALPHABETIC_LOWER,
msg: 'storageType property',
},
};
module.exports = {
performAdditionalSyntaxChecks,
};
const parser = JDLParser.getParser();
parser.parse();
const BaseJDLCSTVisitorWithDefaults = parser.getBaseCstVisitorConstructorWithDefaults();
class JDLSyntaxValidatorVisitor extends BaseJDLCSTVisitorWithDefaults {
constructor() {
super();
this.validateVisitor();
this.errors = [];
}
checkNameSyntax(token, expectedPattern, errorMessagePrefix) {
if (!expectedPattern.test(token.image)) {
this.errors.push({
message: `The ${errorMessagePrefix} name must match: ${trimAnchors(expectedPattern.toString())}, got ${token.image}.`,
token,
});
}
}
checkIsSingleName(fqnCstNode) {
// A Boolean is allowed as a single name as it is a keyword.
// Other keywords do not need special handling as they do not explicitly appear in the rule
// of config values
if (fqnCstNode.tokenType && fqnCstNode.tokenType.CATEGORIES.includes(LexerTokens.BOOLEAN)) {
return false;
}
const dots = fqnCstNode.children.DOT;
if (dots && dots.length >= 1) {
this.errors.push({
message: 'A single name is expected, but found a fully qualified name.',
token: getFirstToken(fqnCstNode),
});
return false;
}
return true;
}
checkExpectedValueType(expected, actual) {
switch (expected) {
case 'NAME':
if (
actual.name !== 'qualifiedName' &&
// a Boolean (true/false) is also a valid name.
actual.tokenType &&
!_.includes(actual.tokenType.CATEGORIES, LexerTokens.BOOLEAN)
) {
this.errors.push({
message: `A name is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return this.checkIsSingleName(actual);
case 'qualifiedName':
if (actual.name !== 'qualifiedName') {
this.errors.push({
message: `A fully qualified name is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return true;
case 'list':
if (actual.name !== 'list') {
this.errors.push({
message: `An array of names is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return true;
case 'INTEGER':
if (actual.tokenType !== LexerTokens.INTEGER) {
this.errors.push({
message: `An integer literal is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return true;
case 'STRING':
if (actual.tokenType !== LexerTokens.STRING) {
this.errors.push({
message: `A string literal is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return true;
case 'BOOLEAN':
if (!matchesToken(actual, LexerTokens.BOOLEAN)) {
this.errors.push({
message: `A boolean literal is expected, but found: "${getFirstToken(actual).image}"`,
token: getFirstToken(actual),
});
return false;
}
return true;
default:
throw Error(`Expected a boolean, a string, an integer, a list or a (qualified) name, got '${expected}'.`);
}
}
checkConfigPropSyntax(key, value) {
const propertyName = key.tokenType.name;
const validation = configPropsValidations[propertyName];
if (!validation) {
throw Error(`Got an invalid application config property: '${propertyName}'.`);
}
if (this.checkExpectedValueType(validation.type, value) && validation.pattern && value.children && value.children.NAME) {
value.children.NAME.forEach(nameTok => this.checkNameSyntax(nameTok, validation.pattern, validation.msg));
}
}
checkDeploymentConfigPropSyntax(key, value) {
const propertyName = key.tokenType.name;
const validation = deploymentConfigPropsValidations[propertyName];
if (!validation) {
throw Error(`Got an invalid deployment config property: '${propertyName}'.`);
}
if (this.checkExpectedValueType(validation.type, value) && validation.pattern && value.children && value.children.NAME) {
value.children.NAME.forEach(nameTok => this.checkNameSyntax(nameTok, validation.pattern, validation.msg));
} else if (value.image && validation.pattern) {
this.checkNameSyntax(value, validation.pattern, validation.msg);
}
}
constantDeclaration(context) {
super.constantDeclaration(context);
this.checkNameSyntax(context.NAME[0], CONSTANT_PATTERN, 'constant');
}
entityDeclaration(context) {
super.entityDeclaration(context);
this.checkNameSyntax(context.NAME[0], ENTITY_NAME_PATTERN, 'entity');
}
fieldDeclaration(context) {
super.fieldDeclaration(context);
this.checkNameSyntax(context.NAME[0], ALPHANUMERIC, 'fieldName');
}
type(context) {
super.type(context);
this.checkNameSyntax(context.NAME[0], TYPE_NAME_PATTERN, 'typeName');
}
minMaxValidation(context) {
super.minMaxValidation(context);
if (context.NAME) {
this.checkNameSyntax(context.NAME[0], CONSTANT_PATTERN, 'constant');
}
}
relationshipSide(context) {
super.relationshipSide(context);
this.checkNameSyntax(context.NAME[0], ENTITY_NAME_PATTERN, 'entity');
if (Array.isArray(context.injectedField)) {
this.checkNameSyntax(context.injectedField[0], ALPHANUMERIC, 'injectedField');
if (context.injectedFieldParam) {
this.checkNameSyntax(context.injectedFieldParam[0], ALPHANUMERIC, 'injectedField');
}
}
}
enumDeclaration(context) {
super.enumDeclaration(context);
this.checkNameSyntax(context.NAME[0], ENUM_NAME_PATTERN, 'enum');
}
enumPropList(context) {
super.enumPropList(context);
context.enumProp.forEach(nameToken => {
const propKey = nameToken.children.enumPropKey[0];
this.checkNameSyntax(propKey, ENUM_PROP_NAME_PATTERN, 'enum property name');
const propValue = nameToken.children.enumPropValue;
if (propValue) {
this.checkNameSyntax(propValue[0], ENUM_PROP_VALUE_PATTERN, 'enum property value');
}
});
}
entityList(context) {
super.entityList(context);
if (context.NAME) {
context.NAME.forEach(nameToken => {
// we don't want this validated as it's an alias for '*'
if (nameToken.image === 'all') {
return;
}
this.checkNameSyntax(nameToken, ENTITY_NAME_PATTERN, 'entity');
});
}
if (context.method) {
this.checkNameSyntax(context.method[0], METHOD_NAME_PATTERN, 'method');
}
if (context.methodPath) {
this.checkNameSyntax(context.methodPath[0], PATH_PATTERN, 'methodPath');
}
}
exclusion(context) {
super.exclusion(context);
context.NAME.forEach(nameToken => {
this.checkNameSyntax(nameToken, ENTITY_NAME_PATTERN, 'entity');
});
}
filterDef(context) {
if (context.NAME) {
context.NAME.forEach(nameToken => {
// we don't want this validated as it's an alias for '*'
if (nameToken.image === 'all') {
return;
}
this.checkNameSyntax(nameToken, ENTITY_NAME_PATTERN, 'entity');
});
}
}
applicationConfigDeclaration(context) {
this.visit(context.configValue, context.CONFIG_KEY[0]);
}
configValue(context, configKey) {
const configValue = _.first(_.first(Object.values(context)));
this.checkConfigPropSyntax(configKey, configValue);
}
deploymentConfigDeclaration(context) {
this.visit(context.deploymentConfigValue, context.DEPLOYMENT_KEY[0]);
}
deploymentConfigValue(context, configKey) {
const configValue = _.first(_.first(_.values(context)));
this.checkDeploymentConfigPropSyntax(configKey, configValue);
}
}
function performAdditionalSyntaxChecks(cst) {
const syntaxValidatorVisitor = new JDLSyntaxValidatorVisitor();
syntaxValidatorVisitor.visit(cst);
return syntaxValidatorVisitor.errors;
}
checkConfigKeys(LexerTokens, Object.keys(configPropsValidations));
function trimAnchors(str) {
return str.replace(/^\^/, '').replace(/\$$/, '');
}
function getFirstToken(tokOrCstNode) {
if (tokOrCstNode.tokenType) {
return tokOrCstNode;
}
// CST Node - - assumes no nested CST Nodes, only terminals
return _.flatten(Object.values(tokOrCstNode.children)).reduce(
(firstTok, nextTok) => (firstTok.startOffset > nextTok.startOffset ? nextTok : firstTok),
{ startOffset: Infinity }
);
}