@sap/cds-compiler 5.6.0 → 5.7.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cdsse.js +1 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +169 -144
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +2 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +2 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +32 -32
  27. package/lib/gen/CdlParser.js +2237 -2196
  28. package/lib/json/from-csn.js +2 -0
  29. package/lib/json/to-csn.js +13 -4
  30. package/lib/language/docCommentParser.js +11 -5
  31. package/lib/language/errorStrategy.js +3 -3
  32. package/lib/language/genericAntlrParser.js +2 -0
  33. package/lib/model/csnUtils.js +6 -1
  34. package/lib/optionProcessor.js +5 -1
  35. package/lib/parsers/AstBuildingParser.js +151 -72
  36. package/lib/parsers/CdlGrammar.g4 +125 -83
  37. package/lib/parsers/Lexer.js +5 -3
  38. package/lib/parsers/index.js +1 -1
  39. package/lib/render/toCdl.js +6 -5
  40. package/lib/render/toHdbcds.js +1 -1
  41. package/lib/render/toSql.js +5 -3
  42. package/lib/render/utils/common.js +19 -6
  43. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  44. package/lib/transform/addTenantFields.js +2 -1
  45. package/lib/transform/db/flattening.js +18 -77
  46. package/lib/transform/db/groupByOrderBy.js +2 -2
  47. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  48. package/lib/transform/db/temporal.js +2 -1
  49. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  50. package/lib/transform/odata/createForeignKeys.js +4 -71
  51. package/lib/transform/odata/flattening.js +11 -1
  52. package/lib/transform/transformUtils.js +20 -85
  53. package/package.json +2 -1
  54. package/bin/cds_update_annotations.js +0 -180
@@ -82,7 +82,7 @@ const expWithFilter = [ 'from', 'expand', 'inline' ];
82
82
  // exception in case of an error, but push the corresponding error object to
83
83
  // that property (should be a vector).
84
84
  function resolve( model ) {
85
- // const { options } = model;
85
+ const { options } = model;
86
86
  // Get shared functionality and the message function:
87
87
  const {
88
88
  info, warning, error, message,
@@ -90,6 +90,7 @@ function resolve( model ) {
90
90
  const {
91
91
  resolvePath,
92
92
  resolveDefinitionName,
93
+ attachAndEmitValidNames,
93
94
  traverseExpr,
94
95
  effectiveType,
95
96
  getOrigin,
@@ -103,7 +104,7 @@ function resolve( model ) {
103
104
  } );
104
105
 
105
106
  const ignoreSpecifiedElements
106
- = isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
107
+ = isDeprecatedEnabled( options, 'ignoreSpecifiedQueryElements' );
107
108
 
108
109
  return doResolve();
109
110
 
@@ -127,7 +128,7 @@ function resolve( model ) {
127
128
  // Phase 4: resolve all artifacts:
128
129
  forEachDefinition( model, resolveRefs );
129
130
  forEachGeneric( model, 'vocabularies', resolveRefs );
130
- if (model.options.lspMode) {
131
+ if (options.lspMode) {
131
132
  for (const name in model.sources)
132
133
  resolveDefinitionName( model.sources[name].namespace );
133
134
  }
@@ -405,7 +406,7 @@ function resolve( model ) {
405
406
  const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
406
407
  const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
407
408
 
408
- if (model.options.lspMode && art.name && !art._main)
409
+ if (options.lspMode && art.name && !art._main)
409
410
  resolveDefinitionName( art );
410
411
 
411
412
  // Check KEY (TODO: make this an extra function)
@@ -551,9 +552,11 @@ function resolve( model ) {
551
552
  addForeignKeyNavigations( art );
552
553
  }
553
554
 
554
- resolveExpr( art.default, 'default', art );
555
+ resolveExprWithEnum( art.default, 'default', art );
556
+
555
557
  // TODO: distinguish not by $syntax (it is semantics), but whether in query
556
- resolveExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
558
+ const valueCtx = (art.$syntax === 'calc') ? 'calc' : 'column';
559
+ resolveExprWithEnum( art.value, valueCtx, art );
557
560
  if (art.type?.$inferred === 'cast')
558
561
  inferTypePropertiesFromCast( art );
559
562
  if (art.value) {
@@ -612,7 +615,7 @@ function resolve( model ) {
612
615
  // If specified type is `null`, type could not be resolved.
613
616
  else if (!compToAssoc && sType && sType !== iType &&
614
617
  // Special case for $recompilation: allow one level of type indirection. See #12113.
615
- (!model.options.$recompile || sType !== iType.type?._artifact)) {
618
+ (!options.$recompile || sType !== iType.type?._artifact)) {
616
619
  const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
617
620
  iType?.name && sType?.name && 'typeName' || // both types are named
618
621
  'type'; // unknown type names
@@ -1391,8 +1394,13 @@ function resolve( model ) {
1391
1394
 
1392
1395
  function resolveExprInAnnotations( art ) {
1393
1396
  for (const anno in art) {
1394
- if (anno.charAt(0) === '@')
1397
+ if (anno.charAt(0) === '@') {
1398
+ const { name } = art[anno];
1399
+ const annoDef = model.vocabularies?.[name.id];
1400
+ if (annoDef)
1401
+ setLink( name, '_artifact', annoDef );
1395
1402
  resolveAnnoExpr( art[anno], art );
1403
+ }
1396
1404
  }
1397
1405
  }
1398
1406
 
@@ -1400,7 +1408,7 @@ function resolve( model ) {
1400
1408
  if (expr.$tokenTexts) {
1401
1409
  if (!anno.kind)
1402
1410
  initAnnotationForExpression( anno, art );
1403
- resolveExpr( expr, 'annotation', anno );
1411
+ resolveExprWithEnum( expr, 'annotation', anno );
1404
1412
  }
1405
1413
  else if (expr.literal === 'array') {
1406
1414
  expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
@@ -1428,6 +1436,34 @@ function resolve( model ) {
1428
1436
  // Might be useful for future recursive types.
1429
1437
  }
1430
1438
 
1439
+ function resolveExprWithEnum( expr, exprCtx, art ) {
1440
+ traverseExpr( expr, exprCtx, art, resolveExprItem );
1441
+ const sym = expr?.sym;
1442
+ const assignment = art;
1443
+ // Check enum as top-level annotation value:
1444
+ if (art?.kind === '$annotation')
1445
+ art = art === expr && art.name._artifact;
1446
+ if (!art)
1447
+ return;
1448
+ const symbols = effectiveType( art )?.enum;
1449
+ if (!sym || !symbols)
1450
+ return;
1451
+ // TODO: or warning if enum symbol but non-enum type?
1452
+ if (symbols[sym.id]) {
1453
+ setLink( sym, '_artifact', symbols[sym.id] );
1454
+ }
1455
+ else {
1456
+ let type = art._effectiveType;
1457
+ // inferred enums can't be extended (yet): show underlying enum
1458
+ while (type.enum[$inferred])
1459
+ type = getOrigin( type );
1460
+ const err = message( 'ref-undefined-enum', [ sym.location, assignment ],
1461
+ { id: sym.id, type } );
1462
+ if (options.newParser)
1463
+ attachAndEmitValidNames( err, symbols );
1464
+ }
1465
+ }
1466
+
1431
1467
  function resolveExpr( expr, exprCtx, user ) {
1432
1468
  traverseExpr( expr, exprCtx, user, resolveExprItem );
1433
1469
  }
@@ -1172,6 +1172,7 @@ function fns( model ) {
1172
1172
  // TODO: if it becomes non-configurable, we can omit this warning
1173
1173
  let id = pathName( path );
1174
1174
  let head = path[0]._artifact || { _parent: art };
1175
+ // eslint-disable-next-line sonarjs/no-nested-assignment
1175
1176
  while ((head = head?._parent) && head.kind === 'builtin')
1176
1177
  id = `${ head.name.id }.${ id }`;
1177
1178
  const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
@@ -538,8 +538,8 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
538
538
  }
539
539
  else {
540
540
  const facetVal = facetArg.args[0].val;
541
- const isNaN = Number.isNaN(Number.parseInt(facetVal, 10));
542
- if (isNaN && options.isV4() && facetName === 'Scale' && facetVal !== 'variable') {
541
+ const isNan = Number.isNaN(Number.parseInt(facetVal, 10));
542
+ if (isNan && options.isV4() && facetName === 'Scale' && facetVal !== 'variable') {
543
543
  error('odata-anno-xpr-args', location, {
544
544
  anno,
545
545
  op: `${facetFuncName}(…)`,
@@ -548,7 +548,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
548
548
  '#': 'wrongval_meta_list',
549
549
  });
550
550
  }
551
- else if (isNaN && facetName !== 'Scale') {
551
+ else if (isNan && facetName !== 'Scale') {
552
552
  error('odata-anno-xpr-args', location, {
553
553
  anno, op: `${facetFuncName}(…)`, meta: 'number', '#': 'wrongval_meta',
554
554
  });
@@ -64,7 +64,7 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
64
64
  // Crawl over the csn and trigger the annotation translation for all kinds
65
65
  // of annotated things.
66
66
  // Note: only works for single service
67
- // Note: we assume that all objects ly flat in the service, i.e. objName always
67
+ // Note: we assume that all objects lie flat in the service, i.e. objName always
68
68
  // looks like <service name, can contain dots>.<id>
69
69
  forEachDefinition(reqDefs, (def, defName) => {
70
70
  if (defName === serviceName || defName.startsWith(`${serviceName}.`)) {
@@ -1345,6 +1345,10 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1345
1345
  // if DataFieldAbstract is expected and no explicit type is provided, automatically choose DataField
1346
1346
  if (dTypeName === 'UI.DataFieldAbstract')
1347
1347
  actualTypeName = 'UI.DataField';
1348
+ // if SemanticObjectMappingAbstract is expected and no explicit type
1349
+ // is provided, automatically choose SemanticObjectMappingType
1350
+ else if (dTypeName === 'Common.SemanticObjectMappingAbstract')
1351
+ actualTypeName = 'Common.SemanticObjectMappingType';
1348
1352
 
1349
1353
  else
1350
1354
  actualTypeName = dTypeName;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /*
4
- OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/master/vocabularies
4
+ OASIS: https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies
5
5
  Aggregation (published)
6
6
  Authorization (published)
7
7
  Capabilities (published)
@@ -12,7 +12,7 @@
12
12
  Temporal (published)
13
13
  Validation (published)
14
14
 
15
- SAP: https://github.com/SAP/odata-vocabularies/tree/master/vocabularies
15
+ SAP: https://github.com/SAP/odata-vocabularies/tree/main/vocabularies
16
16
  Analytics (published)
17
17
  CodeList (published)
18
18
  Common (published)
@@ -866,8 +866,9 @@ function createSchemaRef( serviceRoots, targetSchemaName ) {
866
866
  function path4( def, _path = def['@path'] ) {
867
867
  if (_path)
868
868
  return _path.replace(/^\//, '');
869
+ const last = def.name.split('.').at(-1); // > my.very.CatalogService --> CatalogService
869
870
  return ( // generate one from the service's name
870
- /[^.]+$/.exec(def.name)[0] // > my.very.CatalogService --> CatalogService
871
+ last
871
872
  .replace(/Service$/, '') // > CatalogService --> Catalog
872
873
  .replace(/([a-z0-9])([A-Z])/g, (_, c, C) => `${c}-${C.toLowerCase()}`) // > ODataFooBarX9 --> odata-foo-bar-x9
873
874
  .replace(/_/g, '-') // > foo_bar_baz --> foo-bar-baz
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.1.19
1
+ // Base class for generated parser, for redepage v0.2.1
2
2
 
3
3
  'use strict';
4
4
 
@@ -263,6 +263,8 @@ class BaseParser {
263
263
  if (this.tokenIdx === this.fixKeywordTokenIdx)
264
264
  return this.e();
265
265
  const la = this.tokens[this.tokenIdx];
266
+ // TODO: consider this like a failed condition? Will be relevant if we try
267
+ // different error recovery possibilities.
266
268
  if (this.keywords[la.keyword])
267
269
  this.reportReservedWord_();
268
270
  // with error recovery: use that (consider this having a good score)
@@ -314,7 +316,7 @@ class BaseParser {
314
316
  }
315
317
  // calling the condition might have side effects (precendence conditions have)
316
318
  // → call tracing “name” before
317
- const fail = !this[cond]( true, arg );
319
+ const fail = this[cond]( true, arg );
318
320
  if (this.constructor.tracingParser)
319
321
  this._traceSubPush( !fail );
320
322
  // The default case must not have actions. If written in grammar with action,
@@ -334,6 +336,10 @@ class BaseParser {
334
336
  this.fixKeywordTokenIdx = this.tokenIdx;
335
337
  this.conditionTokenIdx = this.tokenIdx;
336
338
  this.conditionStackLength = this.stack.length;
339
+ this.conditionName = cond;
340
+ // we also set the failure here, because the reporting might have a
341
+ // different context (consider immediate exit)
342
+ this.conditionFailure = fail;
337
343
  }
338
344
  return !fail || this.g( state ) && false;
339
345
  }
@@ -558,7 +564,7 @@ class BaseParser {
558
564
  if (!cond || lean && !this.leanConditions[cond])
559
565
  return false;
560
566
  if (!this.constructor.tracingParser)
561
- return !this[cond]( mode, cmd[4] );
567
+ return !!this[cond]( mode, cmd[4] );
562
568
  // TODO: let this[cond]( true ) return recovery badness in error case
563
569
  if (!lean) {
564
570
  const { traceName } = this[cond];
@@ -566,9 +572,9 @@ class BaseParser {
566
572
  // calling the condition might have side effects (precendence conditions have)
567
573
  // → call tracing “name” before
568
574
  }
569
- const fail = !this[cond]( mode, cmd[4] );
570
- this._traceSubPush( lean ? { true: 'C✔', false: 'C✖' }[!fail] : !fail );
571
- return fail;
575
+ const succeed = !this[cond]( mode, cmd[4] );
576
+ this._traceSubPush( lean ? { true: 'C✔', false: 'C✖' }[succeed] : succeed );
577
+ return !succeed;
572
578
  }
573
579
 
574
580
  _matchesInFollow( type, keyword, mode ) { // mode = E | R
@@ -658,7 +664,7 @@ class BaseParser {
658
664
  // ( <prefer, guard=fail> 'foo' | rule ) with
659
665
  // rule : 'foo' | Id ;
660
666
  // doing it already here would list `foo` as expected token
661
- _tokenSetInRule( expecting, val, cmd ) {
667
+ _tokenSetInRule( expecting, val, cmd, collectKeywordsAndIdOnly = false ) {
662
668
  const savedDynamic = this.dynamic_;
663
669
  const savedState = this.s;
664
670
  let enteredRules = 0;
@@ -669,27 +675,19 @@ class BaseParser {
669
675
  const dict = cmd;
670
676
  for (const prop in dict) {
671
677
  if (prop && Object.hasOwn( dict, prop ) && prop !== 'Id' &&
672
- !Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ') {
673
- // TODO: or call this.translateParserToken_ always?
674
- // it should then directly set the dictionary -> setTokenInSet_()
675
- if (lookahead) { // yes, independently from ckA()
676
- for (const p of this.translateParserToken_( prop, lookahead ))
677
- expecting[p] = val;
678
- }
679
- else {
680
- expecting[prop] = val;
681
- }
682
- }
678
+ !Object.hasOwn( expecting, prop ) && prop.charAt(0) !== ' ')
679
+ this.addTokenToSet_( expecting, prop, val, collectKeywordsAndIdOnly, lookahead );
683
680
  }
684
681
  cmd = dict[''];
685
682
  if (dict.Id) {
686
683
  // recursive call only if Id branch with non-error default branch
687
684
  if (cmd[0] === 'e') {
685
+ collectKeywordsAndIdOnly = true;
688
686
  cmd = dict.Id;
689
687
  }
690
688
  else { // Id branch never leads to rule exit:
691
689
  this._tracePush( [ '[' ] );
692
- this._tokenSetInRule( expecting, val, dict.Id );
690
+ this._tokenSetInRule( expecting, val, dict.Id, true );
693
691
  this._tracePush( [ ']' ] );
694
692
  }
695
693
  }
@@ -697,10 +695,10 @@ class BaseParser {
697
695
  this._traceSubPush( this.s );
698
696
  switch (cmd[0]) {
699
697
  case 'm': case 'mk':
700
- expecting[cmd[2]] ??= val;
698
+ this.addTokenToSet_( expecting, cmd[2], val, collectKeywordsAndIdOnly );
701
699
  break loop;
702
700
  case 'ci': case 'ciA': case 'mi': case 'miA':
703
- expecting['Id'] ??= val;
701
+ this.addTokenToSet_( expecting, 'Id', val, false );
704
702
  // TODO: should we do s/th special, such that a reserved word is a sync
705
703
  // token for Id<all>? Probably not, see also comment in
706
704
  // _findSyncToken()
@@ -731,8 +729,10 @@ class BaseParser {
731
729
  return inspectOuterRules;
732
730
  }
733
731
 
734
- translateParserToken_( token ) {
735
- return [ token ];
732
+ // Remark: when called for `Id` token, `collectKeywordsOnly` is `false`
733
+ addTokenToSet_( set, token, val, collectKeywordsOnly, _lookahead ) {
734
+ if (!collectKeywordsOnly || /^[_a-z]/.test( token ))
735
+ set[token] ??= val;
736
736
  }
737
737
 
738
738
  // Error reporting and recovery -----------------------------------------------
@@ -924,37 +924,37 @@ class BaseParser {
924
924
  // Predefined conditions with extra option names:
925
925
 
926
926
  hide_( mode ) {
927
- return mode !== 'M';
927
+ return mode === 'M';
928
928
  }
929
929
  precLeft_( _test, prec ) { // <prec=…>, <…,assoc=left>, <…,prefix=once>
930
930
  const parentPrec = this.stack.at( -1 ).prec;
931
931
  if (parentPrec != null && parentPrec >= prec)
932
- return false;
932
+ return true;
933
933
  this.prec_ = prec;
934
- return true;
934
+ return false;
935
935
  }
936
936
  precRight_( _test, prec ) { // <…,assoc=right>, <…,prefix>
937
937
  const parentPrec = this.stack.at( -1 ).prec;
938
938
  if (parentPrec != null && parentPrec >= prec)
939
- return false;
939
+ return true;
940
940
  this.prec_ = prec - 1;
941
- return true;
941
+ return false;
942
942
  }
943
943
  precNone_( _test, prec ) { // <…,assoc=none>, <…,postfix=once>
944
944
  const parentPrec = this.stack.at( -1 ).prec;
945
945
  if (parentPrec != null && parentPrec >= prec ||
946
946
  this.prec_ != null && this.prec_ <= prec)
947
- return false;
947
+ return true;
948
948
  this.prec_ = prec;
949
- return true;
949
+ return false;
950
950
  }
951
951
  precPost_( _test, prec ) { // <…,postfix>
952
952
  const parentPrec = this.stack.at( -1 ).prec;
953
953
  if (parentPrec != null && parentPrec >= prec ||
954
954
  this.prec_ != null && this.prec_ < prec)
955
- return false;
955
+ return true;
956
956
  this.prec_ = prec;
957
- return true;
957
+ return false;
958
958
  }
959
959
  }
960
960
  const members = BaseParser.prototype;