Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 2.15.4 to 3.0.0

doc/DeprecatedOptions_v2.md

69

bin/cdsc.js

@@ -22,3 +22,3 @@ #!/usr/bin/env node

const { compactModel } = require('../lib/json/to-csn');
const { toRenameWithCsn, alterConstraintsWithCsn } = require('../lib/backends');
const { toRename: _toRename } = require('../lib/render/toRename');
const util = require('util');

@@ -37,2 +37,3 @@ const fs = require('fs');

const { availableBetaFlags } = require('../lib/base/model');
const { alterConstraintsWithCsn } = require('../lib/render/manageConstraints');

@@ -61,7 +62,7 @@ // Note: Instead of throwing ProcessExitError, we would rather just call process.exit(exitCode),

function remapCmdOptions(options, cmdOptions) {
if (!cmdOptions)
return options;
function remapCmdOptions(options, command) {
if (!command || !options[command])
return;
for (const [ key, value ] of Object.entries(cmdOptions)) {
for (const [ key, value ] of Object.entries(options[command])) {
switch (key) {

@@ -94,2 +95,5 @@ case 'names':

break;
case 'flavor':
options.csnFlavor = value;
break;
default:

@@ -99,3 +103,3 @@ options[key] = value;

}
return options;
delete options[command];
}

@@ -281,2 +285,3 @@

remapCmdOptions( options, command );

@@ -305,3 +310,3 @@ if (commandsWithoutCompilation[command]) {

const csn = options.directBackend ? model : compactModel(model, options);
const cdlResult = main.to.cdl(csn, remapCmdOptions(options));
const cdlResult = main.to.cdl(csn, options);
for (const name in cdlResult)

@@ -331,7 +336,7 @@ writeToFileOrDisplay(options.out, `${name}.cds`, cdlResult[name]);

if (options.toHana && options.toHana.csn) {
displayNamedCsn(for_hdbcds(csn, remapCmdOptions(options, options.toHana)), 'hana_csn');
if (options.csn) {
displayNamedCsn(for_hdbcds(csn, options), 'hana_csn');
}
else {
const hanaResult = main.to.hdbcds(csn, remapCmdOptions(options, options.toHana));
const hanaResult = main.to.hdbcds(csn, options);
for (const name in hanaResult)

@@ -347,16 +352,14 @@ writeToFileOrDisplay(options.out, name, hanaResult[name]);

function toOdata( model ) {
if (options.toOdata &&
options.toOdata.version === 'v4x') {
options.toOdata.version = 'v4';
options.toOdata.odataFormat = 'structured';
options.toOdata.odataContainment = true;
if (options.odataVersion === 'v4x') {
options.odataVersion = 'v4';
options.odataFormat = 'structured';
options.odataContainment = true;
}
const csn = options.directBackend ? model : compactModel(model, options);
const odataOptions = remapCmdOptions(options, options.toOdata);
if (options.toOdata && options.toOdata.csn) {
const odataCsn = main.for.odata(csn, odataOptions);
if (options.csn) {
const odataCsn = main.for.odata(csn, options);
displayNamedCsn(odataCsn, 'odata_csn');
}
else if (options.toOdata && options.toOdata.json) {
const result = main.to.edm.all(csn, odataOptions);
else if (options.json) {
const result = main.to.edm.all(csn, options);
for (const serviceName in result)

@@ -366,3 +369,3 @@ writeToFileOrDisplay(options.out, `${serviceName}.json`, result[serviceName]);

else {
const result = main.to.edmx.all(csn, odataOptions);
const result = main.to.edmx.all(csn, options);
for (const serviceName in result)

@@ -381,4 +384,4 @@ writeToFileOrDisplay(options.out, `${serviceName}.xml`, result[serviceName]);

const csn = options.directBackend ? model : compactModel(model, options);
const renameResult = toRenameWithCsn(csn, options);
let storedProcedure = `PROCEDURE RENAME_${renameResult.options.toRename.names.toUpperCase()}_TO_PLAIN LANGUAGE SQLSCRIPT AS BEGIN\n`;
const renameResult = _toRename(csn, options);
let storedProcedure = `PROCEDURE RENAME_${renameResult.options.sqlMapping.toUpperCase()}_TO_PLAIN LANGUAGE SQLSCRIPT AS BEGIN\n`;
for (const name in renameResult.rename) {

@@ -389,3 +392,3 @@ storedProcedure += ` --\n -- ${name}\n --\n`;

storedProcedure += 'END;\n';
writeToFileOrDisplay(options.out, `storedProcedure_${renameResult.options.toRename.names}_to_plain.sql`, storedProcedure, true);
writeToFileOrDisplay(options.out, `storedProcedure_${renameResult.options.sqlMapping}_to_plain.sql`, storedProcedure, true);
return model;

@@ -412,8 +415,8 @@ }

const csn = options.directBackend ? model : compactModel(model, options);
if (options.toSql && options.toSql.src === 'hdi') {
if (options.toSql.csn) {
displayNamedCsn(for_hdi(csn, remapCmdOptions(options, options.toSql)), 'hdi_csn');
if (options.src === 'hdi') {
if (options.csn) {
displayNamedCsn(for_hdi(csn, options), 'hdi_csn');
}
else {
const hdiResult = main.to.hdi(csn, remapCmdOptions(options, options.toSql));
const hdiResult = main.to.hdi(csn, options);
for (const name in hdiResult)

@@ -423,7 +426,7 @@ writeToFileOrDisplay(options.out, name, hdiResult[name]);

}
else if (options.toSql && options.toSql.csn) {
displayNamedCsn(for_sql(csn, remapCmdOptions(options, options.toSql)), 'sql_csn');
else if (options.csn) {
displayNamedCsn(for_sql(csn, options), 'sql_csn');
}
else {
const sqlResult = main.to.sql(csn, remapCmdOptions(options, options.toSql));
const sqlResult = main.to.sql(csn, options);
writeToFileOrDisplay(options.out, 'model.sql', sqlResult.join('\n'), true);

@@ -541,3 +544,3 @@ }

const csn = compactModel(xsn, options);
if (command === 'toCsn' && options.toCsn && options.toCsn.withLocalized)
if (command === 'toCsn' && options.withLocalized)
addLocalizationViews(csn, options);

@@ -561,3 +564,3 @@ if (options.enrichCsn)

else if (!options.lintMode && !options.internalMsg) {
if (command === 'toCsn' && options.toCsn && options.toCsn.withLocalized)
if (command === 'toCsn' && options.withLocalized)
addLocalizationViews(csn, options);

@@ -564,0 +567,0 @@ writeToFileOrDisplay(options.out, `${name}.json`, csn, true);

@@ -11,8 +11,7 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 2.XX.YY
## Version 3.0.0 - 2022-XX-YY
### Removed `assocsWithParams`
### Removed `addTextsLanguageAssoc`
Instead, of using the beta flag `assocsWithParams`, you can change the severity of the messages
`def-unexpected-paramview-assoc` and `def-unexpected-calcview-assoc`.
Instead, use the option `addTextsLanguageAssoc`, which is available since v2.8.0.

@@ -19,0 +18,0 @@ ## Version 2.12.0 - 2022-01-25

@@ -14,4 +14,38 @@ # ChangeLog of deprecated Features for cdx compiler and backends

## Version 2.XX.YY - 2022-MM-DD
## Version 3.0.0 - 2022-XX-YY
Version 3 of the cds-compiler removes all v2 deprecated flags.
### Removed `createLocalizedViews`
### Removed `downgradableErrors`
### Removed `generatedEntityNameWithUnderscore`
### Removed `longAutoexposed`
### Removed `noElementsExpansion`
### Removed `noInheritedAutoexposeViaComposition`
### Removed `noScopedRedirections`
### Removed `oldVirtualNotNullPropagation`
### Removed `parensAsStrings`
### Removed `projectionAsQuery`
### Removed `redirectInSubQueries`
### Removed `renderVirtualElements`
### Removed `shortAutoexposed`
### Removed `unmanagedUpInComponent`
### Removed `v1KeysForTemporal`
## Version 2.13.0 - 2022-03-22
### Added `redirectInSubQueries`

@@ -18,0 +52,0 @@

@@ -8,2 +8,13 @@ # Versioning

<!-- toc: start -->
1. [Public API](#public-api)
2. [Patch Versions](#patch-versions)
3. [Minor Versions](#minor-versions)
4. [Beta Flags](#beta-flags)
5. [Deprecated Flags](#deprecated-flags)
6. [Command Line Tool `cdsc`](#command-line-tool-cdsc)
<!-- toc: end -->
## Public API

@@ -45,3 +56,3 @@

The compiler provides so called “beta flags” that enable or disable certain
The compiler provides so-called “beta flags” that enable or disable certain
features. We do not guarantee that any such flags stay consistent between

@@ -62,3 +73,11 @@ patch versions! Beta flags may change any time.

## Command Line Tool `cdsc`
`bin/cdsc.js` as well as all other command line tools do _not_ guarantee any
stability. It is considered a compiler internal tool that only serves for
debugging. The official command line tool `cds` of the `@sap/cds` and
`@sap/cds-dk` packages are to be used by users. That means commands and
options may change any time without prior notice. Changes may still be listed
in [CHANGELOG.md](../CHANGELOG.md).
[SemVer]: https://semver.org/

@@ -65,0 +84,0 @@ [§1]: https://semver.org/#spec-item-1

{
"root": true,
"env": {
"es6": true,
"es2020": true,
"node": true

@@ -10,3 +10,3 @@ },

"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2020,
"sourceType": "script"

@@ -13,0 +13,0 @@ },

@@ -5,16 +5,16 @@ /** @module API */

const prepareOptions = require('./options');
const backends = require('../backends');
const { setProp } = require('../base/model');
const { emptyLocation } = require('../base/location');
const { CompilationError, makeMessageFunction } = require('../base/messages');
const { recompileX } = require('../compiler/index');
const { compactModel, sortCsn } = require('../json/to-csn');
const { transform4odataWithCsn } = require('../transform/forOdataNew.js');
const { toSqlDdl } = require('../render/toSql');
const { compareModels } = require('../modelCompare/compare');
const sortViews = require('../model/sortViews');
const { getResultingName } = require('../model/csnUtils');
const { timetrace } = require('../utils/timetrace');
const { transformForHanaWithCsn } = require('../transform/forHanaNew');
const prepareOptions = lazyload('./options');
const baseModel = lazyload('../base/model');
const location = lazyload('../base/location');
const messages = lazyload('../base/messages');
const compiler = lazyload('../compiler/index');
const toCsn = lazyload('../json/to-csn');
const forOdataNew = lazyload('../transform/forOdataNew.js');
const toSql = lazyload('../render/toSql');
const toCdl = require('../render/toCdl');
const modelCompare = lazyload('../modelCompare/compare');
const sortViews = lazyload('../model/sortViews');
const csnUtils = lazyload('../model/csnUtils');
const timetrace = lazyload('../utils/timetrace');
const forHanaNew = lazyload('../transform/forHanaNew');

@@ -30,13 +30,11 @@ /**

function getFileName(artifactName, csn) {
return getResultingName(csn, 'quoted', artifactName);
return csnUtils.getResultingName(csn, 'quoted', artifactName);
}
const propertyToCheck = {
odata: 'toOdata',
};
const { cloneCsnNonDict } = require('../model/csnUtils');
const { toHdbcdsSource } = require('../render/toHdbcds');
const { ModelError } = require('../base/error');
const { forEach } = require('../utils/objectUtils');
const { forEach, forEachKey } = require('../utils/objectUtils');
const { checkRemovedDeprecatedFlags } = require('../base/model');
const { csn2edm, csn2edmAll } = require('../edm/csn2edm');

@@ -59,11 +57,10 @@ const relevantGeneralOptions = [ /* for future generic options */ ];

const relevant = {};
const propName = propertyToCheck[transformation];
for (const name of relevantOptionNames ) {
if (options[propName][name] !== undefined)
relevant[name] = options[propName][name];
if (options[name] !== undefined)
relevant[name] = options[name];
}
for (const name of optionalOptionNames ) {
if (options[propName][name] !== undefined)
relevant[name] = options[propName][name];
if (options[name] !== undefined)
relevant[name] = options[name];
}

@@ -76,6 +73,6 @@

if (!csn.meta)
setProp(csn, 'meta', {});
baseModel.setProp(csn, 'meta', {});
setProp(csn.meta, 'options', relevant);
setProp(csn.meta, 'transformation', transformation);
baseModel.setProp(csn.meta, 'options', relevant);
baseModel.setProp(csn.meta, 'transformation', transformation);
}

@@ -99,3 +96,3 @@

}
const { error, warning, throwWithAnyError } = makeMessageFunction(csn, options, module);
const { error, warning, throwWithAnyError } = messages.makeMessageFunction(csn, options, module);

@@ -136,3 +133,3 @@ for (const name of relevantOptionNames ) {

function odataInternal(csn, internalOptions) {
const oDataCsn = transform4odataWithCsn(csn, internalOptions);
const oDataCsn = forOdataNew.transform4odataWithCsn(csn, internalOptions);
attachTransformerCharacteristics(oDataCsn, 'odata', internalOptions, relevantOdataOptions, warnAboutMismatchOdata);

@@ -159,9 +156,9 @@ return oDataCsn;

* @param {object} [externalOptions={}] Options
* @returns {CDL} { <artifactName>: <CDL representation>, ...}
* @returns {object} { model: string, namespace: string, unappliedExtensions: string }
*/
function cdl(csn, externalOptions = {}) {
const internalOptions = prepareOptions.to.cdl(externalOptions);
const { result } = backends.toCdlWithCsn(cloneCsnNonDict(csn, internalOptions), internalOptions);
return result;
return toCdl.csnToCdl(cloneCsnNonDict(csn, internalOptions), internalOptions);
}
/**

@@ -178,4 +175,5 @@ * Transform a CSN like to.sql

internalOptions.transformation = 'sql';
internalOptions.toSql.csn = true;
return backends.toSqlWithCsn(csn, internalOptions).csn;
const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.sql');
return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
}

@@ -192,4 +190,6 @@ /**

const internalOptions = prepareOptions.to.hdi(options);
internalOptions.toSql.csn = true;
return backends.toSqlWithCsn(csn, internalOptions).csn;
internalOptions.transformation = 'sql';
const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdi');
return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
}

@@ -208,5 +208,5 @@ /**

const hanaCsn = transformForHanaWithCsn(csn, internalOptions, 'to.hdbcds');
const hanaCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdbcds');
return internalOptions.testMode ? sortCsn(hanaCsn, internalOptions) : hanaCsn;
return internalOptions.testMode ? toCsn.sortCsn(hanaCsn, internalOptions) : hanaCsn;
}

@@ -225,9 +225,13 @@

const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
if (internalOptions.sqlDialect === 'postgres' && !baseModel.isBetaEnabled(internalOptions, 'postgres'))
error(null, null, 'sqlDialect: \'postgres\' is only supported with beta-flag \'postgres\'');
// we need the CSN for view sorting
internalOptions.toSql.csn = true;
const transformedCsn = forSql(csn, options);
const sqls = toSql.toSqlDdl(transformedCsn, internalOptions);
const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);
const result = sortViews({ csn: transformedCsn, sql: sqls.sql });
const result = sortViews(intermediateResult);
return result.map(obj => obj.sql).filter(create => create);

@@ -247,9 +251,5 @@ }

// we need the CSN for view sorting
internalOptions.toSql.csn = true;
const sqlCSN = forHdi(csn, options);
const sqls = toSql.toSqlDdl(sqlCSN, internalOptions);
const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);
const sqlCSN = intermediateResult.csn;
delete intermediateResult.csn;
if (internalOptions.testMode) {

@@ -261,3 +261,3 @@ // All this mapping is needed because sortViews crossmatches

const flat = flattenResultStructure(intermediateResult);
const flat = flattenResultStructure(sqls);

@@ -293,3 +293,3 @@ const nameMapping = Object.create(null);

return remapNames(flattenResultStructure(intermediateResult), sqlCSN, k => !k.endsWith('.hdbindex'));
return remapNames(flattenResultStructure(sqls), sqlCSN, k => !k.endsWith('.hdbindex'));
}

@@ -360,41 +360,13 @@ /**

function hdiMigration(csn, options, beforeImage) {
/**
* Swap arguments in case of inverted argument order.
* This is for backward compatibility with @sap/cds@4.5.(2…3).
*
* @todo Remove in cds-compiler@2.x
* @param {HdiOptions|CSN.Model} inputOptions Options or CSN image
* @param {HdiOptions|CSN.Model} inputBeforeImage CSN image or options
* @returns {Array} Array where the real options come first
*/
function backwardCompatible(inputOptions, inputBeforeImage) {
/**
* Check whether the given argument is a CSN
*
* @param {object} arg Argument to verify
* @returns {boolean} True if it is a CSN
*/
function isBeforeImage(arg) {
return arg === null || [ 'definitions', 'meta', '$version' ].some(key => key in arg);
}
return isBeforeImage(inputBeforeImage)
? [ inputOptions, inputBeforeImage ]
: [ inputBeforeImage, inputOptions ];
}
[ options, beforeImage ] = backwardCompatible(options, beforeImage);
const internalOptions = prepareOptions.to.hdi(options);
internalOptions.toSql.csn = true;
// Prepare after-image.
// FIXME: Is this needed?
// cloneCsnMessages(csn, options, internalOptions);
const afterImage = backends.toSqlWithCsn(csn, internalOptions).csn;
const afterImage = forHdi(csn, options);
// Compare both images.
const diff = compareModels(beforeImage || afterImage, afterImage, internalOptions);
const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
// Convert the diff to SQL.
internalOptions.forHana = true; // Make it pass the SQL rendering
const { deletions, migrations, ...hdbkinds } = toSqlDdl(diff, internalOptions);
const { deletions, migrations, ...hdbkinds } = toSql.toSqlDdl(diff, internalOptions);

@@ -458,3 +430,3 @@ return {

function hdbcds(csn, options = {}) {
timetrace.start('to.hdbcds');
timetrace.timetrace.start('to.hdbcds');
const internalOptions = prepareOptions.to.hdbcds(options);

@@ -466,3 +438,3 @@ internalOptions.transformation = 'hdbcds';

const result = flattenResultStructure(toHdbcdsSource(hanaCsn, internalOptions));
timetrace.stop();
timetrace.timetrace.stop();
return result;

@@ -489,7 +461,7 @@ }

checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
servicesEdmj = backends.preparedCsnToEdm(csn, service, internalOptions);
servicesEdmj = preparedCsnToEdm(csn, service, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
servicesEdmj = backends.preparedCsnToEdm(oDataCsn, service, internalOptions);
servicesEdmj = preparedCsnToEdm(oDataCsn, service, internalOptions);
}

@@ -510,5 +482,5 @@ return servicesEdmj.edmj;

const internalOptions = prepareOptions.to.edm(options);
const { error } = makeMessageFunction(csn, internalOptions, 'for.odata');
const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
if (internalOptions.version === 'v2')
if (internalOptions.odataVersion === 'v2')
error(null, null, 'OData JSON output is not available for OData V2');

@@ -525,9 +497,7 @@

const servicesJson = backends.preparedCsnToEdmAll(oDataCsn, internalOptions);
const servicesJson = preparedCsnToEdmAll(oDataCsn, internalOptions);
const services = servicesJson.edmj;
for (const serviceName in services) {
const lEdm = services[serviceName];
// FIXME: Why only metadata_json - isn't this rather a 'combined_json' ? If so, rename it!
result[serviceName] = lEdm;
}
for (const serviceName in services)
result[serviceName] = services[serviceName];
return result;

@@ -554,7 +524,7 @@ }

checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
services = backends.preparedCsnToEdmx(csn, service, internalOptions);
services = preparedCsnToEdmx(csn, service, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = backends.preparedCsnToEdmx(oDataCsn, service, internalOptions);
services = preparedCsnToEdmx(oDataCsn, service, internalOptions);
}

@@ -586,3 +556,3 @@

const servicesEdmx = backends.preparedCsnToEdmxAll(oDataCsn, internalOptions);
const servicesEdmx = preparedCsnToEdmxAll(oDataCsn, internalOptions);
const services = servicesEdmx.edmx;

@@ -599,2 +569,75 @@ // Create annotations and metadata once per service

/**
* Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
* using 'options'
*
* @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
* @param {string} service Service name to use. If you want all services, use preparedCsnToEdmxAll()
* @param {ODataOptions} options OData / EDMX specific options.
* @returns {object} Rendered EDMX string for the given service.
*/
function preparedCsnToEdmx(csn, service, options) {
const e = csn2edm(csn, service, options);
return {
edmx: (e ? e.toXML('all') : undefined),
};
}
/**
* Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
* using 'options'.
*
* @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
* @param {ODataOptions} options OData / EDMX specific options.
* @returns {object} Dictionary of rendered EDMX strings for each service.
*/
function preparedCsnToEdmxAll(csn, options) {
const edmxResult = csn2edmAll(csn, options);
for (const service in edmxResult)
edmxResult[service] = edmxResult[service].toXML('all');
return {
edmx: edmxResult,
};
}
/**
* Generate edm-json for given 'service' based on 'csn' (new-style compact, already prepared for OData)
* using 'options'
*
* @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
* @param {string} service Service name for which EDMX should be rendered.
* @param {ODataOptions} options OData / EDMX specific options.
* @returns {object} Rendered EDM JSON object for of the given service.
*/
function preparedCsnToEdm(csn, service, options) {
// Override OData version as edm json is always v4
options.odataVersion = 'v4';
const e = csn2edm(csn, service, options);
return {
edmj: (e ? e.toJSON() : undefined),
};
}
/**
* Generate edm-json for given 'service' based on 'csn' (new-style compact, already prepared for OData)
* using 'options'
*
* @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
* @param {ODataOptions} options OData / EDMX specific options.
* @returns {object} Dictionary of rendered EDM JSON objects for each service.
*/
function preparedCsnToEdmAll(csn, options) {
// Override OData version as edm json is always v4
options.odataVersion = 'v4';
const edmj = csn2edmAll(csn, options);
for (const service in edmj)
edmj[service] = edmj[service].toJSON();
return {
edmj,
};
}
/**
* Flatten the result structure to a flat map.

@@ -632,3 +675,5 @@ *

for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
/** */
/** Deprecated, will be removed in cds-compiler@v4 */
preparedCsnToEdmx,
preparedCsnToEdm,
};

@@ -666,6 +711,11 @@

try {
if (options.deprecated) {
const messageFunctions = messages.makeMessageFunction(csn, options, 'api');
checkRemovedDeprecatedFlags( options, messageFunctions );
}
checkOutdatedOptions( options );
return processor( csn, options, ...args );
}
catch (err) {
if (err instanceof CompilationError || options.noRecompile || isPreTransformed(csn, 'odata')) // we cannot recompile a pre-transformed CSN
if (err instanceof messages.CompilationError || options.noRecompile || isPreTransformed(csn, 'odata')) // we cannot recompile a pre-transformed CSN
throw err;

@@ -677,4 +727,4 @@

const { info } = makeMessageFunction( csn, options, 'compile' );
const msg = info( 'api-recompiled-csn', emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
const { info } = messages.makeMessageFunction( csn, options, 'compile' );
const msg = info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
if (options.internalMsg)

@@ -684,4 +734,4 @@ msg.error = err; // Attach original error

// next line to be replaced by CSN parser call which reads the CSN object
const xsn = recompileX(csn, options);
const recompiledCsn = compactModel(xsn);
const xsn = compiler.recompileX(csn, options);
const recompiledCsn = toCsn.compactModel(xsn);
return processor( recompiledCsn, options, ...args );

@@ -692,4 +742,71 @@ }

// Note: No toCsn, because @sap/cds may still use it (2022-06-15)
const oldBackendOptionNames = [ 'toSql', 'toOdata', 'toHana', 'forHana' ];
/**
* Checks if outdated options are used and if so, throw a compiler error.
* These include:
* - magicVars (now variableReplacements)
* - toOdata/toSql/toHana/forHana -> now flat options
*
* @param {CSN.Options} options Backend options
*/
function checkOutdatedOptions(options) {
const { error, throwWithError } = messages.makeMessageFunction(null, options, 'api');
// This error has been emitted once, we don't need to emit it again.
if (options.messages?.some(m => m.messageId === 'api-invalid-option')) {
throwWithError();
return;
}
for (const name of oldBackendOptionNames) {
if (typeof options[name] === 'object') // may be a boolean due to internal options
error('api-invalid-option', null, { '#': 'std', name });
}
if (options.magicVars)
error('api-invalid-option', null, { '#': 'magicVars' });
// Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
// forgot about user -> $user and locale -> $user.locale
if (options.variableReplacements?.user)
error('api-invalid-option', null, { '#': 'user' });
if (options.variableReplacements?.locale)
error('api-invalid-option', null, { '#': 'locale' });
forEachKey(options.variableReplacements || {}, (name) => {
if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
error('api-invalid-option', null, { '#': 'noDollar', name });
});
throwWithError();
}
/**
* Load the module on-demand and not immediately.
*
* @param {string} moduleName Name of the module to load - like with require
* @returns {object} A Proxy that handles the on-demand loading
*/
function lazyload(moduleName) {
let module;
return new Proxy(((...args) => {
if (!module) // eslint-disable-next-line global-require
module = require(moduleName);
if (module.apply && typeof module.apply === 'function')
return module.apply(this, args);
return module; // for destructured calls
}), {
get(target, name) {
if (!module) // eslint-disable-next-line global-require
module = require(moduleName);
return module[name];
},
});
}
/**
* Option format used by the old API, where they are grouped thematically.

@@ -696,0 +813,0 @@ *

@@ -27,3 +27,3 @@ 'use strict';

'joinfk',
'magicVars', // deprecated
'magicVars', // deprecated, not removed in v3 as we have specific error messages for it
'variableReplacements',

@@ -111,7 +111,2 @@ // ODATA

for (const optionName in options) {
const optionValue = options[optionName];
mapToOldNames(optionName, optionValue);
}
// Convenience for $user -> $user.id replacement

@@ -121,34 +116,2 @@ if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')

/**
* Map a new-style option to it's old format
*
* @param {string} optionName Name of the option to map
* @param {any} optionValue Value of the option to map
*/
function mapToOldNames(optionName, optionValue) {
// Keep all input options and add the "compatibility" options
switch (optionName) {
case 'beta':
options.betaMode = optionValue;
break;
case 'odataVersion':
options.version = optionValue;
break;
case 'sqlDialect':
options.dialect = optionValue;
break;
case 'sqlMapping':
options.names = optionValue;
break;
// No need to remap variableReplacements here - we use the new mechanism with that directly
case 'magicVars':
if (optionValue.user)
options.user = optionValue.user;
if (optionValue.locale)
options.locale = optionValue.locale;
break;
default: break;
}
}
return options;

@@ -161,74 +124,41 @@ }

sql: (options) => {
const hardOptions = { src: 'sql' };
const hardOptions = { src: 'sql', toSql: true, forHana: true };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
const result = Object.assign({}, processed);
result.toSql = Object.assign({}, processed);
return result;
return Object.assign({}, processed);
},
hdi: (options) => {
const hardOptions = { src: 'hdi' };
const hardOptions = { src: 'hdi', toSql: true, forHana: true };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
const result = Object.assign({}, processed);
result.toSql = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
},
hdbcds: (options) => {
const hardOptions = { forHana: true };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
const processed = translateOptions(options, defaultOptions, {}, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdbcds');
const result = Object.assign({}, processed);
result.forHana = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdbcds');
},
edm: (options) => {
const hardOptions = { json: true, combined: true };
const hardOptions = { json: true, combined: true, toOdata: true };
const defaultOptions = { odataVersion: 'v4', odataFormat: 'flat' };
const processed = translateOptions(options, defaultOptions, hardOptions, { odataVersion: generateStringValidator([ 'v4' ]) }, [ 'valid-structured' ], 'to.edm');
const result = Object.assign({}, processed);
result.toOdata = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, { odataVersion: generateStringValidator([ 'v4' ]) }, [ 'valid-structured' ], 'to.edm');
},
edmx: (options) => {
const hardOptions = { xml: true, combined: true };
const hardOptions = { xml: true, combined: true, toOdata: true };
const defaultOptions = {
odataVersion: 'v4', odataFormat: 'flat',
};
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.edmx');
const result = Object.assign({}, processed);
result.toOdata = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.edmx');
},
},
for: { // TODO: Rename version to oDataVersion
odata: (options) => {
const hardOptions = { toOdata: true };
const defaultOptions = { odataVersion: 'v4', odataFormat: 'flat' };
const processed = translateOptions(options, defaultOptions, undefined, undefined, [ 'valid-structured' ], 'for.odata');
const result = Object.assign({}, processed);
result.toOdata = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'for.odata');
},
hana: (options) => {
const hardOptions = { forHana: true };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
const processed = translateOptions(options, defaultOptions, undefined, undefined, undefined, 'for.hana');
const result = Object.assign({}, processed);
result.forHana = Object.assign({}, processed);
return result;
return translateOptions(options, defaultOptions, hardOptions, undefined, undefined, 'for.hana');
},

@@ -235,0 +165,0 @@ },

@@ -68,9 +68,2 @@ 'use strict';

},
magicVars: {
validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
expected: () => 'type object',
found: (val) => {
return val === null ? val : `type ${ typeof val }`;
},
},
// TODO: Maybe do a deep validation of the whole object with leafs?

@@ -89,3 +82,3 @@ variableReplacements: {

},
sqlDialect: generateStringValidator([ 'sqlite', 'hana', 'plain' ]),
sqlDialect: generateStringValidator([ 'sqlite', 'hana', 'plain', 'postgres' ]),
sqlMapping: generateStringValidator([ 'plain', 'quoted', 'hdbcds' ]),

@@ -136,2 +129,3 @@ odataVersion: generateStringValidator([ 'v2', 'v4' ]),

severity: 'error',
getParameters: () => {},
getMessage: () => 'Structured OData is only supported with OData version v4',

@@ -142,3 +136,4 @@ },

severity: 'error',
getMessage: options => `sqlDialect '${ options.sqlDialect }' can't be combined with sqlMapping '${ options.sqlMapping }'`,
getParameters: options => ({ name: options.sqlDialect, prop: options.sqlMapping }),
getMessage: () => 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
},

@@ -148,2 +143,3 @@ 'beta-no-test': {

severity: 'warning',
getParameters: () => {},
getMessage: () => 'Option "beta" was used. This option should not be used in productive scenarios!',

@@ -184,3 +180,3 @@ },

if (combinationValidator.validate(options))
message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
message[combinationValidator.severity]('invalid-option-combination', null, combinationValidator.getParameters(options), combinationValidator.getMessage(options));
}

@@ -187,0 +183,0 @@

'use strict';
const { functionsWithoutParens } = require("../compiler/builtins");
const { functionsWithoutParens } = require('../compiler/builtins');

@@ -29,5 +29,5 @@ module.exports = {

'SOME',
'WHEN',
'TRIM',
'TRUE', // boolean
'WHEN',
'WHERE',

@@ -49,2 +49,3 @@ 'WITH',

'ALTER',
'ALWAYS',
'ANALYZE',

@@ -90,2 +91,3 @@ 'AND',

'EXCEPT',
'EXCLUDE',
'EXCLUSIVE',

@@ -96,2 +98,3 @@ 'EXISTS',

'FILTER',
'FIRST',
'FOLLOWING',

@@ -102,4 +105,6 @@ 'FOR',

'FULL',
'GENERATED',
'GLOB',
'GROUP',
'GROUPS',
'HAVING',

@@ -122,2 +127,3 @@ 'IF',

'KEY',
'LAST',
'LEFT',

@@ -127,2 +133,3 @@ 'LIKE',

'MATCH',
'MATERIALIZED',
'NATURAL',

@@ -134,2 +141,3 @@ 'NO',

'NULL',
'NULLS',
'OF',

@@ -140,2 +148,3 @@ 'OFFSET',

'ORDER',
'OTHERS',
'OUTER',

@@ -159,2 +168,3 @@ 'OVER',

'RESTRICT',
'RETURNING',
'RIGHT',

@@ -171,2 +181,3 @@ 'ROLLBACK',

'THEN',
'TIES',
'TO',

@@ -188,12 +199,3 @@ 'TRANSACTION',

'WITH',
'WITHOUT',
'ALWAYS',
'EXCLUDE',
'FIRST',
'GENERATED',
'GROUPS',
'LAST',
'NULLS',
'OTHERS',
'TIES',
'WITHOUT'
],

@@ -206,86 +208,4 @@ // SAP HANA keywords, used for smart quoting in to-hdi.plain

hana: [
'ALL',
'ALTER',
'AS',
'BEFORE',
'BEGIN',
'BOTH',
'CASE',
'CHAR',
'CONDITION',
'CONNECT',
'CROSS',
'CUBE',
'CURRENT_CONNECTION',
'CURRENT_DATE',
'CURRENT_SCHEMA',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_TRANSACTION_ISOLATION_LEVEL',
'CURRENT_USER',
'CURRENT_UTCDATE',
'CURRENT_UTCTIME',
'CURRENT_UTCTIMESTAMP',
'CURRVAL',
'CURSOR',
'DECLARE',
'DISTINCT',
'ELSE',
'ELSEIF',
'END',
'EXCEPT',
'EXCEPTION',
'EXEC',
'FALSE',
'FOR',
'FROM',
'FULL',
'GROUP',
'HAVING',
'IF',
'IN',
'INNER',
'INOUT',
'INTERSECT',
'INTO',
'IS',
'JOIN',
'LEADING',
'LEFT',
'LIMIT',
'LOOP',
'MINUS',
'NATURAL',
'NCHAR',
'NEXTVAL',
'NULL',
'ON',
'ORDER',
'OUT',
'PRIOR',
'RETURN',
'RETURNS',
'REVERSE',
'RIGHT',
'ROLLUP',
'ROWID',
'SELECT',
'SESSION_USER',
'SET',
'SQL',
'START',
'SYSUUID',
'TABLESAMPLE',
'TOP',
'TRAILING',
'TRUE',
'UNION',
'UNKNOWN',
'USING',
'UTCTIMESTAMP',
'VALUES',
'WHEN',
'WHERE',
'WHILE',
'WITH',
'ABAPITAB',
'ABAPSTRUCT',
'ABAP_CHAR',

@@ -305,4 +225,2 @@ 'ABAP_DATE',

'ABAP_XSTRING',
'ABAPITAB',
'ABAPSTRUCT',
'ADD_DAYS',

@@ -313,2 +231,4 @@ 'ADD_MONTHS',

'ADOPT',
'ALL',
'ALTER',
'ANALYTIC',

@@ -319,2 +239,3 @@ 'ANY',

'ARRAY_AGG',
'AS',
'AT',

@@ -325,2 +246,4 @@ 'AUTHORIZATION',

'BASIC',
'BEFORE',
'BEGIN',
'BETWEEN',

@@ -339,2 +262,3 @@ 'BIGINT',

'BOOLEAN',
'BOTH',
'BOUNDARY',

@@ -344,2 +268,3 @@ 'BREAKUP',

'BY',
'CASE',
'CAST',

@@ -349,2 +274,3 @@ 'CE_CALC',

'CE_PROJECTION',
'CHAR',
'CHARACTER',

@@ -355,5 +281,8 @@ 'CLOB',

'CONCAT',
'CONDITION',
'CONNECT',
'CONSTANT',
'CONSTRAINT',
'COUNT',
'CROSS',
'CS_DATE',

@@ -380,5 +309,18 @@ 'CS_DAYDATE',

'CS_TIME',
'CUBE',
'CUME_DIST',
'CURDATE',
'CURRENT_CONNECTION',
'CURRENT_DATABASE',
'CURRENT_DATE',
'CURRENT_SCHEMA',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_TRANSACTION_ISOLATION_LEVEL',
'CURRENT_USER',
'CURRENT_UTCDATE',
'CURRENT_UTCTIME',
'CURRENT_UTCTIMESTAMP',
'CURRVAL',
'CURSOR',
'CURTIME',

@@ -441,10 +383,19 @@ 'CURVE',

'DECIMAL',
'DECLARE',
'DENSE_RANK',
'DEPTH',
'DISTANCE',
'DISTINCT',
'DOUBLE',
'DW_OPTIMIZED',
'ELSE',
'ELSEIF',
'EMPTY',
'END',
'EXCEPT',
'EXCEPTION',
'EXEC',
'EXISTS',
'EXTRACT',
'FALSE',
'FILTER',

@@ -454,6 +405,10 @@ 'FIRST_VALUE',

'FLOAT',
'FOR',
'FORCE_FIRST_PASSWORD_CHANGE',
'FREESTYLESEARCHATTRIBUTE',
'FROM',
'FULL',
'GET_NUM_SERVERS',
'GREATEST',
'GROUP',
'GROUPING',

@@ -464,2 +419,3 @@ 'GROUPING_FILTER',

'HASSYSTEMPRIVILEGE',
'HAVING',
'HEXTOBIN',

@@ -478,3 +434,7 @@ 'HIERARCHY',

'HOUR',
'IF',
'IFNULL',
'IN',
'INNER',
'INOUT',
'INSTR',

@@ -484,5 +444,9 @@ 'INT',

'INTERNAL',
'IS_EMPTY',
'INTERSECT',
'INTO',
'IS',
'ISAUTHORIZED',
'ISTOTAL',
'IS_EMPTY',
'JOIN',
'JSON_QUERY',

@@ -497,10 +461,14 @@ 'JSON_TABLE',

'LEAD',
'LEADING',
'LEAST',
'LEAVES',
'LEFT',
'LENGTH',
'LENGTHB',
'LEVELS',
'LIMIT',
'LOCATE',
'LOCATE_REGEXPR',
'LONGDATE',
'LOOP',
'LPAD',

@@ -514,6 +482,10 @@ 'LTRIM',

'MIN',
'MINUS',
'MINUTE',
'MONTH',
'MULTIPARENT',
'NATURAL',
'NCHAR',
'NCLOB',
'NEXTVAL',
'NEXT_DAY',

@@ -526,2 +498,3 @@ 'NO',

'NTILE',
'NULL',
'NULLIF',

@@ -535,10 +508,14 @@ 'NUMBER',

'OLYMP',
'ON',
'OPENCYPHER_TABLE',
'ORDER',
'ORDINALITY',
'ORPHAN',
'OUT',
'OVER',
'PERCENT_RANK',
'PERCENTILE_CONT',
'PERCENTILE_DISC',
'PERCENT_RANK',
'PLAIN',
'PRIOR',
'PRODUCT',

@@ -557,5 +534,11 @@ 'RANGE',

'REPLACE_REGEXPR',
'RETURN',
'RETURNS',
'REVERSE',
'RIGHT',
'ROLLUP',
'ROUND',
'ROUNDROBIN',
'ROW',
'ROWID',
'ROW_NUMBER',

@@ -570,2 +553,3 @@ 'RPAD',

'SECONDTIME',
'SELECT',
'SERIES_ELEMENT_TO_PERIOD',

@@ -575,2 +559,4 @@ 'SERIES_PERIOD_TO_ELEMENT',

'SESSION_CONTEXT',
'SESSION_USER',
'SET',
'SIBLING',

@@ -580,2 +566,7 @@ 'SMALLDECIMAL',

'SOME',
'SQL',
'START',
'STDDEV',
'STRING',
'STRING_AGG',
'ST_ALPHASHAPEAGGR',

@@ -621,15 +612,14 @@ 'ST_ALPHASHAPEAREAAGGR',

'ST_UNIONAGGR',
'STDDEV',
'STRING',
'STRING_AGG',
'SUBSTR',
'SUBSTRING',
'SUBSTRING_REGEXPR',
'SUBSTR_AFTER',
'SUBSTR_BEFORE',
'SUBSTR_REGEXPR',
'SUBSTRING',
'SUBSTRING_REGEXPR',
'SUM',
'SYSTEM_TIME',
'SYSUUID',
'TABLE',
'TABLES',
'TABLESAMPLE',
'TARGET',

@@ -652,2 +642,7 @@ 'TEMPORARY',

'TM_GET_SUGGESTED_TERMS',
'TOKEN',
'TOOLOPTION',
'TOP',
'TOPK',
'TOTAL',
'TO_BIGINT',

@@ -678,9 +673,6 @@ 'TO_BINARY',

'TO_VARCHAR',
'TOKEN',
'TOOLOPTION',
'TOPK',
'TOTAL',
'TRACE',
'TRACEPROFILE',
'TRACES',
'TRAILING',
'TRANSACTION',

@@ -690,4 +682,10 @@ 'TREE',

'TRIM',
'TRUE',
'UNION',
'UNKNOWN',
'UNNEST',
'USER',
'USING',
'UTCTIMESTAMP',
'VALUES',
'VAR',

@@ -701,4 +699,8 @@ 'VARBINARY',

'WEEKDAY',
'WHEN',
'WHERE',
'WHILE',
'WHY_FOUND',
'WINDOW',
'WITH',
'WITHIN',

@@ -737,3 +739,108 @@ 'XMLTABLE',

'WHERE', 'WHILE', 'WITH'
],
// Postgres keywords, used for smart quoting in to-sql.plain.postgres
// Taken from https://www.postgresql.org/docs/current/sql-keywords-appendix.html
// Generated via scripts/keywords/postgres/generateKeywords.js
postgres: [
'ALL',
'ANALYSE',
'ANALYZE',
'AND',
'ANY',
'ARRAY',
'AS',
'ASC',
'ASYMMETRIC',
'AUTHORIZATION',
'BINARY',
'BOTH',
'CASE',
'CAST',
'CHECK',
'COLLATE',
'COLLATION',
'COLUMN',
'CONCURRENTLY',
'CONSTRAINT',
'CREATE',
'CROSS',
'CURRENT_CATALOG',
'CURRENT_DATE',
'CURRENT_ROLE',
'CURRENT_SCHEMA',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_USER',
'DEFAULT',
'DEFERRABLE',
'DESC',
'DISTINCT',
'DO',
'ELSE',
'END',
'EXCEPT',
'FALSE',
'FETCH',
'FOR',
'FOREIGN',
'FREEZE',
'FROM',
'FULL',
'GRANT',
'GROUP',
'HAVING',
'ILIKE',
'IN',
'INITIALLY',
'INNER',
'INTERSECT',
'INTO',
'IS',
'ISNULL',
'JOIN',
'LATERAL',
'LEADING',
'LEFT',
'LIKE',
'LIMIT',
'LOCALTIME',
'LOCALTIMESTAMP',
'NATURAL',
'NOT',
'NOTNULL',
'NULL',
'OFFSET',
'ON',
'ONLY',
'OR',
'ORDER',
'OUTER',
'OVERLAPS',
'PLACING',
'PRIMARY',
'REFERENCES',
'RETURNING',
'RIGHT',
'SELECT',
'SESSION_USER',
'SIMILAR',
'SOME',
'SYMMETRIC',
'TABLE',
'TABLESAMPLE',
'THEN',
'TO',
'TRAILING',
'TRUE',
'UNION',
'UNIQUE',
'USER',
'USING',
'VARIADIC',
'VERBOSE',
'WHEN',
'WHERE',
'WINDOW',
'WITH'
]
}

@@ -48,2 +48,3 @@ // Central registry for messages.

'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
'anno-unstable-array': { severity: 'Warning' },
'anno-invalid-sql-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"

@@ -71,3 +72,3 @@ 'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"

'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },

@@ -85,2 +86,3 @@ 'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },

'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
'type-ignoring-argument': { severity: 'Error', configurableFor: true },
'type-expected-builtin': { severity: 'Error', configurableFor: true },

@@ -122,2 +124,3 @@ 'expected-actionparam-type': { severity: 'Error' },

'ref-rejected-on': { severity: 'Error' },
'ref-expected-element': { severity: 'Error' },

@@ -135,10 +138,7 @@ 'rewrite-key-not-covered-explicit': { severity: 'Error', configurableFor: 'deprecated' },

'syntax-anno-after-enum': { severity: 'Error', configurableFor: true }, // does not hurt
'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-expected-length': { severity: 'Error' },
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
'syntax-expected-cardinality': { severity: 'Error' },
'syntax-expected-length': { severity: 'Error' },
'syntax-expected-translation': { severity: 'Error' },
'syntax-required-subproperty': { severity: 'Error' },
'syntax-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },

@@ -156,2 +156,4 @@ 'syntax-fragile-alias': { severity: 'Error', configurableFor: true },

'syntax-expected-integer': { severity: 'Error' },
'syntax-invalid-masked': { severity: 'Error', configurableFor: true },
'syntax-unexpected-null': { severity: 'Error', configurableFor: true },

@@ -202,3 +204,13 @@ 'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config

const centralMessageTexts = {
'api-invalid-option': {
std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
},
'anno-duplicate': 'Duplicate assignment with $(ANNO)',
'anno-duplicate-unrelated-layer': 'Duplicate assignment with $(ANNO)',
'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',

@@ -208,11 +220,21 @@ 'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',

'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
'syntax-csn-expected-object': 'Expected object for property $(PROP)',
'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
'syntax-csn-expected-natnum': 'Expected non-negative number for property $(PROP)',
'syntax-csn-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
'syntax-csn-expected-reference': 'Expected non-empty string or object for property $(PROP)',
'syntax-csn-expected-term': 'Expected non-empty string or object for property $(PROP)',
'syntax-anno-after-struct': 'Avoid annotation assignments after structure definitions',
'syntax-anno-after-enum': 'Avoid annotation assignments after enum definitions',
'syntax-anno-after-params': 'Avoid annotation assignments after parameters',
'name-duplicate-element': {
'std': 'Generated element $(NAME) conflicts with another element',
'flatten-element-gen': 'Generated element $(NAME) conflicts with other generated element',
'flatten-element-exist': 'Flattened name of structured element conflicts with existing element $(NAME)',
'flatten-fkey-gen': 'Duplicate definition of foreign key element $(NAME) for association $(ART)',
'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
},
'syntax-unexpected-ellipsis': {
std: 'Expected no more than one $(CODE)',
'nested-array': 'Unexpected $(CODE) in nested array'
},
'syntax-expected-object': 'Expected object for property $(PROP)',
'syntax-expected-column': 'Expected object or string \'*\' for property $(PROP)',
'syntax-expected-natnum': 'Expected non-negative number for property $(PROP)',
'syntax-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
'syntax-expected-reference': 'Expected non-empty string or object for property $(PROP)',
'syntax-expected-term': 'Expected non-empty string or object for property $(PROP)',
'syntax-dollar-ident': {

@@ -224,3 +246,3 @@ std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',

},
'syntax-csn-expected-length': {
'syntax-expected-length': {
std: 'Expected array in $(PROP) to have at least $(N) items',

@@ -257,2 +279,16 @@ one: 'Expected array in $(PROP) to have at least one item',

},
'syntax-invalid-literal': {
'std': 'Invalid literal',
'uneven-hex': 'A binary literal must have an even number of characters',
'invalid-hex': 'A binary literal must only contain characters 0-9, a-f and A-F',
'time': 'Expected time\'hh:mm:ss\' where hh, mm and the optional ss are numbers',
'date': 'Expected date\'YYYY-MM-DD\' where YYYY, MM and DD are numbers',
'timestamp': 'Expected timestamp\'YYYY-MM-DD hh:mm:ss.u…u\' where YYYY, MM, DD, hh, mm, ss and u are numbers (optional 1-7×u)',
},
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
'syntax-unexpected-vocabulary': {
std: 'Annotations can\'t be defined inside contexts or services',
service: 'Annotations can\'t be defined inside services',
context: 'Annotations can\'t be defined inside contexts',
},
'ref-undefined-def': {

@@ -279,2 +315,6 @@ std: 'Artifact $(ART) has not been found',

},
'ref-expected-element': {
std: 'Expected element reference',
magicVar: 'Only elements of magic variable $(ID) can be selected',
},
'type-unexpected-typeof': {

@@ -437,3 +477,3 @@ std: 'Unexpected $(KEYWORD) for the type reference here',

* Only has an effect if default severity is 'Error'.
* 'deprecated': severity can only be changed with deprecated.downgradableErrors.
* 'deprecated': severity can only be changed with deprecated._downgradableErrors.
* TODO: Value `true` is temporary. Use an array instead.

@@ -440,0 +480,0 @@ * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.

@@ -30,3 +30,3 @@ // Functions and classes for syntax messages

*
* @param {CSN.Message[]} messages
* @param {CompileMessage[]} messages
* @returns {boolean}

@@ -43,3 +43,3 @@ */

*
* @param {CSN.Message[]} messages
* @param {CompileMessage[]} messages
* @param {string} moduleName

@@ -324,11 +324,14 @@ * @returns {boolean}

* @param {CSN.Options} [options]
* @param {string} [moduleName]
* @param {string|null} [moduleName]
*/
function makeMessageFunction( model, options, moduleName = null ) {
// ensure message consistency during runtime with --test-mode
if (options.testMode)
if (options.testMode) {
// ensure message consistency during runtime with --test-mode
_check$Init( options );
if (!options.messages)
throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
}
const hasMessageArray = !!options.messages;
const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );
const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
/**

@@ -338,3 +341,3 @@ * Array of collected compiler messages. Only use it for debugging. Will not

*
* @type {CSN.Message[]}
* @type {CompileMessage[]}
*/

@@ -368,3 +371,3 @@ let messages = options.messages || [];

/** @type {CSN.Message} */
/** @type {CompileMessage} */
const msg = new CompileMessage( fileLocation, text, severity, id, semanticLocation, moduleName );

@@ -561,3 +564,3 @@ if (options.internalMsg)

* @param {...any} args
* @returns {CSN.Message[]}
* @returns {CompileMessage[]}
*/

@@ -588,2 +591,18 @@ function callTransparently(callback, ...args) {

/**
* Check the consistency of the given message and run some basic lint checks. These include:
*
* - Long message IDs must be listed centrally.
* - Messages with the same ID must have the same severity (in a module).
* - Messages with the same ID must have the same message texts.
* This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
* use the same ID for different meanings, i.e. texts.
*
* @param {string} id
* @param {string} moduleName
* @param {string} severity
* @param {string|object} texts
* @param {CSN.Options} options
* @private
*/
function _check$Consistency( id, moduleName, severity, texts, options ) {

@@ -599,2 +618,12 @@ if (id.length > 30 && !centralMessages[id])

/**
* Check the consistency of the message severity for the given message ID.
* Messages with the same ID must have the same severity (in a module).
* Non-downgradable errors must never be called with a lower severity.
*
* @param {string} id
* @param {string} moduleName
* @param {string} severity
* @private
*/
function _check$Severities( id, moduleName, severity ) {

@@ -609,3 +638,3 @@ if (!severity) // if just used message(), we are automatically consistent

else if (expected !== severity)
throw new CompilerAssertion( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
throw new CompilerAssertion( `Inconsistent severity: Expecting "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
return;

@@ -616,12 +645,24 @@ }

if (severity !== 'Error')
throw new CompilerAssertion( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}
else if (spec.severity === 'Error') {
throw new CompilerAssertion( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
}
else if (spec.severity !== severity) {
throw new CompilerAssertion( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
throw new CompilerAssertion( `Inconsistent severity: Expecting "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}
}
/**
* Check the consistency of the message text for the given message ID.
*
* Messages with the same ID must have the same message texts.
* This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
* use the same ID for different meanings, i.e. texts.
*
* @param {string} id
* @param {string} prop
* @param {string} value
* @private
*/
function _check$Texts( id, prop, value ) {

@@ -634,3 +675,3 @@ if (!test$texts[id])

else if (expected !== value)
throw new CompilerAssertion( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
}

@@ -833,3 +874,3 @@

*
* @param {CSN.Message} err
* @param {CompileMessage} err
* @param {boolean} [normalizeFilename]

@@ -856,3 +897,3 @@ * @param {boolean} [noMessageId]

*
* @param {CSN.Message} msg
* @param {CompileMessage} msg
* @returns {string} can be used to uniquely identify a message

@@ -883,3 +924,3 @@ */

*
* @param {CSN.Message} err
* @param {CompileMessage} err
* @param {object} [config = {}]

@@ -932,3 +973,3 @@ * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.

* from `lib/utils/file.js`
* @param {CSN.Message} err Error object containing all details like line, message, etc.
* @param {CompileMessage} err Error object containing all details like line, message, etc.
* @param {object} [config = {}]

@@ -1012,4 +1053,4 @@ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no

*
* @param {CSN.Message} a
* @param {CSN.Message} b
* @param {CompileMessage} a
* @param {CompileMessage} b
*/

@@ -1048,4 +1089,4 @@ function compareMessage( a, b ) {

*
* @param {CSN.Message} a
* @param {CSN.Message} b
* @param {CompileMessage} a
* @param {CompileMessage} b
*/

@@ -1062,3 +1103,3 @@ function compareMessageSeverityAware( a, b ) {

*
* @param {CSN.Message} msg
* @param {CompileMessage} msg
*/

@@ -1082,3 +1123,3 @@ function homeSortName( { home, messageId } ) {

*
* @param {CSN.Message[]} messages
* @param {CompileMessage[]} messages
*/

@@ -1085,0 +1126,0 @@ function deduplicateMessages( messages ) {

'use strict';
const { forEach } = require("../utils/objectUtils");
const queryOps = {

@@ -23,3 +25,2 @@ query: 'select', // TODO: rename to SELECT

toRename: true,
addTextsLanguageAssoc: true,
assocsWithParams: true,

@@ -62,3 +63,3 @@ hanaAssocRealCardinality: true,

* @param {object} options Options
* @param {string} [feature] Feature to check for
* @param {string|null} [feature] Feature to check for
* @returns {boolean}

@@ -70,5 +71,45 @@ */

return !!deprecated;
return deprecated && typeof deprecated === 'object' && deprecated[feature];
}
const oldDeprecatedFlags_v2 = [
'createLocalizedViews',
'downgradableErrors',
'generatedEntityNameWithUnderscore',
'longAutoexposed',
'noElementsExpansion',
'noInheritedAutoexposeViaComposition',
'noScopedRedirections',
'oldVirtualNotNullPropagation',
'parensAsStrings',
'projectionAsQuery',
'redirectInSubQueries',
'renderVirtualElements',
'shortAutoexposed',
'unmanagedUpInComponent',
'v1KeysForTemporal',
];
/**
* In cds-compiler v3, we removed old v2 deprecated flags. That can lead to silent
* errors such as entity/view names changing. To ensure that the user is forced
* to change their code, emit an error if one of such removed flags was used.
*
* @param {CSN.Options} options
* @param error Error message function returned by makeMessageFunctions().
*/
function checkRemovedDeprecatedFlags( options, { error } ) {
// Assume that we emitted these errors once if a message with this ID was found.
if (!options.deprecated || options.messages?.some(m => m.messageId === 'api-invalid-deprecated'))
return;
forEach(options.deprecated, (key, val) => {
if (val && oldDeprecatedFlags_v2.includes(key)) {
error('api-invalid-deprecated', null, { name: key },
'Deprecated flag $(NAME) has been removed in CDS compiler v3');
}
});
}
// Apply function `callback` to all artifacts in dictionary

@@ -135,2 +176,3 @@ // `model.definitions`. See function `forEachGeneric` for details.

isDeprecatedEnabled,
checkRemovedDeprecatedFlags,
queryOps,

@@ -137,0 +179,0 @@ forEachDefinition,

@@ -19,4 +19,4 @@ 'use strict';

// const isMultiSchema = this.options.toOdata.odataFormat === 'structured' &&
// (this.options.toOdata.odataProxies || this.options.toOdata.odataXServiceRefs);
// const isMultiSchema = this.options.odataFormat === 'structured' &&
// (this.options.odataProxies || this.options.odataXServiceRefs);

@@ -89,5 +89,7 @@ const serviceName = this.csnUtils.getServiceName(artName);

function checkReturns(returns, currPath, actKind) {
const finalReturnType = returns.type ? this.csnUtils.getFinalBaseType(returns.type) : returns;
const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
if (!finalReturnType)
return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
this.error(null, currPath, { '#': actKind },

@@ -103,3 +105,3 @@ {

checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
else // check if return type is user definited from the current service
else // check if return type is user defined from the current service
checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);

@@ -106,0 +108,0 @@ }

@@ -26,3 +26,3 @@ 'use strict';

};
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },

@@ -29,0 +29,0 @@ 'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');

@@ -17,2 +17,3 @@ 'use strict';

const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
// TODO: Why not filter over persistenceAnnos, is shorter!
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);

@@ -19,0 +20,0 @@ if (TableUdfCv.length > 1)

@@ -39,6 +39,6 @@ 'use strict';

if (member.key || parentIsKey) {
const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType)) {
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
if (isGeoTypeName(finalBaseType?.type)) {
this.error(null, parentPath || member.$path,
{ type: finalBaseType, name: elemFqName },
{ type: finalBaseType.type, name: elemFqName },
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');

@@ -67,3 +67,3 @@ }

if (member.key || parentIsKey) {
const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
if (member.items || (finalBaseType && finalBaseType.items)) {

@@ -101,6 +101,6 @@ this.error(null, parentPath || member.$path, { name: elemFqName },

* Checks whether managed associations
* with cardinality 'to many' have an on-condition
* and if managed associations have foreign keys.
* with cardinality 'to many' have an on-condition.
*
* @param {CSN.Artifact} art The artifact
* @todo this is a member validator, is it not?
*/

@@ -107,0 +107,0 @@ function checkManagedAssoc(art) {

@@ -29,3 +29,3 @@ 'use strict';

if (target.kind !== 'entity') {
const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
this.error(

@@ -32,0 +32,0 @@ null,

@@ -24,3 +24,3 @@ 'use strict';

// Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
if (_art.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
this.error(null, expression[i].$path, { elemref: { ref } },

@@ -27,0 +27,0 @@ 'Unexpected usage of structured type $(ELEMREF)');

@@ -6,3 +6,4 @@ 'use strict';

* Make sure that all source artifacts and association targets reach the database
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
* (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
*
* Check the given query for:

@@ -9,0 +10,0 @@ * - Associations-traversal over skipped/abstract things

@@ -8,5 +8,9 @@ 'use strict';

/**
* Validate select items of a query.
* Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
* Furthermore, for to.hdbcds, window functions are not allowed.
*
* For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
*
* @param {CSN.Query} query query object
* @todo Why do we care about this with $self?
*/

@@ -13,0 +17,0 @@ function validateSelectItems(query) {

@@ -132,4 +132,4 @@ 'use strict';

const { getFinalBaseType } = getUtils(model);
const typeOfType = getFinalBaseType(artOrElement.type, path);
const { getFinalBaseTypeWithProps } = getUtils(model);
const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);

@@ -161,2 +161,3 @@ if (typeOfType === null) {

* @param {boolean} isElement indicates whether we are dealing with an element or an artifact
* @todo Rename, is an error not a warning
*/

@@ -178,2 +179,3 @@ function warnAboutMissingType(error, path, name, isElement = false) {

* @returns {boolean} indicates whether the artifact has type information
* @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
*/

@@ -180,0 +182,0 @@ function hasArtifactTypeInformation(artifact) {

@@ -34,3 +34,3 @@ 'use strict';

/**
* Artifact is structured or a managed association/compoisition
* Artifact is structured or a managed association/composition
*

@@ -53,3 +53,3 @@ * @param {CSN.Artifact} art Artifact

if (art && art.type && !isBuiltinType(art.type))
return this.getFinalBaseType(art);
return this.csnUtils.getFinalBaseTypeWithProps(art.type);

@@ -56,0 +56,0 @@ return art;

@@ -106,4 +106,5 @@ 'use strict';

// TODO: checkManagedAssoc is a forEachMemberRecursively!
const commonArtifactValidators = [ checkTypeDefinitionHasType, checkPrimaryKey, checkManagedAssoc ];
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
const commonQueryValidators = [ validateMixinOnCondition ];

@@ -110,0 +111,0 @@

@@ -121,4 +121,6 @@ // Consistency checker on model (XSN = augmented CSN)

},
location: { // location req if at least one property:
isRequired: parent => noSyntaxErrors() || Object.keys( parent ).length,
location: {
// every thing with a $location in CSN must have a XSN location even
// with syntax errors (currently even internal artifacts like $using):
isRequired: parent => noSyntaxErrors() || parent && parent.kind,
kind: true,

@@ -189,3 +191,6 @@ requires: [ 'file' ], // line is optional in top-level location

requires: [ 'kind', 'name' ],
optional: [ 'elements', '$autoElement', '$uncheckedElements', '_effectiveType', '_deps' ],
optional: [
'elements', '$autoElement', '$uncheckedElements',
'$requireElementAccess', '_effectiveType', '_deps',
],
schema: {

@@ -196,2 +201,3 @@ kind: { test: isString, enum: [ 'builtin' ] },

$uncheckedElements: { test: isBoolean },
$requireElementAccess: { test: isBoolean },
// missing location for normal "elements"

@@ -223,4 +229,3 @@ elements: { test: TODO },

requires: [ 'kind', 'location' ],
optional: [ 'name', 'extern', 'usings', '$annotations', 'fileDep' ],
// TODO: get rid of $annotations: []
optional: [ 'name', 'extern', 'usings', 'fileDep' ],
},

@@ -311,3 +316,2 @@ extern: {

requires: [ 'location', 'name' ],
optional: [ '$annotations' ], // TODO: get rid of annos: []
},

@@ -457,7 +461,7 @@ orderBy: { inherits: 'value', test: isArray( expression ) },

inherits: 'value',
optional: [ 'name', '_block', '$priority', '$duplicate', '$inferred', '$duplicates' ],
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
// TODO: name requires if not in parser?
},
$priority: { test: TODO }, // TODO: rename to $priority
$annotations: { parser: true, kind: true, test: TODO },
$priority: { test: TODO },
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
name: {

@@ -477,3 +481,3 @@ isRequired: stageParser && (() => false), // not required in parser

absolute: { test: isString },
variant: { test: TODO }, // TODO: not set in CDL parser, only in $annotations
variant: { test: TODO }, // TODO: not set in CDL parser
element: { test: TODO }, // TODO: { test: isString },

@@ -591,2 +595,3 @@ action: { test: isString },

$errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
$duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true

@@ -593,0 +598,0 @@ $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status

@@ -79,16 +79,89 @@ // The builtin artifacts of CDS

const specialFunctions = {
const specialFunctions = compileFunctions( {
'': [ // the default
{
intro: [ 'ALL', 'DISTINCT' ],
introMsg: [], // do not list them in code completion
},
{},
],
ROUND: [
null, null, { // 3rd argument: rounding mode
ROUND_HALF_UP: 'argFull',
ROUND_HALF_DOWN: 'argFull',
ROUND_HALF_EVEN: 'argFull',
ROUND_UP: 'argFull',
ROUND_DOWN: 'argFull',
ROUND_CEILING: 'argFull',
ROUND_FLOOR: 'argFull',
expr: [ 'ROUND_HALF_UP', 'ROUND_HALF_DOWN', 'ROUND_HALF_EVEN',
'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR' ],
},
],
};
TRIM: [
{
intro: [ 'LEADING', 'TRAILING', 'BOTH' ],
expr: [ 'LEADING', 'TRAILING', 'BOTH' ],
separator: [ 'FROM' ],
},
],
EXTRACT: [
{
expr: [ 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND' ],
separator: [ 'FROM' ],
},
],
COUNT: [
{
expr: [ '*' ],
intro: [ 'ALL', 'DISTINCT' ],
},
],
MIN: 'COUNT',
MAX: 'COUNT',
SUM: 'COUNT',
AVG: 'COUNT',
STDDDEV: 'COUNT',
VAR: 'COUNT',
LOCATE_REGEXPR: [
{
intro: [ 'START', 'AFTER' ],
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
},
],
OCCURRENCES_REGEXPR: [
{
separator: [ 'FLAG', 'IN', 'FROM' ],
},
],
REPLACE_REGEXPR: [
{
separator: [ 'FLAG', 'IN', 'WITH', 'FROM', 'OCCURRENCE' ],
expr: [ 'ALL' ],
},
],
SUBSTRING_REGEXPR: [
{
separator: [ 'FLAG', 'IN', 'FROM', 'OCCURRENCE', 'GROUP' ],
},
],
} );
function compileFunctions( special ) {
const compiled = {};
for (const [ name, val ] of Object.entries( special ))
compiled[name] = (typeof val === 'string' ? special[val] : val).map( compileArg );
return compiled;
}
function compileArg( src ) {
if (!src)
return src;
const tgt = {
intro: src.intro || [],
expr: src.expr || [],
separator: src.separator || [],
};
for (const generic of [ 'intro', 'expr', 'separator' ]) { // intro before expr!
for (const token of src[generic] || [])
tgt[token] = generic;
}
if (tgt.intro) // same token could be in both 'expr' and 'intro':
tgt.introMsg = src.introMsg || tgt.intro.filter( token => tgt[token] === 'intro' );
return tgt;
}
/**

@@ -110,2 +183,4 @@ * Variables that have special meaning in CDL/CSN.

},
// Require that elements are accessed, i.e. no $at, only $at.<element>.
$requireElementAccess: true,
},

@@ -117,2 +192,3 @@ $now: {}, // Dito

$uncheckedElements: true,
$requireElementAccess: true,
},

@@ -292,2 +368,4 @@ };

art.$uncheckedElements = magic.$uncheckedElements;
if (magic.$requireElementAccess)
art.$requireElementAccess = magic.$requireElementAccess;

@@ -294,0 +372,0 @@ createMagicElements( art, magic.elements );

@@ -508,3 +508,3 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN

if (art.kind !== 'namespace' &&
isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' )) {
isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
let p = parent;

@@ -515,3 +515,3 @@ while (p && kindProperties[p.kind].artifacts)

error( 'subartifacts-not-supported', [ art.name.location, art ],
{ art: p, prop: 'deprecated.generatedEntityNameWithUnderscore' },
{ art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
// eslint-disable-next-line max-len

@@ -518,0 +518,0 @@ 'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );

@@ -10,4 +10,4 @@ // Extend, include, localized data and managed compositions

const {
isDeprecatedEnabled, isBetaEnabled,
forEachGeneric, forEachInOrder,
isDeprecatedEnabled,
forEachGeneric, forEachInOrder, forEachDefinition,
} = require('../base/model');

@@ -51,5 +51,4 @@ const { dictAdd } = require('../base/dictionaries');

const commonLanguagesEntity // TODO: remove beta after a grace period
= (options.addTextsLanguageAssoc || isBetaEnabled( options, 'addTextsLanguageAssoc' )) &&
model.definitions['sap.common.Languages'];
const commonLanguagesEntity = options.addTextsLanguageAssoc &&
model.definitions['sap.common.Languages'];
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&

@@ -61,2 +60,4 @@ commonLanguagesEntity.elements.code);

compositionChildPersistence();
/**

@@ -94,2 +95,21 @@ * Process "composition of" artifacts.

/**
* Copy `@cds.persistence.skip` and `@cds.persistence.skip` from parent to child
* for managed compositions. This needs to be done after extensions, i.e. annotations,
* have been applied or `annotate E.comp` would not have an effect on `E.comp.subComp`.
*/
function compositionChildPersistence() {
const processed = new WeakSet();
forEachDefinition(model, processCompositionPersistence);
function processCompositionPersistence(def) {
if (def.$inferred === 'composition-entity' && !processed.has(def)) {
if (def._parent)
processCompositionPersistence(def._parent);
copyPersistenceAnnotations(def, def._parent, options);
processed.add(def);
}
}
}
// extend ------------------------------------------------------------------

@@ -499,3 +519,3 @@

const textsName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
const textsName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
? `${ art.name.absolute }_texts`

@@ -509,4 +529,5 @@ : `${ art.name.absolute }.texts`;

return; // -> make it idempotent
createTextsEntity( art, textsName, localized, fioriEnabled );
const newTextsEntity = createTextsEntity( art, textsName, localized, fioriEnabled );
addTextsAssociations( art, textsName, localized );
copyPersistenceAnnotations(newTextsEntity, art, options);
}

@@ -645,3 +666,3 @@

}
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
setLink( art, '_base', base );

@@ -705,2 +726,4 @@

annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
return art;
}

@@ -819,3 +842,3 @@

return;
const entityName = (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
const entityName = (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
? `${ base.name.absolute }_${ elem.name.id }`

@@ -943,3 +966,3 @@ : `${ base.name.absolute }.${ elem.name.id }`;

// primary keys and add the ON condition
if (isDeprecatedEnabled( options, 'unmanagedUpInComponent' )) {
if (isDeprecatedEnabled( options, '_unmanagedUpInComponent' )) {
addProxyElements( art, keys, 'aspect-composition', target.name && location,

@@ -955,3 +978,3 @@ 'up__', '@odata.containment.ignore' );

}
if (isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ))
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
setLink( art, '_base', base._base || base );

@@ -965,2 +988,6 @@

initArtifact( art );
// Copy persistence annotations from aspect.
copyPersistenceAnnotations(art, target, options);
return art;

@@ -987,2 +1014,23 @@ }

/**
* Copy the annotations `@cds.persistence.skip`/`@cds.persistence.exists` from
* source to target if present on source but not target.
*
* @param {object} target
* @param {object} source
* @param {CSN.Options} options
*/
function copyPersistenceAnnotations(target, source, options) {
if (!source)
return;
// Copy @cds.persistence.skip/exists annotation.
const noCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
const existsAnno = '@cds.persistence.exists';
const skipAnno = '@cds.persistence.skip';
if (!noCopyExists && source[existsAnno] && !target[existsAnno])
target[existsAnno] = source[existsAnno];
if (source[skipAnno] && !target[skipAnno])
target[skipAnno] = source[skipAnno];
}
function augmentEqual( location, assocname, relations, prefix = '' ) {

@@ -989,0 +1037,0 @@ const args = relations.map( eq );

@@ -5,2 +5,3 @@ // Things which needs to done for parse.cdl after define()

const { dictAddArray } = require('../base/dictionaries');
const { forEachGeneric, forEachMember } = require('../base/model');

@@ -37,3 +38,3 @@ const { setLink, setArtifactLink } = require('./utils');

// Define annotations of this top-level extension
defineAnnotations( ext, ext, ext._block );
defineAnnotations( ext, ext, ext._block, 'extend' );
mergeAnnotatesForSameArtifact( ext );

@@ -226,15 +227,25 @@ // Initialize members and define annotations in sub-elements.

if (ext.$annotations && Array.isArray(ext.$duplicates)) {
const annotates = ext.$duplicates.filter(val => (val.kind === 'annotate'));
for (const dup of annotates) {
ext.$annotations.push(...dup.$annotations);
delete dup.$annotations;
// do not do a complex merge:
if (isComplexExtension( ext ) ||
!Array.isArray( ext.$duplicates ) || ext.$duplicates.some( isComplexExtension ))
return;
for (const dup of ext.$duplicates) {
for (const prop in dup) {
if (prop.charAt(0) === '@')
dictAddArray( ext, prop, dup[prop] );
}
ext.$duplicates = ext.$duplicates.filter(val => (val.kind !== 'annotate'));
if (ext.$duplicates.length === 0)
delete ext.$duplicates;
}
delete ext.$duplicates;
}
}
/**
* We only de-duplicate an extend/annotate `ext` in function
* mergeAnnotatesForSameArtifact() if the extend/annotate is simple, i.e. has
* no members like elements.
*/
function isComplexExtension( ext ) {
return ext.kind !== 'annotate' || ext.elements || ext.parameters || ext.actions;
}
module.exports = finalizeParseCdl;

@@ -36,2 +36,3 @@ // Main XSN-based compiler functions

const { createMessageFunctions, deduplicateMessages } = require('../base/messages');
const { checkRemovedDeprecatedFlags } = require('../base/model');
const { promiseAllDoNotRejectImmediately } = require('../base/node-helpers');

@@ -228,2 +229,3 @@ const { cdsFs } = require('../utils/file');

* @param {object} [options={}] Compilation options.
* @param {object} [fileCache]
* @returns {XSN.Model} Augmented CSN

@@ -240,3 +242,3 @@ */

let asts = [];
const asts = [];
const errors = [];

@@ -259,6 +261,5 @@ a.files.forEach( val => readAndParseSync( val, (err, ast) => {

const fileNames = readDependenciesSync( asts );
asts = [];
// TODO: check the following eslint error
// eslint-disable-next-line no-loop-func
fileNames.forEach( (fileName) => {
asts.length = 0;
// Push dependencies to `ast`. Only works because readAndParseSync() is synchronous.
for (const fileName of fileNames) {
readAndParseSync(fileName, ( err, ast ) => {

@@ -270,3 +271,3 @@ if (err)

});
} );
}
}

@@ -386,2 +387,11 @@ }

}
for (const dep of sources[filename].dependencies || []) {
if (!dep.realname) {
// `realname` is used by setLayers(). For compileSources(), we don't resolve
// the USING paths and use the literal instead, which may be part of the
// source dictionary.
dep.realname = dep.val;
}
}
}

@@ -400,10 +410,11 @@ moduleLayers.setLayers( sources );

*
* TODO: probaby issue message api-recompiled-csn there.
* TODO: probably issue message api-recompiled-csn there.
*/
function recompileX( csn, options ) {
// Explicitly set parseCdl to false because backends cannot handle it
options = { ...options, parseCdl: false, $recompile: true };
// Reset csnFlavor: Use client style (default)
delete options.csnFlavor;
delete options.toCsn;
// TODO: $recompile: true should be enough
// Explicitly set parseCdl to false because backends cannot handle it
// Explicitly delete all toCsn options:
delete options.toCsn;

@@ -438,2 +449,5 @@ const file = csn.$location && csn.$location.file &&

model.meta = {}; // provide initial central meta object
checkRemovedDeprecatedFlags( options, model.$messageFunctions );
if (options.parseOnly) {

@@ -511,3 +525,3 @@ throwWithError();

// already handles non-existent files.
name = fs.realpathSync(name);
name = fs.realpathSync.native(name);
}

@@ -514,0 +528,0 @@ catch (e) {

@@ -56,2 +56,8 @@ // Module handling, layers and packages

function realname( art ) {
while (art && art.kind !== 'source')
art = art._block;
return art && art.realname || '';
}
function compareLayer( a, b ) {

@@ -68,3 +74,4 @@ while (a && a.kind !== 'source')

layer,
realname,
compareLayer,
};

@@ -77,3 +77,3 @@ // Populate views with elements, elements with association targets, ...

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both

@@ -83,12 +83,12 @@ // beta.nestedProjections and beta.universalCsn do not work with it.

= enableExpandElements &&
!isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
!isDeprecatedEnabled( options, 'shortAutoexposed' ) &&
!isDeprecatedEnabled( options, 'longAutoexposed' ) &&
!isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ) &&
!isDeprecatedEnabled( options, 'noScopedRedirections' );
!isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
!isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
!isDeprecatedEnabled( options, '_longAutoexposed' ) &&
!isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
!isDeprecatedEnabled( options, '_noScopedRedirections' );
const autoexposeViaComposition
= (isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ))
= (isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ))
? 'Composition'
: true;
const redirectInSubQueries = isDeprecatedEnabled( options, 'redirectInSubQueries' );
const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );

@@ -1099,3 +1099,3 @@ forEachDefinition( model, traverseElementEnvironments );

// no @cds.autoexpose or @cds.autoexpose:null
// TODO: introduce deprecated.noInheritedAutoexposeViaComposition
// TODO: introduce deprecated._noInheritedAutoexposeViaComposition
art.$autoexpose = model.$compositionTargets[art.name.absolute]

@@ -1109,11 +1109,11 @@ ? autoexposeViaComposition

const { absolute } = target.name;
if (isDeprecatedEnabled( options, 'shortAutoexposed' )) {
if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
const parent = definitionScope( target )._parent;
const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
// no need for dedot here (as opposed to deprecated.longAutoexposed), as
// no need for dedot here (as opposed to deprecated._longAutoexposed), as
// the name for dependent entities have already been created using `_` then
return `${ service.name.absolute }.${ name }`;
}
if (isDeprecatedEnabled( options, 'longAutoexposed' )) {
const dedot = isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' );
if (isDeprecatedEnabled( options, '_longAutoexposed' )) {
const dedot = isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' );
return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;

@@ -1120,0 +1120,0 @@ }

@@ -59,5 +59,5 @@ //

const { options } = model;
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// eslint-disable-next-line max-len
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, 'oldVirtualNotNullPropagation' );
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );

@@ -193,3 +193,3 @@ forEachDefinition( model, run );

// We do not consider the $expand status, as elements are already expanded
// by the resolve(), and if not due to deprecated.noElementsExpansion
// by the resolve(), and if not due to deprecated._noElementsExpansion
run( type );

@@ -196,0 +196,0 @@ return type[prop];

@@ -52,3 +52,2 @@ // Compiler phase "resolve": resolve all references

const { combinedLocation } = require('../base/location');
const { forEachValue } = require('../utils/objectUtils');
const { typeParameters } = require('./builtins');

@@ -60,2 +59,3 @@

setArtifactLink,
annotationHasEllipsis,
pathName,

@@ -78,5 +78,2 @@ linkToOrigin,

const annotationPriorities = {
define: 1, extend: 2, annotate: 2, edmx: 3,
};
const $inferred = Symbol.for('cds.$inferred');

@@ -113,3 +110,3 @@

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both

@@ -218,5 +215,5 @@ // beta.nestedProjections and beta.universalCsn do not work with it.

// eslint-disable-next-line max-len
std: 'Selecting from to-many association $(ART) - key properties are not propagated',
std: 'Key properties are not propagated because a to-many association $(ART) is selected',
// eslint-disable-next-line max-len
element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
element: 'Key properties are not propagated because a to-many association $(MEMBER) of $(ART) is selected',
} );

@@ -722,237 +719,215 @@ }

function chooseAssignment( annoName, art ) {
// TODO: getPath an all names
const anno = art[annoName];
if (!Array.isArray(anno)) { // just one assignment -> use it
if (removeEllipsis( anno )) {
error( 'anno-unexpected-ellipsis',
[ anno.name.location, art ], { code: '...' } );
let anno = art[annoName];
if (!Array.isArray( anno )) { // just one assignment -> use it
if (!annotationHasEllipsis( anno ))
return;
anno = [ anno ];
}
// console.log('ASSIGN:',art.name.absolute,annoName)
const scheduledAssignments = [];
// sort assignment according to layer (define is bottom layer):
const layeredAnnos = layeredAssignments( anno );
let cont = true;
while (cont) {
const { assignments, issue } = assignmentsOfHighestLayers( layeredAnnos );
let index = assignments.length;
cont = !!index; // safety
while (--index >= 0) {
const a = assignments[index];
scheduledAssignments.push( a );
if (!annotationHasEllipsis( a )) {
cont = false;
break;
}
}
return;
if (issue) {
// eslint-disable-next-line no-nested-ternary
const msg = (issue === true)
? 'anno-duplicate'
: (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
for (const a of assignments) {
if (!a.$errorReported)
message( msg, [ a.name.location, art ], { anno: annoName } );
}
}
// else if (index > 0) -- if we allow multiple assignments in one file - the last wins
}
// sort assignment according to layer
const layerAnnos = Object.create(null);
for (const a of anno) {
const layer = layers.layer( a._block );
// Now apply the assignments - all but the first have a '...'
let result = null;
scheduledAssignments.reverse();
for (const a of scheduledAssignments)
result = applyAssignment( result, a, art, annoName );
art[annoName] = result.name ? result
: Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
}
// Group assignments by their layers. An assignment provided with a definition
// is considered to be provided in a layer named '', the lowest layer.
// TODO: make this usable for extend (elements), too =
// do not use $priority, make assignments on define do not have own _block
function layeredAssignments( assignment ) {
const layered = Object.create(null);
for (const a of assignment) {
const layer = a.$priority && layers.layer( a );
// just consider layer if Extend/Annotate, not Define
const name = (layer) ? layer.realname : '';
const done = layerAnnos[name];
const done = layered[name];
if (done)
done.annos.push( a );
done.assignments.push( a );
else
layerAnnos[name] = { layer, annos: [ a ] };
layered[name] = { name, layer, assignments: [ a ] };
// TODO: file - if set: unique in layer
}
mergeArrayInSCCs();
art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
return;
return layered;
}
// Merge annotations in each layer, i.e. multiple annotations in the same layer are
// stored in an array and need to be merged before different layers can be merged.
function mergeArrayInSCCs( ) {
let pos = 0;
forEachValue(layerAnnos, (layer) => {
const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
annotationPriorities[v.$priority] === annotationPriorities.define));
if (mergeSource) {
// If the source annotation (at 'define' level) contains an ellipsis,
// there is no base to apply to.
if (removeEllipsis( mergeSource )) {
error( 'anno-unexpected-ellipsis',
[ mergeSource.name.location, art ], { code: '...' } );
}
// merge source into ellipsis array annotates
layer.annos.forEach( (mergeTarget) => {
if (mergeTarget.$priority &&
annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
pos = findEllipsis( mergeTarget );
if (pos > -1) {
if (mergeSource.literal !== 'array') {
error( 'anno-mismatched-ellipsis',
[ mergeSource.name.location, art ], { code: '...' } );
return;
}
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
}
}
});
}
});
// Return assignments of the highest layers.
// Also return whether there could be an issue:
// - false: there is just one assignment
// - 'unrelated': there is just one assignment per layer
// - true: there is at least one layer with two or more assignments
// TODO: make this usable for extend (elements), too
function assignmentsOfHighestLayers( layeredAnnos ) {
const layerNames = Object.keys( layeredAnnos );
// console.log('HIB:',layerNames)
if (layerNames.length <= 1) {
const name = layerNames[0];
const { assignments } = layeredAnnos[name] || { assignments: [] };
delete layeredAnnos[name];
return { assignments, issue: assignments.length > 1 };
}
function mergeLayeredArrays( mergeTarget ) {
if (mergeTarget.literal === 'array') {
let layer = layers.layer( mergeTarget._block );
delete layerAnnos[(layer) ? layer.realname : ''];
let pos = findEllipsis( mergeTarget );
let hasRun = false;
while (pos > -1 && Object.keys( layerAnnos ).length ) {
hasRun = true;
const mergeSource = findLayerCandidate();
if (mergeSource.literal !== 'array') {
error( 'anno-mismatched-ellipsis',
[ mergeSource.name.location, art ], { code: '...' } );
return mergeTarget;
}
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
layer = layers.layer( mergeSource._block );
delete layerAnnos[(layer) ? layer.realname : ''];
pos = findEllipsis( mergeTarget );
}
// All layers were processed. Remove excess ellipsis.
if (removeEllipsis( mergeTarget, pos ) && hasRun) {
// There shouldn't be any ellipsis or we don't have a base annotation.
// But only if the loop above has run. Otherwise the in-layer merge
// already warned about this case.
message( 'anno-unexpected-ellipsis-layers',
[ mergeTarget.name.location, art ], { code: '...' } );
}
// collect all layers which are lower than another layer
const allExtends = Object.create(null);
allExtends[''] = {}; // the "Define" layer
for (const name of layerNames) {
if (name) // not the "Define" layer
Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
}
// console.log('HIE:',Object.keys(allExtends))
const assignments = [];
const highest = [];
for (const name of layerNames) {
if (!(name in allExtends)) {
const layer = layeredAnnos[name];
delete layeredAnnos[name];
highest.push( layer );
assignments.push( ...layer.assignments );
}
return mergeTarget;
}
assignments.sort( compareAssignments );
const good = highest.every( layer => layer.assignments.length === 1 );
// TODO: use layer.file instead
const issue = !good || highest.length > 1 && 'unrelated';
// console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
return { assignments, issue };
}
function mergeArrayValues( previousValue, arraySpec ) {
let prevPos = 0;
const result = [];
for (const item of arraySpec) {
const ell = item && item.literal === 'token' && item.val === '...';
if (!ell) {
result.push( item );
}
else {
let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
while (prevPos < previousValue.length) {
const prevItem = previousValue[prevPos++];
result.push( prevItem );
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
upToSpec = false;
break;
}
function compareAssignments( a, b ) {
const fileA = layers.realname( a._block );
const fileB = layers.realname( b._block );
if (fileA !== fileB)
return (fileA > fileB) ? 1 : -1;
return (a?.location?.line || 0) - (b?.location?.line || 0) ||
(a?.location?.col || 0) - (b?.location?.col || 0);
}
function applyAssignment( previousAnno, anno, art, annoName ) {
if (!previousAnno) {
if (!annotationHasEllipsis( anno ))
return anno;
if (anno.$priority) { // already complained about with Define
message( 'anno-unexpected-ellipsis-layers', // TODO: better location
[ anno.name.location, art ], { code: '...' } );
}
previousAnno = { val: [] };
}
else if (previousAnno.literal !== 'array') {
error( 'anno-mismatched-ellipsis', // TODO: better location
[ anno.name.location, art ], { code: '...' } );
previousAnno = { val: [] };
}
const previousValue = previousAnno.val;
let prevPos = 0;
const result = [];
for (const item of anno.val) {
const ell = item && item.literal === 'token' && item.val === '...';
if (!ell) {
result.push( item );
}
else {
let upToSpec = item.upTo && checkUpToSpec( item.upTo, art, annoName, true );
while (prevPos < previousValue.length) {
const prevItem = previousValue[prevPos++];
result.push( prevItem );
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
upToSpec = false;
break;
}
if (upToSpec) { // non-matched UP TO
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
}
}
if (upToSpec) { // non-matched UP TO
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
}
}
return result;
}
// console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
return { val: result, literal: 'array' };
}
// function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
const { literal } = upToSpec;
if (trueIfFullUpTo !== true) { // inside struct of UP TO
if (literal !== 'struct' && literal !== 'array' )
return true;
}
else if (literal === 'struct') {
return Object.values( upToSpec.struct ).every( checkUpToSpec );
}
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
function checkUpToSpec( upToSpec, art, annoName, isFullUpTo ) {
const { literal } = upToSpec;
if (!isFullUpTo) { // inside struct of UP TO
if (literal !== 'struct' && literal !== 'array' )
return true;
}
error( null, [ upToSpec.location, art ],
{ anno: annoName, code: '... up to', '#': literal },
{
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
// eslint-disable-next-line max-len
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
} );
return false;
}
function equalUpTo( previousItem, upToSpec ) {
if (!previousItem)
return false;
if ('val' in upToSpec) {
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
return true;
const typeUpTo = typeof upToSpec.val;
const typePrev = typeof previousItem.val;
if (typeUpTo === 'number')
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
if (typePrev === 'number')
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
}
else if (upToSpec.path) {
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
}
else if (upToSpec.sym) {
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
}
else if (upToSpec.struct && previousItem.struct) {
return Object.entries( upToSpec.struct )
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
}
return false;
else if (literal === 'struct') {
return Object.values( upToSpec.struct ).every( v => checkUpToSpec( v, art, annoName ) );
}
function normalizeRef( node ) { // see to-csn.js
const ref = pathName( node.path );
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
return true;
}
error( null, [ upToSpec.location, art ],
{ anno: annoName, code: '... up to', '#': literal },
{
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
// eslint-disable-next-line max-len
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
} );
return false;
}
function removeEllipsis(a, pos = findEllipsis( a )) {
let count = 0;
while (a.literal === 'array' && pos > -1) {
count++;
a.val.splice(pos, 1);
pos = findEllipsis( a );
}
return count;
function equalUpTo( previousItem, upToSpec ) {
if (!previousItem)
return false;
if ('val' in upToSpec) {
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
return true;
const typeUpTo = typeof upToSpec.val;
const typePrev = typeof previousItem.val;
if (typeUpTo === 'number')
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
if (typePrev === 'number')
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
}
function findEllipsis(a) {
return (a.literal === 'array' && a.val)
? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
else if (upToSpec.path) {
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
}
function findLayerCandidate() {
// collect assignments of upper layers (are in no _layerExtends)
const exts = Object.keys( layerAnnos ).map( layerExtends );
const allExtends = Object.assign( Object.create(null), ...exts );
const collected = [];
for (const name in layerAnnos) {
if (!(name in allExtends))
collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
}
// inspect collected assignments - choose the one or signal error
const justOnePerLayer = collected.every( annos => annos.length === 1);
if (!justOnePerLayer || collected.length > 1) {
for (const annos of collected) {
for (const a of annos ) {
// Only the message ID is different.
if (justOnePerLayer) {
message( 'anno-duplicate-unrelated-layer',
[ a.name.location, art ], { anno: annoName },
'Duplicate assignment with $(ANNO)' );
}
else {
message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
}
}
}
}
return collected[0][0]; // just choose any one with error
else if (upToSpec.sym) {
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
}
function layerExtends( name ) {
const { layer } = layerAnnos[name];
return layer && layer._layerExtends;
else if (upToSpec.struct && previousItem.struct) {
return Object.entries( upToSpec.struct )
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
}
return false;
}
function prioritizedAnnos( annos ) {
let prio = 0;
let r = [];
for (const a of annos) {
const p = annotationPriorities[a.$priority] || annotationPriorities.define;
if (p === prio) {
r.push(a);
}
else if (p > prio) {
r = [ a ];
prio = p;
}
}
return r;
function normalizeRef( node ) { // see to-csn.js
const ref = pathName( node.path );
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
}

@@ -959,0 +934,0 @@

@@ -14,2 +14,3 @@ // Compiler functions and utilities shared across all phases

pathName,
annotationHasEllipsis,
} = require('./utils');

@@ -516,3 +517,3 @@

if (typeArtifact.builtin)
warning( 'type-ignoring-argument', loc, { art: typeArtifact } );
message( 'type-ignoring-argument', loc, { art: typeArtifact } );
else

@@ -678,2 +679,5 @@ error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });

return false;
if (art.$requireElementAccess && path.length === 1)
// Path with only one item, but we expect an element, e.g. `$at.from`.
signalMissingElementAccess(art, [ item.location, user ]);
continue;

@@ -815,6 +819,25 @@ }

/**
* Emit a 'ref-expected-element' error for magic variable references
* that require element accesses but don't do.
* For example: `$at`, but `$at.from` or `$at.to` is required.
*
* @param {object} art
* @param {any} location
*/
function signalMissingElementAccess(art, location) {
const err = message( 'ref-expected-element', location,
{ '#': 'magicVar', id: art.name.id } );
// Mapping for better valid names: from -> $at.from
const valid = Object.keys(art.elements || {}).reduce((prev, curr) => {
prev[`${ art.name.id }.${ curr }`] = true;
return prev;
}, Object.create(null));
attachAndEmitValidNames(err, valid);
}
/**
* Attaches a dictionary of valid names to the given compiler message.
* In test mode, an info message is emitted with a list of valid names.
*
* @param {CSN.Message} msg CDS Compiler message
* @param {CompileMessage} msg CDS Compiler message
* @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.

@@ -845,10 +868,6 @@ */

// Resolve all annotation assignments for the node `art`. Set `art.@` to all
// flattened assignments. This function might issue error message for
// duplicate assignments.
// TODOs:
// * do something for extensions by CSN or Properties parsers
// * make sure that we do not issue repeated warnings due to flattening if an
// annotation definition is missing
function defineAnnotations( construct, art, block, priority = 'define' ) {
// Set _block links for annotations (necessary for layering).
// Issue messages for annotations on namespaces and builtins (TODO: really here?)
// Also copy annotations from `construct` to `art` (TODO: separate that functionality).
function defineAnnotations( construct, art, block, priority = false ) {
if (!options.parseCdl && construct.kind === 'annotate') {

@@ -871,73 +890,25 @@ // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts

}
// TODO: block should be construct._block
if (construct.$annotations && construct.$annotations.doc )
art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
else if (construct.doc)
if (construct.doc)
art.doc = construct.doc; // e.g. through `extensions` array in CSN
if (!construct.$annotations) {
if (!block || block.$frontend !== 'json')
return; // namespace, or in CDL source without @annos:
// CSN input: set _block and $priority, shallow-copy from extension
for (const annoProp in construct) {
if (annoProp.charAt(0) === '@') {
let annos = construct[annoProp];
if (!(Array.isArray(annos)))
annos = [ annos ];
for (const a of annos) {
setLink( a, '_block', block );
a.$priority = priority;
if (construct !== art)
addAnnotation( art, annoProp, a );
// set _block (for layering) and $priority, shallow-copy from extension
// TODO: think of removing $priority, then
// no _block: define, _block: annotate/extend/edmx
// would fit with extending defs with props like length
for (const annoProp in construct) {
if (annoProp.charAt(0) === '@') {
let annos = construct[annoProp];
if (!(Array.isArray(annos)))
annos = [ annos ];
for (const a of annos) {
setLink( a, '_block', block );
a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
if (construct !== art)
addAnnotation( art, annoProp, a );
if (!priority && annotationHasEllipsis( a )) {
error( 'anno-unexpected-ellipsis',
[ a.name.location, art ], { code: '...' } );
}
}
}
return;
}
for (const anno of construct.$annotations) {
const ref = anno.name;
const name = resolveUncheckedPath( ref, 'annotation', { _block: block } );
const annoProp = (anno.name.variant)
? `@${ name }#${ anno.name.variant.id }`
: `@${ name }`;
flatten( ref.path, annoProp, anno.value || {}, anno.name.variant, anno.name.location );
}
return;
function flatten( path, annoProp, value, iHaveVariant, location ) {
// Be robust if struct value has duplicate element names
if (Array.isArray(value)) // TODO: do that differently in CDL parser
return; // discard duplicates in flattened form
if (value.literal === 'struct') {
for (const item of value._struct || []) {
let prop = pathName(item.name.path);
if (item.name.variant) {
if (iHaveVariant) {
error( 'anno-duplicate-variant', [ item.name.variant.location, construct ],
{}, // TODO: params
'Annotation variant has been already provided' );
}
prop = `${ prop }#${ item.name.variant.id }`; // TODO: check for double variants
}
flatten( [ ...path, ...item.name.path ], `${ annoProp }.${ prop }`, item, iHaveVariant || item.name.variant);
}
for (const prop in value.struct) {
const item = value.struct[prop];
flatten( [ ...path, item.name ], `${ annoProp }.${ prop }`, item, iHaveVariant );
}
return;
}
const anno = Object.assign( {}, value ); // shallow copy
anno.name = {
path,
location: location ||
value.name && value.name.location ||
value.path && value.path.location,
};
setLink( anno, '_block', block );
// TODO: _parent, _main is set later (if we have ElementRef), or do we
// set _artifact?
anno.$priority = priority;
addAnnotation( art, annoProp, anno );
}
}

@@ -944,0 +915,0 @@ }

@@ -42,3 +42,3 @@ // Tweak associations: rewrite keys and on conditions

// behavior depending on option `deprecated`:
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
// TODO: we should get rid of noElementsExpansion soon; both

@@ -99,11 +99,10 @@ // beta.nestedProjections and beta.universalCsn do not work with it.

if (!elem.$inferred) { // && !elem.target.$inferred
// TODO: spec meeting 2021-01-22: no warning
warning( 'assoc-target-not-in-service', [ elem.target.location, elem ],
{ target, '#': (elem._main.query ? 'select' : 'define') }, {
std: 'Target $(TARGET) of association is outside any service', // not used
// eslint-disable-next-line max-len
define: 'Target $(TARGET) of explicitly defined association is outside any service',
// eslint-disable-next-line max-len
select: 'Target $(TARGET) of explicitly selected association is outside any service',
} );
info( 'assoc-target-not-in-service', [ elem.target.location, elem ],
{ target, '#': (elem._main.query ? 'select' : 'define') }, {
std: 'Target $(TARGET) of association is outside any service', // not used
// eslint-disable-next-line max-len
define: 'Target $(TARGET) of explicitly defined association is outside any service',
// eslint-disable-next-line max-len
select: 'Target $(TARGET) of explicitly selected association is outside any service',
} );
}

@@ -110,0 +109,0 @@ else {

@@ -34,2 +34,6 @@ // Simple compiler utility functions

}
function annotationHasEllipsis( anno ) {
const { val } = anno || {};
return Array.isArray( val ) && val.some( v => v.literal === 'token' && v.val === '...' );
}

@@ -385,2 +389,3 @@ /**

annotationIsFalse,
annotationHasEllipsis,
annotateWith,

@@ -387,0 +392,0 @@ setLink,

@@ -398,3 +398,3 @@ 'use strict';

const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
const [ properties, hasStream ] = createProperties(entityCsn);

@@ -406,3 +406,3 @@

warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
else if(entityCsn.$edmKeyPaths.length === 0)
else if(entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
message('odata-spec-violation-no-key', loc);

@@ -693,5 +693,3 @@

if(!elementCsn._ignore) {
if(edmUtils.isAssociationOrComposition(elementCsn))
{
if(edmUtils.isAssociationOrComposition(elementCsn)) {
// Foreign keys are part of the generic elementCsn.elements property creation

@@ -704,13 +702,13 @@

// (undefined !== false) still evaluates to true
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
{
const navProp = new Edm.NavigationProperty(v, {
Name: elementName,
Type: elementCsn._target.name
}, elementCsn);
props.push(navProp);
const navProp = new Edm.NavigationProperty(v, {
Name: elementName,
Type: elementCsn._target.name
}, elementCsn);
props.push(navProp);
// save the navProp in the global array for late constraint building
navigationProperties.push(navProp);
}
navigationProperties.push(navProp);
}
}
// render ordinary property if element is NOT ...

@@ -720,17 +718,16 @@ // 1) ... annotated @cds.api.ignore

else if(isEdmPropertyRendered(elementCsn, options))
else if(isEdmPropertyRendered(elementCsn, options))
{
// CDXCORE-CDXCORE-173
// V2: filter @Core.MediaType
if ( options.isV2() && elementCsn['@Core.MediaType']) {
hasStream = elementCsn['@Core.MediaType'];
delete elementCsn['@Core.MediaType'];
if ( options.isV2() && elementCsn['@Core.MediaType']) {
hasStream = elementCsn['@Core.MediaType'];
delete elementCsn['@Core.MediaType'];
// CDXCORE-CDXCORE-177:
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
// V4: render property type 'Edm.Stream'
streamProps.push(elementName);
streamProps.push(elementName);
} else {
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
}
} else {
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
}

@@ -737,0 +734,0 @@ }

@@ -11,28 +11,14 @@ 'use strict';

{
// csn2edm expects "version" to be a top-level property of options
// csn2edm expects "odataVersion" to be a top-level property of options
// set to 'v4' as default, override with value from incoming options
// (here version comes inside "toOdata")
const options = Object.assign({ version: 'v4'}, _options);
if (options.toOdata) {
if(options.toOdata.version)
options.version = options.toOdata.version;
if(options.toOdata.odataFormat)
options.odataFormat = options.toOdata.odataFormat;
if(options.toOdata.odataContainment)
options.odataContainment = options.toOdata.odataContainment;
if(options.toOdata.odataForeignKeys)
options.odataForeignKeys = options.toOdata.odataForeignKeys;
if(options.toOdata.odataV2PartialConstr)
options.odataV2PartialConstr = options.toOdata.odataV2PartialConstr;
// global flag that indicates whether or not FKs shall be rendered in general
// V2/V4 flat: yes
// V4/struct: depending on odataForeignKeys
options.renderForeignKeys =
options.version === 'v4' ? options.odataFormat === 'structured' && !!options.odataForeignKeys : true;
const options = Object.assign({ odataVersion: 'v4'}, _options);
// global flag that indicates whether or not FKs shall be rendered in general
// V2/V4 flat: yes
// V4/struct: depending on odataForeignKeys
options.renderForeignKeys =
options.odataVersion === 'v4' ? options.odataFormat === 'structured' && !!options.odataForeignKeys : true;
}
const v2 = options.odataVersion.match(/v2/i) !== null;
const v4 = options.odataVersion.match(/v4/i) !== null;
const v2 = options.version.match(/v2/i) !== null;
const v4 = options.version.match(/v4/i) !== null;
options.v = [v2, v4];

@@ -39,0 +25,0 @@ options.isStructFormat = options.odataFormat && options.odataFormat === 'structured';

@@ -138,3 +138,3 @@ // CSN frontend - transform CSN into XSN

type: condition,
msgId: 'syntax-csn-expected-term',
msgId: 'syntax-expected-term',
// TODO: also specify requires here, and adapt onlyWith()

@@ -149,7 +149,7 @@ optional: exprProperties,

type: natnumOrStar,
msgId: 'syntax-csn-expected-cardinality',
msgId: 'syntax-expected-cardinality',
},
columns: {
arrayOf: selectItem,
msgId: 'syntax-csn-expected-column',
msgId: 'syntax-expected-column',
defaultKind: '$column',

@@ -295,3 +295,3 @@ validKinds: [], // pseudo kind '$column'

type: artifactRef,
msgId: 'syntax-csn-expected-reference',
msgId: 'syntax-expected-reference',
optional: [ 'ref', 'global' ],

@@ -372,3 +372,3 @@ inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation' ],

type: renameTo( 'path', arrayOf( refItem ) ),
msgId: 'syntax-csn-expected-reference',
msgId: 'syntax-expected-reference',
minLength: 1,

@@ -589,3 +589,3 @@ requires: 'id',

masked: {
type: boolOrNull,
type: masked,
inKind: [ 'element' ],

@@ -787,3 +787,3 @@ },

if (minLength > val.length) {
message( 'syntax-csn-expected-length', location(true),
message( 'syntax-expected-length', location(true),
{ prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });

@@ -858,3 +858,3 @@ }

if (!relevantProps) {
error( 'syntax-csn-required-subproperty', location(true),
error( 'syntax-required-subproperty', location(true),
{

@@ -885,3 +885,3 @@ prop: spec.msgProp,

if (!csnVersionZero) {
warning( 'syntax-csn-zero-delete', location(true), { prop: spec.msgProp },
warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
'Delete/inline CSN v0.1.0 property $(PROP)' );

@@ -971,3 +971,3 @@ }

if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
error( 'syntax-csn-expected-object', location(true),
error( 'syntax-expected-object', location(true),
{ prop: spec.prop }); // spec.prop, not spec.msgProp!

@@ -984,3 +984,3 @@ return ignore( dict );

if (!name) {
warning( 'syntax-csn-empty-name', location(true),
warning( 'syntax-empty-name', location(true),
{ prop: spec.prop }, // TODO: Error

@@ -1062,7 +1062,7 @@ 'Property names in dictionary $(PROP) must not be empty' );

if (val === 'view' && xsn.kind === 'entity') {
warning( 'syntax-csn-zero-value', location(true), { prop: spec.msgProp },
warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
'Replace CSN v0.1.0 value in $(PROP) by something specified' );
}
else if ((val === 'entity' || val === 'type') && xsn.kind === 'aspect') {
info( 'syntax-csn-aspect', location(true), { kind: 'aspect', '#': val },
info( 'syntax-aspect', location(true), { kind: 'aspect', '#': val },
{

@@ -1075,3 +1075,3 @@ std: 'Use the dedicated kind $(KIND) for aspect definitions',

else {
error( 'syntax-csn-expected-valid', location(true), { prop: spec.msgProp },
error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
'Expected valid string for property $(PROP)' );

@@ -1118,3 +1118,3 @@ }

if (!path.every( id => id)) {
warning( 'syntax-csn-expected-name', location(true), { prop: spec.msgProp },
warning( 'syntax-expected-name', location(true), { prop: spec.msgProp },
'Expected correct name for property $(PROP)' );

@@ -1130,3 +1130,3 @@ }

return { val, location: location() };
warning( 'syntax-csn-expected-boolean', location(true), { prop: spec.msgProp },
warning( 'syntax-expected-boolean', location(true), { prop: spec.msgProp },
'Expected boolean or null for property $(PROP)' );

@@ -1141,3 +1141,3 @@ ignore( val );

return val;
error( 'syntax-csn-expected-string', location(true), { prop: spec.msgProp },
error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
'Expected non-empty string for property $(PROP)' );

@@ -1151,3 +1151,3 @@ return ignore( val );

return { val, literal: 'string', location: location() };
error( 'syntax-csn-expected-string', location(true), { prop: spec.msgProp },
error( 'syntax-expected-string', location(true), { prop: spec.msgProp },
'Expected non-empty string for property $(PROP)' );

@@ -1174,3 +1174,3 @@ return ignore( val );

return { val, literal: 'number', location: location() };
error( spec.msgId || 'syntax-csn-expected-natnum', location(true),
error( spec.msgId || 'syntax-expected-natnum', location(true),
{ prop: spec.msgProp } );

@@ -1210,6 +1210,4 @@ return ignore( val );

if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
if (val.some( isEllipsis )) {
error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
'Unexpected $(CODE) in nested array' );
}
if (val.some( isEllipsis ))
error( 'syntax-unexpected-ellipsis', location(true), { '#': 'nested-array', code: '...' } );
}

@@ -1223,3 +1221,3 @@ else {

// error position at the beginning of the array, but that is fine
error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
error( 'syntax-duplicate-ellipsis', location(true), { code: '...' },
'Expected no more than one $(CODE)' );

@@ -1238,3 +1236,3 @@ break;

if (seenEllipsis === 'upTo') {
error( 'syntax-csn-expecting-ellipsis', location(true), // at closing bracket
error( 'syntax-expecting-ellipsis', location(true), // at closing bracket
{ code: '... up to', newcode: '...' },

@@ -1286,7 +1284,10 @@ // TODO: should we be more CSN specific in the message?

function annotation( val, spec, xsn, csn, name ) {
const variantIndex = name.indexOf('#') + 1 || name.length;
const n = refSplit( name.substring( (xsn ? 1 : 0), variantIndex ), spec.msgProp );
const absolute = (xsn ? name.substring(1) : name);
// TODO: really care about variant (qualifier parts)?
const variantIndex = absolute.indexOf('#') + 1 || absolute.length;
const n = refSplit( absolute.substring( 0, variantIndex ), spec.msgProp );
if (!n)
return undefined;
if (variantIndex < name.length)
n.absolute = absolute;
if (variantIndex < absolute.length)
n.variant = { id: name.substring( variantIndex ), location: location() };

@@ -1306,3 +1307,3 @@ const r = annoValue( val, spec );

}
error( 'syntax-csn-expected-scalar', location(true), { prop: spec.msgProp },
error( 'syntax-expected-scalar', location(true), { prop: spec.msgProp },
'Only scalar values are supported for property $(PROP)' );

@@ -1319,3 +1320,3 @@ return ignore( val );

return val;
error( 'syntax-csn-expected-valid', location(true), { prop: spec.msgProp },
error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
'Expected valid string for property $(PROP)' );

@@ -1335,3 +1336,3 @@ return ignore( val );

if (!exprs.length) {
message( 'syntax-csn-expected-length', location(true),
message( 'syntax-expected-length', location(true),
{ prop: 'xpr', otherprop: 'func', '#': 'suffix' });

@@ -1369,3 +1370,3 @@ }

else if (!exprs || typeof exprs !== 'object') {
error( 'syntax-csn-expected-args', location(true),
error( 'syntax-expected-args', location(true),
{ prop: spec.prop }, // spec.prop, not spec.msgProp!

@@ -1433,3 +1434,3 @@ 'Expected array or object for property $(PROP)' );

// TODO: also "sign" xsn.value created by inValue to complain about both 'value' and 'ref' etc
warning( 'syntax-csn-unexpected-property', location(true), { prop: spec.msgProp },
warning( 'syntax-unexpected-property', location(true), { prop: spec.msgProp },
'Unexpected CSN property $(PROP)' );

@@ -1439,3 +1440,3 @@ return undefined;

if (!csnVersionZero) {
warning( 'syntax-csn-zero-delete', location(true), { prop: spec.msgProp },
warning( 'syntax-zero-delete', location(true), { prop: spec.msgProp },
'Delete/inline CSN v0.1.0 property $(PROP)' );

@@ -1487,2 +1488,8 @@ }

function masked( val, spec ) {
message('syntax-invalid-masked', location(), { keyword: 'masked' },
'Keyword $(KEYWORD) not supported');
return boolOrNull( val, spec );
}
function duplicateExcluding( name, loc ) {

@@ -1508,3 +1515,3 @@ error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },

if (Array.isArray( val ) && val.length > 1 && !csn.op) {
warning( 'syntax-csn-expected-property', location(true),
warning( 'syntax-expected-property', location(true),
{ prop: 'args', otherprop: 'op' },

@@ -1528,3 +1535,3 @@ 'CSN property $(PROP) expects property $(OTHERPROP) to be specified' );

return { val: keyVal, literal: 'string', location: location() };
error( 'syntax-csn-expected-translation', location(true),
error( 'syntax-expected-translation', location(true),
{ prop: textKey, otherprop: spec.prop },

@@ -1543,3 +1550,3 @@ 'Expected string for text key $(PROP) of language $(OTHERPROP)' );

// TODO v2: Warning only with --sloppy
warning( 'syntax-csn-unknown-property', location(true), { prop },
warning( 'syntax-unknown-property', location(true), { prop },
'Unknown CSN property $(PROP)' );

@@ -1555,3 +1562,3 @@ }

if (s.vZeroIgnore && s.vZeroIgnore === csn[prop]) { // for "op": "call"
warning( 'syntax-csn-zero-delete', location(true), { prop },
warning( 'syntax-zero-delete', location(true), { prop },
'Delete/inline CSN v0.1.0 property $(PROP)' );

@@ -1575,3 +1582,3 @@ return { type: ignore };

: (parentSpec.msgProp ? 'std' : 'top');
message( 'syntax-csn-unexpected-property', location(true),
message( 'syntax-unexpected-property', location(true),
{

@@ -1634,3 +1641,3 @@ prop, otherprop: parentSpec.msgProp, kind, '#': variant,

if (prop) {
error( 'syntax-csn-dependent-property', location(true),
error( 'syntax-dependent-property', location(true),
{ prop, otherprop: need },

@@ -1641,3 +1648,3 @@ 'CSN property $(PROP) can only be used in combination with $(OTHERPROP)');

else if (!xor['no:req']) {
error( 'syntax-csn-required-property', location(true),
error( 'syntax-required-property', location(true),
{ prop: need, otherprop: spec.msgProp, '#': spec.prop },

@@ -1664,3 +1671,3 @@ { // TODO $(PARENT), TODO: do not use prop===0 hack

return true; // hack for window function: both func and xpr is allowed
error( 'syntax-csn-excluded-property', location(true),
error( 'syntax-excluded-property', location(true),
{ prop, otherprop: xor[group] },

@@ -1680,3 +1687,3 @@ 'CSN property $(PROP) can only be used alternatively to $(OTHERPROP)');

return;
warning( 'syntax-csn-zero-prop', location(true), { prop, otherprop },
warning( 'syntax-zero-prop', location(true), { prop, otherprop },
'Replace CSN v0.1.0 property $(OTHERPROP) by $(PROP)' );

@@ -1690,3 +1697,3 @@ }

return array;
error( 'syntax-csn-expected-array', location(true), { prop: spec.prop },
error( 'syntax-expected-array', location(true), { prop: spec.prop },
'Expected array for property $(PROP)' );

@@ -1699,3 +1706,3 @@ return ignore( array );

return obj;
error( spec.msgId || 'syntax-csn-expected-object', location(true),
error( spec.msgId || 'syntax-expected-object', location(true),
{ prop: spec.msgProp });

@@ -1708,3 +1715,3 @@ return ignore( obj );

if (!path.every( id => id)) {
warning( 'syntax-csn-expected-name', location(true), { prop },
warning( 'syntax-expected-name', location(true), { prop },
'Expected correct name for property $(PROP)' );

@@ -1717,3 +1724,3 @@ }

if (!csnVersionZero && spec.vZeroFor == null) { // but 0 does not match!
warning( 'syntax-csn-zero-value', location(true), { prop: spec.msgProp },
warning( 'syntax-zero-value', location(true), { prop: spec.msgProp },
'Replace CSN v0.1.0 value in $(PROP) by something specified' );

@@ -1748,3 +1755,3 @@ }

dollarLocations.push( null ); // must match with popLocation()
error( 'syntax-csn-expected-object', location(true), { prop: '$location' } );
error( 'syntax-expected-object', location(true), { prop: '$location' } );
}

@@ -1862,3 +1869,3 @@ // hidden feature: string $location

};
messageFunctions.error( 'syntax-csn-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
messageFunctions.error( 'syntax-illegal-json', loc, { msg }, 'Illegal JSON: $(MSG)' );
return xsn;

@@ -1865,0 +1872,0 @@ }

@@ -225,3 +225,3 @@ // Transform XSN (augmented CSN) into CSN

frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
// xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
ixpr: exprs => [].concat( ...exprs ), // xpr extra, due to extra parentheses
};

@@ -729,3 +729,4 @@

// and @odata.containment.ignore
if (val.$priority && (val.$priority !== 'define') === annotated) {
// TODO: use $inferred instead special $priority value
if (val.$priority !== undefined && (!!val.$priority) === annotated) {
// transformer (= value) takes care to exclude $inferred annotation assignments

@@ -1593,6 +1594,7 @@ const sub = transformer( val );

gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
options.toCsn && options.toCsn.flavor === 'gensrc';
universalCsn = (options.csnFlavor === 'universal' ||
options.toCsn && options.toCsn.flavor === 'universal' ) &&
isBetaEnabled( options, 'enableUniversalCsn' ) && !options.parseCdl;
( options.toCsn && options.toCsn.flavor === 'gensrc');
universalCsn = ( options.csnFlavor === 'universal' ||
( options.toCsn && options.toCsn.flavor === 'universal') ) &&
isBetaEnabled( options, 'enableUniversalCsn' ) &&
!options.parseCdl;
strictMode = options.testMode;

@@ -1605,4 +1607,4 @@ const proto = options.dictionaryPrototype;

withLocations = options.withLocations;
parensAsStrings = isDeprecatedEnabled( options, 'parensAsStrings' );
projectionAsQuery = isDeprecatedEnabled( options, 'projectionAsQuery' );
parensAsStrings = isDeprecatedEnabled( options, '_parensAsStrings' );
projectionAsQuery = isDeprecatedEnabled( options, '_projectionAsQuery' );
}

@@ -1609,0 +1611,0 @@

@@ -16,4 +16,4 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

const Parser = require('../gen/languageParser').languageParser;
const Lexer = require('../gen/languageLexer').languageLexer;
const Parser = require('../gen/languageParser').default;
const Lexer = require('../gen/languageLexer').default;

@@ -20,0 +20,0 @@ // Error listener used for ANTLR4-generated parser

'use strict';
const { splitLines } = require('../utils/file');
const {
isWhitespaceOrNewLineOnly,
isWhitespaceCharacterNoNewline,
cdlNewLineRegEx,
} = require('./textUtils');

@@ -21,24 +25,26 @@ /**

let lines = splitLines(comment);
let lines = comment.split(cdlNewLineRegEx);
if (lines.length === 1) {
// special case for one-liners
// remove "/***/" and trim white space
const content = lines[0].replace(/^\/[*]{2,}/, '').replace(/\*\/$/, '').trim();
return isWhiteSpaceOnly(content) ? null : content;
// Special case for one-liners.
// Remove "/***/" and trim white space and asterisks.
const content = lines[0]
.replace(/^\/[*]{2,}/, '')
.replace(/\*+\/$/, '')
.trim();
return isWhitespaceOrNewLineOnly(content) ? null : content;
}
lines[0] = removeHeaderFence(lines[0]);
// If the comment already has content on the first line, i.e. after `/**`,
// its leading whitespace is ignored for whitespace trimming.
const hasContentOnFirstLine = /\/\*+\s*\S/.test(lines[0]);
// First line, i.e. header, is always trimmed from left.
lines[0] = removeHeaderFence(lines[0]).trimStart();
lines[lines.length - 1] = removeFooterFence(lines[lines.length - 1]);
if (isFencedComment(lines)) {
lines = lines.map((line, index) => ((index === 0) ? line : removeFence(line)));
}
else if (lines.length === 2) {
if (lines.length === 2) {
// Comment that is essentially just a header + footer.
// First line, i.e. header, is always trimmed from left.
lines[0] = lines[0].trimStart();
// If the second line starts with an asterisk then remove it.
// Otherwise trim all whitespace.
// Otherwise, trim all left whitespace.
if ((/^\s*[*]/.test(lines[1])))

@@ -49,8 +55,7 @@ lines[1] = removeFence(lines[1]);

}
else if (isFencedComment(lines)) {
lines = lines.map((line, index) => ((index === 0) ? line : removeFence(line)));
}
else {
const firstNonEmptyLine = lines.find((line, index) => index !== 0 && /[^\s]/.test(line)) || '';
// Tabs are regarded as one space.
const spacesAtBeginning = firstNonEmptyLine.match(/^\s*/)[0].length;
if (spacesAtBeginning > 0)
lines = lines.map(line => removeWhitespace(line, spacesAtBeginning));
stripCommentIndentation(lines, hasContentOnFirstLine);
}

@@ -63,14 +68,43 @@

const content = lines.slice(startIndex, endIndex).join('\n');
return isWhiteSpaceOnly(content) ? null : content;
return isWhitespaceOrNewLineOnly(content) ? null : content;
}
/**
* Checks whether the given string is whitespace only, i.e. newline
* spaces, tabs.
* Strips and counts the indentation from the given comment string.
* This function is similar to the one in multiLineStringParser.js, but does not
* have special handling for the first and last line of the string.
*
* @param {string} content
* @example
* | hello
* | world
* | foo bar
* becomes
* | hello
* | world
* | foo bar
*
* @param {string[]} lines String split into lines.
* @param {boolean} ignoreFirstLine Whether to ignore the first line for indentation counting.
*/
function isWhiteSpaceOnly(content) {
return /^\s*$/.test(content);
function stripCommentIndentation(lines, ignoreFirstLine) {
const n = lines.length;
const minIndent = lines.reduce((min, line, index) => {
// Blank lines are ignored.
if (isWhitespaceOrNewLineOnly(line) || (index === 0 && ignoreFirstLine))
return min;
let count = 0;
const length = Math.min(min, line.length);
while (count < length && isWhitespaceCharacterNoNewline(line[count])) {
count++;
}
return Math.min(min, count);
}, Number.MAX_SAFE_INTEGER);
for (let i = (ignoreFirstLine ? 1 : 0); i < n; ++i) {
// Note: Line may be empty and have fewer characters than `min`.
// In that case, slice() returns an empty string.
lines[i] = lines[i].slice(minIndent);
}
}

@@ -92,13 +126,2 @@

/**
* Remove the TODO
*
* @param {string} line
* @param {number} spaces Number of whitespace to remove at the beginning of the line
* @returns {string} line without fence
*/
function removeWhitespace(line, spaces) {
return line.replace(new RegExp(`^\\s{0,${ spaces }}`), ''); // Trailing spaces with '*'? => .replace(/\s+[*]$/, '');
}
/**
* Removes a header fence, i.e. '/**'.

@@ -105,0 +128,0 @@ * May remove more than two asterisks e.g. '/*******'

@@ -32,8 +32,8 @@ // Error strategy with special handling for (non-reserved) keywords

const antlr4 = require('antlr4');
const IntervalSet = require('antlr4/IntervalSet');
const antlr4_error = require('antlr4/error/ErrorStrategy');
const antlr4_LL1Analyzer = require('antlr4/LL1Analyzer.js').LL1Analyzer;
const predictionContext = require('antlr4/PredictionContext').predictionContextFromRuleContext;
const { ATNState } = require('antlr4/atn/ATNState');
const { InputMismatchException } = antlr4.error;
const antlr4_LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
const { DefaultErrorStrategy } = require('antlr4/src/antlr4/error/ErrorStrategy');
const { InputMismatchException } = require('antlr4/src/antlr4/error/Errors');
const { predictionContextFromRuleContext: predictionContext } = require('antlr4/src/antlr4/PredictionContext');
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
const { IntervalSet, Interval } = require('antlr4/src/antlr4/IntervalSet');

@@ -69,26 +69,31 @@ const keywordRegexp = /^[a-zA-Z]+$/; // we don't have keywords with underscore

// parser (prototype).
function KeywordErrorStrategy( ...args ) {
antlr4_error.DefaultErrorStrategy.call( this, ...args );
}
const super1 = antlr4_error.DefaultErrorStrategy.prototype;
class KeywordErrorStrategy extends DefaultErrorStrategy {
constructor( ...args ) {
super( ...args );
KeywordErrorStrategy.prototype = Object.assign(
Object.create( super1 ), {
sync,
reportNoViableAlternative,
reportInputMismatch,
reportUnwantedToken,
reportMissingToken,
reportIgnoredWith,
// getErrorRecoverySet,
consumeUntil,
recoverInline,
getMissingSymbol,
getExpectedTokensForMessage,
getTokenDisplay,
constructor: KeywordErrorStrategy,
this._super = {
consumeUntil: super.consumeUntil,
recoverInline: super.recoverInline,
getExpectedTokens: super.getExpectedTokens,
};
}
);
}
// Attemp to recover from problems in subrules, except if rule has defined a
// TODO: Use actual methods
Object.assign( KeywordErrorStrategy.prototype, {
sync,
reportNoViableAlternative,
reportInputMismatch,
reportUnwantedToken,
reportMissingToken,
reportIgnoredWith,
// getErrorRecoverySet,
consumeUntil,
recoverInline,
getMissingSymbol,
getExpectedTokensForMessage,
getTokenDisplay,
});
// Attempt to recover from problems in subrules, except if rule has defined a
// local variable `_sync` with value 'nop'

@@ -171,3 +176,3 @@ function sync( recognizer ) {

this.reportUnwantedToken(recognizer);
const expecting = new IntervalSet.IntervalSet();
const expecting = new IntervalSet();
expecting.addSet(recognizer.getExpectedTokens());

@@ -276,6 +281,6 @@ const whatFollowsLoopIterationOrRule = expecting.addSet(this.getErrorRecoverySet(recognizer));

if (SEMI < 1 || RBRACE < 1) {
super1.consumeUntil.call( this, recognizer, set );
this._super.consumeUntil.call( this, recognizer, set );
}
else if (set.contains(SEMI)) { // do not check for RBRACE here!
super1.consumeUntil.call( this, recognizer, set );
this._super.consumeUntil.call( this, recognizer, set );
// console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(), recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));

@@ -285,3 +290,3 @@ }

// DO NOT modify input param `set`, as the set might be cached in the ATN
const stop = new IntervalSet.IntervalSet();
const stop = new IntervalSet();
stop.addSet( set );

@@ -292,3 +297,3 @@ stop.removeOne( recognizer.constructor.Identifier );

stop.addOne( RBRACE );
super1.consumeUntil.call( this, recognizer, stop );
this._super.consumeUntil.call( this, recognizer, stop );
if (recognizer.getTokenStream().LA(1) === SEMI ||

@@ -319,7 +324,7 @@ recognizer.getTokenStream().LA(1) === RBRACE && !set.contains(RBRACE)) {

if (!identType || !recognizer.isExpectedToken( identType ))
return super1.recoverInline.call( this, recognizer );
return this._super.recoverInline.call( this, recognizer );
const token = recognizer.getCurrentToken();
if (!keywordRegexp.test( token.text ))
return super1.recoverInline.call( this, recognizer );
return this._super.recoverInline.call( this, recognizer );

@@ -354,4 +359,8 @@ recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },

// which are function name and argument position dependent:
if (j === pc.GenericArgFull)
names.push( ...recognizer.$genericKeywords.argFull );
if (j === pc.GenericExpr)
names.push( ...recognizer.$genericKeywords.expr );
else if (j === pc.GenericSeparator)
names.push( ...recognizer.$genericKeywords.separator );
else if (j === pc.GenericIntro)
names.push( ...recognizer.$genericKeywords.introMsg );
// other expected tokens usually appear in messages, except the helper tokens

@@ -373,2 +382,5 @@ // which are used to solve ambiguities via the parser method setLocalToken():

}
else if (names.includes("'?'")) {
names = names.filter( n => n !== "'?'" );
}
names.sort( (a, b) => (tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1) );

@@ -433,6 +445,6 @@ return names;

if (!identType || !beforeUnreserved || beforeUnreserved + 2 > identType)
return intervalSetToArray( recognizer, super1.getExpectedTokens.call( this, recognizer ) );
return intervalSetToArray( recognizer, this._super.getExpectedTokens.call( this, recognizer ) );
const ll1 = new antlr4_LL1Analyzer(atn);
const expected = new IntervalSet.IntervalSet();
const expected = new IntervalSet();
const orig_addInterval = expected.addInterval;

@@ -498,3 +510,3 @@ const orig_addSet = expected.addSet;

function addRange( interval, start, stop ) {
orig_addInterval.call( interval, new IntervalSet.Interval( start, stop || start + 1 ) );
orig_addInterval.call( interval, new Interval( start, stop || start + 1 ) );
}

@@ -501,0 +513,0 @@ }

@@ -10,3 +10,3 @@ // Generic ANTLR parser class with AST-building functions

const antlr4 = require('antlr4');
const { ATNState } = require('antlr4/atn/ATNState');
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
const { dictAdd, dictAddArray } = require('../base/dictionaries');

@@ -36,82 +36,83 @@ const locUtils = require('../base/location');

//
function GenericAntlrParser( ...args ) {
// ANTLR restriction: we cannot add parameters to the constructor.
antlr4.Parser.call( this, ...args );
this.buildParseTrees = false;
class GenericAntlrParser extends antlr4.Parser {
constructor( ...args ) {
// ANTLR restriction: we cannot add parameters to the constructor.
super( ...args );
this.buildParseTrees = false;
// Common properties.
// We set them here so that they are available in the prototype.
// This improved performance by 25% for certain scenario tests.
// Probably because there was no need to look up the prototype chain anymore.
this.$adaptExpectedToken = null;
this.$adaptExpectedExcludes = [ ];
this.$nextTokensToken = null;
this.$nextTokensContext = null;
// Common properties.
// We set them here so that they are available in the prototype.
// This improved performance by 25% for certain scenario tests.
// Probably because there was no need to look up the prototype chain anymore.
this.$adaptExpectedToken = null;
this.$adaptExpectedExcludes = [ ];
this.$nextTokensToken = null;
this.$nextTokensContext = null;
this.prepareGenericKeywords();
this.options = {};
this.options = {};
return this;
this.genericFunctionsStack = [];
this.$genericKeywords = specialFunctions[''][1];
}
}
// When we define this class with the ES6 `class` syntax, we get
// TypeError: Class constructors cannot be invoked without 'new'
// Reason: the generated ANTLR constructor calls its super constructor via
// old-style `<super>.call(this,...)`, not via `super(...)`.
// TODO: Use actual methods.
Object.assign(GenericAntlrParser.prototype, {
message: function(...args) { return _message( this, 'message', ...args ); },
error: function(...args) { return _message( this, 'error', ...args ); },
warning: function(...args) { return _message( this, 'warning', ...args ); },
info: function(...args) { return _message( this, 'info', ...args ); },
attachLocation,
assignAnnotation,
startLocation,
tokenLocation,
valueWithTokenLocation,
previousTokenAtLocation,
combinedLocation,
surroundByParens,
unaryOpForParens,
leftAssocBinaryOp,
classifyImplicitName,
fragileAlias,
identAst,
functionAst,
setLastAsXpr,
xprToken,
valuePathAst,
signedExpression,
numberLiteral,
quotedLiteral,
pathName,
docComment,
addDef,
addItem,
addExtension,
createSource,
createDict,
createArray,
finalizeDictOrArray,
createPrefixOp,
setOnce,
setMaxCardinality,
pushIdent,
handleComposition,
associationInSelectItem,
reportExpandInline,
checkTypeFacet,
notSupportedYet,
csnParseOnly,
disallowElementExtension,
noAssignmentInSameLine,
noSemicolonHere,
setLocalToken,
setLocalTokenIfBefore,
setLocalTokenForId,
excludeExpected,
isStraightBefore,
meltKeywordToIdentifier,
prepareGenericKeywords,
reportErrorForGenericKeyword,
parseMultiLineStringLiteral,
});
GenericAntlrParser.prototype = Object.assign(
Object.create( antlr4.Parser.prototype ), {
message: function(...args) { return _message( this, 'message', ...args ); },
error: function(...args) { return _message( this, 'error', ...args ); },
warning: function(...args) { return _message( this, 'warning', ...args ); },
info: function(...args) { return _message( this, 'info', ...args ); },
attachLocation,
startLocation,
tokenLocation,
valueWithTokenLocation,
previousTokenAtLocation,
combinedLocation,
createDict,
setDictEndLocation,
surroundByParens,
unaryOpForParens,
leftAssocBinaryOp,
classifyImplicitName,
fragileAlias,
identAst,
functionAst,
valuePathAst,
signedExpression,
numberLiteral,
quotedLiteral,
pathName,
docComment,
addDef,
addItem,
artifactForElementAnnotateOrExtend,
assignProps,
createPrefixOp,
setOnce,
setMaxCardinality,
pushIdent,
handleComposition,
associationInSelectItem,
reportExpandInline,
checkTypeFacet,
notSupportedYet,
csnParseOnly,
disallowElementExtension,
noAssignmentInSameLine,
noSemicolonHere,
setLocalToken,
setLocalTokenIfBefore,
excludeExpected,
isStraightBefore,
meltKeywordToIdentifier,
prepareGenericKeywords,
parseMultiLineStringLiteral,
constructor: GenericAntlrParser, // keep this last
}
);
// Patterns for literal token tests and creation. The value is a map from the

@@ -126,22 +127,23 @@ // `prefix` argument of function `quotedliteral` to the following properties:

// - `literal`: the value which is used instead of `prefix` in the AST
// - `normalized`: function called with argument `value`, return value is used
// instead of `value` in the AST
// TODO: think about laxer regexp for date/time/timestamp - normalization?
// TODO: we might do a range check (consider leap seconds, i.e. max value 60),
// but always allow Feb 29 (no leap year computation)
// TODO: make it a configurable error (syntax-invalid-literal)
// TODO: also use for CSN input
const quotedLiteralPatterns = {
x: {
test_msg: 'A binary literal must have an even number of characters',
test_variant: 'uneven-hex',
test_fn: (str => Number.isInteger(str.length / 2)),
unexpected_msg: 'A binary literal must only contain characters 0-9, a-f and A-F',
unexpected_variant: 'invalid-hex',
unexpected_char: /[^0-9a-f]/i,
},
time: {
test_msg: 'Expected time\'HH:MM:SS\' where H, M and S are numbers and \':SS\' is optional',
test_variant: 'time',
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
},
date: {
test_msg: 'Expected date\'YYYY-MM-DD\' where Y, M and D are numbers',
test_re: /^[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2}$/,
test_variant: 'date',
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
},
timestamp: {
test_msg: 'Expected timestamp\'YYYY-MM-DD HH:MM:SS.u…u\' where Y, M, D, H, S and u are numbers (optional 1-7×u)',
test_variant: 'timestamp',
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,

@@ -169,4 +171,4 @@ },

// Use the following function for language constructs which we (currently) do
// not really compile, just use to produce a CSN for functions parseToCqn() and
// parseToExpr().
// not really compile, just use to produce a CSN for functions parse.cql() and
// parse.expr().
function csnParseOnly( text, ...tokens ) {

@@ -225,2 +227,11 @@ if (!text || this.options.parseOnly)

function setLocalTokenForId( tokenNameMap ) {
const ll1 = this.getCurrentToken();
if (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )) {
const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
if (tokenName)
ll1.type = this.constructor[tokenName];
}
}
// // Special function for rule `requiredSemi` before return $ctx

@@ -283,21 +294,47 @@ // function braceForSemi() {

function prepareGenericKeywords( pathItem ) {
this.$genericKeywords = { argFull: [] };
if (!pathItem)
return;
const func = pathItem.id && specialFunctions[pathItem.id.toUpperCase()];
const spec = func && func[pathItem.args ? pathItem.args.length : 0];
if (!spec)
return;
// currently, we only have 'argFull', i.e. a keyword which is alternative to expression
const genericTokenTypes = {
expr: 'GenericExpr',
separator: 'GenericSeparator',
intro: 'GenericIntro',
};
function prepareGenericKeywords( pathItem, expected = null) {
const length = pathItem?.args?.length || 0;
const argPos = (expected ? length -1 : length);
const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
this.$genericKeywords = spec;
// currently, we only have 'TODO', i.e. a keyword which is alternative to expression
// TODO: If not just at the beginning, we need a stack for $genericKeywords,
// as we can have nested special functions
this.$genericKeywords.argFull = Object.keys( spec );
// @ts-ignore
const token = this.getCurrentToken() || { text: '' };
if (spec[token.text.toUpperCase()] === 'argFull') {
// @ts-ignore
token.type = this.constructor.GenericArgFull;
const text = token.text.toUpperCase();
let generic = spec[text];
// console.log('PGK:',token.text,generic,expected,spec,func,argPos)
if (expected) { // 'separator' or 'expr' (after 'separator')
if (generic !== expected)
return;
}
else if (!generic || generic === 'separator') {
// Mismatch at beginning (or just an expression): keep token type
// (if not expression, issue error and consider the token to be an
// expression replacement, like ALL)
return;
}
else if (generic === 'expr' && spec.intro && spec.intro.includes( text )) {
// token is both an intro and an expression, like LEADING for TRIM
const next = this._input.LT(2).text;
if (!next || // followed by EOF -> consider it to be 'intro', better for CC
next !== ',' && next !== ')' && spec[next.toUpperCase()] !== 'separator')
generic = 'intro'; // is intro if next token is not separator, not ',', ')'
}
// @ts-ignore
token.type = this.constructor[genericTokenTypes[generic]];
}
// To be called before having matched ( HideAlternatives | … )
function reportErrorForGenericKeyword() {
this._errHandler.reportUnwantedToken( this );
//this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
}

@@ -319,2 +356,48 @@ // Attach location matched by current rule to node `art`. If a location is

function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
const { name, $flatten } = anno;
const { path } = name;
if (path.broken || !path[path.length - 1].id)
return;
const pathname = pathName( path );
let absolute = '';
if (name.variant) {
absolute = `${ prefix }${ pathname }#${ name.variant.id }`;
if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?
this.error( 'anno-duplicate-variant', [ name.variant.location ],
{}, // TODO: params
'Annotation variant has been already provided' );
}
}
else if (!prefix || pathname !== '$value') {
absolute = `${ prefix }${ pathname }`;
}
else {
absolute = prefix.slice( 0, -1 );
}
if ($flatten) {
for (const a of $flatten)
this.assignAnnotation( art, a, `${ absolute }.`, iHaveVariant || name.variant);
}
else {
name.absolute = absolute;
const prop = '@' + absolute;
const old = art[prop];
if (old && old.$inferred)
art[prop] = anno;
else
dictAddArray( art, prop, anno, (n, location, a) => {
this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
'Duplicate assignment with $(ANNO)' );
a.$errorReported = true; // do not report again later as anno-duplicate-xyz
} );
}
if (!prefix) { // set deprecated $annnotations for cds-lsp
if (!art.$annotations)
art.$annotations = [];
const location = locUtils.combinedLocation( anno.name, anno );
art.$annotations.push( { value: anno, location } );
}
}
/**

@@ -419,16 +502,2 @@ * Return start location of `token`, or the first token matched by the current

function createDict( location = null ) {
const dict = Object.create(null);
dict[$location] = location || this.startLocation( this._input.LT(-1) );
return dict;
}
function setDictEndLocation( dict ) {
const stop = this._input.LT(-1);
Object.assign( dict[$location], {
endLine: stop.line,
endCol: stop.stop - stop.start + stop.column + 2,
} );
}
function surroundByParens( expr, open, close, asQuery = false ) {

@@ -499,6 +568,7 @@ if (!expr)

// Return AST for identifier token `token`. Also check that identifer is not empty.
function identAst( token, category ) {
function identAst( token, category, noTokenTypeCheck = false ) {
token.isIdentifier = category;
let id = token.text;
if (token.type !== this.constructor.Identifier && !/^[a-zA-Z]+$/.test( id ))
if (!noTokenTypeCheck &&
token.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( id ))
id = '';

@@ -530,7 +600,7 @@ if (token.text[0] === '!') {

function functionAst( token, xprToken ) {
function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
// TODO: XSN func cleanup
const location = this.tokenLocation( token );
const args = xprToken
? [ { op: { location, val: 'xpr' }, args: [], location: this.tokenLocation( xprToken ) } ]
const args = xpr
? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
: [];

@@ -545,2 +615,17 @@ return {

function setLastAsXpr( args ) {
const pos = args.length - 1;
const last = args[pos];
if (!last || last?.op?.val === 'ixpr') // actuall an internal xpr, which is always flattened
return last;
const location = { ...last.location };
args[pos] = { op: { location, val: 'ixpr' }, args: [ last ], location };
return args[pos].args;
}
function xprToken() {
const token = this._input.LT(-1);
return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
}
function valuePathAst( ref ) {

@@ -644,6 +729,5 @@ // TODO: XSN representation of functions is a bit strange - rework if methods

// TODO: make tests available for CSN parser
if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
!this.options.parseOnly)
this.error( null, location, p.test_msg ); // TODO: message id
this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );

@@ -653,3 +737,3 @@ if (p.unexpected_char) {

if (~idx) {
this.error( null, { // TODO: message id
this.warning( 'syntax-invalid-literal', {
file: location.file,

@@ -660,3 +744,3 @@ line: location.line,

endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
}, p.unexpected_msg );
}, { '#': p.unexpected_variant } );
}

@@ -708,15 +792,12 @@ }

// Add new definition to dictionary property `env` of node `parent` and return
// that definition. Also attach the following properties to the new definition:
// - `name`: argument `name`, which is used as key in the dictionary
// - `kind`: argument `kind` if that is truthy
// - `location`: argument `location` or the start location of source matched by
// current rule
// - properties in argument `props` which are no empty (undefined, null, {},
// []), ANTLR tokens are replaced by their locations
// Add new definition `art` to dictionary property `env` of node `parent`.
// Return `art`.
//
// Hack: if argument `location` is exactly `true`, do not set `location`
// (except if part of `props`), but also include the empty properties of
// `props`.
function addDef( parent, env, kind, name, annos, props, location ) {
// If argument `kind` is provided, set `art.kind` to that value.
// If argument `name` is provided, set `art.name`:
// - if `name` is an array, the name consist of the ID of the last path item
// (for elements via columns, foreign keys, table aliases)
// - if `name` is an object, the name consist of the IDs of all path items
// (for main artifact definitions)
function addDef( art, parent, env, kind, name ) {
if (Array.isArray(name)) {

@@ -726,3 +807,3 @@ // XSN TODO: clearly say: definitions have name.path, members have name.id

if (last && last.id) { // // A.B.C -> 'C'
name = {
art.name = {
id: last.id, location: last.location, $inferred: 'as',

@@ -732,10 +813,11 @@ };

}
else if (name && name.id == null) {
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
else if (name) {
art.name = name;
if (name.id == null)
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
// TODO: get rid of setting `id`, only use for named values in structs
}
const art = this.assignProps( { name }, annos, props, location );
if (kind)
art.kind = kind;
if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
if (!art.name || art.name.id == null) {

@@ -745,5 +827,8 @@ // no id was parsed, but with error recovery: no further error

// which could be tested in name search (then no undefined-ref error)
// console.log( kind+': !!', art, parent, env, name )
return art;
}
else if (env === 'artifacts' || env === 'vocabularies') {
// console.log( kind+':', art, parent, env, name )
if (env === 'artifacts' || env === 'vocabularies') {
dictAddArray( parent[env], art.name.id, art );

@@ -755,14 +840,14 @@ }

else {
dictAdd( parent[env], art.name.id, art, ( name, loc ) => {
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
// do not use function(), otherwise `this` is wrong:
if (kind === 0) {
this.error( 'duplicate-argument', loc, { name },
this.error( 'duplicate-argument', loc, { name: duplicateName },
'Duplicate value for parameter $(NAME)' );
}
else if (kind === '') {
this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
'Duplicate $(NAME) in the $(KEYWORD) clause' );
}
else {
this.error( 'duplicate-prop', loc, { name },
this.error( 'duplicate-prop', loc, { name: duplicateName },
'Duplicate value for structure property $(NAME)' );

@@ -775,83 +860,76 @@ }

// Add new definition to array property `env` of node `parent` and return
// that definition. Also attach the following properties to the new definition:
// - `kind`: argument `kind` if that is truthy
// - `location`: argument `location` or the start location of source matched by
// current rule
// - properties in argument `props` which are no empty (undefined, null, {},
// []); ANTLR tokens are replaced by their locations
//
// Hack: if argument `location` is exactly `true`, do not set `location`
// (except if part of `props`), but also include the empty properties of
// `props`.
function addItem( parent, env, kind, annos, props, location ) {
const art = this.assignProps( {}, annos, props, location );
if (kind)
art.kind = kind;
if (!env)
parent.push( art );
else if (!parent[env])
parent[env] = [ art ];
else
parent[env].push( art );
// Add new definition `art` to array property `env` of node `parent`.
// Also set `kind`. Returns `art`.
function addItem( art, parent, env, kind ) {
art.kind = kind;
parent[env].push( art );
return art;
}
/**
* For `annotate/extend E:elem.sub`, create the `elements` structure
* that can be used by the core compiler to annotate/extend elements.
* Add `annotate/extend Main.Artifact:elem.sub` to `‹xsn›.extensions`:
* - the array item is an extend/annotate for `Main.Artifact`,
* - for each path item in `elem.sub`, we add an `elements` property containing
* one extend/annotate for the corresponding element
* - The deepest extend/annotate is the object which is to be extended
*
* @param {string} kind Either `annotate` or `extend`
* @param {object} artifact Main artifact that shall have `elements`.
* @param {XSN.Path} elementPath Path as returned by `simplePath` token.
* @param {object[]} annos Existing annotations that shall be added to the _last_ path step.
* @param {XSN.Location} artifactLocation Start location of the `annotate` statement.
* @returns {object} Deepest element
*/
function artifactForElementAnnotateOrExtend(kind, artifact, elementPath, annos, artifactLocation ) {
if (!Array.isArray(elementPath) || elementPath.broken || elementPath.length < 1)
return artifact;
* @param {object} ext The object containing the location and annotations for the extension.
* @param {object} parent The parent containing the `extensions` property, i.e. the source.
* @param {string} kind Either `annotate` or `extend`.
* @param {object} artName The "name object" for `Main.Artifact`.
* @param {XSN.Path} elemPath Path as returned by `simplePath` rule.
*/
function addExtension( ext, parent, kind, artName, elemPath ) {
const { location } = ext;
if (!Array.isArray( elemPath ) || !elemPath.length || elemPath.broken) {
ext.name = artName;
this.addItem( ext, parent, 'extensions', kind );
return;
}
// Note: the element extensions share a common `location`, also with the
// extension of the main artifact; its end location will usually set later
parent = this.addItem( { name: artName, location }, parent, 'extensions', kind );
for (const seg of elementPath.slice(0, -1)) {
artifact = this.addDef( artifact, 'elements', kind,
{ path: [seg], location: seg.location }, null, {}, artifactLocation );
const last = elemPath[elemPath.length - 1];
for (const seg of elemPath) {
parent.elements = Object.create(null); // no dict location → no createDict()
parent = this.addDef( (seg === last ? ext : { location }),
parent, 'elements', kind, seg );
}
const last = elementPath[elementPath.length - 1];
artifact = this.addDef( artifact, 'elements', kind,
{ path: [ last ], location: last.location }, annos, {}, artifactLocation );
return artifact;
}
/** Assign all non-empty (undefined, null, {}, []) properties in argument
* `props` and argument `annos` as property `$annotations` to `target`
* and return it. Hack: if argument `annos` is exactly `true`, return
* `Object.assign( target, props )`, for rule `namedValue`. ANTLR tokens are
* replaced by their locations.
*
* @param {any} target
* @param {any[]|true} [annos=[]]
* @param {any} [props]
* @param {any} [location]
*/
function assignProps( target, annos = [], props = null, location = null) {
if (annos === true)
return Object.assign( target, props );
target.location = location || this.startLocation( this._ctx.start );
// Object.assign without "empty" elements/properties and with mappings:
// - token instanceof antlr4.CommonToken => location of token
for (const key in props) {
let val = props[key];
if (val instanceof antlr4.CommonToken)
val = this.valueWithTokenLocation( true, val);
// only copy properties which are not undefined, null, {} or []
if (val != null &&
(typeof val !== 'object' ||
(Array.isArray(val) ? val.length : Object.getOwnPropertyNames(val).length) ) )
target[key] = val;
}
if (annos)
target.$annotations = annos;
return target;
// must be in action directly after having parsed '{' or '(`
function createDict() {
const dict = Object.create(null);
dict[$location] = this.startLocation( this._input.LT(-1) );
return dict;
}
// must be in action directly after having parsed '[' or '(` or `{`
function createArray() {
const array = [];
array[$location] = this.startLocation( this._input.LT(-1) );
return array;
}
// must be in action directly after having parsed '}' or ')`
function finalizeDictOrArray( dict ) {
const loc = dict[$location];
if (!loc)
return;
const stop = this._input.LT(-1);
loc.endLine = stop.line;
loc.endCol = stop.stop - stop.start + stop.column + 2;
}
function createSource() {
return {
kind: 'source',
usings: [],
dependencies: [],
artifacts: Object.create(null),
// vocabularies: Object.create(null),
extensions: [],
};
}
// Create AST node for prefix operator `op` and arguments `args`

@@ -902,10 +980,8 @@ function createPrefixOp( token, args ) {

function setMaxCardinality( art, token, max, inferred ) {
function setMaxCardinality( art, token, max ) {
const location = this.tokenLocation( token );
if (!art.cardinality) {
art.cardinality = { targetMax: Object.assign( { location }, max ), location };
if (inferred)
art.cardinality.$inferred = inferred;
}
else if (!inferred) {
else {
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },

@@ -982,4 +1058,2 @@ 'The target cardinality has already been specified - ignored $(KEYWORD)' );

module.exports = {
genericAntlrParser: GenericAntlrParser,
};
module.exports = GenericAntlrParser;
'use strict';
const whitespaceRegEx = /[\t\u{000B}\u{000C} \u{00A0}\u{FEFF}\p{Zs}]/u;
const newLineRegEx = /\r\n?|\n|\u2028|\u2029/u;
const {
isWhitespaceOrNewLineOnly,
isWhitespaceCharacterNoNewline,
cdlNewLineRegEx,
} = require('./textUtils');
/**
* Returns true if the given string only contains whitespace characters.
*
* @todo Combine with function from docCommentParser
* @param {string} str
* @returns {boolean}
*/
function isWhiteSpaceOnly(str) {
return /^\s*$/.test(str);
}
/**
* Check whether the given character is a white-space character as
* defined by §11.2 of the ECMAScript 2020 specification.
* See <https://262.ecma-international.org/11.0/#sec-white-space>.
*
* | Code Point | Name | Abbreviation |
* |:--------------------|:-----------------------------------------------|--------------|
* | U+0009 | CHARACTER TABULATION | `<TAB>` |
* | U+000B | LINE TABULATION | `<VT>` |
* | U+000C | FORM FEED (FF) | `<FF>` |
* | U+0020 | SPACE | `<SP>` |
* | U+00A0 | NO-BREAK SPACE | `<NBSP>` |
* | U+FEFF | ZERO WIDTH NO-BREAK SPACE | `<ZWNBSP>` |
* | Other category “Zs” | Any other Unicode “Space_Separator” code point | `<USP>` |
*
* @param char
* @returns {boolean}
*/
function isWhitespaceCharacter(char) {
return whitespaceRegEx.test(char);
}
/**
* Strips and counts the indentation from the given string.
* This function is similar to the one in docCommentParser.js, but
* has special handling for the first and last line of the string.
*

@@ -61,6 +33,6 @@ * @example

// Note: We have to check all newline characters, as the string is not normalized, yet.
const lines = str.split(newLineRegEx);
const lines = str.split(cdlNewLineRegEx);
const n = lines.length;
const hasTrailingLineBreak = newLineRegEx.test(str[str.length - 1]);
const hasTrailingLineBreak = cdlNewLineRegEx.test(str[str.length - 1]);
if (hasTrailingLineBreak) {

@@ -77,3 +49,3 @@ // Shortcut:

// even if blank. For all other lines, blank lines are ignored.
if (isWhiteSpaceOnly(line) && index !== (n-1))
if (isWhitespaceOrNewLineOnly(line) && index !== (n-1))
return min;

@@ -83,3 +55,3 @@

const length = Math.min(min, line.length);
while (count < length && isWhitespaceCharacter(line[count])) {
while (count < length && isWhitespaceCharacterNoNewline(line[count])) {
count++;

@@ -144,3 +116,3 @@ }

// there is no text without at least one line break.
if (!newLineRegEx.test(this.str)) {
if (!cdlNewLineRegEx.test(this.str)) {
const loc = this._locationForCharacters(this.end, 1);

@@ -307,3 +279,3 @@ this.parser.message('syntax-invalid-text-block', loc);

for (let j = 0; j < count; ++j) {
if (!this._eos() && /^[0-9A-Fa-f]$/.test(this._lookahead())) {
if (!this._eos() && /^[\dA-Fa-f]$/.test(this._lookahead())) {
this._move();

@@ -338,3 +310,3 @@ codePoint += this._current();

while (!this._eos()) {
if (/^[0-9A-Fa-f]$/.test(this._lookahead())) {
if (/^[\dA-Fa-f]$/.test(this._lookahead())) {
this._move();

@@ -341,0 +313,0 @@ codePoint += this._current();

@@ -90,2 +90,11 @@ // Official cds-compiler API.

/**
* When set to `true`, and the model contains an entity `sap.common.Languages`
* with an element `code`, all generated texts entities additionally contain
* an element `language` which is an association to `sap.common.Languages`
* using element `locale`.
*
* @since v2.8.0
*/
addTextsLanguageAssoc?: boolean
/**
* Option for {@link compileSources}. If set, all objects inside the

@@ -407,2 +416,6 @@ * provided sources dictionary are interpreted as XSN structures instead

*
* This function uses the direct value of USINGs (in CSN `"requires"`) for its dependency graph,
* i.e. this function does not resolve USINGs. If a USING matches a key in sourcesDict,
* we assume that it depends on that file (/sourcesDict entry).
*
* See function {@link compile} for the meaning of the argument `options`. If there

@@ -586,3 +599,2 @@ * are parse or other compilation errors, throws an exception {@link CompilationError}

* unset.
*/

@@ -614,21 +626,2 @@ export function messageContext(sourceLines: string[], msg: CompileMessage, config?: {

/**
* Same as {@link to.edm} but expects a {@link for.odata} transformed CSN.
*
* Note that parameter `service` is the same as `ODataOptions.service`.
* The latter is not used. OData specific options have an internal format.
*
* @deprecated Use {@link to.edm} instead. If it detects a pre-transformed CSN, it won't run for.odata().
*/
export function preparedCsnToEdm(csn: CSN, service: string, options: ODataOptions): object;
/**
* Same as {@link to.edm} but expects a {@link for.odata} transformed CSN.
*
* Note that parameter `service` is the same as `ODataOptions.service`.
* The latter is not used. OData specific options have an outdated format.
*
* @deprecated Use {@link to.edmx} instead. If it detects a pre-transformed CSN, it won't run for.odata().
*/
export function preparedCsnToEdmx(csn: CSN, service: string, options: ODataOptions): string;
export namespace parse {

@@ -666,11 +659,2 @@ /**

/**
* @deprecated Use {@link parse.cql} instead.
*/
export function parseToCqn(cql: string, filename?: string, options?: Options): CQN;
/**
* @deprecated Use {@link parse.expr} instead.
*/
export function parseToExpr(expr: string, filename?: string, options?: Options): CXN;
/**
* @note Actual name is "for" which can't be used directly in the documentation

@@ -820,3 +804,3 @@ * as it is a reserved name in TypeScript.

*/
[namespace: string]: string
namespace?: string
}

@@ -866,17 +850,14 @@

*
* The above rules might differ for different SQL dialects.
* Exceptions will be listed below.
*
* @param artifactName The fully qualified name of the artifact
* @param sqlMapping The naming mode to use. See {@link SqlOptions.sqlMapping} for more details.
* @param csn
* @param sqlDialect The SQL dialect to use. See {@link SqlOptions.sqlDialect} for more details.
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
* @since v2.1.0
*/
export function getArtifactCdsPersistenceName(artifactName: string, sqlMapping: string, csn: CSN): string;
export function getArtifactCdsPersistenceName(artifactName: string, sqlMapping: string, csn: CSN, sqlDialect: string): string;
/**
* This is an old function signature. If it is used - with a namespace as the third argument - the result might be wrong,
* since the `.` -> `_` conversion for 'quoted'/'hdbcds' is missing.
*
* @deprecated Use the other overload with CSN instead.
*/
export function getArtifactCdsPersistenceName(artifactName: string, sqlMapping: string, namespace: string): string;
/**
* Return the resulting database element name for `elemName`, depending on the current

@@ -889,7 +870,11 @@ * naming mode.

*
* The above rules might differ for different SQL dialects.
* Exceptions will be listed below.
*
* @param elemName The name of the element. For structured elements, concat by dot, e.g. `sub.elem`.
* @param sqlMapping The naming mode to use. See {@link SqlOptions.sqlMapping} for more details.
* @param sqlDialect The SQL dialect to use. See {@link SqlOptions.sqlDialect} for more details.
* @returns The resulting database element name for 'elemName', depending on the current naming mode.
*/
export function getElementCdsPersistenceName(elemName: string, sqlMapping: string): string;
export function getElementCdsPersistenceName(elemName: string, sqlMapping: string, sqlDialect: string): string;

@@ -1014,3 +999,3 @@ /**

/**
* String representation of the message. May be a multi-line message in the future.
* String representation of the message. It may be a multi-line message in the future.
*/

@@ -1034,3 +1019,3 @@ message: string

/**
* Returns a human readable string of the compiler message. Uses {@link messageString} to render
* Returns a human-readable string of the compiler message. Uses {@link messageString} to render
* the message without filename normalization and without a message ID.

@@ -1067,3 +1052,3 @@ */

* - `string` or `Buffer`: the file content
* - `{ realname: fs.realpath(filename) }`: if filename is not canonicalized
* - `{ realname: fs.realpath.native(filename) }`: if filename is not canonicalized
*/

@@ -1070,0 +1055,0 @@ export type FileCache = Record<string, boolean | string | Buffer | { realname: string }>;

@@ -16,16 +16,15 @@ // Main entry point for the CDS Compiler

const backends = require('./backends');
const { odata, cdl, sql, hdi, hdbcds, edm, edmx } = require('./api/main');
const { getArtifactDatabaseNameOf, getElementDatabaseNameOf } = require('./model/csnUtils');
const { traverseCsn } = require('./model/api');
const { createMessageFunctions, sortMessages, sortMessagesSeverityAware, deduplicateMessages } = require('./base/messages');
const { smartId, smartFuncId, delimitedId } = require('./sql-identifier');
const keywords = require( './base/keywords' );
const snapi = lazyload('./api/main');
const csnUtils = lazyload('./model/csnUtils');
const model_api = lazyload('./model/api');
const messages = lazyload('./base/messages');
const sqlIdentifier = lazyload('./sql-identifier');
const keywords = lazyload( './base/keywords' );
const parseLanguage = require('./language/antlrParser');
const { parseX, compileX, compileSyncX, compileSourcesX, InvocationError } = require('./compiler');
const { fns } = require('./compiler/shared');
const define = require('./compiler/define');
const { isInReservedNamespace } = require("./compiler/builtins");
const finalizeParseCdl = require('./compiler/finalize-parse-cdl');
const parseLanguage = lazyload('./language/antlrParser');
const compiler = lazyload('./compiler');
const shared = lazyload('./compiler/shared');
const define = lazyload('./compiler/define');
const builtins = lazyload("./compiler/builtins");
const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');

@@ -37,14 +36,4 @@ // The compiler version (taken from package.json)

const {
CompilationError,
messageString,
messageStringMultiline,
messageContext,
hasErrors,
explainMessage,
hasMessageExplanation
} = require('./base/messages');
const toCsn = lazyload('./json/to-csn')
const { compactModel, compactQuery, compactExpr } = require('./json/to-csn')
function parseCdl( cdlSource, filename, options = {} ) {

@@ -55,3 +44,3 @@ options = Object.assign( {}, options, { parseCdl: true } );

const model = { sources, options, $functions: {}, $volatileFunctions: {} };
const messageFunctions = createMessageFunctions( options, 'parse', model );
const messageFunctions = messages.createMessageFunctions( options, 'parse', model );
model.$messageFunctions = messageFunctions;

@@ -62,23 +51,23 @@

sources[filename] = xsn;
fns( model );
shared.fns( model );
define( model );
finalizeParseCdl( model );
messageFunctions.throwWithError();
return compactModel( model );
return toCsn.compactModel( model );
}
function parseCql( cdlSource, filename = '<query>.cds', options = {} ) {
const messageFunctions = createMessageFunctions( options, 'parse' );
const messageFunctions = messages.createMessageFunctions( options, 'parse' );
const xsn = parseLanguage( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
messageFunctions, 'query' );
messageFunctions.throwWithError();
return compactQuery( xsn );
return toCsn.compactQuery( xsn );
}
function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
const messageFunctions = createMessageFunctions( options, 'parse' );
const messageFunctions = messages.createMessageFunctions( options, 'parse' );
const xsn = parseLanguage( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
messageFunctions, 'expr' );
messageFunctions.throwWithError();
return compactExpr( xsn );
return toCsn.compactExpr( xsn );
}

@@ -91,62 +80,71 @@

version,
compile: (...args) => compileX(...args).then( compactModel ), // main function
compileSync: (filenames, dir, options, fileCache) => compactModel( compileSyncX(filenames, dir, options, fileCache) ), // main function
compileSources: (sourcesDict, options) => compactModel( compileSourcesX(sourcesDict, options) ), // main function
compile: (...args) => compiler.compileX(...args).then( toCsn.compactModel ), // main function
compileSync: (filenames, dir, options, fileCache) => toCsn.compactModel( compiler.compileSyncX(filenames, dir, options, fileCache) ), // main function
compileSources: (sourcesDict, options) => toCsn.compactModel( compiler.compileSourcesX(sourcesDict, options) ), // main function
compactModel: csn => csn, // for easy v2 migration
CompilationError,
sortMessages,
sortMessagesSeverityAware,
deduplicateMessages,
messageString,
messageStringMultiline,
messageContext,
explainMessage,
hasMessageExplanation,
InvocationError, // TODO: make it no error if same file name is provided twice
hasErrors,
get CompilationError() {
Object.defineProperty(this, "CompilationError", {
value: messages.CompilationError,
writable: false,
configurable: false,
enumerable: false
});
return messages.CompilationError;
},
sortMessages: (...args) => messages.sortMessages(...args),
sortMessagesSeverityAware: (...args) => messages.sortMessagesSeverityAware(...args),
deduplicateMessages: (...args) => messages.deduplicateMessages(...args),
messageString: (...args) => messages.messageString(...args),
messageStringMultiline: (...args) => messages.messageStringMultiline(...args),
messageContext: (...args) => messages.messageContext(...args),
explainMessage: (...args) => messages.explainMessage(...args),
hasMessageExplanation: (...args) => messages.hasMessageExplanation(...args),
get InvocationError() {
Object.defineProperty(this, "InvocationError", {
value: compiler.InvocationError,
writable: false,
configurable: false,
enumerable: false
});
return compiler.InvocationError;
},
hasErrors: (...args) => messages.hasErrors(...args),
// Backends
// TODO: Expose when transformers are switched to CSN
// toOdataWithCsn: backends.toOdataWithCsn,
preparedCsnToEdmx : (csn, service, options) => { return backends.preparedCsnToEdmx(csn, service, options).edmx},
preparedCsnToEdm : (csn, service, options) => { return backends.preparedCsnToEdm(csn, service, options).edmj},
// additional API:
parse: { cdl: parseCdl, cql: parseCql, expr: parseExpr }, // preferred names
/**
* @deprecated Use parse.cql instead
*/
parseToCqn: parseCql,
/**
* @deprecated Use parse.expr instead
*/
parseToExpr: parseExpr, // deprecated name
parse: { cdl: (...args) => parseCdl(...args), cql: (...args) => parseCql(...args), expr: (...args) => parseExpr(...args) },
// SNAPI
for: { odata },
for: { odata: (...args) => snapi.odata(...args) },
to: {
cdl: Object.assign(cdl, {
keywords: Object.freeze([ ...keywords.cdl ]),
functions: Object.freeze([ ...keywords.cdl_functions ]),
cdl: Object.assign((...args) => snapi.cdl(...args), {
keywords: Object.freeze([ ...keywords.cdl ] ),
functions: Object.freeze([ ...keywords.cdl_functions ] ),
}),
sql: Object.assign(sql, {
sqlite: { keywords: Object.freeze([ ...keywords.sqlite ] )},
smartId,
smartFunctionId: smartFuncId,
delimitedId,
sql: Object.assign((...args) => snapi.sql(...args), {
sqlite: {
keywords: Object.freeze([ ...keywords.sqlite ] )
},
smartId: (...args) => sqlIdentifier.smartId(...args),
smartFunctionId: (...args) => sqlIdentifier.smartFuncId(...args),
delimitedId: (...args) => sqlIdentifier.delimitedId(...args),
}),
hdi: Object.assign(hdi, {
keywords: Object.freeze([ ...keywords.hana ]),
hdi: Object.assign((...args) => snapi.hdi(...args), {
migration: (...args) => snapi.hdi.migration(...args),
keywords: Object.freeze([ ...keywords.hana ] ),
}),
hdbcds: Object.assign(hdbcds, {
keywords: Object.freeze([ ...keywords.hdbcds ]),
hdbcds: Object.assign((...args) => snapi.hdbcds(...args), {
keywords: Object.freeze([ ...keywords.hdbcds ] ),
}),
edm,
edmx
edm: Object.assign((...args) => snapi.edm(...args), {
all: (...args) => snapi.edm.all(...args)
}),
edmx: Object.assign((...args) => snapi.edmx(...args), {
all: (...args) => snapi.edmx.all(...args)
}),
},
// Convenience for hdbtabledata calculation in @sap/cds
getArtifactCdsPersistenceName: getArtifactDatabaseNameOf,
getElementCdsPersistenceName: getElementDatabaseNameOf,
getArtifactCdsPersistenceName: (...args) => csnUtils.getArtifactDatabaseNameOf(...args),
getElementCdsPersistenceName: (...args) => csnUtils.getElementDatabaseNameOf(...args),
// Other API functions:
traverseCsn,
traverseCsn: (...args) => model_api.traverseCsn(...args),

@@ -156,8 +154,33 @@ // INTERNAL functions for the cds-lsp package and friends - before you use

// new releases (even having the same major version):
$lsp: { parse: parseX, compile: compileX, getArtifactName: a => a.name },
$lsp: { parse: (...args) => compiler.parseX(...args), compile: (...args) => compiler.compileX(...args), getArtifactName: a => a.name },
// CSN Model related functionality
model: {
isInReservedNamespace,
isInReservedNamespace: (...args) => builtins.isInReservedNamespace(...args),
},
};
/**
* Load the module on-demand and not immediately.
*
* @param {string} moduleName Name of the module to load - like with require
* @returns {object} A Proxy that handles the on-demand loading
*/
function lazyload(moduleName) {
let module;
return new Proxy(((...args) => {
if (!module) // eslint-disable-next-line global-require
module = require(moduleName);
if (module.apply && typeof module.apply === 'function')
return module.apply(this, args);
return module; // for destructured calls
}), {
get(target, name) {
if (!module) // eslint-disable-next-line global-require
module = require(moduleName);
return module[name];
},
});
}

@@ -330,3 +330,3 @@ // CSN functionality for resolving references

return notFound;
throw new ModelError( 'Undefined reference' );
throw new ModelError( `Unknown artifact reference: ${typeof ref !== 'string' ? JSON.stringify(ref.ref) : ref}` );
}

@@ -333,0 +333,0 @@

@@ -8,2 +8,4 @@ 'use strict';

const { ModelError } = require("../base/error");
const { typeParameters } = require("../compiler/builtins");
const { forEach } = require('../utils/objectUtils');
const version = require('../../package.json').version;

@@ -38,7 +40,10 @@

/**
* Get utility functions for a given CSN.
* Get utility functions for a given CSN. Re-exports functions of `csnRefs()`.
* @param {CSN.Model} model (Compact) CSN model
*/
function getUtils(model, universalReady) {
const { artifactRef, inspectRef, effectiveType, getOrigin, targetAspect, getColumn, getElement, initDefinition } = csnRefs(model, universalReady);
const _csnRefs = csnRefs(model, universalReady);
const { artifactRef } = _csnRefs;
/** Cache for getFinalBaseTypeWithProps(). Specific to the current model. */
const finalBaseTypeCache = Object.create(null);

@@ -61,13 +66,6 @@ return {

cloneWithTransformations,
getFinalBaseType,
inspectRef,
artifactRef,
effectiveType,
getFinalBaseTypeWithProps,
get$combined,
getOrigin,
getQueryPrimarySource,
targetAspect,
getColumn,
getElement,
initDefinition
..._csnRefs,
};

@@ -267,3 +265,3 @@

return obj.elements ||
(obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseType(obj.type).elements)));
(obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseTypeWithProps(obj.type)?.elements)));
}

@@ -493,93 +491,118 @@

function _normalizeTypeRef(type) {
if (type && typeof type === 'object' && type.ref?.length === 1)
type = type.ref[0]; // simplify type: no element -> simple string can be used
return type;
}
/**
* Resolve to the final type of a type, that means follow type chains, references to other types or
* elements a.s.o
* Works for all kinds of types, strings as well as type objects. Strings need to be absolute type names.
* Returns the final type as string (if it has a name, which is not always the case, think of embedded structures),
* else the type object itself is returned. If a type is structured, you can navigate into it by providing a path,
* e.g. given the following model
* type bar: S.foo;
* type s1 {
* s: s2;
* };
* type s2 {
* u: type of S.e:t;
* }
* service S {
* type foo: type of S.e:i.j1;
* entity e {
* key i: { j1: Integer };
* t: bar;
* v: s1;
* x: blutz.s.u;
* };
* type blutz: S.e.v;
* view V as select from e {
* 1+1 as i: bar,
* };
* type tt: type of V:i;
* }
* the following calls will all return 'cds.Integer'
* getFinalBaseType('S.tt')
* getFinalBaseType('S.e',['i','j1'])
* getFinalBaseType('S.e',['t'])
* getFinalBaseType('S.e',['x'])
* getFinalBaseType('S.blutz',['s', 'u'])
* Types are always resolved as far as possible. A type name which has no further definition is simply returned.
* Composed types (structures, entities, views, ...) are returned as type objects, if not drilled down into
* the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
* no type (e.g. expr in a view without explicit type) returns 'null'
* Resolve to the final type of a type, that means follow type chains, references, etc.
* Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
*
* @param {string|object} type Type - either string or ref
* @param {CSN.Path} path
* @param {WeakMap} [resolved=new WeakMap()] WeakMap containing already resolved refs - if a ref is not cached, it will be resolved JIT
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
* @returns
* Returns `null` if the type can't be resolved or if the referenced element has no type,
* e.g. `typeof V:calculated`.
* Otherwise, if scalar, returns an object that has a `type` property and all collected type
* properties, or the type object with `elements` or `items` property if structured/arrayed.
*
* Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
* and use the newly returned `getFinalBaseTypeWithProps()`.
*
* @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
* @returns {object|null}
*/
function getFinalBaseType(type, path = [], resolved = new WeakMap(), cycleCheck = undefined) {
function getFinalBaseTypeWithProps(type) {
type = _normalizeTypeRef(type);
if (!type)
return type;
if (typeof(type) === 'string') {
if (isBuiltinType(type)) // built-in type
return type;
if (cycleCheck) {
let visited = path.length? type + ':' + path.join('.') : type;
if (cycleCheck[visited])
throw new ModelError('Circular type chain on type ' + type);
else
cycleCheck[visited] = true;
return null;
// Nothing to copy from builtin type name.
if (typeof type === 'string' && isBuiltinType( type )) {
return { type };
}
// We differentiate between ref and type to avoid collisions due to dict key.
// Delimiter chosen arbitrarily; just one that is rarely used.
const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
if (finalBaseTypeCache[resolvedKey]) {
if (finalBaseTypeCache[resolvedKey] === true)
throw new ModelError(`Detected circular type reference; can't resolve: ${resolvedKey}`);
return finalBaseTypeCache[resolvedKey];
}
let typeRef = artifactRef(type); // throws if not found
const isNonScalar = _cacheNonScalar(typeRef);
if (isNonScalar)
return finalBaseTypeCache[resolvedKey];
const props = {};
_copyTypeProps(props, typeRef);
// If the resolved type is a builtin, stop and use its type arguments.
type = _normalizeTypeRef(typeRef.type);
if (typeof type === 'string' && isBuiltinType(type))
return _cacheResolved(props);
// Set to true (before the recursive call) to avoid cyclic issues.
finalBaseTypeCache[resolvedKey] = true;
// Continue the search
const finalBase = getFinalBaseTypeWithProps(type);
if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
return _cacheResolved(null);
const nonScalar = _cacheNonScalar(finalBase);
if (nonScalar)
return finalBaseTypeCache[resolvedKey];
// If not a non-scalar, must be resolved type.
_copyTypeProps(props, finalBase);
_cacheResolved(props);
return props;
/**
* Cache/Store the type props under the current `resolvedKey` in the `resolved` cache.
*
* @param {object} typeProps
*/
function _cacheResolved(typeProps) {
finalBaseTypeCache[resolvedKey] = typeProps;
return typeProps;
}
/**
* Structured or arrayed types are not followed further, so cache them.
*
* @param obj
* @returns {boolean} True, if structured/arrayed/invalid, false if scalar.
*/
function _cacheNonScalar(obj) {
if (!obj) { // Reference has no proper type, e.g. due to `type of View:calculated`.
_cacheResolved(null);
return true;
}
else {
cycleCheck = Object.create(null);
if (obj.elements || obj.items) {
_cacheResolved(obj);
return true;
}
let definedType = model.definitions[type];
if (definedType && definedType.type)
return getFinalBaseType(definedType.type, path, resolved, cycleCheck);
else
return getFinalBaseType(definedType, path, resolved, cycleCheck);
return false;
}
else if (typeof(type) === 'object') {
if (type.ref) {
// assert type.ref instanceof Array && type.ref.length >= 1
const ref = resolved.has(type) ? resolved.get(type).art : artifactRef(type);
return getFinalBaseType(ref, path, resolved, cycleCheck);
}
else if (type.elements) {
if (path.length) {
let [e, ...p] = path;
return getFinalBaseType(type.elements[e], p, resolved, cycleCheck);
/**
* Copy type properties from source to target. Also copies `type`, `enum`,
* and `localized` (if keepLocalized is true). Only copies from source,
* if target does not have them.
*
* @param {object} target
* @param {object} source
*/
function _copyTypeProps(target, source) {
target.type = source.type;
const typeProps = [ ...typeParameters.list, 'enum', 'default', 'localized' ];
for (const param of typeProps) {
if (target[param] === undefined && source[param] !== undefined) {
target[param] = source[param];
}
}
else if (type.type)
return (getFinalBaseType(type.type, path, resolved, cycleCheck));
else if (type.items)
return type;
else
// TODO: this happens if we don't have a type, e.g. an expression in a select list
// in a view without explicit type. Instead of returning null we might want to return
// the object instead?
return null;
return target;
}
return type;
}

@@ -778,21 +801,2 @@ }

// Like Object.assign() but copies also non enumerable properties
function assignAll(target, ...sources) {
sources.forEach(source => {
const descriptors = Object.getOwnPropertyNames(source).reduce((propertyDescriptors, current) => {
propertyDescriptors[current] = Object.getOwnPropertyDescriptor(source, current);
return propertyDescriptors;
}, {});
// by default, Object.assign copies enumerable Symbols too
Object.getOwnPropertySymbols(source).forEach(sym => {
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
/**

@@ -905,8 +909,6 @@ * @param {CSN.Query} mainQuery

function isEdmPropertyRendered(elementCsn, options) {
if(options.toOdata)
options = options.toOdata;
// FKs are rendered in
// V2/V4 flat: always on
// V4 struct: on/off
const renderForeignKey = (options.version === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;

@@ -934,12 +936,15 @@ const isNavigable = elementCsn.target ?

*
* If the old function signature is used - with a namespace as the third argument - the result might be wrong,
* since the '.' -> '_' conversion for quoted/hdbcds is missing.
* The above rules might differ for different SQL dialects.
* Exceptions will be listed below.
*
* @param {string} artifactName The fully qualified name of the artifact
* @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
* @param {CSN.Model|string|undefined} csn
* @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
* @param {CSN.Model} csn
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
*/
function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
if(csn && typeof csn === 'object' && csn.definitions)
// eslint-disable-next-line no-unused-vars
function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn, sqlDialect='plain') {
if(csn && typeof csn === 'object' && csn.definitions) {
isValidMappingDialectCombi(sqlDialect, sqlMapping)
if (sqlMapping === 'quoted' || sqlMapping === 'hdbcds') {

@@ -953,20 +958,4 @@ return getResultingName(csn, sqlMapping, artifactName);

}
else {
console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
if (sqlMapping === 'hdbcds') {
if (csn) {
const namespace = String(csn);
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
}
return artifactName;
}
else if (sqlMapping === 'plain') {
return artifactName.replace(/\./g, '_').toUpperCase();
}
else if (sqlMapping === 'quoted') {
return artifactName;
}
else {
throw new Error('Unknown naming mode: ' + sqlMapping);
}
} else {
throw new Error('A valid CSN model is required to correctly calculate the database name of an artifact.');
}

@@ -1054,3 +1043,9 @@ }

function isValidMappingDialectCombi(sqlDialect, sqlMapping) {
if(sqlMapping === 'hdbcds' && sqlDialect !== 'hana')
throw new Error(`sqlMapping "hdbcds" must only be used with sqlDialect "hana" - found: ${sqlDialect}`);
return true;
}
/**

@@ -1064,7 +1059,13 @@ * Return the resulting database element name for 'elemName', depending on the current

*
* The above rules might differ for different SQL dialects.
* Exceptions will be listed below.
*
* @param {string} elemName The name of the element
* @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
* @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
* @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
*/
function getElementDatabaseNameOf(elemName, sqlMapping) {
// eslint-disable-next-line no-unused-vars
function getElementDatabaseNameOf(elemName, sqlMapping, sqlDialect='plain') {
isValidMappingDialectCombi(sqlMapping, sqlDialect)
if (sqlMapping === 'hdbcds') {

@@ -1185,81 +1186,2 @@ return elemName;

/**
* Merge multiple 'options' objects (from right to left, i.e. rightmost wins). Structured option values are
* merged deeply. Structured option value from the right may override corresponding bool options on the left,
* but no other combination of struct/scalar values is allowed. Array options are not merged, i.e. their
* content is treated like scalars.
* Returns a new options object.
*
* @param {...CSN.Options} optionsObjects
* @return {CSN.Options}
*/
function mergeOptions(...optionsObjects) {
let result = {};
for (const options of optionsObjects) {
if (options)
result = mergeTwo(result, options, 'options');
}
// Reverse the array to ensure that the rightmost option has priority
const reversedOptions = [...optionsObjects].reverse(); // de-structure and create a new array, so reverse doesn't impact optionsObject
const msgOptions = reversedOptions.find(opt => opt && Array.isArray(opt.messages));
if (msgOptions) {
result.messages = msgOptions.messages;
}
return result;
// Recursively used for scalars, too
function mergeTwo(left, right, name) {
let intermediateResult;
// Copy left as far as required
if (Array.isArray(left)) {
// Shallow-copy left array
intermediateResult = left.slice();
} else if (isObject(left)) {
// Deep-copy left object (unless empty)
intermediateResult = Object.keys(left).length ? mergeTwo({}, left, name) : {};
} else {
// Just use left scalar
intermediateResult = left;
}
// Check against improper overwriting
if (isObject(left) && !Array.isArray(left) && (Array.isArray(right) || isScalar(right))) {
throw new ModelError(`Cannot overwrite structured option "${name}" with array or scalar value`);
}
if ((isScalar(left) && typeof left !== 'boolean' || Array.isArray(left)) && isObject(right) && !Array.isArray(right)) {
throw new ModelError(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
}
// Copy or overwrite properties from right to left
if (Array.isArray(right)) {
// Shallow-copy right array
intermediateResult = right.slice();
} else if (isObject(right)) {
// Object overwrites undefined, scalars and arrays
if (intermediateResult === undefined || isScalar(intermediateResult) || Array.isArray(intermediateResult)) {
intermediateResult = {};
}
// Deep-copy right object into result
for (let key of Object.keys(right)) {
intermediateResult[key] = mergeTwo(intermediateResult[key], right[key], `${name}.${key}`);
}
} else {
// Right scalar wins (unless undefined)
intermediateResult = (right !== undefined) ? right : intermediateResult;
}
return intermediateResult;
}
// Return true if 'o' is a non-null object or array
function isObject(o) {
return typeof o === 'object' && o !== null
}
// Return true if 'o' is a non-undefined scalar
function isScalar(o) {
return o !== undefined && !isObject(o);
}
}
/**
* If the artifact with the name given is part of a context (or multiple), return the top-most context.

@@ -1315,25 +1237,2 @@ * Else, return the artifact itself. Namespaces are not of concern here.

// Return the name of the parent artifact of the artifact 'name' or
// '' if there is no parent.
function getParentNameOf(name) {
return name.substring(0, name.lastIndexOf('.'));
}
// Return an array of parent names of 'name' (recursing into grand-parents)
// Examples:
// 'foo.bar.wiz' => [ 'foo.bar', 'foo' ]
// 'foo' => []
// 'foo::bar.wiz' => 'foo::bar'
// 'foo::bar' => []
function getParentNamesOf(name) {
let remainder = name.slice(0, -getLastPartOf(name).length);
if (remainder.endsWith('.')) {
let parentName = remainder.slice(0, -1);
return [parentName, ...getParentNamesOf(parentName)];
} else {
return [];
}
}
/**

@@ -1389,11 +1288,13 @@ * Copy all annotations from 'fromNode' to 'toNode'.

/**
* Applies annotations from `csn.extensions` to definitions, i.e. top-level artifacts.
* Does _not_ apply element/param/action/... annotations.
* Applies annotations from `csn.extensions` to definitions and their elements.
*
* `config.filter` can be used to only copy annotations for those definitions,
* for which the filter returns true.
*
* @todo Does _not_ apply param/action/... annotations.
*
* @param {CSN.Model} csn
* @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
*/
function applyDefinitionAnnotationsFromExtensions(csn, config) {
function applyAnnotationsFromExtensions(csn, config) {
if (!csn.extensions)

@@ -1405,6 +1306,27 @@ return;

const name = ext.annotate || ext.extend;
if (name && csn.definitions[name] && filter(name)) {
copyAnnotationsAndDoc(ext, csn.definitions[name], config.overwrite);
const def = csn.definitions[name];
if (name && def && filter(name)) {
copyAnnotationsAndDoc(ext, def, config.overwrite);
applyAnnotationsToElements(ext, def);
}
}
function applyAnnotationsToElements(ext, def) {
// Only the definition is arrayed but the extension is not since
// `items` is not expected in `extensions` by the CSN frontend and not
// generated by the CDL parser for `annotate E:arrayed.elem`.
if (def.items)
def = def.items;
if (!ext.elements || !def.elements)
return;
forEach(ext.elements, (key, sourceElem) => {
const targetElem = def.elements[key];
if (targetElem) {
copyAnnotationsAndDoc(sourceElem, targetElem, config.overwrite);
applyAnnotationsToElements(sourceElem, targetElem);
}
});
}
}

@@ -1416,21 +1338,2 @@

// For each property named 'path' in 'node' (recursively), call callback(path, node)
function forEachPath(node, callback) {
if (node === null || typeof node !== 'object') {
// Primitive node
return;
}
for (let name in node) {
if (!Object.hasOwnProperty.call( node, name ))
continue;
// If path found within a non-dictionary, call callback
if (name === 'path' && Object.getPrototypeOf(node)) {
callback(node.path, node);
}
// Descend recursively
forEachPath(node[name], callback);
}
}
/**

@@ -1512,12 +1415,2 @@ * Return true if the artifact has a valid, truthy persistence.exists/skip annotation

/**
* Check whether the artifact is @cds.persistence.skip
*
* @param {CSN.Artifact} artifact
* @returns {Boolean}
*/
function isSkipped(artifact) {
return hasAnnotationValue(artifact, '@cds.persistence.skip', true)
}
/**
* Walk path in the CSN and return the result.

@@ -1609,4 +1502,3 @@ *

isBuiltinType,
assignAll,
applyDefinitionAnnotationsFromExtensions,
applyAnnotationsFromExtensions,
forEachGeneric,

@@ -1631,7 +1523,4 @@ forEachDefinition,

getNormalizedQuery,
mergeOptions,
getRootArtifactName,
getLastPartOfRef,
getParentNamesOf,
getParentNameOf,
getLastPartOf,

@@ -1641,3 +1530,2 @@ copyAnnotations,

isAspect,
forEachPath,
hasValidSkipOrExists,

@@ -1647,3 +1535,2 @@ getNamespace,

getServiceNames,
isSkipped,
walkCsnPath,

@@ -1650,0 +1537,0 @@ getVariableReplacement,

@@ -17,2 +17,3 @@ // Make internal properties of the XSN / augmented CSN visible

const $inferred = Symbol.for('cds.$inferred');
const $location = Symbol.for('cds.$location');

@@ -100,2 +101,3 @@ class NOT_A_DICTIONARY {} // used for consol.log display

_status: primOrString, // is a string anyway
$annotations: as => as.map( $annotation ),
$messageFunctions: () => '‹some functions›',

@@ -154,3 +156,3 @@ }

const name = reveal( node, parent );
if (name && typeof name === 'object' && name.absolute) {
if (name && typeof name === 'object' && name.absolute && node.kind) {
const text = artifactIdentifier( parent );

@@ -184,7 +186,16 @@ delete name.absolute;

function $annotation( anno ) { // property for cds-lsp
const { name, $flatten } = anno.value || anno;
const value = ($flatten)
? { name: reveal( name ), $flatten: $flatten.map( $annotation ) }
: `@${name?.absolute}`;
return { value, location: locationString( anno.location || anno.name.location ) };
}
function columns( nodes, query ) {
// If we will have specified elements, we need another test to see columns in --parse-cdl
return nodes && nodes.map( c => (c._parent && c._parent.elements)
? artifactIdentifier( c, query )
: reveal( c, nodes ) );
return nodes && array( nodes,
c => (c._parent && c._parent.elements)
? artifactIdentifier( c, query )
: reveal( c, nodes ) );
}

@@ -222,3 +233,3 @@

if (Array.isArray(node)) // with args
return node.map( n => reveal( n, node ) );
return array( node, reveal );
// Make unexpected prototype visible with node-10+:

@@ -233,2 +244,4 @@ const r = Object.create( Object.getPrototypeOf(node)

r['[$inferred]'] = node[$inferred];
if (node[$location] && !node['[$location]'])
r['[$location]'] = locationString( node[$location] );
return r;

@@ -248,3 +261,3 @@ }

if (Array.isArray(node))
return node.map( n => revealNonEnum( n, node ) );
return array( node, revealNonEnum );

@@ -260,3 +273,3 @@ if (Object.getPrototypeOf( node ))

if (Array.isArray(node))
return node.map( n => reveal( n, node, name ) );
return array( node, n => reveal( n, node, name ) );

@@ -291,2 +304,9 @@ const asLinkTest = kindsRepresentedAsLinks[ node.kind ];

function array( node, fn ) {
const r = node.map( n => fn( n, node ) );
if (node[$location])
r.push( { $location: locationString( node[$location] ) } );
return r;
}
function artifactIdentifier( node, parent ) {

@@ -347,3 +367,3 @@ if (Array.isArray(node))

if (Array.isArray(node))
return node.map( primOrString );
return array( node, primOrString );
if (Object.getPrototypeOf( node ))

@@ -350,0 +370,0 @@ return '' + node;

@@ -24,3 +24,3 @@ 'use strict';

const layers = [];
let { zero, nonZero } = calculateDepth(Object.entries(csn.definitions));
let { zero, nonZero } = _calculateDepth(Object.entries(csn.definitions), _dependents, _dependencies);
while (zero.length !== 0){

@@ -38,42 +38,42 @@ const currentLayer = [];

layers.push(currentLayer);
({zero, nonZero} = findWithXPointers(nonZero, 0));
({zero, nonZero} = _findWithXPointers(nonZero, 0, _dependents, _dependencies));
}
return { layers, leftover: nonZero };
}
function calculateDepth(definitionsArray) {
const zero = [];
const nonZero = [];
function _calculateDepth(definitionsArray, _dependents, _dependencies) {
const zero = [];
const nonZero = [];
definitionsArray.forEach(([artifactName, artifact]) => {
if(artifact[_dependencies]) {
artifact.$pointers = artifact[_dependencies].size;
nonZero.push([artifactName, artifact]);
} else {
artifact.$pointers = 0;
zero.push([artifactName, artifact]);
}
});
return {
zero,
nonZero
definitionsArray.forEach(([artifactName, artifact]) => {
if(artifact[_dependencies]) {
artifact.$pointers = artifact[_dependencies].size;
nonZero.push([artifactName, artifact]);
} else {
artifact.$pointers = 0;
zero.push([artifactName, artifact]);
}
});
return {
zero,
nonZero
}
}
function findWithXPointers(definitionsArray, x=0){
const zero = [];
const nonZero = [];
function _findWithXPointers(definitionsArray, x, _dependents, _dependencies){
const zero = [];
const nonZero = [];
definitionsArray.forEach(([artifactName, artifact]) => {
if(artifact.$pointers !== undefined && artifact.$pointers === x) {
zero.push([artifactName, artifact]);
} else {
nonZero.push([artifactName, artifact]);
}
});
definitionsArray.forEach(([artifactName, artifact]) => {
if(artifact.$pointers !== undefined && artifact.$pointers === x) {
zero.push([artifactName, artifact]);
} else {
nonZero.push([artifactName, artifact]);
}
});
return {
zero,
nonZero
}
return {
zero,
nonZero
}

@@ -88,2 +88,3 @@ }

* @param {object} sql Map of <object name>: "CREATE STATEMENT"
* @param {CSN.Model} csn
*

@@ -90,0 +91,0 @@ * @returns {{name: string, sql: string}[]} Sorted array of artifact name / "CREATE STATEMENTS" pairs

@@ -37,3 +37,2 @@ 'use strict';

.option(' --deprecated <list>')
.option(' --hana-flavor')
.option(' --direct-backend')

@@ -111,3 +110,2 @@ .option(' --parse-only')

Valid values are:
addTextsLanguageAssoc
hanaAssocRealCardinality

@@ -118,11 +116,3 @@ mapAssocToJoinCardinality

Valid values are:
noElementsExpansion
v1KeysForTemporal
parensAsStrings
projectionAsQuery
renderVirtualElements
unmanagedUpInComponent
createLocalizedViews
redirectInSubQueries
--hana-flavor Compile with backward compatibility for HANA CDS (incomplete)
eagerPersistenceForGeneratedEntities
--parse-only Stop compilation after parsing and write result to <stdout>

@@ -213,3 +203,3 @@ --fallback-parser <type> If the language cannot be deduced by the file's extensions, use this

.option('-h, --help')
.option('-v, --version <version>', ['v2', 'v4', 'v4x'])
.option('-v, --odata-version <version>', ['v2', 'v4', 'v4x'])
.option('-x, --xml')

@@ -233,6 +223,6 @@ .option('-j, --json')

-h, --help Show this help text
-v, --version <version> ODATA version
v2: ODATA V2
v4: (default) ODATA V4
v4x: { version: 'v4', odataContainment:true, format:'structured' }
-v, --odata-version <version> ODATA version
v2: ODATA V2
v4: (default) ODATA V4
v4x: { version: 'v4', odataContainment:true, format:'structured' }
-x, --xml (default) Generate XML output (separate or combined)

@@ -258,3 +248,3 @@ -j, --json Generate JSON output as "<svc>.json" (not available for v2)

-s, --service-names <list> List of comma-separated service names to be rendered
(default) empty, all services are rendered
(default) empty, all services are rendered
`);

@@ -278,3 +268,3 @@

.option(' --joinfk')
.option('-d, --dialect <dialect>', ['hana', 'sqlite', 'plain'])
.option('-d, --dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'])
.option('-u, --user <user>')

@@ -310,5 +300,6 @@ .option('-l, --locale <locale>')

-d, --dialect <dialect> SQL dialect to be generated:
plain : (default) Common SQL - no assumptions about DB restrictions
hana : SQL with HANA specific language features
sqlite : Common SQL for sqlite
plain : (default) Common SQL - no assumptions about DB restrictions
hana : SQL with HANA specific language features
sqlite : Common SQL for sqlite
postgres : Common SQL for postgres - beta-feature
-u, --user <user> Value for the "$user" variable

@@ -315,0 +306,0 @@ -l, --locale <locale> Value for the "$user.locale" variable in "sqlite"/"plain" dialect

@@ -19,4 +19,4 @@ {

"env": {
"es6": true
"es2020": true
}
}

@@ -5,4 +5,4 @@ /**

* then call addElement to register the elements of the current artifact.
* Finally call the "done" function to check for duplicates.
* In addition the internal structures will be reinitialized to enable reuse of the instance.
* Finally, call the "done" function to check for duplicates.
* In addition, the internal structures will be reinitialized to enable reuse of the instance.
*/

@@ -12,2 +12,4 @@

const { forEach } = require('../utils/objectUtils');
/**

@@ -126,7 +128,2 @@ * database name - uppercase if not quoted

function forEach(obj, callback) {
for (const key in obj)
callback(key, obj[key]);
}
module.exports = DuplicateChecker;

@@ -9,2 +9,5 @@

const { forEach } = require('../utils/objectUtils');
const { makeMessageFunction } = require('../base/messages');
const { optionProcessor } = require('../optionProcessor');
const { transformForHanaWithCsn } = require('../transform/forHanaNew');

@@ -16,2 +19,66 @@ const {

/**
* Used only by `cdsc manageConstraints`.
* Not part of our API, yet.
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
*/
function alterConstraintsWithCsn(csn, options) {
const { error } = makeMessageFunction(csn, options, 'manageConstraints');
const {
drop, alter, names, src, violations,
} = options.manageConstraints || {};
if (drop && alter)
error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
options.sqlDialect = 'hana';
options.sqlMapping = names || 'plain';
// Of course we want the database constraints
options.assertIntegrityType = 'DB';
const transformedOptions = _transformSqlOptions(csn, options);
const forSqlCsn = transformForHanaWithCsn(csn, transformedOptions, 'to.sql');
if (violations && src && src !== 'sql')
error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
let intermediateResult;
if (violations)
intermediateResult = listReferentialIntegrityViolations(forSqlCsn, transformedOptions);
else
intermediateResult = manageConstraints(forSqlCsn, transformedOptions);
return intermediateResult;
}
function _transformSqlOptions(model, options) {
// Merge options with defaults.
options = Object.assign({ sqlMapping: 'plain', sqlDialect: 'plain' }, options);
options.toSql = true;
if (!options.src && !options.csn)
options.src = 'sql';
const { warning, error } = makeMessageFunction(model, options, 'to.sql');
optionProcessor.verifyOptions(options, 'toSql', true)
.forEach(complaint => warning(null, null, `${complaint}`));
if (options.sqlDialect !== 'hana') {
// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be combined with "{ sqlMapping: '${options.sqlMapping}' }"`);
// No non-HANA SQL for HDI
if (options.src === 'hdi')
error(null, null, `Option "{ sqlDialect: '${options.sqlDialect}' }" can't be used for HDI"`);
}
return options;
}
/**
* This render middleware can be used to generate SQL DDL ALTER TABLE <table> ALTER / ADD / DROP CONSTRAINT <constraint> statements for a given CDL model.

@@ -43,3 +110,3 @@ * Moreover, it can be used to generate .hdbconstraint artifacts.

let alterTableStatement = '';
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.toSql.names, constraint.dependentTable))}`;
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
if (renderAlterConstraintStatement)

@@ -178,3 +245,3 @@ alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;

function quoteAndGetResultingName(id) {
return quoteSqlId(getResultingName(csn, options.toSql.names, id));
return quoteSqlId(getResultingName(csn, options.sqlMapping, id));
}

@@ -199,4 +266,5 @@

module.exports = {
alterConstraintsWithCsn,
manageConstraints,
listReferentialIntegrityViolations,
};
'use strict';
const { CompilationError, hasErrors } = require('../base/messages');
const { makeMessageFunction } = require('../base/messages');
const { checkCSNVersion } = require('../json/csnVersion');
const { getUtils } = require('../model/csnUtils');
const { getUtils, forEachDefinition } = require('../model/csnUtils');
const { optionProcessor } = require('../optionProcessor');
const { isBetaEnabled } = require('../base/model');
const { transformForHanaWithCsn } = require('../transform/forHanaNew');
// FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
/**
* FIXME: Not yet supported, only in beta mode
* FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
*
* Render the augmented CSN 'model' to SQL DDL statements renaming existing tables and their
* Generate SQL DDL rename statements for a migration, renaming existing tables and their
* columns so that they match the result of "toHana" or "toSql" with the 'plain' option for names.
* Expects the naming convention of the existing tables to be either 'quoted' or 'hdbcds' (default).
* The following options control what is actually generated:
* The following options control what is actually generated (see help above):
* options : {
* toRename.names : existing names, either 'quoted' or 'hdbcds' (default)
* sqlMapping : existing names, either 'quoted' or 'hdbcds' (default)
* }

@@ -25,30 +29,48 @@ * Return a dictionary of top-level artifacts by their names, like this:

* @todo clarify input parameters
* @param {CSN.Model} csn Augmented csn?
* @param {CSN.Model} inputCsn CSN?
* @param {CSN.Options} options Transformation options
* @returns {object} A dictionary of name: rename statement
*/
function toRenameDdl(csn, options) {
// Merge options (arguments first, then model options and default)
function toRename(inputCsn, options) {
const { error, warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
// Merge options with defaults.
options = Object.assign({ sqlMapping: 'hdbcds' }, options);
// Verify options
optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));
checkCSNVersion(inputCsn, options);
// Requires beta mode
if (!isBetaEnabled(options, 'toRename'))
error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forHana)
const csn = transformForHanaWithCsn(inputCsn, options, 'to.rename');
// forHanaCsn looses empty contexts and services, add them again so that toRename can calculate the namespaces
forEachDefinition(csn, (artifact, artifactName) => {
if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
csn.definitions[artifactName] = artifact;
});
const result = Object.create(null);
const { getNamespaceOfArtifact } = getUtils(csn, false);
checkCSNVersion(csn, options);
const { getNamespaceOfArtifact } = getUtils(csn);
// Render each artifact on its own
for (const artifactName in csn.definitions) {
const sourceStr = renameTableAndColumns(artifactName, csn.definitions[artifactName]);
if (sourceStr !== '')
result[artifactName] = sourceStr;
}
// Throw exception in case of errors
if (hasErrors(options.messages))
throw new CompilationError(options.messages);
return result;
throwWithError();
return {
rename: result,
options,
};
/**
* If 'art' is a non-view entity, generate SQL statements to rename the corresponding
* table and its columns from the naming conventions given in 'options.toRename.name'
* table and its columns from the naming conventions given in 'options.sqlMapping'
* (either 'quoted' or 'hdbcds') to 'plain'. In addition, drop any existing associations

@@ -101,3 +123,3 @@ * from the columns (they would likely become invalid anyway).

function absoluteCdsName(name) {
if (options.toRename.names !== 'hdbcds')
if (options.sqlMapping !== 'hdbcds')
return name;

@@ -113,3 +135,3 @@

/**
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.toRename.names'
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
* is 'quoted'

@@ -121,3 +143,3 @@ *

function quoteSqlId(name) {
if (options.toRename.names === 'quoted')
if (options.sqlMapping === 'quoted')
name = name.replace(/::/g, '.');

@@ -141,3 +163,3 @@

module.exports = {
toRenameDdl,
toRename,
};

@@ -282,2 +282,14 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

},
postgres: {
// TODO: Type mapping for binary types is not correct, yet.
// We can't use text types for binary on PostgreSQL due to NUL!
'cds.String': 'VARCHAR',
'cds.LargeString': 'text',
'cds.hana.CLOB': 'text',
'cds.LargeBinary': 'bytea',
'cds.Binary': 'VARCHAR',
'cds.hana.BINARY': 'VARCHAR',
'cds.Double': 'double precision',
'cds.hana.TINYINT': 'INTEGER',
},
};

@@ -379,3 +391,3 @@

* @callback renderPart
* @param {object||array} expression
* @param {object|array} expression
* @param {CdlRenderEnvironment} env

@@ -402,2 +414,4 @@ * @this {{inline: Boolean, nestedExpr: Boolean}}

* @property {renderPart} SET
* @property {boolean} [inline]
* @property {boolean} [nestedExpr]
*/

@@ -404,0 +418,0 @@

@@ -43,14 +43,14 @@ // Render functions for toSql.js

const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
(options.toSql && options.toSql.src === 'hdi') ||
options.src === 'hdi' ||
(options.manageConstraints && options.manageConstraints.src === 'hdi');
const { names } = options.forHana;
const forSqlite = options.toSql && options.toSql.dialect === 'sqlite';
const { sqlMapping } = options;
const forSqlite = options.sqlDialect === 'sqlite';
let result = '';
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
if (renderAsHdbconstraint)
result += `${indent}ON ${quoteId(getResultingName(csn, names, constraint.dependentTable))}\n`;
result += `${indent}ON ${quoteId(getResultingName(csn, sqlMapping, constraint.dependentTable))}\n`;
if (!alterConstraint) {
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, sqlMapping, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';

@@ -69,3 +69,4 @@

// constraint enforcement / validation must be switched off using sqlite pragma statement
if (options.toSql && options.toSql.dialect !== 'sqlite') {
// Does not include HDBCDS.
if (options.toSql && !forSqlite) {
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;

@@ -75,3 +76,3 @@ result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;

// for sqlite, the DEFERRABLE keyword is required
result += `${indent}${options.toSql && options.toSql.dialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
return result;

@@ -91,5 +92,5 @@ }

* Additionally perform the following conversions on 'name'
* If 'options.toSql.names' is 'plain'
* If 'options.sqlMapping' is 'plain'
* - replace '.' or '::' by '_'
* else if 'options.toSql.names' is 'quoted'
* else if 'options.sqlMapping' is 'quoted'
* - replace '::' by '.'

@@ -104,9 +105,9 @@ * Complain about names that collide with known SQL keywords or functions

switch (options.toSql.names) {
switch (options.sqlMapping) {
case 'plain':
return smartId(name, options.toSql.dialect);
return smartId(name, options.sqlDialect);
case 'quoted':
return delimitedId(name, options.toSql.dialect);
return delimitedId(name, options.sqlDialect);
case 'hdbcds':
return delimitedId(name, options.toSql.dialect);
return delimitedId(name, options.sqlDialect);
default:

@@ -119,5 +120,5 @@ return undefined;

* Prepare an identifier:
* If 'options.toSql.names' is 'plain'
* If 'options.sqlMapping' is 'plain'
* - replace '.' or '::' by '_'
* else if 'options.toSql.names' is 'quoted'
* else if 'options.sqlMapping' is 'quoted'
* - replace '::' by '.'

@@ -130,7 +131,7 @@ *

// Sanity check
if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
switch (options.toSql.names) {
switch (options.sqlMapping) {
case 'plain':

@@ -143,3 +144,3 @@ return name.replace(/(\.|::)/g, '_');

default:
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
}

@@ -146,0 +147,0 @@ }

@@ -48,2 +48,8 @@ // API functions returning the SQL identifier token text for a name

},
postgres: {
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
reservedWords: keywords.postgres,
effectiveName: name => name.toLowerCase(),
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
},
hana: {

@@ -50,0 +56,0 @@ regularRegex: /^[A-Za-z_][A-Za-z_$#0-9]*$/,

@@ -20,2 +20,3 @@ {

"sonarjs/cognitive-complexity": "off",
"sonarjs/no-duplicate-string": "off",
// Does not recognize TS types

@@ -25,7 +26,7 @@ "jsdoc/no-undefined-types": "off"

"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2020,
"sourceType": "script"
},
"env": {
"es6": true,
"es2020": true,
"node": true

@@ -32,0 +33,0 @@ },

@@ -84,6 +84,8 @@ 'use strict';

function ignore(member, memberName, prop, path) {
if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
if (options.sqlDialect === 'hana' &&
!member._ignore && member.target &&
isAssocOrComposition(member.type) &&
!isPersistedOnDatabase(csn.definitions[member.target])) {
info(null, path,
{ target: member.target, anno: targetAnnotation },
{ target: member.target, anno: '@cds.persistence.skip' },
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');

@@ -93,14 +95,2 @@ member._ignore = true;

}
/**
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
* - @cds.persistence.skip/exists
* - abstract
*
* @param {CSN.Artifact} art
* @returns {boolean}
*/
function isUnreachableAssociationTarget(art) {
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
}
}

@@ -107,0 +97,0 @@

@@ -520,3 +520,3 @@ 'use strict';

// constraint identifier start with `c__` to avoid name clashes
identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
identifier: `c__${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
foreignKey: dependentKey,

@@ -523,0 +523,0 @@ parentKey,

@@ -27,6 +27,7 @@ 'use strict';

function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
const csnUtils = getUtils(csn);
const {
isStructured, get$combined, getFinalBaseType, getServiceName,
} = getUtils(csn);
let { effectiveType, inspectRef } = csnRefs(csn);
isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
} = csnUtils;
let { effectiveType, inspectRef } = csnUtils;

@@ -243,9 +244,9 @@ if (isBetaEnabled(options, 'nestedProjections'))

if (parent.ref) {
const finalBaseType = getFinalBaseType(parent._art.type);
const finalBaseType = getFinalBaseTypeWithProps(parent._art.type);
const art = parent._art;
if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
return csn.definitions[art.target].elements;
return art.elements || finalBaseType.elements;
return art.elements || finalBaseType?.elements;
}

@@ -252,0 +253,0 @@

@@ -50,2 +50,3 @@ 'use strict';

function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOptions = {}) {
const typeCache = Object.create(null); // TODO: Argument as well?
/**

@@ -71,4 +72,4 @@ * Remove .localized from the element and any sub-elements

}
const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { getServiceName, getFinalBaseType } = getUtils(csn);
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { getServiceName, getFinalBaseTypeWithProps } = csnUtils;

@@ -87,3 +88,3 @@ // We don't want to iterate over actions

if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
toFinalBaseType(parent.cast, resolved, true);
toFinalBaseType(parent.cast, resolved, true, typeCache);
},

@@ -95,3 +96,3 @@ // @ts-ignore

if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
toFinalBaseType(parent, resolved);
toFinalBaseType(parent, resolved, true, typeCache);
// structured types might not have the child-types replaced.

@@ -105,3 +106,3 @@ // Drill down to ensure this.

if (e.type && !isBuiltinType(e.type))
toFinalBaseType(e, resolved);
toFinalBaseType(e, resolved, true, typeCache);

@@ -160,7 +161,7 @@ if (e.elements)

function isODataV4BuiltinFromService(typeName, path) {
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2') || typeof typeName !== 'string')
if (!options.toOdata || (options.odataVersion === 'v2') || typeof typeName !== 'string')
return false;
const typeServiceName = getServiceName(typeName);
const finalBaseType = getFinalBaseType(typeName);
const finalBaseType = getFinalBaseTypeWithProps(typeName)?.type;
// we need the service of the current definition

@@ -275,5 +276,4 @@ const currDefServiceName = getServiceName(path[1]);

function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {
const { isAssocOrComposition } = getUtils(csn);
const { flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { effectiveType } = csnRefs(csn);
const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { isAssocOrComposition, effectiveType } = csnUtils;
const transformers = {

@@ -309,7 +309,9 @@ elements: flatten,

for (const flatElemName in flatElems) {
if (parent[prop][flatElemName])
if (parent[prop][flatElemName]) {
// TODO: combine message ID with generated FK duplicate
// do the duplicate check in the consruct callback, requires to mark generated flat elements,
// do the duplicate check in the construct callback, requires to mark generated flat elements,
// check: Error location should be the existing element like @odata.foreignKey4
error(null, path.concat([ 'elements', elementName ]), `"${path[1]}.${elementName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
error('name-duplicate-element', path.concat([ 'elements', elementName ]),
{ '#': 'flatten-element-exist', name: flatElemName });
}

@@ -620,4 +622,3 @@ const flatElement = flatElems[flatElemName];

// error location is the colliding element
error(null, eltPath, { name: fk[0], art: elementName },
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
}

@@ -631,6 +632,4 @@ // attach a proper $path

Object.entries(refCount).forEach(([ name, occ ]) => {
if (occ > 1) {
error(null, eltPath, { name },
'Duplicate definition of foreign key element $(NAME)');
}
if (occ > 1)
error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-gen', name, art: elementName });
});

@@ -637,0 +636,0 @@ if (element.keys) {

@@ -6,3 +6,3 @@ 'use strict';

} = require('../../model/csnUtils');
const { implicitAs, csnRefs } = require('../../model/csnRefs');
const { implicitAs } = require('../../model/csnRefs');
const { isBetaEnabled } = require('../../base/model');

@@ -73,5 +73,5 @@ const { ModelError } = require('../../base/error');

get$combined, isAssocOrComposition,
inspectRef, queryOrMain, // csnRefs
} = getUtils(csn);
const { inspectRef, queryOrMain } = csnRefs(csn);
const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
const pathDelimiter = options.forHana && (options.sqlMapping === 'hdbcds') ? '.' : '_';
const { error, info } = messageFunctions;

@@ -78,0 +78,0 @@ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');

@@ -26,7 +26,7 @@ {

"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2020,
"sourceType": "script"
},
"env": {
"es6": true,
"es2020": true,
"node": true

@@ -33,0 +33,0 @@ },

'use strict';
const {
hasAnnotationValue, getUtils, getServiceNames, forEachDefinition,
hasAnnotationValue, getServiceNames, forEachDefinition,
getResultingName, forEachMemberRecursively,

@@ -22,3 +22,3 @@ } = require('../../model/csnUtils');

function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
const draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
const draftSuffix = isDeprecatedEnabled(options, '_generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
// All services of the model - needed for drafts

@@ -29,5 +29,5 @@ const allServices = getServiceNames(csn);

createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
addElement, copyAndAddElement, createAssociationPathComparison,
addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
} = getTransformers(csn, options, pathDelimiter);
const { getCsnDef, isComposition } = getUtils(csn);
const { getCsnDef, isComposition } = csnUtils;
const { error, warning } = messageFunctions;

@@ -160,3 +160,3 @@

const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
const persistenceName = getResultingName(csn, options.sqlMapping, draftsArtifactName);
// Duplicate the artifact as a draft shadow entity

@@ -193,3 +193,3 @@ if (csn.definitions[persistenceName]) {

let elem;
if ((isDeprecatedEnabled(options, 'renderVirtualElements') && origElem.virtual) || !origElem.virtual)
if ((isDeprecatedEnabled(options, '_renderVirtualElements') && origElem.virtual) || !origElem.virtual)
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];

@@ -196,0 +196,0 @@ if (elem) {

'use strict';
const { forEachDefinition, getUtils, getServiceNames } = require('../../model/csnUtils');
const { forEachDefinition, getServiceNames } = require('../../model/csnUtils');
const { forEach } = require('../../utils/objectUtils');

@@ -33,4 +33,4 @@ const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');

resetAnnotation,
csnUtils,
} = getTransformers(csn, options);
const {

@@ -40,5 +40,4 @@ getFinalType,

hasAnnotationValue,
getFinalBaseType,
getFinalTypeDef,
} = getUtils(csn);
getFinalBaseTypeWithProps,
} = csnUtils;

@@ -202,4 +201,4 @@ const { error, info } = makeMessageFunction(csn, options, 'for.odata');

else if (elem.type) { // types - possibly structured
const typeDef = elem.type.ref ? getFinalBaseType(elem.type) : getFinalTypeDef(elem.type);
if (typeDef.elements)
const typeDef = getFinalBaseTypeWithProps(elem.type);
if (typeDef?.elements)
stack.push(typeDef);

@@ -206,0 +205,0 @@ }

@@ -44,5 +44,3 @@ 'use strict';

* options = {
* forHana.names // See the behavior of 'names' in toHana, toSql and toRename
* forHana.alwaysResolveDerivedTypes // Always resolve derived type chains (by default, this is only
* // done for 'quoted' names). FIXME: Should always be done in general.
* sqlMapping // See the behavior of 'sqlMapping' in toHana, toSql and toRename
* }

@@ -73,5 +71,5 @@ * The result model will always have 'options.forHana' set, to indicate that these transformations have happened.

* - (130) (not for to.hdbcds with hdbcds names): Elements having structured types are flattened into
* multiple elements (using '_' or '.' as name separator, depending on 'forHana.names').
* multiple elements (using '_' or '.' as name separator, depending on 'sqlMapping').
* - (140) (not for to.hdbcds with hdbcds names): Managed associations get explicit ON-conditions, with
* generated foreign key elements (also using '_' or '.' as name separator, depending on 'forHana.names').
* generated foreign key elements (also using '_' or '.' as name separator, depending on 'sqlMapping').
* - (150) (a) Elements from inherited (included) entities are copied into the receiving entity

@@ -99,3 +97,3 @@ * (b) The 'include' property is removed from entities.

* with their database name (as '@cds.persistence.name') according to the naming convention chosen
* in 'options.forHana.names'.
* in 'options.sqlMapping'.
* - (250) Remove name space definitions again (only in forHanaNew). Maybe we can omit inserting namespace definitions

@@ -114,7 +112,5 @@ * completely (TODO)

checkCSNVersion(csn, options);
const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';

@@ -125,3 +121,2 @@ let message, error, warning, info; // message functions

let artifactRef, inspectRef, effectiveType, get$combined,
getFinalBaseType, // csnUtils (csnRefs)
addDefaultTypeFacets, expandStructsInExpression; // transformUtils

@@ -138,3 +133,3 @@

const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
const dialect = options.sqlDialect;
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');

@@ -146,3 +141,3 @@ if (!doA2J)

const cleanup = validate.forHana(csn, {
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, isAspect
});

@@ -232,2 +227,3 @@

isAssociationOperand, isDollarSelfOrProjectionOperand,
csnUtils,
} = transformUtils.getTransformers(csn, options, pathDelimiter);

@@ -240,3 +236,3 @@

cloneWithTransformations,
} = getUtils(csn);
} = csnUtils;

@@ -421,4 +417,5 @@ // (000) Rename primitive types, make UUID a String

({ error, warning, info, message, throwWithAnyError } = makeMessageFunction(csn, options, moduleName));
({ artifactRef, inspectRef, effectiveType, getFinalBaseType, get$combined } = getUtils(csn));
({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));
// TODO: Can we use csnUtils of the call above (transformUtils.getTransformers)?
({ artifactRef, inspectRef, effectiveType, get$combined } = getUtils(csn));
}

@@ -522,3 +519,3 @@

if (artifact.kind !== 'service' && artifact.kind !== 'context')
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.forHana.names, csn), artifact);
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);

@@ -531,3 +528,3 @@ forEachMemberRecursively(artifact, (member, memberName, property, path) => {

if ((!member.virtual || artifact.query))
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.forHana.names), member);
addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member);
}, [ 'definitions', artifactName ]);

@@ -558,3 +555,3 @@ }

// mixin elements must be transformed, why can't toSql also use mixins?
if(artifact.kind === 'entity' || artifact.query || (options.toHana && options.toHana.names === 'hdbcds' && artifact.kind == 'type'))
if(artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
doit(artifact.elements, path.concat([ 'elements' ]));

@@ -590,3 +587,3 @@ if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)

// - association that points to entity with parameters
if (options.forHana.dialect === 'hana' && member.target && isAssocOrComposition(member.type) && !isBetaEnabled(options, 'assocsWithParams')) {
if (options.sqlDialect === 'hana' && member.target && isAssocOrComposition(member.type) && !isBetaEnabled(options, 'assocsWithParams')) {
if (artifact.params) {

@@ -634,3 +631,3 @@ // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:

}
else if (options.forHana.dialect === 'sqlite') { // view with params
else if (options.sqlDialect === 'sqlite') { // view with params
// Allow with plain

@@ -644,4 +641,4 @@ error(null, [ 'definitions', artifactName ], `SQLite does not support entities with parameters`);

}
else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
else if (options.sqlMapping !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.sqlMapping },
'Expecting parameter to be uppercase in naming mode $(NAME)');

@@ -1067,3 +1064,3 @@ }

function isMaxParameterLengthRestricted(type) {
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
return !(options.toSql && type === 'cds.String' && (options.sqlDialect === 'sqlite' || options.sqlDialect === 'plain'));
}

@@ -1070,0 +1067,0 @@

@@ -6,4 +6,3 @@ 'use strict';

const transformUtils = require('./transformUtilsNew');
const { getUtils,
cloneCsnNonDict,
const { cloneCsnNonDict,
forEachDefinition,

@@ -90,6 +89,6 @@ forEachMemberRecursively,

recurseElements, setAnnotation, renameAnnotation,
expandStructsInExpression
expandStructsInExpression,
csnUtils,
} = transformers;
const csnUtils = getUtils(csn);
const {

@@ -103,7 +102,6 @@ getCsnDef,

effectiveType,
getFinalBaseType,
} = csnUtils;
// are we working with structured OData or not
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';
const structuredOData = options.odataFormat === 'structured' && options.odataVersion === 'v4';

@@ -121,3 +119,3 @@ // collect all declared non-abstract services from the model

const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');

@@ -132,3 +130,3 @@ function acceptLocalizedView(_name, parent) {

const cleanup = validate.forOdata(csn, {
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
});

@@ -222,4 +220,4 @@

// Skip artifacts that have no DB equivalent anyway
if (options.toOdata.names && !(def.kind in skipPersNameKinds))
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
if (options.sqlMapping && !(def.kind in skipPersNameKinds))
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"

@@ -230,5 +228,5 @@ forEachMemberRecursively(def, (member, memberName, propertyName) => {

// as they have no DB representation (although in views)
if (options.toOdata.names && typeof member === 'object' && !(member.kind === 'action' || member.kind === 'function') && propertyName !== 'enum' && (!member.virtual || def.query)) {
if (options.sqlMapping && typeof member === 'object' && !(member.kind === 'action' || member.kind === 'function') && propertyName !== 'enum' && (!member.virtual || def.query)) {
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.toOdata.names);
member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
}

@@ -235,0 +233,0 @@

'use strict';
const { makeMessageFunction } = require('../base/messages');
const { setProp } = require('../base/model');
const { setProp, isDeprecatedEnabled} = require('../base/model');
const { hasErrors } = require('../base/messages');
const { cloneCsnDictionary, applyDefinitionAnnotationsFromExtensions} = require('../model/csnUtils');
const { forEachKey } = require('../utils/objectUtils');
const { cleanSymbols } = require('../base/cleanSymbols.js');
const {
cloneCsnDictionary,
cloneCsnNonDict,
applyAnnotationsFromExtensions,
forEachDefinition,

@@ -70,3 +72,4 @@ forEachGeneric,

* join in direct convenience views.
* @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
* @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view
* name and its parent name provided as parameter should be created
*/

@@ -91,3 +94,3 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = null) {

// In case that the user tried to annotate `localized.*` artifacts, apply them.
applyDefinitionAnnotationsFromExtensions(csn, {
applyAnnotationsFromExtensions(csn, {
overwrite: true,

@@ -152,3 +155,3 @@ filter: (name) => name.startsWith('localized.')

copyPersistenceAnnotations(view, art);
copyPersistenceAnnotations(view, art, options);
csn.definitions[viewName] = view;

@@ -673,17 +676,20 @@ }

/**
* Copy some @cds.persistence.* annotations from the source to
* the target. Ignores existing annotations on the target.
* Copy @cds.persistence.exists/skip annotations from the source to
* the target. Ignores existing annotations on the _target_.
*
* @param {CSN.Artifact} target
* @param {CSN.Artifact} source
* @param {CSN.Options} options
*/
function copyPersistenceAnnotations(target, source) {
Object.keys(source)
.forEach(anno => {
// Do NOT copy ".exists" at the moment. ".exists" is not propagated
// and this would lead to some localization views referencing not-existing
// "localized.XYZ" views.
if (anno === '@cds.persistence.skip')
target[anno] = source[anno];
});
function copyPersistenceAnnotations(target, source, options) {
const doNotCopyExists = isDeprecatedEnabled( options, 'eagerPersistenceForGeneratedEntities' );
forEachKey(source, anno => {
// Note:
// Because `.exists` is copied to the convenience view, it could
// lead to some localization views referencing non-existing ones.
// But that is the contract: User says that it already exists!
// In v2, `.exists` was never copied.
if (anno === '@cds.persistence.skip' || (!doNotCopyExists && anno === '@cds.persistence.exists'))
target[anno] = source[anno];
});
}

@@ -690,0 +696,0 @@

@@ -10,3 +10,3 @@ 'use strict';

function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
const isV4 = options.toOdata.version === 'v4';
const isV4 = options.odataVersion === 'v4';
forEachDefinition(csn, (def, defName) => {

@@ -91,5 +91,5 @@ // Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)

// elements have precedence over type
if (node.type && (!isBuiltinType(node.type) &&isExpandable(node, defName) || node.kind === 'type')) {
if (node.type && (!isBuiltinType(node.type) && isExpandable(node, defName) || node.kind === 'type')) {
// 1. Get the final type of the node (resolve derived type chain)
const finalType = csnUtils.getFinalBaseType(node.type);
const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
if (finalType) {

@@ -106,3 +106,3 @@ // The type replacement depends on whether 'node' is a definition or a member[element].

// type C { .... };
if (isBuiltinType(finalType)) {
if (isBuiltinType(finalType.type)) {
// use transformUrilsNew::toFinalBaseType for the moment,

@@ -135,4 +135,4 @@ // as it is collects along the chain of types

// type C { .... };
if (isBuiltinType(finalType)) {
// use transformUrilsNew::toFinalBaseType for the moment,
if (isBuiltinType(finalType.type)) {
// use transformUtilsNew::toFinalBaseType for the moment,
// as it is collects along the chain of types

@@ -155,4 +155,4 @@ // attributes that need to be propagated

const finalType = csnUtils.getFinalTypeDef(node.type);
if (finalType.items &&
(isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseType(finalType.items.type))))
if (finalType.items &&
(isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)))
{

@@ -194,4 +194,4 @@ if (!isArtifactInService(node.type, currService) || !isV4) {

// a builtin from the service - do not expand to the final base type
let finalBaseType = csnUtils.getFinalBaseType(node.type);
// if (finalBaseType && finalBaseType.items) finalBaseType = csnUtils.getFinalBaseType(finalBaseType.items);
let finalBaseType = csnUtils.getFinalBaseTypeWithProps(node.type).type;
// if (finalBaseType && finalBaseType.items) finalBaseType = csnUtils.getFinalBaseTypeWithProps(finalBaseType.items);
const currService = csnUtils.getServiceName(defName);

@@ -198,0 +198,0 @@ return node.type && !node.type.ref

@@ -17,3 +17,3 @@ 'use strict';

// are we working with OData proxies or cross-service refs
const isMultiSchema = options.toOdata.version === 'v4' && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs);
const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
// collect in this variable all the newly exposed types

@@ -126,3 +126,3 @@ const schemas = Object.create(null);

defName = typeDef.kind === 'type' ? typeName : defName;
exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName);

@@ -156,3 +156,3 @@ });

* check if this is a structured type
* Returns an object that indicates
* Returns an object that indicates
* - wether or not the type needs exposure

@@ -159,0 +159,0 @@ * - the elements dictionary that needs to be cloned

@@ -1,37 +0,3 @@

const {
forEachDefinition,
forEachMemberRecursively,
} = require('../../model/csnUtils');
'use strict';
// Return true if 'artifact' has an association type
function isAssociation(artifact) {
return (artifact.type === 'cds.Association' || artifact.type === 'Association');
}
// Return true if 'artifact' has a composition type
function isComposition(artifact) {
return (artifact.type === 'cds.Composition' || artifact.type === 'Composition')
}
function isAssociationOrComposition(artifact) {
return isAssociation(artifact) || isComposition(artifact);
}
function isManagedAssociation(artifact) {
return artifact.target !== undefined && artifact.on === undefined;
}
function forEachManagedAssociation(csn, callback, isExternalServiceMember) {
forEachDefinition(csn, (def) => {
forEachMemberRecursively(def, (element) => {
if (isAssociationOrComposition(element) && !element.on) {
callback(element)
}
})
}, { skipArtifact: isExternalServiceMember });
}
/**

@@ -90,3 +56,2 @@ * Return the definition name, without the prefixed service name

module.exports = {
forEachManagedAssociation,
defNameWithoutServiceOrContextName,

@@ -96,5 +61,3 @@ getServiceOfArtifact,

isArtifactInSomeService,
isAssociationOrComposition,
isLocalizedArtifactInService,
isManagedAssociation,
}

@@ -9,6 +9,6 @@ 'use strict';

const { setProp } = require('../base/model');
const { csnRefs } = require('../model/csnRefs');
const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
const { cloneCsnNonDict, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnUtils');
const { typeParameters, isBuiltinType } = require('../compiler/builtins');
const { ModelError } = require("../base/error");

@@ -24,16 +24,14 @@ const { forEach } = require('../utils/objectUtils');

const { error, warning, info } = makeMessageFunction(model, options);
const csnUtils = getUtils(model);
const {
getCsnDef,
getFinalBaseType,
getFinalBaseTypeWithProps,
hasAnnotationValue,
inspectRef,
isStructured,
} = getUtils(model);
const {
effectiveType,
} = csnRefs(model);
} = csnUtils;
return {
csnUtils,
resolvePath,

@@ -50,3 +48,2 @@ flattenPath,

isDollarSelfOrProjectionOperand,
getFinalBaseType,
createExposingProjection,

@@ -249,3 +246,3 @@ createAndAddDraftAdminDataProjection,

if (!elem.elements) // structures do not have final base type
elemType = getFinalBaseType(elem.type);
elemType = getFinalBaseTypeWithProps(elem.type);

@@ -256,11 +253,9 @@ const struct = elemType ? elemType.elements : elem.elements;

// TODO: Do not report collisions in the generated elements here, but instead
// leave that work to the receiver of this result
// leave that work to the receiver of this result
let result = Object.create(null);
const addGeneratedFlattenedElement = (e, eName) => {
if(result[eName]){
error(null, pathInCsn, { name: eName },
'Generated element $(NAME) conflicts with other generated element')
} else {
if (result[eName])
error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName })
else
result[eName] = e;
}
}

@@ -295,3 +290,7 @@ forEach(struct, (childName, childElem) => {

forEach(result, (name, flatElem) => {
// Copy annotations from struct (not overwriting, because deep annotations should have precedence)
// Copy annotations from struct (not overwriting, because deep annotations should have precedence).
// Attention:
// This has historic reasons. We don't copy doc-comments because copying annotations
// is questionable to begin with. Only selected annotations should have been copied,
// if at all.
copyAnnotations(elem, flatElem, false);

@@ -404,74 +403,61 @@ // Copy selected type properties

/**
* Replace the type of 'node' with its final base type (in contrast to the compiler,
* also unravel derived enum types, i.e. take the final base type of the enum's base type.
* Similar with associations and compositions (we probably need a _baseType link)
* Replace the type of 'nodeWithType' with its final base type, i.e. copy relevant type properties and
* set the `type` property to the builtin if scalar or delete it if structured/arrayed.
*
* @param {CSN.Artifact} node
* @param {object} nodeWithType
* @param {WeakMap} [resolved] WeakMap containing already resolved refs
* @param {boolean} [keepLocalized=false] Whether to clone .localized from a type def
* @returns {void}
*/
function toFinalBaseType(node, resolved, keepLocalized=false) {
// Nothing to do if no type (or if array/struct type)
if (!node || !node.type) return;
// In case of a ref -> Follow the ref
if (node.type && node.type.ref) {
const finalBaseType = getFinalBaseType(node.type, undefined, resolved);
if(finalBaseType === null)
throw Error('Failed to obtain final base type for reference : ' + node.type.ref.join('/'));
if(finalBaseType.elements) {
// This changes the order - to be discussed!
node.elements = cloneCsnNonDict(finalBaseType, options).elements; // copy elements
delete node.type; // delete the type reference as edm processing does not expect it
} else if(finalBaseType.items) {
// This changes the order - to be discussed!
node.items = cloneCsnNonDict(finalBaseType.items, options); // copy items
delete node.type;
} else {
node.type=finalBaseType;
}
function toFinalBaseType(nodeWithType, resolved = new WeakMap(), keepLocalized = false) {
const type = nodeWithType?.type;
if (!type || nodeWithType.elements || nodeWithType.items || resolved.has(nodeWithType)) {
return;
}
// .. or builtin already
if (node.type && isBuiltinType(node.type)) return;
// The caller may use `{ art }` syntax for `{ ref }` objects, but we only use
// it to indicate that an artifact has been processed.
resolved.set(nodeWithType, nodeWithType);
// The type might already be a full fledged type def (array of)
let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
// Nothing to do if type is an array or a struct type
if (typeDef.items || typeDef.elements) {
// cloneCsn only works correctly if we start "from the top"
const cloneTypeDef = cloneCsnNonDict(typeDef, options);
// With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.
if(typeDef.items) {
delete node.type;
if(!node.items)
Object.assign(node, {items: cloneTypeDef.items});
// Nothing to copy from builtin.
if (typeof type === 'string' && isBuiltinType(type))
return;
let typeRef = null;
if (resolved.has(type)) {
typeRef = resolved.get(type)?.art
// The cached entry may not be resolved, yet.
if (typeRef.type && !isBuiltinType(typeRef.type))
typeRef = getFinalBaseTypeWithProps(typeRef.type);
} else {
typeRef = getFinalBaseTypeWithProps(type);
}
if (typeRef.elements || typeRef.items) {
// Copy elements/items and we're finished. No need to look up actual base type,
// since it must also be structured and must contain at least as many elements,
// if not more (in client style CSN).
if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
nodeWithType.elements = cloneCsnDictionary(typeRef.elements, options);
delete nodeWithType.type;
}
if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
if(!typeDef.items)
delete node.type;
if(!node.elements)
Object.assign(node, {elements: cloneTypeDef.elements});
if (typeRef.items) {
nodeWithType.items = cloneCsnNonDict(typeRef.items, options);
delete nodeWithType.type;
}
return;
}
if (typeRef.enum && nodeWithType.enum === undefined)
nodeWithType.enum = cloneCsnDictionary(typeRef.enum, options);
return;
// Copy type and type arguments (+ localized)
for (const param of typeParameters.list) {
if (nodeWithType[param] === undefined && typeRef[param] !== undefined &&!typeRef.$default) {
nodeWithType[param] = typeRef[param];
}
}
// if the declared element is an enum, these values are with priority
if (!node.enum && typeDef.enum) {
const clone = cloneCsnDictionary(typeDef.enum, options);
Object.assign(node, { enum: clone });
}
if (node.length === undefined && typeDef.length !== undefined)
Object.assign(node, { length: typeDef.length });
if (node.precision === undefined && typeDef.precision !== undefined)
Object.assign(node, { precision: typeDef.precision });
if (node.scale === undefined && typeDef.scale !== undefined)
Object.assign(node, { scale: typeDef.scale });
if (node.srid === undefined && typeDef.srid !== undefined)
Object.assign(node, { srid: typeDef.srid });
if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
Object.assign(node, { localized: typeDef.localized });
node.type = typeDef.type;
toFinalBaseType(node);
if (keepLocalized && nodeWithType.localized === undefined && typeRef.localized !== undefined)
nodeWithType.localized = typeRef.localized;
if (typeRef.type)
nodeWithType.type = typeRef.type;
}

@@ -478,0 +464,0 @@

@@ -24,7 +24,7 @@ {

"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2020,
"sourceType": "script"
},
"env": {
"es6": true,
"es2020": true,
"node": true

@@ -31,0 +31,0 @@ },

@@ -15,3 +15,3 @@ 'use strict';

const {
artifactRef, getColumn, getElement,
artifactRef, getColumn, getElement, getOrigin,
} = getUtils(csn, 'init-all');

@@ -61,3 +61,11 @@

return column;
return getElementFromFrom(name, base.from);
const from = getElementFromFrom(name, base.from);
if (from)
return from;
// For .expand/.inline, we can find it via origin
// Although I would have expected to find it via getColumn...
const origin = getOrigin(element);
if (origin)
return origin;
throw new Error(`Could not find ancestor for ${JSON.stringify(element)} named ${name}`);
}

@@ -92,6 +100,3 @@

else if (base.args && base.join) {
const result = checkJoinSources(base.args, name);
if (!result)
throw new Error(`Could not find ${name} in ${JSON.stringify(base.args)}`);
return result;
return checkJoinSources(base.args, name);
}

@@ -98,0 +103,0 @@

@@ -32,6 +32,3 @@ 'use strict';

'@fiori.draft.enabled': onlyViaArtifact,
'@': (prop, target, source) => {
if (source[prop] !== null)
target[prop] = source[prop];
},
'@': nullStopsPropagation,
// Example: `type E : F;` does not have `elements`, but they are required for e.g. OData.

@@ -52,3 +49,3 @@ elements: onlyTypeDef,

type: always,
doc: always,
doc: nullStopsPropagation,
length: always,

@@ -99,2 +96,6 @@ precision: always,

val: always,
type: notWithItemsOrElements,
target: notWithItemsOrElements,
keys: notWithItemsOrElements,
cardinality: notWithItemsOrElements,
};

@@ -709,2 +710,15 @@

/**
* The value `null` tells us to skip the propagation of the property.
* This is the case e.g. for `doc` or for annotations.
*
* @param {string} prop
* @param {CSN.Element} target
* @param {CSN.Element} source
*/
function nullStopsPropagation(prop, target, source) {
if (source[prop] !== null)
target[prop] = source[prop];
}
/**
* Special propagation rules for .items - depending on the exact type of .items and the

@@ -730,2 +744,16 @@ * way it was referenced (type of, direct type, direct many), we need to propagate (or not).

/**
* Don't propagate certain properties if the target already has a .items or .elements
*
* This happens with .expand/.inline
*
* @param {string} prop
* @param {CSN.Element} target
* @param {CSN.Element} source
*/
function notWithItemsOrElements(prop, target, source) {
if (!target.items && !target.elements || !source.target)
target[prop] = source[prop];
}
/**
* Some properties must not be copied over if the type of this member

@@ -732,0 +760,0 @@ * is a reference to another element.

@@ -64,3 +64,3 @@ // Util functions for operations usually used with files.

function realpath(path, cb) {
return fs.realpath(path, cb);
return fs.realpath.native(path, cb);
}

@@ -70,3 +70,3 @@

try {
cb(null, fs.realpathSync(path));
cb(null, fs.realpathSync.native(path));
}

@@ -92,3 +92,3 @@ catch (err) {

if (body && typeof body === 'object' && body.realname) {
filename = body.realname; // use fs.realpath name
filename = body.realname; // use fs.realpath.native name
body = fileCache[filename];

@@ -95,0 +95,0 @@ }

@@ -17,37 +17,33 @@ 'use strict';

this.id = id;
// TODO: If we require Node 12, use process.hrtime.bigint()
// as process.hrtime() is deprecated.
// eslint-disable-next-line no-multi-assign
this.startTime = this.lapTime = process.hrtime();
this.startTime = process.hrtime.bigint();
this.lapTime = this.startTime;
}
/**
* Start watch.
*/
* Start watch.
*/
start() {
// eslint-disable-next-line no-multi-assign
this.startTime = this.lapTime = process.hrtime();
this.startTime = process.hrtime.bigint();
this.lapTime = this.startTime;
}
/**
* Stop and return delta T
* Stop and return delta T in nanoseconds,
* but do not set start time
*/
stop() {
return process.hrtime(this.startTime);
const endTime = process.hrtime.bigint();
return endTime - this.startTime;
}
/**
* return lap time
*/
lap() {
const dt = process.hrtime(this.lapTime);
this.lapTime = process.hrtime();
const endTime = process.hrtime.bigint();
const dt = endTime - this.startTime;
this.lapTime = process.hrtime.bigint();
return dt;
}
// stop as sec.ns float
stopInFloatSecs() {
const dt = this.stop();
return dt[0] + dt[1] / 1000000000;
return dt / BigInt(1000000000);
}

@@ -58,3 +54,3 @@

const dt = this.lap();
return dt[0] + dt[1] / 1000000000;
return dt / BigInt(1000000000);
}

@@ -68,3 +64,3 @@ }

*
* To enable time tracing, set CDSC_TIMETRACE to true in the environment
* To enable time tracing, set CDSC_TIMETRACING to true in the environment
*

@@ -113,6 +109,9 @@ * @class TimeTracer

const current = this.traceStack.pop();
const dT = current.stop();
const dt = current.stop();
const base = `${ ' '.repeat(this.traceStack.length * 2) }${ current.id } took:`;
const sec = (dt / BigInt(1000000000)).toString();
// first, get remaining ns, then convert to ms.
const msec = ((dt % BigInt(1000000000)) / BigInt(1000000)).toString();
// eslint-disable-next-line no-console
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, dT[0], dT[1] / 1000000);
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, sec, msec );
}

@@ -119,0 +118,0 @@ catch (e) {

{
"name": "@sap/cds-compiler",
"version": "2.15.4",
"version": "3.0.0",
"description": "CDS (Core Data Services) compiler and backends",

@@ -8,2 +8,3 @@ "homepage": "https://cap.cloud.sap/",

"license": "SEE LICENSE IN LICENSE",
"type": "commonjs",
"bin": {

@@ -17,3 +18,33 @@ "cdsc": "bin/cdsc.js",

"scripts": {
"postinstall": "node lib/fix_antlr4-8_warning.js"
"download": "node scripts/downloadANTLR.js",
"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.8 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.8",
"xmakeAfterInstall": "npm run gen",
"xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
"test": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/ && mocha scripts/testLazyLoading.js",
"testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",
"testdot": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter dot --reporter-option maxDiffSize=0 --full-trace scripts/linter/lintMessages.js test/ test3/",
"test3": "node scripts/verifyGrammarChecksum.js && node scripts/linter/lintTests.js test3/ && mocha --reporter-option maxDiffSize=0 scripts/linter/lintMessages.js test3/",
"deployTest3SQL": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/testHANASQLDeployment.js",
"deployTest3": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/testDeployment.js",
"deployDiffs": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/deployDiffs.js",
"gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
"testdb": "node scripts/verifyGrammarChecksum.js && cross-env TESTDB='TRUE' mocha",
"testmigration": "npm install --no-save @sap/hana-client@2.3.123 @sap/hdi-deploy@3.10.0 && node scripts/verifyGrammarChecksum.js && cross-env TESTMIGRATION='TRUE' mocha",
"testall": "npm install --no-save @sap/hana-client@2.3.123 @sap/hdi-deploy@3.10.0 && node scripts/verifyGrammarChecksum.js && cross-env TESTDB='TRUE' TESTMIGRATION='TRUE' mocha",
"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/ && nyc report --reporter=lcov",
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
"tslint": "tsc --pretty -p .",
"updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
"generateCompilerRefs": "cross-env MAKEREFS='true' mocha test/testCompiler.js",
"generateEdmRefs": "cross-env MAKEREFS='true' mocha test/testEdmPositive.js",
"generateForHanaRefs": "cross-env MAKEREFS='true' mocha test/testHanaTransformation.js",
"generateOdataRefs": "cross-env MAKEREFS='true' mocha test/testODataTransformation.js",
"generateOdataAnnoRefs": "cross-env MAKEREFS='true' mocha test/testODataAnnotations.js",
"generateToSqlRefs": "cross-env MAKEREFS='true' mocha test/testToSql.js",
"generateToRenameRefs": "cross-env MAKEREFS='true' mocha test/testToRename.js",
"generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js",
"generateScenarioRefs": "cross-env MAKEREFS='true' mocha test/testScenarios.js",
"generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",
"generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/"
},

@@ -24,3 +55,3 @@ "keywords": [

"dependencies": {
"antlr4": "4.8.0"
"antlr4": "4.9.3"
},

@@ -38,4 +69,4 @@ "files": [

"engines": {
"node": ">=12"
"node": ">=14"
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc