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.13.8 to 2.15.2

share/messages/syntax-expected-integer.md

149

bin/cdsc.js

@@ -47,2 +47,14 @@ #!/usr/bin/env node

try {
cdsc_main();
}
catch (err) {
// This whole try/catch is only here because process.exit does not work in combination with
// stdout/err - see comment at ProcessExitError
if (err instanceof ProcessExitError)
process.exitCode = err.exitCode;
else
throw err;
}
function remapCmdOptions(options, cmdOptions) {

@@ -77,2 +89,5 @@ if (!cmdOptions)

break;
case 'serviceNames':
options.serviceNames = value.split(',');
break;
default:

@@ -85,4 +100,5 @@ options[key] = value;

// Parse the command line and translate it into options
try {
function cdsc_main() {
// Parse the command line and translate it into options
const cmdLine = optionProcessor.processCmdLine(process.argv);

@@ -121,2 +137,7 @@ // Deal with '--version' explicitly

if (cmdLine.options.options) {
if (!loadOptionsFromJson(cmdLine))
return;
}
// Default warning level is 2 (info)

@@ -132,7 +153,3 @@ // FIXME: Is that not set anywhere in the API?

// --cds-home <dir>: modules starting with '@sap/cds/' are searched in <dir>
if (cmdLine.options.cdsHome) {
if (!global.cds)
global.cds = {};
global.cds.home = cmdLine.options.cdsHome;
}
// -> cmdLine.options.cdsHome is passed down to moduleResolve

@@ -155,4 +172,4 @@ // Set default command if required

if (cmdLine.options.beta) {
// If set through CLI (and not options file), `beta` is a string and needs processing.
if (cmdLine.options.beta && typeof cmdLine.options.beta === 'string') {
const features = cmdLine.options.beta.split(',');

@@ -165,8 +182,9 @@ cmdLine.options.beta = {};

const to = cmdLine.options.toSql ? 'toSql' : 'toHana';
// remap string values for `assertIntegrity` option to boolean
if (cmdLine.options.assertIntegrity &&
cmdLine.options.assertIntegrity === 'true' ||
cmdLine.options.assertIntegrity === 'false'
if (cmdLine.options[to] && cmdLine.options[to].assertIntegrity &&
(cmdLine.options[to].assertIntegrity === 'true' ||
cmdLine.options[to].assertIntegrity === 'false')
)
cmdLine.options.assertIntegrity = cmdLine.options.assertIntegrity === 'true';
cmdLine.options[to].assertIntegrity = cmdLine.options[to].assertIntegrity === 'true';

@@ -177,3 +195,4 @@ // Enable all beta-flags if betaMode is set to true

if (cmdLine.options.deprecated) {
// If set through CLI (and not options file), `deprecated` is a string and needs processing.
if (cmdLine.options.deprecated && typeof cmdLine.options.deprecated === 'string') {
const features = cmdLine.options.deprecated.split(',');

@@ -185,13 +204,8 @@ cmdLine.options.deprecated = {};

}
parseSeverityOptions(cmdLine);
// Do the work for the selected command
executeCommandLine(cmdLine.command, cmdLine.options, cmdLine.args);
}
catch (err) {
// This whole try/catch is only here because process.exit does not work in combination with
// stdout/err - see comment at ProcessExitError
if (err instanceof ProcessExitError)
process.exitCode = err.exitCode;
else
throw err;
}

@@ -242,6 +256,6 @@ /**

const messageLevels = {
Error: 0, Warning: 1, Info: 2, None: 3,
Error: 0, Warning: 1, Info: 2, Debug: 3,
};
// All messages are put into the message array, even those which should not
// been displayed (severity 'None')
// been displayed (severity 'Debug')

@@ -342,8 +356,9 @@ // Create output directory if necessary

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

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

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

@@ -462,3 +477,3 @@ writeToFileOrDisplay(options.out, `${serviceName}.xml`, result[serviceName]);

function displayMessages( model, messages = options.messages ) {
if (!Array.isArray(messages))
if (!Array.isArray(messages) || options.quiet)
return model;

@@ -568,2 +583,4 @@

if (dir === '-') {
if (options.quiet)
return;
if (!omitHeadline) {

@@ -586,13 +603,71 @@ const sqlTypes = {

}
}
function catchErrors(err) {
// @ts-ignore
if (err instanceof Error && err.hasBeenReported)
return;
console.error( '' );
console.error( 'INTERNAL ERROR:' );
console.error( util.inspect(err, false, null) );
console.error( '' );
process.exitCode = 70;
function loadOptionsFromJson(cmdLine) {
try {
let opt = JSON.parse(fs.readFileSync(cmdLine.options.options, 'utf-8'));
if (opt.cds)
opt = opt.cds;
if (opt.cdsc)
opt = opt.cdsc;
Object.assign(cmdLine.options, opt);
return true;
}
catch (e) {
catchErrors(e);
return false;
}
}
function catchErrors(err) {
// @ts-ignore
if (err instanceof Error && err.hasBeenReported)
return;
console.error( '' );
console.error( 'INTERNAL ERROR:' );
console.error( util.inspect(err, false, null) );
console.error( '' );
process.exitCode = 70;
}
/**
* Parses the options `--error` and similar.
* Sets the dictionary `severities` on the given options.
*
* @param {object} options
*/
function parseSeverityOptions({ options }) {
if (!options.severities)
options.severities = Object.create(null);
const severityMap = {
error: 'Error',
warn: 'Warning',
info: 'Info',
debug: 'Debug',
};
// Note: We use a for loop to ensure that the order of the options on the command line is respected, i.e.
// `--warn id --error id` would lead to `id` being reclassified as an error and not a warning.
for (const key in options) {
switch (key) {
case 'error':
case 'warn':
case 'info':
case 'debug':
parseSeverityOption(options[key], severityMap[key]);
break;
default:
break;
}
}
function parseSeverityOption(list, severity) {
const ids = list.split(',');
for (let id of ids) {
id = id.trim();
if (id)
options.severities[id] = severity;
}
}
}

@@ -36,5 +36,6 @@ /** @module API */

const { cloneCsn } = require('../model/csnUtils');
const { cloneCsnNonDict } = require('../model/csnUtils');
const { toHdbcdsSource } = require('../render/toHdbcds');
const { ModelError } = require('../base/error');
const { forEach } = require('../utils/objectUtils');

@@ -95,3 +96,3 @@ const relevantGeneralOptions = [ /* for future generic options */ ];

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

@@ -108,3 +109,3 @@ for (const name of relevantOptionNames ) {

throwWithError();
throwWithAnyError();
}

@@ -159,3 +160,3 @@

const internalOptions = prepareOptions.to.cdl(externalOptions);
const { result } = backends.toCdlWithCsn(cloneCsn(csn, internalOptions), internalOptions);
const { result } = backends.toCdlWithCsn(cloneCsnNonDict(csn, internalOptions), internalOptions);
return result;

@@ -258,3 +259,3 @@ }

Object.keys(flat).forEach((key) => {
forEach(flat, (key) => {
const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');

@@ -277,3 +278,3 @@ nameMapping[artifactNameLikeInCsn] = key;

// now add the not-sorted stuff, like indices
Object.keys(sqlArtifactsNotToSort).forEach((key) => {
forEach(sqlArtifactsNotToSort, (key) => {
sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlArtifactsNotToSort[key];

@@ -300,6 +301,6 @@ });

for (const [ key, value ] of Object.entries(dict)) {
forEach(dict, (key, value) => {
const name = remapName(key, csn, filter);
result[name] = value;
}
});

@@ -406,5 +407,5 @@ return result;

const result = [];
for (const [ kind, artifacts ] of Object.entries(hdbkinds)) {
forEach(hdbkinds, (kind, artifacts) => {
const suffix = `.${ kind }`;
for (const [ name, sqlStatement ] of Object.entries(artifacts)) {
forEach(artifacts, (name, sqlStatement) => {
if ( kind !== 'hdbindex' )

@@ -414,4 +415,4 @@ result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });

result.push({ name, suffix, sql: sqlStatement });
}
}
});
});
return result;

@@ -426,5 +427,3 @@ }

const result = [];
for (const [ name ] of Object.entries(deletions))
result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' });
forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
return result;

@@ -439,5 +438,3 @@ }

const result = [];
for (const [ name, changeset ] of Object.entries(migrations))
result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset });
forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
return result;

@@ -600,8 +597,9 @@ }

const result = {};
for (const [ fileType, artifacts ] of Object.entries(toProcess)) {
forEach(toProcess, (fileType, artifacts) => {
if (fileType === 'messages')
continue;
for (const filename of Object.keys(artifacts))
return;
forEach(artifacts, (filename) => {
result[`${ filename }.${ fileType }`] = artifacts[filename];
}
});
});

@@ -608,0 +606,0 @@ return result;

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

const options = Object.assign({}, defaults);
const inputOptionNames = Object.keys(input);
for (const name of overallOptions) {
// Ensure that arrays are not passed as a reference!
// This caused issues with the way messages are handled in processMessages
if (Array.isArray(input[name]) && inputOptionNames.includes(name))
if (Array.isArray(input[name]))
options[name] = [ ...input[name] ];
else if (inputOptionNames.includes(name))
else if (Object.hasOwnProperty.call(input, name))
options[name] = input[name];

@@ -92,0 +91,0 @@ }

'use strict';
const { makeMessageFunction } = require('../base/messages');
const { forEach } = require('../utils/objectUtils');

@@ -162,6 +163,5 @@ /* eslint-disable arrow-body-style */

const messageCollector = { messages: [] };
const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
const { error, throwWithAnyError } = makeMessageFunction(null, messageCollector, moduleName);
for (const optionName of Object.keys(options)) {
const optionValue = options[optionName];
forEach(options, (optionName, optionValue) => {
const validator = customValidators[optionName] || validators[optionName] || booleanValidator;

@@ -171,4 +171,4 @@

error('invalid-option', null, {}, `Expected option "${ optionName }" to have "${ validator.expected(optionValue) }". Found: "${ validator.found(optionValue) }"`);
}
throwWithError();
});
throwWithAnyError();
}

@@ -184,3 +184,3 @@

message.throwWithError();
message.throwWithAnyError();
}

@@ -187,0 +187,0 @@ /* eslint-enable jsdoc/no-undefined-types */

@@ -33,2 +33,5 @@ // Central registry for messages.

const { CompilerAssertion } = require("./error");
const { createDict } = require("../utils/objectUtils");
/**

@@ -66,3 +69,3 @@ * Central register of messages and their configuration.

'def-unexpected-calcview-assoc': { severity: 'Error' },
'chained-array-of': { severity: 'Error' },
'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },

@@ -82,2 +85,3 @@ 'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },

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

@@ -103,2 +107,3 @@ 'ref-sloppy-actionparam-type': { severity: 'Error' },

'query-ignoring-param-nullability': { severity: 'Info' },
'query-expected-identifier': { severity: 'Error' },

@@ -149,2 +154,4 @@ 'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js

'syntax-expected-integer': { severity: 'Error' },
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config

@@ -159,3 +166,5 @@

'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
'odata-spec-violation-id': { severity: 'Error' },
'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
'odata-spec-violation-type-unknown': { severity: 'Warning' },
'odata-spec-violation-no-key': { severity: 'Warning' },

@@ -166,5 +175,27 @@ 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars

'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
};
// Old/Deprecated message IDs that we only still use for backwards-compatibility.
// We keep them in a separate array for easier access. No need to go through all
// existing messages and search for the old one in `oldNames` property.
// The keys will be added to `oldNames` of the new message, which is used for reclassification.
const oldMessageIds = createDict({
'old-anno-duplicate': 'anno-duplicate', // Example
});
// Set up the old-to-new message ID mapping in the message registry.
for (const oldName in oldMessageIds) {
const newName = oldMessageIds[oldName];
if (centralMessages[oldName])
throw new CompilerAssertion(`Mapping from ${oldName} not possible: ID is still used in message registry.`);
if (!centralMessages[newName])
throw new CompilerAssertion(`Mapping from ${oldName} to new message ID ${newName} does not exist!`);
if (!centralMessages[newName].oldNames)
centralMessages[newName].oldNames = [ oldName ];
else
centralMessages[newName].oldNames.push(oldName);
}
// For messageIds, where no text has been provided via code (central def)

@@ -176,3 +207,3 @@ const centralMessageTexts = {

'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
'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)',

@@ -215,2 +246,12 @@ 'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',

},
'syntax-expected-integer': {
std: 'A safe integer is expected here',
normal: 'An integer number is expected here',
unsafe: 'The provided integer is too large',
},
'syntax-duplicate-argument': {
std: 'Unexpected argument $(CODE)',
unknown: 'Unknown argument $(CODE)',
duplicate: 'Duplicate argument $(CODE)',
},
'ref-undefined-def': {

@@ -225,3 +266,4 @@ std: 'Artifact $(ART) has not been found',

std: 'Element $(ART) has not been found',
element: 'Artifact $(ART) has no element $(MEMBER)'
element: 'Artifact $(ART) has no element $(MEMBER)',
aspect: 'Element $(ID) has not been found in the anonymous target aspect'
},

@@ -244,2 +286,15 @@ 'ref-unknown-var': {

},
'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
'type-ignoring-argument': 'Too many arguments for type $(ART)',
'type-unexpected-argument': {
std: 'Too many arguments for type $(ART)',
type: 'Unexpected argument $(PROP) for type $(ART) with base type $(TYPE)',
builtin: 'Unexpected argument $(PROP) for type $(ART)',
'non-scalar': 'Only scalar types can have arguments',
max: 'Expecting argument $(PROP) for type $(TYPE) to not exceed $(NUMBER)',
min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',
'incorrect-type': 'Expected $(NAMES) for argument $(PROP), but found $(CODE)',
},
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',

@@ -310,2 +365,6 @@ 'anno-undefined-def': 'Artifact $(ART) has not been found',

},
'query-expected-identifier': {
std: 'Expected identifier for select item',
assoc: 'Expected identifier as the association\'s name',
},

@@ -327,7 +386,12 @@ 'ref-sloppy-type': 'A type or an element is expected here',

// OData version dependent messages
'odata-spec-violation-array': 'Unexpected array type for $(API)',
'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for $(API)',
'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for $(API)',
'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
'odata-spec-violation-array': 'Unexpected array type for OData $(VERSION)',
'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for OData $(VERSION)',
'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for OData $(VERSION)',
'odata-spec-violation-assoc': 'Unexpected association in structured type for OData $(VERSION)',
'odata-spec-violation-constraints': 'Partial referential constraints produced for OData $(VERSION)',
'odata-spec-violation-id': {
std: 'Expected EDM name $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
},
// version independent messages

@@ -347,5 +411,13 @@ 'odata-spec-violation-key-array': {

'odata-spec-violation-no-key': 'Expected entity to have a primary key',
'odata-spec-violation-type': 'Expected element to have a type',
'odata-spec-violation-type-unknown': 'Unknown Edm Type $(TYPE)',
'odata-spec-violation-type': {
std: 'Expected element to have a type',
incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
},
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
'odata-spec-violation-namespace': {
std: 'Expected service name not to be one of the reserved names $(NAMES)',
length: 'Expected service name not to exceed 511 characters',
},
// Other odata/edm errors

@@ -355,3 +427,4 @@ 'odata-definition-exists': {

proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
}
},
'odata-navigation': 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)'
}

@@ -363,3 +436,3 @@

* @typedef {object} MessageConfig
* @property {CSN.MessageSeverity} severity Default severity for the message.
* @property {MessageSeverity} severity Default severity for the message.
* @property {string[]|'deprecated'|true} [configurableFor]

@@ -375,4 +448,6 @@ * Whether the error can be reclassified to a warning or lower.

* call. Used for ensuring that all calls with the same message-id have the same severity.
* @property {string[]} [oldNames] Aliases for the message id. Used for reclassification as well as "explain" messages.
* Don't set this property directly! Append to object oldMessageIds instead!
*/
module.exports = { centralMessages, centralMessageTexts };
module.exports = { centralMessages, centralMessageTexts, oldMessageIds };

@@ -10,6 +10,7 @@ // Functions and classes for syntax messages

const { isDeprecatedEnabled } = require('./model');
const { centralMessages, centralMessageTexts } = require('./message-registry');
const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
const { copyPropIfExist } = require('../utils/objectUtils');
const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
const { CompilerAssertion } = require("./error");

@@ -28,3 +29,4 @@ const fs = require('fs');

/**
* Returns true if at least one of the given messages is of severity "Error"
* Returns true if at least one of the given messages is of severity "Error".
*
* @param {CSN.Message[]} messages

@@ -39,3 +41,4 @@ * @returns {boolean}

* Returns true if at least one of the given messages is of severity "Error"
* and *cannot* be reclassified to a warning.
* and *cannot* be reclassified to a warning for the given module.
* Won't detect already downgraded messages.
*

@@ -128,3 +131,3 @@ * @param {CSN.Message[]} messages

* @param {string} msg The message text
* @param {CSN.MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {string} [id] The ID of the message - visible as property messageId

@@ -148,3 +151,3 @@ * @param {any} [home]

if (moduleName)
Object.defineProperty( this, '$module', { value: moduleName } );
Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
}

@@ -192,3 +195,3 @@

/**
* Reclassify the given message's severity using:
* Get the reclassified severity of the given message using:
*

@@ -204,13 +207,10 @@ * 1. The specified severity: either centrally provided or via the input severity

*
* @param {string} id
* @param {CSN.MessageSeverity} severity
* @param {object} severities
* @param {object} msg The CompileMessage.
* @param {CSN.Options} options
* @param {string} moduleName
* @returns {CSN.MessageSeverity}
*
* TODO: we should pass options as usual
* TODO: should be part of the returned function
* @param {boolean} deprecatedDowngradable
* @returns {MessageSeverity}
*/
function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable ) {
const spec = centralMessages[id] || { severity };
function reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable ) {
const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
if (spec.severity === 'Error') {

@@ -228,3 +228,13 @@ const { configurableFor } = spec;

}
return normalizedSeverity( severities[id] ) || spec.severity;
if (!options.severities)
return spec.severity;
let newSeverity = options.severities[msg.messageId];
// The user could have specified a severity through an old message ID.
if (!newSeverity && spec.oldNames) {
const oldName = spec.oldNames.find((name => options.severities[name]))
newSeverity = options.severities[oldName];
}
return normalizedSeverity( newSeverity ) || spec.severity;
}

@@ -240,20 +250,2 @@

/**
* Reclassifies all messages according to the current module.
* This is required because if throwWithError() throws and the message's
* severities has `errorFor` set, then the message may still appear to be a warning.
*
* TODO: this actually likely needs to be called by the backend module at the beginning!
*
* @param {CSN.Message[]} messages
* @param {object} severities
* @param {string} moduleName
*/
function reclassifyMessagesForModule(messages, severities, moduleName, deprecatedDowngradable) {
for (const msg of messages) {
if (msg.messageId && msg.severity !== 'Error')
msg.severity = reclassifiedSeverity(msg.messageId, msg.severity, severities, moduleName, deprecatedDowngradable);
}
}
/**
* Compare two severities. Returns 0 if they are the same, and <0 if

@@ -265,4 +257,4 @@ * `a` has a lower `level` than `b` according to {@link severitySpecs},

*
* @param {CSN.MessageSeverity} a
* @param {CSN.MessageSeverity} b
* @param {MessageSeverity} a
* @param {MessageSeverity} b
* @see severitySpecs

@@ -302,6 +294,5 @@ */

* Create the `message` functions to emit messages.
* See internalDoc/ReportingMessages.md for detail
*
* @example
* ```
* ```js
* const { createMessageFunctions } = require(‘../base/messages’);

@@ -322,3 +313,3 @@ * function module( …, options ) {

function createMessageFunctions( options, moduleName, model = null ) {
return makeMessageFunction( model, options, moduleName, true );
return makeMessageFunction( model, options, moduleName );
}

@@ -330,3 +321,3 @@

* @example
* ```
* ```js
* const { makeMessageFunction } = require(‘../base/messages’);

@@ -345,5 +336,4 @@ * function module( …, options ) {

* @param {string} [moduleName]
* @param {boolean} [throwOnlyWithNew=false] behave like createMessageFunctions
*/
function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNew = false ) {
function makeMessageFunction( model, options, moduleName = null ) {
// ensure message consistency during runtime with --test-mode

@@ -354,3 +344,2 @@ if (options.testMode)

const hasMessageArray = !!options.messages;
const severities = options.severities || {};
const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );

@@ -364,6 +353,13 @@ /**

let messages = options.messages || [];
/**
* Whether an error was emitted in the module. Also includes reclassified errors.
* @type {boolean}
*/
let hasNewError = false;
reclassifyMessagesForModule();
return {
message, error, warning, info, debug, messages,
throwWithError: (throwOnlyWithNew ? throwWithError : throwWithAnyError),
throwWithError, throwWithAnyError,
callTransparently, moduleName,

@@ -380,7 +376,2 @@ };

}
if (id) {
if (options.testMode && !options.$recompile)
_check$Consistency( id, moduleName, severity, texts, options )
severity = reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable );
}

@@ -397,2 +388,8 @@ const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);

if (id) {
if (options.testMode && !options.$recompile)
_check$Consistency( id, moduleName, severity, texts, options )
msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
}
messages.push( msg );

@@ -427,3 +424,3 @@ hasNewError = hasNewError || msg.severity === 'Error' &&

if (texts)
throw new Error('No "texts" argument expected because text was already provided as third argument.');
throw new CompilerAssertion('No "texts" argument expected because text was already provided as third argument.');
} else {

@@ -437,3 +434,3 @@ if (textArguments !== undefined && typeof textArguments !== 'object')

function _expectedType(field, value, type) {
throw new Error(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
}

@@ -493,5 +490,5 @@ }

if (!id)
throw new Error('A message id is missing!');
throw new CompilerAssertion('A message id is missing!');
if (!centralMessages[id])
throw new Error(`Message id '${ id }' is missing an entry in the central message register!`);
throw new CompilerAssertion(`Message id '${ id }' is missing an entry in the central message register!`);
return _message(id, location, textArguments, null, texts);

@@ -548,3 +545,2 @@ }

return;
reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;

@@ -556,2 +552,22 @@ if (hasError( messages, moduleName ))

/**
* Reclassifies all messages according to the current module.
* This is required because if throwWithError() throws and the message's
* severities has `errorFor` set, then the message may still appear to be a warning.
*/
function reclassifyMessagesForModule() {
for (const msg of messages) {
if (msg.messageId && msg.severity !== 'Error') {
const severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable);
if (severity !== msg.severity) {
msg.severity = severity;
// Re-set the module regardless of severity, since we reclassified it.
Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
hasNewError = hasNewError || severity === 'Error' &&
!(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
}
}
}
}
/**
* Collects all messages during the call of the callback function instead of

@@ -591,3 +607,3 @@ * storing them in the model. Returns the collected messages.

if (id.length > 30 && !centralMessages[id])
throw new Error( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
if (!options.severities)

@@ -609,16 +625,15 @@ _check$Severities( id, moduleName || '?', severity );

else if (expected !== severity)
throw new Error( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
throw new CompilerAssertion( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
return;
}
// now try whether the message could be something less than an Error in the module due to user wishes
const user = reclassifiedSeverity( id, null, { [id]: 'Info' }, moduleName, false );
if (user === 'Error') { // always an error in module
if (!isDowngradable( id, moduleName )) { // always an error in module
if (severity !== 'Error')
throw new Error( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
throw new CompilerAssertion( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}
else if (spec.severity === 'Error') {
throw new Error( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
throw new CompilerAssertion( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
}
else if (spec.severity !== severity) {
throw new Error( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
throw new CompilerAssertion( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}

@@ -634,3 +649,3 @@ }

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

@@ -678,2 +693,3 @@

$reviewed: ignoreTextTransform,
version: quote.meta,
};

@@ -843,3 +859,3 @@

: '') +
(err.severity||'Error') +
(err.severity || 'Error') +
// TODO: use [message-id]

@@ -1426,2 +1442,3 @@ (err.messageId && !noMessageId ? ' ' + err.messageId + ': ' : ': ') +

* Get the explanation string for the given message-id.
* Ensure to have called hasMessageExplanation() before.
*

@@ -1434,2 +1451,3 @@ * @param {string} messageId

function explainMessage(messageId) {
messageId = oldMessageIds[messageId] || messageId;
const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);

@@ -1441,2 +1459,4 @@ return fs.readFileSync(filename, 'utf8');

* Returns true if the given message has an explanation file.
* Takes into account changed message ids, i.e. looks up if the new
* message id has an explanation.
*

@@ -1447,3 +1467,4 @@ * @param {string} messageId

function hasMessageExplanation(messageId) {
return messageId && _messageIdsWithExplanation.includes(messageId);
const id = oldMessageIds[messageId] || messageId || false;
return id && _messageIdsWithExplanation.includes(id);
}

@@ -1450,0 +1471,0 @@

@@ -342,3 +342,7 @@ 'use strict'

}
else if (!seenDashDash && arg.startsWith('--')) {
i += processOption(i);
}
else if (!seenDashDash && arg.startsWith('-')) {
splitSingleLetterOption(argv, i); // `-ab` -> `-a -b`
i += processOption(i);

@@ -645,2 +649,17 @@ }

/**
* Splits `-abc` into `-a -b -c`. Does this in-place on argv.
*
* @param {string[]} argv Argument array
* @param {number} i Current option index.
*/
function splitSingleLetterOption(argv, i) {
const arg = argv[i];
if (arg.length > 2) { // must be at least `-ab`.
const rest = argv.slice(i + 1);
argv.length = i; // trim array
argv.push(...arg.split('').slice(1).map(a => `-${a}`), ...rest);
}
}
/**
* Return a camelCase name "fooBar" for a long option "--foo-bar"

@@ -647,0 +666,0 @@ */

@@ -16,13 +16,13 @@ 'use strict';

function checkCoreMediaTypeAllowence(member) {
const allowedCoreMediaTypes = [
'cds.String',
'cds.LargeString',
'cds.hana.VARCHAR',
'cds.hana.CHAR',
'cds.Binary',
'cds.LargeBinary',
'cds.hana.CLOB',
'cds.hana.BINARY',
];
if (member['@Core.MediaType'] && member.type && !allowedCoreMediaTypes.includes(member.type)) {
const allowedCoreMediaTypes = {
'cds.String': 1,
'cds.LargeString': 1,
'cds.hana.VARCHAR': 1,
'cds.hana.CHAR': 1,
'cds.Binary': 1,
'cds.LargeBinary': 1,
'cds.hana.CLOB': 1,
'cds.hana.BINARY': 1,
};
if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },

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

/**
* Make sure only one element in a definition is annotated with `@Core.MediaType`
* This is only OData V2 relevant.
*
* @param {CSN.Artifact} artifact Definition to be checked
* @param {string} artifactName The name of the artifact
*/
function checkForMultipleCoreMediaTypes(artifact, artifactName) {
if (!this.csnUtils.getServiceName(artifactName))
return;
if (this.options.toOdata && this.options.toOdata.version === 'v2' && artifact.elements) {
const mediaTypeElementsNames = Object.keys(artifact.elements)
.filter(elementName => artifact.elements[elementName]['@Core.MediaType']);
if (mediaTypeElementsNames.length > 1) {
this.error(null, artifact.$path, { names: mediaTypeElementsNames },
`Multiple elements $(NAMES) annotated with “@Core.MediaType”, OData V2 allows only one`);
}
}
}
/**
* Check if `@Aggregation.default` is assigned together with `@Analytics.Measure`

@@ -93,3 +73,2 @@ *

checkCoreMediaTypeAllowence,
checkForMultipleCoreMediaTypes,
checkAnalytics,

@@ -96,0 +75,0 @@ checkAtSapAnnotations,

'use strict';
const { forEachMemberRecursively } = require('../model/csnUtils');
// Only to be used with validator.js - a correct `this` value needs to be provided!

@@ -46,33 +44,2 @@

/**
* Check that there are no .items containing .items.
*
* @param {CSN.Artifact} art Artifact
* @param {string} artName Name of the artifact
*/
function checkChainedArray(art, artName) {
if (!this.csnUtils.getServiceName(artName))
return;
checkIfItemsOfItems.bind(this)(art);
forEachMemberRecursively(art, checkIfItemsOfItems.bind(this));
/**
*
* @param {object} construct the construct to be checked
*/
function checkIfItemsOfItems(construct) {
const constructType = this.csnUtils.effectiveType(construct);
if (constructType.items) {
if (constructType.items.items) {
this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
return;
}
const itemsType = this.csnUtils.effectiveType(constructType.items);
if (itemsType.items)
this.error('chained-array-of', construct.$path, '"Array of"/"many" must not be chained with another "array of"/"many" inside a service');
}
}
}
module.exports = { validateAssociationsInItems, checkChainedArray };
module.exports = { validateAssociationsInItems };

@@ -17,7 +17,6 @@ 'use strict';

const { validateDefaultValues } = require('./defaultValues');
// const { checkChainedArray } = require('./arrayOfs');
const { checkActionOrFunction } = require('./actionsFunctions');
const {
checkCoreMediaTypeAllowence, checkForMultipleCoreMediaTypes,
checkAnalytics, checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
checkCoreMediaTypeAllowence, checkAnalytics,
checkAtSapAnnotations, checkReadOnlyAndInsertOnly,
} = require('./annotationsOData');

@@ -96,3 +95,2 @@ // both

// checkChainedArray,
checkForMultipleCoreMediaTypes,
checkReadOnlyAndInsertOnly,

@@ -99,0 +97,0 @@ ];

@@ -75,2 +75,3 @@ // Consistency checker on model (XSN = augmented CSN)

'type', '$typeArgs', 'length', 'precision', 'scale', 'srid',
'_effectiveType',
];

@@ -77,0 +78,0 @@

@@ -48,2 +48,3 @@ // Base Definitions for the Core Compiler

actions: true, /* only for parse-cdl */
enum: true, /* only for parse-cdl */
},

@@ -50,0 +51,0 @@ annotate: {

@@ -49,2 +49,12 @@ // The builtin artifacts of CDS

const typeParameters = {
expectedLiteralsFor: {
length: [ 'number' ],
scale: [ 'number', 'string' ],
precision: [ 'number' ],
srid: [ 'number' ],
},
};
typeParameters.list = Object.keys( typeParameters.expectedLiteralsFor );
// const hana = {

@@ -316,2 +326,3 @@ // BinaryFloat: {},

module.exports = {
typeParameters,
functionsWithoutParens,

@@ -318,0 +329,0 @@ specialFunctions,

@@ -27,2 +27,3 @@ // Checks on XSN performed during compile()

forEachDefinition( model, checkArtifact );
checkSapCommonLocale( model, model.$messageFunctions );
return;

@@ -451,6 +452,2 @@

checkExpression(pathStep.args);
if (!path[0] || !path[0]._navigation) { // TODO: Discuss (see #4108)
checkPathForMissingArguments(pathStep);
}
}

@@ -460,68 +457,2 @@ });

/**
* Check whether the argument count of the given path expression matches its artifact.
* If there is a mismatch, an error is issued.
*
* TODO: remove this function - it also checks for parameter in type
* references. We could have a warning, see also configurable errors
* 'args-no-params', 'args-undefined-param'.
*
* @param {object} pathStep The expression to check
*/
function checkPathForMissingArguments(pathStep) {
// _artifact may not be set, e.g. for functions like `convert_currency( amount => 3 )`
// _navigation must not be set or we would (for example) check each field of an entity
if (!pathStep._artifact || pathStep._navigation)
return;
const isAssociation = !!pathStep._artifact.target;
if (isAssociation) {
const targetFinalType = pathStep._artifact.target._artifact &&
pathStep._artifact.target._artifact._effectiveType;
const finalTypeParams = targetFinalType ? targetFinalType.params : null;
compareActualNamedArgsWithFormalNamedArgs(pathStep.args, finalTypeParams);
}
else {
// Parameters can only be provided when navigating along associations, so because this path
// is for non-associations, checking arguments along a navigation is unnecessary and faulty.
compareActualNamedArgsWithFormalNamedArgs(pathStep.args, pathStep._artifact.params);
}
/**
* Compare two argument dictionaries for correct argument count.
* @param {object} actualArgs
* @param {object} formalArgs
*/
function compareActualNamedArgsWithFormalNamedArgs(actualArgs, formalArgs) {
actualArgs = actualArgs || {};
formalArgs = formalArgs || {};
const aArgsCount = Object.keys(actualArgs).length;
const expectedNames = Object.keys(formalArgs);
const missingArgs = [];
for (const fAName in formalArgs) {
if (!actualArgs[fAName]) {
// Note: _effectiveType points to cds.String for `type T : DefaultString`.
// And `default` may appear at any `type` in the hierarchy.
let fArg = formalArgs[fAName];
while (fArg.type && !fArg.default)
fArg = fArg.type._artifact;
if (!fArg.default)
missingArgs.push(fAName);
}
}
if (missingArgs.length) {
error(null, [ pathStep.location, pathStep ],
{ names: missingArgs, expected: expectedNames.length, given: aArgsCount },
'Expected $(EXPECTED) arguments but $(GIVEN) given; missing: $(NAMES)');
}
// Note:
// Unknown arguments are already handled by messages
// args-expected-named and args-undefined-param
}
}
function checkAssociation(elem) {

@@ -962,2 +893,23 @@ // TODO: yes, a check similar to this could make it into the compiler)

/**
* Checks that sap.common.Locale is of type cds.String. This limitation may
* be lifted later on.
*
* @param {XSN.Model} model
* @param {object} messageFunctions
*/
function checkSapCommonLocale( model, messageFunctions ) {
const localeArt = model.definitions['sap.common.Locale'];
if (localeArt) {
const type = localeArt._effectiveType;
const isCdsString = type && type.name && type.name.absolute === 'cds.String';
if (!isCdsString) {
const { message } = messageFunctions;
message('type-expected-builtin', [ localeArt.name.location, localeArt ],
{ name: 'sap.common.Locale' },
'Expected $(NAME) to be a string type');
}
}
}
// For each property named 'path' in 'node' (recursively), call callback(path, node)

@@ -964,0 +916,0 @@ //

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

// Sub phase 1 (addXYZ) - only for main artifats
// Sub phase 1 (addXYZ) - only for main artifacts
// - set _block links

@@ -576,3 +576,3 @@ // - store definitions (including context extensions), NO duplicate check

// TODO: MIXIN with name = ...subquery (not yet supported anyway)
initSelectItems( query, query.columns );
initSelectItems( query, query.columns, query );
if (query.where)

@@ -585,8 +585,10 @@ initExprForQuery( query.where, query );

function initSelectItems( parent, columns ) {
function initSelectItems( parent, columns, user ) {
// TODO: forbid expand/inline with :param, global:true, in ref-where, outside queries (CSN), ...
let wildcard = null;
let hasItems = false;
for (const col of columns || parent.expand || parent.inline || []) {
if (!col) // parse error
continue;
hasItems = true;
if (!columns) {

@@ -612,3 +614,4 @@ if (parent.value)

}
else if (col.value || col.expand) {
// Either expression (value), expand or new association (target && type)
else if (col.value || col.expand || (col.target && col.type)) {
setLink( col, '_block', parent._block );

@@ -619,5 +622,18 @@ defineAnnotations( col, col, parent._block ); // TODO: complain with inline

initExprForQuery( col.value, parent );
initSelectItems( col );
initSelectItems( col, null, user );
}
}
if (hasItems && !wildcard && parent.excludingDict) {
// TODO: Better way to get source file?
let block = parent;
while (block._block)
block = block._block;
if (block.$frontend === 'cdl') {
warning('query-ignoring-exclude', [ parent.excludingDict[$location], user ],
{ prop: '*' },
'Excluding elements without wildcard $(PROP) has no effect');
}
}
}

@@ -906,11 +922,39 @@

if (targetAspect.elements) {
const inEntity = parent._main && parent._main.kind === 'entity';
// TODO: also allow indirectly (component in component in entity)?
setLink( targetAspect, '_outer', obj );
setLink( targetAspect, '_parent', parent._parent );
setLink( targetAspect, '_main', null ); // for name resolution
parent = targetAspect;
construct = parent; // avoid extension behavior
targetAspect.kind = 'aspect'; // TODO: probably '$aspect' to detect
setLink( targetAspect, '_block', block );
initDollarSelf( targetAspect );
// allow ref of up_ in anonymous aspect inside entity
// (TODO: complain if used and the managed composition is included into
// another entity - might induce auto-redirection):
if (inEntity && !targetAspect.elements.up_) {
const up = {
name: {
id: 'up_',
alias: 'up_',
element: obj.name.element,
absolute: obj.name.absolute,
},
kind: '$navElement',
location: obj.location,
};
setLink( up, '_parent', targetAspect );
setLink( up, '_main', targetAspect ); // used on main artifact
// recompilation case: both target and targetAspect → allow up_ in that case, too:
const name = obj.target && resolveUncheckedPath( obj.target, 'target', obj );
const entity = name && model.definitions[name];
if (entity && entity.elements)
setLink( up, '_origin', entity.elements.up_ );
// processAspectComposition/expand() sets _origin to element of
// generated target entity
targetAspect.$tableAliases.up_ = up;
}
obj = targetAspect;
parent = obj;
construct = parent; // avoid extension behavior
obj.kind = 'aspect'; // TODO: probably '$aspect' to detect
setLink( obj, '_block', block );
initDollarSelf( obj );
}

@@ -935,2 +979,3 @@ }

}
if (obj.foreignKeys) // cannot be extended or annotated - TODO: check anyway?

@@ -1019,3 +1064,3 @@ forEachInOrder( obj, 'foreignKeys', init );

return false;
const names = Object.getOwnPropertyNames( dict );
const names = Object.keys( dict );
if (!names.length) // TODO: re-check - really allow empty dict if no other?

@@ -1057,2 +1102,5 @@ return false;

}
else if (prop === 'columns') {
error( 'extend-columns', [ location, construct ], { art: construct } );
}
else { // if (prop === 'enum') {

@@ -1059,0 +1107,0 @@ error( 'unexpected-enum', [ location, construct ], {},

@@ -161,2 +161,3 @@ // Extend, include, localized data and managed compositions

checkDefinitions( ext, art, 'params');
checkDefinitions( ext, art, 'columns');
defineAnnotations( ext, art, ext._block, ext.kind );

@@ -249,3 +250,3 @@ }

const validDict = obj[prop] || prop === 'elements' && obj.enum;
const member = validDict[name];
const member = validDict && validDict[name];
if (!member)

@@ -267,2 +268,6 @@ extendNothing( dict[name], prop, name, art, validDict );

// TODO: consider reportUnstableExtensions
for (const col of ext.columns)
defineAnnotations( col, col, ext._block, ext.kind );
const { location } = ext.name;

@@ -608,9 +613,14 @@ const { query } = art;

};
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
// If not, use the default `cds.String` with a length of 14.
const hasLocaleType = model.definitions['sap.common.Locale'] &&
model.definitions['sap.common.Locale'].kind === 'type';
const locale = {
name: { location, id: 'locale' },
kind: 'element',
type: augmentPath( location, 'cds.String' ),
length: { literal: 'number', val: 14, location },
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
location,
};
if (!hasLocaleType)
locale.length = { literal: 'number', val: 14, location };

@@ -816,2 +826,9 @@ if (!fioriEnabled) {

if (entity) {
// Support using the up_ element in the generated entity to be used
// inside the anonymous aspect:
const { up_ } = target.$tableAliases;
// TODO: invalidate "up_" alias (at least further navigation) if it
// already has an _origin (when the managed composition is included)
if (up_)
setLink( up_, '_origin', entity.elements.up_ );
model.$compositionTargets[entity.name.absolute] = true;

@@ -818,0 +835,0 @@ processAspectComposition( entity );

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

resolveUncheckedPath,
resolveTypeArguments,
resolveTypeArgumentsUnchecked,
defineAnnotations,

@@ -30,4 +30,3 @@ initMembers,

function resolveTypesAndExtensionsForParseCdl() {
if (!model.extensions)
model.extensions = [];
const extensions = [];

@@ -43,3 +42,7 @@ // TODO: probably better to loop over extensions of all sources (there is just one)

initMembers( ext, ext, ext._block, true );
model.extensions.push( ext );
extensions.push( ext );
for (const col of ext.columns || []) {
// Note, no `priority` argument, since we don't apply the extension in the end.
defineAnnotations( col, col, ext._block );
}
}

@@ -50,4 +53,7 @@ }

forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
if (model.extensions)
if (extensions.length > 0) {
model.extensions = extensions;
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
}
}

@@ -151,7 +157,9 @@

* Resolves `artWithType.type` in an unchecked manner. Handles `type of` cases.
* `artWithType` has the `type` property, i.e. it could be an `items` object.
* `user` is the actual artifact, e.g. entity or element.
*
* @param {object} artWithType
* @param {XSN.Artifact} artifact
* @param {XSN.Artifact} user
*/
function resolveTypeUnchecked(artWithType, artifact) {
function resolveTypeUnchecked(artWithType, user) {
if (!artWithType.type)

@@ -167,10 +175,9 @@ return;

// elem: Type or elem: type of Artifact:elem
const name = resolveUncheckedPath(artWithType.type, 'type', artifact);
const def = name && model.definitions[name];
if (def)
resolveTypeArguments( artWithType, def, artifact );
const name = resolveUncheckedPath( artWithType.type, 'type', user );
const type = name && model.definitions[name] || { name: { absolute: name } };
resolveTypeArgumentsUnchecked( artWithType, type, user );
return;
}
else if (!artifact._main) {
error( 'ref-undefined-typeof', [ artWithType.type.location, artifact ], {},
else if (!user._main) {
error( 'ref-undefined-typeof', [ artWithType.type.location, user ], {},
'Current artifact has no element to refer to as type' );

@@ -180,3 +187,3 @@ return;

else if (root.id === '$self' || root.id === '$projection') {
setArtifactLink( root, artifact._main );
setArtifactLink( root, user._main );
}

@@ -189,4 +196,4 @@ else {

struct = struct._parent;
if (struct.kind === 'select' || struct !== artifact._main) {
message( 'type-unexpected-typeof', [ artWithType.type.location, artifact ],
if (struct.kind === 'select' || struct !== user._main) {
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
{ keyword: 'type of', '#': struct.kind } );

@@ -196,9 +203,8 @@ return;

const fake = { name: { absolute: artifact.name.absolute } };
const fake = { name: { absolute: user.name.absolute } };
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
setLink( fake, '_parent', artifact._parent );
setLink( fake, '_main', artifact._main ); // value does not matter...
setLink( fake, '_parent', user._parent );
setLink( fake, '_main', user._main ); // value does not matter...
setArtifactLink( root, fake );
}
resolveTypeArguments( artifact, {}, artifact ); // issue error for type args
}

@@ -205,0 +211,0 @@

@@ -131,9 +131,12 @@ // Main XSN-based compiler functions

dir = path.resolve(dir);
const a = processFilenames( filenames, dir );
const model = { sources: a.sources, options };
const model = { sources: null, options };
model.$messageFunctions = createMessageFunctions( options, 'compile', model );
let all = promiseAllDoNotRejectImmediately( a.files.map(readAndParse) );
let input = null;
all = all
let all = processFilenames( filenames, dir )
.then((processedInput) => {
input = processedInput;
model.sources = input.sources;
})
.then(() => promiseAllDoNotRejectImmediately( input.files.map(readAndParse) ))
.then( testInvocation, (reason) => {

@@ -144,8 +147,10 @@ // do not reject with PromiseAllError, use InvocationError:

return Promise.reject( errs.find( e => !e.path ) ||
new InvocationError( [ ...a.repeated, ...errs ]) );
new InvocationError( [ ...input.repeated, ...errs ]) );
});
if (!options.parseOnly && !options.parseCdl)
all = all.then( readDependencies );
return all.then( () => {
moduleLayers.setLayers( a.sources );
moduleLayers.setLayers( input.sources );
return compileDoX( model );

@@ -156,3 +161,3 @@ });

async function readAndParse( filename ) {
const { sources } = a;
const { sources } = input;
if ( filename === false ) // module which has not been found

@@ -179,5 +184,5 @@ return [];

function testInvocation( values ) {
if (a.repeated.length)
if (input.repeated.length)
// repeated file names in invocation => just report these
return Promise.reject( new InvocationError(a.repeated) );
return Promise.reject( new InvocationError(input.repeated) );
return values;

@@ -232,3 +237,3 @@ }

dir = path.resolve(dir);
const a = processFilenames( filenames, dir );
const a = processFilenamesSync( filenames, dir );

@@ -461,18 +466,40 @@ const model = { sources: a.sources, options };

// Process an array of `filenames`. Returns an object with properties:
// - `sources`: dictionary which has a filename as key (value is irrelevant)
// - `files`: the argument array without repeating the same name
// - `repeated`: array of filenames which have been repeatedly listed
// (listed only once here even if listed thrice)
//
// Note: there is nothing file-specific about the filenames, the filenames are
// not normalized - any strings work
function processFilenames( filenames, dir ) {
const sources = Object.create(null); // not {} = no [[Prototype]]
const files = [];
const repeated = [];
/**
* Process an array of `filenames`. Returns an object with properties:
* - `sources`: dictionary which has a filename as key (value is irrelevant)
* - `files`: the argument array without repeating the same name
* - `repeated`: array of filenames which have been repeatedly listed
* (listed only once here even if listed thrice)
*
* Note: there is nothing file-specific about the filenames, the filenames are
* not normalized - any strings work
*/
async function processFilenames( filenames, dir ) {
const filenameMap = Object.create(null);
const promises = [];
for (const originalName of filenames) {
const setName = (name) => {
filenameMap[originalName] = name;
};
// Resolve possible symbolic link; if the file does not exist
// we just continue using the original name because readFile()
// already handles non-existent files.
const promise = fs.promises.realpath(path.resolve(dir, originalName))
.then(setName, () => setName(originalName));
promises.push(promise);
}
await Promise.all(promises);
return createSourcesDict( filenames, filenameMap, dir );
}
/**
* Synchronous version of processFilenames().
*/
function processFilenamesSync( filenames, dir ) {
const filenameMap = Object.create(null);
for (const originalName of filenames) {
let name = path.resolve(dir, originalName);
try {

@@ -487,3 +514,25 @@ // Resolve possible symbolic link; if the file does not exist

}
filenameMap[originalName] = name;
}
return createSourcesDict( filenames, filenameMap, dir );
}
/**
* Creates the sources dictionary as well as a list of absolute filenames.
* If files are repeated, `repeated` will contain ArgumentErrors for it.
*
* @param {string[]} filenames List of (possibly relative) filenames. Defines the file order.
* @param {Record<string, string>} filenameMap Map from original name to actual filename
* (e.g. from symlink to underlying path)
* @param {string} dir "Current working directory"
* @return {{sources: object, files: string[], repeated: ArgumentError[]}}
*/
function createSourcesDict( filenames, filenameMap, dir ) {
const sources = Object.create(null);
const files = [];
const repeated = [];
for (const originalName of filenames) {
const name = filenameMap[originalName];
if (!sources[name]) {

@@ -498,6 +547,6 @@ sources[name] = path.relative( dir, name );

}
return { sources, files, repeated };
}
module.exports = {

@@ -504,0 +553,0 @@ parseX,

@@ -8,5 +8,5 @@ // Populate views with elements, elements with association targets, ...

//
// To calculate that info, the compiler might needs the same info for other
// definitions. In other words: it calls itself recursively (using an iterative
// algorithm where appropriate). The be able to calculate that info on demand,
// To calculate that info, the compiler might need the same info for other
// definitions. In other words: it calls itself recursively (using an iterative
// algorithm where appropriate). To be able to calculate that info on demand,
// the definitions need to have enough information, which must have been set in

@@ -170,3 +170,3 @@ // an earlier compiler phase. It is essential to do things in the right order.

(art.type || art._origin || art.value && art.value.path) &&
// TODO: really stop at art.enum?
// TODO: really stop at art.enum? See #8942
!art.target && !art.enum && !art.elements && !art.items) {

@@ -569,3 +569,4 @@ chain.push( art );

}
if (!col.value && !col.expand)
// If neither expression (value), expand nor new association.
if (!col.value && !col.expand && !(col.target && col.type))
continue; // error should have been reported by parser

@@ -572,0 +573,0 @@ if (col.inline) {

@@ -120,2 +120,5 @@ //

forEachMember( art, run ); // after propagation in parent!
// propagate to sub query elements even if not requested:
if (art.$queries)
art.$queries.forEach( run );
let obj = art;

@@ -234,3 +237,3 @@ if (art.returns) {

function notWithExpand( prop, target, source ) {
if (!target.expand)
if (!target.expand || prop === 'type' && source.elements)
always( prop, target, source );

@@ -237,0 +240,0 @@ }

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

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

@@ -93,3 +94,2 @@ const { kindProperties } = require('./base');

resolvePath,
resolveTypeArguments,
defineAnnotations,

@@ -101,2 +101,3 @@ attachAndEmitValidNames,

resolveType,
resolveTypeArgumentsUnchecked,
populateQuery,

@@ -377,4 +378,4 @@ } = model.$functions;

resolveTarget( art, obj._origin );
// console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
// .toString(), obj.target.$inferred)
// console.log(error( 'test-target', [ obj.location, obj ],
// { target: obj.target, kind: obj.kind }, 'Target: $(TARGET), Kind $(KIND)'));
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')

@@ -455,4 +456,6 @@ resolveTarget( art, obj );

function getAssocSpec( type ) {
const cyclic = new Set(); // TODO(#8942): May not be necessary if effectiveType() is adapted.
// only to be called without cycles
while (type) {
while (type && !cyclic.has(type)) {
cyclic.add(type);
if (type.on || type.foreignKeys || type.targetAspect)

@@ -472,3 +475,3 @@ return type;

setArtifactLink( elem.type, type._artifact );
for (const prop of [ 'length', 'precision', 'scale', 'srid' ]) {
for (const prop of typeParameters.list) {
if (elem.value[prop])

@@ -1035,2 +1038,11 @@ elem[prop] = { ...elem.value[prop], $inferred: 'cast' };

const target = resolvePath( obj.target, 'target', art );
if (obj._pathHead && obj.type && !obj.type.$inferred && art._main && art._main.query) {
// New association inside expand/inline: The on-condition can't be properly checked,
// so abort early. See #8797
error( 'query-unexpected-assoc', [ obj.name.location, art ], {},
'Unexpected new association in expand/inline' );
return; // avoid subsequent errors
}
if (obj.on) {

@@ -1067,3 +1079,10 @@ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {

'Managed associations are not allowed for MIXIN elements' );
return; // avoid subsequent errors
}
else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
// New association in views, i.e. parent is a query.
error( 'query-expected-on-condition', [ obj.target.location, art ], {},
'Expected on-condition for published association' );
return; // avoid subsequent errors
}
else if (target && !obj.foreignKeys && target.kind === 'entity') {

@@ -1073,11 +1092,13 @@ if (obj.$inferred === 'REDIRECTED') {

}
else if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
resolveRedirected( art, target );
}
else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
else if (obj.type && obj.type._artifact && obj.type._artifact.internal) {
// cds.Association, ...
addImplicitForeignKeys( art, obj, target );
}
// else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
}
// else console.log( message( null, obj.location, obj, {target}, 'Info','NORE').toString())
if (target && !target.$inferred) {
if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
resolveRedirected( art, target );
}
}
}

@@ -1142,3 +1163,5 @@

if (!origType || !origType.target) {
error( 'redirected-no-assoc', [ elem.target.location, elem ], {},
const path = (elem.value && elem.value.path);
const loc = (path && path[path.length - 1] || elem.value || elem).location;
error( 'redirected-no-assoc', [ loc, elem ], {},
'Only an association can be redirected' );

@@ -1151,3 +1174,3 @@ return;

if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
if (origType.on) {
if (!elem.on && origType.on) {
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},

@@ -1165,3 +1188,5 @@ // TODO: Better text ?

if (target === origTarget) {
if (!elem.target.$inferred) {
if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
// Only a managed redirection gets this info message. Because otherwise
// we'd have to check whether on-condition/foreignKeys are the same.
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },

@@ -1280,6 +1305,69 @@ 'The redirected target is the original $(ART)' );

const typeArt = resolveType( art.type, user );
if (typeArt)
resolveTypeArguments( art, typeArt, user );
if (typeArt) {
resolveTypeArgumentsUnchecked( art, typeArt, user );
checkTypeArguments( art );
}
}
/**
* Check the type arguments on `artWithType`.
* If the effective type is an array or structured type, an error is emitted.
*/
function checkTypeArguments( artWithType ) {
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
// Also: For enums, it points to the enum type, which is why this trick is needed.
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
// trick may be removed if effectiveType() does not stop at enums.
const cyclic = new Set();
let effectiveTypeArt = effectiveType( artWithType );
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
cyclic.add(effectiveTypeArt);
const underlyingEnumType = directType(effectiveTypeArt);
if (underlyingEnumType)
effectiveTypeArt = effectiveType(underlyingEnumType);
else
break;
}
if (!effectiveTypeArt)
return; // e.g. illegal definition references
const params = effectiveTypeArt.parameters &&
effectiveTypeArt.parameters.map(p => p.name || p) || [];
for (const param of typeParameters.list) {
if (artWithType[param] !== undefined) {
if (!params.includes(param)) {
// Whether the type ref itself is a builtin or a custom type with a builtin as base.
const type = directType(artWithType);
let variant;
if (type.builtin)
// `.type` is already a builtin: use a nicer message.
variant = 'builtin';
else if (effectiveTypeArt.builtin)
// base type is a builtin, i.e. a scalar
variant = 'type';
else
// effectiveType is not a builtin -> array or structured
variant = 'non-scalar';
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
'#': variant, prop: param, art: artWithType.type, type: effectiveTypeArt,
});
break; // Avoid spam: Only emit the first error.
}
else if (!typeParameters.expectedLiteralsFor[param].includes(artWithType[param].literal)) {
error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
'#': 'incorrect-type',
prop: param,
code: artWithType[param].literal,
names: typeParameters.expectedLiteralsFor[param],
});
break; // Avoid spam: Only emit the first error.
}
}
}
}
function resolveExpr( expr, expected, user, extDict, expandOrInline) {

@@ -1286,0 +1374,0 @@ // TODO: when we have rewritten the resolvePath functions,

@@ -160,4 +160,4 @@ // Compiler functions and utilities shared across all phases

resolveUncheckedPath,
resolveTypeArgumentsUnchecked,
resolvePath,
resolveTypeArguments,
defineAnnotations,

@@ -463,31 +463,60 @@ attachAndEmitValidNames,

// Resolve the type arguments provided with a type referenced for artifact or
// element `artifact`. This function does nothing if the referred type
// `typeArtifact` does not have a `parameters` property (currently, only
// builtin-types have it, see ./builtins.js).
//
// For each property name `<prop>` in `typeArtifact.parameters`, we move a number
// in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
// TODO: error if no parameters applicable
// TODO: also check for number
function resolveTypeArguments(artifact, typeArtifact, user) {
const args = artifact.$typeArgs || [];
/**
* Resolve the type arguments of `artifact` according to the type `typeArtifact`.
* User is used for semantic message location.
*
* For builtins, for each property name `<prop>` in `typeArtifact.parameters`, we move a value
* in art.$typeArgs (a vector of numbers with locations) to `artifact.<prop>`.
*
* For non-builtins, we take either one or two arguments and interpret them
* as `length` or `precision`/`scale`.
*
* Left-over arguments are errors for non-builtins and warnings for builtins.
*
* @param {object} artifact
* @param {object} typeArtifact
* @param {CSN.Artifact} user
*/
function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) {
let args = artifact.$typeArgs || [];
const parameters = typeArtifact.parameters || [];
const parLength = parameters.length;
for (let i = 0; i < parLength; ++i) {
let par = parameters[i];
if (!(par instanceof Object))
par = { name: par };
if (!artifact[par.name] && i < args.length)
artifact[par.name] = args[i];
if (parameters.length > 0) {
// For Builtins
for (let i = 0; i < parameters.length; ++i) {
let par = parameters[i];
if (!(par instanceof Object))
par = { name: par };
if (!artifact[par.name] && i < args.length)
artifact[par.name] = args[i];
}
args = args.slice(parameters.length);
}
if (args.length > parLength) {
artifact.$typeArgs = artifact.$typeArgs.slice(parLength);
warning( 'unexpected-type-arg', [ artifact.$typeArgs[0].location, user ],
{ art: typeArtifact }, 'Too many arguments for type $(ART)' );
else if (args.length > 0 && !typeArtifact.builtin) {
// One or two arguments are interpreted as either length or precision/scale.
// For builtins, we know what arguments are expected, and we do not need this mapping.
// Also, we expect non-structured types.
if (args.length === 1) {
artifact.length = args[0];
args = args.slice(1);
}
else if (args.length === 2) {
artifact.precision = args[0];
artifact.scale = args[1];
args = args.slice(2);
}
}
else if (artifact.$typeArgs) {
delete artifact.$typeArgs;
if (!artifact.$typeArgs)
return;
// Warn about left-over arguments.
if (args.length > 0) {
const loc = [ args[args.length - 1].location, user ];
if (typeArtifact.builtin)
warning( 'type-ignoring-argument', loc, { art: typeArtifact } );
else
error( 'type-unexpected-argument', loc, { '#': 'std', art: typeArtifact });
}
artifact.$typeArgs = undefined;
}

@@ -737,3 +766,3 @@

}
else if (art.name.select && art.name.select > 1) {
else if (art.name && art.name.select && art.name.select > 1) {
// TODO: 'The current query has no element $(MEMBER)' with $self.MEMBER

@@ -754,4 +783,9 @@ // and 'The sub query for alias $(ALIAS) has no element $(MEMBER)'

else {
const variant = art.kind === 'aspect' && !art.name && 'aspect';
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
[ env ], { art: searchName( art, item.id, 'element' ) } );
[ env ], {
'#': variant,
art: (variant ? '' : searchName( art, item.id, 'element' )),
id: item.id,
} );
}

@@ -758,0 +792,0 @@ return null;

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

setArtifactLink( item, null );
error( 'rewrite-undefined-key', [ weakLocation( (elem.target || elem).location ), assoc ],
const culprit = elem.target && !elem.target.$inferred && elem.target ||
(elem.value && elem.value.path &&
elem.value.path[elem.value.path.length - 1]) ||
elem;
// TODO: probably better to collect the non-projected foreign keys
// and have one message for all
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ],
{ id: item.id, art: alias._main },

@@ -465,0 +471,0 @@ 'Foreign key $(ID) has not been found in target $(ART)' );

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

const { forEachDefinition } = require('../../model/csnUtils');
const { forEach } = require("../../utils/objectUtils");
/* Vocabulary overview as of January 2020:
/*
OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies

@@ -17,2 +17,3 @@ Aggregation (published)

Core (published)
JSON (published)
Measures (published)

@@ -28,4 +29,5 @@ Repeatability (published)

Communication (published)
DataIntegration (published)
Graph (published, experimental)
Hierarchy (not published, still experimental)
Hierarchy (published, experimental)
HTML5 (published, experimental)

@@ -79,2 +81,7 @@ ODM (published, experimental)

},
'DataIntegration': {
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/DataIntegration.xml' },
'inc': { Alias: 'DataIntegration', Namespace: 'com.sap.vocabularies.DataIntegration.v1' },
'int': { filename: 'DataIntegration.xml' }
},
'Graph': {

@@ -85,2 +92,7 @@ 'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Graph.xml' },

},
'Hierarchy': {
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/Hierarchy.xml' },
'inc': { Alias: 'Hierarchy', Namespace: 'com.sap.vocabularies.Hierarchy.v1' },
'int': { filename: 'Hierarchy.xml' }
},
'HTML5': {

@@ -91,2 +103,7 @@ 'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/HTML5.xml' },

},
'JSON': {
'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.JSON.V1.xml' },
'inc': { Alias: 'JSON', Namespace: 'Org.OData.JSON.V1' },
'int': { filename: 'JSON.xml' }
},
'Measures': {

@@ -423,3 +440,3 @@ 'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml' },

if(!isEdmPropertyRendered(carrier, options) ||
(isV2() && (edmUtils.isDerivedType(carrier) || carrier['@Core.MediaType']))) {
(isV2() && (edmUtils.isDerivedType(carrier)))) {
return;

@@ -673,3 +690,3 @@ }

}
else {
else if(typeof tree === 'object' ){
tree[name] = carrier['@' + pathSteps.join('.')];

@@ -725,5 +742,6 @@ }

let newAnno = undefined;
const nullWhitelist = [ 'Core.OperationAvailable' ];
const omissions = { 'Aggregation.default':1 };
const nullList = { 'Core.OperationAvailable':1 };
const voc = termName.slice(0, termName.indexOf('.'));
if(vocabularyDefinitions[voc] && annoValue !== null || nullWhitelist.includes(termName)) {
if(vocabularyDefinitions[voc] && annoValue !== null && !omissions[termName]|| nullList[termName]) {
newAnno = new Edm.Annotation(v, termName);

@@ -741,4 +759,4 @@

}
newAnno.Term = termNameWithoutQualifiers;
newAnno.Qualifier = p[1];
newAnno.setEdmAttribute('Term', termNameWithoutQualifiers);
newAnno.setEdmAttribute('Qualifier', p[1]);
}

@@ -1085,3 +1103,3 @@ if (p.length>2) {

// explicitly mentioned type, render in XML and JSON
newRecord.Type = actualTypeName;
newRecord.setEdmAttribute('Type', actualTypeName);
}

@@ -1094,3 +1112,3 @@ else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {

// explicitly mentioned type, render in XML and JSON
newRecord.Type = actualTypeName;
newRecord.setEdmAttribute('Type', actualTypeName);
}

@@ -1103,3 +1121,3 @@ else if (isAbstractType(actualTypeName)) {

// set to definition name and render in XML and JSON
newRecord.Type = actualTypeName;
newRecord.setEdmAttribute('Type', actualTypeName);
}

@@ -1267,3 +1285,3 @@ else {

if(k === '@type') {
edmNode.Type = val;
edmNode.setEdmAttribute('Type', val);
}

@@ -1314,3 +1332,3 @@ else {

// iterate over each obj.property and translate expression into EDM
Object.entries(obj).forEach(([name, val]) => {
forEach(obj, (name, val) => {
if(exprDef) {

@@ -1322,3 +1340,3 @@ if(exprDef.anno && name[0] === '@') {

if (name[0] === '$') {
edmNode[name.slice(1)] = val;
edmNode.setEdmAttribute(name.slice(1), val);
}

@@ -1325,0 +1343,0 @@ }

@@ -10,9 +10,10 @@ 'use strict';

const edmUtils = require('./edmUtils.js')
const { initializeModel } = require('./edmPreprocessor.js');
const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
const translate = require('./annotations/genericTranslation.js');
const { setProp } = require('../base/model');
const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
const { checkCSNVersion } = require('../json/csnVersion');
const { makeMessageFunction } = require('../base/messages');
const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
/*

@@ -29,7 +30,7 @@ OData V2 spec 06/01/2017 PDF version is available from here:

// get us a fresh model copy that we can work with
const csn = cloneCsn(_csn, _options);
const csn = cloneCsnNonDict(_csn, _options);
// use original options for messages; cloned CSN for semantic location
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
const { info, warning, error, message, throwWithError } = messageFunctions;
const { info, warning, error, message, throwWithAnyError } = messageFunctions;
checkCSNVersion(csn, _options);

@@ -47,9 +48,10 @@

let [ allServices,
const [ allServices,
allSchemas,
reqDefs,
whatsMyServiceRootName,
autoexposeSchemaName,
options ] = initializeModel(csn, _options, messageFunctions);
fallBackSchemaName,
options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
const Edm = require('./edm.js')(options, error);
const Edm = getEdm(options, messageFunctions);

@@ -80,3 +82,3 @@ const v = options.v;

}
throwWithError();
throwWithAnyError();
return rc;

@@ -196,5 +198,5 @@

// Bring the schemas in alphabetical order, service first, root last
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
if(subSchemaDictionary[autoexposeSchemaName])
sortedSchemaNames.push(autoexposeSchemaName);
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
if(subSchemaDictionary[fallBackSchemaName])
sortedSchemaNames.push(fallBackSchemaName);

@@ -229,3 +231,3 @@ // Finally create the schemas and register them in the service.

if(arr.length > 1) {
error(null, null, { name: c.Namespace, id: setName },
error(null, null, { name: c._edmAttributes.Namespace, id: setName },
`Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `);

@@ -241,3 +243,3 @@ }

function populateSchemas(schemas) {
Object.entries(csn.definitions).forEach(([fqName, art]) => {
Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
// Identify service members by their definition name only, this allows

@@ -311,6 +313,14 @@ // to let the internal object.name have the sub-schema name.

const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
if(reservedNames.includes(schema.name)) {
warning('odata-spec-violation-namespace',
[ 'definitions', schema.name ], { names: reservedNames });
const loc = ['definitions', schema.name];
if(reservedNames.includes(schema.name))
warning('odata-spec-violation-namespace', loc, { names: reservedNames });
if (schema.name.length > 511)
warning('odata-spec-violation-namespace', loc, { '#': 'length' });
else {
schema.name.split('.').forEach(id => {
if (!edmUtils.isODataSimpleIdentifier(id))
message('odata-spec-violation-id', loc, { id });
});
}
/** @type {any} */

@@ -354,6 +364,7 @@ const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);

const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
if(acc[cur.Name] === undefined) {
acc[cur.Name] = [ cur ];
const name = cur._edmAttributes.Name;
if(acc[name] === undefined) {
acc[name] = [ cur ];
} else {
acc[cur.Name].push(cur);
acc[name].push(cur);
}

@@ -380,3 +391,3 @@ return acc;

// FIXME: Location for sub schemas?
warning(null, ['definitions', Schema.Namespace], { name: Schema.Namespace }, 'Schema $(NAME) is empty');
warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
}

@@ -386,3 +397,3 @@

if(refs.length > 1) {
error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
'Duplicate name in Schema $(NAME)');

@@ -403,18 +414,26 @@ }

const type = `${schema.name}.${EntityTypeName}`;
if(properties.length === 0) {
if(properties.length === 0)
warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
} else if(entityCsn.$edmKeyPaths.length === 0) {
else if(entityCsn.$edmKeyPaths.length === 0)
message('odata-spec-violation-no-key', loc);
}
if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
message('odata-spec-violation-id', loc, { id: EntityTypeName });
properties.forEach(p => {
const pLoc = [...loc, 'elements', p.Name];
if(!p[p._typeName]) {
message('odata-spec-violation-type', pLoc);
}
if(p.Name === EntityTypeName) {
const pLoc = [...loc, 'elements', p._edmAttributes.Name];
edmTypeCompatibilityCheck(p, pLoc);
if(p._edmAttributes.Name === EntityTypeName)
warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
error('odata-spec-violation-id', pLoc,
{ prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
}
if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
}
});

@@ -426,4 +445,6 @@

// CDXCORE-CDXCORE-173
if(options.isV2() && hasStream)
attributes['m:HasStream'] = hasStream;
if(options.isV2() && hasStream) {
attributes['m:HasStream'] = true;
assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
}

@@ -440,3 +461,3 @@ Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));

if(entityCsn['@odata.singleton.nullable'])
containerEntry.Nullable= true;
containerEntry._edmAttributes.Nullable= true;
}

@@ -464,10 +485,15 @@ else {

// add bound/unbound actions/functions for V4
function createActionV4(actionCsn, name, entityCsn=undefined)
function createActionV4(actionCsn, _name, entityCsn=undefined)
{
const iAmAnAction = actionCsn.kind === 'action';
const actionName = edmUtils.getBaseName(actionCsn.name);
const attributes = { Name: actionName, IsBound : false };
const loc = entityCsn
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
: [ 'definitions', actionCsn.name ];
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
message('odata-spec-violation-id', loc, { id: attributes.Name });
if(!iAmAnAction)

@@ -486,3 +512,3 @@ attributes.IsComposable = false;

{
actionNode.IsBound = true;
actionNode.setEdmAttribute('IsBound', true);
const bpType = fullQualified(entityCsn.name);

@@ -508,3 +534,3 @@ // Binding Parameter: 'in' at first position in sequence, this is decisive!

{
actionImport.EntitySet = edmUtils.getBaseName(rt);
actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
}

@@ -517,3 +543,9 @@ }

edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
actionNode.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn ));
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
if(!edmUtils.isODataSimpleIdentifier(parameterName))
message('odata-spec-violation-id', pLoc, { id: parameterName });
edmTypeCompatibilityCheck(p, pLoc);
actionNode.append(p);
});

@@ -524,5 +556,6 @@

actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);
// if binding type matches return type add attribute EntitySetPath
if(entityCsn != undefined && fullQualified(entityCsn.name) === actionNode._returnType._type) {
actionNode.EntitySetPath = bpName;
actionNode.setEdmAttribute('EntitySetPath', bpName);
}

@@ -537,3 +570,4 @@ }

/** @type {object} */
const functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
const attributes = { Name: name.replace(schemaNamePrefix, '') };
const functionImport = new Edm.FunctionImport(v, attributes );

@@ -551,3 +585,9 @@ // inserted now to maintain attribute order with old odata generator...

const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
const loc = entityCsn
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
: [ 'definitions', actionCsn.name ];
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
message('odata-spec-violation-id', loc, { id: attributes.Name });
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);

@@ -559,3 +599,3 @@ if(rt) // add EntitySet attribute only if return type is an entity

{
functionImport.EntitySet = rt.replace(schemaNamePrefix, '');
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
}

@@ -565,3 +605,3 @@ }

if(actionCsn.returns)
functionImport.ReturnType = getReturnType(actionCsn);
functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));

@@ -577,3 +617,4 @@ if(actionCsn.kind === 'function')

functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
functionImport.Name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport.Name;
const name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport._edmAttributes.Name;
functionImport.setEdmAttribute('Name', name);

@@ -600,10 +641,18 @@ // Binding Parameter: Primary Keys at first position in sequence, this is decisive!

edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
const paramLoc = [...actLoc, 'params', parameterName];
const pLoc = [...loc, 'params', parameterName];
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
}
if(param._isCollection) {
warning('odata-spec-violation-array', paramLoc, { api: 'OData V2' });
}
edmTypeCompatibilityCheck(param, pLoc);
if(!edmUtils.isODataSimpleIdentifier(parameterName))
message('odata-spec-violation-id', pLoc, { id: parameterName });
// only scalar or structured type in V2 (not entity)
if(param._type &&
!param._type.startsWith('Edm.') &&
csn.definitions[param._type] &&
!edmUtils.isStructuredType(csn.definitions[param._type]))
warning('odata-spec-violation-param', pLoc, { version: '2.0' });
if(param._isCollection)
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
functionImport.append(param);

@@ -618,15 +667,29 @@ });

// it is safe to assume that either type or items.type are set
const returnsLoc = [ ...loc, 'returns'];
const returns = action.returns.items || action.returns;
let type = returns.type;
if (type){
if (type) {
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
const returnsLoc = [ ...actLoc, 'returns'];
warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
}
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
else if(isBuiltinType(type)) {
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
if(type) {
const td = EdmPrimitiveTypeMap[type];
if(td && !td.v2) {
message('odata-spec-violation-type', returnsLoc,
{ type, version: '2.0', '#': 'incompatible' });
}
}
else {
message('odata-spec-violation-type-unknown', returnsLoc, { type });
}
}
if(action.returns._isCollection)
type = `Collection(${type})`
}
if(action.returns._isCollection)
type = `Collection(${type})`
else {
// type is missing
message('odata-spec-violation-type', returnsLoc);
}
return type;

@@ -639,5 +702,5 @@ }

* @param {object} edmParentCsn
* @returns {[object[], boolean]} Returns a [ [ Edm Properties ], boolean hasStream ]:
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
* array of Edm Properties
* boolean hasStream : true if at least one element has @Core.MediaType assignment
* hasStream : value of @Core.MediaType assignment
*/

@@ -648,2 +711,4 @@ function createProperties(elementsCsn, edmParentCsn=elementsCsn)

let hasStream = false;
const streamProps = [];
edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>

@@ -684,8 +749,8 @@ {

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'
hasStream = true;
info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
'$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
streamProps.push(elementName);

@@ -699,2 +764,12 @@ } else {

});
if(options.isV2()) {
if(streamProps.length > 1) {
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
`Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
}
else if(streamProps.length === 1) {
info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
}
}
return [ props, hasStream ];

@@ -714,20 +789,23 @@ }

if(properties.length === 0) {
warning(null, ['definitions', structuredTypeCsn.name], { name: structuredTypeCsn.name },
warning(null, loc, { name: structuredTypeCsn.name },
'EDM ComplexType $(NAME) has no properties');
}
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
message('odata-spec-violation-id', loc, { id: attributes.Name });
properties.forEach(p => {
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
if(!p[p._typeName]) {
message('odata-spec-violation-type', pLoc);
}
if(p.Name === complexType.Name) {
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
edmTypeCompatibilityCheck(p, pLoc);
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
}
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
if(options.isV2()) {
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
}
if(edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-assoc', pLoc, { api: 'OData V2' });
}
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
if(edmUtils.isAssociationOrComposition(p._csn))
warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
}

@@ -746,4 +824,8 @@ });

// derived types are already resolved to base types
const props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
Schema.append(typeDef);

@@ -770,3 +852,3 @@ }

let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
let assocName = plainAssocName;

@@ -779,3 +861,3 @@ let i = 1;

let fromRole = parentName;
let toRole = navigationProperty.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias

@@ -800,13 +882,10 @@ let fromEntityType = fromRole;

// add V2 attributes to navigationProperty
navigationProperty.Relationship = fullQualified(assocName);
navigationProperty.FromRole = fromRole;
navigationProperty.ToRole = toRole;
navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
navigationProperty.setEdmAttribute('FromRole', fromRole);
navigationProperty.setEdmAttribute('ToRole', toRole);
// remove V4 attributes
if(navigationProperty.Type != undefined)
delete navigationProperty.Type;
if(navigationProperty.Partner != undefined)
delete navigationProperty.Partner;
if(navigationProperty.ContainsTarget != undefined)
delete navigationProperty.ContainsTarget;
navigationProperty.removeEdmAttribute('Type');
navigationProperty.removeEdmAttribute('Partner');
navigationProperty.removeEdmAttribute('ContainsTarget');

@@ -837,3 +916,3 @@ /*

navigationProperty.Relationship = fullQualified(assocName)
navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));

@@ -897,3 +976,3 @@ reuseAssoc = !!forwardAssocCsn._NavigationProperty;

annos.forEach(anno => {
let targetSchema = whatsMySchemaName(anno.Target);
let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
// if no target schema has been found, it's a service annotation that applies to the service schema

@@ -904,3 +983,4 @@ if(targetSchema === undefined)

if(targetSchema !== serviceCsn.name) {
anno.Target = anno.Target.replace(serviceCsn.name + '.', '');
const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
anno.setEdmAttribute('Target', newTarget);
}

@@ -918,4 +998,42 @@ edm._service._schemas[targetSchema]._annotations.push(anno);

}
function edmTypeCompatibilityCheck(p, pLoc) {
const edmType = p._type;
if(!edmType) {
message('odata-spec-violation-type', pLoc);
}
else if(p._scalarType) {
const td = EdmPrimitiveTypeMap[edmType];
if(td) {
// The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
// Only the unknown type warning may be triggered by an unknown @odata.Type override.
if(td.v2 !== p.v2 && td.v4 !== p.v4)
message('odata-spec-violation-type', pLoc,
{ type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
EdmTypeFacetNames.forEach(name => {
const facet = EdmTypeFacetMap[name];
const optional =
(facet.optional !== undefined)
? (Array.isArray(facet.optional)
? facet.optional.includes(edmType)
: facet.optional)
: false;
// facet is not in attributes
// facet is member of type definition and mandatory
// node and facet version match
if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
message('odata-spec-violation-type', pLoc,
{ type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
}
});
}
else {
message('odata-spec-violation-type-unknown', pLoc,
{ type:edmType });
}
}
}
}
}
module.exports = { csn2edm, csn2edmAll };

@@ -1,3 +0,1 @@

// @ts-nocheck
'use strict'

@@ -7,7 +5,68 @@

const { isBuiltinType } = require('../model/csnUtils.js');
const { forEach } = require("../utils/objectUtils");
module.exports = function (options, error) {
// facet definitions, optional could either be true or array of edm types
// remove indicates wether or not the canonic facet shall be removed when applying @odata.Type
const EdmTypeFacetMap = {
'MaxLength': { v2: true, v4: true, remove: true, optional: true },
'Precision': { v2: true, v4: true, remove: true, optional: true },
'Scale': { v2: true, v4: true, remove: true, optional: true },
'SRID': { v4: true, remove: true, optional: true },
//'FixedLength': { v2: true },
//'Collation': { v2: true },
//'Unicode': { v2: true, v4: true },
};
const EdmTypeFacetNames = Object.keys(EdmTypeFacetMap);
// Merged primitive type map with descriptions taken from V4 spec and filled up with V2 spec
const EdmPrimitiveTypeMap = {
'Edm.Binary': { v2: true, v4: true, MaxLength: true, FixedLength: true, desc: 'Binary data' },
'Edm.Boolean': { v2: true, v4: true, desc: 'Binary-valued logic' },
'Edm.Byte': { v2: true, v4: true, desc: 'Unsigned 8-bit integer' },
'Edm.Date': { v4: true, desc: 'Date without a time-zone offset' },
'Edm.DateTime': { v2: true, Precision: true, desc: 'Date and time with values ranging from 12:00:00 midnight, January 1, 1753 A.D. through 11:59:59 P.M, December 31, 9999 A.D.' },
'Edm.DateTimeOffset': { v2: true, v4: true, Precision: true, desc: 'Date and time with a time-zone offset, no leap seconds' },
'Edm.Decimal': { v2: true, v4: true, Precision: true, Scale: true, desc: 'Numeric values with decimal representation' },
'Edm.Double': { v2: true, v4: true, desc: 'IEEE 754 binary64 floating-point number (15-17 decimal digits)' },
'Edm.Duration': { v4: true, Precision: true, desc: 'Signed duration in days, hours, minutes, and (sub)seconds' },
'Edm.Guid': { v2: true, v4: true, desc: '16-byte (128-bit) unique identifier' },
'Edm.Int16': { v2: true, v4: true, desc: 'Signed 16-bit integer' },
'Edm.Int32': { v2: true, v4: true, desc: 'Signed 32-bit integer' },
'Edm.Int64': { v2: true, v4: true, desc: 'Signed 64-bit integer' },
'Edm.SByte': { v2: true, v4: true, desc: 'Signed 8-bit integer' },
'Edm.Single': { v2: true, v4: true, desc: 'IEEE 754 binary32 floating-point number (6-9 decimal digits)' },
'Edm.Stream': { v4: true, MaxLength: true, desc: 'Binary data stream' },
'Edm.String': { v2: true, v4: true, MaxLength: true, FixedLength: true, Collation: true, Unicode: true, desc: 'Sequence of characters' },
'Edm.Time': { v2: true, Precision: true, desc: 'time of day with values ranging from 0:00:00.x to 23:59:59.y, where x and y depend upon the precision' },
'Edm.TimeOfDay': { v4: true, Precision: true, desc: 'Clock time 00:00-23:59:59.999999999999' },
'Edm.Geography': { v4: true, SRID: true, desc: 'Abstract base type for all Geography types' },
'Edm.GeographyPoint': { v4: true, SRID: true, desc: 'A point in a round-earth coordinate system' },
'Edm.GeographyLineString': { v4: true, SRID: true, desc: 'Line string in a round-earth coordinate system' },
'Edm.GeographyPolygon': { v4: true, SRID: true, desc: 'Polygon in a round-earth coordinate system' },
'Edm.GeographyMultiPoint': { v4: true, SRID: true, desc: 'Collection of points in a round-earth coordinate system' },
'Edm.GeographyMultiLineString': { v4: true, SRID: true, desc: 'Collection of line strings in a round-earth coordinate system' },
'Edm.GeographyMultiPolygon': { v4: true, SRID: true, desc: 'Collection of polygons in a round-earth coordinate system' },
'Edm.GeographyCollection': { v4: true, SRID: true, desc: 'Collection of arbitrary Geography values' },
'Edm.Geometry': { v4: true, SRID: true, desc: 'Abstract base type for all Geometry types' },
'Edm.GeometryPoint': { v4: true, SRID: true, desc: 'Point in a flat-earth coordinate system' },
'Edm.GeometryLineString': { v4: true, SRID: true, desc: 'Line string in a flat-earth coordinate system' },
'Edm.GeometryPolygon': { v4: true, SRID: true, descr: 'Polygon in a flat-earth coordinate system' },
'Edm.GeometryMultiPoint': { v4: true, SRID: true, desc: 'Collection of points in a flat-earth coordinate system' },
'Edm.GeometryMultiLineString': { v4: true, SRID: true, desc: 'Collection of line strings in a flat-earth coordinate system' },
'Edm.GeometryMultiPolygon': { v4: true, SRID: true, desc: 'Collection of polygons in a flat-earth coordinate system' },
'Edm.GeometryCollection': { v4: true, SRID: true, desc: 'Collection of arbitrary Geometry values' },
//'Edm.PrimitiveType': { v4: true, desc: 'Abstract meta type' },
//'Edm.Untyped': { v4: true, desc: 'Abstract void type' },
};
function getEdm(options, messageFunctions) {
const { error } = messageFunctions || { error: ()=>true, warning: ()=>true };
class Node
{
constructor(v, attributes=Object.create(null), csn=undefined)
/**
* @param {boolean[]} v Versions in the form of [<v2>, <v4>].
* @param {object} attributes
* @param {CSN.Model} csn
*/
constructor(v, attributes = Object.create(null), csn=undefined)
{

@@ -18,7 +77,16 @@ if(!attributes || typeof attributes !== 'object')

error(null, 'Please debug me: v is either undefined or not an array: ' + v);
if(v.filter(v=>v).length != 1)
if(v.filter(v => v).length !== 1)
error(null, 'Please debug me: exactly one version must be set');
Object.assign(this, attributes);
this.set({ _children: [], _xmlOnlyAttributes: Object.create(null), _jsonOnlyAttributes: Object.create(null), _v: v, _ignoreChildren: false });
// Common attributes of JSON and XML.
// Note: Can't assign attributes directly, due to the input object being modified.
// The caller re-uses the object for other nodes.
this._edmAttributes = Object.assign(Object.create(null), attributes);
this._xmlOnlyAttributes = Object.create(null);
this._jsonOnlyAttributes = Object.create(null);
this._children = [];
this._ignoreChildren = false;
this._v = v;
if(this.v2)

@@ -35,33 +103,40 @@ this.setSapVocabularyAsAttributes(csn);

// set's additional properties that are invisible for the iterators
set(attributes)
{
if(!attributes || typeof attributes !== 'object')
error(null, 'Please debug me: attributes must be a dictionary');
let newAttributes = Object.create(null);
edmUtils.forAll(attributes, (value, p) => {
newAttributes[p] = {
value,
configurable: true,
enumerable: false,
writable: true
}
});
return Object.defineProperties(this, newAttributes)
/**
* Set the EDM(X) attribute on the Node.
* @param {string} key
* @param {any} value
*/
setEdmAttribute(key, value) {
if(key !== undefined && key !== null && value !== undefined && value !== null)
this._edmAttributes[key] = value;
}
// set properties that should only appear in the XML representation
/**
* Remove the EDM(X) attribute on the Node.
* @param {string} name
*/
removeEdmAttribute(name) {
if (name in this._edmAttributes)
delete this._edmAttributes[name];
}
/**
* Set properties that should only appear in the XML representation
* @param {object} attributes
* @return {any}
*/
setXml(attributes)
{
if(!attributes || typeof attributes !== 'object')
error(null, 'Please debug me: attributes must be a dictionary');
return Object.assign(this._xmlOnlyAttributes, attributes);
}
// set properties that should only appear in the JSON representation
// today JSON attributes are not rendered in toJSONattributes()
/**
* Set properties that should only appear in the JSON representation.
* Today JSON attributes are not rendered in toJSONattributes()
*
* @param {object} attributes
* @return {any}
*/
setJSON(attributes)
{
if(!attributes || typeof attributes !== 'object')
error(null, 'Please debug me: attributes must be a dictionary');
return Object.assign(this._jsonOnlyAttributes, attributes);

@@ -73,3 +148,5 @@ }

this._children.splice(0, 0, ...children.filter(c => c));
return this;
}
append(...children)

@@ -85,5 +162,5 @@ {

{
let json = Object.create(null);
const json = Object.create(null);
// $kind Property MAY be omitted in JSON for performance reasons
if(![ 'Property', 'EntitySet', 'ActionImport', 'FunctionImport', 'Singleton', 'Schema' ].includes(this.kind))
if(!(this.kind in Node.noJsonKinds))
json['$Kind'] = this.kind;

@@ -99,3 +176,3 @@

{
edmUtils.forAll(this, (v,p) => {
forEach(this._edmAttributes, (p, v) => {
if (p !== 'Name')

@@ -110,5 +187,5 @@ json[p[0] === '@' ? p : '$' + p] = v;

{
// any child with a Name should be added by it's name into the JSON object
// any child with a Name should be added by its name into the JSON object
// all others must overload toJSONchildren()
this._children.filter(c => c.Name).forEach(c => json[c.Name] = c.toJSON());
this._children.filter(c => c._edmAttributes.Name).forEach(c => json[c._edmAttributes.Name] = c.toJSON());
}

@@ -122,5 +199,5 @@

if(kind=='Parameter' && this.Collection) {
delete this.Collection;
this.Type=`Collection(${this.Type})`;
if(kind==='Parameter' && this._edmAttributes.Collection) {
delete this._edmAttributes.Collection;
this._edmAttributes.Type=`Collection(${this._edmAttributes.Type})`;
}

@@ -146,8 +223,8 @@

let tmpStr = '';
edmUtils.forAll(this, (v, p) => {
if (typeof this[p] !== 'object')
forEach(this._edmAttributes, (p, v) => {
if (v !== undefined && typeof v !== 'object')
tmpStr += ' ' + p + '="' + edmUtils.escapeStringForAttributeValue(v) + '"'
});
edmUtils.forAll(this._xmlOnlyAttributes, (v,p) => {
if (typeof v !== 'object')
forEach(this._xmlOnlyAttributes, (p, v) => {
if (v !== undefined && typeof v !== 'object')
tmpStr += ' ' + p + '="' + edmUtils.escapeStringForAttributeValue(v) + '"'

@@ -182,2 +259,5 @@ });

// $kind Property MAY be omitted in JSON for performance reasons
Node.noJsonKinds = {'Property':1, 'EntitySet':1, 'ActionImport':1, 'FunctionImport':1, 'Singleton':1, 'Schema':1};
class Reference extends Node

@@ -189,3 +269,3 @@ {

if(this.v2)
this['xmlns:edmx'] = 'http://docs.oasis-open.org/odata/ns/edmx';
this._edmAttributes['xmlns:edmx'] = 'http://docs.oasis-open.org/odata/ns/edmx';
}

@@ -221,8 +301,8 @@

{
let props = Object.create(null);
props.Namespace = ns;
if(alias != undefined)
const props = { Namespace: ns };
if(alias !== undefined)
props.Alias = alias;
super(v, props);
this.set( { _annotations: annotations, _actions: Object.create(null) } );
this._annotations = annotations;
this._actions = Object.create(null);
this.setXml( { xmlns: (this.v2) ? 'http://schemas.microsoft.com/ado/2008/09/edm' : 'http://docs.oasis-open.org/odata/ns/edm' } );

@@ -242,3 +322,3 @@

// set as attribute for later access...
this.set({ _ec : ec })
this._ec = ec;
}

@@ -250,6 +330,6 @@ }

{
if(this._actions[action.Name])
this._actions[action.Name].push(action);
if(this._actions[action._edmAttributes.Name])
this._actions[action._edmAttributes.Name].push(action);
else
this._actions[action.Name] = [action];
this._actions[action._edmAttributes.Name] = [action];
}

@@ -266,3 +346,3 @@

let xml = '';
if(what=='metadata' || what=='all')
if(what==='metadata' || what==='all')
{

@@ -275,7 +355,7 @@ xml += super.innerXML(indent);

}
if(what=='annotations' || what=='all')
if(what==='annotations' || what==='all')
{
if(this._annotations.length > 0) {
this._annotations.filter(a => a.Term).forEach(a => xml += a.toXML(indent) + '\n');
this._annotations.filter(a => a.Target).forEach(a => xml += a.toXML(indent) + '\n');
this._annotations.filter(a => a._edmAttributes.Term).forEach(a => xml += a.toXML(indent) + '\n');
this._annotations.filter(a => a._edmAttributes.Target).forEach(a => xml += a.toXML(indent) + '\n');
}

@@ -289,3 +369,3 @@ }

{
edmUtils.forAll(this, (v,p) => {
edmUtils.forAll(this._edmAttributes, (v,p) => {
if (p !== 'Name' && p !== 'Namespace')

@@ -301,3 +381,3 @@ json[p[0] === '@' ? p : '$' + p] = v;

if(this._annotations.length > 0) {
this._annotations.filter(a => a.Term).forEach(a => {
this._annotations.filter(a => a._edmAttributes.Term).forEach(a => {
Object.entries(a.toJSON()).forEach(([n, v]) => {

@@ -308,3 +388,3 @@ json[n] = v;

let json_Annotations = Object.create(null);
this._annotations.filter(a => a.Target).forEach(a => json_Annotations[a.Target] = a.toJSON());
this._annotations.filter(a => a._edmAttributes.Target).forEach(a => json_Annotations[a._edmAttributes.Target] = a.toJSON());
if(Object.keys(json_Annotations).length)

@@ -330,3 +410,3 @@ json['$Annotations'] = json_Annotations;

super(v);
this.set( { _schemas: Object.create(null) } );
this._schemas = Object.create(null);

@@ -350,3 +430,3 @@ if(this.v2)

// 'edmx:DataServices' should not appear in JSON
this._children.forEach(s => json[s.Namespace] = s.toJSON());
this._children.forEach(s => json[s._edmAttributes.Namespace] = s.toJSON());
return json;

@@ -371,8 +451,11 @@ }

super(v, { Version : (v[1]) ? '4.0' : '1.0' });
this.set( { _service: service, _defaultRefs: [] } );
this._service = service;
this._defaultRefs = [];
let xmlProps = Object.create(null);
const xmlProps = Object.create(null);
if(this.v4)
{
xmlProps['xmlns:edmx'] = 'http://docs.oasis-open.org/odata/ns/edmx';
xmlProps['xmlns:edmx'] = 'http://docs.oasis-open.org/odata/ns/edmx';
xmlProps['xmlns:m'] = undefined;
xmlProps['xmlns:sap'] = undefined;
}

@@ -390,15 +473,2 @@ else

hasAnnotations()
{
let rc = false;
this._service._children.forEach(c =>
{ if(c._annotations.length > 0) rc = true; } )
return rc;
}
getSchemaCount()
{
return this._service._children.length;
}
getAnnotations(schemaIndex=0)

@@ -423,8 +493,8 @@ {

let json = Object.create(null);
json['$Version'] = this.Version;
json['$EntityContainer'] = schema.Namespace + '.' + schema._ec.Name;
json['$Version'] = this._edmAttributes.Version;
json['$EntityContainer'] = schema._edmAttributes.Namespace + '.' + schema._ec._edmAttributes.Name;
let reference_json = Object.create(null);
this._defaultRefs.forEach(r => reference_json[r.Uri] = r.toJSON());
this._children.forEach(r => reference_json[r.Uri] = r.toJSON());
this._defaultRefs.forEach(r => reference_json[r._edmAttributes.Uri] = r.toJSON());
this._children.forEach(r => reference_json[r._edmAttributes.Uri] = r.toJSON());

@@ -442,5 +512,3 @@ if(Object.keys(reference_json).length)

{
let rc = '<?xml version="1.0" encoding="utf-8"?>\n';
rc += `${super.toXML('', what)}`;
return rc;
return '<?xml version="1.0" encoding="utf-8"?>\n' + super.toXML('', what);
}

@@ -462,5 +530,5 @@

{
constructor() {
super(...arguments);
this.set( { _registry: Object.create(null) } );
constructor(v, attributes, csn) {
super(v, attributes, csn);
this._registry = Object.create(null);
}

@@ -472,8 +540,9 @@ // use the _SetAttributes

}
register(entry) {
if(!this._registry[entry.Name])
this._registry[entry.Name] = [entry];
if(!this._registry[entry._edmAttributes.Name])
this._registry[entry._edmAttributes.Name] = [entry];
else
this._registry[entry.Name].push(entry);
super.append(entry);
this._registry[entry._edmAttributes.Name].push(entry);
this.append(entry);
}

@@ -483,3 +552,2 @@ }

class Singleton extends Node

@@ -489,5 +557,4 @@ {

{
edmUtils.forAll(this, (v,p) => {
if (p !== 'Name')
{
forEach(this._edmAttributes, (p, v) => {
if (p !== 'Name') {
if(p === 'EntityType') // it's $Type in json

@@ -505,3 +572,3 @@ json['$Type'] = v;

let json_navPropBinding = Object.create(null);
this._children.forEach(npb => json_navPropBinding[npb.Path] = npb.Target);
this._children.forEach(npb => json_navPropBinding[npb._edmAttributes.Path] = npb._edmAttributes.Target);
if(Object.keys(json_navPropBinding).length > 0)

@@ -514,3 +581,3 @@ json['$NavigationPropertyBinding'] = json_navPropBinding;

getDuplicateMessage() {
return `EntityType "${this.EntityType}"`
return `EntityType "${this._edmAttributes.EntityType}"`
}

@@ -568,3 +635,3 @@ }

super(v, details);
this.set( { _returnType: undefined });
this._returnType = undefined;
}

@@ -575,3 +642,3 @@

let xml = super.innerXML(indent);
if(this._returnType != undefined)
if(this._returnType !== undefined)
xml += this._returnType.toXML(indent) + '\n';

@@ -609,3 +676,3 @@ return xml

getDuplicateMessage() {
return `Function "${this.Name}"`
return `Function "${this._edmAttributes.Name}"`
}

@@ -615,3 +682,3 @@ } //ActionFunctionBase {}

getDuplicateMessage() {
return `Action "${this.Name}"`
return `Action "${this._edmAttributes.Name}"`
}

@@ -624,10 +691,7 @@ }

{
if(!(csn instanceof Object || (typeof csn === 'object' && csn !== null)))
error(null, 'Please debug me: csn must be an object');
// ??? Is CSN still required? NavProp?
super(v, attributes, csn);
this.set({ _typeName: typeName });
if(this[typeName] == undefined)
this._typeName = typeName;
this._scalarType = undefined;
if(this._edmAttributes[typeName] === undefined)
{

@@ -639,12 +703,11 @@ let typecsn = csn.type ? csn : (csn.items && csn.items.type ? csn.items : csn);

// check whether this is a scalar type (or array of scalar type) or a named type
let scalarType = undefined;
if(typecsn.items && typecsn.items.type &&
isBuiltinType(typecsn.items.type)) {
scalarType = typecsn.items;
this._scalarType = typecsn.items;
}
else if(isBuiltinType(typecsn.type)) {
scalarType = typecsn;
this._scalarType = typecsn;
}
if(scalarType) {
this[typeName] = csn._edmType;
if(this._scalarType) {
this._edmAttributes[typeName] = csn._edmType;
// CDXCORE-CDXCORE-173 ignore type facets for Edm.Stream

@@ -655,34 +718,44 @@ // cds-compiler/issues/7835: Only set length for Binary as long as it is

// multi-byte characters.
if(!(this[typeName] === 'Edm.Stream' &&
![ /*'cds.String',*/ 'cds.Binary'].includes(scalarType.type)))
edmUtils.addTypeFacets(this, scalarType);
if(!(this._edmAttributes[typeName] === 'Edm.Stream' &&
!( /*scalarType.type === 'cds.String' ||*/ this._scalarType.type === 'cds.Binary')))
edmUtils.addTypeFacets(this, this._scalarType);
// CDXCORE-245:
// map type to @odata.Type
// optionally add @odata { MaxLength, Precision, Scale, SRID } but only in combination with @odata.Type
const odataType = csn['@odata.Type'];
if(odataType)
{
const td = EdmPrimitiveTypeMap[odataType];
// If type is known, it must be available in the current version
// Reason: EDMX Importer may set `@odata.Type: 'Edm.DateTime'` on imported V2 services
// Not filtering out this incompatible type here in case of a V4 rendering would
// produce an unrecoverable error.
if(td && (td.v2 === this.v2 || td.v4 === this.v4)) {
this.setEdmAttribute(typeName, odataType);
EdmTypeFacetNames.forEach(facet => {
if(EdmTypeFacetMap[facet].remove)
this.removeEdmAttribute(facet);
if(td[facet] !== undefined &&
(EdmTypeFacetMap[facet].v2 === this.v2 ||
EdmTypeFacetMap[facet].v4 === this.v4)
) {
this.setEdmAttribute(facet, csn['@odata.'+facet]);
}
});
}
}
}
else {
this[typeName] = typecsn.type;
this._edmAttributes[typeName] = typecsn.type;
}
// CDXCORE-245:
// map type to @odata.Type
// optionally add @odata.MaxLength but only in combination with @odata.Type
// In absence of checks restrict @odata.Type to 'Edm.String' and 'Edm.Int[16,32,64]'
let odataType = csn['@odata.Type'];
if(odataType === 'Edm.String')
{
this[typeName] = odataType;
if(csn['@odata.MaxLength']) {
this['MaxLength'] = csn['@odata.MaxLength'];
}
} else if(['Edm.Int16', 'Edm.Int32', 'Edm.Int64'].includes(odataType)) {
this[typeName] = odataType;
}
}
}
// Set the collection property if this is either an element or a parameter
if(csn.kind === undefined) {
this.set({ _isCollection: csn._isCollection });
}
this._isCollection = (csn.kind === undefined) ? csn._isCollection : false;
if(options.whatsMySchemaName && this[typeName]) {
let schemaName = options.whatsMySchemaName(this[typeName]);
if(options.whatsMySchemaName && this._edmAttributes[typeName]) {
let schemaName = options.whatsMySchemaName(this._edmAttributes[typeName]);
if(schemaName && schemaName !== options.serviceName) {
this[typeName] = this[typeName].replace(options.serviceName + '.', '');
this._edmAttributes[typeName] = this._edmAttributes[typeName].replace(options.serviceName + '.', '');
}

@@ -692,6 +765,6 @@ }

// store undecorated type for JSON
this.set( { _type : this[typeName] });
this._type = this._edmAttributes[typeName];
// decorate for XML (not for Complex/EntityType)
if(this._isCollection)
this[typeName] = `Collection(${this[typeName]})`
this._edmAttributes[typeName] = `Collection(${this._edmAttributes[typeName]})`
}

@@ -706,3 +779,3 @@

edmUtils.forAll(this, (v,p) => {
edmUtils.forAll(this._edmAttributes, (v,p) => {
if (p !== 'Name' && p !== this._typeName

@@ -764,3 +837,5 @@ // remove this line if Nullable=true becomes default

if(csn.$edmKeyPaths && csn.$edmKeyPaths.length)
this.set( { _keys: new Key(v, csn.$edmKeyPaths) } );
this._keys = new Key(v, csn.$edmKeyPaths);
else
this._keys = undefined;
}

@@ -832,3 +907,3 @@

{
json[this.Name] = this.Value;
json[this._edmAttributes.Name] = this._edmAttributes.Value;
return json;

@@ -843,3 +918,3 @@ }

super(v, attributes, csn);
this.set({ _csn: csn });
this._csn = csn;
if(this.v2)

@@ -852,3 +927,3 @@ {

// but not if Edm.DateTime is the result of a regular cds type mapping
if(this.Type === 'Edm.DateTime'
if(this._edmAttributes.Type === 'Edm.DateTime'
&& (typecsn.type !== 'cds.DateTime' && typecsn.type !== 'cds.Timestamp'))

@@ -865,3 +940,3 @@ this.setXml( { 'sap:display-format' : 'Date' } );

if(this._isCollection) {
this.Nullable = !this.isNotNullable();
this._edmAttributes.Nullable = !this.isNotNullable();
}

@@ -873,3 +948,3 @@ // Nullable=true is default, mention Nullable=false only in XML

{
this.Nullable = false;
this._edmAttributes.Nullable = false;
}

@@ -891,3 +966,3 @@ }

// mention all nullable elements explicitly, remove if Nullable=true becomes default
if(this.Nullable === undefined || this.Nullable === true)
if(this._edmAttributes.Nullable === undefined || this._edmAttributes.Nullable === true)
{

@@ -914,5 +989,2 @@ json['$Nullable'] = true;

this.toJSONattributes(json);
// !this._nullable if Nullable=true become default
if(this._nullable)
json['$Nullable'] = this._nullable;
return json;

@@ -926,14 +998,2 @@ }

{
// the annotations in this array shall become exposed as Property attributes in
// the V2 metadata.xml
// @ts-ignore
Property.SAP_Annotation_Attribute_WhiteList = [
'@sap.hierarchy.node.for', // -> sap:hierarchy-node-for
'@sap.hierarchy.parent.node.for', // -> sap:hierarchy-parent-node-for
'@sap.hierarchy.level.for', // -> sap:hierarchy-level-for
'@sap.hierarchy.drill.state.for', // -> sap:hierarchy-drill-state-for
'@sap.hierarchy.node.descendant.count.for', // -> sap:hierarchy-node-descendant-count-for
'@sap.parameter'
];
super(v, attributes, csn);

@@ -944,8 +1004,7 @@ // TIPHANACDS-4180

if(csn['@odata.etag'] == true || csn['@cds.etag'] == true)
this.ConcurrencyMode='Fixed'
this._edmAttributes.ConcurrencyMode='Fixed'
// translate the following @sap annos as xml attributes to the Property
edmUtils.forAll(csn, (v, p) => {
// @ts-ignore
if (Property.SAP_Annotation_Attribute_WhiteList.includes(p))
forEach(csn, (p, v) => {
if (p in Property.SAP_Annotation_Attributes)
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });

@@ -960,3 +1019,3 @@ });

let def = csn.default;
const def = csn.default;
// if def has a value, it's a simple value

@@ -982,3 +1041,3 @@ let defVal = def.val;

if(this.v4)
this[`Default${this.v4 ? 'Value' : ''}`] = defVal;
this._edmAttributes[`Default${this.v4 ? 'Value' : ''}`] = defVal;
}

@@ -992,2 +1051,13 @@ }

// the annotations in this array shall become exposed as Property attributes in
// the V2 metadata.xml
Property.SAP_Annotation_Attributes = {
'@sap.hierarchy.node.for':1, // -> sap:hierarchy-node-for
'@sap.hierarchy.parent.node.for':1, // -> sap:hierarchy-parent-node-for
'@sap.hierarchy.level.for':1, // -> sap:hierarchy-level-for
'@sap.hierarchy.drill.state.for':1, // -> sap:hierarchy-drill-state-for
'@sap.hierarchy.node.descendant.count.for':1, // -> sap:hierarchy-node-descendant-count-for
'@sap.parameter':1
};
class PropertyRef extends Node

@@ -1000,3 +1070,3 @@ {

toJSON() {
return this.Alias ? { [this.Alias]:this.Name } : this.Name;
return this._edmAttributes.Alias ? { [this._edmAttributes.Alias]:this._edmAttributes.Name } : this._edmAttributes.Name;
}

@@ -1012,3 +1082,3 @@ }

if(mode != null)
this.Mode = mode;
this._edmAttributes.Mode = mode;

@@ -1018,3 +1088,3 @@ // V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true

// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
if(this.v2 && this.Nullable === undefined)
if(this.v2 && this._edmAttributes.Nullable === undefined)
this.setXml({Nullable: true});

@@ -1027,3 +1097,3 @@ }

let json = Object.create(null);
json['$Name'] = this.Name;
json['$Name'] = this._edmAttributes.Name;
return this.toJSONattributes(json);

@@ -1044,13 +1114,14 @@ }

this.set( {
_type: attributes.Type,
_isCollection: this.isToMany(),
_targetCsn: csn._target
} );
this._type = attributes.Type;
this._isCollection = this.isToMany();
this._targetCsn = csn._target;
if (this.v4)
{
if(options.isStructFormat && this._csn.key)
this._edmAttributes.Nullable = false;
// either csn has multiplicity or we have to use the multiplicity of the backlink
if(this._isCollection) {
this.Type = `Collection(${attributes.Type})`
this._edmAttributes.Type = `Collection(${attributes.Type})`
// attribute Nullable is not allowed in combination with Collection (see Spec)

@@ -1060,9 +1131,14 @@ // Even if min cardinality is > 0, remove Nullable, because the implicit OData contract

// values are !null (with other words: a collection must never return [1,2,null,3])
delete this.Nullable;
delete this._edmAttributes.Nullable;
}
// we have exactly one selfReference or the default partner
let partner = (!csn.$noPartner && csn._selfReferences.length === 1) ? csn._selfReferences[0] : csn._constraints._partnerCsn;
let partner =
!csn.$noPartner ?
csn._selfReferences.length === 1
? csn._selfReferences[0]
: csn._constraints._partnerCsn
: undefined;
if(partner && partner['@odata.navigable'] !== false) {
// $abspath[0] is main entity
this.Partner = partner.$abspath.slice(1).join(options.pathDelimiter);
this._edmAttributes.Partner = partner.$abspath.slice(1).join(options.pathDelimiter);
}

@@ -1080,5 +1156,5 @@

if(csn['@odata.contained'] == true || csn.containsTarget) {
this.ContainsTarget = true;
this._edmAttributes.ContainsTarget = true;
}
if(this.ContainsTarget === undefined && csn.type === 'cds.Composition') {
if(this._edmAttributes.ContainsTarget === undefined && csn.type === 'cds.Composition') {
// Delete is redundant in containment

@@ -1094,3 +1170,3 @@ // TODO: to be specified via @sap.on.delete

// store Nullable=false and evaluate in determineMultiplicity()
delete this.Nullable;
delete this._edmAttributes.Nullable;
}

@@ -1139,6 +1215,6 @@

// collect ref constraints in dictionary
json_constraints[c.Property] = c.ReferencedProperty;
json_constraints[c._edmAttributes.Property] = c._edmAttributes.ReferencedProperty;
break;
case 'OnDelete':
json['$OnDelete'] = c.Action;
json['$OnDelete'] = c._edmAttributes.Action;
break;

@@ -1174,2 +1250,8 @@ default:

{
constructor(v, attributes, csn) {
super(v, attributes, csn);
this._d = null;
this._p = null;
}
innerXML(indent)

@@ -1206,8 +1288,8 @@ {

const inlineConstExpr =
[ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
[ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', /*'Edm.Stream',*/ 'Edm.String', 'Edm.TimeOfDay',
/* UI.xml: defines Annotations with generic type 'Edm.PrimitiveType' */
'Edm.PrimitiveType', 'Bool'
// Official JSON V4.01 Spec defines these paths as constant inline expression (OKRA requires them as explicit exprs):
// 'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
'Edm.PrimitiveType', 'Bool',
// Official JSON V4.01 Spec defines these paths as constant inline expression:
'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
];

@@ -1223,16 +1305,9 @@

/* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project:
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
*/
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
*/
case 'Edm.Boolean':
v = (v=='true'?true:(v=='false'?false:v));
// eslint-no-fallthrough
case 'Edm.String':
// eslint-no-fallthrough
case 'Edm.Float':
// eslint-no-fallthrough
default:
return v;
default:
// OKRA requires this for JSON->XML mapping
// because they didn't want to lookup the type in the vocabulary
return { '$Cast': v, '$Type': inline[0] };
}

@@ -1267,4 +1342,3 @@ }

super(v, { Target: target });
if (this.v2)
this.setXml( { xmlns : 'http://docs.oasis-open.org/odata/ns/edm' } );
this.setXml( { xmlns : this.v2 ? 'http://docs.oasis-open.org/odata/ns/edm' : undefined } );
}

@@ -1274,3 +1348,3 @@

{
edmUtils.forAll(this, (v,p) => {
forEach(this._edmAttributes, (p, v) => {
if (p !== 'Target')

@@ -1324,3 +1398,3 @@ json[p[0] === '@' ? p : '$' + p] = v;

getJsonFQTermName() {
return '@' + this.Term + (this.Qualifier ? '#' + this.Qualifier : '');
return '@' + this._edmAttributes.Term + (this._edmAttributes.Qualifier ? '#' + this._edmAttributes.Qualifier : '');
}

@@ -1342,7 +1416,7 @@ }

{
if(this.Type)
json['@type'] = this.Type;
let keys = Object.keys(this).filter(k => k !== 'Type');
if(this._edmAttributes.Type)
json['@type'] = this._edmAttributes.Type;
let keys = Object.keys(this._edmAttributes).filter(k => k !== 'Type');
for(const key of keys)
json['$'+key] = this[key];
json['$'+key] = this._edmAttributes[key];
}

@@ -1367,3 +1441,3 @@

// render property as const expr (or subnode)
json[c.Property] = c.toJSON();
json[c._edmAttributes.Property] = c.toJSON();
break;

@@ -1383,3 +1457,3 @@ }

super(v);
this.Property = property;
this._edmAttributes.Property = property;
}

@@ -1398,3 +1472,3 @@

mergeJSONannotations() {
return super.mergeJSONAnnotations(this.Property);
return super.mergeJSONAnnotations(this._edmAttributes.Property);
}

@@ -1408,10 +1482,6 @@ }

super(v, details);
this.setKind(kind);
this._kind = kind;
}
setKind(kind)
{
Object.defineProperty(this, 'kind',
{ get: function() { return kind; }});
}
get kind() { return this._kind; }
}

@@ -1424,3 +1494,3 @@

super(v, kind, undefined);
this.set( { _value : value });
this._value = value;
}

@@ -1487,6 +1557,7 @@

toXMLattributes() {
// TODO: Why json?
if(this._jsonOnlyAttributes['Collection'])
return ` Type="Collection(${this.Type})"`
return ` Type="Collection(${this._edmAttributes.Type})"`
else
return ` Type="${this.Type}"`
return ` Type="${this._edmAttributes.Type}"`
}

@@ -1530,3 +1601,3 @@ toJSON() {

{
edmUtils.forAll(this, (v,p) => {
forEach(this._edmAttributes, (p, v) => {
json[p[0] === '@' ? p : '$' + p] = v;

@@ -1560,6 +1631,6 @@ });

super(v, details);
this.set( { _end: [] });
this._end.push(
this._end = [
new End(v, { Role: fromRole[0], Type: fromRole[1], Multiplicity: multiplicity[0] } ),
new End(v, { Role: toRole[0], Type: toRole[1], Multiplicity: multiplicity[1] } ) );
new End(v, { Role: toRole[0], Type: toRole[1], Multiplicity: multiplicity[1] } )
];

@@ -1595,3 +1666,3 @@ // set Delete:Cascade on composition end

getDuplicateMessage() {
return `Association "${this.Association}"`
return `Association "${this._edmAttributes.Association}"`
}

@@ -1607,4 +1678,4 @@ }

let node = new ReferentialConstraint(v, {});
node.set({ _d: new Dependent(v, { Role: from } ) });
node.set({ _p: new Principal(v, { Role: to } ) });
node._d = new Dependent(v, { Role: from } );
node._p = new Principal(v, { Role: to } );

@@ -1673,1 +1744,3 @@ edmUtils.forAll(c, cv => {

} // instance function
module.exports = { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm };

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

info('odata-spec-violation-constraints',
['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { api: 'OData V2' });
['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { version: '2.0' });
}

@@ -443,3 +443,3 @@ else {

info('odata-spec-violation-constraints',
['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { api: 'OData V2' } );
['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { version: '2.0' } );
}

@@ -472,3 +472,3 @@ else {

(!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
!['cds.Association', 'cds.Composition'].includes(elt.type) &&
!(elt.type === 'cds.Association' || elt.type === 'cds.Composition') &&
isEdmPropertyRendered(elt, options));

@@ -528,8 +528,10 @@ }

function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false)
function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, location=undefined)
{
if(location === undefined)
location = csn.$path;
const { error } = messageFunctions || { error: ()=>true };
let cdsType = csn.type;
if(cdsType === undefined) {
error(null, csn.$location, `no type found`);
error(null, location, `no type found`);
return '<NOTYPE>';

@@ -589,3 +591,3 @@ }

if (edmType == undefined) {
error(null, csn.$path, { type: cdsType }, `No EDM type available for $(TYPE)`);
error(null, location, { type: cdsType }, `No EDM type available for $(TYPE)`);
}

@@ -598,5 +600,2 @@ if(isV2)

edmType = 'Edm.Time';
if(['cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY'].includes(cdsType)) {
error(null, csn.$path, { type: cdsType }, `OData V2 does not support Geometry data types, $(TYPE) can't be mapped`);
}
}

@@ -615,21 +614,22 @@ else // isV4

const isV2 = node.v2;
const decimalTypes = {'cds.Decimal':1, 'cds.DecimalFloat':1, 'cds.hana.SMALLDECIMAL':1};
if (csn.length != null)
node.MaxLength = csn.length;
node.setEdmAttribute('MaxLength', csn.length);
if (csn.scale !== undefined)
node.Scale = csn.scale;
node.setEdmAttribute('Scale', csn.scale);
// else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
// node.Scale = 'floating';
// node._edmAttributes.Scale = 'floating';
if (csn.precision != null)
node.Precision = csn.precision;
node.setEdmAttribute('Precision', csn.precision);
// else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
// node.Precision = 16;
else if (csn.type === 'cds.Timestamp' && node.Type === 'Edm.DateTimeOffset')
node.Precision = 7;
if([ 'cds.Decimal', 'cds.DecimalFloat', 'cds.hana.SMALLDECIMAL' ].includes(csn.type)) {
else if (csn.type === 'cds.Timestamp' && node._edmAttributes.Type === 'Edm.DateTimeOffset')
node.setEdmAttribute('Precision', 7);
if(csn.type in decimalTypes) {
if(isV2) {
// no prec/scale or scale is 'floating'/'variable'
if(!(csn.precision || csn.scale) || ['floating', 'variable'].includes(csn.scale)) {
if(!(csn.precision || csn.scale) || (csn.scale === 'floating' || csn.scale === 'variable')) {
node.setXml( { 'sap:variable-scale': true } );
delete node.Scale;
node.removeEdmAttribute('Scale');
}

@@ -639,4 +639,4 @@ }

// map both floating and variable to => variable
if(node.Scale === 'floating')
node.Scale = 'variable';
if(node._edmAttributes.Scale === 'floating')
node.setEdmAttribute('Scale', 'variable');
if(!csn.precision && !csn.scale)

@@ -649,5 +649,5 @@ // if Decimal has no p, s set scale 'variable'

if(csn.unicode)
node.Unicode = csn.unicode;
node.setEdmAttribute('Unicode', csn.unicode);
if(csn.srid)
node.SRID = csn.srid;
node.setEdmAttribute('SRID', csn.srid);
}

@@ -654,0 +654,0 @@

@@ -18,14 +18,13 @@ // csn version functions

// Use literal version constants intentionally and not number intervals to
// Use literal version constants intentionally and not number intervals to
// record all published version strings of the core compiler.
const newCSNVersions = ["0.1.99","0.2","0.2.0","1.0","2.0"];
const newCSNVersions = [ '0.1.99', '0.2', '0.2.0', '1.0', '2.0' ];
// checks if new-csn is requested via the options of already specified in the CSN
// default: old-style
function isNewCSN(csn, options) {
if( (options && options.newCsn === false) ||
if ( (options && options.newCsn === false) ||
(csn.version && !newCSNVersions.includes(csn.version.csn)) ||
(csn.$version && !newCSNVersions.includes(csn.$version)))
{
return false;
}
return true;

@@ -38,10 +37,10 @@ }

const { makeMessageFunction } = require('../base/messages');
const { error, throwWithError } = makeMessageFunction(csn, options);
const { error, throwWithAnyError } = makeMessageFunction(csn, options);
let errStr = 'CSN Version not supported, version tag: "';
errStr += (csn.version && csn.version.csn ? csn.version.csn : (csn.$version ? csn.$version : 'not available')) + '"';
errStr += (options.newCsn !== undefined) ? ', options.newCsn: ' + options.newCsn : '';
errStr += `${ csn.version && csn.version.csn ? csn.version.csn : (csn.$version ? csn.$version : 'not available') }"`;
errStr += (options.newCsn !== undefined) ? `, options.newCsn: ${ options.newCsn }` : '';
error(null, null, errStr);
throwWithError();
throwWithAnyError();
}

@@ -52,3 +51,3 @@ }

isNewCSN,
checkCSNVersion
}
checkCSNVersion,
};

@@ -155,3 +155,4 @@ // CSN frontend - transform CSN into XSN

validKinds: [], // pseudo kind '$column'
requires: [ 'ref', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
// A column with only as+cast.type is a new association
requires: [ 'ref', 'as', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
schema: {

@@ -201,3 +202,3 @@ xpr: {

validKinds: [ 'enum' ],
inKind: [ 'element', 'type', 'param', 'annotation', 'annotate' ],
inKind: [ 'element', 'type', 'param', 'annotation', 'annotate', 'extend' ],
},

@@ -204,0 +205,0 @@ elements: {

@@ -407,4 +407,2 @@ // Transform XSN (augmented CSN) into CSN

*/
function extensions( node, csn, model ) {

@@ -411,0 +409,0 @@ if (model.kind && model.kind !== 'source')

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

// First line, i.e. header, is always trimmed from left.
lines[0] = lines[0].trimLeft();
lines[0] = lines[0].trimStart();

@@ -47,3 +47,3 @@ // If the second line starts with an asterisk then remove it.

else
lines[1] = lines[1].trimLeft();
lines[1] = lines[1].trimStart();
}

@@ -50,0 +50,0 @@ else {

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

handleComposition,
associationInSelectItem,
reportExpandInline,
checkTypeFacet,
notSupportedYet,

@@ -598,7 +600,7 @@ csnParseOnly,

}
const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
if (!Number.isSafeInteger(num)) {
if (sign == null) {
this.error( 'syntax-no-integer', token, {},
'An integer number is expected here' );
this.error( 'syntax-expected-integer', token, { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe'} );
}

@@ -907,2 +909,27 @@ else if (text !== `${num}`) {

function associationInSelectItem( art ) {
const isPath = art.value.path && art.value.path.length
const isIdentifier = isPath && art.value.path.length === 1;
if (isIdentifier) {
if (!art.name) {
art.name = art.value.path[0];
} else {
// Use alias if provided, i.e. ignore art.value.path.
this.error( 'query-unexpected-alias', art.name.location, {},
'Unexpected alias for association' );
}
delete art.value;
} else {
const loc = isPath ? art.value.path[1].location : art.value.location;
// If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
if (isPath || art.name) {
this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
if (isPath) {
art.name = art.value.path[art.value.path.length - 1];
}
delete art.value;
}
}
}
function reportExpandInline( clauseName ) {

@@ -919,4 +946,22 @@ let token = this.getCurrentToken();

function checkTypeFacet( art, argIdent ) {
const id = argIdent.id;
if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
if (art[id] !== undefined) {
this.error( 'syntax-duplicate-argument', argIdent.location,
{ '#': 'duplicate', code: id } );
this.error( 'syntax-duplicate-argument', art[id].location,
{ '#': 'duplicate', code: id } );
}
return true;
} else {
this.error( 'syntax-duplicate-argument', argIdent.location,
{ '#': 'unknown', code: id } );
return false;
}
}
module.exports = {
genericAntlrParser: GenericAntlrParser,
};

@@ -85,2 +85,7 @@ // Official cds-compiler API.

/**
* Where to find `@sap/cds/` packages. This string, if set, is used as
* the prefix for SAP CDS packages / CDS files.
*/
cdsHome?: string
/**
* Option for {@link compileSources}. If set, all objects inside the

@@ -899,5 +904,18 @@ * provided sources dictionary are interpreted as XSN structures instead

*/
export function traverseCsn(userFunctions: Record<string, Function>, csn: object|any[]);
export function traverseCsn(userFunctions: Record<string, Function>, csn: object|any[]): void;
/**
* CSN Model related functions.
*/
export namespace model {
/**
* Returns true if the given definition name is in a reserved namespace such as `cds.*`
* but not `cds.foundation.*`.
*
* @param definitionName Top-level definition name of the artifact.
*/
function isInReservedNamespace(definitionName: string): boolean;
}
/**
* @private

@@ -904,0 +922,0 @@ */

@@ -28,2 +28,3 @@ // Main entry point for the CDS Compiler

const define = require('./compiler/define');
const { isInReservedNamespace } = require("./compiler/builtins");
const finalizeParseCdl = require('./compiler/finalize-parse-cdl');

@@ -152,2 +153,7 @@

$lsp: { parse: parseX, compile: compileX, getArtifactName: a => a.name },
// CSN Model related functionality
model: {
isInReservedNamespace,
},
};

@@ -445,3 +445,7 @@ // CSN functionality for resolving references

const { _select } = qcache;
traverseType( _select, main, null, null, initNode ); // also inits elements
const { elements } = _select;
if (elements) {
for (const n of Object.keys( elements ))
traverseDef( elements[n], _select, 'element', n, initNode );
}
if (_select.mixin) {

@@ -457,4 +461,10 @@ for (const n of Object.keys( _select.mixin ))

setCache( art, '_parent', parent );
if (art.type || !kind || kind === 'target') // with type, top-level, query or mixin
if (kind === 'target') {
// Prevent re-initialization of anonymous aspect with initDefinition():
// (that would be with parent: null which would be wrong)
setCache( art, '$queries', null );
return;
}
if (art.type || !kind) // with type, top-level, query or mixin
return;
const { $origin } = art;

@@ -552,4 +562,15 @@ if (typeof $origin === 'object') // null, […], {…}

return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
if (!query) // outside queries - TODO: items?
return resolvePath( path, parent.elements[head], parent, 'parent' );
if (!query) { // outside queries - TODO: items?
let art = parent.elements[head];
// Ref to up_ in anonymous aspect
if (!art && head === 'up_') {
const up = getCache( parent, '_parent' );
const target = up && typeof up.target === 'string' && csn.definitions[up.target];
if (target && target.elements) {
initDefinition( target );
art = target.elements.up_;
}
}
return resolvePath( path, art, parent, 'parent' );
}

@@ -709,2 +730,7 @@ if (semantics.dynamic === 'query')

}
// TODO: we might keep the following with --test-mode
// if (hidden[prop] !== undefined) {
// console.log('RS:',prop,hidden[prop],val,obj)
// throw Error('RESET')
// }
hidden[prop] = val;

@@ -825,3 +851,3 @@ return val;

traverseType( node.returns, node, 'returns', true, callback );
traverseType( node, parent, kind, name, callback );
traverseType( node, true, kind, name, callback );
if (node.actions) {

@@ -834,3 +860,4 @@ for (const n of Object.keys( node.actions ))

function traverseType( node, parent, kind, name, callback ) {
callback ( node, parent, kind, name );
if (parent !== true)
callback ( node, parent, kind, name );
const target = targetAspect( node );

@@ -837,0 +864,0 @@ if (target && typeof target === 'object' && target.elements) {

@@ -450,9 +450,9 @@ 'use strict';

*
* @param {any} node Node to transform
* @param {any} rootNode Node to transform
* @param {any} transformers Object defining transformer functions
* @returns {object}
*/
function cloneWithTransformations(node, transformers) {
function cloneWithTransformations(rootNode, transformers) {
return transformNode(node);
return transformNode(rootNode);

@@ -592,3 +592,3 @@ // This general transformation function will be applied to each node recursively

*/
function cloneCsn(csn, options) {
function cloneCsnNonDict(csn, options) {
return sortCsn(csn, options);

@@ -601,3 +601,3 @@ }

* This function does _not_ sort the given dictionary.
* See cloneCsn() if you want sorted definitions.
* See cloneCsnNonDict() if you want sorted definitions.
*

@@ -638,4 +638,6 @@ * @param {object} csn

* @param {object} iterateOptions can be used to skip certain kinds from being iterated
* @param constructCallback
*/
function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) {
function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
constructCallback = (_construct, _prop, _path) => {}) {
// Allow processing _ignored elements if requested

@@ -648,4 +650,3 @@ if (ignoreIgnore && construct._ignore) {

if (construct.items) {
// TODO: Should we go to the deepest items.items?
forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions );
forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions, constructCallback );
}

@@ -658,3 +659,3 @@

if (construct.returns && !iterateOptions.elementsOnly) {
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions, constructCallback );
}

@@ -664,6 +665,37 @@

const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'actions', 'params']);
propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));
propsWithMembers.forEach((prop) => {
forEachGeneric( construct, prop, callback, path, iterateOptions );
if (construct[prop]) {
if (Array.isArray(constructCallback))
constructCallback.forEach(cb => cb(construct, prop, path));
else
constructCallback(construct, prop, path);
}
});
}
/**
* Call `forEachMember` and then apply `forEachMember` on queries.
*
* @param {CSN.Artifact} construct
* @param {genericCallback|genericCallback[]} callback
* @param {CSN.Path} [path]
* @param {boolean} [ignoreIgnore]
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
* @param {constructCallback|constructCallback[]} callback
*/
function forEachMemberWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
constructCallback = (_construct, _prop, _path) => {}) {
forEachMember(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
if (construct.query) {
forAllQueries(construct.query, (q, p) => {
const s = q.SELECT;
if(s) {
forEachMember(s, callback, p, ignoreIgnore, iterateOptions);
}
}, [ ...path, 'query' ]);
}
}
/**
* Apply function `callback(member, memberName)` to each member in `construct`,

@@ -677,15 +709,42 @@ * recursively (i.e. also for sub-elements of elements).

* @param {object} iterateOptions can be used to skip certain kinds from being iterated
* @param {constructCallback|constructCallback[]} callback
*/
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) {
forEachMember( construct, ( member, memberName, prop, subpath ) => {
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
constructCallback = (_construct, _prop, _path) => {}) {
forEachMember( construct, ( member, memberName, prop, subpath, parent ) => {
if(Array.isArray(callback))
callback.forEach(cb => cb( member, memberName, prop, subpath, construct ));
callback.forEach(cb => cb( member, memberName, prop, subpath, parent ));
else
callback( member, memberName, prop, subpath, construct );
callback( member, memberName, prop, subpath, parent );
// Descend into nested members, too
forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions);
}, path, ignoreIgnore, iterateOptions);
forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions, constructCallback);
}, path, ignoreIgnore, iterateOptions, constructCallback);
}
/**
* Apply function `callback(member, memberName)` to each member in `construct`,
* recursively (i.e. also for sub-elements of elements).
* Recursively iterate over elements of `construct` query.
*
* @param {CSN.Artifact} construct
* @param {genericCallback|genericCallback[]} callback
* @param {CSN.Path} [path]
* @param {boolean} [ignoreIgnore]
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
* @param {constructCallback|constructCallback[]} callback
*/
function forEachMemberRecursivelyWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
constructCallback = (_construct, _prop, _path) => {}) {
forEachMemberRecursively(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
if(construct.query) {
forAllQueries(construct.query, (q, p) => {
const s = q.SELECT;
if(s) {
forEachMemberRecursively(s, callback, p, ignoreIgnore, iterateOptions);
}
}, [ ...path, 'query' ]);
}
}
/**
* Apply function `callback` to all objects in dictionary `dict`, including all

@@ -696,3 +755,3 @@ * duplicates (found under the same name). Function `callback` is called with

*
* @param {object} obj
* @param {object} construct
* @param {string} prop

@@ -703,4 +762,4 @@ * @param {genericCallback|genericCallback[]} callback

*/
function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) {
const dict = obj[prop];
function forEachGeneric( construct, prop, callback, path = [], iterateOptions = {}) {
const dict = construct[prop];
for (const name in dict) {

@@ -714,9 +773,9 @@ if (!Object.prototype.hasOwnProperty.call(dict, name))

continue;
cb( dictObj, name );
executeCallbacks( dictObj, name );
}
function cb(o, name ) {
function executeCallbacks(o, name ) {
if (Array.isArray(callback))
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
else
callback( o, name, prop, path.concat([prop, name]))
callback( o, name, prop, path.concat([prop, name]), construct )
}

@@ -728,9 +787,9 @@ }

sources.forEach(source => {
let descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
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 => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {

@@ -746,37 +805,37 @@ descriptors[sym] = descriptor;

/**
* @param {CSN.Query} query
* @param {queryCallback|queryCallback[]} callback
* @param {CSN.Query} mainQuery
* @param {queryCallback|queryCallback[]} queryCallback
* @param {CSN.Path} path
*/
function forAllQueries(query, callback, path = []){
return traverseQuery(query, callback, path);
function traverseQuery( q, callback, p ) {
if (q.SELECT) {
function forAllQueries(mainQuery, queryCallback, path = []){
return traverseQuery(mainQuery, queryCallback, path);
function traverseQuery( query, callback, queryPath ) {
if (query.SELECT) {
// The projection is turned into a normalized query - there
// is no real SELECT, it is fake
if(!(path.length === 3 && path[2] === 'projection'))
p.push('SELECT');
cb( q, p );
q = q.SELECT;
queryPath.push('SELECT');
executeCallbacks();
query = query.SELECT;
}
else if (q.SET) {
p.push('SET');
cb( q, p );
q = q.SET;
else if (query.SET) {
queryPath.push('SET');
executeCallbacks();
query = query.SET;
}
if (q.from)
traverseFrom( q.from, callback, p.concat(['from']) );
if (query.from)
traverseFrom( query.from, callback, queryPath.concat(['from']) );
for (const prop of ['args', 'xpr', 'columns', 'where', 'having']) {
// all properties which could have sub queries (directly or indirectly)
const expr = q[prop];
const expr = query[prop];
if (expr && typeof expr === 'object') {
if(Array.isArray(expr)){
for(let i = 0; i < expr.length; i++){
traverseQuery(expr[i], callback, p.concat([prop, i]));
traverseQuery(expr[i], callback, queryPath.concat([prop, i]));
}
} else {
for(const argName of Object.keys( expr )){
traverseQuery(expr[argName], callback, p.concat([prop, argName]))
traverseQuery(expr[argName], callback, queryPath.concat([prop, argName]))
}

@@ -786,7 +845,7 @@ }

}
function cb(q, p) {
function executeCallbacks() {
if(Array.isArray(callback))
callback.forEach(cb => cb( q, p ));
callback.forEach(cb => cb( query, queryPath ));
else
callback( q, p );
callback( query, queryPath );
}

@@ -798,5 +857,5 @@ }

* @param {Function} callback
* @param {CSN.Path} path
* @param {CSN.Path} csnPath
*/
function traverseFrom( from, callback, path = [] ) {
function traverseFrom( from, callback, csnPath = [] ) {
if (from.ref) // ignore

@@ -806,7 +865,7 @@ return;

for(let i = 0; i < from.args.length; i++){
traverseFrom(from.args[i], callback, path.concat(['args', i]));
traverseFrom(from.args[i], callback, csnPath.concat(['args', i]));
}
}
else
traverseQuery( from, callback, path ); // sub query in FROM
traverseQuery( from, callback, csnPath ); // sub query in FROM
}

@@ -986,3 +1045,3 @@ }

} else if(art && art.kind === 'service') {
// inside services, we immediatly turn . into _
// inside services, we immediately turn . into _
const prefix = parts.slice(0, i).join('.');

@@ -1159,13 +1218,13 @@ const suffix = parts.slice(i).join('_');

function mergeTwo(left, right, name) {
let result;
let intermediateResult;
// Copy left as far as required
if (Array.isArray(left)) {
// Shallow-copy left array
result = left.slice();
intermediateResult = left.slice();
} else if (isObject(left)) {
// Deep-copy left object (unless empty)
result = Object.keys(left).length ? mergeTwo({}, left, name) : {};
intermediateResult = Object.keys(left).length ? mergeTwo({}, left, name) : {};
} else {
// Just use left scalar
result = left;
intermediateResult = left;
}

@@ -1183,17 +1242,17 @@ // Check against improper overwriting

// Shallow-copy right array
result = right.slice();
intermediateResult = right.slice();
} else if (isObject(right)) {
// Object overwrites undefined, scalars and arrays
if (result === undefined || isScalar(result) || Array.isArray(result)) {
result = {};
if (intermediateResult === undefined || isScalar(intermediateResult) || Array.isArray(intermediateResult)) {
intermediateResult = {};
}
// Deep-copy right object into result
for (let key of Object.keys(right)) {
result[key] = mergeTwo(result[key], right[key], `${name}.${key}`);
intermediateResult[key] = mergeTwo(intermediateResult[key], right[key], `${name}.${key}`);
}
} else {
// Right scalar wins (unless undefined)
result = (right !== undefined) ? right : result;
intermediateResult = (right !== undefined) ? right : intermediateResult;
}
return result;
return intermediateResult;
}

@@ -1286,15 +1345,47 @@

// Copy all annotations from 'fromNode' to 'toNode'. Overwrite existing ones only if 'overwrite' is true
function copyAnnotations(fromNode, toNode, overwrite=false) {
/**
* Copy all annotations from 'fromNode' to 'toNode'.
*
* Overwrite existing ones only if 'overwrite' is true.
*
* @param {object} fromNode
* @param {object} toNode
* @param {boolean} [overwrite]
*/
function copyAnnotations(fromNode, toNode, overwrite = false) {
// Ignore if no toNode (in case of errors)
if (!toNode) {
if (!toNode)
return;
const annotations = Object.keys(fromNode).filter(key => key.startsWith('@'));
for (const anno of annotations) {
if (toNode[anno] === undefined || overwrite) {
toNode[anno] = fromNode[anno];
}
}
for (let prop in fromNode) {
if (!Object.hasOwnProperty.call( fromNode, prop ))
continue;
if (prop.startsWith('@')) {
if (toNode[prop] === undefined || overwrite) {
toNode[prop] = fromNode[prop];
}
}
/**
* Same as `copyAnnotations()` but also copies the
* annotation-like property `doc`.
*
* Overwrite existing ones only if 'overwrite' is true.
*
* @param {object} fromNode
* @param {object} toNode
* @param {boolean} [overwrite]
*/
function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
// Ignore if no toNode (in case of errors)
if (!toNode)
return;
const annotations = Object.keys(fromNode)
.filter(key => key.startsWith('@') || key === 'doc');
for (const anno of annotations) {
if (toNode[anno] === undefined || overwrite) {
toNode[anno] = fromNode[anno];
}

@@ -1304,2 +1395,24 @@ }

/**
* Applies annotations from `csn.extensions` to definitions, i.e. top-level artifacts.
* Does _not_ apply element/param/action/... annotations.
* `config.filter` can be used to only copy annotations for those definitions,
* for which the filter returns true.
*
* @param {CSN.Model} csn
* @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
*/
function applyDefinitionAnnotationsFromExtensions(csn, config) {
if (!csn.extensions)
return;
const filter = config.filter || ((_name) => true);
for (const ext of csn.extensions) {
const name = ext.annotate || ext.extend;
if (name && csn.definitions[name] && filter(name)) {
copyAnnotationsAndDoc(ext, csn.definitions[name], config.overwrite);
}
}
}
function isAspect(node) {

@@ -1466,3 +1579,3 @@ return node && node.kind === 'aspect';

* noExtendedProps remove '$', '_' and '@' properties from
* the comparision. This eliminates false negatives such as
* the comparison. This eliminates false negatives such as
* mismatching $locations or @odata.foreignKey4.

@@ -1497,10 +1610,14 @@ */

getUtils,
cloneCsn,
cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
cloneCsnNonDict,
cloneCsnDictionary,
isBuiltinType,
assignAll,
applyDefinitionAnnotationsFromExtensions,
forEachGeneric,
forEachDefinition,
forEachMember,
forEachMemberWithQuery,
forEachMemberRecursively,
forEachMemberRecursivelyWithQuery,
forAllQueries,

@@ -1526,2 +1643,3 @@ hasAnnotationValue,

copyAnnotations,
copyAnnotationsAndDoc,
isAspect,

@@ -1528,0 +1646,0 @@ forEachPath,

@@ -155,2 +155,3 @@ // For testing: reveal non-enumerable properties in CSN, display result of csnRefs

dictionary( ref, 'elements', ref.elements );
_cache_debug( ref );
csnPath.pop();

@@ -157,0 +158,0 @@ }

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

// represent "navigation elemens" in _combined as links:
$navElement: (art, parent) => art._parent && parent !== art._parent.elements,
$navElement: (art, parent) => art._parent && parent !== art._parent.elements && art._parent.kind !== 'aspect',
// represent mixin in $tableAliases as link:

@@ -133,3 +133,3 @@ mixin: tableAliasAsLink,

if (path.length === 1) {
return reveal( xsn.definitions[path] );
return reveal( xsn.definitions[path] || xsn.vocabularies && xsn.vocabularies[path] );
}

@@ -136,0 +136,0 @@

@@ -48,15 +48,15 @@ 'use strict';

if (!beforeVersionParts || beforeVersionParts.length < 2) {
const { error, throwWithError } = makeMessageFunction(beforeModel, options, 'modelCompare');
const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
error(null, null, `Invalid CSN version: ${beforeVersion}`);
throwWithError();
throwWithAnyError();
}
if (!afterVersionParts || afterVersionParts.length < 2) {
const { error, throwWithError } = makeMessageFunction(afterModel, options, 'modelCompare');
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
error(null, null, `Invalid CSN version: ${afterVersion}`);
throwWithError();
throwWithAnyError();
}
if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
const { error, throwWithError } = makeMessageFunction(afterModel, options, 'modelCompare');
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
error(null, null, `Incompatible CSN versions: ${afterVersion} is a major downgrade from ${beforeVersion}. Is @sap/cds-compiler version ${require('../../package.json').version} outdated?`);
throwWithError();
throwWithAnyError();
}

@@ -63,0 +63,0 @@ }

@@ -14,3 +14,5 @@ 'use strict';

.option('-v, --version')
.option(' --options <file>')
.option('-w, --warning <level>', ['0', '1', '2', '3'])
.option(' --quiet')
.option(' --show-message-id')

@@ -26,2 +28,6 @@ .option(' --no-message-context')

.option(' --trace-fs')
.option(' --error <id-list>')
.option(' --warn <id-list>')
.option(' --info <id-list>')
.option(' --debug <id-list>')
.option('-E, --enrich-csn')

@@ -32,7 +38,2 @@ .option('-R, --raw-output <name>')

.option(' --beta <list>')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', [ 'true', 'false', 'individual' ])
.option(' --assert-integrity-type <type>', [ 'RT', 'DB' ], { ignoreCase: true })
.option(' --constraints-in-create-table')
.option(' --deprecated <list>')

@@ -65,2 +66,3 @@ .option(' --hana-flavor')

-v, --version Display version number and exit
--quiet Don't emit anything, neither results nor messages.
-w, --warning <level> Show messages up to <level>

@@ -71,8 +73,11 @@ 0: Error

3: Debug
--options <file> Use the given JSON file as input options.
The key 'cdsc' of 'cds' is used. If not present 'cdsc' is used.
Otherwise, the JSON as-is is used as options.
--show-message-id Show message ID in error, warning and info messages
--no-message-context Print messages as single lines without code context (useful for
redirecting output to other processes). Default is to print human
redirecting output to other processes). Default is to print human-
readable text similar to Rust's compiler with a code excerpt.
--color <mode> Use colors for warnings. Modes are:
auto: (default) Detect color support of the tty.
auto: (default) Detect color support of the TTY.
always:

@@ -95,2 +100,9 @@ never:

Severity options
Use these options to reclassify messages. Option argument is a comma separated list of message IDs.
--error <id-list> IDs that should be reclassified to errors.
--warn <id-list> IDs that should be reclassified to warnings.
--info <id-list> IDs that should be reclassified to info messages.
--debug <id-list> IDs that should be reclassified to debug messages.
Internal options (for testing only, may be changed/removed at any time)

@@ -108,18 +120,2 @@ -E, --enrich-csn Show non-enumerable CSN properties and locations of references

ignoreAssocPublishingInUnion
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
This option is also applied to result of "cdsc manageConstraints"
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
This option is also applied to result of "cdsc manageConstraints"
--assert-integrity <mode> Turn DB constraints on/off:
true : (default) Constraints will be generated for all associations if
the assert-integrity-type is set to DB
false : No constraints will be generated
individual : Constraints will be generated for selected associations
--assert-integrity-type <type> Specifies how the referential integrity checks should be performed:
RT : (default) No database constraint for an association
if not explicitly demanded via annotation
DB : Create database constraints for associations
--constraints-in-create-table If set, the foreign key constraints will be rendered as
part of the "CREATE TABLE" statements rather than as separate
"ALTER TABLE ADD CONSTRAINT" statements
--deprecated <list> Comma separated list of deprecated options.

@@ -179,2 +175,6 @@ Valid values are:

.option('-c, --csn')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
.help(`

@@ -203,2 +203,13 @@ Usage: cdsc toHana [options] <files...>

-c, --csn Generate "hana_csn.json" with HANA-preprocessed model
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
--assert-integrity <mode> Turn DB constraints on/off:
true : (default) Constraints will be generated for all associations if
the assert-integrity-type is set to DB
false : No constraints will be generated
individual : Constraints will be generated for selected associations
--assert-integrity-type <type> Specifies how the referential integrity checks should be performed:
RT : (default) No database constraint for an association
if not explicitly demanded via annotation
DB : Create database constraints for associations
`);

@@ -219,2 +230,3 @@

.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-s, --service-names <list>')
.help(`

@@ -250,2 +262,4 @@ Usage: cdsc toOdata [options] <files...>

source (like "quoted", but using element names with dots)
-s, --service-names <list> List of comma-separated service names to be rendered
(default) empty, all services are rendered
`);

@@ -274,2 +288,7 @@

.option('-c, --csn')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
.option(' --constraints-in-create-table')
.help(`

@@ -307,2 +326,16 @@ Usage: cdsc toSql [options] <files...>

-c, --csn Generate "sql_csn.json" with SQL-preprocessed model
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
--assert-integrity <mode> Turn DB constraints on/off:
true : (default) Constraints will be generated for all associations if
the assert-integrity-type is set to DB
false : No constraints will be generated
individual : Constraints will be generated for selected associations
--assert-integrity-type <type> Specifies how the referential integrity checks should be performed:
RT : (default) No database constraint for an association
if not explicitly demanded via annotation
DB : Create database constraints for associations
--constraints-in-create-table If set, the foreign key constraints will be rendered as
part of the "CREATE TABLE" statements rather than as separate
"ALTER TABLE ADD CONSTRAINT" statements
`);

@@ -338,2 +371,4 @@

.option(' --violations')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.help(`

@@ -343,5 +378,4 @@ Usage: cdsc manageConstraints [options] <files...>

(internal, subject to change): Generate SQL DDL ALTER TABLE statements to add / modify
referential constraints on an existing model.
Combine with options "--integrity-not-enforced" and "--integrity-not-validated"
to switch off foreign key constraint enforcement / validation.
referential constraints on an existing model. This can also be used to
generate SELECT statements which list all referential integrity violations.

@@ -366,2 +400,4 @@ Options

referential integrity violations on the existing data
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
`);

@@ -368,0 +404,0 @@

@@ -5,2 +5,4 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

const { ModelError } = require('../../base/error');
const functionsWithoutParams = {

@@ -284,8 +286,8 @@ hana: {

/**
* Get the element matching the column
*
* @param {CSN.Elements} elements Elements of a query
* @param {CSN.Column} column Column from the same query
* @returns {CSN.Element}
*/
* Get the element matching the column
*
* @param {CSN.Elements} elements Elements of a query
* @param {CSN.Column} column Column from the same query
* @returns {CSN.Element}
*/
function findElement(elements, column) {

@@ -346,12 +348,13 @@ if (!elements)

/**
* Return the comment of the given artifact or element.
* Uses the first block (everything up to the first empty line (double \n)).
* Remove leading/trailing whitespace.
*
* @param {CSN.Artifact|CSN.Element} obj
* @returns {string}
* @todo Warning/info to user?
*/
* Return the comment of the given artifact or element.
* Uses the first block (everything up to the first empty line (double \n)).
* Remove leading/trailing whitespace.
* Does not escape any characters.
*
* @param {CSN.Artifact|CSN.Element} obj
* @returns {string}
* @todo Warning/info to user?
*/
function getHanaComment(obj) {
return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
return obj.doc.split('\n\n')[0].trim();
}

@@ -375,2 +378,114 @@

/**
* A function used to render a certain part of an expression object
*
* @callback renderPart
* @param {object||array} expression
* @param {CdlRenderEnvironment} env
* @this {{inline: Boolean, nestedExpr: Boolean}}
* @returns {string}
*/
/**
* The object containing the concrete rendering functions for the different parts
* of an expression
*
* @typedef {object} ExpressionConfiguration
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
* @property {renderPart} explicitTypeCast
* @property {renderPart} val
* @property {renderPart} enum
* @property {renderPart} ref
* @property {renderPart} aliasOnly
* @property {renderPart} windowFunction
* @property {renderPart} func
* @property {renderPart} xpr
* @property {renderPart} SELECT
* @property {renderPart} SET
*/
/**
* Render an expression (including paths and values) or condition 'x'.
* (no trailing LF, don't indent if inline)
*
* @param {ExpressionConfiguration} renderer
* @returns {Function} Rendered expression
*/
function getExpressionRenderer(renderer) {
/**
* Render an expression (including paths and values) or condition 'x'.
* (no trailing LF, don't indent if inline)
*
* @todo Reuse this with toCdl
* @param {Array|object|string} expr Expression to render
* @param {object} env Render environment
* @param {boolean} [inline=true] Whether to render the expression inline
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
* Note: This is a hack for casts() inside groupBy.
* @returns {string} Rendered expression
*/
return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
// Compound expression
if (Array.isArray(expr)) {
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
return beautifyExprArray(tokens);
}
else if (typeof expr === 'object' && expr !== null) {
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
return renderExprObject(expr);
}
// Not a literal value but part of an operator, function etc - just leave as it is
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
return renderer.finalize.call({ inline, nestedExpr }, expr, env);
/**
* Various special cases represented as objects
*
* @param {object} x Expression
* @returns {string} String representation of the expression
*/
function renderExprObject(x) {
if (x.list) { // TODO: Does this still exist?
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
}
else if (x.val !== undefined) {
return renderer.val.call({ inline, nestedExpr }, x, env);
}
// Enum symbol
else if (x['#']) {
return renderer.enum.call({ inline, nestedExpr }, x, env);
}
// Reference: Array of path steps, possibly preceded by ':'
else if (x.ref) {
return renderer.ref.call({ inline, nestedExpr }, x, env);
}
// Function call, possibly with args (use '=>' for named args)
else if (x.func) {
if (x.xpr)
return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
return renderer.func.call({ inline, nestedExpr }, x, env);
}
// Nested expression
else if (x.xpr) {
return renderer.xpr.call({ inline, nestedExpr }, x, env);
}
// Sub-select
else if (x.SELECT) {
return renderer.SELECT.call({ inline, nestedExpr }, x, env);
}
else if (x.SET) {
return renderer.SET.call({ inline, nestedExpr }, x, env);
}
else if (x.as && x.cast && x.cast.type && x.cast.target) {
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
}
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
}
};
}
/**
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.

@@ -393,2 +508,3 @@ *

renderFunc,
getExpressionRenderer,
beautifyExprArray,

@@ -395,0 +511,0 @@ getNamespace,

@@ -11,3 +11,3 @@ {

"template-curly-spacing":["error", "never"],
"complexity": ["warn", 30],
"complexity": ["warn", 40],
"max-len": "off",

@@ -14,0 +14,0 @@ // Don't enforce stupid descriptions

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

*
* @param {Object} elem The association to process
* @param {object} elem The association to process
* @param {string} elemName

@@ -171,3 +171,3 @@ * @returns {void}

* @param {number} startIndex
* @returns {Object| undefined} CSN definition of the source of the managed association
* @returns {object | undefined} CSN definition of the source of the managed association
*/

@@ -174,0 +174,0 @@ function findSource(links, startIndex) {

@@ -280,3 +280,5 @@ 'use strict';

hasAnnotationValue(parent, '@cds.persistence.skip') ||
hasAnnotationValue(dependent, '@cds.persistence.skip')
hasAnnotationValue(dependent, '@cds.persistence.skip') ||
hasAnnotationValue(parent, '@cds.persistence.exists') ||
hasAnnotationValue(dependent, '@cds.persistence.exists')
)

@@ -283,0 +285,0 @@ return true;

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

const { setProp, isBetaEnabled } = require('../../base/model');
const { forEach } = require('../../utils/objectUtils');

@@ -23,6 +24,6 @@ /**

* @param {Function} messageFunctions.info
* @param {Function} messageFunctions.throwWithError
* @param {Function} messageFunctions.throwWithAnyError
* @param {object} iterateOptions
*/
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }, iterateOptions = {}) {
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
const {

@@ -187,3 +188,3 @@ isStructured, get$combined, getFinalBaseType, getServiceName,

// We would be broken if we continue with assoc usage to now skipped
throwWithError();
throwWithAnyError();

@@ -217,3 +218,3 @@

if (a[_dependents]) {
Object.entries(a[_dependents]).forEach(([ dependentName, dependent ]) => {
forEach(a[_dependents], (dependentName, dependent) => {
stack.push([ dependent, dependentName ]);

@@ -260,3 +261,3 @@ });

*
* @param {CSN.Artifact} root All elements visible fromt he query source ($combined)
* @param {CSN.Artifact} root All elements visible from the query source ($combined)
* @param {CSN.Column[]} columns

@@ -269,4 +270,8 @@ * @param {string[]} excluding

const newThing = [];
// Replace stars - needs to happen here since the .expand/.inline first path step affects the root *
columns = replaceStar(root, columns, excluding);
const containsExpandInline = columns.some(col => col.expand || col.inline);
if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
columns = replaceStar(root, columns, excluding);
else
return { columns, toMany: [] };
for (const col of columns) {

@@ -411,4 +416,4 @@ if (col.expand) {

function findAnEntity() {
for (const [ name, artifact ] of Object.entries(csn.definitions)) {
if (artifact.kind === 'entity' && !artifact.query)
for (const name in csn.definitions) {
if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
return name;

@@ -554,3 +559,3 @@ }

*
* @param {Object} base The raw set of things a * can expand to
* @param {object} base The raw set of things a * can expand to
* @param {Array} subs Things - the .expand/.inline or .columns

@@ -557,0 +562,0 @@ * @param {string[]} [excluding=[]]

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

applyTransformations, applyTransformationsOnNonDictionary,
isBuiltinType, cloneCsn,
isBuiltinType, cloneCsnNonDict,
copyAnnotations, implicitAs, isDeepEqual,

@@ -13,2 +13,3 @@ } = require('../../model/csnUtils');

const { setProp } = require('../../base/model');
const { forEach } = require('../../utils/objectUtils');

@@ -79,13 +80,16 @@ /**

iterateOptions.skipDict = { actions: true };
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
applyTransformations(csn, {
cast: (parent) => {
cast: (parent, prop, cast, path) => {
// Resolve cast already - we otherwise lose .localized
if (parent.cast.type && !isBuiltinType(parent.cast.type))
if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
toFinalBaseType(parent.cast, resolved, true);
},
// @ts-ignore
type: (parent, prop, type, csnPath) => {
if (options.toOdata && parent.kind && [ 'aspect', 'event', 'type' ].includes(parent.kind))
type: (parent, prop, type, path) => {
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
return;
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type) && !isODataItems(type))) {
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
toFinalBaseType(parent, resolved);

@@ -116,36 +120,2 @@ // structured types might not have the child-types replaced.

}
/**
* OData V4 only:
* Do not replace a type ref if:
* The type definition is terminating on a scalar type (that can also be a derived type chain)
* AND the typeName (that is the start of that (derived) type chain is defined within the same
* service as the artifact from which the type reference has to be resolved.
*
* @param {string} typeName
* @returns {boolean}
*/
function isODataV4BuiltinFromService(typeName) {
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2'))
return false;
const typeServiceName = getServiceName(typeName);
const finalBaseType = getFinalBaseType(typeName);
// we need the service of the current definition
const currDefServiceName = getServiceName(csnPath[1]);
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
}
/**
* OData stops replacing types @ 'items', if the type ref is a user defined type
* AND that type has items, don't do toFinalBaseType
*
* @param {string} typeName
* @returns {boolean}
*/
function isODataItems(typeName) {
const typeDef = csn.definitions[typeName];
return !!(options.toOdata && typeDef && typeDef.items);
}
},

@@ -166,4 +136,3 @@ // HANA/SQLite do not support array-of - turn into CLOB/Text

// TODO:factor out somewhere else
if (!options.toOdata &&
([ 'action', 'function', 'event' ].includes(artifact.kind))) {
if (!options.toOdata && artifact.kind in replaceWithDummyKinds) {
const dummy = { kind: artifact.kind };

@@ -177,2 +146,38 @@ if (artifact.$location)

} ], iterateOptions);
/**
* OData V4 only:
* Do not replace a type ref if:
* The type definition is terminating on a scalar type (that can also be a derived type chain)
* AND the typeName (that is the start of that (derived) type chain is defined within the same
* service as the artifact from which the type reference has to be resolved.
*
* @param {string} typeName
* @param {CSN.Path} path
* @returns {boolean}
*/
function isODataV4BuiltinFromService(typeName, path) {
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2') || typeof typeName !== 'string')
return false;
const typeServiceName = getServiceName(typeName);
const finalBaseType = getFinalBaseType(typeName);
// we need the service of the current definition
const currDefServiceName = getServiceName(path[1]);
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
}
/**
* OData stops replacing types @ 'items', if the type ref is a user defined type
* AND that type has items, don't do toFinalBaseType
*
* @param {string} typeName
* @returns {boolean}
*/
function isODataItems(typeName) {
const typeDef = csn.definitions[typeName];
return !!(options.toOdata && typeDef && typeDef.items);
}
}

@@ -267,3 +272,3 @@

* @param {Function} error
* @param {Object} iterateOptions
* @param {object} iterateOptions
*/

@@ -294,3 +299,3 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {

setProp(parent[prop], '$orderedElements', []);
Object.entries(dict).forEach(([ elementName, element ]) => {
forEach(dict, (elementName, element) => {
if (element.elements) {

@@ -306,5 +311,5 @@ // Ignore the structured element, replace it by its flattened form

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,
// check: Error location should be the existing element like @odata.foreignKey4
// TODO: combine message ID with generated FK duplicate
// do the duplicate check in the consruct 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}"`);

@@ -327,12 +332,12 @@

/*
when element is defined in the current name resolution scope, like
entity E {
key x: Integer;
s : {
y : Integer;
a3 : association to E on a3.x = y;
}
}
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
*/
when element is defined in the current name resolution scope, like
entity E {
key x: Integer;
s : {
y : Integer;
a3 : association to E on a3.x = y;
}
}
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
*/
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);

@@ -431,7 +436,7 @@ const possibleFlatName = prefix + pathDelimiter + firstRef;

},
}, [], {
}, [], Object.assign({
skipIgnore: false,
allowArtifact: artifact => (artifact.kind === 'entity' || artifact.kind === 'type'),
skipDict: { actions: true },
});
}, iterateOptions));
}

@@ -483,3 +488,3 @@ createForeignKeyElements();

const key = assoc.keys[i];
const clone = cloneCsn(assoc.keys[i], options);
const clone = cloneCsnNonDict(assoc.keys[i], options);
if (clone.as) {

@@ -548,3 +553,3 @@ const lastRef = clone.ref[clone.ref.length - 1];

function cloneAndExtendRef(key, base, ref) {
const clone = cloneCsn(base, options);
const clone = cloneCsnNonDict(base, options);
if (key.ref) {

@@ -698,2 +703,3 @@ // We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet

let finalElement = element;
let finalTypeName; // TODO: Find a way to not rely on $path?
// TODO: effectiveType's return value is 'path' for the next inspectRef

@@ -705,7 +711,10 @@ if (element.type && !isBuiltinType(element.type)) {

finalElement = tmpElt;
finalTypeName = finalElement.$path[1];
}
else {
// unwind a derived type chain to a scalar type
while (finalElement.type && !isBuiltinType(finalElement.type))
while (finalElement.type && !isBuiltinType(finalElement.type)) {
finalTypeName = finalElement.type;
finalElement = csn.definitions[finalElement.type];
}
}

@@ -727,3 +736,3 @@ }

finalElement.keys.forEach((key, keyIndex) => {
const continuePath = isInspectRefResult ? [ path, 'keys', keyIndex ] : [ ...path, 'keys', keyIndex ];
const continuePath = getContinuePath([ 'keys', keyIndex ]);
const alias = key.as || implicitAs(key.ref);

@@ -754,3 +763,3 @@ const result = inspectRef(continuePath);

if (!elem['@odata.foreignKey4']) {
const continuePath = isInspectRefResult ? [ path, 'elements', elemName ] : [ ...path, 'elements', elemName ];
const continuePath = getContinuePath([ 'elements', elemName ]);
fks = fks.concat(createForeignKeysInternal(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));

@@ -761,2 +770,23 @@ }

/**
* Get the path to continue resolving references
*
* If we are currently inside of a type, we need to start our path fresh from that given type.
* Otherwise, we would try to resolve .elements on a thing that does not exist.
*
* We also respect if we have a previous inspectRef result as our base.
*
* @param {Array} additions
* @returns {CSN.Path}
*/
function getContinuePath(additions) {
if (csn.definitions[finalElement.type])
return [ 'definitions', finalElement.type, ...additions ];
else if (finalTypeName)
return [ 'definitions', finalTypeName, ...additions ];
else if (isInspectRefResult)
return [ path, ...additions ];
return [ ...path, ...additions ];
}
fks.forEach((fk) => {

@@ -763,0 +793,0 @@ // prepend current prefix

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

else if (join.args) {
const subsources = getJoinSources(join.args);
sources = Object.assign(sources, subsources);
const subSources = getJoinSources(join.args);
sources = Object.assign(sources, subSources);
}

@@ -200,3 +200,3 @@ else if (join.ref) {

while (stack.length > 0) {
// previous: to nest "up" if the previous assoc did not originaly have a filter
// previous: to nest "up" if the previous assoc did not originally have a filter
// assoc: the assoc path step

@@ -416,5 +416,5 @@ // rest: path steps after assoc

* For each of the foreign keys, do:
* + build the target side by prefixing `target` infront of the ref
* + build the target side by prefixing `target` in front of the ref
* + build the source side by prefixing `base` (if not already part of `current`)
* and the assoc name itself (current) infront of the ref
* and the assoc name itself (current) in front of the ref
* + Compare source and target with `=`

@@ -424,3 +424,3 @@ *

*
* The new tokens are immediatly added to the WHERE of the subselect
* The new tokens are immediately added to the WHERE of the subselect
*

@@ -759,3 +759,3 @@ * @param {CSN.Element} root

* @param {TokenStream} where
* @returns {TokenStream} The input-where with the refs "absolutified"
* @returns {TokenStream} The input-where with the refs transformed to absolute ones
*/

@@ -762,0 +762,0 @@ function remapExistingWhere(target, where) {

'use strict';
const {
getUtils, cloneCsn, applyTransformationsOnNonDictionary,
getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary,
} = require('../../model/csnUtils');

@@ -212,3 +212,3 @@ const { implicitAs, csnRefs } = require('../../model/csnRefs');

elem.keys.forEach((foreignKey) => {
const ref = cloneCsn(assocCol.ref, options);
const ref = cloneCsnNonDict(assocCol.ref, options);
ref[ref.length - 1] = [ getLastRefStepString(ref) ].concat(foreignKey.as).join(pathDelimiter);

@@ -281,3 +281,3 @@ const result = {

// Needs to be a deep copy, as we transform the on-condition
const mixinElem = cloneCsn(elem, options);
const mixinElem = cloneCsnNonDict(elem, options);
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)

@@ -462,2 +462,5 @@ transformCommon(mixinElem, mixinElemName);

if (!col.ref) // No ref -> new association, but not a mixin.
return true;
// Use getLastRefStepString - with hdbcds.hdbcds and malicious CSN input we might have .id

@@ -464,0 +467,0 @@ const realName = getLastRefStepString(col.ref);

'use strict';
const { setProp, isBetaEnabled } = require('../base/model');
const { getUtils, cloneCsn,
const { getUtils, cloneCsnNonDict,
forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,

@@ -19,3 +19,3 @@ getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,

const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
const { createDict } = require('../utils/objectUtils');
const { createDict, forEach } = require('../utils/objectUtils');
const handleExists = require('./db/transformExists');

@@ -110,3 +110,3 @@ const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');

/** @type {CSN.Model} */
let csn = cloneCsn(inputModel, options);
let csn = cloneCsnNonDict(inputModel, options);

@@ -121,3 +121,3 @@

/** @type {() => void} */
let throwWithError;
let throwWithAnyError;
let artifactRef, inspectRef, effectiveType, get$combined,

@@ -129,3 +129,3 @@ getFinalBaseType, // csnUtils (csnRefs)

throwWithError(); // reclassify and throw in case of non-configurable errors
throwWithAnyError(); // reclassify and throw in case of non-configurable errors

@@ -153,3 +153,3 @@ if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {

// subsequent procession steps (especially a2j) to see plain paths in expressions.
// If errors are detected, throwWithError() will return from further processing
// If errors are detected, throwWithAnyError() will return from further processing

@@ -160,3 +160,3 @@ // If this function is ever undefined, we have a bug in our logic.

throwWithError();
throwWithAnyError();

@@ -176,3 +176,3 @@ // FIXME: This does something very similar to cloneWithTransformations -> refactor?

// Expand a structured thing in: keys, columns, order by, group by
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithError});
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError});
bindCsnReference();

@@ -363,3 +363,3 @@ }

throwWithError();
throwWithAnyError();

@@ -392,3 +392,18 @@ timetrace.stop();

// Set when we remove .key from temporal things, used in localized.js
'$key': killProp
'$key': killProp,
// We need .elements easily for rendering - otherwise we have to compute it then
// Does not fit in the "killers" theme - TODO: Find a better place
SET: (parent, prop, SET) => {
if(!SET.elements) {
const stack = [parent];
while(stack.length > 0) {
const query = stack.pop();
if(query.SET)
stack.push(query.SET.args[0]);
else if(query.SELECT)
setProp(SET, 'elements', query.SELECT.elements);
}
}
}
}

@@ -405,3 +420,3 @@

function bindCsnReference(){
({ error, warning, info, message, throwWithError } = makeMessageFunction(csn, options, moduleName));
({ error, warning, info, message, throwWithAnyError } = makeMessageFunction(csn, options, moduleName));
({ artifactRef, inspectRef, effectiveType, getFinalBaseType, get$combined } = getUtils(csn));

@@ -461,3 +476,3 @@ ({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));

const elementsOfQuerySources = get$combined(query);
Object.entries(elementsOfQuerySources).forEach(([id, element]) => {
forEach(elementsOfQuerySources, (id, element) => {
// if the ref points to an element which is not explicitly exposed in the column list,

@@ -472,3 +487,3 @@ // but through the '*' operator -> replace the $projection / $self with the correct source entity

if(replaceWith && replaceWith.cast) {
const clone = cloneCsn(replaceWith, options);
const clone = cloneCsnNonDict(replaceWith, options);
delete clone.cast;

@@ -490,6 +505,10 @@ return clone;

if ((artifact.kind === 'entity') && artifact.query) {
forAllQueries(artifact.query, (q, p) => {
transformEntityOrViewPass2(q, artifact, artifactName, p)
replaceAssociationsInGroupByOrderBy(q, options, inspectRef, error, p);
}, [ 'definitions', artifactName, 'query' ]);
const process = (parent, prop, query, path) => {
transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
return query;
}
applyTransformationsOnNonDictionary(csn.definitions, artifactName, {
SELECT: process
}, {}, [ 'definitions']);
}

@@ -634,3 +653,3 @@ }

// With flattening errors, it makes little sense to continue.
throwWithError();
throwWithAnyError();
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we

@@ -1015,7 +1034,7 @@ // simply make it invisible and copy it over to the result csn

const paramValue = node[paramName];
if (paramValue == undefined) {
if (paramValue === undefined || paramValue === null) {
if(options.toSql || artifact.query || !['cds.Binary','cds.hana.BINARY', 'cds.hana.NCHAR','cds.hana.CHAR'].includes(node.type)) {
return true;
} else {
return error('missing-type-parameter', path, { name: paramName, id: node.type, $reviewed: false });
return error('type-missing-argument', path, { name: paramName, id: node.type, $reviewed: false });
}

@@ -1025,11 +1044,9 @@ }

if (isMaxParameterLengthRestricted(node.type) && range.max && paramValue > range.max) {
error(null, path,
{ prop: paramName, type: node.type, number: range.max, $reviewed: false },
'Expecting parameter $(PROP) for type $(TYPE) to not exceed $(NUMBER)');
error('type-unexpected-argument', path,
{ '#': 'max', prop: paramName, type: node.type, number: range.max, $reviewed: false });
return false;
}
if (range.min && paramValue < range.min) {
error(null, path,
{ prop: paramName, type: node.type, number: range.min, $reviewed: false },
'Expecting parameter $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)');
error('type-unexpected-argument', path,
{ '#': 'min', prop: paramName, type: node.type, number: range.min, $reviewed: false });
return false;

@@ -1036,0 +1053,0 @@ }

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

const { getUtils,
cloneCsn,
cloneCsnNonDict,
forEachDefinition,

@@ -21,10 +21,8 @@ forEachMemberRecursively,

const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
const ReferenceFlattener = require('./odata/referenceFlattener');
const { flattenCSN } = require('./odata/structureFlattener');
const generateForeignKeys = require('./odata/generateForeignKeyElements');
const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssociations');
const expandToFinalBaseType = require('./odata/toFinalBaseType');
const { timetrace } = require('../utils/timetrace');
const { attachPath } = require('./odata/attachPath');
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
const flattening = require('./db/flattening');
const associations = require('./db/associations')
const expansion = require('./db/expansion');
const generateDrafts = require('./draft/odata');

@@ -79,6 +77,6 @@

// copy the model as we don't want to change the input model
let csn = cloneCsn(inputModel, options);
let csn = cloneCsnNonDict(inputModel, options);
const { message, error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
throwWithError();
const { message, error, warning, info, throwWithAnyError } = makeMessageFunction(csn, options, 'for.odata');
throwWithAnyError();

@@ -103,3 +101,2 @@ // the new transformer works only with new CSN

isAssociation,
isStructured,
inspectRef,

@@ -134,3 +131,3 @@ artifactRef,

validate.forOdata(csn, {
const cleanup = validate.forOdata(csn, {
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember

@@ -141,3 +138,3 @@ });

// Throw exception in case of errors
throwWithError();
throwWithAnyError();

@@ -164,32 +161,35 @@ // Semantic checks before flattening regarding temporal data

// subsequent procession steps (especially a2j) to see plain paths in expressions.
// If errors are detected, throwWithError() will return from further processing
// If errors are detected, throwWithAnyError() will return from further processing
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
// handles reference flattening
let referenceFlattener = new ReferenceFlattener();
referenceFlattener.resolveAllReferences(csn, inspectRef, isStructured);
attachPath(csn);
referenceFlattener.applyAliasesInOnCond(csn, inspectRef);
if (!structuredOData) {
// flatten structures
// @ts-ignore
flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember);
// flatten references
referenceFlattener.flattenAllReferences(csn);
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, { skipArtifact: isExternalServiceMember });
const resolved = new WeakMap();
// No refs with struct-steps exist anymore
flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
// No type references exist anymore
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
// OData doesn't resolve type chains after the first 'items'
flattening.resolveTypeReferences(csn, options, resolved, '_',
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'], skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
// No structured elements exists anymore
flattening.flattenElements(csn, options, '_', error,
{ skip: ['action', 'aspect', 'event', 'function', 'type'], skipArtifact: isExternalServiceMember,
skipStandard: { items: true }, // don't drill further into .items
skipDict: { actions: true } }); // don't drill further into .actions -> bound actions, action-artifacts are handled by skip
}
// TODO: add the generated foreign keys to the columns when we are in a view
// see db/views.js::addForeignKeysToColumns
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, { skipArtifact: isExternalServiceMember });
// Allow using managed associations as steps in on-conditions to access their fks
// To be done after handleManagedAssociationsAndCreateForeignKeys,
// since then the foreign keys of the managed assocs are part of the elements
if(!structuredOData)
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, '_'));
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
throwWithError();
throwWithAnyError();
// Process associations
// 1. In case we generate flat mode, expand the structured foreign keys.
// This logic rewrites the 'ref' for such keys with the corresponding flattened
// elements.
if (!structuredOData)
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils, isExternalServiceMember);
// 2. generate foreign keys for managed associations
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
// Apply default type facets as set by options

@@ -217,2 +217,3 @@ // Flatten on-conditions in unmanaged associations

// Deal with all kind of annotations manipulations here
const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
forEachDefinition(csn, (def, defName) => {

@@ -224,12 +225,12 @@ // Resolve annotation shorthands for entities, types, annotations, ...

// Skip artifacts that have no DB equivalent anyway
if (options.toOdata.names && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
if (options.toOdata.names && !(def.kind in skipPersNameKinds))
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
forEachMemberRecursively(def, (member, memberName, propertyName) => {
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
// Only these are actually required and don't annotate virtual elements in entities or types
// as they have no DB representation (although in views)
if (options.toOdata.names && typeof member === 'object' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
if (options.toOdata.names && 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(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.toOdata.names);
}

@@ -264,5 +265,5 @@

// Throw exception in case of errors
throwWithError();
if (options.testMode) csn = cloneCsn(csn, options); // sort, keep hidden properties
throwWithAnyError();
cleanup();
if (options.testMode) csn = cloneCsnNonDict(csn, options); // sort, keep hidden properties
timetrace.stop();

@@ -269,0 +270,0 @@ return csn;

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

const { hasErrors } = require('../base/messages');
const { cloneCsnDictionary } = require('../model/csnUtils');
const { cloneCsnDictionary, applyDefinitionAnnotationsFromExtensions} = require('../model/csnUtils');
const { cleanSymbols } = require('../base/cleanSymbols.js');
const {
cloneCsn,
cloneCsnNonDict,
forEachDefinition,

@@ -90,2 +90,8 @@ forEachGeneric,

// In case that the user tried to annotate `localized.*` artifacts, apply them.
applyDefinitionAnnotationsFromExtensions(csn, {
overwrite: true,
filter: (name) => name.startsWith('localized.')
});
sortCsnDefinitionsForTests(csn, options);

@@ -146,5 +152,4 @@ return csn;

copyPersistenceAnnotations(view, art);
csn.definitions[viewName] = view;
copyPersistenceAnnotations(csn.definitions[viewName], art);
}

@@ -251,5 +256,5 @@

if (view.query)
convenienceView.query = cloneCsn(view.query, options);
convenienceView.query = cloneCsnNonDict(view.query, options);
else if (view.projection)
convenienceView.projection = cloneCsn(view.projection, options);
convenienceView.projection = cloneCsnNonDict(view.projection, options);

@@ -556,3 +561,3 @@ convenienceView.elements = cloneCsnDictionary(view.elements, options);

for (const prop of [ 'ref', 'target', 'on' ]) {
for (const prop of [ 'ref', 'target' ]) {
const val = obj[prop];

@@ -559,0 +564,0 @@ if (prop === 'ref') {

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

forEachDefinition, forEachMemberRecursively,
isBuiltinType, cloneCsnDictionary, cloneCsn,
isBuiltinType, cloneCsnDictionary, cloneCsnNonDict,
} = require('../../model/csnUtils');

@@ -101,3 +101,3 @@ const { isArtifactInSomeService, isArtifactInService } = require('./utils');

// type X: Integer;
//
//
// type A: B; -> {...}

@@ -125,3 +125,3 @@ // type B: C; -> { ... }

// type X: Integer;
//
//
// type {

@@ -168,3 +168,3 @@ // struct_elt: many A; ---> stays the same

(finalType.elements && !node.elements))
clone = cloneCsn({ definitions: { 'TypeDef': finalType } }, options);
clone = cloneCsnNonDict({ definitions: { 'TypeDef': finalType } }, options);
if (finalType.items) {

@@ -200,2 +200,2 @@ delete node.type;

module.exports = expandToFinalBaseType;
module.exports = expandToFinalBaseType;

@@ -11,13 +11,6 @@ 'use strict';

const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember, cloneCsnDictionary } = require('../../model/csnUtils');
const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
const { copyAnnotations } = require('../../model/csnUtils');
/**
* @param {CSN.Model} csn
* @param {function} whatsMyServiceName
* @param {CSN.Options} options
* @param {*} csnUtils
* @param {object} message message object with { error } function
*/
function typesExposure(csn, whatsMyServiceName, autoexposeSchemaName, options, csnUtils, message) {
function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
const { error } = message;

@@ -27,4 +20,4 @@ // are we working with OData proxies or cross-service refs

// collect in this variable all the newly exposed types
const exposedStructTypes = [];
const schemas = Object.create(null);
const exposedTypes = Object.create(null);
// walk through the definitions of the given CSN and expose types where needed

@@ -34,10 +27,11 @@ forEachDefinition(csn, (def, defName, propertyName, path) => {

const serviceName = whatsMyServiceName(defName, false);
if (serviceName) {
// run type exposure only on requested services if not in multi schema mode
// multi schema mode requires a proper type exposure for all services as a prerequisite
// for the proxy exposure
if (serviceName && requestedServiceNames.includes(serviceName)) {
if (def.kind === 'type' || def.kind === 'entity') {
forEachMember(def, (element, elementName, propertyName, path) => {
if (propertyName === 'elements' || propertyName === 'params') {
const artificialtName = `${isMultiSchema ?
defNameWithoutServiceOrContextName(defName, serviceName)
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, artificialtName, path);
const newTypeName = getNewTypeName(element, elementName, defName, serviceName);
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, newTypeName, path);
}

@@ -61,26 +55,4 @@ }, path);

return schemas;
/**
* General function used for exposing a type of given element
* @param {object} node
* @param {string} memberName
* @param {string} service
* @param {string} artificialName
* @param {CSN.Path} path
*/
function exposeTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
if (isArrayed(node))
exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path);
else if (csnUtils.isStructured(node))
exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path);
}
/**
* Check if a node is arrayed
* @param {object} node
*/
function isArrayed(node) {
return node.items || (node.type && csnUtils.getFinalTypeDef(node.type).items);
}
/**
/**
* If an 'action' uses structured types as parameters or return values that are not exposed in 'service'

@@ -91,13 +63,15 @@ * (because the types are anonymous or have a definition outside of 'service'),

* @param {String} actionName
* @param {String} service
* @param {String} serviceName
*/
function exposeTypesOfAction(action, actionName, defName, service, path) {
function exposeTypesOfAction(action, actionName, defName, serviceName, path) {
if (action.returns) {
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
exposeTypeOf(action.returns, false, actionName, defName, service, artificialName, path.concat(['returns']));
const newTypeName = getNewTypeName(action.returns, undefined, artificialName, serviceName);
exposeTypeOf(action.returns, false, actionName, defName, serviceName, newTypeName, path.concat(['returns']));
}
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
const artificialName = `param_${actionName.replace(/\./g, '_')}_${paramName}`;
exposeTypeOf(param, false, actionName, defName, service, artificialName, path.concat(['params', paramName]));
const artificialName = `param_${actionName.replace(/\./g, '_')}`;//_${paramName}`;
const newTypeName = getNewTypeName(param, paramName, artificialName, serviceName);
exposeTypeOf(param, false, actionName, defName, serviceName, newTypeName, path.concat(['params', paramName]));
});

@@ -112,69 +86,115 @@ }

* @param {String} memberName
* @param {String} service
* @param {String} artificialName
* @param {String} serviceName
* @param {String} newTypeName
*/
function exposeStructTypeOf(node, isKey, memberName, defName, service, artificialName, path, parentName) {
if (node.items) exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path);
// start conservative, assume we're in a named type
let isAnonymous = false;
if (isExposableStructure(node)) {
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node;
let newTypeId = node.type ? `${isMultiSchema ? node.type : node.type.replace(/\./g, '_')}` : artificialName;
let newTypeFullName =
function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName) {
const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable(node);
if (isExposable) {
// this is the name used to register the new type in csn.definitions
let fullQualifiedNewTypeName =
isMultiSchema
? node.type ? getTypeNameInMultiSchema(node.type, service) : getAnonymousTypeNameInMultiSchema(artificialName, parentName || defName)
: `${service}.${newTypeId}`;
? (node.type || (node.items && node.items.type)
? getTypeNameInMultiSchema(node.type|| (node.items && node.items.type), serviceName)
: getAnonymousTypeNameInMultiSchema(newTypeName, parentName || defName))
: `${serviceName}.${newTypeName}`;
// With the redirection of sub elements, the element which is of named type with an association is now expanded and contains the association
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
// as the correct target is there and no longer in the type definition
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
// if node and typeDef are identical, we're anonymous
isAnonymous = node === typeDef;
// if we've left the anonymous world, we're no longer in a key def
if (!isAnonymous)
// as soon as we leave of the anonymous world,
// we're no longer in a key def => don't set notNull:true on named types
if (!isAnonymous && isKey)
isKey = false;
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
if (!newType) {
// Error already reported
return;
// check if that type is already defined
let newType = csn.definitions[fullQualifiedNewTypeName];
if (newType) {
// error, if it was not exposed by us
if (!exposedTypes[fullQualifiedNewTypeName]) {
error(null, path, `Cannot create artificial type "${fullQualifiedNewTypeName}" for "${memberName}" because the name is already used`);
return;
}
}
else {
/* Expose new structured type
* Treat items.elements as ordinary elements for now.
*/
newType = createNewStructType(elements);
if (node.$location)
setProp(newType, '$location', node.$location);
if (node.$location) setProp(newType, '$location', node.$location);
setProp(newType, '$exposedBy', 'typeExposure');
csn.definitions[fullQualifiedNewTypeName] = newType;
exposedTypes[fullQualifiedNewTypeName] = 1;
// Recurse into elements of 'type' (if any) and expose them as well (is needed)
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
if (node.elements && node.elements[elemName].$location) setProp(newElem, '$location', node.elements[elemName].$location);
exposeStructTypeOf(newElem,
isKey,
memberName,
typeDef.kind === 'type' ? node.type : defName,
service,
isMultiSchema ? `${newTypeFullName}_${elemName}` : `${newTypeId}_${elemName}`,
path,
newTypeFullName);
});
typeDef.kind === 'type' ? copyAnnotations(typeDef, newType) : copyAnnotations(node, newType);
delete node.elements;
node.type = newTypeFullName;
// Recurse into elements of 'type' (if any) and expose them as well (is needed)
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
if (node.elements && node.elements[elemName].$location)
setProp(newElem, '$location', node.elements[elemName].$location);
defName = typeDef.kind === 'type' ? typeName : defName;
exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName);
});
copyAnnotations(typeDef, newType);
// if the origin type had items, add items to exposed type
if(typeDef.kind === 'type') {
if(typeDef.items) {
newType.items = { elements: newType.elements };
delete newType.elements;
}
}
}
// adjust current node to new type
if(node.items) {
delete node.items.elements;
delete node.type;
node.items.type = fullQualifiedNewTypeName;
}
else {
delete node.elements;
node.type = fullQualifiedNewTypeName;
}
}
/**
* Returns whether the 'node' is for exposing the in service.
* There are 2 cases when we would like to expose a type is the service:
* 1. If the node is of user-defined type which is not part of the service
* 2. When we have structured element (the object has property 'elements')
* @param {Object} node
/**
* Check if the node's type can be exposed:
* 1) If it's an anonymous structured type (items.elements || elements)
* 2) If it's a named type resolve to the final type definition and
* check if this is a structured type
* Returns an object that indicates
* - wether or not the type needs exposure
* - the elements dictionary that needs to be cloned
* - the typeDef, either the resolved type def or the node itself
* - if structured type was anonymously defined
* @param {object} node
* @returns {object} { isExposable, typeDef, typeName, elements, isAnonymous }
*/
function isExposableStructure(node) {
let finalNodeType = node.type ? csnUtils.getFinalType(node.type) : undefined;
return finalNodeType && isArtifactInService(finalNodeType, service)
? false
: csnUtils.isStructured(node);
function isTypeExposable(node) {
let typeName = undefined;
let typeDef = node;
let elements = (node.items && node.items.elements || node.elements)
// anonymous structured type
if(elements)
return { isExposable: true, typeDef, typeName, elements, isAnonymous: true };
// named type, resolve the type to inspect it
let type = node.items && node.items.type || node.type;
if(type) {
typeName = (type.ref && csnUtils.getFinalType(type)) || type;
typeDef = csnUtils.getFinalTypeDef(typeName);
const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
if(!isBuiltinType(typeName) && !isArtifactInService(typeName, serviceName)) {
while(!isBuiltinType(typeName)) {
typeDef = csnUtils.getFinalTypeDef(typeName);
if(typeDef) {
if((rc.elements = (typeDef.items && typeDef.items.elements || typeDef.elements)) !== undefined)
return rc;
type = typeDef.items && typeDef.items.type || typeDef.type;
typeName = (type.ref && csnUtils.getFinalType(type)) || type;
}
else {
throw Error(`Please debug me: ${typeName} not found`);
}
}
}
}
return { isExposable: false };
}
/**

@@ -185,5 +205,5 @@ * Calculate the new type name that will be exposed in multi schema,

* @param {string} typeName type of the element
* @param {string} service current service name
* @param {string} serviceName current service name
*/
function getTypeNameInMultiSchema(typeName, service) {
function getTypeNameInMultiSchema(typeName, serviceName) {
const typeService = whatsMyServiceName(typeName);

@@ -193,3 +213,3 @@ if (typeService) {

const typePlainName = defNameWithoutServiceOrContextName(typeName, typeService);
const newSchemaName = `${service}.${typeService}`;
const newSchemaName = `${serviceName}.${typeService}`;
createSchema(newSchemaName);

@@ -201,3 +221,3 @@ // return the new type name

const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
const newSchemaName = `${service}.${typeContext || typeNamespace || autoexposeSchemaName}`;
const newSchemaName = `${serviceName}.${typeContext || typeNamespace || fallBackSchemaName}`;
// new type name without any prefixes

@@ -221,3 +241,3 @@ const typePlainName = typeContext ? defNameWithoutServiceOrContextName(typeName, typeContext)

let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
const newSchemaName = currPrefix || autoexposeSchemaName;
const newSchemaName = currPrefix || fallBackSchemaName;
// new type name without any prefixes

@@ -235,38 +255,20 @@ const typePlainName = defNameWithoutServiceOrContextName(typeName, newSchemaName);

function createSchema(name) {
schemas[`${name}`] = { kind: 'schema', name };
schemas[name] = { kind: 'schema', name };
}
/**
* Expose a new type definition in the 'definitions' of the CSN and return that type(reusing such a type
* if it already exists).
* The new type has name 'typeName', elements which are 'elements'.
* 'parentName' is used for error reporting.x
* @param {String} typeName
* create a new structured type for 'elements'
* @param {Object} elements
* @param {String} parentName
*/
function exposeStructType(typeName, elements, parentName, path) {
// If type already exists, reuse it (complain if not created here)
let type = csn.definitions[typeName];
if (type) {
if (!exposedStructTypes.includes(typeName)) {
error(null, path, `Cannot create artificial type "${typeName}" for "${parentName}" because the name is already used`);
return null;
}
return type;
}
function createNewStructType(elements) {
// Create a type with empty elements
type = {
const type = {
kind: 'type',
elements: Object.create(null),
};
setProp(type, '$exposedBy', 'typeExposure');
// Duplicate the type's elements
// Duplicate elements
Object.entries(elements).forEach(([elemName, element]) => {
if (type.elements[elemName]) {
const path = ['definitions', typeName, 'elements', elemName];
error(null, path, `"${elemName}": Element name conflicts with existing element`);
}
let cloned = cloneCsn(element, options);
let cloned = cloneCsnNonDict(element, options);
// if this was an anonymous sub element of a key, mark it as not nullable

@@ -277,7 +279,2 @@ if(isAnonymous && isKey && !cloned.key && cloned.notNull === undefined)

});
// add to the CSN
csn.definitions[typeName] = type;
// store typeName in set of exposed struct types
exposedStructTypes.push(typeName);
return type;

@@ -287,70 +284,21 @@ }

// If a member is of type "array of <named type|anonymous type>", we expose the arrayed type,
// like we expose structures in structured mode
function exposeArrayOfTypeOf(node, isKey, memberName, defName, service, artificialName, path) {
// if anonymously defined in place -> we always expose the type
// this would be definition like 'elem: array of { ... }'
// and we use the artificial name for the new type name
if (node.items && !node.type) {
exposeStructTypeOf(node.items, isKey, memberName, defName, service, artificialName, path.concat('items'));
}
// we can have both of the 'type' and 'items' in the cases:
// 1. 'elem: Foo' and 'type Foo: array of Baz' and 'type Baz: { ... }'
// or 2. 'elem: Foo' and type Foo: array of Integer|String|...'
else if (node.type) {
let finalType = csnUtils.getFinalTypeDef(node.type);
if (finalType.items) {
if (!isArtifactInService(node.type, service)) {
let typeId = `${service}.${node.type.replace(/\./g, '_')}`;
let newType = exposeArrayedType(node.items || finalType.items, typeId);
// When we have in the model something like:
// type Foo: array of Bar; type Bar: { qux: Integer };
// In the type Foo we expand the first level of elements of the items like we have in CDL this:
// type Foo: array of { qux: Integer };
expandFirstLevelOfArrayed(newType);
node.type = typeId;
}
// case 1. - as we keep the type property, the items property is removed
if (node.items) delete node.items;
}
}
function exposeArrayedType(items, typeId) {
let newType = csn.definitions[typeId];
if (newType) {
if (!exposedStructTypes.includes(typeId)) {
error(null, newType.$path, `Cannot create artificial type "${typeId}" because the name is already used`);
}
return newType;
}
// create empty type
newType = {
kind: 'type',
items: Object.create(null),
}
// copy over the items
newType.items = cloneCsn(items, options);
csn.definitions[typeId] = newType;
exposedStructTypes.push(typeId);
return newType;
}
/*
* Calculate the name of the exposed type based on the information, the element can provide
* If the element is typed, use the type name
* else assume it's a de-anonymized type, concatenate the element name to the defName
*/
function getNewTypeName(element, elementName, typeNamePrefix, serviceName) {
// for the new type name node.type has precedence over node.items.type
const typeName = (!element.elements && element.type || element.items && !element.items.elements && element.items.type);
return typeName
? `${isMultiSchema
? typeName.split('.').pop() // use leaf element
: typeName.replace(/\./g, '_')}` // concatenate path
: ( elementName // returns has no elementName, return the precalculated prefix
? `${defNameWithoutServiceOrContextName(typeNamePrefix, serviceName).replace(/\./g, '_')}_${elementName}`
: typeNamePrefix
);
}
// In case we have in the model something like:
// type Foo: array of Bar; type Bar: { qux: Integer };
// In the type Foo we expand the first level of elements of the items like we have in CDL this:
// type Foo: array of { qux: Integer };
function expandFirstLevelOfArrayed(def) {
if (def.items.type && !isBuiltinType(def.items.type)) {
let finalType = csnUtils.getFinalTypeDef(def.items.type);
if (csnUtils.isStructured(finalType)) {
if (!def.items.elements) def.items.elements = cloneCsnDictionary(finalType.elements, options);
delete def.items.type;
}
}
}
}
module.exports = typesExposure;

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

const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
const { cloneCsn, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
const { cloneCsnNonDict, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
const { ModelError } = require("../base/error");
const { forEach } = require('../utils/objectUtils');

@@ -262,3 +263,3 @@ // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).

}
Object.entries(struct).forEach(([childName, childElem]) => {
forEach(struct, (childName, childElem) => {
if (isStructured(childElem)) {

@@ -281,3 +282,3 @@ // Descend recursively into structured children

let flatElemName = elemName + pathDelimiter + childName;
let flatElem = cloneCsn(childElem, options);
let flatElem = cloneCsnNonDict(childElem, options);
// Don't take over notNull from leaf elements

@@ -291,3 +292,3 @@ delete flatElem.notNull;

// Fix all collected flat elements (names, annotations, properties, origin ..)
Object.values(result).forEach(flatElem => {
forEach(result, (name, flatElem) => {
// Copy annotations from struct (not overwriting, because deep annotations should have precedence)

@@ -420,7 +421,7 @@ copyAnnotations(elem, flatElem, false);

// This changes the order - to be discussed!
node.elements = cloneCsn(finalBaseType, options).elements; // copy elements
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 = cloneCsn(finalBaseType.items, options); // copy items
node.items = cloneCsnNonDict(finalBaseType.items, options); // copy items
delete node.type;

@@ -439,8 +440,4 @@ } else {

if (typeDef.items || typeDef.elements) {
// Expand structured types on entity elements for flat OData
if(!(options.transformation === 'hdbcds' || options.toSql))
return;
// cloneCsn only works correctly if we start "from the top"
const cloneTypeDef = cloneCsn(typeDef, options);
const cloneTypeDef = cloneCsnNonDict(typeDef, options);
// With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.

@@ -908,3 +905,3 @@ if(typeDef.items) {

path.push('elements', null);
Object.entries(elements).forEach(([name, obj]) => {
forEach(elements, (name, obj) => {
path[path.length - 1] = name;

@@ -911,0 +908,0 @@ recurseElements(obj, path, callback);

@@ -140,5 +140,7 @@ 'use strict';

function needsCoreComputed(column) {
return column && ( column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET || column.ref && column.ref[0] in {
$at: 1, $now: 1, $user: 1, $session: 1,
});
return column &&
(
column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])
);
}

@@ -145,0 +147,0 @@

@@ -291,3 +291,7 @@ 'use strict';

},
items: (parent, prop, items) => propagateMemberPropsFromOrigin(items, { '@': true, doc: true }),
items: (parent, prop, items) => {
// items in items must be propagated
// `specialItemsRule()` does not cover this case
propagateMemberPropsFromOrigin(items, { '@': true, doc: true }, { items: onlyWithTypeRef });
},
elements: (parent, prop, elements) => {

@@ -362,2 +366,20 @@ forEachValue(elements, (e) => {

}
/**
* Some properties must only be copied over if the target
* is a type reference pointing to an element of a type or
* an aspect.
*
* @param {string} prop
* @param {CSN.Element} target
* @param {CSN.Element} source
*/
function onlyWithTypeRef(prop, target, source) {
const typeIsTypeRef = Boolean(typeof target.type === 'object' && target.type.ref);
if (typeIsTypeRef) {
const referencedArtifact = csn.definitions[target.type.ref[0]];
if (referencedArtifact.kind in { type: true, aspect: true })
target[prop] = source[prop];
}
}
}

@@ -369,3 +391,3 @@

*
* @param {Object} art Target object for propagation
* @param {object} art Target object for propagation
*/

@@ -421,3 +443,3 @@ function propagateOnArtifactLevel(art) {

// We need to propagate .elements to type artifacts - but our direct origin might not have .elements,
// because they are not propagated to members. We check if our root had elements (so we know that we should have some aswell)
// because they are not propagated to members. We check if our root had elements (so we know that we should have some as well)
// and then walk the origin-chain until we find the first .elements

@@ -587,3 +609,3 @@ if (definition.kind === 'type' && root.elements && !definition.elements) {

* @param {object} [except=null] array of properties which should not be propagated
* @param {object} [force=null] Force propagation of the contained keys via rule "always"
* @param {object} [force=null] Force propagation of the contained keys via a custom rule.
*/

@@ -597,3 +619,3 @@ function copyProperties(from, to, getCustomRule, except = null, force = null) {

if (!(key in to)) {
const func = force && force[key] ? always : getCustomRule(key);
const func = force && force[key] ? force[key] : getCustomRule(key);
if (func)

@@ -600,0 +622,0 @@ func(key, to, from);

@@ -31,9 +31,14 @@ // Custom resolve functionality for the CDS compiler

* @param {string} modulePath
* @param {CSN.Options} options
* @returns {string}
*/
function adaptCdsModule(modulePath) {
// eslint-disable-next-line
if (global['cds'] && global['cds'].home && modulePath.startsWith( '@sap/cds/' ))
function adaptCdsModule(modulePath, options = {}) {
if (modulePath.startsWith( '@sap/cds/' )) {
if (options.cdsHome)
return options.cdsHome + modulePath.slice(8);
// eslint-disable-next-line
return global['cds'].home + modulePath.slice(8)
if (global['cds'] && global['cds'].home)
// eslint-disable-next-line
return global['cds'].home + modulePath.slice(8);
}
return modulePath;

@@ -46,2 +51,3 @@ }

* @param {CSN.Options} options
* @param {object} messageFunctions
*/

@@ -63,3 +69,3 @@ function resolveModule( dep, fileCache, options, messageFunctions ) {

return new Promise( (fulfill, reject) => {
const lookupPath = adaptCdsModule(dep.module);
const lookupPath = adaptCdsModule(dep.module, options);
resolveCDS( lookupPath, opts, (err, res) => {

@@ -111,2 +117,3 @@ // console.log('RESOLVE', dep, res, err)

* @param {CSN.Options} options
* @param {object} messageFunctions
*/

@@ -125,3 +132,3 @@ function resolveModuleSync( dep, fileCache, options, messageFunctions ) {

let error = null;
const lookupPath = adaptCdsModule(dep.module);
const lookupPath = adaptCdsModule(dep.module, options);

@@ -128,0 +135,0 @@ resolveCDS( lookupPath, opts, (err, res) => {

{
"name": "@sap/cds-compiler",
"version": "2.13.8",
"version": "2.15.2",
"description": "CDS (Core Data Services) compiler and backends",

@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/",

@@ -12,4 +12,5 @@ {

"redirected-to-unrelated",
"rewrite-not-supported"
"rewrite-not-supported",
"syntax-expected-integer"
]
}

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 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