UNPKG

generator-pyhipster

Version:

Python (Flask) + Angular/React/Vue in one handy generator

597 lines (552 loc) 17.4 kB
/** * 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 } ); }