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.4.2 to 3.4.4

lib/modelCompare/utils/.eslintrc.json

7

bin/cdsc.js

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

hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId);
const name = msg.location && msg.location.file;
const name = msg.$location && msg.$location.file;
const fullFilePath = name ? path.resolve('', name) : undefined;

@@ -518,5 +518,4 @@ log(main.messageStringMultiline(msg, {

}));
if (fullFilePath && msg.location.col) {
// A message context only makes sense, if we have at least a medium precision,
// i.e. line and column for start position.
if (fullFilePath && msg.$location.line) {
// A message context only makes sense, if we have at least start line
const context = sourceLines(fullFilePath);

@@ -523,0 +522,0 @@ log(main.messageContext(context, msg, { color: options.color }));

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

for (const tok of ts.tokens) {
const cat = tok.isIdentifier;
if (cat && tok.start >= 0) {
if (cat !== 'ref' || chars[tok.start] !== '$')
chars[tok.start] = categoryChars[cat] || cat.charAt(0);
if (tok.stop > tok.start) // stop in ANTLR at last char, not behind
chars[tok.start + 1] = '_';
if (tok.start < 0)
continue;
if (tok.$isSkipped) {
if (tok.stop > tok.start) {
chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16');
chars[tok.stop] = '\x17';
}
else {
chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15');
}
}
else {
const cat = tok.isIdentifier;
if (cat) {
if (cat !== 'ref' || chars[tok.start] !== '$')
chars[tok.start] = categoryChars[cat] || cat.charAt(0);
if (tok.stop > tok.start) // stop in ANTLR at last char, not behind
chars[tok.start + 1] = '_';
}
}
}

@@ -49,0 +62,0 @@ for (const c of chars)

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

## Version 3.4.4 - 2022-11-25
### Fixed
- compiler: CSN flavor `gensrc` (known as `xtended` in `@sap/cds`) lost annotations
on enum values and projection columns.
## Version 3.4.2 - 2022-11-11

@@ -12,0 +20,0 @@

@@ -15,3 +15,3 @@ /** @module API */

const modelCompare = lazyload('../modelCompare/compare');
const diffFilter = lazyload('../modelCompare/filter');
const diffFilter = lazyload('../modelCompare/utils/filter');
const sortViews = lazyload('../model/sortViews');

@@ -21,2 +21,3 @@ const csnUtils = lazyload('../model/csnUtils');

const forRelationalDB = lazyload('../transform/forRelationalDB');
const sqlUtils = lazyload('../render/utils/sql');

@@ -403,6 +404,8 @@ /**

const identifierUtils = sqlUtils.getIdentifierUtils(internalOptions);
const drops = {
creates: {},
final: Object.entries(diff.deletions).reduce((previous, [ name, artifact ]) => {
previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ artifact['@cds.persistence.name'] };`;
previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ identifierUtils.quoteSqlId(artifact['@cds.persistence.name']) };`;
return previous;

@@ -423,3 +426,3 @@ }, {}),

diffArtifact[modelCompare.isChanged] === undefined)) { // if it was removed in the after, then we don't have the flag
drops.creates[artifactName] = `DROP VIEW ${ diffArtifact['@cds.persistence.name'] };`;
drops.creates[artifactName] = `DROP VIEW ${ identifierUtils.quoteSqlId(diffArtifact['@cds.persistence.name']) };`;
} // TODO: What happens with a changed kind -> entity becomes a view?

@@ -426,0 +429,0 @@ else if (diffArtifact &&

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

'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' },
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
'ref-undefined-art': { severity: 'Error' },

@@ -141,16 +142,18 @@ 'ref-undefined-def': { severity: 'Error' },

// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues
// Also used by other projects that rely on double-quotes for delimited identifiers.
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
// 'syntax-duplicate-annotate' came late with v3 - make it configurable as
// fallback, but then parse.cdl is not supposed to work correctly (it can
// then either issue an error or produce a CSN missing some annotations):
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: true },
'syntax-unexpected-property': { severity: 'Error' },
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: 'v3' },
'syntax-invalid-name': { severity: 'Error', configurableFor: 'v3' },
'syntax-missing-as': { severity: 'Error', configurableFor: true },
'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'v3' },
'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
'type-unsupported-precision-change': { severity: 'Error'},

@@ -236,3 +239,3 @@ 'def-missing-element': { severity: 'Error' },

},
// Syntax messages, both CDL and CSN parser: ----------------------------------

@@ -245,10 +248,21 @@ 'syntax-dollar-ident': { // Warning, TODO: make it name-invalid-alias

},
// 'syntax-duplicate-excluding' (TODO: also CDL)
'syntax-deprecated-abstract': 'Abstract entity definitions are deprecated; use aspect definitions instead', // Warning, TODO: also use for CSN
'syntax-duplicate-excluding': {
std: 'Duplicate $(NAME) in the $(KEYWORD) clause',
csn: 'Duplicate $(NAME) in property $(PROP)',
},
'syntax-expecting-integer': { // TODO: currently 'syntax-expecting-natnum' in CSN
std: 'A safe integer is expected here',
normal: 'An integer number is expected here',
unsafe: 'The provided integer is too large',
},
'syntax-ignoring-anno': {
std: 'Annotations can\'t be used at prefix references',
doc: 'Doc comments can\'t be used at prefix references',
std: 'Annotations can\'t be used in a column with $(CODE)',
doc: 'Doc comments can\'t be used in a column with $(CODE)',
},
'syntax-invalid-name': {
std: 'Identifier for name must not be empty', // TODO: use
std: 'Identifier must consist of at least one character',
csn: 'Property name in dictionary $(PARENTPROP) must not be empty',
as: 'String in property $(PROP) must not be empty', // TODO: use
},

@@ -275,4 +289,9 @@ 'syntax-invalid-literal': { // TODO: write texts less CDL specific

// Syntax messages, CDL parser - default: Error ------------------------------
// Syntax messages, CDL parser -----------------------------------------------
// 'syntax-deprecated-auto-as', 'syntax-deprecated-ident'
'syntax-duplicate-annotate': 'You can\'t refer to $(NAME) repeatedly with property $(PROP) in the same annotate statement',
'syntax-duplicate-argument': {
std: 'Duplicate value for parameter $(NAME)',
type: 'Duplicate value for type parameter $(NAME)',
},
'syntax-duplicate-extend': {

@@ -283,9 +302,28 @@ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',

},
'syntax-invalid-extend': 'Can\'t extend an element with $(KIND)',
// 'syntax-duplicate-anno', 'syntax-duplicate-doc-comment', 'syntax-duplicate-cardinality',
// 'syntax-duplicate-property'
'syntax-unexpected-reserved-word': '$(CODE) is a reserved word - write $(DELIMITED) instead if you want to use it as name',
'syntax-invalid-text-block': 'Missing newline in text block',
// 'syntax-missing-newline' (Warning), 'syntax-missing-as',
// 'syntax-missing-token'
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
'syntax-unexpected-token': {
std: 'Mismatched $(OFFENDING), expecting $(EXPECTING)',
unwanted: 'Extraneous $(OFFENDING), expecting $(EXPECTING)'
},
'syntax-unexpected-vocabulary': {
std: 'Annotations can\'t be defined inside contexts or services', // not used
service: 'Annotations can\'t be defined inside services',
context: 'Annotations can\'t be defined inside contexts',
},
// 'syntax-unexpected-space', 'syntax-unexpected-doc-comment' (TODO is: -ignoring-, Info)
// 'syntax-unexpected-alias' (is 'syntax-unexpected-property' in CSN),
// 'syntax-unexpected-right-paren'
'syntax-unsupported-calc-field': 'Calculated fields are not supported',
'syntax-unsupported-param': {
std: 'Parameter not supported', // unused
dynamic: 'Dynamic parameter $(NAME) is not supported',
positional: 'Positional parameter $(NAME) is not supported',
dynamic: 'Dynamic parameter $(CODE) is not supported',
positional: 'Positional parameter $(CODE) is not supported',
},
// 'syntax-unsupported-method', 'syntax-unsupported-new'

@@ -323,6 +361,6 @@ // Syntax messages, CSN parser - default: Error ------------------------------

},
// 'syntax-invalid-ref' (Warning?), 'syntax-invalid-kind', 'syntax-invalid-literal' (Warning)
'syntax-invalid-string': {
std: 'Invalid string value in property $(PROP)',
},
// 'syntax-invalid-ref' (Warning?), 'syntax-invalid-kind', 'syntax-invalid-literal' (Warning)
'syntax-missing-property': { // location at sibling or '}' otherwise

@@ -345,2 +383,3 @@ std: 'Object in $(PARENTPROP) must have the property $(PROP)',

// multi-line strings: --------------------------------------------------------
'syntax-unknown-escape': 'Unknown escape sequence $(CODE)',

@@ -361,20 +400,2 @@ 'syntax-invalid-escape': {

},
'syntax-expecting-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': { // TODO: also CDL
std: 'Unexpected argument $(CODE)',
unknown: 'Unknown argument $(CODE)', // huh?
duplicate: 'Duplicate argument $(CODE)',
},
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
'syntax-unexpected-vocabulary': {
std: 'Annotations can\'t be defined inside contexts or services',
service: 'Annotations can\'t be defined inside services',
context: 'Annotations can\'t be defined inside contexts',
},
'syntax-fragile-ident': '$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it',
'syntax-unsupported-field': 'Calculated fields are not supported, yet',

@@ -555,3 +576,3 @@ // Syntax messages for errorneous references ----------------------------------

std: 'Unexpected array type for element $(NAME)',
scalar: 'Unexpected array type'
scalar: 'Unexpected array type',
},

@@ -572,2 +593,4 @@ 'odata-spec-violation-key-null': {

facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
missing: 'Expected referenced type $(TYPE) to be included in service $(NAME)',
external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)',
},

@@ -597,3 +620,3 @@ 'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',

* @property {MessageSeverity} severity Default severity for the message.
* @property {string[]|'deprecated'|true} [configurableFor]
* @property {string[]|'deprecated'|'v3'|true} [configurableFor]
* Whether the error can be reclassified to a warning or lower.

@@ -600,0 +623,0 @@ * If not `true` then an array is expected with specified modules in which the error is downgradable.

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

annos: anno => anno.map(paramsTransform.anno).join(', '),
delimited: n => '![' + n + ']', // TODO: use quote.single around?
delimited: n => quote.single( `![${ n }]` ), // represent as delimited id (TODO: double-']')
file: quote.single,

@@ -675,2 +675,3 @@ prop: quote.single,

value: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),
values: value => value.map(paramsTransform.value).join(', '),
othervalue: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )),

@@ -712,3 +713,5 @@ art: transformArg,

function quoted( name ) {
return (name) ? quote.double( name ) : quote.angle( '?' ); // TODO: failure in --test-mode, then remove
if (typeof name === 'string')
return quote.double( name );
throw new CompilerAssertion( `Expecting a string, not ${ name }` );
}

@@ -805,3 +808,4 @@

let t = transform && transform[p] || paramsTransform[p];
args[p] = (t) ? t( params[p], args, params, texts ) : params[p];
if (params[p] !== undefined)
args[p] = (t) ? t( params[p], args, params, texts ) : params[p];
}

@@ -808,0 +812,0 @@ let variant = args['#'];

@@ -17,2 +17,4 @@ {

"jsdoc/require-param-description": 0,
// there seem to be false positives
"jsdoc/require-returns-check": 0,
// =airbnb, >eslint:

@@ -19,0 +21,0 @@ "max-len": [ "error", {

{
"root": true,
"extends": "../../.eslintrc-ydkjsi.json"
"extends": "../../.eslintrc-ydkjsi.json",
"rules": {
"cds-compiler/space-in-func-decl": "error"
}
}

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

requires: [ 'literal', 'location' ],
// TODO: rename symbol to sym
// TODO: struct and variant only for annotation assignments

@@ -445,3 +444,3 @@ optional: [

optional: [
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
],

@@ -467,3 +466,6 @@ // TODO: restrict path to #simplePath

// preliminary -----------------------------------------------------------
doc: { kind: true, test: locationVal( isStringOrNull ) }, // doc comment
doc: {
kind: true,
test: locationVal( isStringOrNull ),
}, // doc comment
'@': {

@@ -894,3 +896,3 @@ kind: true,

const requires = [ 'val', 'location' ];
const optional = [ 'literal', '$inferred', '_pathHead' ];
const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
standard( node, parent, prop, { schema: valSchema, requires, optional }, name );

@@ -919,3 +921,3 @@ };

function isOneOf(values) {
function isOneOf( values ) {
return function isOneOfInner( node, parent, prop ) {

@@ -967,3 +969,3 @@ if (!values.includes(node))

function isScope(node, parent, prop) {
function isScope( node, parent, prop ) {
// artifact refs in CDL have scope:0 in XSN

@@ -970,0 +972,0 @@ if (Number.isInteger(node))

@@ -262,3 +262,3 @@ // The builtin artifacts of CDS

*/
function checkDate(year, month, day) {
function checkDate( year, month, day ) {
// Negative years are allowed

@@ -279,3 +279,3 @@ year = Math.abs(Number.parseInt(year, 10));

*/
function checkTime(hour, minutes, seconds) {
function checkTime( hour, minutes, seconds ) {
hour = Number.parseInt(hour, 10);

@@ -316,31 +316,31 @@ minutes = Number.parseInt(minutes, 10);

/** @param {string} typeName */
function isIntegerTypeName(typeName) {
function isIntegerTypeName( typeName ) {
return typeCategories.integer.includes(typeName);
}
/** @param {string} typeName */
function isDecimalTypeName(typeName) {
function isDecimalTypeName( typeName ) {
return typeCategories.decimal.includes(typeName);
}
/** @param {string} typeName */
function isNumericTypeName(typeName) {
function isNumericTypeName( typeName ) {
return isIntegerTypeName(typeName) || isDecimalTypeName(typeName);
}
/** @param {string} typeName */
function isStringTypeName(typeName) {
function isStringTypeName( typeName ) {
return typeCategories.string.includes(typeName);
}
/** @param {string} typeName */
function isDateOrTimeTypeName(typeName) {
function isDateOrTimeTypeName( typeName ) {
return typeCategories.dateTime.includes(typeName);
}
/** @param {string} typeName */
function isBooleanTypeName(typeName) {
function isBooleanTypeName( typeName ) {
return typeCategories.boolean.includes(typeName);
}
/** @param {string} typeName */
function isBinaryTypeName(typeName) {
function isBinaryTypeName( typeName ) {
return typeCategories.binary.includes(typeName);
}
/** @param {string} typeName */
function isGeoTypeName(typeName) {
function isGeoTypeName( typeName ) {
return typeCategories.geo.includes(typeName);

@@ -353,3 +353,3 @@ }

*/
function isRelationTypeName(typeName) {
function isRelationTypeName( typeName ) {
return typeCategories.relation.includes(typeName);

@@ -364,3 +364,3 @@ }

*/
function isInReservedNamespace(absolute) {
function isInReservedNamespace( absolute ) {
return absolute.startsWith( 'cds.') &&

@@ -383,3 +383,3 @@ !absolute.match(/^cds\.foundation(\.|$)/) &&

*/
function isBuiltinType(type) {
function isBuiltinType( type ) {
return typeof type === 'string' && isInReservedNamespace(type);

@@ -386,0 +386,0 @@ }

@@ -26,5 +26,6 @@ // Checks on XSN performed during compile()

const {
error, warning, message,
error, warning, message, info,
} = model.$messageFunctions;
forEachDefinition( model, checkArtifact );
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition );
checkSapCommonLocale( model, model.$messageFunctions );

@@ -40,2 +41,7 @@ return;

function checkAnnotationDefinition( art ) {
checkEnumType( art );
// TODO: Should we check elements similar to definition-elements as well?
}
function checkGenericConstruct( art ) {

@@ -85,3 +91,3 @@ checkName( art );

function checkLocalizedElement(elem) {
function checkLocalizedElement( elem ) {
// if it is directly a localized element

@@ -92,4 +98,5 @@ if (elem.localized && elem.localized.val) {

if (!type || !type.builtin || type.category !== 'string') {
warning(null, [ elem.type?.location, elem ], { keyword: 'localized' },
'Keyword $(KEYWORD) should only be used in combination with string types');
info('ref-expecting-localized-string', [ elem.type?.location, elem ],
{ keyword: 'localized' },
'Expecting a string type in combination with keyword $(KEYWORD)');
}

@@ -158,3 +165,3 @@ }

*/
function checkEnum(enumNode) {
function checkEnum( enumNode ) {
if (!enumNode.value)

@@ -175,3 +182,3 @@ return;

function checkEnumType(enumNode) {
function checkEnumType( enumNode ) {
// Either the type is an enum or an arrayed enum. We are only interested in

@@ -231,3 +238,3 @@ // the enum and don't care whether the enum is arrayed.

*/
function checkEnumValue(enumNode) {
function checkEnumValue( enumNode ) {
const type = enumNode.type && enumNode.type._artifact &&

@@ -303,3 +310,3 @@ enumNode.type._artifact._effectiveType;

*/
function checkLocalizedSubElement(element) {
function checkLocalizedSubElement( element ) {
if (element._parent.kind !== 'element')

@@ -321,3 +328,3 @@ return;

// TODO: Recursive check
function isTypeLocalized(type) {
function isTypeLocalized( type ) {
return (type && type.localized && type.localized.val);

@@ -333,3 +340,3 @@ }

*/
function checkForUnmanagedAssociations(element, keyObj) {
function checkForUnmanagedAssociations( element, keyObj ) {
if (element.targetAspect) {

@@ -355,3 +362,3 @@ // TODO: bad location / message

// TODO: move to define.js or parsers
function checkCardinality(elem) {
function checkCardinality( elem ) {
if (!elem.cardinality)

@@ -458,3 +465,3 @@ return;

// found within paths (e.g. filters, parameters, ...)
function checkExpressionsInPaths(node) {
function checkExpressionsInPaths( node ) {
foreachPath(node, (path) => {

@@ -473,3 +480,3 @@ for (const pathStep of path) {

function checkAssociation(elem) {
function checkAssociation( elem ) {
// TODO: yes, a check similar to this could make it into the compiler)

@@ -490,3 +497,3 @@ // when virtual element is part of association

function checkAssociationCondition(elem, onCond) {
function checkAssociationCondition( elem, onCond ) {
if (Array.isArray(onCond)) // condition in brackets results an array

@@ -498,3 +505,3 @@ onCond.forEach(Cond => checkAssociationCondition(elem, Cond));

function checkAssociationConditionArgs(elem, args, op) {
function checkAssociationConditionArgs( elem, args, op ) {
if (args)

@@ -504,3 +511,3 @@ args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));

function checkAssociationOnCondArg(elem, arg, op) {
function checkAssociationOnCondArg( elem, arg, op ) {
if (Array.isArray(arg)) {

@@ -522,3 +529,3 @@ arg.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op));

// Additionally, `$self.assoc` references are also not found.
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) {
if (!arg.path)

@@ -565,3 +572,3 @@ return;

*/
function checkTypeStructure(artifact) {
function checkTypeStructure( artifact ) {
// Just a basic check. We do not check that the inner structure of `items`

@@ -596,3 +603,3 @@ // is the same as the type but only that all are arrayed or structured.

*/
function checkExpression(xpr, allowAssocTail = false) {
function checkExpression( xpr, allowAssocTail = false ) {
// Since the checks for tree-like and token-stream expressions differ,

@@ -612,3 +619,3 @@ // check here what kind of expression we are looking at

*/
function isVirtualElement(arg) {
function isVirtualElement( arg ) {
return arg.path &&

@@ -625,3 +632,3 @@ arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true &&

*/
function checkTokenStreamExpression(xpr, allowAssocTail) {
function checkTokenStreamExpression( xpr, allowAssocTail ) {
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});

@@ -645,3 +652,3 @@ // Check for illegal argument usage within the expression

*/
function checkTreeLikeExpression(xpr, allowAssocTail) {
function checkTreeLikeExpression( xpr, allowAssocTail ) {
// No further checks regarding associations and $self required if this is a

@@ -680,3 +687,3 @@ // backlink-like expression (a comparison of $self with an assoc)

// Return true if 'arg' is an expression argument of type association or composition
function isAssociationOperand(arg) {
function isAssociationOperand( arg ) {
if (!arg.path) {

@@ -692,3 +699,3 @@ // Not a path, hence not an association (literal, expression, function, whatever ...)

// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
function isDollarSelfOrProjectionOperand(arg) {
function isDollarSelfOrProjectionOperand( arg ) {
return arg.path && arg.path.length === 1 &&

@@ -704,3 +711,3 @@ (arg.path[0].id === '$self' || arg.path[0].id === '$projection');

*/
function isBinaryDollarSelfComparisonWithAssoc(xpr) {
function isBinaryDollarSelfComparisonWithAssoc( xpr ) {
// Must be an expression with arguments

@@ -763,3 +770,3 @@ if (!xpr.op || !xpr.args)

// or undefined). Report errors on 'options.messages.
function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) {
function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) {
// Nothing to check if no actual annotation declaration was found

@@ -808,3 +815,3 @@ if (!annoDecl || annoDecl.artifacts && !elementDecl)

// if not
function checkValueAssignableTo(value, elementDecl, art) {
function checkValueAssignableTo( value, elementDecl, art ) {
// FIXME: We currently do not have any element declaration that could match

@@ -870,3 +877,3 @@ // a 'path' value, so we simply leave those alone

}
else {
else if (!elementDecl._effectiveType.enum) {
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);

@@ -892,3 +899,5 @@ }

// Enum symbol not provided but expected
if (!Object.keys(expectedEnum).some(symbol => expectedEnum[symbol].value.val === value.val)) {
const hasValidValue = Object.keys(expectedEnum)
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val);
if (!hasValidValue) {
// ... and none of the valid enum symbols matches the value

@@ -900,2 +909,10 @@ warning(null, loc, {}, 'An enum value is required here');

function getEnumValue( enumSymbol ) {
if (enumSymbol.value)
return enumSymbol.value?.val;
if (enumSymbol._effectiveType)
return enumSymbol._effectiveType?.value?.val;
return null;
}
// TODO: remove the following

@@ -910,3 +927,3 @@

// functions, parameters etc.)
function resolvePathFrom(path, from, result = {}) {
function resolvePathFrom( path, from, result = {} ) {
// Keep last encountered artifacts

@@ -930,7 +947,7 @@ if (from && !from._main)

// anonymous types
function getFinalTypeNameOf(node) {
function getFinalTypeNameOf( node ) {
let type = node._effectiveType;
if (type.type)
type = type.type._artifact;
return type && type.name && type.name.absolute;
return type?.name?.absolute;
}

@@ -963,3 +980,3 @@ }

// TODO: remove - this is not a good way to traverse expressions
function foreachPath(node, callback) {
function foreachPath( node, callback ) {
if (node === null || typeof node !== 'object') {

@@ -966,0 +983,0 @@ // Primitive node

@@ -675,10 +675,13 @@ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN

if (col.inline) { // `@anno elem.{ * }` does not work
if (col.doc)
warning( 'syntax-ignoring-anno', [ col.doc.location, col ], { '#': 'doc' } );
if (col.doc) {
warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
{ '#': 'doc', code: '.{ ‹inline› }' } );
}
// col.$annotations no available for CSN input, have to search.
// Warning about first annotation should be enough to avoid spam.
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
if (firstAnno)
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ] );
if (firstAnno) {
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
{ code: '.{ ‹inline› }' } );
}
}

@@ -742,3 +745,3 @@ // TODO: allow sub queries? at least in top-level expand without parallel ref

*/
function approveExistsInChildren(exprOrPathElement) {
function approveExistsInChildren( exprOrPathElement ) {
if (!exprOrPathElement) // may be null in case of parse error

@@ -745,0 +748,0 @@ return;

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

const commonLanguagesEntity = options.addTextsLanguageAssoc &&
model.definitions['sap.common.Languages'];
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements &&
commonLanguagesEntity.elements.code);
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
Object.keys( model.definitions ).forEach( processArtifact );

@@ -111,3 +109,3 @@

function processCompositionPersistence(def) {
function processCompositionPersistence( def ) {
if (def.$inferred === 'composition-entity' && !processed.has(def)) {

@@ -212,3 +210,3 @@ if (def._parent)

*/
function extendArtifact( extensions, art, noIncludes = false) {
function extendArtifact( extensions, art, noIncludes = false ) {
if (!noIncludes && !(canApplyIncludes( art, art ) &&

@@ -338,3 +336,3 @@ extensions.every( ext => canApplyIncludes(ext, art) )))

*/
function applyTypeExtensions(art) {
function applyTypeExtensions( art ) {
/**

@@ -749,3 +747,3 @@ * Contains the previous extension for each property that was applied

const errpos = elem.localized || elem.type || elem.name;
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' },
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' },
'Keyword $(KEYWORD) is ignored for primary keys' );

@@ -758,3 +756,3 @@ }

if (!keys) {
warning( null, [ art.name.location, art ], {},
warning( 'def-expecting-key', [ art.name.location, art ], {},
'No texts entity can be created when no key element exists' );

@@ -809,2 +807,44 @@ return false;

function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
const art = createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled );
const { location } = base.name;
const { locale } = art.elements;
// assertUnique array value, first entry is 'locale'
const assertUniqueValue = [ {
path: [ { id: locale.name.id, location: locale.location } ],
location: locale.location,
} ];
for (const orig of textElems) {
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
if (orig.key && orig.key.val) {
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
// TODO: the previous would be better, but currently not supported in toCDL
if (!fioriEnabled) {
elem.key = { val: true, $inferred: 'localized', location };
// If the propagated elements remain key (that is not fiori.draft.enabled)
// they should be omitted from OData containment EDM
annotateWith( elem, '@odata.containment.ignore', location );
}
else {
// add the former key paths to the unique constraint
assertUniqueValue.push({
path: [ { id: orig.name.id, location: orig.location } ],
location: orig.location,
});
}
}
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
const localized = orig.localized || orig.type || orig.name;
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
}
}
if (fioriEnabled)
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
return art;
}
function createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled ) {
const elements = Object.create(null);

@@ -821,4 +861,3 @@ const { location } = base.name;

// 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 hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
const locale = {

@@ -874,35 +913,2 @@ name: { location, id: 'locale' },

// assertUnique array value, first entry is 'locale'
const assertUniqueValue = [ {
path: [ { id: locale.name.id, location: locale.location } ],
location: locale.location,
} ];
for (const orig of textElems) {
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' );
if (orig.key && orig.key.val) {
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location };
// TODO: the previous would be better, but currently not supported in toCDL
if (!fioriEnabled) {
elem.key = { val: true, $inferred: 'localized', location };
// If the propagated elements remain key (that is not fiori.draft.enabled)
// they should be omitted from OData containment EDM
annotateWith( elem, '@odata.containment.ignore', location );
}
else {
// add the former key paths to the unique constraint
assertUniqueValue.push({
path: [ { id: orig.name.id, location: orig.location } ],
location: orig.location,
});
}
}
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword
const localized = orig.localized || orig.type || orig.name;
elem.localized = { val: null, $inferred: 'localized', location: localized.location };
}
}
if (fioriEnabled)
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' );
return art;

@@ -1280,3 +1286,3 @@ }

*/
function copyPersistenceAnnotations(target, source, options) {
function copyPersistenceAnnotations( target, source, options ) {
if (!source)

@@ -1338,2 +1344,22 @@ return;

function checkTextsLanguageAssocOption( model, options ) {
const languages = model.definitions['sap.common.Languages'];
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code;
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) {
const variant = !languages ? 'std' : 'code';
const loc = model.definitions['sap.common.Languages']?.name?.location || null;
model.$messageFunctions.info('api-ignoring-language-assoc', loc, {
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code',
}, {
std: 'Ignoring option $(OPTION) because entity $(ART) is missing',
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)',
});
}
return !!commonLanguagesEntity;
}
module.exports = extend;

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

*/
function resolveTypesForParseCdl(artifact, main) {
function resolveTypesForParseCdl( artifact, main ) {
if (!artifact || typeof artifact !== 'object')

@@ -164,3 +164,3 @@ return;

*/
function resolveTypeUnchecked(artWithType, user) {
function resolveTypeUnchecked( artWithType, user ) {
const root = artWithType.type.path && artWithType.type.path[0];

@@ -207,3 +207,3 @@ if (!root) // parse error

function chooseAndReportDuplicateAnnotation(artifact, annoName) {
function chooseAndReportDuplicateAnnotation( artifact, annoName ) {
for (const anno of artifact[annoName])

@@ -210,0 +210,0 @@ message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } );

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

setExpandStatus,
setExpandStatusAnnotate,
} = require('./utils');

@@ -167,3 +168,2 @@

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

@@ -197,3 +197,2 @@ chain.push( art );

art._effectiveType && (() => getCardinality( art._effectiveType ));
let prev = art;
for (const a of chain) {

@@ -207,4 +206,6 @@ if (a.cardinality)

art = a;
else if (art.enum && expandEnum( a, prev ))
prev = a; // do not set art - effective type is base
else if (art.enum && expandEnum( a, art ))
art = a;
setLink( a, '_effectiveType', art );

@@ -476,7 +477,17 @@ }

else {
let wasAnnotated = false;
for (const prop in selem) {
// just annotation assignments and doc comments for the moment
if (prop.charAt(0) === '@' || prop === 'doc')
if (prop.charAt(0) === '@' || prop === 'doc') {
ielem[prop] = selem[prop];
// required for gensrc mode of to-csn.js, otherwise the annotation
// may be lost during recompilation.
ielem[prop].$priority = 'annotate';
wasAnnotated = true;
}
}
if (wasAnnotated)
setExpandStatusAnnotate(art, 'annotate');
selem.$replacement = true;

@@ -483,0 +494,0 @@ if (selem.elements) {

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

dependsOnSilent,
setExpandStatusAnnotate,
testExpr,

@@ -622,16 +623,2 @@ targetMaxNotOne,

function setExpandStatusAnnotate( elem, status ) {
for (;;) {
if (elem.$expand === status)
return; // already set
elem.$expand = status; // meaning: expanded, containing annos
for (let line = elem.items; line; line = line.items)
line.$expand = status; // to-csn just uses the innermost $expand
if (!elem._main)
return;
elem = elem._parent;
}
}
function expandParameters( action ) {

@@ -1296,3 +1283,3 @@ // see also expandElements()

function resolveExpr( expr, expected, user, extDict, expandOrInline) {
function resolveExpr( expr, expected, user, extDict, expandOrInline ) {
// TODO: when we have rewritten the resolvePath functions,

@@ -1299,0 +1286,0 @@ // define a traverseExpr() in ./utils.js

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

*/
function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) {
function resolveTypeArgumentsUnchecked( artifact, typeArtifact, user ) {
let args = artifact.$typeArgs || [];

@@ -877,3 +877,3 @@ const parameters = typeArtifact.parameters || [];

*/
function signalNotFound(msgId, location, valid, textParams ) {
function signalNotFound( msgId, location, valid, textParams ) {
if (location.$notFound)

@@ -896,3 +896,3 @@ return;

*/
function signalMissingElementAccess(art, location) {
function signalMissingElementAccess( art, location ) {
const err = message( 'ref-expected-element', location,

@@ -915,3 +915,3 @@ { '#': 'magicVar', id: art.name.id } );

*/
function attachAndEmitValidNames(msg, ...validDicts) {
function attachAndEmitValidNames( msg, ...validDicts ) {
if (!options.testMode && !options.attachValidNames)

@@ -918,0 +918,0 @@ return;

@@ -403,2 +403,15 @@ // Simple compiler utility functions

function setExpandStatusAnnotate( elem, status ) {
for (;;) {
if (elem.$expand === status)
return; // already set
elem.$expand = status; // meaning: expanded, containing annos
for (let line = elem.items; line; line = line.items)
line.$expand = status; // to-csn just uses the innermost $expand
if (!elem._main)
return;
elem = elem._parent;
}
}
module.exports = {

@@ -428,2 +441,3 @@ pushLink,

setExpandStatus,
setExpandStatusAnnotate,
};

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

const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
const { info, warning, error, message, throwWithAnyError } = messageFunctions;
const { info, warning, error, message, throwWithError } = messageFunctions;
checkCSNVersion(csn, _options);

@@ -58,3 +58,3 @@

if(Object.keys(allServices).length === 0) {
info(null, null, `No Services in model`);
info(null, null, 'No Services in model');
return rc;

@@ -81,3 +81,3 @@ }

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

@@ -97,2 +97,4 @@

function markRendered(def) { setProp(def, '$isRendered', true); }
const service = new Edm.DataServices(v);

@@ -138,6 +140,17 @@ /** @type {object} */

const fqSchemaXRef = [serviceCsn.name];
const whatsMySchemaName = function(n) {
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
const whatsMySchemaName = function(n, arr=fqSchemaXRef) {
return arr.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
}
let xServiceRefs = [];
const UsedTypes = {};
function collectUsedType(csn, typeName = (csn.items?.type || csn.type)) {
if(typeName) {
if(UsedTypes[typeName])
UsedTypes[typeName].push(csn)
else
UsedTypes[typeName] = [ csn ];
}
}
// create schema containers

@@ -183,16 +196,3 @@ const subSchemaDictionary = {

populateSchemas(subSchemaDictionary);
const xServiceRefs = populateXserviceRefs();
/* TODO:
const references = Object.entries(allSchemas).reduce((references, [fqName, art]) => {
// add references
if(fqName.startsWith(serviceCsn.name + '.') && art.kind === 'reference') {
fqSchemaXRef.push(fqName);
references.push(art);
}
return references;
}, []);
*/
// Add xrefs to full schema cross ref list for further usage
fqSchemaXRef.push(...xServiceRefs);
fqSchemaXRef.sort((a,b) => b.length-a.length);
xServiceRefs = populateXserviceRefs();

@@ -232,4 +232,7 @@ // Bring the schemas in alphabetical order, service first, root last

if(arr.length > 1) {
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(', ')} `);
error(null, null, {
name: c._edmAttributes.Namespace,
id: setName,
names: arr.map(a => a.getDuplicateMessage())
}, 'Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for $(NAMES)');
}

@@ -240,2 +243,38 @@ }));

addAnnotations();
// type cross check
const xServiceRefNames = xServiceRefs.map(r => r.name).sort((a,b)=>b.length-a.length);
for(let typeName in UsedTypes) {
if(!isBuiltinType(typeName)) {
let iTypeName = typeName;
/*
Report type ref, if the type is
- not a builtin,
- not included in required definitions (for this service)
- not a type clash (reported in type exposure),
- a @cds.external service member but can't be rendered
- and not a cross referenced type
*/
if(!typeName.startsWith(serviceCsn.name + '.'))
iTypeName = serviceCsn.name + '.' + typeName;
const def = reqDefs.definitions[iTypeName];
const usages = UsedTypes[typeName].filter(u => !u.$NameClashReported);
if(usages.length > 0) {
if(def && !def.$isRendered && def['@cds.external'])
message('odata-spec-violation-type', usages[0].$location,
{
type: typeName,
anno: '@cds.external',
name: serviceCsn.name,
code: def.elements ? 'Edm.ComplexType' : 'Edm.TypeDefinition',
version: options.isV4() ? '4.0' : '2.0',
'#': 'external' } );
else if(!def && !whatsMySchemaName(typeName, xServiceRefNames))
message('odata-spec-violation-type', usages[0].$location,
{ type: typeName, name: serviceCsn.name, '#': 'missing' } );
}
}
}
return edm

@@ -301,3 +340,2 @@

if(art.kind === 'reference' && whatsMySchemaName(fqName) && serviceCsn.name === whatsMyServiceRootName(fqName, false)) {
fqSchemaXRef.push(fqName);
references.push(art);

@@ -343,8 +381,8 @@ }

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

@@ -354,3 +392,3 @@ // create the complex types

a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix),
createComplexType);
[createComplexType, markRendered]);

@@ -363,6 +401,6 @@ if(options.isV4())

artifact.name.startsWith(schemaNamePrefix),
createTypeDefinition);
[createTypeDefinitionV4, markRendered]);
}
// fetch all exising children names in a map
// fetch all existing child names in a map
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {

@@ -385,11 +423,8 @@ const name = cur._edmAttributes.Name;

else {
addAssociation(np);
addAssociationV2(np);
}
});
// remove EntityContainer if empty
if(Schema._ec && Schema._ec._children.length === 0) {
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
}
if(Schema._children.length === 0) {
if(Schema._children.length === 0 ||
(Schema._children.length === 1 && Schema._children[0].kind === 'EntityContainer')) {
// FIXME: Location for sub schemas?

@@ -484,2 +519,130 @@ warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');

function createComplexType(structuredTypeCsn)
{
// V4 attributes: Name, BaseType, Abstract, OpenType
const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') };
const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn);
const elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
const loc = ['definitions', structuredTypeCsn.name];
if(properties.length === 0) {
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._edmAttributes.Name ];
edmTypeCompatibilityCheck(p, pLoc);
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
message('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 && !p._csn.target)
message('odata-spec-violation-array', pLoc, { version: '2.0' });
if(p._csn.target)
message('odata-spec-violation-assoc', pLoc, { version: '2.0' });
}
});
complexType.append(...(properties));
Schema.append(complexType);
}
/**
* @param {object} elementsCsn
* @param {object} edmParentCsn
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
* array of Edm Properties
* hasStream : value of @Core.MediaType assignment
*/
function createProperties(elementsCsn, edmParentCsn=elementsCsn)
{
const props = [];
let hasStream = false;
const streamProps = [];
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
{
if(elementCsn._edmParentCsn == undefined)
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
if(elementCsn.target) {
// Foreign keys are part of the generic elementCsn.elements property creation
// This is the V4 edmx:NavigationProperty
// gets rewritten for V2 in addAssociations()
// suppress navprop creation only if @odata.navigable:false is not annotated.
// (undefined !== false) still evaluates to true
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
{
const navProp = new Edm.NavigationProperty(v, {
Name: elementName,
Type: elementCsn._target.name
}, elementCsn);
collectUsedType(elementCsn, elementCsn._target.name);
props.push(navProp);
// save the navProp in the global array for late constraint building
navigationProperties.push(navProp);
}
}
// render ordinary property if element is NOT ...
// 1) ... annotated @cds.api.ignore
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured
else if(isEdmPropertyRendered(elementCsn, options))
{
// CDXCORE-CDXCORE-173
// V2: filter @Core.MediaType
if ( options.isV2() && elementCsn['@Core.MediaType']) {
hasStream = elementCsn['@Core.MediaType'];
delete elementCsn['@Core.MediaType'];
// CDXCORE-CDXCORE-177:
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
// V4: render property type 'Edm.Stream'
streamProps.push(elementName);
} else {
collectUsedType(elementCsn);
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
}
}
});
if(options.isV2()) {
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text?
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 ];
}
// V4 <TypeDefintion>
function createTypeDefinitionV4(typeCsn)
{
// derived types are already resolved to base types
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);
}
// add bound/unbound actions/functions for V4

@@ -545,3 +708,3 @@ function createActionV4(actionCsn, _name, entityCsn=undefined)

message('odata-spec-violation-id', pLoc, { id: parameterName });
collectUsedType(parameterCsn);
edmTypeCompatibilityCheck(p, pLoc);

@@ -554,2 +717,3 @@ actionNode.append(p);

actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
collectUsedType(actionCsn.returns);
edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);

@@ -636,2 +800,3 @@ // if binding type matches return type add attribute EntitySetPath

const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
collectUsedType(parameterCsn);
edmTypeCompatibilityCheck(param, pLoc);

@@ -664,2 +829,3 @@ if(!edmUtils.isODataSimpleIdentifier(parameterName))

if (type) {
collectUsedType(action.returns);
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {

@@ -692,143 +858,17 @@ message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });

/**
* @param {object} elementsCsn
* @param {object} edmParentCsn
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
* array of Edm Properties
* hasStream : value of @Core.MediaType assignment
*/
function createProperties(elementsCsn, edmParentCsn=elementsCsn)
{
const props = [];
let hasStream = false;
const streamProps = [];
/*
addAssociation() constructs a V2 association.
In V4 all this has been simplified very much, the only thing actually left over is
<ReferentialConstriant> that is then a sub element to <NavigationProperty>.
However, referential constraints are substantially different to its V2 counterpart,
so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate
function.
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
{
if(elementCsn._edmParentCsn == undefined)
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
if(elementCsn.target) {
// Foreign keys are part of the generic elementCsn.elements property creation
// This is the V4 edmx:NavigationProperty
// gets rewritten for V2 in addAssociations()
// suppress navprop creation only if @odata.navigable:false is not annotated.
// (undefined !== false) still evaluates to true
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
{
const navProp = new Edm.NavigationProperty(v, {
Name: elementName,
Type: elementCsn._target.name
}, elementCsn);
props.push(navProp);
// save the navProp in the global array for late constraint building
navigationProperties.push(navProp);
}
}
// render ordinary property if element is NOT ...
// 1) ... annotated @cds.api.ignore
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured
else if(isEdmPropertyRendered(elementCsn, options))
{
// CDXCORE-CDXCORE-173
// V2: filter @Core.MediaType
if ( options.isV2() && elementCsn['@Core.MediaType']) {
hasStream = elementCsn['@Core.MediaType'];
delete elementCsn['@Core.MediaType'];
// CDXCORE-CDXCORE-177:
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
// V4: render property type 'Edm.Stream'
streamProps.push(elementName);
} else {
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
}
}
});
if(options.isV2()) {
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text?
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 ];
}
function createComplexType(structuredTypeCsn)
{
// V4 attributes: Name, BaseType, Abstract, OpenType
const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') };
const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn);
const elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
const loc = ['definitions', structuredTypeCsn.name];
if(properties.length === 0) {
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._edmAttributes.Name ];
edmTypeCompatibilityCheck(p, pLoc);
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
message('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 && !p._csn.target)
message('odata-spec-violation-array', pLoc, { version: '2.0' });
if(p._csn.target)
message('odata-spec-violation-assoc', pLoc, { version: '2.0' });
}
});
complexType.append(...(properties));
Schema.append(complexType);
}
// V4 <TypeDefintion>
function createTypeDefinition(typeCsn)
{
// derived types are already resolved to base types
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);
}
/*
* addAssociation() constructs a V2 association.
* In V4 all this has been simplified very much, the only thing actually left over is
* <ReferentialConstriant> that is then a sub element to <NavigationProperty>.
* However, referential constraints are substantially different to its V2 counterpart,
* so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate
* function.
*
* This method does:
* rewrite <NavigationProperty> attributes to be V2 compliant
* add <Association> elements to the schema
* add <End>, <ReferentialConstraint>, <Dependent> and <Principal> sub elements to <Association>
* add <AssociationSet> to the EntityContainer for each <Association>
This method does:
rewrite <NavigationProperty> attributes to be V2 compliant
add <Association> elements to the schema
add <End>, <ReferentialConstraint>, <Dependent> and <Principal> sub elements to <Association>
add <AssociationSet> to the EntityContainer for each <Association>
*/
function addAssociation(navigationProperty)
function addAssociationV2(navigationProperty)
{

@@ -835,0 +875,0 @@ let constraints = navigationProperty._csn._constraints;

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

{ anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) },
`Expect array value for $(ANNOTATION): $(CODE)`);
'Expect array value for $(ANNOTATION): $(CODE)');
return;

@@ -129,3 +129,3 @@ }

{ anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) },
`Expect array value for $(ANNOTATION): $(CODE)`);
'Expect array value for $(ANNOTATION): $(CODE)');
return;

@@ -203,3 +203,3 @@ }

code: JSON.stringify(filterRestrictions) },
`Expect array value for $(ANNOTATION): $(CODE)`);
'Expect array value for $(ANNOTATION): $(CODE)');
return;

@@ -221,3 +221,3 @@ }

code: JSON.stringify(requiredProperties) },
`Expect array value for $(ANNOTATION): $(CODE)`);
'Expect array value for $(ANNOTATION): $(CODE)');
return;

@@ -353,2 +353,2 @@ }

setSAPSpecificV2AnnotationsToAssociation
};
};

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

const csnUtils = getUtils(csn);
const { message, throwWithAnyError } = messageFunctions;
const { message, throwWithError } = messageFunctions;
forEachDefinition(csn, [ attach$path, checkChainedArray ]);
checkNestedContextsAndServices();
throwWithAnyError();
throwWithError();

@@ -34,0 +34,0 @@ // attach $path to all

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

if(arg.xpr)
arg.xpr.map(fillConstraints);
getExpressionArguments(arg.xpr);
else if(pos > 0 && pos < expr.length)

@@ -489,3 +489,3 @@ {

if(cdsType === undefined) {
error(null, location, `no type found`);
error(null, location, 'no type found');
return '<NOTYPE>';

@@ -549,3 +549,3 @@ }

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

@@ -552,0 +552,0 @@ if(isV2)

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

if (!name) {
warning( 'syntax-invalid-name', location(true), // TODO: Error
message( 'syntax-invalid-name', location(true),
{ '#': 'csn', parentprop: spec.prop } );

@@ -1152,4 +1152,7 @@ }

return { val, literal: 'number', location: location() };
error( spec.msgId || 'syntax-expecting-natnum', location(true),
{ prop: spec.msgProp } );
const loc = location(true);
if (spec.msgId)
error( spec.msgId, loc, { prop: spec.msgProp } );
else
error( 'syntax-expecting-natnum', loc, { prop: spec.msgProp } );
return ignore( val );

@@ -1461,4 +1464,3 @@ }

function duplicateExcluding( name, loc ) {
error( 'syntax-duplicate-excluding', loc, { name, keyword: 'excluding' }, // TODO: also CDL
'Duplicate $(NAME) in the $(KEYWORD) clause' );
error( 'syntax-duplicate-excluding', loc, { '#': 'csn', name, prop: 'excluding[]' } );
}

@@ -1547,10 +1549,10 @@

: (parentSpec.msgProp ? 'prop' : 'top');
message( 'syntax-unexpected-property', location(true),
{
'#': variant,
prop,
parentprop:
parentSpec.msgProp,
kind,
} );
error( 'syntax-unexpected-property', location(true),
{
'#': variant,
prop,
parentprop:
parentSpec.msgProp,
kind,
} );
}

@@ -1629,3 +1631,3 @@ else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) {

return true;
message( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
return false;

@@ -1660,4 +1662,7 @@ }

return obj;
error( spec.msgId || 'syntax-expecting-object', location(true),
{ prop: spec.msgProp });
const loc = location(true);
if (spec.msgId)
error( spec.msgId, loc, { prop: spec.msgProp });
else
error( 'syntax-expecting-object', loc, { prop: spec.msgProp });
return ignore( obj );

@@ -1664,0 +1669,0 @@ }

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

function: 'action',
enum: 'enum',
};

@@ -433,17 +434,6 @@

// From definitions (without redefinitions) with potential inferred elements:
const annotate = { annotate: name };
if (art.$inferred)
Object.assign( annotate, annotationsAndDocComment( art, true ) );
if (art.$expand === 'annotate') {
if (art.actions)
attachAnnotations( annotate, 'actions', art.actions, art.$inferred );
else if (art.params)
attachAnnotations( annotate, 'params', art.params, art.$inferred );
const obj = art.returns || art;
const elems = (obj.items || obj).elements; // no targetAspect here
if (elems)
attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns );
}
if (Object.keys( annotate ).length > 1)
exts.push( annotate );
const result = { annotate: Object.create(null) };
attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
if (result.annotate[name])
exts.push({ annotate: name, ...result.annotate[name] } );
}

@@ -475,3 +465,3 @@ }

continue;
const render = annotationsAndDocComment( par, true );
const render = annotationsAndDocComment( par );
const subElems = par.$expand !== 'origin' && (par.items || par).elements;

@@ -493,3 +483,3 @@ if (subElems) {

return;
const render = annotationsAndDocComment( par, true );
const render = annotationsAndDocComment( par );
const subElems = par.$expand !== 'origin' && (par.items || par).elements;

@@ -515,3 +505,3 @@ if (subElems) {

// happen through extensions.
const annos = annotationsAndDocComment( art, true );
const annos = annotationsAndDocComment( art );
const annotate = Object.assign( { annotate: name }, annos );

@@ -575,12 +565,17 @@ if (Object.keys( annotate ).length > 1) {

for (const name in dict) {
const elem = dict[name];
const inf = inferred || elem.$inferred; // is probably always inferred if parent was
const sub = (inf) ? annotationsAndDocComment( elem, true ) : {};
if (elem.$expand === 'annotate') {
if (elem.params)
attachAnnotations( sub, 'params', elem.params, inf );
const obj = elem.returns || elem;
const elems = (obj.items || obj.targetAspect || obj).elements;
const entry = dict[name];
const inf = inferred || entry.$inferred; // is probably always inferred if parent was
const sub = (inf) ? annotationsAndDocComment( entry ) : {};
if (entry.$expand === 'annotate') {
if (entry.actions)
attachAnnotations( sub, 'actions', entry.actions, inf );
else if (entry.params)
attachAnnotations( sub, 'params', entry.params, inf );
const obj = entry.returns || entry;
const many = obj.items || obj;
const elems = (many.targetAspect || many).elements;
if (elems)
attachAnnotations( sub, 'elements', elems, inf, elem.returns );
attachAnnotations( sub, 'elements', elems, inf, entry.returns );
if (many.enum)
attachAnnotations( sub, 'enum', many.enum, inf );
}

@@ -732,6 +727,10 @@ if (Object.keys( sub ).length)

// for gensrcFlavor and namespace/builtin annotation extraction:
// return annotations from definition (annotated==false)
// or annotations (annotated==true)
function annotationsAndDocComment( node, annotated ) {
/**
* For gensrcFlavor and namespace/builtin annotation extraction:
* return annotations from definition and annotations.
* The call side should check that node.$inferred is truthy.
*
* @param {object} node
*/
function annotationsAndDocComment( node ) {
const csn = {};

@@ -744,11 +743,8 @@ const transformer = transformers['@'];

// and @odata.containment.ignore
// TODO: use $inferred instead special $priority value
if (val.$priority !== undefined && (!!val.$priority) === annotated) {
// transformer (= value) takes care to exclude $inferred annotation assignments
const sub = transformer( val );
// As value() just has one value, so we do not provide ( val, csn, node, prop )
// which would be more robust, but makes some JS checks unhappy
if (sub !== undefined)
csn[prop] = sub;
}
// transformer (= value) takes care to exclude $inferred annotation assignments
const sub = transformer( val );
// As value() just has one value, so we do not provide ( val, csn, node, prop )
// which would be more robust, but makes some JS checks unhappy
if (sub !== undefined)
csn[prop] = sub;
}

@@ -1545,3 +1541,3 @@ if (node.doc)

// only list annotations here which are provided directly with definition
const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {};
const col = (gensrcFlavor) ? annotationsAndDocComment( elem ) : {};
// with `client` flavor, assignments are available at the element

@@ -1548,0 +1544,0 @@ const gensrcSaved = gensrcFlavor;

{
"root": true,
"extends": "../../.eslintrc-ydkjsi.json"
"extends": "../../.eslintrc-ydkjsi.json",
"rules": {
"cds-compiler/space-in-func-decl": "error"
}
}

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

if (token.type === parser.constructor.DocComment && !token.isUsed) {
messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
'Ignoring doc comment as it does not belong to any artifact');
// TODO: think of 'syntax-unexpected-doc-comment'
messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
'Ignoring doc comment as it is not written at a defined position' );
// this is also for position inside some artifact definition, i.e. previous text
// "does not belong to any artifact" might be confusing
}

@@ -171,0 +174,0 @@ }

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

*/
function parseDocComment(comment) {
function parseDocComment( comment ) {
// Also return "null" for empty doc comments so that doc comment propagation

@@ -87,3 +87,3 @@ // can be stopped.

*/
function stripCommentIndentation(lines, ignoreFirstLine) {
function stripCommentIndentation( lines, ignoreFirstLine ) {
const n = lines.length;

@@ -119,3 +119,3 @@

*/
function removeFence(line) {
function removeFence( line ) {
return line.replace(/^\s*[*]\s?/, '');

@@ -131,3 +131,3 @@ }

*/
function removeHeaderFence(line) {
function removeHeaderFence( line ) {
return line.replace(/^\/[*]{2,}\s?/, '');

@@ -145,3 +145,3 @@ }

*/
function removeFooterFence(line) {
function removeFooterFence( line ) {
return line.replace(/\s*[*]+\/$/, '');

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

*/
function isFencedComment(lines) {
function isFencedComment( lines ) {
const index = lines.findIndex((line, i) => {

@@ -159,0 +159,0 @@ const exclude = (i === 0 || i === lines.length - 1);

@@ -58,4 +58,7 @@ // Error strategy with special handling for (non-reserved) keywords

return antlr4.Parser.prototype.match.call( this, ttype );
this.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
// This is very likely to be dead code: we do not use a simple Identifier
// without alternatives in the grammar. With alternatives, recoverInline() is
// the place to go. (But this code should work with a changed grammar…)
this.message( 'syntax-unexpected-reserved-word', token,
{ code: token.text, delimited: token.text } );
this._errHandler.reportMatch(this);

@@ -76,3 +79,2 @@ this.consume();

this._super = {
consumeUntil: super.consumeUntil,
recoverInline: super.recoverInline,

@@ -94,2 +96,3 @@ getExpectedTokens: super.getExpectedTokens,

consumeUntil,
consumeAndMarkUntil,
recoverInline,

@@ -143,6 +146,7 @@ getMissingSymbol,

// Expected token is identifier, current is (reserved) KEYWORD:
// TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens
// TODO: do not use this if "close enough" (1 char diff or prefix)
// to a keyword in nextTokens
//
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
// which means that we cannot bring the better special syntax-fragile-ident
// which means that we cannot bring the better special syntax-unexpected-reserved
// in all cases. Reason: high performance impact of the alternative,

@@ -153,3 +157,5 @@ // i.e. calling method Parser#isExpectedToken() = invoking the ATN

if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
recognizer.message( 'syntax-unexpected-reserved-word', token,
{ code: token.text, delimited: token.text } );
// TODO: attach tokens like for 'syntax-unexpected-token'
token.type = identType; // make next ANTLR decision assume identifier

@@ -218,7 +224,7 @@ return;

const offending = this.getTokenDisplay( e.offendingToken, recognizer );
e.offendingToken.$isSkipped = 'offending';
let err;
if (expecting && expecting.length) {
err = recognizer.error( 'syntax-mismatched-token', e.offendingToken,
{ offending, expecting },
'Mismatched $(OFFENDING), expecting $(EXPECTING)' );
err = recognizer.error( 'syntax-unexpected-token', e.offendingToken,
{ offending, expecting } );
err.expectedTokens = expecting;

@@ -241,7 +247,8 @@ }

const token = recognizer.getCurrentToken();
token.$isSkipped = 'offending';
const expecting = this.getExpectedTokensForMessage( recognizer, token );
const offending = this.getTokenDisplay( token, recognizer );
const err = recognizer.error( 'syntax-extraneous-token', token,
{ offending, expecting },
'Extraneous $(OFFENDING), expecting $(EXPECTING)' );
// Just text variant, no other message id! Would depend on ANTLR-internals
const err = recognizer.error( 'syntax-unexpected-token', token,
{ '#': 'unwanted', offending, expecting } );
err.expectedTokens = expecting; // TODO: remove next token?

@@ -259,5 +266,7 @@ if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig

const token = recognizer.getCurrentToken();
token.$isSkipped = 'offending';
const expecting = this.getExpectedTokensForMessage( recognizer, token );
const offending = this.getTokenDisplay( token, recognizer );
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending
// Hopefully not too ANTLR-specific, so extra message id is ok:
const err = recognizer.error( 'syntax-missing-token', token,

@@ -275,6 +284,6 @@ { offending, expecting },

const expecting = this.getExpectedTokensForMessage( recognizer, t );
const m = recognizer.warning( 'syntax-ignored-with', t,
{ offending: "';'", expecting },
const m = recognizer.warning( 'syntax-unexpected-semicolon', t,
{ offending: "';'", expecting, keyword: 'with' },
// eslint-disable-next-line max-len
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous WITH' );
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' );
m.expectedTokens = expecting;

@@ -292,6 +301,6 @@ }

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

@@ -308,5 +317,5 @@ // recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));

stop.addOne( RBRACE );
this._super.consumeUntil.call( this, recognizer, stop );
if (recognizer.getTokenStream().LA(1) === SEMI ||
recognizer.getTokenStream().LA(1) === RBRACE && !set.contains(RBRACE)) {
this.consumeAndMarkUntil( recognizer, stop );
const ttype = recognizer.getTokenStream().LA(1);
if (ttype === SEMI || ttype === RBRACE && !set.contains(RBRACE)) {
recognizer.consume();

@@ -326,2 +335,11 @@ this.reportMatch(recognizer); // we know current token is correct

function consumeAndMarkUntil( recognizer, set ) {
let t = recognizer.getTokenStream().LT(1);
while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) {
if (!t.$isSkipped)
t.$isSkipped = true;
recognizer.consume();
t = recognizer.getTokenStream().LT(1);
}
}

@@ -343,3 +361,5 @@ // As the `match` function of the parser `recognizer` does not allow to check

recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
// TODO: attach `Identifier` as valid name to message?
recognizer.message( 'syntax-unexpected-reserved-word', token,
{ code: token.text, delimited: token.text } );
this.reportMatch(recognizer); // we know current token is correct

@@ -512,3 +532,3 @@ recognizer.consume();

function addSet(other) {
function addSet( other ) {
if (!other.contains( hideAltsType ))

@@ -520,3 +540,3 @@ origAddSet.call( this, other );

// type `Identifier`, do not add non-reserved keywords in `v`.
function addInterval(v) {
function addInterval( v ) {
if (v.stop <= identType) {

@@ -523,0 +543,0 @@ origAddInterval.call(this, v);

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

classifyImplicitName,
warnIfColonFollows,
fragileAlias,

@@ -113,3 +114,2 @@ identAst,

createPrefixOp,
setOnce,
setMaxCardinality,

@@ -122,3 +122,2 @@ pushIdent,

csnParseOnly,
disallowElementExtension,
noAssignmentInSameLine,

@@ -223,25 +222,8 @@ noSemicolonHere,

/**
* For element extensions (`extend E:elem` syntax).
* If `elemName.path` is set, remove the last extension from `$outer` and
* emit an error that the extension is invalid.
*
* @param {object} elemName
* @param {object} outer
* @param {string} extensionVariant
*/
function disallowElementExtension(elemName, outer, extensionVariant) {
if (elemName.path) {
const loc = this.tokenLocation(this.getCurrentToken());
this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } );
// remove last, i.e. new extension
outer.extensions.pop();
}
}
function noAssignmentInSameLine() {
const t = this.getCurrentToken();
if (t.text === '@' && t.line <= this._input.LT(-1).line) {
this.warning( 'syntax-anno-same-line', t, {},
'Annotation assignment belongs to next statement' );
this.warning( 'syntax-missing-newline', t, { anno: '‹anno›' }, // TODO: single quotes, @()
// eslint-disable-next-line max-len
'Add a newline before $(ANNO) to indicate that it belongs to the next statement' );
}

@@ -274,3 +256,3 @@ }

function prepareGenericKeywords( pathItem, expected = null) {
function prepareGenericKeywords( pathItem, expected = null ) {
const length = pathItem?.args?.length || 0;

@@ -500,3 +482,3 @@ const argPos = (expected ? length - 1 : length);

function isMultiLineToken(token) {
function isMultiLineToken( token ) {
return (

@@ -593,2 +575,13 @@ token.type === this.constructor.DocComment ||

// ANTLR on some OS might corrupt non-ASCII chars for messages
function warnIfColonFollows( anno ) {
const t = this.getCurrentToken();
if (t.text === ':') {
this.warning( 'syntax-missing-parens', anno.name.location,
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' },
// eslint-disable-next-line max-len
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' );
}
}
// If the token before the current one is a doc comment (ignoring other tokens

@@ -621,5 +614,5 @@ // on the hidden channel), put its "cleaned-up" text as value of property `doc`

// to be used in the empty alternative to AS <explicitName>.
function classifyImplicitName( category, ref ) {
function classifyImplicitName( category, ref, tokpos = 1 ) {
if (!ref || ref.path && this.getCurrentToken().text !== '.') {
const implicit = this._input.LT(-1);
const implicit = this._input.LT( tokpos - 1 || -1 );
if (implicit.isIdentifier)

@@ -634,7 +627,7 @@ implicit.isIdentifier = category;

if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) {
this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' },
this.warning( 'syntax-deprecated-auto-as', ast.location, { keyword: 'as' },
'Please add the keyword $(KEYWORD) in front of the alias name' );
}
else { // configurable error
this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' },
this.message( 'syntax-missing-as', ast.location, { keyword: 'as' },
'Please add the keyword $(KEYWORD) in front of the alias name' );

@@ -654,6 +647,5 @@ }

id = id.slice( 2, -1 ).replace( /]]/g, ']' );
if (!id) {
this.error( 'syntax-empty-ident', token, {},
'Delimited identifier must contain at least one character' );
}
if (!id)
this.message( 'syntax-invalid-name', token, {} );
// $delimited is used to complain about ![$self] and other magic vars usage;

@@ -668,4 +660,3 @@ // we might complain about that already here via @arg{category}

if (!id) {
this.error( 'syntax-empty-ident', token, {},
'Delimited identifier must contain at least one character' );
this.message( 'syntax-invalid-name', token, {} );
}

@@ -719,3 +710,3 @@ else {

return ref;
this.error( 'syntax-not-supported', item.location, {},
this.error( 'syntax-unsupported-method', item.location, {},
'Methods in expressions are not supported yet' );

@@ -834,3 +825,3 @@ path.broken = true;

function atChar(i) {
function atChar( i ) {
// Is only used with single-line strings.

@@ -859,3 +850,3 @@ return location.col + pos + i;

};
this.error( 'syntax-anno-space', wsLocation, {}, // TODO: really Error?
this.error( 'syntax-unexpected-space', wsLocation, {}, // TODO: really Error?
'Expected identifier after \'@\' but found whitespace' );

@@ -917,11 +908,11 @@ }

if (kind === 0) {
this.error( 'duplicate-argument', loc, { name: duplicateName },
this.error( 'syntax-duplicate-argument', loc, { name: duplicateName },
'Duplicate value for parameter $(NAME)' );
}
else if (kind === '') {
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
'Duplicate $(NAME) in the $(KEYWORD) clause' );
this.error( 'syntax-duplicate-excluding', loc,
{ name: duplicateName, keyword: 'excluding' } );
}
else {
this.error( 'duplicate-prop', loc, { name: duplicateName },
this.error( 'syntax-duplicate-property', loc, { name: duplicateName },
'Duplicate value for structure property $(NAME)' );

@@ -1044,21 +1035,2 @@ }

// Set property `prop` of `target` to value `value`. Issue error if that
// property has been set before, while mentioning the keywords previously
// provided (as arguments `tokens`).
function setOnce( target, prop, value, ...tokens ) {
const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] );
const prev = target[prop];
if (prev) {
this.error( 'syntax-repeated-option', loc, { option: prev.option },
'Option $(OPTION) has already been specified' );
}
if (typeof value === 'boolean') {
if (!value)
loc.value = false;
value = loc;
}
value.option = tokens.map( t => t.text.toUpperCase() ).join(' ');
target[prop] = value;
}
function setMaxCardinality( art, token, max ) {

@@ -1070,4 +1042,4 @@ const location = this.tokenLocation( token );

else {
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
'The target cardinality has already been specified - ignored $(KEYWORD)' );
this.warning( 'syntax-duplicate-cardinality', location, { keyword: token.text },
'The target cardinality has already been specified - ignoring $(KEYWORD)' );
}

@@ -1089,29 +1061,16 @@ }

function associationInSelectItem( art ) {
if (!art.value) // e.g. `expand` without value (for new structures)
return;
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];
const { value } = art;
const path = value?.path;
// we cannot compare "just one token before `:`" because there might be annos
if (path && path.length === 1 && !art.name && !art.expand && !art.inline) {
const name = value.path[0];
if (path.length === 1 && !name.args && !name.cardinality && !name.where) {
art.name = name;
delete art.value;
return art;
}
}
this.error( 'syntax-unexpected-assoc', this.getCurrentToken(), {},
'Unexpected association definition in select item' );
return {}; // result of the association rules are written into /dev/null
}

@@ -1129,4 +1088,4 @@

this.error( 'syntax-unexpected-nested-proj', token,
{ prop: isInline ? 'inline' : 'expand' },
{ std: 'Unexpected nested $(PROP), can only be used after a reference' } );
{ code: isInline ? '.{ ‹inline› }' : '{ ‹expand› }' },
'Unexpected $(CODE); nested projections can only be used after a reference' );
// continuation semantics:

@@ -1139,4 +1098,4 @@ // - add elements anyway (could lead to duplicate errors as usual)

const location = this.tokenLocation( isInline, this._input.LT(-1) );
this.error( 'syntax-unexpected-alias', location, { prop: 'inline' },
'Unexpected alias name before nested $(PROP)' );
this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' },
'Unexpected alias name before $(CODE)' );
// continuation semantics: ignore AS

@@ -1147,14 +1106,14 @@ }

function checkTypeFacet( art, argIdent ) {
// TODO: use dictAddArray or dictAdd?
const { id } = argIdent;
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 } );
{ '#': 'type', name: id } );
// continuation semantics: use last
}
return true;
}
this.error( 'syntax-duplicate-argument', argIdent.location,
{ '#': 'unknown', code: id } );
this.error( 'syntax-undefined-param', argIdent.location, { name: id },
'There is no type parameter called $(NAME)');
return false;

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

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

*/
function stripIndentation(str) {
function stripIndentation( str ) {
if (str === '')

@@ -514,3 +514,3 @@ return [ '', 0 ];

*/
function parseMultiLineStringLiteral(token) {
function parseMultiLineStringLiteral( token ) {
const p = new MultiLineStringParser(this, token);

@@ -517,0 +517,0 @@ return p.parse();

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

*/
function isWhitespaceOrNewLineOnly(str) {
function isWhitespaceOrNewLineOnly( str ) {
return /^\s*$/.test(str);

@@ -37,3 +37,3 @@ }

*/
function isWhitespaceCharacterNoNewline(char) {
function isWhitespaceCharacterNoNewline( char ) {
return whitespaceRegEx.test(char);

@@ -40,0 +40,0 @@ }

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

const referenceSemantics = {
$init: { $initOnly: true },
type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458

@@ -536,2 +537,4 @@ includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway

const semantics = referenceSemantics[refCtx] || {};
if (semantics.$initOnly)
return undefined;
if (semantics.dynamic === 'global' || ref.global)

@@ -984,2 +987,4 @@ return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc );

// $self refers to the anonymous aspect
if (resolve)
resolve( '', '$init', main );
main = obj;

@@ -986,0 +991,0 @@ art = obj;

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

const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
error(null, null, { version: beforeVersion }, 'Invalid CSN version: $(VERSION)');
error(null, null, { version: beforeVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
throwWithAnyError();

@@ -58,3 +58,3 @@ }

const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
error(null, null, { version: afterVersion }, 'Invalid CSN version: $(VERSION)');
error(null, null, { version: afterVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
throwWithAnyError();

@@ -61,0 +61,0 @@ }

@@ -13,2 +13,3 @@ {

"template-curly-spacing":["error", "never"],
"class-methods-use-this": "off",
// Who cares - just very whiny and in the way

@@ -15,0 +16,0 @@ "complexity": "off",

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

/**

@@ -90,3 +89,2 @@ * Get the part that is really the name of this artifact and not just prefix caused by a context/service

const namespace = getNamespace(csn, artifactName);

@@ -134,3 +132,2 @@ const startIndex = namespace ? namespace.split('.').length : 0;

}
return null;

@@ -410,4 +407,3 @@ }

* @param {object|array} expression
* @param {CdlRenderEnvironment} env
* @this {{inline: Boolean, nestedExpr: Boolean}}
* @this {ExpressionRenderer}
* @returns {string}

@@ -422,3 +418,3 @@ */

* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
* @property {renderPart} explicitTypeCast
* @property {renderPart} typeCast
* @property {renderPart} val

@@ -433,90 +429,144 @@ * @property {renderPart} enum

* @property {renderPart} SET
* @property {boolean} [inline]
* @property {boolean} [nestedExpr]
* @property {Function} [visitExpr]
* @property {Function} [renderExpr]
* @property {Function} [renderSubExpr]
* @property {boolean} [isNestedXpr]
* @property {CdlRenderEnvironment} [env]
*/
/**
* @typedef {object} ExpressionRenderer
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
* @property {renderPart} typeCast
* @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
* @property {Function} visitExpr
* @property {Function} renderExpr
* @property {Function} renderSubExpr
* @property {boolean} isNestedXpr
* @property {CdlRenderEnvironment} env
*/
/**
* If `xpr` has a `cast` property, return a copy without it, otherwise return `xpr`.
* Useful for removing e.g. top-level CDL-style casts that should not be rendered as CAST().
*
* @param xpr
*/
function withoutCast(xpr) {
return !xpr.cast ? xpr : { ...xpr, cast: undefined };
}
/**
* 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
* @param {ExpressionConfiguration} rendererBase
* @returns {ExpressionRenderer} Expression rendering utility
*/
function getExpressionRenderer(renderer) {
function createExpressionRenderer(rendererBase) {
const renderer = Object.create(rendererBase);
renderer.visitExpr = visitExpr;
/**
* 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
* @param {any} x
* @param {CdlRenderEnvironment} env
*/
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);
renderer.renderExpr = function renderExpr(x, env) {
/** @type {ExpressionRenderer} */
const renderObj = Object.create(renderer);
renderObj.env = env || this?.env;
// The outermost expression is not nested. All `.xpr` inside `expr`
// are nested. This information is used for adding parentheses around
// expressions (see `this.xpr()`).
renderObj.isNestedXpr = false;
return renderObj.visitExpr(x);
};
/**
* @param {any} x
* @param {CdlRenderEnvironment} env
*/
renderer.renderSubExpr = function renderSubExpr(x, env) {
/** @type {ExpressionRenderer} */
const renderObj = Object.create(renderer);
renderObj.env = env || this?.env;
renderObj.isNestedXpr = true;
return renderObj.visitExpr(x);
};
return renderer;
}
/**
* 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)}`);
}
};
/**
* Render an expression (including paths and values) or condition 'x'.
* (no trailing LF, don't indent if inline)
*
* `this` must refer to an object of type `ExpressionRenderer`, see
* `createExpressionRenderer()`
*
* @param {any} x (Sub-)Expression to render
*
* @this ExpressionRenderer
* @returns {string} Rendered expression
*/
function visitExpr(x) {
if (Array.isArray(x)) {
// Compound expression, e.g. for on- or where-conditions.
// If xpr is part of an array, it's always a nested xpr,
// e.g. CSN for `(1=1 or 2=2) and 3=3`.
const tokens = x.map(item => this.renderSubExpr(item));
return beautifyExprArray(tokens);
}
else if (typeof x !== 'object' || x === null) {
// Not a literal value but part of an operator, function etc - just leave as it is
return this.finalize(x);
}
else if (x.cast?.type && !x.cast.target) {
return this.typeCast(x);
}
else if (x.list) {
// Render as non-nested expr.
return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`;
}
else if (x.val !== undefined) {
return this.val(x);
}
else if (x['#']) {
// Enum symbol
return this.enum(x);
}
else if (x.ref) {
// Reference: Array of path steps, possibly preceded by ':'
return this.ref(x);
}
else if (x.func) {
// Function call, possibly with args (use '=>' for named args)
if (x.xpr)
return this.windowFunction(x);
return this.func(x);
}
else if (x.xpr) {
return this.xpr(x);
}
else if (x.SELECT) {
return this.SELECT(x);
}
else if (x.SET) {
return this.SET(x);
}
else if (x.as) {
return this.aliasOnly(x);
}
throw new ModelError(`renderExpr(): Unknown expression: ${JSON.stringify(x)}`);
}
/**

@@ -540,4 +590,3 @@ * @typedef CdlRenderEnvironment Rendering environment used throughout the render process.

renderFunc,
getExpressionRenderer,
beautifyExprArray,
createExpressionRenderer,
getNamespace,

@@ -554,2 +603,3 @@ getRealName,

getSqlSnippets,
withoutCast,
};

@@ -13,2 +13,4 @@ {

"max-len": "off",
// there seem to be false positives
"jsdoc/require-returns-check": "off",
// Don't enforce stupid descriptions

@@ -15,0 +17,0 @@ "jsdoc/require-param-description": "off",

{
"root": true,
"plugins": ["sonarjs", "jsdoc"],
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
"rules": {
"prefer-const": "error",
"quotes": ["error", "single", "avoid-escape"],
"prefer-template": "error",
"no-trailing-spaces": "error",
"template-curly-spacing":["error", "never"],
"complexity": ["warn", 30],
"max-len": "off",
// Don't enforce stupid descriptions
"jsdoc/require-param-description": "off",
"jsdoc/require-returns-description": "off",
// Sometimes if-else's are more specific
"sonarjs/prefer-single-boolean-return": "off",
// Very whiny and nitpicky
"sonarjs/cognitive-complexity": "off",
// Does not recognize TS types
"jsdoc/no-undefined-types": "off",
// Whiny and annoying
"sonarjs/no-duplicate-string": "off"
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "script"
},
"env": {
"es2020": true,
"node": true
},
"settings": {
"jsdoc": {
"mode": "typescript"
}
}
"extends": ["../db/.eslintrc.json"]
}

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

// Resolve annotation shorthands for entities, types, annotations, ...
renameShorthandAnnotations(def);
renameShorthandAnnotations(def, ['definitions', defName ]);

@@ -224,3 +224,3 @@ // Annotate artifacts with their DB names if requested.

forEachMemberRecursively(def, (member, memberName, propertyName) => {
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested

@@ -238,3 +238,3 @@ // Only these are actually required and don't annotate virtual elements in entities or types

// Resolve annotation shorthands for elements, actions, action parameters
renameShorthandAnnotations(member);
renameShorthandAnnotations(member, path);

@@ -304,3 +304,3 @@ // - If the association target is annotated with @cds.odata.valuelist, annotate the

// list.
function renameShorthandAnnotations(node) {
function renameShorthandAnnotations(node, path) {
// FIXME: Verify this list - are they all still required? Do we need any more?

@@ -375,18 +375,25 @@ const mappings = {

if(typeDef.enum) {
let enumValue = Object.keys(typeDef.enum).map(enumSymbol => {
const enumValue = [];
for(const enumSymbol in typeDef.enum) {
const enumSymbolDef = typeDef.enum[enumSymbol];
let result = { '@Core.SymbolicName': enumSymbol };
if (enumSymbolDef.val !== undefined)
result.Value = enumSymbolDef.val;
else if (node.type && node.type === 'cds.String')
// the symbol is used as value only for type 'cds.String'
result.Value = enumSymbol;
// Can't rely that @description has already been renamed to @Core.Description
// Eval description according to precedence (doc comment must be considered already in Odata transformer
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
if (desc)
result['@Core.Description'] = desc;
return result;
});
if(enumSymbolDef.val === null)
info('odata-enum-value-type', path,
{name: enumSymbol, value: null, anno: '@Valiation.AllowedValues' },
'Value $(VALUE) for enum element $(NAME) not added to $(ANNO)');
else {
const result = { '@Core.SymbolicName': enumSymbol };
if (enumSymbolDef.val !== undefined)
result.Value = enumSymbolDef.val;
else if (node.type && node.type === 'cds.String')
// the symbol is used as value only for type 'cds.String'
result.Value = enumSymbol;
// Can't rely that @description has already been renamed to @Core.Description
// Eval description according to precedence (doc comment must be considered already in Odata transformer
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
if (desc)
result['@Core.Description'] = desc;
enumValue.push(result);
}
}
setAnnotation(node, '@Validation.AllowedValues', enumValue);

@@ -393,0 +400,0 @@ }

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

const { setProp, isDeprecatedEnabled} = require('../base/model');
const { hasErrors } = require('../base/messages');
const { forEachKey } = require('../utils/objectUtils');

@@ -48,4 +47,7 @@ const { cleanSymbols } = require('../base/cleanSymbols.js');

/**
* Create transitive localized convenience views
* Create transitive localized convenience views.
*
* A convenience view is created if the entity/view has a localized element
* or if it exposes an association leading to a localized-tagged target.
*
* INTERNALS:

@@ -77,9 +79,7 @@ * We have three kinds of localized convenience views:

function _addLocalizationViews(csn, options, useJoins, config) {
// Don't try to create convenience views with errors.
if (hasErrors(options.messages)) // TODO: this is actually wrong, consider --test-mode
return csn;
const messageFunctions = makeMessageFunction(csn, options);
if (hasExistingLocalizationViews(csn, options, messageFunctions))
if (checkExistingLocalizationViews(csn, options, messageFunctions)) {
messageFunctions.throwWithError();
return csn;
}

@@ -95,2 +95,3 @@ const { acceptLocalizedView, ignoreUnknownExtensions } = config;

sortCsnDefinitionsForTests(csn, options);
messageFunctions.throwWithError();
return csn;

@@ -718,3 +719,3 @@

*/
function hasExistingLocalizationViews(csn, options, messageFunctions) {
function checkExistingLocalizationViews(csn, options, messageFunctions) {
if (!csn || !csn.definitions)

@@ -721,0 +722,0 @@ return false;

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

if (!exposedTypes[fullQualifiedNewTypeName]) {
setProp(node, '$NameClashReported', true);
error(null, path, { type: fullQualifiedNewTypeName, name: memberName },
'Can\'t create artificial type $(TYPE) for $(NAME) because the name is already used');
return;
return { isExposable, typeDef, typeName, isAnonymous };
}

@@ -136,4 +137,24 @@ }

defName = typeDef.kind === 'type' ? typeName : defName;
exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName);
{
const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName);
// if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
// been catched by expandToFinalBaseType() (forODataNew must not modify external imported services)
if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
if(typeDef.items) {
newElem.items = typeDef.items;
delete newElem.type;
}
else if(newElem.items) {
newElem.items.type = typeName;
if(typeDef.enum)
newElem.items.enum = typeDef.enum;
}
else {
newElem.type = typeName;
if(typeDef.enum)
newElem.enum = typeDef.enum;
}
}
}
});

@@ -160,2 +181,3 @@ copyAnnotations(typeDef, newType);

}
return { isExposable, typeDef, typeName, isAnonymous };

@@ -203,3 +225,3 @@ /**

}
return { isExposable: false };
return { isExposable: false, typeDef, typeName, isAnonymous: false };
}

@@ -206,0 +228,0 @@

@@ -1223,13 +1223,20 @@ 'use strict';

const bop = (op === 'is' && not) || op === '!=' || op === '<>' ? 'or' : 'and';
rc.push('(');
const xpr = { xpr: [] };
xrefvalues.filter(x => x.lhs && x.rhs).forEach((x,i) => {
if(i>0)
rc.push(bop);
rc.push(x.lhs);
rc.push(op);
xpr.i = i;
if(i>0) {
xpr.xpr.push(bop);
}
xpr.xpr.push(x.lhs);
xpr.xpr.push(op);
if(not)
rc.push('not')
rc.push(x.rhs);
xpr.xpr.push('not')
xpr.xpr.push(x.rhs);
});
rc.push(')');
if(xpr.i > 0) {
delete xpr.i;
rc.push(xpr);
}
else
rc.push(...xpr.xpr);
i += not ? 3 : 2;

@@ -1236,0 +1243,0 @@ }

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

@@ -33,3 +33,3 @@ "homepage": "https://cap.cloud.sap/",

"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/ && nyc report --reporter=lcov",
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
"tslint": "tsc --pretty -p .",

@@ -45,3 +45,2 @@ "updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",

"generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js",
"generateScenarioRefs": "cross-env MAKEREFS='true' mocha test/testScenarios.js",
"generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",

@@ -48,0 +47,0 @@ "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/"

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

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

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