@sap/cds-compiler 2.11.2 → 2.13.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -11,14 +11,18 @@ const { ATNState } = require('antlr4/atn/ATNState');
11
11
  const { dictAdd, dictAddArray } = require('../base/dictionaries');
12
12
  const locUtils = require('../base/location');
13
13
  const { parseDocComment } = require('./docCommentParser');
14
+ const { parseMultiLineStringLiteral } = require('./multiLineStringParser');
14
15
  const { functionsWithoutParens, specialFunctions } = require('../compiler/builtins');
15
16
 
17
+ const $location = Symbol.for('cds.$location');
16
18
 
17
19
  // Push message `msg` with location `loc` to array of errors:
18
20
  function _message( parser, severity, id, loc, ...args ) {
19
21
  const msg = parser.$messageFunctions[severity]; // set in antlrParser.js
20
- return msg( id,
21
- (loc instanceof antlr4.CommonToken) ? parser.tokenLocation(loc) : loc, ...args );
22
+ if (loc instanceof antlr4.CommonToken) {
23
+ loc = parser.tokenLocation(loc);
24
+ }
25
+ return msg( id, loc, ...args );
22
26
  }
23
27
 
24
28
  // Class which is to be used as grammar option with
@@ -32,6 +36,19 @@ function GenericAntlrParser( ...args ) {
32
36
  // ANTLR restriction: we cannot add parameters to the constructor.
33
37
  antlr4.Parser.call( this, ...args );
34
38
  this.buildParseTrees = false;
39
+
40
+ // Common properties.
41
+ // We set them here so that they are available in the prototype.
42
+ // This improved performance by 25% for certain scenario tests.
43
+ // Probably because there was no need to look up the prototype chain anymore.
44
+ this.$adaptExpectedToken = null;
45
+ this.$adaptExpectedExcludes = [ ];
46
+ this.$nextTokensToken = null;
47
+ this.$nextTokensContext = null;
48
+
49
+ this.prepareGenericKeywords();
50
+ this.options = {};
51
+
35
52
  return this;
36
53
  }
37
54
 
@@ -49,9 +66,11 @@ GenericAntlrParser.prototype = Object.assign(
49
66
  attachLocation,
50
67
  startLocation,
51
68
  tokenLocation,
52
- multiLineTokenLocation,
69
+ valueWithTokenLocation,
53
70
  previousTokenAtLocation,
54
71
  combinedLocation,
72
+ createDict,
73
+ setDictEndLocation,
55
74
  surroundByParens,
56
75
  unaryOpForParens,
57
76
  leftAssocBinaryOp,
@@ -67,6 +86,7 @@ GenericAntlrParser.prototype = Object.assign(
67
86
  docComment,
68
87
  addDef,
69
88
  addItem,
89
+ artifactForElementAnnotateOrExtend,
70
90
  assignProps,
71
91
  createPrefixOp,
72
92
  setOnce,
@@ -76,6 +96,7 @@ GenericAntlrParser.prototype = Object.assign(
76
96
  reportExpandInline,
77
97
  notSupportedYet,
78
98
  csnParseOnly,
99
+ disallowElementExtension,
79
100
  noAssignmentInSameLine,
80
101
  noSemicolonHere,
81
102
  setLocalToken,
@@ -84,6 +105,7 @@ GenericAntlrParser.prototype = Object.assign(
84
105
  isStraightBefore,
85
106
  meltKeywordToIdentifier,
86
107
  prepareGenericKeywords,
108
+ parseMultiLineStringLiteral,
87
109
  constructor: GenericAntlrParser, // keep this last
88
110
  }
89
111
  );
@@ -209,6 +231,22 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
209
231
  // // throw new antlr4.error.InputMismatchException(this);
210
232
  // }
211
233
 
234
+ /**
235
+ * For element extensions (`extend E:elem` syntax).
236
+ * If `elemName.path` is set, remove the last extension from `$outer` and
237
+ * emit an error that the extension is invalid.
238
+ *
239
+ * @param {object} elemName
240
+ * @param {object} outer
241
+ * @param {string} extensionVariant
242
+ */
243
+ function disallowElementExtension(elemName, outer, extensionVariant) {
244
+ if (elemName.path) {
245
+ this.message( 'syntax-invalid-extend', this.tokenLocation(this.getCurrentToken()), { 'kind': extensionVariant } );
246
+ outer.extensions.length = outer.extensions.length - 1; // remove last, i.e. new extension
247
+ }
248
+ }
249
+
212
250
  function noAssignmentInSameLine() {
213
251
  const t = this.getCurrentToken();
214
252
  if (t.text === '@' && t.line <= this._input.LT(-1).line) {
@@ -290,61 +328,66 @@ function startLocation( token = this._ctx.start ) {
290
328
  *
291
329
  * @param {object} token
292
330
  * @param {object} endToken
293
- * @param {any} val
331
+ * @return {CSN.Location}
294
332
  */
295
- function tokenLocation( token, endToken, val ) {
333
+ function tokenLocation( token, endToken = null ) {
296
334
  if (!token)
297
335
  return undefined;
298
336
  if (!endToken) // including null
299
337
  endToken = token;
338
+
300
339
  /** @type {CSN.Location} */
301
- const r = {
340
+ const loc = {
302
341
  file: this.filename,
303
342
  line: token.line,
304
343
  col: token.column + 1,
305
- // we only have single-line tokens, except for docComments
344
+ // Default for single line tokens
306
345
  endLine: endToken.line,
307
346
  endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)
308
- };
309
- if (val !== undefined)
310
- return { location: r, val };
311
- return r;
347
+ }
348
+
349
+ // This check is done for performance reason. No need to access a token's
350
+ // data if we know that it spans only one single line.
351
+ const isMultiLineToken = (
352
+ endToken.type === this.constructor.DocComment ||
353
+ endToken.type === this.constructor.String ||
354
+ endToken.type === this.constructor.UnterminatedLiteral
355
+ );
356
+ if (isMultiLineToken) {
357
+ // Count the number of newlines in the token.
358
+ const source = endToken.source[1].data;
359
+ let newLineCount = 0;
360
+ let lastNewlineIndex = endToken.start;
361
+ for (let i = endToken.start; i < endToken.stop; i++) {
362
+ // Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
363
+ // because ANTLR only uses LF for line break detection.
364
+ if (source[i] === 10) { // code point of '\n'
365
+ newLineCount++;
366
+ lastNewlineIndex = i;
367
+ }
368
+ }
369
+ if (newLineCount > 0) {
370
+ loc.endLine = endToken.line + newLineCount;
371
+ loc.endCol = endToken.stop - lastNewlineIndex + 1;
372
+ }
373
+ }
374
+
375
+ return loc;
312
376
  }
313
377
 
314
378
  /**
315
- * Return location of `token`. In contrast to `tokenLocation()`, this function
316
- * can handle multiline tokens.
379
+ * Return `val` with the location of `token`. If `endToken` is provided, use its end
380
+ * location as end location in the result.
381
+ *
382
+ * @param {object} startToken
383
+ * @param {object} endToken
384
+ * @param {any} val
317
385
  */
318
- function multiLineTokenLocation(token, val) {
319
- if (!token)
386
+ function valueWithTokenLocation( val, startToken, endToken = null ) {
387
+ if (!startToken)
320
388
  return undefined;
321
-
322
- // Count the number of newlines in the token.
323
- // TODO: I want to avoid a substring, that's why I don't use RegEx here
324
- const source = token.source[1].data;
325
- let newLineCount = 0;
326
- let lastNewlineIndex = token.start;
327
- for (let i = token.start; i < token.stop; i++) {
328
- if (source[i] === 10) { // ASCII code for '\n'
329
- newLineCount++;
330
- lastNewlineIndex = i;
331
- }
332
- }
333
- if (newLineCount === 0)
334
- // endCol calculation below requires at least one newLine.
335
- return this.tokenLocation(token, token, val);
336
-
337
- /** @type {CSN.Location} */
338
- const r = {
339
- file: this.filename,
340
- line: token.line,
341
- col: token.column + 1,
342
- endLine: token.line + newLineCount,
343
- endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
344
- };
345
- if (val !== undefined)
346
- return { location: r, val };
347
- return r;
389
+ const loc = this.tokenLocation( startToken, endToken );
390
+ return { location: loc, val };
348
391
  }
349
392
 
350
393
  function previousTokenAtLocation( location ) {
@@ -364,6 +407,20 @@ function combinedLocation( start, end ) {
364
407
  return locUtils.combinedLocation( start, end );
365
408
  }
366
409
 
410
+ function createDict( location = null ) {
411
+ const dict = Object.create(null);
412
+ dict[$location] = location || this.startLocation( this._input.LT(-1) );
413
+ return dict;
414
+ }
415
+
416
+ function setDictEndLocation( dict ) {
417
+ const stop = this._input.LT(-1);
418
+ Object.assign( dict[$location], {
419
+ endLine: stop.line,
420
+ endCol: stop.stop - stop.start + stop.column + 2,
421
+ } );
422
+ }
423
+
367
424
  function surroundByParens( expr, open, close, asQuery = false ) {
368
425
  if (!expr)
369
426
  return expr;
@@ -405,7 +462,7 @@ function docComment( node ) {
405
462
  this.warning( 'syntax-duplicate-doc-comment', token, {},
406
463
  'Repeated doc comment - previous doc is replaced' );
407
464
  }
408
- node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
465
+ node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
409
466
  }
410
467
 
411
468
  // Classify token (identifier category) for implicit names,
@@ -506,7 +563,7 @@ function valuePathAst( ref ) {
506
563
  // If a '-' is directly before an unsigned number, consider it part of the number;
507
564
  // otherwise (including for '+'), represent it as extra unary prefix operator.
508
565
  function signedExpression( signToken, expr ) {
509
- const sign = this.tokenLocation( signToken, undefined, signToken.text );
566
+ const sign = this.valueWithTokenLocation( signToken.text, signToken );
510
567
  const nval =
511
568
  (signToken.text === '-' &&
512
569
  expr && // expr may be null if `-` rule can't be parsed
@@ -556,8 +613,16 @@ function numberLiteral( token, sign, text = token.text ) {
556
613
  function quotedLiteral( token, literal ) {
557
614
  /** @type {CSN.Location} */
558
615
  const location = this.tokenLocation( token );
559
- const pos = token.text.search( '\'' ) + 1; // pos of char after quote
560
- const val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
616
+ let pos;
617
+ let val;
618
+
619
+ if (token.text.startsWith('`')) {
620
+ val = this.parseMultiLineStringLiteral(token);
621
+ literal = 'string';
622
+ } else {
623
+ pos = token.text.search( '\'' ) + 1; // pos of char after quote
624
+ val = token.text.slice( pos, -1 ).replace( /''/g, '\'' );
625
+ }
561
626
 
562
627
  if (!literal)
563
628
  literal = token.text.slice( 0, pos - 1 ).toLowerCase();
@@ -587,6 +652,7 @@ function quotedLiteral( token, literal ) {
587
652
  };
588
653
 
589
654
  function atChar(i) {
655
+ // Is only used with single-line strings.
590
656
  return location.col + pos + i;
591
657
  }
592
658
  }
@@ -646,13 +712,13 @@ function addDef( parent, env, kind, name, annos, props, location ) {
646
712
  }
647
713
  }
648
714
  else if (name && name.id == null) {
649
- name.id = pathName(name.path ); // A.B.C -> 'A.B.C'
715
+ name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
650
716
  }
651
717
  const art = this.assignProps( { name }, annos, props, location );
652
718
  if (kind)
653
719
  art.kind = kind;
654
- if (!parent[env])
655
- parent[env] = Object.create(null);
720
+ if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
721
+ parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
656
722
  if (!art.name || art.name.id == null) {
657
723
  // no id was parsed, but with error recovery: no further error
658
724
  // TODO: add to parent[env]['']
@@ -709,6 +775,31 @@ function addItem( parent, env, kind, annos, props, location ) {
709
775
  return art;
710
776
  }
711
777
 
778
+ /**
779
+ * For `annotate/extend E:elem.sub`, create the `elements` structure
780
+ * that can be used by the core compiler to annotate/extend elements.
781
+ *
782
+ * @param {string} kind Either `annotate` or `extend`
783
+ * @param {object} artifact Main artifact that shall have `elements`.
784
+ * @param {XSN.Path} elementPath Path as returned by `simplePath` token.
785
+ * @param {object[]} annos Existing annotations that shall be added to the _last_ path step.
786
+ * @param {XSN.Location} artifactLocation Start location of the `annotate` statement.
787
+ * @returns {object} Deepest element
788
+ */
789
+ function artifactForElementAnnotateOrExtend(kind, artifact, elementPath, annos, artifactLocation ) {
790
+ if (!Array.isArray(elementPath) || elementPath.broken || elementPath.length < 1)
791
+ return artifact;
792
+
793
+ for (const seg of elementPath.slice(0, -1)) {
794
+ artifact = this.addDef( artifact, 'elements', kind,
795
+ { path: [seg], location: seg.location }, null, {}, artifactLocation );
796
+ }
797
+ const last = elementPath[elementPath.length - 1];
798
+ artifact = this.addDef( artifact, 'elements', kind,
799
+ { path: [ last ], location: last.location }, annos, {}, artifactLocation );
800
+ return artifact;
801
+ }
802
+
712
803
  /** Assign all non-empty (undefined, null, {}, []) properties in argument
713
804
  * `props` and argument `annos` as property `$annotations` to `target`
714
805
  * and return it. Hack: if argument `annos` is exactly `true`, return
@@ -729,7 +820,7 @@ function assignProps( target, annos = [], props = null, location = null) {
729
820
  for (const key in props) {
730
821
  let val = props[key];
731
822
  if (val instanceof antlr4.CommonToken)
732
- val = this.tokenLocation( val, undefined, true);
823
+ val = this.valueWithTokenLocation( true, val);
733
824
  // only copy properties which are not undefined, null, {} or []
734
825
  if (val != null &&
735
826
  (typeof val !== 'object' ||
@@ -743,15 +834,15 @@ function assignProps( target, annos = [], props = null, location = null) {
743
834
 
744
835
  // Create AST node for prefix operator `op` and arguments `args`
745
836
  function createPrefixOp( token, args ) {
746
- const op = this.tokenLocation( token, undefined, token.text.toLowerCase() );
837
+ const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
747
838
  return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
748
839
  }
749
840
 
750
841
  // Create AST node for binary operator `op` and arguments `args`
751
842
  function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
752
- const op = this.tokenLocation( opToken, undefined, opToken.text.toLowerCase() );
843
+ const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
753
844
  const extra = eToken
754
- ? this.tokenLocation( eToken, undefined, eToken.text.toLowerCase() )
845
+ ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
755
846
  : undefined;
756
847
  if (!left.$parens &&
757
848
  (left.op && left.op.val) === (op && op.val) &&