@sap/cds-compiler 5.9.4 → 5.9.6

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 5.9.6 - 2025-06-18
11
+
12
+ - to.sql: Fix error when calculated element refers to a localized element.
13
+ - to.edm(x):
14
+ + Fix errors for service entities containing multiple path steps (e.g. `Service.Prefix.MyEntity`).
15
+ + Support enum references in annotation expressions that were resolved by the compiler.
16
+
10
17
  ## Version 5.9.4 - 2025-05-22
11
18
 
12
19
  - to.edm(x): Parameters are marked optional unless explicitly marked as `not null`.
@@ -1489,12 +1489,17 @@ function homeName( art, absoluteOnly ) {
1489
1489
  return art;
1490
1490
  if (art._user) // when providing a path item with filter as “user”
1491
1491
  return homeName( art._user, absoluteOnly );
1492
- if (art._outer) { // in items property, or annotation with path
1492
+ if (art._outer) { // in items property, or annotation with path
1493
+ if (art._outer?.kind === '$annotation')
1494
+ art = art._outer;
1493
1495
  const outer = homeName( art._outer, absoluteOnly );
1494
1496
  if (art.kind === '$annotation') // eslint-disable-next-line sonarjs/no-nested-template-literals
1495
1497
  return `${ outer }/${ quoted( `@${ art.name.id }` ) }`;
1496
1498
  return outer;
1497
1499
  }
1500
+ else if (art.kind === '$annotation') { // in items property, or annotation with path
1501
+ return quoted( `@${ art.name.id }` );
1502
+ }
1498
1503
  else if (art.kind === 'source' || !art.name) { // error reported in parser or on source level
1499
1504
  return null;
1500
1505
  }
@@ -496,6 +496,7 @@ function assertConsistency( model, stage ) {
496
496
  // expressions as annotation values
497
497
  '$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
498
498
  'scale', 'srid', 'length', 'precision', 'scope', '$parens',
499
+ '_block', '_outer', // for annotation assignments
499
500
  ],
500
501
  // TODO: restrict path to #simplePath
501
502
  },
@@ -991,7 +991,7 @@ function check( model ) {
991
991
 
992
992
  function checkAnnotationAssignment1( art, anno ) {
993
993
  const name = anno.name?.id;
994
- if (art.$contains?.$annotation && anno.kind === '$annotation') {
994
+ if (art.$contains?.$annotation && anno.kind === '$annotation' && anno._outer) {
995
995
  if (checkAnnotationAcceptsExpressions( anno, art ))
996
996
  checkAnnotationExpressions( anno, art );
997
997
  }
@@ -1144,7 +1144,7 @@ function check( model ) {
1144
1144
  return;
1145
1145
  }
1146
1146
 
1147
- // Struct expected (can only happen within arrays)?
1147
+ // Struct expected (can only happen within arrays, or CSN input)?
1148
1148
  if (elementDecl._effectiveType.elements) {
1149
1149
  if (value.literal !== 'struct') {
1150
1150
  warning( null, loc, { anno }, 'A struct value is required here for annotation $(ANNO)' );
@@ -298,6 +298,7 @@ function define( model ) {
298
298
  return;
299
299
  }
300
300
  setLink( art, '_block', block );
301
+ initExprAnnoBlock( art, block );
301
302
  // dictAdd might set $duplicates
302
303
  dictAdd( model.definitions, absolute, art );
303
304
  }
@@ -393,6 +394,7 @@ function define( model ) {
393
394
 
394
395
  function addExtension( ext, block ) {
395
396
  setLink( ext, '_block', block );
397
+ initExprAnnoBlock( ext, block );
396
398
  const absolute = ext.name && resolveUncheckedPath( ext.name, '_extensions', ext );
397
399
  if (!absolute) // broken path
398
400
  return;
@@ -429,6 +431,7 @@ function define( model ) {
429
431
  if (prop === 'params' && name === '') // RETURNS
430
432
  sub.name = { id: '', location: sub.location };
431
433
  setLink( sub, '_block', parent._block );
434
+ initExprAnnoBlock( sub, parent._block );
432
435
  setLink( sub, '_parent', parent );
433
436
  setLink( sub, '_main', parent._main || parent );
434
437
  initExtension( sub );
@@ -446,6 +449,26 @@ function define( model ) {
446
449
  }
447
450
  }
448
451
 
452
+ function initExprAnnoBlock( art, block ) {
453
+ // remark: `art` could also be the extension
454
+ for (const prop in art) {
455
+ if (prop.charAt(0) !== '@')
456
+ continue;
457
+ const anno = art[prop];
458
+ // _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
459
+ if (anno.literal === 'array') {
460
+ anno.kind = '$annotation';
461
+ for (const item of anno.val)
462
+ setLink( item, '_block', block );
463
+ }
464
+ else if (anno.$tokenTexts) {
465
+ // remark: it wouldn't hurt to set it always...
466
+ anno.kind = '$annotation';
467
+ setLink( anno, '_block', block );
468
+ }
469
+ }
470
+ }
471
+
449
472
  function addVocabulary( vocab, block, prefix ) {
450
473
  setLink( vocab, '_block', block );
451
474
  const { name } = vocab;
@@ -1009,6 +1032,7 @@ function define( model ) {
1009
1032
  }
1010
1033
 
1011
1034
  initItemsLinks( col, parent._block );
1035
+ initExprAnnoBlock( col, parent._block );
1012
1036
  }
1013
1037
 
1014
1038
  if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
@@ -1069,6 +1093,7 @@ function define( model ) {
1069
1093
  const main = parent._main || parent;
1070
1094
  const isQueryExtension = construct.kind === 'extend' && main.query;
1071
1095
  let obj = initItemsLinks( construct, block );
1096
+ initExprAnnoBlock( construct, block );
1072
1097
  if (obj.target && targetIsTargetAspect( obj )) {
1073
1098
  obj.targetAspect = obj.target;
1074
1099
  delete obj.target;
@@ -489,6 +489,7 @@ function extend( model ) {
489
489
  const location = firstEllipsis.location || anno.name.location;
490
490
  message( 'anno-unexpected-ellipsis', [ location, art ], { code: '...' } );
491
491
  previousAnno = {
492
+ kind: '$annotation',
492
493
  val: [],
493
494
  literal: 'array',
494
495
  name: { id: annoName.slice( 1 ) },
@@ -499,6 +500,7 @@ function extend( model ) {
499
500
  // TODO: If we introduce sub-messages, point to the non-array base value.
500
501
  error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
501
502
  previousAnno = {
503
+ kind: '$annotation',
502
504
  val: [],
503
505
  literal: 'array',
504
506
  name: previousAnno.name,
@@ -532,6 +534,7 @@ function extend( model ) {
532
534
  }
533
535
  // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
534
536
  return {
537
+ kind: '$annotation',
535
538
  val: result,
536
539
  literal: 'array',
537
540
  name: previousAnno.name,
@@ -1456,19 +1456,35 @@ function resolve( model ) {
1456
1456
  }
1457
1457
  }
1458
1458
 
1459
- function resolveAnnoExpr( expr, art, anno = expr ) {
1459
+ function resolveAnnoExpr( expr, art, anno = expr, topItem = false ) {
1460
1460
  if (expr.$tokenTexts) {
1461
- if (!anno.kind)
1461
+ if (anno === expr) {
1462
1462
  initAnnotationForExpression( anno, art );
1463
+ }
1464
+ else if (topItem) { // item of top-level array annotation
1465
+ setLink( expr, '_outer', anno );
1466
+ anno = expr;
1467
+ }
1468
+
1463
1469
  const type = anno === expr && anno.name;
1464
1470
  // TODO: it might be best to set an _artifact link also for property values
1465
1471
  // like in `@Anno: [ { foo: #EnumSymbol }]
1466
1472
  resolveExpr( expr, 'annotation', anno, type );
1467
1473
  }
1468
1474
  else if (expr.literal === 'array') {
1469
- expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
1475
+ const withExpr = anno === expr &&
1476
+ // the following is needed for checkAnnotationAcceptsExpressions(),
1477
+ // otherwise @cds.persistence.skip: [hdi, hdbcds, sql.postgres] fails
1478
+ expr.val.some( item => item.$tokenTexts || item.literal === 'struct');
1479
+ if (withExpr)
1480
+ initAnnotationForExpression( anno, art );
1481
+ expr.val.forEach( val => resolveAnnoExpr( val, art, anno, withExpr ) );
1470
1482
  }
1471
1483
  else if (expr.literal === 'struct') {
1484
+ if (topItem) { // item of top-level array annotation
1485
+ setLink( expr, '_outer', anno );
1486
+ anno = expr;
1487
+ }
1472
1488
  Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art, anno ) );
1473
1489
  }
1474
1490
  }
@@ -1480,10 +1496,9 @@ function resolve( model ) {
1480
1496
  * @param {XSN.Artifact} art
1481
1497
  */
1482
1498
  function initAnnotationForExpression( anno, art ) {
1483
- anno.kind = '$annotation';
1484
1499
  setLink( anno, '_outer', art );
1485
1500
  art.$contains ??= {};
1486
- art.$contains.$annotation = { // set in resolveExprNode
1501
+ art.$contains.$annotation ??= { // set in resolveExprPath
1487
1502
  $path: false,
1488
1503
  $self: false,
1489
1504
  };
@@ -1491,6 +1506,9 @@ function resolve( model ) {
1491
1506
  // Might be useful for future recursive types.
1492
1507
  }
1493
1508
 
1509
+ // Note: for annotation assignments, `art` should be the annotation, not the
1510
+ // assignee (element, ...); otherwise, artifact links could be resolved in the
1511
+ // wrong block
1494
1512
  function resolveExpr( expr, exprCtx, art, type = null ) {
1495
1513
  traverseTypedExpr( expr, exprCtx, art, type, resolveExprNode );
1496
1514
  }
@@ -1543,8 +1561,12 @@ function resolve( model ) {
1543
1561
  const ref = resolvePath( expr, expected, user );
1544
1562
 
1545
1563
  if (expected === 'annotation') {
1546
- user._outer.$contains.$annotation.$path = true;
1547
- user._outer.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1564
+ const ruser = (user._outer.kind === '$annotation')
1565
+ ? user._outer._outer
1566
+ : user._outer;
1567
+ // if (!ruser.$contains) console.log('UO:',!!ruser,!!user._block,user._outer)
1568
+ ruser.$contains.$annotation.$path = true;
1569
+ ruser.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1548
1570
  }
1549
1571
 
1550
1572
  // check whether arguments and filters are allowed on last path item;
@@ -775,8 +775,13 @@ function fns( model ) {
775
775
  if (head._artifact !== undefined)
776
776
  return head._artifact;
777
777
  let ruser = user._user || user; // TODO: nicer name if we keep this
778
- if (ruser.kind === '$annotation')
779
- ruser = ruser._outer;
778
+ // TODO: re-think _user link
779
+ if (ruser._outer && !semantics.isMainRef) {
780
+ if (ruser.kind === '$annotation')
781
+ ruser = ruser._outer; // for elem refs, use elem as real "user"
782
+ else if (ruser._outer.kind === '$annotation')
783
+ ruser = ruser._outer._outer;
784
+ }
780
785
 
781
786
  // Handle expand/inline, `type of`, :param, global (internally for CDL):
782
787
  if (user._columnParent && !semantics.isMainRef) { // in expand/inline
@@ -1002,7 +1007,8 @@ function fns( model ) {
1002
1007
  // TODO: remove again, should be easy enough in to-csn without.
1003
1008
  if (path.length === 1 && art.kind === '$tableAlias')
1004
1009
  (user._user || user).$noOrigin = true;
1005
- if (head.id === '$projection' && user.kind === '$annotation') {
1010
+ if (head.id === '$projection' &&
1011
+ (user.kind === '$annotation' || user._outer?.kind === '$annotation')) {
1006
1012
  error( 'ref-unsupported-projection', [ head.location, user ],
1007
1013
  { code: '$projection', newcode: '$self' },
1008
1014
  '$(CODE) is not supported in annotations; replace by $(NEWCODE)' );
@@ -83,7 +83,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
83
83
  });
84
84
  delete parent[op];
85
85
  };
86
- const noOp = () => (true);
86
+ const noOp = () => {};
87
87
  //----------------------------------
88
88
  // Create the transformer dictionary
89
89
  const transform = {
@@ -96,7 +96,6 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
96
96
  isNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is null'),
97
97
  isNotNull: (p, o) => notADynExpr(p, o, null, null, null, null, 'is not null'),
98
98
  exists: notADynExpr,
99
- '#': notADynExpr,
100
99
  SELECT: notADynExpr,
101
100
  SET: (p, o) => notADynExpr(p, o, null, null, null, null, 'UNION'),
102
101
  like: notADynExpr,
@@ -312,6 +311,12 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
312
311
  else
313
312
  parentparent[parentprop] = xpr;
314
313
  };
314
+ transform['#'] = (parent, prop, xpr, csnPath, parentParent, parentprop, txt) => {
315
+ if (parent['#'] && parent.val) // enum reference that was resolved by the compiler
316
+ delete parent['#'];
317
+ else
318
+ notADynExpr(parent, prop, xpr, csnPath, parentParent, parentprop, txt);
319
+ };
315
320
  transform.ref = (parent, prop, xpr, csnPath, parentparent, parentprop) => {
316
321
  if (xpr.some(ps => ps.args || ps.where)) {
317
322
  error('odata-anno-xpr-ref', location, {
@@ -5,7 +5,8 @@ const { setProp, isDeprecatedEnabled, isBetaEnabled } = require('../base/model')
5
5
  const {
6
6
  forEachDefinition, forEachGeneric, forEachMemberRecursively,
7
7
  isEdmPropertyRendered, getUtils,
8
- applyTransformations, transformAnnotationExpression, findAnnotationExpression,
8
+ applyTransformations, applyTransformationsOnNonDictionary,
9
+ transformAnnotationExpression, findAnnotationExpression,
9
10
  cardinality2str,
10
11
  } = require('../model/csnUtils');
11
12
  const { isBuiltinType, isMagicVariable } = require('../base/builtins');
@@ -322,11 +323,32 @@ function initializeModel( csn, _options, messageFunctions, requestedServiceNames
322
323
  if (node.$path && dotEntityNameMap[node.$path[1]])
323
324
  node.$path[1] = dotEntityNameMap[node.$path[1]];
324
325
 
326
+ if (node.projection || node.query) {
327
+ applyTransformationsOnNonDictionary(node.projection || node.query, undefined, {
328
+ ref: (parent, prop, ref) => {
329
+ if (dotEntityNameMap[ref[0]])
330
+ parent.ref[0] = dotEntityNameMap[ref[0]];
331
+ },
332
+ }, { directDict: true });
333
+ }
334
+
325
335
  rewriteReferencesInActions(node);
326
336
  };
327
337
 
328
- forEachMemberRecursively(def, applyOnNode);
338
+ const applyOnAnnoXprs = (node) => {
339
+ Object.keys(node).filter(pn => pn[0] === '@').forEach((anno) => {
340
+ transformAnnotationExpression(node, anno, {
341
+ ref: (elemRef, _prop, ref) => {
342
+ if (dotEntityNameMap[ref[0]] || dotTypeNameMap[ref[0]])
343
+ elemRef.ref[0] = dotEntityNameMap[ref[0]] || dotTypeNameMap[ref[0]];
344
+ },
345
+ });
346
+ });
347
+ };
348
+
349
+ forEachMemberRecursively(def, [ applyOnNode, applyOnAnnoXprs ]);
329
350
  applyOnNode(def);
351
+ applyOnAnnoXprs(def);
330
352
  // handle unbound action/function and params in views
331
353
  rewriteReferencesInActions(def);
332
354
  };
@@ -697,8 +697,10 @@ function removeDummyValueInEntity( artifact, path, options ) {
697
697
  function dummifyInEntity( artifact, path ) {
698
698
  applyTransformationsOnDictionary(artifact.elements, {
699
699
  value: (parent, _prop, value) => {
700
- if (!value.stored)
700
+ if (!value.stored) {
701
701
  parent.value = { val: 'DUMMY' };
702
+ delete parent.localized;
703
+ }
702
704
  },
703
705
  }, {}, path);
704
706
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "5.9.4",
3
+ "version": "5.9.6",
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)",
@@ -18,7 +18,8 @@
18
18
  "gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js && node ./redepage/bin/redepage --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4",
19
19
  "xmakeAfterInstall": "npm run gen",
20
20
  "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
21
- "test": "npm run test:piper",
21
+ "test": "node scripts/xmakeTestDispatcher.js",
22
+ "test:xmake": "npm run test3 -- --topic=Drafts --test=CalculatedElements",
22
23
  "test:ci": "node scripts/verifyGrammarChecksum.js && mocha --timeout 10000 --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
23
24
  "test:piper": "node scripts/verifyGrammarChecksum.js && npm run coverage:piper",
24
25
  "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",