@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -19,6 +19,8 @@ const {
19
19
  } = require('../compiler/builtins');
20
20
  const { pathName } = require('../compiler/utils');
21
21
  const { isBetaEnabled } = require('../base/model');
22
+ const { weakLocation } = require('../base/messages');
23
+ const { normalizeNewLine } = require('./textUtils');
22
24
 
23
25
  const $location = Symbol.for('cds.$location');
24
26
 
@@ -73,9 +75,11 @@ Object.assign(GenericAntlrParser.prototype, {
73
75
  info(...args) {
74
76
  return _message( this, 'info', ...args );
75
77
  },
78
+ isBetaEnabled,
76
79
  attachLocation,
77
80
  assignAnnotation,
78
81
  addAnnotation,
82
+ expressionAsAnnotationValue,
79
83
  checkExtensionDict,
80
84
  handleDuplicateExtension,
81
85
  startLocation,
@@ -86,6 +90,7 @@ Object.assign(GenericAntlrParser.prototype, {
86
90
  previousTokenAtLocation,
87
91
  combinedLocation,
88
92
  surroundByParens,
93
+ tokensToStringRepresentation,
89
94
  secureParens,
90
95
  unaryOpForParens,
91
96
  leftAssocBinaryOp,
@@ -96,6 +101,7 @@ Object.assign(GenericAntlrParser.prototype, {
96
101
  pushXprToken,
97
102
  argsExpression,
98
103
  valuePathAst,
104
+ fixNewKeywordPlacement,
99
105
  signedExpression,
100
106
  numberLiteral,
101
107
  quotedLiteral,
@@ -120,6 +126,7 @@ Object.assign(GenericAntlrParser.prototype, {
120
126
  associationInSelectItem,
121
127
  reportExpandInline,
122
128
  checkTypeFacet,
129
+ checkTypeArgs,
123
130
  csnParseOnly,
124
131
  noAssignmentInSameLine,
125
132
  noSemicolonHere,
@@ -361,15 +368,12 @@ function assignAnnotation( art, anno, prefix = '' ) {
361
368
  }
362
369
 
363
370
  function addAnnotation( art, prop, anno ) {
364
- // TODO: just overwrite after having reported the error
365
- dictAddArray( art, prop, anno, (n, location, a) => {
366
- // if we would make it a warning, we would still need to keep it an error
367
- // with '...'; otherwise parse.cdl would have to split annotate statements
368
- this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
369
- 'Duplicate assignment with $(ANNO)' );
370
- a.$errorReported = 'syntax-duplicate-anno';
371
- // do not report again later as anno-duplicate-xyz
372
- } );
371
+ const old = art[prop];
372
+ if (old) {
373
+ this.error( 'syntax-duplicate-anno', old.name.location, { anno: prop },
374
+ 'Assignment for $(ANNO) is overwritten by another one below' );
375
+ }
376
+ art[prop] = anno;
373
377
  }
374
378
 
375
379
  const extensionDicts = {
@@ -525,18 +529,24 @@ function fixMultiLineTokenEndLocation( token, location ) {
525
529
  }
526
530
 
527
531
  /**
528
- * Return `val` with the location of `token`. If `endToken` is provided, use its end
529
- * location as end location in the result.
532
+ * Return `val` with a location; if `val` and `endToken` are not provided, use the
533
+ * lower-cased token string of `startToken` as `val`. As location, use the
534
+ * location covered by `startToken` and `endToken`, or only `startToken` if no
535
+ * `endToken` is provided. The `startToken` defaults to the previous token.
530
536
  *
531
537
  * @param {object} startToken
532
538
  * @param {object} endToken
533
539
  * @param {any} val
534
540
  */
535
- function valueWithTokenLocation( val, startToken, endToken = null ) {
536
- if (!startToken)
537
- return undefined;
541
+ function valueWithTokenLocation( val = undefined, startToken = this._input.LT(-1),
542
+ endToken = undefined ) {
543
+ // if (!startToken)
544
+ // startToken = this._input.LT(-1);
538
545
  const loc = this.tokenLocation( startToken, endToken );
539
- return { location: loc, val };
546
+ return {
547
+ location: loc,
548
+ val: (endToken || val !== undefined) ? val : startToken.text.toLowerCase(),
549
+ };
540
550
  }
541
551
 
542
552
  function previousTokenAtLocation( location ) {
@@ -584,6 +594,23 @@ function surroundByParens( expr, open, close, asQuery = false ) {
584
594
  return (asQuery) ? { query: expr, location } : expr;
585
595
  }
586
596
 
597
+
598
+ function tokensToStringRepresentation( matchedRule ) {
599
+ const tokens = this._input.getTokens(
600
+ matchedRule.start.tokenIndex,
601
+ matchedRule.stop.tokenIndex + 1, null
602
+ ).filter(tok => tok.channel === antlr4.Token.DEFAULT_CHANNEL);
603
+ if (tokens.length === 0)
604
+ return '';
605
+
606
+ let result = tokens[0].text;
607
+ for (let i = 1; i < tokens.length; ++i) {
608
+ const str = normalizeNewLine(tokens[i].text);
609
+ result += (tokens[i].start > tokens[i - 1].stop + 1) ? ` ${ str }` : str;
610
+ }
611
+ return result;
612
+ }
613
+
587
614
  function unaryOpForParens( query, val ) {
588
615
  const parens = query?.$parens;
589
616
  if (!parens)
@@ -652,7 +679,7 @@ function fragileAlias( ast, safe = false ) {
652
679
  return ast;
653
680
  }
654
681
 
655
- // Return AST for identifier token `token`. Also check that identifer is not empty.
682
+ // Return AST for identifier token `token`. Also check that identifier is not empty.
656
683
  function identAst( token, category, noTokenTypeCheck = false ) {
657
684
  token.isIdentifier = category;
658
685
  let id = token.text;
@@ -696,8 +723,11 @@ function argsExpression( args, nary, location ) {
696
723
  location: undefined,
697
724
  } );
698
725
  }
726
+ // eslint-disable-next-line no-nested-ternary
727
+ const val = nary === '?:' ? nary
728
+ : (nary && nary !== '=' ? 'nary' : 'ixpr');
699
729
  const op = {
700
- val: (nary && nary !== '=' ? 'nary' : 'ixpr'), // there is no n-ary in rule conditionTerm
730
+ val, // there is no n-ary in rule conditionTerm
701
731
  location: this.startLocation(),
702
732
  };
703
733
  return this.attachLocation( { op, args, location: location && { ...location } } );
@@ -713,35 +743,115 @@ function pushXprToken( args ) {
713
743
  }
714
744
 
715
745
  function valuePathAst( ref ) {
716
- // TODO: XSN representation of functions is a bit strange - rework if methods
717
- // are introduced
746
+ // TODO: XSN representation of functions is a bit strange - rework
718
747
  const { path } = ref;
719
748
  if (!path || path.broken)
720
749
  return ref;
721
750
  if (path.length !== 1) {
722
751
  const item = path.find( i => i.args && i.$syntax !== ':' );
723
- if (!item)
752
+ if (!item) // also covers empty paths
724
753
  return ref;
725
- this.error( 'syntax-unsupported-method', item.location, {},
726
- 'Methods in expressions are not supported yet' );
727
- path.broken = true;
728
- path.length = 1;
729
754
  }
730
- const { args, id, location } = path[0];
731
- if (args
755
+ else if (path.length === 1) {
756
+ const { args, id, location } = path[0];
757
+ if (args
732
758
  ? path[0].$syntax === ':'
733
759
  : path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
734
- return ref;
760
+ return ref;
761
+
762
+ const implicit = this.previousTokenAtLocation( location );
763
+ if (implicit && implicit.isIdentifier)
764
+ implicit.isIdentifier = 'func';
765
+ const op = { location, val: 'call' };
766
+ return (args)
767
+ ? {
768
+ op, func: ref, location: ref.location, args,
769
+ }
770
+ : { op, func: ref, location: ref.location };
771
+ }
772
+
773
+ // method call ---------------------------
774
+
775
+ const args = [];
776
+ const pathRest = [ ...path ];
777
+ let pathHead = pathRest.shift();
735
778
 
736
- const implicit = this.previousTokenAtLocation( location );
737
- if (implicit && implicit.isIdentifier)
738
- implicit.isIdentifier = 'func';
739
- const op = { location, val: 'call' };
740
- return (args)
741
- ? {
742
- op, func: ref, location: ref.location, args,
779
+ if (pathHead.args) {
780
+ args.push({
781
+ op: { location: pathHead.location, val: 'call' },
782
+ func: { path: [ pathHead ] },
783
+ location: pathHead.location,
784
+ args: pathHead.args || [],
785
+ });
786
+ pathHead = pathRest.shift();
787
+ }
788
+ else {
789
+ const refPath = [];
790
+ while (pathHead && !pathHead.args) {
791
+ refPath.push(pathHead);
792
+ pathHead = pathRest.shift();
743
793
  }
744
- : { op, func: ref, location: ref.location };
794
+ args.push({ path: refPath, location: refPath[0].location });
795
+ }
796
+
797
+ if (pathHead?.args)
798
+ pathRest.unshift(pathHead);
799
+
800
+ for (const method of pathRest) {
801
+ args.push({
802
+ // TODO: Update parser to have proper location for `.`?
803
+ location: weakLocation(method.location),
804
+ val: '.',
805
+ literal: 'token',
806
+ });
807
+ const func = {
808
+ op: { location: method.location, val: 'call' },
809
+ func: { path: [ method ] },
810
+ location: method.location,
811
+ };
812
+ if (method.args)
813
+ func.args = method.args;
814
+ args.push(func);
815
+ }
816
+
817
+ return {
818
+ op: {
819
+ val: 'ixpr',
820
+ location: this.startLocation(),
821
+ },
822
+ args,
823
+ location: ref.location,
824
+ };
825
+ }
826
+
827
+
828
+ /**
829
+ * Adds the first argument of `args` ('new' keyword) to the second argument, if it's a method-ixpr.
830
+ *
831
+ * @todo Cleanup, remove.
832
+ * @param args
833
+ */
834
+ function fixNewKeywordPlacement( args ) {
835
+ // TODO: Currently, the parser creates an args-array with `new` and an `ixpr` for
836
+ // `new P().abc()`. That is, "new" is separate from the methods.
837
+ // This function tries to work around it, but its more of a hack.
838
+ if (args.length !== 2 || !args[1].args || args[1].op?.val !== 'ixpr')
839
+ return;
840
+ const ixpr = args[1];
841
+ ixpr.args.unshift(args[0]);
842
+ args.length = 0;
843
+ args.push(ixpr);
844
+ }
845
+
846
+ function expressionAsAnnotationValue( assignment, cond ) {
847
+ if (!cond.cond) // parse error
848
+ return;
849
+ Object.assign(assignment, cond.cond);
850
+ assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
851
+ if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
852
+ this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
853
+ 'Expressions in annotation values are not supported' );
854
+ }
745
855
  }
746
856
 
747
857
  // If a '-' is directly before an unsigned number, consider it part of the number;
@@ -992,7 +1102,7 @@ function aspectWithoutElements( art ) {
992
1102
  }
993
1103
  }
994
1104
 
995
- // must be in action directly after having parsed '{' or '(`
1105
+ // must be in action directly after having parsed '{', '(`, or a keyword before
996
1106
  function createDict() {
997
1107
  const dict = Object.create(null);
998
1108
  dict[$location] = this.startLocation( this._input.LT(-1) );
@@ -1174,4 +1284,14 @@ function checkTypeFacet( art, argIdent ) {
1174
1284
  return false;
1175
1285
  }
1176
1286
 
1287
+ function checkTypeArgs( art ) {
1288
+ const args = art.$typeArgs;
1289
+ // One or two arguments are interpreted as either length or precision/scale.
1290
+ if (args.length > 2) {
1291
+ const loc = args[2].location;
1292
+ this.error( 'syntax-unexpected-argument', loc, {}, 'Too many type arguments' );
1293
+ art.$typeArgs = undefined;
1294
+ }
1295
+ }
1296
+
1177
1297
  module.exports = GenericAntlrParser;