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 3.0.0 to 3.0.2

25

bin/cdsc.js

@@ -66,5 +66,2 @@ #!/usr/bin/env node

switch (key) {
case 'names':
options.sqlMapping = value;
break;
case 'user':

@@ -77,8 +74,2 @@ if (!options.variableReplacements)

break;
case 'dialect':
options.sqlDialect = value;
break;
case 'version':
options.odataVersion = value;
break;
case 'locale':

@@ -94,5 +85,2 @@ if (!options.variableReplacements)

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

@@ -164,3 +152,3 @@ options[key] = value;

// Internally, parseCdl is an option so we map the command to it.
// Internally, parseCdl/parseOnly are options, so we map the command to it.
if (cmdLine.command === 'parseCdl') {

@@ -171,2 +159,7 @@ cmdLine.command = 'toCsn';

}
else if (cmdLine.command === 'parseOnly') {
cmdLine.command = 'toCsn';
cmdLine.options.parseOnly = true;
cmdLine.args.files = [ cmdLine.args.file ];
}

@@ -533,3 +526,3 @@ if (cmdLine.options.directBackend)

}
else if (!options.lintMode) {
else if (!options.parseOnly) { // no output if parseOnly but not rawOutput
const csn = compactModel(xsn, options);

@@ -554,3 +547,3 @@ if (command === 'toCsn' && options.withLocalized)

}
else if (!options.lintMode && !options.internalMsg) {
else if (!options.internalMsg) {
if (command === 'toCsn' && options.withLocalized)

@@ -568,3 +561,3 @@ addLocalizationViews(csn, options);

function writeToFileOrDisplay(dir, fileName, content, omitHeadline = false) {
if (options.lintMode && !options.rawOutput || options.internalMsg)
if (options.internalMsg)
return;

@@ -571,0 +564,0 @@ fileName = fileName.replace(/[:/\\]/g, '_');

@@ -10,2 +10,18 @@ # ChangeLog for cds compiler and backends

## Version 3.0.2 - 2022-07-05
### Fixed
- to.sql: For `sqlDialect` `plain`, `$now` is replaced by `CURRENT_TIMESTAMP` again.
- compiler:
+ Don't crash if a USING filename is invalid on the operating system, e.g. if `\0` is used.
+ Info messages for annotations on localized convenience views are only emitted for unknown ones.
+ Improve error message for an `extend` statement which had added a new element
and tried to extend that element further. Similarly for a new action.
(If you consider this really of any use, use two `extend` statements.)
- for.odata: expand `@readonly`/`@insertonly` on aspects as for entities into `@Capabilities`.
- to.edm(x):
+ Exclude managed association as primary key on value list annotation preprocessing.
+ Don't render annotations for aspects defined in a service.
## Version 3.0.0 - 2022-06-23

@@ -16,3 +32,5 @@

- Instead of requiring all files on startup, they are required on an as-needed basis to reduce startup times.
- Allow `*` as argument in SQL functions `count`, `min`, `max`, `sum`, `avg`, `stddev`, `var`.
- CDL parser: support SQL functions `locate_regexpr`, `occurrences_regexpr`,
`replace_regexpr` and `substring_regexpr` with their special argument syntax.
- CDL parser: the names `trim` and `extract` are not reserved anymore.

@@ -23,12 +41,13 @@ ### Changed

- `compile()` and its derivates now use `fs.realpath.native()` instead of `fs.realpath()`.
- Multi-line doc comments without leading `*` were inconsistently trimmed.
- CDL parser:
+ Multi-line doc comments without leading `*` were inconsistently trimmed.
+ As before, a structure value for an annotation assignment in a CDL source is flattened,
i.e. `@Anno: { foo: 1, @bar: 2 }` becomes `@Anno.foo: 1 @Anno.@bar: 2`.
Now, the structure property name `$value` is basically ignored:
`@Anno: { $value: 1, @bar: 2 }` becomes `@Anno: 1 @Anno.@bar: 2`.
The advantage is that overwriting or appending the annotation value works as expected.
+ Keywords `not null` is only valid after `many String enum {...}` and no longer after `String`.
- `@cds.persistence.skip` and `@cds.persistence.exists` are both copied to generated child artifacts
such as localized convenience views, texts entities and managed compositions.
- As before, a structure value for an annotation assignment in a CDL source is flattened,
i.e. `@Anno: { foo: 1, @bar: 2 }` becomes `@Anno.foo: 1 @Anno.@bar: 2`.
Now, the structure property name `$value` is basically ignored:
`@Anno: { $value: 1, @bar: 2 }` becomes `@Anno: 1 @Anno.@bar: 2`.
The advantage is that overwriting or appending the annotation value works as expected.
- Update OData vocabularies 'Common', 'UI'.
- Keywords `not null` is only valid after `many String enum {...}` and no longer after `String`.
- (Sub-)Elements of localized convenience views can now be annotated, e.g. `annotate localized.E:elem`.

@@ -42,3 +61,4 @@ - `getArtifactCdsPersistenceName` now enforces the `csn` argument and can optionally have the `sqlDialect` passed in.

- Keyword `masked`.
- `*` as generic argument to SQL functions/expressions.
- CDL parser: `*` is not parsed anymore as argument to all SQL functions;
it is now only allowed for `count`, `min`, `max`, `sum`, `avg`, `stddev`, `var`.
- All non-SNAPI options.

@@ -45,0 +65,0 @@

@@ -230,2 +230,46 @@ /** @module API */

/**
* Render the given deltaCSN as SQL.
*
* @param {CSN.Model} csn A clean input CSN
* @param {CSN.Model} deltaCsn A CSN representing new entities and extensions
* @param {SqlOptions} [options={}] Options
* @returns {object} - definitions: An array of objects with all artifacts in the after-image. Each object specifies
* the artifact filename, the suffix, and the corresponding SQL statement to create
* the artifact.
* - deletions: An array of objects with the deleted artifacts. Each object specifies the artifact
* filename and the suffix.
* - migrations: An array of objects with the changed (migrated) artifacts. Each object specifies the
* artifact filename, the suffix, and the changeset (an array of changes, each specifying
* whether it incurs potential data loss, and its respective SQL statement(s), with
* multiple statements concatenated as a multi-line string in case the change e.g.
* consists of a column drop and add).
*/
function mtx(csn, deltaCsn, options = {}) {
if (!baseModel.isBetaEnabled(options, 'to.mtx'))
throw new Error('to.mtx is only available with beta flag `to.mtx`');
const internalOptions = prepareOptions.to.sql(options);
// TODO: Use compiler.compileSources() when this function is moved to lib/main.js
const merged = toCsn.compactModel(compiler.compileSourcesX({ 'base.csn': csn, 'delta.csn': deltaCsn }), options);
const baseSql = forSql(csn, options);
const mergedSql = forSql(merged, options);
const diff = modelCompare.compareModels(baseSql, mergedSql, options);
// Delete artifacts that are already present in csn
Object.keys(baseSql.definitions).forEach((artifactName) => {
if (diff.definitions[artifactName]) // don't render again, but need info for primary key extension
diff.definitions[artifactName]['@cds.persistence.skip'] = true;
});
internalOptions.forHana = true;
internalOptions.beta.sqlExtensions = true;
const { deletions, migrations, ...additions } = toSql.toSqlDdl(diff, internalOptions);
return {
additions: createSqlDefinitions(additions, mergedSql),
deletions: createSqlDeletions(deletions, baseSql),
migrations: createSqlMigrations(migrations, mergedSql),
};
}
/**
* Process the given CSN into HDI artifacts.

@@ -361,46 +405,52 @@ *

afterImage,
definitions: createDefinitions(),
deletions: createDeletions(),
migrations: createMigrations(),
definitions: createSqlDefinitions(hdbkinds, afterImage),
deletions: createSqlDeletions(deletions, beforeImage),
migrations: createSqlMigrations(migrations, afterImage),
};
}
/**
* From the given HDI artifacts, create the the correct result structure.
*
* @returns {object[]} Array of objects, each having: name, suffix and sql
*/
function createDefinitions() {
const result = [];
forEach(hdbkinds, (kind, artifacts) => {
const suffix = `.${ kind }`;
forEach(artifacts, (name, sqlStatement) => {
if ( kind !== 'hdbindex' )
result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
else
result.push({ name, suffix, sql: sqlStatement });
});
/**
* From the given SQLs, create the the correct result structure.
*
* @param {object} hdbkinds
* @param {CSN.Model} afterImage
* @returns {object[]} Array of objects, each having: name, suffix and sql
*/
function createSqlDefinitions(hdbkinds, afterImage) {
const result = [];
forEach(hdbkinds, (kind, artifacts) => {
const suffix = `.${ kind }`;
forEach(artifacts, (name, sqlStatement) => {
if ( kind !== 'hdbindex' )
result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
else
result.push({ name, suffix, sql: sqlStatement });
});
return result;
}
/**
* From the given deletions, create the correct result structure.
*
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
*/
function createDeletions() {
const result = [];
forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
return result;
}
/**
* From the given migrations, create the correct result structure.
*
* @returns {object[]} Array of objects, each having: name, suffix and changeset.
*/
function createMigrations() {
const result = [];
forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
return result;
}
});
return result;
}
/**
* From the given deletions, create the correct result structure.
*
* @param {object} deletions
* @param {CSN.Model} beforeImage
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
*/
function createSqlDeletions(deletions, beforeImage) {
const result = [];
forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
return result;
}
/**
* From the given migrations, create the correct result structure.
*
* @param {object} migrations
* @param {CSN.Model} afterImage
* @returns {object[]} Array of objects, each having: name, suffix and changeset.
*/
function createSqlMigrations(migrations, afterImage) {
const result = [];
forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
return result;
}

@@ -654,2 +704,4 @@ hdi.migration = hdiMigration;

for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
/** beta - WIP */
mtx: publishCsnProcessor(mtx, 'to.mtx'),
/** Deprecated, will be removed in cds-compiler@v4 */

@@ -656,0 +708,0 @@ preparedCsnToEdmx,

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

// Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
// in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
// in rule unreserved_keyword_column (=…_common - 'CONSTRAINT') in
// ptime/query/parser/syntax/qp_gram.y of the HANA sources.

@@ -213,2 +213,4 @@ hana: [

'ABAP_XSTRING',
'ABS',
'ACOS',
'ADD_DAYS',

@@ -218,4 +220,6 @@ 'ADD_MONTHS',

'ADD_YEARS',
'ADJACENCY',
'ADOPT',
'ALL',
'ALPHANUM',
'ALTER',

@@ -228,3 +232,7 @@ 'ANALYTIC',

'AS',
'ASCII',
'ASIN',
'AT',
'ATAN',
'ATAN2',
'AUTHORIZATION',

@@ -247,2 +255,5 @@ 'AUTO',

'BIND_REAL',
'BINTEXT',
'BINTOHEX',
'BITAND',
'BLOB',

@@ -257,5 +268,21 @@ 'BOOLEAN',

'CAST',
'CEIL',
'CEILING',
'CE_AGGREGATION',
'CE_CALC',
'CE_CALC_VIEW',
'CE_COLUMN_TABLE',
'CE_COMM2R',
'CE_CONVERSION',
'CE_FULL_OUTER_JOIN',
'CE_JOIN',
'CE_JOIN_VIEW',
'CE_LEFT_OUTER_JOIN',
'CE_MERGE',
'CE_OLAP_VIEW',
'CE_PARTITION',
'CE_PROJECTION',
'CE_RIGHT_OUTER_JOIN',
'CE_UNION_ALL',
'CE_VERTICAL_UNION',
'CHAR',

@@ -271,4 +298,8 @@ 'CHARACTER',

'CONSTRAINT',
'COS',
'COSH',
'COT',
'COUNT',
'CROSS',
'CS_ALPHANUM',
'CS_DATE',

@@ -295,2 +326,3 @@ 'CS_DAYDATE',

'CS_TIME',
'CS_ZONE',
'CUBE',

@@ -324,2 +356,3 @@ 'CUME_DIST',

'DDIC_ACCP',
'DDIC_ALNM',
'DDIC_CDAY',

@@ -385,2 +418,3 @@ 'DDIC_CHAR',

'EXISTS',
'EXP',
'EXTRACT',

@@ -392,2 +426,3 @@ 'FALSE',

'FLOAT',
'FLOOR',
'FOR',

@@ -404,2 +439,3 @@ 'FORCE_FIRST_PASSWORD_CHANGE',

'GROUPING_ID',
'GROUP_SCORE',
'HASANYPRIVILEGES',

@@ -418,2 +454,3 @@ 'HASSYSTEMPRIVILEGE',

'HIERARCHY_TEMPORAL',
'HIGHLIGHTED',
'HILBERT',

@@ -442,2 +479,3 @@ 'HOST',

'LAG',
'LANGUAGE',
'LAST_DAY',

@@ -447,2 +485,3 @@ 'LAST_VALUE',

'LAYOUT',
'LCASE',
'LEAD',

@@ -457,6 +496,9 @@ 'LEADING',

'LIMIT',
'LN',
'LOCATE',
'LOCATE_REGEXPR',
'LOG',
'LONGDATE',
'LOOP',
'LOWER',
'LPAD',

@@ -469,5 +511,7 @@ 'LTRIM',

'MEASURES',
'MIMETYPE',
'MIN',
'MINUS',
'MINUTE',
'MOD',
'MONTH',

@@ -506,5 +550,7 @@ 'MULTIPARENT',

'PLAIN',
'POWER',
'PRIOR',
'PRODUCT',
'RANGE',
'RANGE_RESTRICTION',
'RANK',

@@ -546,7 +592,13 @@ 'RAW',

'SET',
'SHORTTEXT',
'SIBLING',
'SIGN',
'SIN',
'SINH',
'SMALLDECIMAL',
'SMALLINT',
'SNIPPETS',
'SOME',
'SQL',
'SQRT',
'START',

@@ -559,2 +611,3 @@ 'STDDEV',

'ST_ALPHASHAPEEDGEAGGR',
'ST_ASMVT',
'ST_ASSVGAGGR',

@@ -575,2 +628,3 @@ 'ST_CIRCULARSTRING',

'ST_GEOMETRYCOLLECTION',
'ST_GEOMFROMESRIJSON',
'ST_GEOMFROMEWKB',

@@ -586,2 +640,4 @@ 'ST_GEOMFROMEWKT',

'ST_MAKELINE',
'ST_MAKEPOLYGON',
'ST_MEMORY_LOB',
'ST_MULTILINESTRING',

@@ -611,5 +667,8 @@ 'ST_MULTIPOINT',

'TABLESAMPLE',
'TAN',
'TANH',
'TARGET',
'TEMPORARY',
'TEXT',
'TEXT_FILTER',
'THEN',

@@ -640,2 +699,3 @@ 'THREAD',

'TO_DATE',
'TO_DATS',
'TO_DECIMAL',

@@ -669,5 +729,8 @@ 'TO_DOUBLE',

'TRUE',
'UCASE',
'UNICODE',
'UNION',
'UNKNOWN',
'UNNEST',
'UPPER',
'USER',

@@ -674,0 +737,0 @@ 'USING',

@@ -179,2 +179,11 @@ // Central registry for messages.

'old-anno-duplicate': 'anno-duplicate', // Example
// These IDs are used by large stakeholders. If we change them, we should
// be backward-compatible.
// 'redirected-to-complex': 'TODO',
// 'wildcard-excluding-one': 'TODO',
// 'wildcard-excluding-many': 'TODO',
// 'assoc-outside-service': 'TODO',
// 'redirected-to-same': 'TODO',
// 'query-navigate-many': 'TODO',
});

@@ -201,3 +210,3 @@

std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',

@@ -272,2 +281,8 @@ locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',

},
'syntax-duplicate-annotate': 'You shouldn\'t refer to $(NAME) repeatedly in the same annotate statement',
'syntax-duplicate-extend': {
std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
define: 'You can\'t refer to $(NAME) in the same extend statement where it was defined',
extend: 'You can\'t refer to $(NAME) repeatedly in the same extend statement',
},
'syntax-invalid-literal': {

@@ -318,2 +333,3 @@ 'std': 'Invalid literal',

select: 'Unexpected $(KEYWORD) for type references in queries',
annotation: '$(KEYWORD) can\'t be used in annotation definitions',
},

@@ -320,0 +336,0 @@

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

const isCsnPath = (typeof location[0] === 'string');
const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
if (isCsnPath) {

@@ -1178,3 +1178,3 @@ return [

return 'using:' + quoted( art.name.id );
else if (art.kind === 'extend')
else if (art.kind === 'extend' || art.kind === 'annotate')
return !absoluteOnly && homeNameForExtend ( art );

@@ -1194,6 +1194,7 @@ else if (art.name._artifact) // block, extend, annotate

function homeNameForExtend( art ) {
const kind = art.kind || 'extend';
// TODO: fix the following - do like in collectArtifactExtensions() or
// basically resolveUncheckedPath()
const absoluteName = (art.name.id ? art.name.id :
art.name.path.map(s => s && s.id).join('.'));
// basically resolveUncheckedPath()
const absoluteName = art.name.id ? art.name.id :
(!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));

@@ -1203,23 +1204,28 @@ // Surrounding parent may be another extension.

if (!parent)
return 'extend:' + quoted(absoluteName);
return kind + ':' + quoted(absoluteName);
// And that extension's artifact could have been found.
const parentArt = parent.name && parent.name._artifact;
if (!parentArt)
return artName(parent) + '/' + quoted(absoluteName);
if (art.name.param && parent.params) {
const fakeArt = { kind: 'param', name: { param: absoluteName } };
return homeNameForExtend(parent) + '/' + artName(fakeArt);
}
else if (art.name.action && parent.actions) {
const type = art.name._artifact?.kind || 'action';
const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
return homeNameForExtend(parent) + '/' + artName(fakeArt);
}
else if (parent.enum || parent.elements || parent.returns?.elements) {
// For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
// so we need to look at the parent artifact.
// For `extend <art> with enum`, there is `enum`.
const parentArt = parent.name?._artifact;
const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
let parentOfElementChain = parent;
while (parentOfElementChain.name?.element && parentOfElementChain._parent)
parentOfElementChain = parentOfElementChain._parent;
let extensionName;
if (parentArt.enum || parentArt.elements) {
const fakeArt = {
kind: parentArt.enum ? 'enum' : 'element',
name: { element: absoluteName }
};
extensionName = artName(fakeArt);
return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
}
else {
extensionName = 'extend:' + quoted(absoluteName);
}
// Even though the parent artifact was found, we use kind 'extend'
// to make it clear that we are inside an (element) extension.
return 'extend:' + artName(parentArt) + '/' + extensionName;
// This case should not happen, but just in case
return kind + ':' + artName(parent);
}

@@ -1237,7 +1243,11 @@

let { query } = analyseCsnPath(
csnPath,
model
);
if (csnPath[0] === 'extensions') {
const ext = model.extensions && model.extensions[csnPath[1]] || {};
if (ext.annotate)
return 'annotate:' + quoted(ext.annotate);
return 'extend:' + quoted(ext.extend);
}
let { query } = analyseCsnPath(csnPath, model, false);
// remove definitions

@@ -1244,0 +1254,0 @@ csnPath.shift();

@@ -156,2 +156,3 @@ 'use strict'

* or in the given command.
*
* @private

@@ -161,28 +162,58 @@ * @see option()

function _addOption(cmd, optString, validValues, options) {
const opt = _parseOptionString(optString, validValues);
Object.assign(opt, options);
const cliOpt = _parseOptionString(optString, validValues);
Object.assign(cliOpt, options);
_addLongOption(cmd, cliOpt.longName, cliOpt);
_addShortOption(cmd, cliOpt.shortName, cliOpt);
if (cmd.options[opt.longName]) {
throw new Error(`Duplicate assignment for long option ${opt.longName}`);
} else if (optionProcessor.options[opt.longName]) {
for (const alias of cliOpt.aliases || []) {
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
_addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
}
return cmd;
}
/**
* Internal: Add longName to the list of options.
* Throws if the option is already registered in the given command context.
* or in the given command.
* `longName` may differ from `opt.longName`, e.g. for aliases.
*
* @private
* @see _addOption()
*/
function _addLongOption(cmd, longName, opt) {
if (cmd.options[longName]) {
throw new Error(`Duplicate assignment for long option ${longName}`);
} else if (optionProcessor.options[longName]) {
// This path is only taken if optString is for commands
optionProcessor.optionClashes.push({
option: opt.longName,
description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
option: longName,
description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
});
}
cmd.options[opt.longName] = opt;
if (opt.shortName) {
if (cmd.options[opt.shortName]) {
throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
} else if (optionProcessor.options[opt.shortName]) {
// This path is only taken if optString is for commands
optionProcessor.optionClashes.push({
option: opt.shortName,
description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
});
}
cmd.options[opt.shortName] = opt;
cmd.options[longName] = opt;
}
/**
* Internal: Add shortName to the list of options.
* Throws if the option is already registered in the given command context.
* or in the given command.
* `longName` may differ from `opt.longName`, e.g. for aliases.
*
* @private
* @see _addOption()
*/
function _addShortOption(cmd, shortName, opt) {
if (!shortName)
return;
if (cmd.options[shortName]) {
throw new Error(`Duplicate assignment for short option ${shortName}`);
} else if (optionProcessor.options[shortName]) {
// This path is only taken if optString is for commands
optionProcessor.optionClashes.push({
option: shortName,
description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
});
}
return cmd;
cmd.options[shortName] = opt;
}

@@ -280,3 +311,4 @@

param,
validValues
validValues,
isAlias: false, // default
}

@@ -283,0 +315,0 @@ }

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

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

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

// The builtin artifacts of CDS
// TODO: split this file
// - in base/: common definitions
// - in base/: common definitions, datetime formats
// - in compiler/: XSN-specific

@@ -196,2 +196,40 @@ // - in ?: CSN-specific

/**
* Patterns for literal token tests and creation. The value is a map from the
* `prefix` argument of function `quotedliteral` to the following properties:
* - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
* - `test_fn`: function called with argument `value`, fails falsy return value
* - `test_re`: regular expression, fails if it does not match argument `value`
* - `unexpected_msg`: error message which is issued if `unexpected_char` matches
* - `unexpected_char`: regular expression matching an illegal character in `value`,
* the error location is only correct for a literal <prefix>'<value>'
* - `literal`: the value which is used instead of `prefix` in the AST
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),
* but always allow Feb 29 (no leap year computation)
*/
const quotedLiteralPatterns = {
x: {
test_variant: 'uneven-hex',
test_fn: (str => Number.isInteger(str.length / 2)),
unexpected_variant: 'invalid-hex',
unexpected_char: /[^0-9a-f]/i,
json_type: 'string',
},
time: {
test_variant: 'time',
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
json_type: 'string',
},
date: {
test_variant: 'date',
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
json_type: 'string',
},
timestamp: {
test_variant: 'timestamp',
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
json_type: 'string',
},
};
/** All types belong to one category. */

@@ -407,2 +445,3 @@ const typeCategories = {

specialFunctions,
quotedLiteralPatterns,
initBuiltins,

@@ -409,0 +448,0 @@ isInReservedNamespace,

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

if (!name.absolute)
name.absolute = prefix + name.path.map( id => id.id ).join('.');
name.absolute = prefix + pathName( name.path );
dictAdd( model.vocabularies, name.absolute, vocab );

@@ -401,3 +401,3 @@ }

function checkRedefinition( art ) {
if (!art.$duplicates)
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
return;

@@ -675,2 +675,4 @@ if (art._main) {

function approveExistsInChildren(exprOrPathElement) {
if (!exprOrPathElement) // may be null in case of parse error
return;
if (exprOrPathElement.$expected === 'exists')

@@ -677,0 +679,0 @@ exprOrPathElement.$expected = 'approved-exists';

@@ -392,3 +392,6 @@ // Extend, include, localized data and managed compositions

// TODO: make resolvePath('extend'/'annotate') ignore namespaces
if (resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
// Don't try to apply annotations in the `localized.` namespace.
// That's done in `localized.js`.
if (!name.startsWith('localized.') &&
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
// should issue error for cds extensions (annotate ok)

@@ -395,0 +398,0 @@ if (art.kind === 'namespace') {

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

struct = struct._parent;
if (struct.kind === 'select') {
if (struct.kind === 'select' || struct.kind === 'annotation') {
// `type of` in annotation definitions can't work, because csn type refs
// always refer to definitions.
message( 'type-unexpected-typeof', [ ref.location, user ],

@@ -252,0 +254,0 @@ { keyword: 'type of', '#': struct.kind } );

@@ -484,6 +484,3 @@ // Compiler phase "resolve": resolve all references

annotateMembers( ext );
for (const prop in ext) {
if (prop.charAt(0) === '@')
chooseAssignment( prop, ext );
}
chooseAnnotationsInArtifact( ext );
}

@@ -490,0 +487,0 @@

@@ -884,5 +884,14 @@ // Compiler functions and utilities shared across all phases

}
else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
art.kind !== 'function' ) {
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
// `art._block` ensures that `art` is a defined def.
warning('anno-unexpected-returns', [ construct.name.location, construct ],
{ keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
}
}
if (construct.doc)
art.doc = construct.doc; // e.g. through `extensions` array in CSN
// set _block (for layering) and $priority, shallow-copy from extension

@@ -889,0 +898,0 @@ // TODO: think of removing $priority, then

@@ -191,4 +191,4 @@ // Simple compiler utility functions

*/
function pathName(path) {
return (path.broken) ? '' : path.map( id => id.id ).join('.');
function pathName( path ) {
return (path && !path.broken) ? path.map( id => id.id ).join('.') : '';
}

@@ -195,0 +195,0 @@

'use strict';
const edmUtils = require('../edmUtils.js');
const { makeMessageFunction } = require('../../base/messages.js');
const { forEachDefinition } = require('../../model/csnUtils.js');

@@ -40,3 +40,3 @@

let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key);
let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
if (keyNames.length === 0) {

@@ -48,4 +48,3 @@ keyNames.push('MISSING');

warning(null, null, `in annotation preprocessing: target ${targetName} has multiple key elements`);
// TODO: what happens if key of target is itself a managed association?
return keyNames[0];

@@ -63,11 +62,11 @@ }

edmUtils.forAll(csn.definitions, (artifact, artifactName) => {
forEachDefinition(csn, (artifact, artifactName) => {
if(artifactName == serviceName || artifactName.startsWith(serviceName + '.')) {
art = artifactName;
handleAnnotations(artifactName, artifact);
edmUtils.forAll(artifact.elements, (element, elementName) => {
artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
handleAnnotations(elementName, element);
});
edmUtils.forAll(artifact.actions, (action) => {
edmUtils.forAll(action.params, (param, paramName) => {
artifact.actions && Object.values(artifact.actions).forEach(action => {
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
handleAnnotations(paramName, param);

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

let assoc = csn.definitions[art].elements[assocName];
if (!assoc || !(assoc.type === 'cds.Association' || assoc.type === 'cds.Composition')) {
if (!assoc || !assoc.target) {
warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);

@@ -203,3 +202,3 @@ return false;

let localDataProp = carrierName.split('/').pop();
if (edmUtils.isManagedAssociation(carrier)) {
if (carrier.target && carrier.on === undefined) {
localDataProp = localDataProp + fkSeparator + getKeyOfTargetOfManagedAssoc(carrier);

@@ -220,3 +219,3 @@ }

let valueListProp = null;
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key );
let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
if (keys.length === 0) {

@@ -223,0 +222,0 @@ warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);

@@ -333,7 +333,8 @@ 'use strict';

edmUtils.foreach(schemaCsn.definitions,
a => edmUtils.isEntity(a) && !a.abstract && a.name.startsWith(schemaNamePrefix),
a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
createEntityTypeAndSet
);
// create unbound actions/functions
edmUtils.foreach(schemaCsn.definitions, a => edmUtils.isActionOrFunction(a) && a.name.startsWith(schemaNamePrefix),
edmUtils.foreach(schemaCsn.definitions,
a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
(options.isV4()) ? createActionV4 : createActionV2);

@@ -350,3 +351,3 @@

artifact => edmUtils.isDerivedType(artifact) &&
!edmUtils.isAssociationOrComposition(artifact) &&
!artifact.target &&
artifact.name.startsWith(schemaNamePrefix),

@@ -419,3 +420,3 @@ createTypeDefinition);

if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
if(options.isV2() && p._isCollection && !p._csn.target)
warning('odata-spec-violation-array', pLoc, { version: '2.0' });

@@ -467,3 +468,3 @@

// put actions behind entity types in Schema/EntityContainer
edmUtils.forAll(entityCsn.actions, (a, n) => {
entityCsn.actions && Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
(options.isV4()) ? createActionV4(a, n, entityCsn)

@@ -529,3 +530,3 @@ : createActionV2(a, n, entityCsn)

// Parameter Nodes
edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );

@@ -581,3 +582,3 @@ const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];

const defintion = schemaCsn.definitions[rt];
if(defintion && edmUtils.isEntity(defintion))
if(defintion && defintion.kind === 'entity')
{

@@ -606,3 +607,3 @@ functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));

edmUtils.foreach(entityCsn.elements,
elementCsn => elementCsn.key && !edmUtils.isAssociationOrComposition(elementCsn),
elementCsn => elementCsn.key && !elementCsn.target,
(elementCsn, elementName) => {

@@ -615,3 +616,3 @@ functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));

// is this still required?
edmUtils.forAll(actionCsn, (v, p) => {
Object.entries(actionCsn).forEach(([p, v]) => {
if (p.match(/^@sap\./))

@@ -624,3 +625,3 @@ functionImport.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });

// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
const pLoc = [...loc, 'params', parameterName];

@@ -695,3 +696,3 @@ const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );

edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
{

@@ -701,3 +702,3 @@ if(elementCsn._edmParentCsn == undefined)

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

@@ -783,6 +784,6 @@

if(options.isV2()) {
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
if(p._isCollection && !p._csn.target)
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
if(edmUtils.isAssociationOrComposition(p._csn))
if(p._csn.target)
warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });

@@ -789,0 +790,0 @@ }

@@ -242,3 +242,3 @@ 'use strict'

const attr = (useSetAttributes ? csn._SetAttributes : csn);
edmUtils.forAll(attr, (v, p) => {
attr && Object.entries(attr).forEach(([p, v]) => {
if (p.match(/^@sap./))

@@ -336,3 +336,3 @@ this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v } );

xml += super.innerXML(indent);
edmUtils.forAll(this._actions, actionArray => {
this._actions && Object.values(this._actions).forEach(actionArray => {
actionArray.forEach(action => {

@@ -355,3 +355,3 @@ xml += action.toXML(indent, what) + '\n'; });

{
edmUtils.forAll(this._edmAttributes, (v,p) => {
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
if (p !== 'Name' && p !== 'Namespace')

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

}
edmUtils.forAll(this._actions, (actionArray, actionName) => {
this._actions && Object.entries(this._actions).forEach(([actionName, actionArray]) => {
json[actionName] = [];

@@ -744,3 +744,3 @@ actionArray.forEach(action => {

edmUtils.forAll(this._edmAttributes, (v,p) => {
this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
if (p !== 'Name' && p !== this._typeName

@@ -848,4 +848,4 @@ // remove this line if Nullable=true becomes default

// array of enum not yet allowed
let enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
edmUtils.forAll(enumValues, (e, en) => {
const enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
enumValues && Object.entries(enumValues).forEach(([en, e]) => {
this.append(new Member(v, { Name: en, Value: e.val } ));

@@ -1186,5 +1186,6 @@ });

}
edmUtils.forAll(_constraints.constraints,
c => this.append(new ReferentialConstraint(this._v,
{ Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) ) );
_constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
this.append(new ReferentialConstraint(this._v,
{ Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
);
}

@@ -1512,3 +1513,3 @@ }

super.toJSONattributes(json);
edmUtils.forAll(this._jsonOnlyAttributes, (v,p) => {
this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
json[p[0] === '@' ? p : '$' + p] = v;

@@ -1617,3 +1618,3 @@ });

edmUtils.forAll(c, cv => {
c && Object.values(c).forEach(cv => {
node._d.append(new PropertyRef(v, cv[0].join(options.pathDelimiter)));

@@ -1620,0 +1621,0 @@ node._p.append(new PropertyRef(v, cv[1].join(options.pathDelimiter)));

@@ -59,7 +59,2 @@ 'use strict';

// Call func(art, name) for each artifact 'art' with name 'name' in 'dictionary'
function forAll(dictionary, func) {
foreach(dictionary, ()=>true, func);
}
// true if _containerEntity is unequal to artifact name (non-recursive containment association)

@@ -72,8 +67,2 @@ // or if artifact belongs to an artificial parameter entity

// Return true if 'artifact' has an association type
function isAssociation(artifact) {
return (artifact.type === 'cds.Association' || artifact.type === 'Association') && artifact.target != undefined;
//return artifact.target != undefined;
}
function isComposition(artifact) {

@@ -84,12 +73,2 @@ return (artifact.type === 'cds.Composition' || artifact.type === 'Composition') &&

function isAssociationOrComposition(artifact)
{
return isAssociation(artifact) || isComposition(artifact);
}
function isManagedAssociation(artifact)
{
return isAssociation(artifact) && artifact.on == undefined;
}
// Return true if the association 'assoc' has cardinality 'to-many'

@@ -114,9 +93,4 @@ function isToMany(assoc) {

function isEntity(artifact)
{
return artifact.kind === 'entity';
}
function isParameterizedEntity(artifact) {
return isEntity(artifact) && artifact.params;
return artifact.kind === 'entity' && artifact.params;
}

@@ -139,6 +113,2 @@

function isActionOrFunction(artifact) {
return artifact.kind === 'action' || artifact.kind === 'function';
}
function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions) {

@@ -183,3 +153,3 @@ if(!assocCsn._constraints)

}
if(isAssociationOrComposition(originAssocCsn)) {
if(originAssocCsn.target) {
// Mark this association as backlink if $self appears exactly once

@@ -354,3 +324,3 @@ // to surpress edm:Association generation in V2 mode

const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
const fk = (dependentEntity.kind === 'entity' && dependentEntity.elements[ depEltName ]) ||
(localDepEntity && localDepEntity.elements && localDepEntity.elements[ depEltName ]);

@@ -797,11 +767,6 @@ const pk = principalEntity.$keys && principalEntity.$keys[ principalEltName ];

foreach,
forAll,
isContainee,
isAssociation,
isManagedAssociation,
isComposition,
isAssociationOrComposition,
isToMany,
isSingleton,
isEntity,
isStructuredType,

@@ -811,3 +776,2 @@ isStructuredArtifact,

isDerivedType,
isActionOrFunction,
resolveOnConditionAndPrepareConstraints,

@@ -814,0 +778,0 @@ finalizeReferentialConstraints,

@@ -91,2 +91,3 @@ // CSN frontend - transform CSN into XSN

const { dictAdd } = require('../base/dictionaries');
const { quotedLiteralPatterns } = require('../compiler/builtins');

@@ -686,11 +687,2 @@ const $location = Symbol.for('cds.$location');

const validLiteralsExtra = Object.assign( Object.create(null), {
// TODO: should we use quotedLiteralPatterns from genericAntlrParser?
number: 'string',
x: 'string',
time: 'string',
date: 'string',
timestamp: 'string',
} );
// Module variables, schema compilation, and functors ------------------------

@@ -1293,12 +1285,18 @@

function literal( val, spec, xsn, csn ) {
function literal( lit, spec, xsn, csn ) {
// TODO: general: requires other property (here: 'val')
const type = (csn.val == null) ? 'null' : typeof csn.val;
if (val === type) // also for 'object' which is an error for 'val'
return val;
if (typeof val === 'string' && validLiteralsExtra[val] === type)
return val;
if (lit === type) // also for 'object' which is an error for 'val'
return lit;
if (typeof lit === 'string' && quotedLiteralPatterns[lit]?.json_type === type) {
const p = quotedLiteralPatterns[lit];
if (p && (p.test_fn && !p.test_fn(csn.val) || p.test_re && !p.test_re.test(csn.val)))
warning( 'syntax-invalid-literal', location(), { '#': p.test_variant } );
return lit;
}
if (lit === 'number' && type === 'string') // special case, not a quoted literal in CDL
return lit;
error( 'syntax-expected-valid', location(true), { prop: spec.msgProp },
'Expected valid string for property $(PROP)' );
return ignore( val );
return ignore( lit );
}

@@ -1541,3 +1539,3 @@

const group = s.xorGroup;
if (zero && expected( zero, schema[zero] ) && !(group && xor[group])) {
if (expected( zero, schema[zero] ) && !(group && xor[group])) {
replaceZeroProp( prop, zero );

@@ -1562,3 +1560,3 @@ if (group)

def: 'CSN property $(PROP) is not expected by a definition of kind $(KIND)',
extend: 'CSN property $(PROP) is not expected by an extend in $(OTHERPROP))',
extend: 'CSN property $(PROP) is not expected by an extend in $(OTHERPROP)',
annotate: 'CSN property $(PROP) is not expected by an annotate in $(OTHERPROP)',

@@ -1791,3 +1789,3 @@ } );

function augment( csn, filename = 'csn.json', options = {}, messageFunctions ) {
function augment( csn, filename = 'csn.json', options = {}, messageFunctions = {} ) {
try {

@@ -1802,3 +1800,3 @@ return toXsn( csn, filename, options, messageFunctions );

function parse( source, filename = 'csn.json', options = {}, messageFunctions ) {
function parse( source, filename = 'csn.json', options = {}, messageFunctions = {} ) {
try {

@@ -1805,0 +1803,0 @@ return augment( JSON.parse(source), filename, options, messageFunctions );

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

const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
const { pathName } = require('../compiler/utils');

@@ -1222,4 +1223,4 @@ const compilerVersion = require('../../package.json').version;

if (node.path) {
const ref = node.path.map( id => id.id ).join('.');
return extra( { '=': node.variant ? `${ ref }#${ node.variant.id }` : ref }, node );
const ref = pathName( node.path );
return extra( { '=': node.variant ? `${ ref }#${ pathName(node.variant.path) }` : ref }, node );
}

@@ -1226,0 +1227,0 @@ if (node.literal === 'enum')

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

const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
const { functionsWithoutParens, specialFunctions, quotedLiteralPatterns } = require('../compiler/builtins');
const { pathName } = require("../compiler/utils");

@@ -66,2 +67,4 @@ const $location = Symbol.for('cds.$location');

assignAnnotation,
checkExtensionDict,
handleExtension,
startLocation,

@@ -118,36 +121,2 @@ tokenLocation,

// Patterns for literal token tests and creation. The value is a map from the
// `prefix` argument of function `quotedliteral` to the following properties:
// - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
// - `test_fn`: function called with argument `value`, fails falsy return value
// - `test_re`: regular expression, fails if it does not match argument `value`
// - `unexpected_msg`: error message which is issued if `unexpected_char` matches
// - `unexpected_char`: regular expression matching an illegal character in `value`,
// the error location is only correct for a literal <prefix>'<value>'
// - `literal`: the value which is used instead of `prefix` in the AST
// TODO: we might do a range check (consider leap seconds, i.e. max value 60),
// but always allow Feb 29 (no leap year computation)
// TODO: make it a configurable error (syntax-invalid-literal)
// TODO: also use for CSN input
const quotedLiteralPatterns = {
x: {
test_variant: 'uneven-hex',
test_fn: (str => Number.isInteger(str.length / 2)),
unexpected_variant: 'invalid-hex',
unexpected_char: /[^0-9a-f]/i,
},
time: {
test_variant: 'time',
test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
},
date: {
test_variant: 'date',
test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
},
timestamp: {
test_variant: 'timestamp',
test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
},
};
// Use the following function for language constructs which we (currently)

@@ -361,3 +330,4 @@ // just being able to parse, in able to run tests from HANA CDS. As soon as we

if (name.variant) {
absolute = `${ prefix }${ pathname }#${ name.variant.id }`;
const variant = pathName( name.variant.path );
absolute = `${ prefix }${ pathname }#${ variant }`;
if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?

@@ -389,3 +359,4 @@ this.error( 'anno-duplicate-variant', [ name.variant.location ],

'Duplicate assignment with $(ANNO)' );
a.$errorReported = true; // do not report again later as anno-duplicate-xyz
a.$errorReported = 'syntax-duplicate-anno';
// do not report again later as anno-duplicate-xyz
} );

@@ -401,2 +372,32 @@ }

function checkExtensionDict( dict ) {
for (const name in dict) {
const def = dict[name];
if (!def.$duplicates)
continue;
const numDefines = (def.kind === 'annotate')
? 0
: def.$duplicates.reduce( addOneForDefinition, addOneForDefinition( 0, def ) );
this.handleExtension( def, name, numDefines );
for (const dup of def.$duplicates)
this.handleExtension( dup, name, numDefines );
}
}
function addOneForDefinition( count, ext ) {
return (ext.kind === 'extend') ? count : count + 1;
}
function handleExtension( ext, name, numDefines ) {
if (ext.kind === 'annotate')
this.warning( 'syntax-duplicate-annotate', [ ext.name.location ], { name } );
else if (ext.kind === 'extend')
this.error( 'syntax-duplicate-extend', [ ext.name.location ],
{ name, '#': (numDefines ? 'define' : 'extend') } );
else if (numDefines === 1)
ext.$errorReported = 'syntax-duplicate-extend'; // a definition, but not duplicate
}
/**

@@ -513,3 +514,3 @@ * Return start location of `token`, or the first token matched by the current

function unaryOpForParens( query, val ) {
const parens = query.$parens;
const parens = query?.$parens;
if (!parens)

@@ -753,6 +754,2 @@ return query;

function pathName( path, brokenName ) {
return (path && !path.broken) ? path.map( id => id.id ).join('.') : brokenName;
}
function pushIdent( path, ident, prefix ) {

@@ -992,2 +989,5 @@ if (!ident) {

function associationInSelectItem( art ) {
if (!art.value) // e.g. `expand` without value (for new structures)
return;
const isPath = art.value.path && art.value.path.length

@@ -994,0 +994,0 @@ const isIdentifier = isPath && art.value.path.length === 1;

@@ -910,2 +910,3 @@ // CSN functionality for resolving references

* @param {CSN.Model} csn
* @param {any} resolve
*/

@@ -912,0 +913,0 @@ function analyseCsnPath( csnPath, csn, resolve ) {

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

function getElementDatabaseNameOf(elemName, sqlMapping, sqlDialect='plain') {
isValidMappingDialectCombi(sqlMapping, sqlDialect)
isValidMappingDialectCombi(sqlDialect, sqlMapping)
if (sqlMapping === 'hdbcds') {

@@ -1284,3 +1284,9 @@ return elemName;

* @param {CSN.Model} csn
* @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
* @param {{notFound?: (name: string, index: number) => void, override?: boolean, filter?: (name: string) => boolean}} config
* notFound: Function that is called if the referenced definition can't be found.
* Second argument is index in `csn.extensions` array.
* override: Whether to ignore existing annotations.
* filter: Positive filter. If it returns true, annotations for the referenced artifact
* will be applied.
* @todo Improve to also apply element annotations
*/

@@ -1292,8 +1298,13 @@ function applyAnnotationsFromExtensions(csn, config) {

const filter = config.filter || ((_name) => true);
for (const ext of csn.extensions) {
for (let i = 0; i < csn.extensions.length; ++i) {
const ext = csn.extensions[i];
const name = ext.annotate || ext.extend;
const def = csn.definitions[name];
if (name && def && filter(name)) {
copyAnnotationsAndDoc(ext, def, config.overwrite);
applyAnnotationsToElements(ext, def);
if (name && filter(name)) {
const def = csn.definitions[name];
if (def) {
copyAnnotationsAndDoc(ext, def, config.override);
applyAnnotationsToElements(ext, def);
} else if (config.notFound) {
config.notFound(name, i);
}
}

@@ -1315,3 +1326,3 @@ }

if (targetElem) {
copyAnnotationsAndDoc(sourceElem, targetElem, config.overwrite);
copyAnnotationsAndDoc(sourceElem, targetElem, config.override);
applyAnnotationsToElements(sourceElem, targetElem);

@@ -1318,0 +1329,0 @@ }

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

if (path.length === 1) {
return reveal( xsn.definitions[path] || xsn.vocabularies && xsn.vocabularies[path] );
const def = xsn.definitions?.[path[0]] || xsn.vocabularies?.[path[0]];
if (!def)
throw new Error(`reveal xsn: Unknown definition: “${path[0]}”`)
return reveal( def );
}

@@ -137,0 +140,0 @@

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

.option(' --cds-home <dir>')
.option(' --lint-mode')
.option(' --fuzzy-csn-error')
.option(' --trace-parser')
.option(' --trace-parser-amb')
.option(' --trace-fs')

@@ -39,3 +36,2 @@ .option(' --error <id-list>')

.option(' --direct-backend')
.option(' --parse-only')
.option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])

@@ -83,3 +79,2 @@ .option(' --test-mode')

--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
--lint-mode Generate nothing, just produce messages if any (for use by editors)
--fuzzy-csn-error Report free-style CSN properties as errors

@@ -93,4 +88,2 @@ -- Indicate the end of options (helpful if source names start with "-")

Diagnostic options
--trace-parser Trace parser
--trace-parser-amb Trace parser ambiguities
--trace-fs Trace file system access caused by "using from"

@@ -119,3 +112,2 @@

eagerPersistenceForGeneratedEntities
--parse-only Stop compilation after parsing and write result to <stdout>
--fallback-parser <type> If the language cannot be deduced by the file's extensions, use this

@@ -149,2 +141,4 @@ parser as a fallback. Valid values are:

explain <message-id> Explain a compiler message.
parseOnly [options] <files...> (internal) Stop compilation after parsing, write messages to <stderr>,
per default no output.
toRename [options] <files...> (internal) Generate SQL DDL rename statements

@@ -158,3 +152,3 @@ manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to

.option('-h, --help')
.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
.option(' --render-virtual')

@@ -176,3 +170,3 @@ .option(' --joinfk')

-h, --help Show this help text
-n, --names <style> Naming style for generated entity and element names:
-n, --sql-mapping <style> Naming style for generated entity and element names:
plain : (default) Produce HANA entity and element names in

@@ -208,3 +202,3 @@ uppercase and flattened with underscores. Do not generate

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

@@ -219,3 +213,3 @@ .option('-j, --json')

.option('-f, --odata-format <format>', ['flat', 'structured'])
.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
.option('-s, --service-names <list>')

@@ -245,9 +239,9 @@ .help(`

(Not spec compliant and V2 only)
-n, --names <style> Annotate artifacts and elements with "@cds.persistence.name", which is
the corresponding database name (see "--names" for "toHana or "toSql")
plain : (default) Names in uppercase and flattened with underscores
quoted : Names in original case as in CDL. Entity names with dots,
but element names flattened with underscores
hdbcds : Names as HANA CDS would generate them from the same CDS
source (like "quoted", but using element names with dots)
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
plain : (default) Names in uppercase and flattened with underscores
quoted : Names in original case as in CDL. Entity names with dots,
but element names flattened with underscores
hdbcds : Names as HANA CDS would generate them from the same CDS
source (like "quoted", but using element names with dots)
-s, --service-names <list> List of comma-separated service names to be rendered

@@ -270,6 +264,6 @@ (default) empty, all services are rendered

.option('-h, --help')
.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'], { aliases: [ '--dialect' ] })
.option(' --render-virtual')
.option(' --joinfk')
.option('-d, --dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'])
.option('-u, --user <user>')

@@ -291,3 +285,3 @@ .option('-l, --locale <locale>')

-h, --help Show this help text
-n, --names <style> Naming style for generated entity and element names:
-n, --sql-mapping <style> Naming style for generated entity and element names:
plain : (default) Produce SQL table and view names in

@@ -305,3 +299,3 @@ flattened with underscores format (no quotes required)

--joinfk Create JOINs for foreign key accesses
-d, --dialect <dialect> SQL dialect to be generated:
-d, --sql-dialect <dialect> SQL dialect to be generated:
plain : (default) Common SQL - no assumptions about DB restrictions

@@ -337,3 +331,3 @@ hana : SQL with HANA specific language features

.option('-h, --help')
.option('-n, --names <style>', ['quoted', 'hdbcds'])
.option('-n, --sql-mapping <style>', ['quoted', 'hdbcds'], { aliases: ['--names'] })
.help(`

@@ -344,13 +338,14 @@ Usage: cdsc toRename [options] <files...>

"storedProcedure.sql" that allows to rename existing tables and their columns so that they
match the result of "toHana" or "toSql" with the "--names plain" option.
match the result of "toHana" or "toSql" with the "--sql-mapping plain" option.
Options
-h, --help Display this help text
-n, --names <style> Assume existing tables were generated with "--names <style>":
-n, --sql-mapping <style>
Assume existing tables were generated with "--sql-mapping <style>":
quoted : Assume existing SQL tables and views were named in original
case as in CDL (with dots), but column names were flattened
with underscores (e.g. resulting from "toHana --names quoted")
with underscores (e.g. resulting from "toHana --sql-mapping quoted")
hdbcds : (default) Assume existing SQL tables, views and columns were
generated by HANA CDS from the same CDS source (or resulting
from "toHana --names hdbcds")
from "toHana --sql-mapping hdbcds")
`);

@@ -360,3 +355,3 @@

.option('-h, --help')
.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
.option('-s, --src <style>', ['sql', 'hdi'])

@@ -377,3 +372,4 @@ .option(' --drop')

-h, --help Display this help text
-n, --names <style> Assume existing tables were generated with "--names <style>":
-n, --sql-mapping <style>
Assume existing tables were generated with "--sql-mapping <style>":
plain : (default) Assume SQL tables were flattened and dots were

@@ -400,3 +396,3 @@ replaced by underscores

.option('-h, --help')
.option('-f, --flavor <flavor>', ['client', 'gensrc', 'universal'])
.option('-f, --csn-flavor <flavor>', ['client', 'gensrc', 'universal'], { aliases: ['--flavor'] })
.option(' --with-localized')

@@ -410,9 +406,9 @@ .help(`

-h, --help Show this help text
-f, --flavor <flavor> Generate CSN in one of two flavors:
client : (default) Standard CSN consumable by clients and backends
gensrc : CSN specifically for use as a source, e.g. for
combination with additional "extend" or "annotate"
statements, but not suitable for consumption by clients or
backends
universal: in development (BETA)
-f, --csn-flavor <flavor> Generate CSN in one of two flavors:
client : (default) Standard CSN consumable by clients and backends
gensrc : CSN specifically for use as a source, e.g. for
combination with additional "extend" or "annotate"
statements, but not suitable for consumption by clients or
backends
universal: in development (BETA)

@@ -436,2 +432,22 @@ Internal options (for testing only, may be changed/removed at any time)

optionProcessor.command('parseOnly')
.option('-h, --help')
.option(' --trace-parser')
.option(' --trace-parser-amb')
.positionalArgument('<file>')
.help(`
Usage: cdsc parseOnly [options] <files...>
(internal): Stop compilation after parsing and write messages to <stderr>.
Per default, nothing is printed. With \`--raw-output +\`, XSN is printed
to <stdout>.
Options
-h, --help Show this help text
Diagnostic options
--trace-parser Trace parser
--trace-parser-amb Trace parser ambiguities
`);
optionProcessor.command('explain')

@@ -438,0 +454,0 @@ .option('-h, --help')

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

addLocalizationViews(csn, options, acceptLocalizedView);
addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });

@@ -328,3 +328,3 @@ const cleanup = validate.forOdata(csn, {

if (name === '@readonly' && node[name] !== null) {
if (node.kind === 'entity') {
if (node.kind === 'entity' || node.kind === 'aspect') {
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);

@@ -339,3 +339,3 @@ setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);

else if (name === '@insertonly' && node[name] !== null) {
if (node.kind === 'entity') {
if (node.kind === 'entity' || node.kind === 'aspect') {
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);

@@ -342,0 +342,0 @@ setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);

@@ -72,6 +72,5 @@ 'use strict';

* join in direct convenience views.
* @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view
* name and its parent name provided as parameter should be created
* @param {object} config
*/
function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = null) {
function _addLocalizationViews(csn, options, useJoins, config) {
// Don't try to create convenience views with errors.

@@ -85,2 +84,3 @@ if (hasErrors(options.messages))

const { acceptLocalizedView, ignoreUnknownExtensions } = config;
const noCoalesce = (options.localizedLanguageFallback === 'none' ||

@@ -96,4 +96,8 @@ options.localizedWithoutCoalesce);

applyAnnotationsFromExtensions(csn, {
overwrite: true,
filter: (name) => name.startsWith('localized.')
override: true,
filter: (name) => name.startsWith('localized.'),
notFound(name, index) {
if (!ignoreUnknownExtensions)
messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
},
});

@@ -614,6 +618,6 @@

* @param {CSN.Options} options
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
* @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
*/
function addLocalizationViews(csn, options, acceptLocalizedView = null) {
return _addLocalizationViews(csn, options, false, acceptLocalizedView);
function addLocalizationViews(csn, options, config = {}) {
return _addLocalizationViews(csn, options, false, config);
}

@@ -628,6 +632,6 @@

* @param {CSN.Options} options
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
* @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
*/
function addLocalizationViewsWithJoins(csn, options, acceptLocalizedView = null) {
return _addLocalizationViews(csn, options, true, acceptLocalizedView);
function addLocalizationViewsWithJoins(csn, options, config = {}) {
return _addLocalizationViews(csn, options, true, config);
}

@@ -634,0 +638,0 @@

@@ -116,7 +116,12 @@ // Util functions for operations usually used with files.

// e.g. an error of callback functions!
reader(filename, enc, ( err, data ) => {
fileCache[filename] = err || data;
traceFS( 'READFILE:data:', filename, err || data );
cb( err, data );
});
try {
reader(filename, enc, (err, data) => {
fileCache[filename] = err || data;
traceFS('READFILE:data:', filename, err || data);
cb(err, data);
});
}
catch (err) {
cb(err); // if filename is not a valid (e.g. contains NUL byte), readFile() may throw
}
}

@@ -147,15 +152,20 @@ };

// fileCache[ filename ] before starting fs.readFile().
fsStat( filename, ( err, stat ) => {
if (err)
body = (err.code === 'ENOENT' || err.code === 'ENOTDIR') ? false : err;
else
body = !!(stat.isFile() || stat.isFIFO());
if (fileCache[filename] === undefined) // parallel readFile() has been processed
fileCache[filename] = body;
traceFS( 'ISFILE:data:', filename, body );
if (body instanceof Error)
cb( err );
else
cb( null, body );
});
try {
fsStat(filename, (err, stat) => {
if (err)
body = (err.code === 'ENOENT' || err.code === 'ENOTDIR') ? false : err;
else
body = !!(stat.isFile() || stat.isFIFO());
if (fileCache[filename] === undefined) // parallel readFile() has been processed
fileCache[filename] = body;
traceFS('ISFILE:data:', filename, body);
if (body instanceof Error)
cb(err);
else
cb(null, body);
});
}
catch (err) {
cb(err); // if filename is not a valid (e.g. contains NUL byte), fsStat() may throw
}
}

@@ -162,0 +172,0 @@ };

{
"name": "@sap/cds-compiler",
"version": "3.0.0",
"version": "3.0.2",
"description": "CDS (Core Data Services) compiler and backends",

@@ -22,5 +22,4 @@ "homepage": "https://cap.cloud.sap/",

"xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
"test": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/ && mocha scripts/testLazyLoading.js",
"test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
"testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",
"testdot": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter dot --reporter-option maxDiffSize=0 --full-trace scripts/linter/lintMessages.js test/ test3/",
"test3": "node scripts/verifyGrammarChecksum.js && node scripts/linter/lintTests.js test3/ && mocha --reporter-option maxDiffSize=0 scripts/linter/lintMessages.js test3/",

@@ -27,0 +26,0 @@ "deployTest3SQL": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/testHANASQLDeployment.js",

# syntax-expected-integer
The compiler expects a safe integer here.
The last safe Integer is `2^53 - 1` or `9007199254740991`.
The last safe integer is `2^53 - 1` or `9007199254740991`.
A safe integer is an integer that
A safe integer is an integer that fulfills all of the following:
- can be exactly represented as an IEEE-754 double precision number, and
- whose IEEE-754 representation cannot be the result of rounding any
- Can be exactly represented as an IEEE-754 double precision number.
- The IEEE-754 representation cannot be the result of rounding any
other integer to fit the IEEE-754 representation.

@@ -18,2 +18,3 @@

<!-- cds-mode: ignore -->
```cdl

@@ -24,4 +25,4 @@ type LengthIsUnsafe : String(9007199254740992);

In the above example, the string length for the type `LengthIsUnsafe` is not a
safe Integer. It is too large.
In the erroneous example, the string length for the type `LengthIsUnsafe` is
not a safe integer. It is too large.
Likewise, the string length for the type `NotAnInteger` is a decimal.

@@ -38,3 +39,3 @@

If not feasible, a string representation of the number needs to be used,
e.g. in annotation values.
If it's not feasible to use a safe integer, a string representation of the
number needs to be used, for example, in annotation values.

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

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