@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. 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,62 +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
- const source = token.source[1].data;
324
- let newLineCount = 0;
325
- let lastNewlineIndex = token.start;
326
- for (let i = token.start; i < token.stop; i++) {
327
- // Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
328
- // because ANTLR only uses LF for line break detection.
329
- if (source[i] === 10) { // ASCII code for '\n'
330
- newLineCount++;
331
- lastNewlineIndex = i;
332
- }
333
- }
334
- if (newLineCount === 0)
335
- // endCol calculation below requires at least one newLine.
336
- return this.tokenLocation(token, token, val);
337
-
338
- /** @type {CSN.Location} */
339
- const r = {
340
- file: this.filename,
341
- line: token.line,
342
- col: token.column + 1,
343
- endLine: token.line + newLineCount,
344
- endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
345
- };
346
- if (val !== undefined)
347
- return { location: r, val };
348
- return r;
389
+ const loc = this.tokenLocation( startToken, endToken );
390
+ return { location: loc, val };
349
391
  }
350
392
 
351
393
  function previousTokenAtLocation( location ) {
@@ -365,6 +407,20 @@ function combinedLocation( start, end ) {
365
407
  return locUtils.combinedLocation( start, end );
366
408
  }
367
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
+
368
424
  function surroundByParens( expr, open, close, asQuery = false ) {
369
425
  if (!expr)
370
426
  return expr;
@@ -406,7 +462,7 @@ function docComment( node ) {
406
462
  this.warning( 'syntax-duplicate-doc-comment', token, {},
407
463
  'Repeated doc comment - previous doc is replaced' );
408
464
  }
409
- node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
465
+ node.doc = this.valueWithTokenLocation( parseDocComment( token.text ), token );
410
466
  }
411
467
 
412
468
  // Classify token (identifier category) for implicit names,
@@ -507,7 +563,7 @@ function valuePathAst( ref ) {
507
563
  // If a '-' is directly before an unsigned number, consider it part of the number;
508
564
  // otherwise (including for '+'), represent it as extra unary prefix operator.
509
565
  function signedExpression( signToken, expr ) {
510
- const sign = this.tokenLocation( signToken, undefined, signToken.text );
566
+ const sign = this.valueWithTokenLocation( signToken.text, signToken );
511
567
  const nval =
512
568
  (signToken.text === '-' &&
513
569
  expr && // expr may be null if `-` rule can't be parsed
@@ -557,8 +613,16 @@ function numberLiteral( token, sign, text = token.text ) {
557
613
  function quotedLiteral( token, literal ) {
558
614
  /** @type {CSN.Location} */
559
615
  const location = this.tokenLocation( token );
560
- const pos = token.text.search( '\'' ) + 1; // pos of char after quote
561
- 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
+ }
562
626
 
563
627
  if (!literal)
564
628
  literal = token.text.slice( 0, pos - 1 ).toLowerCase();
@@ -588,6 +652,7 @@ function quotedLiteral( token, literal ) {
588
652
  };
589
653
 
590
654
  function atChar(i) {
655
+ // Is only used with single-line strings.
591
656
  return location.col + pos + i;
592
657
  }
593
658
  }
@@ -647,13 +712,13 @@ function addDef( parent, env, kind, name, annos, props, location ) {
647
712
  }
648
713
  }
649
714
  else if (name && name.id == null) {
650
- name.id = pathName(name.path ); // A.B.C -> 'A.B.C'
715
+ name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
651
716
  }
652
717
  const art = this.assignProps( { name }, annos, props, location );
653
718
  if (kind)
654
719
  art.kind = kind;
655
- if (!parent[env])
656
- 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 } );
657
722
  if (!art.name || art.name.id == null) {
658
723
  // no id was parsed, but with error recovery: no further error
659
724
  // TODO: add to parent[env]['']
@@ -710,6 +775,31 @@ function addItem( parent, env, kind, annos, props, location ) {
710
775
  return art;
711
776
  }
712
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
+
713
803
  /** Assign all non-empty (undefined, null, {}, []) properties in argument
714
804
  * `props` and argument `annos` as property `$annotations` to `target`
715
805
  * and return it. Hack: if argument `annos` is exactly `true`, return
@@ -730,7 +820,7 @@ function assignProps( target, annos = [], props = null, location = null) {
730
820
  for (const key in props) {
731
821
  let val = props[key];
732
822
  if (val instanceof antlr4.CommonToken)
733
- val = this.tokenLocation( val, undefined, true);
823
+ val = this.valueWithTokenLocation( true, val);
734
824
  // only copy properties which are not undefined, null, {} or []
735
825
  if (val != null &&
736
826
  (typeof val !== 'object' ||
@@ -744,15 +834,15 @@ function assignProps( target, annos = [], props = null, location = null) {
744
834
 
745
835
  // Create AST node for prefix operator `op` and arguments `args`
746
836
  function createPrefixOp( token, args ) {
747
- const op = this.tokenLocation( token, undefined, token.text.toLowerCase() );
837
+ const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
748
838
  return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
749
839
  }
750
840
 
751
841
  // Create AST node for binary operator `op` and arguments `args`
752
842
  function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifier' ) {
753
- const op = this.tokenLocation( opToken, undefined, opToken.text.toLowerCase() );
843
+ const op = this.valueWithTokenLocation( opToken.text.toLowerCase() , opToken);
754
844
  const extra = eToken
755
- ? this.tokenLocation( eToken, undefined, eToken.text.toLowerCase() )
845
+ ? this.valueWithTokenLocation( eToken.text.toLowerCase(), eToken )
756
846
  : undefined;
757
847
  if (!left.$parens &&
758
848
  (left.op && left.op.val) === (op && op.val) &&