@sap/cds-compiler 5.5.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -208,7 +208,7 @@ aspectDef[ art, outer ]
208
208
  @finally{ this.attachLocation( $art ); }
209
209
  :
210
210
  ( ASPECT
211
- | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.lb().location ); }
211
+ | <hide> ABSTRACT { this.warning( 'syntax-deprecated-abstract', this.combineLocation( this.lb(), this.la() ) ); }
212
212
  ENTITY
213
213
  )
214
214
  name=namePath[ 'Type' ] // TODO: Type?
@@ -418,7 +418,7 @@ elementDef[ outer, art = undefined ]
418
418
  ( KEY { $art.key = this.valueWithLocation( true ); } )?
419
419
  ( <hide> MASKED { $art.masked = this.valueWithLocation( true ); }
420
420
  { this.message( 'syntax-unsupported-masked', this.lb(), { keyword: 'masked' } ); } )?
421
- ( ELEMENT { $art.$syntax = 'element'; } )?
421
+ ( <hide> ELEMENT { $art.$syntax = 'element'; } )?
422
422
  Id['Element'] <prepare=elementRestriction, arg=elem>
423
423
  { this.addDef( $art, $outer, 'elements', 'element', this.identAst() ); }
424
424
  { this.docComment( $art ); } annoAssignMid[ $art ]*
@@ -507,7 +507,8 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
507
507
  ( assoc=ASSOCIATION cardinality[ $art ]? TO
508
508
  | assoc=COMPOSITION cardinality[ $art ]? OF
509
509
  )
510
- card=ONE/MANY? target=simplePath
510
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
511
+ target=simplePath
511
512
  { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
512
513
  ON expr=condition { $art.on = $expr; }
513
514
  ;
@@ -515,7 +516,7 @@ mixinElementDef[ outer ] locals[ art = new XsnArtifact() ]
515
516
  // Annotate and Extend: main definitions ----------------------------------------
516
517
 
517
518
  annotateArtifact[ art, outer ]
518
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
519
+ @finally{ this.attachLocation( $art ); }
519
520
  :
520
521
  name=namePath[ 'Ext' ]
521
522
  ( // direct element annotation:
@@ -538,10 +539,11 @@ annotateArtifact[ art, outer ]
538
539
  annotateActionsBlock[ $art ]?
539
540
  )
540
541
  )
542
+ { this.checkWith( $keyword ); }
541
543
  ;
542
544
 
543
545
  extendArtifact[ art, outer ]
544
- @finally{ this.checkWith( $keyword ); this.attachLocation( $art ); }
546
+ @finally{ this.attachLocation( $art ); }
545
547
  :
546
548
  name=namePath[ 'Ext' ]
547
549
  ( // direct element annotation:
@@ -586,6 +588,7 @@ extendArtifact[ art, outer ]
586
588
  DEFINITIONS artifactsBlock[ $art, this.lb() ]
587
589
  )?
588
590
  )
591
+ { this.checkWith( $keyword ); }
589
592
  ;
590
593
 
591
594
  extendService[ art, outer ]
@@ -864,11 +867,13 @@ typeExpression[ art ]
864
867
  typeProperties[ $art ]?
865
868
  |
866
869
  assoc=ASSOCIATION <prepare=elementRestriction, arg=calc>
867
- cardinality[ $art ]? TO card=ONE/MANY?
870
+ cardinality[ $art ]? TO
871
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
868
872
  typeAssocProperties[ $art, $assoc, $card ]
869
873
  |
870
874
  assoc=COMPOSITION <prepare=elementRestriction, arg=calc>
871
- cardinality[ $art ]? OF card=ONE/MANY?
875
+ cardinality[ $art ]? OF
876
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
872
877
  ( typeAssocProperties[ $art, $assoc, $card ]
873
878
  | elementsBlock[ this.setAssocAndComposition( $art, $assoc, $card ) ]
874
879
  { $art.target.location = $art.target.elements[Symbol.for('cds.$location')]; }
@@ -987,6 +992,7 @@ typeNamedArgsList[ art ]
987
992
 
988
993
  typeNamedArg[ art ]
989
994
  :
995
+ // TODO: or keywords with guards for better code completion?
990
996
  name=Id['typeparamname']
991
997
  ':'
992
998
  ( Number
@@ -1053,12 +1059,11 @@ projectionSpec returns[ default query = {} ]
1053
1059
  @finally{ this.attachLocation($query); }
1054
1060
  :
1055
1061
  // TODO, currently just with simple ref
1056
- PROJECTION { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1062
+ PROJECTION <prepare=afterBrace> // v6: remove <prepare>
1063
+ { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1057
1064
  ON
1058
- tab=fromRefWithOptAlias
1065
+ tab=fromRefWithOptAlias <prepare=afterBrace>
1059
1066
  // TODO: this <prepare=afterBrace> is extremely strange... v6 forbid.
1060
- // Deliberately set this via action (→ interpreter will not accept this)
1061
- { this.afterBrace(); }<always>
1062
1067
  { $query.from = tab; }
1063
1068
  selectItemsList[ $query ]?
1064
1069
  excludingClause[ $query ]?
@@ -1115,6 +1120,8 @@ selectQuery returns[ default query = {} ]
1115
1120
  excludingClause[ $query ]?
1116
1121
  |
1117
1122
  ( ALL/DISTINCT { $query.quantifier = this.valueWithLocation(); } )?
1123
+ // TODO TOOL: move <prepare> to all branches if "simple", or with special <…,attach>
1124
+ {;} <prepare=inSelectItem, arg=sqlStyle>
1118
1125
  ( '*' { $query.columns = [ this.valueWithLocation() ]; }
1119
1126
  | selectItemDef[ ($query.columns = []) ]
1120
1127
  )
@@ -1221,8 +1228,7 @@ fromRefWithOptAlias returns[ default expr = {} ]
1221
1228
  (
1222
1229
  AS Id['FromAlias'] { $expr.name = this.identAst(); }
1223
1230
  |
1224
- <cond=tableWithoutAs>
1225
- // TODO: probably not necessary, TOOL already uses `default: this.giR()`
1231
+ // <cond=tableWithoutAs> not necessary, tool uses `default: this.giR()`
1226
1232
  Id_restricted['FromAlias']
1227
1233
  { $expr.name = this.fragileAlias(); }
1228
1234
  |
@@ -1237,6 +1243,7 @@ fromPath[ table, category ] locals[ pathItem ]
1237
1243
  Id[ $category ] { $table.path.push( $pathItem = this.identAst() ); }
1238
1244
  ( fromArgumentsAndFilter[ $pathItem ] { $pathItem = null; } )?
1239
1245
  (
1246
+ <cond=notAfterEntityArgOrFilter> // TODO TOOL: allow <hide=method>
1240
1247
  '.' { if (!$pathItem && !$table.scope) {
1241
1248
  $table.scope = $table.path.length; $category = 'ref';
1242
1249
  this.warning( 'syntax-invalid-path-separator', this.lb(),
@@ -1352,24 +1359,24 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1352
1359
  { $columns.push( $art ); } // TODO: probably too early
1353
1360
  { this.docComment( $art ); } annoAssignCol[ $art ]*
1354
1361
  ( <cond=modifierRestriction> VIRTUAL
1355
- { $art.virtual = this.valueWithLocation( true ); } )?
1362
+ { $art.virtual = this.valueWithLocation( true ); }
1363
+ )?
1364
+ {;} <prepare=columnExpr, arg=key> // TOOL TODO: disappears without {;}
1356
1365
  ( <cond=modifierRestriction> KEY
1357
- { $art.key = this.valueWithLocation( true ); } )?
1366
+ { $art.key = this.valueWithLocation( true ); }
1367
+ )?
1358
1368
  (
1359
1369
  expr=expression { $art.value = $expr; }
1360
1370
  ( as=AS Id['ItemAlias'] { $art.name = this.identAst(); }
1361
1371
  | Id_restricted['ItemAlias'] { $art.name = this.fragileAlias( true ); }
1362
1372
  | { $alias = this.classifyImplicitName( 'ItemImplicit', $expr ); }
1363
- )
1364
- // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1365
- (
1366
- { this.reportExpandInline( $art, false ); }
1367
- nestedSelectItemsList[ $art, 'expand' ]
1368
- excludingClause[ $art ]?
1369
1373
  |
1374
+ // TODO: guard for ref-only expression can probably replace reportExpandInline
1370
1375
  '.'
1371
1376
  { this.reportUnexpectedSpace( this.lb(), this.la().location, true ); } // TODO: no ERR
1372
1377
  { this.reportExpandInline( $art, $as || true ); }
1378
+ // no extra 'syntax-unexpected-alias' anymore,
1379
+ // 'syntax-unexpected-anno' reported in define.js
1373
1380
  { if ($alias) $alias.token.parsedAs = $alias.parsedAs; }
1374
1381
  (
1375
1382
  nestedSelectItemsList[ $art, 'inline' ]
@@ -1377,13 +1384,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1377
1384
  |
1378
1385
  '*' { $art.inline = [ this.valueWithLocation() ]; }
1379
1386
  )
1387
+ <exitRule>
1388
+ )
1389
+ // TODO: <cond> instead `reportExpandInline` for "expand/inline only w/ ref"
1390
+ (
1391
+ // TODO: guard for ref-only expression can probably replace:
1392
+ { this.reportExpandInline( $art, false ); }
1393
+ nestedSelectItemsList[ $art, 'expand' ]
1394
+ excludingClause[ $art ]?
1380
1395
  )?
1381
1396
  |
1382
1397
  nestedSelectItemsList[ $art, 'expand' ]
1383
1398
  excludingClause[ $art ]?
1384
1399
  AS Id['ItemAlias'] { $art.name = this.identAst(); }
1385
1400
  )
1386
- { this.docComment( $art ); } annoAssignMid[ $art ]*
1401
+ { this.docComment( $art ); } <prepare=columnExpr> annoAssignMid[ $art ]*
1387
1402
  (
1388
1403
  ':'
1389
1404
  (
@@ -1392,18 +1407,21 @@ selectItemDef[ columns ] locals[ art = new XsnArtifact(), alias ]
1392
1407
  typeRefOptArgs[ $art ]
1393
1408
  )
1394
1409
  |
1410
+ // TODO: guard for ref-only expression ?
1395
1411
  REDIRECTED TO target=simplePath { $art.target = $target; }
1396
1412
  ( ON cond=condition { $art.on = $cond; }
1397
1413
  | foreignKeysBlock[ $art ]
1398
1414
  )?
1399
1415
  |
1400
- // TODO: condition for this
1401
- ( assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1416
+ ( <cond=columnExpr> // arg=singleId
1417
+ assoc=ASSOCIATION { this.associationInSelectItem( $art ); }
1402
1418
  cardinality[ $art ]? TO
1403
- | assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1419
+ | <cond=columnExpr> // arg=singleId
1420
+ assoc=COMPOSITION { this.associationInSelectItem( $art ); }
1404
1421
  cardinality[ $art ]? OF
1405
1422
  )
1406
- card=ONE/MANY? target=simplePath
1423
+ ( <cond=noRepeatedCardinality> card=ONE/MANY )?
1424
+ target=simplePath
1407
1425
  { this.setAssocAndComposition( $art, $assoc, $card, $target ); }
1408
1426
  ON expr=condition { $art.on = $expr; }
1409
1427
  )
@@ -1692,7 +1710,7 @@ options{ minTokensMatched = 1 }
1692
1710
  )*
1693
1711
  )
1694
1712
  )
1695
- ')'
1713
+ ')' { this.finalizeDictOrArray( $pathStep.args ); }
1696
1714
  )?
1697
1715
  // TODO: not with function!
1698
1716
  cardinalityAndFilter[ ...$ ]?
@@ -1972,11 +1990,19 @@ annoValue returns[ default value = {} ]
1972
1990
  ( sub=annoValue { $value.val.push( $sub ) }
1973
1991
  |
1974
1992
  <cond=arrayAnno, arg=ellipsis> ellipsis='...'
1975
- ( UP TO upTo=annoValue | { $upTo = undefined; } )
1976
- { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1993
+ ( UP TO upTo=annoValue
1994
+ { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1995
+ | { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location } ); }
1996
+ )
1997
+ // TODO TOOL: if at last good state the command is ['g'],resume after the
1998
+ // gotos, do not execute its actions - ?
1999
+ // ( UP TO upTo=annoValue | { $upTo = undefined; } )
2000
+ // { $value.val.push( { literal: 'token', val: '...', location: $ellipsis.location, upTo: $upTo } ); }
1977
2001
  )
1978
2002
  ( ',' | <exitLoop> )
1979
2003
  )*
2004
+ // TODO TOOL: allow ( <cond=arrayAnno, arg=bracket> ']' )
2005
+ { this.ec( 'arrayAnno', 'bracket' ); }<always>
1980
2006
  ']'
1981
2007
  |
1982
2008
  '(' $value=condition ')' { $value.$tokenTexts = this.ruleTokensText(); }
@@ -652,6 +652,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
652
652
  if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
653
653
  obj.ref = [ root.$env, ...obj.ref ];
654
654
 
655
+ if (iterateOptions.keepKeysOrigin)
656
+ setProp(obj, '$originalKeyRef', { ref: root.ref, as: root.as });
657
+
655
658
  return obj;
656
659
  });
657
660
  }
@@ -188,7 +188,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
188
188
  if (!structuredOData) {
189
189
  expansion.expandStructureReferences(csn, options, '_',
190
190
  { error, info, throwWithAnyError }, csnUtils,
191
- { skipArtifact: isExternalServiceMember });
191
+ { skipArtifact: isExternalServiceMember, keepKeysOrigin: true });
192
192
  }
193
193
 
194
194
  createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { isBuiltinType } = require('../../base/builtins');
4
4
  const { setProp } = require('../../base/model');
5
- const { applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
5
+ const { transformAnnotationExpression, applyTransformations, implicitAs, findAnnotationExpression, copyAnnotations, isDeepEqual } = require('../../model/csnUtils');
6
6
  const { EdmTypeFacetNames } = require('../../edm/EdmPrimitiveTypeDefinitions');
7
7
 
8
8
  function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iterateOptions = {}) {
@@ -60,6 +60,8 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
60
60
  if (options.transformation === 'effective')
61
61
  delete element.default;
62
62
  }
63
+
64
+ adaptAnnotationsRefs(generatedForeignKeys, csnUtils);
63
65
  setProp(element, '$generatedForeignKeys', generatedForeignKeys.map(gfk => gfk[0]));
64
66
  orderedElements.push(...generatedForeignKeys);
65
67
 
@@ -69,9 +71,9 @@ function createForeignKeyElements(csn, options, messageFunctions, csnUtils, iter
69
71
  elementsAccumulator[name] = element;
70
72
  return elementsAccumulator;
71
73
  }, Object.create(null));
72
- }
74
+ }
73
75
 
74
- function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
76
+ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0, originalkey = {} ) {
75
77
  const special$self = !csn?.definitions?.$self && '$self';
76
78
  const isInspectRefResult = !Array.isArray(path);
77
79
 
@@ -109,7 +111,16 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
109
111
  const continuePath = getContinuePath([ 'keys', keyIndex ]);
110
112
  const alias = key.as || implicitAs(key.ref);
111
113
  const result = csnUtils.inspectRef(continuePath);
112
- fks = fks.concat(createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
114
+ let gfks = createForeignKeysForElement(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1,
115
+ lvl === 0 ? { ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef } : originalkey);
116
+ if (lvl === 0) {
117
+ gfks.forEach(gfk => copyAnnotations(key, gfk[1]));
118
+ // once applied -> remove the annotations from the keys array, to keep the OData CSN size as small as possible
119
+ Object.keys(key).forEach( prop => {
120
+ if (prop[0] === '@') delete key[prop]
121
+ });
122
+ }
123
+ fks = fks.concat(gfks);
113
124
  });
114
125
  }
115
126
  // the element is a structure
@@ -118,7 +129,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
118
129
  // Skip already produced foreign keys
119
130
  if (!elem['@odata.foreignKey4']) {
120
131
  const continuePath = getContinuePath([ 'elements', elemName ]);
121
- fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
132
+ fks = fks.concat(createForeignKeysForElement(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1, originalkey));
122
133
  }
123
134
  });
124
135
  }
@@ -130,7 +141,7 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
130
141
  if (element[prop] !== undefined)
131
142
  newFk[prop] = element[prop];
132
143
  });
133
- return [ [ prefix, newFk ] ];
144
+ return [ [ prefix, newFk, originalkey ] ];
134
145
  }
135
146
 
136
147
  fks.forEach((fk) => {
@@ -140,9 +151,13 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
140
151
  if (lvl === 0) {
141
152
  if (options.transformation !== 'effective')
142
153
  fk[1]['@odata.foreignKey4'] = prefix;
143
-
144
- const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !findAnnotationExpression(element, pn));
145
- copyAnnotations(element, fk[1], true, {}, validAnnoNames);
154
+
155
+ const allowedOverwriteAnnotationNames = ['@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`)];
156
+ const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
157
+ copyAnnotations(element, fk[1], false, {}, validAnnoNames);
158
+ const overwriteAnnoNames = Object.keys(element).filter(pn => allowedOverwriteAnnotationNames.includes(pn) && !findAnnotationExpression(element, pn));
159
+ copyAnnotations(element, fk[1], true, {}, overwriteAnnoNames);
160
+
146
161
  // propagate not null to final foreign key
147
162
  for (const prop of [ 'notNull', 'key' ]) {
148
163
  if (element[prop] !== undefined)
@@ -175,6 +190,74 @@ function createForeignKeysForElement(csnUtils, path, element, prefix, csn, optio
175
190
  return [ ...path, ...additions ];
176
191
  }
177
192
  }
193
+
194
+ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils) {
195
+ let reportedErrorsForAnnoPath = {};
196
+ generatedForeignKeys.forEach(gfk => {
197
+ Object.entries(gfk[1]).forEach(([key, value]) => {
198
+ if (key[0] !== '@') return;
199
+
200
+ transformAnnotationExpression(gfk[1], key, {
201
+ ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
202
+ if (ref[0] !== '$self') {
203
+
204
+ const { art } = csnUtils.inspectRef(getOriginatingKeyPath(gfk, path));
205
+ if (csnUtils.isManagedAssociation(art)) {
206
+ if (!reportedErrorsForAnnoPath[path]) {
207
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
208
+ reportedErrorsForAnnoPath[path] = true;
209
+ }
210
+ } else {
211
+ const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
212
+ if (gfkForRef.length === 1) {
213
+ ref[0] = gfkForRef[0][0];
214
+
215
+ if (ctx?.annoExpr?.['=']) {
216
+ ctx.annoExpr['='] = true;
217
+ }
218
+ } else {
219
+ // check if the annotation reference points to a structure that has been expanded,
220
+ // if so -> report an error
221
+ const foundInOriginalRef = findOriginalRef(generatedForeignKeys.filter(gfk => gfk[2].$originalKeyRef), ref);
222
+ // references to expanded structures in flat mode will be found in the $originalKeyRef
223
+ // and in strucred mode more than one match will be found in the generated foreign keys
224
+ if ((foundInOriginalRef.length || gfkForRef.length > 1) && !reportedErrorsForAnnoPath[path]) {
225
+ error('odata-anno-xpr-ref', path, { elemref: { ref }, anno: key, '#': 'fk_substitution' });
226
+ reportedErrorsForAnnoPath[path] = true;
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }, value?.$path?.slice(0, value.$path.length - 1));
233
+ });
234
+ });
235
+
236
+ // During tuple expansion, the key ref object looses the $path, therefore
237
+ // it needs to be extracted from the anno path
238
+ function getOriginatingKeyPath(gfk, path) {
239
+ return gfk[2].$path || path.slice(0, path.findIndex(ps => ps[0] === '@'));
240
+ }
241
+
242
+ // Loops through the generated foreign keys for this entity
243
+ // and filters the ones, which were created for this specific
244
+ // key ref. In case there are more than one foreign keys found,
245
+ // that means the key ref points to a structured element/managed assoc
246
+ function findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref) {
247
+ return generatedForeignKeys.filter(gfk => {
248
+ return (ref.join() === (gfk[2].as || implicitAs(gfk[2].ref)));
249
+ });
250
+ }
251
+
252
+ // Tuple expansion is performed before the generation of the foreign keys and the original(unexpanded) key ref
253
+ // is stored in the property $originalKeyRef. Here we try to evaluate whether the reference in the annotation
254
+ // points to a structure that has been expanded.
255
+ function findOriginalRef(generatedForeignKeys, ref) {
256
+ return generatedForeignKeys.filter(gfk => {
257
+ return (ref.join() === (gfk[2].$originalKeyRef.as || implicitAs(gfk[2].$originalKeyRef.ref)));
258
+ });
259
+ }
260
+ }
178
261
  }
179
262
 
180
263
  module.exports = createForeignKeyElements;
@@ -83,6 +83,18 @@ function setHidden( obj, prop, val ) {
83
83
  } );
84
84
  }
85
85
 
86
+ /**
87
+ * Check if the given object has the property as non-enumerable
88
+ *
89
+ * @param {Object} object
90
+ * @param {string} propertyName
91
+ * @returns {boolean}
92
+ */
93
+ function hasNonEnumerable( object, propertyName ) {
94
+ return Object.prototype.hasOwnProperty.call( object, propertyName ) &&
95
+ !Object.prototype.propertyIsEnumerable.call( object, propertyName );
96
+ }
97
+
86
98
  module.exports = {
87
99
  copyPropIfExist,
88
100
  createDict,
@@ -90,4 +102,5 @@ module.exports = {
90
102
  forEachValue,
91
103
  forEachKey,
92
104
  setHidden,
105
+ hasNonEnumerable,
93
106
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",