@sap/cds-compiler 3.3.2 → 3.4.2

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 (76) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/cdsc.js +3 -1
  3. package/doc/CHANGELOG_BETA.md +17 -0
  4. package/lib/api/main.js +147 -18
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/keywords.js +104 -0
  8. package/lib/base/message-registry.js +137 -68
  9. package/lib/base/messages.js +59 -48
  10. package/lib/base/model.js +1 -0
  11. package/lib/checks/actionsFunctions.js +1 -1
  12. package/lib/checks/cdsPersistence.js +1 -1
  13. package/lib/checks/checkForTypes.js +13 -8
  14. package/lib/checks/defaultValues.js +3 -1
  15. package/lib/checks/elements.js +1 -1
  16. package/lib/checks/parameters.js +4 -2
  17. package/lib/checks/queryNoDbArtifacts.js +1 -1
  18. package/lib/checks/sql-snippets.js +12 -10
  19. package/lib/checks/validator.js +14 -4
  20. package/lib/compiler/assert-consistency.js +8 -7
  21. package/lib/compiler/checks.js +30 -20
  22. package/lib/compiler/define.js +89 -25
  23. package/lib/compiler/extend.js +33 -28
  24. package/lib/compiler/finalize-parse-cdl.js +14 -9
  25. package/lib/compiler/populate.js +30 -8
  26. package/lib/compiler/propagator.js +23 -28
  27. package/lib/compiler/resolve.js +11 -5
  28. package/lib/compiler/shared.js +66 -48
  29. package/lib/compiler/tweak-assocs.js +2 -3
  30. package/lib/compiler/utils.js +11 -0
  31. package/lib/edm/annotations/genericTranslation.js +7 -4
  32. package/lib/edm/csn2edm.js +1 -1
  33. package/lib/gen/language.checksum +1 -1
  34. package/lib/gen/language.interp +1 -1
  35. package/lib/gen/languageParser.js +3565 -3544
  36. package/lib/json/csnVersion.js +13 -13
  37. package/lib/json/from-csn.js +140 -158
  38. package/lib/json/to-csn.js +23 -5
  39. package/lib/language/.eslintrc.json +4 -0
  40. package/lib/language/antlrParser.js +7 -10
  41. package/lib/language/docCommentParser.js +1 -2
  42. package/lib/language/errorStrategy.js +54 -27
  43. package/lib/language/genericAntlrParser.js +115 -84
  44. package/lib/language/language.g4 +29 -25
  45. package/lib/language/multiLineStringParser.js +75 -63
  46. package/lib/main.js +1 -0
  47. package/lib/model/csnRefs.js +4 -3
  48. package/lib/model/csnUtils.js +39 -7
  49. package/lib/model/sortViews.js +7 -3
  50. package/lib/modelCompare/compare.js +49 -15
  51. package/lib/modelCompare/filter.js +83 -0
  52. package/lib/optionProcessor.js +5 -1
  53. package/lib/render/manageConstraints.js +9 -5
  54. package/lib/render/toCdl.js +120 -62
  55. package/lib/render/toHdbcds.js +1 -1
  56. package/lib/render/toSql.js +6 -2
  57. package/lib/render/utils/common.js +7 -0
  58. package/lib/sql-identifier.js +7 -0
  59. package/lib/transform/db/assertUnique.js +27 -38
  60. package/lib/transform/db/expansion.js +11 -4
  61. package/lib/transform/db/temporal.js +3 -1
  62. package/lib/transform/db/transformExists.js +7 -1
  63. package/lib/transform/db/views.js +42 -13
  64. package/lib/transform/draft/db.js +2 -2
  65. package/lib/transform/forOdataNew.js +7 -3
  66. package/lib/transform/forRelationalDB.js +12 -6
  67. package/lib/transform/localized.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +2 -1
  69. package/lib/transform/parseExpr.js +245 -0
  70. package/lib/transform/transformUtilsNew.js +23 -14
  71. package/lib/transform/translateAssocsToJoins.js +12 -12
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  73. package/lib/utils/term.js +5 -5
  74. package/package.json +2 -2
  75. package/share/messages/message-explanations.json +1 -1
  76. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
@@ -186,7 +186,7 @@ const propertyOrder = (function orderPositions() {
186
186
  const typeProperties = [
187
187
  'target', 'elements', 'enum', 'items',
188
188
  'cardinality', // for association publishing in views
189
- 'type', 'length', 'precision', 'scale', 'srid', 'localized', // TODO: notNull?
189
+ 'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
190
190
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
191
191
  ];
192
192
 
@@ -882,7 +882,24 @@ function definition( art, _csn, _node, prop ) {
882
882
  return c;
883
883
  }
884
884
 
885
- // create $origin specification for `includes` of `art`
885
+ /**
886
+ * Create $origin specification for query/projection.
887
+ */
888
+ function queryOrigin( xsn ) {
889
+ const source = xsn._from[0]._origin;
890
+ let $origin;
891
+ if (xsn.includes)
892
+ // includesOrigin() does originRef() on the first include.
893
+ // Use it to behave the same as entity includes.
894
+ $origin = includesOrigin( [ { _artifact: source }, ...xsn.includes ], xsn );
895
+ else
896
+ $origin = originRef( source );
897
+ return $origin;
898
+ }
899
+
900
+ /**
901
+ * Create $origin specification for `includes` of `art`.
902
+ */
886
903
  function includesOrigin( includes, art ) {
887
904
  const $origin = originRef( includes[0]._artifact );
888
905
  if (includes.length === 1)
@@ -922,7 +939,7 @@ function addOrigin( csn, xsn, node ) {
922
939
  }
923
940
  if (xsn._from) {
924
941
  const source = xsn._from[0]._origin;
925
- csn.$origin = originRef( source );
942
+ csn.$origin = queryOrigin( xsn );
926
943
  if (source.params && !xsn.params)
927
944
  csn.params = null; // discontinue `params` inheritance
928
945
  if (source.actions && !xsn.actions)
@@ -1059,10 +1076,11 @@ function getOrigin( art ) {
1059
1076
  return (_origin.kind === 'builtin') ? undefined : _origin; // not $dollarVariable
1060
1077
  if (hasExplicitProp( art.type, 'cast' ))
1061
1078
  return art.type._artifact;
1062
- if (art.includes)
1063
- return art.includes[0]._artifact;
1064
1079
  if (art._from)
1065
1080
  return art._from[0]._origin;
1081
+ // must come after _from, since queries can have includes as well.
1082
+ if (art.includes)
1083
+ return art.includes[0]._artifact;
1066
1084
  return undefined;
1067
1085
  }
1068
1086
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "root": true,
3
+ "extends": "../../.eslintrc-ydkjsi.json"
4
+ }
@@ -18,22 +18,17 @@ const Lexer = require('../gen/languageLexer').default;
18
18
 
19
19
  // Error listener used for ANTLR4-generated parser
20
20
  class ErrorListener extends antlr4.error.ErrorListener {
21
- constructor(...args) {
22
- super(...args);
23
- }
24
-
25
21
  // method which is called by generated parser with --trace-parser[-amg]:
22
+ // eslint-disable-next-line class-methods-use-this
26
23
  syntaxError( recognizer, offendingSymbol, line, column, msg, e ) {
27
24
  if (!(e instanceof CompileMessage)) // not already reported
25
+ // Ignore warning, because only relevant for --trace-parser
26
+ // eslint-disable-next-line cds-compiler/message-call-format
28
27
  recognizer.error( null, offendingSymbol, {}, msg );
29
28
  }
30
29
  }
31
30
 
32
31
  class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
33
- constructor(...args) {
34
- super(...args);
35
- }
36
-
37
32
  LT( k ) {
38
33
  const t = super.LT(k);
39
34
  if (!t || !t.type)
@@ -112,7 +107,9 @@ const rules = {
112
107
  expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
113
108
  };
114
109
 
115
- function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions = null, rule = 'cdl' ) {
110
+ function parse( source, filename = '<undefined>.cds',
111
+ options = {}, messageFunctions = null,
112
+ rule = 'cdl' ) {
116
113
  const lexer = new Lexer( new antlr4.InputStream(source) );
117
114
  const tokenStream = new RewriteTypeTokenStream(lexer);
118
115
  /** @type {object} */
@@ -168,7 +165,7 @@ function parse( source, filename = '<undefined>.cds', options = {}, messageFunct
168
165
  for (const token of tokenStream.tokens) {
169
166
  if (token.type === parser.constructor.DocComment && !token.isUsed) {
170
167
  messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
171
- "Ignoring doc-comment as it does not belong to any artifact");
168
+ 'Ignoring doc comment as it does not belong to any artifact');
172
169
  }
173
170
  }
174
171
  }
@@ -93,9 +93,8 @@ function stripCommentIndentation(lines, ignoreFirstLine) {
93
93
 
94
94
  let count = 0;
95
95
  const length = Math.min(min, line.length);
96
- while (count < length && isWhitespaceCharacterNoNewline(line[count])) {
96
+ while (count < length && isWhitespaceCharacterNoNewline(line[count]))
97
97
  count++;
98
- }
99
98
  return Math.min(min, count);
100
99
  }, Number.MAX_SAFE_INTEGER);
101
100
 
@@ -29,10 +29,13 @@
29
29
  'use strict';
30
30
 
31
31
  const antlr4 = require('antlr4');
32
- const antlr4_LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
32
+ // eslint-disable-next-line camelcase
33
+ const Antlr4LL1Analyzer = require('antlr4/src/antlr4/LL1Analyzer');
33
34
  const { DefaultErrorStrategy } = require('antlr4/src/antlr4/error/ErrorStrategy');
34
35
  const { InputMismatchException } = require('antlr4/src/antlr4/error/Errors');
35
- const { predictionContextFromRuleContext: predictionContext } = require('antlr4/src/antlr4/PredictionContext');
36
+ const {
37
+ predictionContextFromRuleContext: predictionContext,
38
+ } = require('antlr4/src/antlr4/PredictionContext');
36
39
  const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
37
40
  const { IntervalSet, Interval } = require('antlr4/src/antlr4/IntervalSet');
38
41
 
@@ -53,8 +56,7 @@ function match( ttype ) {
53
56
  if (token.type === identType || !keywordRegexp.test( token.text ))
54
57
  return antlr4.Parser.prototype.match.call( this, ttype );
55
58
 
56
- this.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
57
- '$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
59
+ this.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
58
60
  this._errHandler.reportMatch(this);
59
61
  this.consume();
60
62
  return token;
@@ -107,7 +109,8 @@ function sync( recognizer ) {
107
109
  const s = recognizer._interp.atn.states[recognizer.state];
108
110
  // try cheaper subset first; might get lucky. seems to shave a wee bit off
109
111
  const nextTokens = recognizer.atn.nextTokens(s);
110
- // console.log('SYNC:', recognizer._ctx._sync, s.stateType, token.text, intervalSetToArray( recognizer, nextTokens ))
112
+ // console.log('SYNC:', recognizer._ctx._sync, s.stateType, token.text,
113
+ // intervalSetToArray( recognizer, nextTokens ))
111
114
 
112
115
  if (nextTokens.contains(token.type)) { // we are sure the token matches
113
116
  if (token.text === '}' && recognizer.$nextTokensToken !== token &&
@@ -124,7 +127,8 @@ function sync( recognizer ) {
124
127
  // when exiting a (innermost) rule, remember the state to make
125
128
  // getExpectedTokensForMessage() calculate the full "expected set"
126
129
  if (recognizer.$nextTokensToken !== token) {
127
- // console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken && recognizer.$nextTokensToken.type)
130
+ // console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken &&
131
+ // recognizer.$nextTokensToken.type)
128
132
  recognizer.$nextTokensToken = token;
129
133
  recognizer.$nextTokensState = recognizer.state;
130
134
  recognizer.$nextTokensContext = recognizer._ctx;
@@ -142,8 +146,7 @@ function sync( recognizer ) {
142
146
  // interpreter to see behind EPSILON.
143
147
  const identType = recognizer.constructor.Identifier;
144
148
  if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
145
- recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
146
- '$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
149
+ recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
147
150
  token.type = identType; // make next ANTLR decision assume identifier
148
151
  return;
149
152
  }
@@ -187,7 +190,8 @@ function sync( recognizer ) {
187
190
 
188
191
  // Report `NoViableAltException e` signalled by parser `recognizer`
189
192
  function reportNoViableAlternative( recognizer, e ) {
190
- // console.log('NOV:',this.getTokenErrorDisplay(e.startToken), this.getTokenErrorDisplay(e.offendingToken))
193
+ // console.log('NOV:',this.getTokenErrorDisplay(e.startToken),
194
+ // this.getTokenErrorDisplay(e.offendingToken))
191
195
  if (e.startToken === e.offendingToken) { // mismatch at LA(1)
192
196
  this.reportInputMismatch( recognizer, e );
193
197
  }
@@ -262,6 +266,7 @@ function reportIgnoredWith( recognizer, t ) {
262
266
  const expecting = this.getExpectedTokensForMessage( recognizer, t );
263
267
  const m = recognizer.warning( 'syntax-ignored-with', t,
264
268
  { offending: "';'", expecting },
269
+ // eslint-disable-next-line max-len
265
270
  'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous WITH' );
266
271
  m.expectedTokens = expecting;
267
272
  }
@@ -279,7 +284,8 @@ function consumeUntil( recognizer, set ) {
279
284
  }
280
285
  else if (set.contains(SEMI)) { // do not check for RBRACE here!
281
286
  this._super.consumeUntil.call( this, recognizer, set );
282
- // console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(), recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
287
+ // console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
288
+ // recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set ));
283
289
  }
284
290
  else {
285
291
  // DO NOT modify input param `set`, as the set might be cached in the ATN
@@ -300,7 +306,8 @@ function consumeUntil( recognizer, set ) {
300
306
  recognizer.consume();
301
307
  this.reportMatch(recognizer); // we know current token is correct
302
308
  }
303
- // console.log('CONSUMED:',s,this.getTokenDisplay( recognizer.getCurrentToken(), recognizer ),recognizer.getCurrentToken().line);
309
+ // console.log('CONSUMED:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
310
+ // recognizer ),recognizer.getCurrentToken().line);
304
311
  // throw new Error('Sync')
305
312
  }
306
313
  }
@@ -321,8 +328,7 @@ function recoverInline( recognizer ) {
321
328
  if (!keywordRegexp.test( token.text ))
322
329
  return this._super.recoverInline.call( this, recognizer );
323
330
 
324
- recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
325
- '$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
331
+ recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } );
326
332
  this.reportMatch(recognizer); // we know current token is correct
327
333
  recognizer.consume();
328
334
  return token;
@@ -362,7 +368,8 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
362
368
  }
363
369
  }
364
370
  // The parser method excludeExpected() additionally removes some tokens from the message:
365
- if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) {
371
+ if (recognizer.$adaptExpectedToken &&
372
+ recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) {
366
373
  const excludes = (excludesForNextToken && Array.isArray(recognizer.$adaptExpectedExcludes[0]))
367
374
  ? recognizer.$adaptExpectedExcludes[0]
368
375
  : recognizer.$adaptExpectedExcludes;
@@ -381,13 +388,32 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
381
388
  const token1sort = {
382
389
  // 0: Identifier, Number, ...
383
390
  // 1: separators:
384
- ',': 1, '.': 1, ':': 1, ';': 1,
391
+ ',': 1,
392
+ '.': 1,
393
+ ':': 1,
394
+ ';': 1,
385
395
  // 2: parentheses:
386
- '(': 2, ')': 2, '[': 2, ']':2, '{': 2, '}': 2,
396
+ '(': 2,
397
+ ')': 2,
398
+ '[': 2,
399
+ ']': 2,
400
+ '{': 2,
401
+ '}': 2,
387
402
  // 3: special:
388
- '!': 3, '#': 3, '$': 3, '?': 3, '@': 3,
403
+ '!': 3,
404
+ '#': 3,
405
+ $: 3,
406
+ '?': 3,
407
+ '@': 3,
389
408
  // 4: operators:
390
- '*': 4, '+': 4, '-': 4, '/': 4, '<': 4, '=': 4, '>': 4, '|': 4,
409
+ '*': 4,
410
+ '+': 4,
411
+ '-': 4,
412
+ '/': 4,
413
+ '<': 4,
414
+ '=': 4,
415
+ '>': 4,
416
+ '|': 4,
391
417
  // 8: KEYWORD
392
418
  // 9: <EOF>
393
419
  };
@@ -425,7 +451,7 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
425
451
  if (recognizer.state < 0)
426
452
  return [];
427
453
  if (recognizer.state >= atn.states.length) {
428
- throw ( `Invalid state number ${ recognizer.state } for ${
454
+ throw new Error( `Invalid state number ${ recognizer.state } for ${
429
455
  this.getTokenErrorDisplay( offendingToken ) }`);
430
456
  }
431
457
 
@@ -435,10 +461,10 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
435
461
  if (!identType || !beforeUnreserved || beforeUnreserved + 2 > identType)
436
462
  return intervalSetToArray( recognizer, this._super.getExpectedTokens.call( this, recognizer ) );
437
463
 
438
- const ll1 = new antlr4_LL1Analyzer(atn);
464
+ const ll1 = new Antlr4LL1Analyzer(atn);
439
465
  const expected = new IntervalSet();
440
- const orig_addInterval = expected.addInterval;
441
- const orig_addSet = expected.addSet;
466
+ const origAddInterval = expected.addInterval;
467
+ const origAddSet = expected.addSet;
442
468
  expected.addInterval = addInterval;
443
469
  expected.addSet = addSet;
444
470
  const lookBusy = new antlr4.Utils.Set();
@@ -466,23 +492,24 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
466
492
  predictionContext( atn, recognizer._ctx ),
467
493
  expected, lookBusy, calledRules, true, true );
468
494
  }
469
- // console.log(state, recognizer.$nextTokensState, expected.toString(recognizer.literalNames, recognizer.symbolicNames));
495
+ // console.log(state, recognizer.$nextTokensState,
496
+ // expected.toString(recognizer.literalNames, recognizer.symbolicNames));
470
497
  return intervalSetToArray( recognizer, expected );
471
498
 
472
499
  function addSet(other) {
473
500
  if (!other.contains( hideAltsType ))
474
- orig_addSet.call( this, other );
501
+ origAddSet.call( this, other );
475
502
  }
476
503
 
477
504
  // Add an interval `v` to the IntervalSet `this`. If `v` contains the token
478
505
  // type `Identifier`, do not add non-reserved keywords in `v`.
479
506
  function addInterval(v) {
480
507
  if (v.stop <= identType) {
481
- orig_addInterval.call(this, v);
508
+ origAddInterval.call(this, v);
482
509
  }
483
510
  else if (v.start >= identType) {
484
511
  if (v.stop === identType + 1 || !recognizer.tokenRewrite) {
485
- orig_addInterval.call(this, v);
512
+ origAddInterval.call(this, v);
486
513
  }
487
514
  else {
488
515
  for (let j = v.start; j < v.stop; j++)
@@ -497,7 +524,7 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
497
524
  }
498
525
 
499
526
  function addRange( interval, start, stop ) {
500
- orig_addInterval.call( interval, new Interval( start, stop || start + 1 ) );
527
+ origAddInterval.call( interval, new Interval( start, stop || start + 1 ) );
501
528
  }
502
529
  }
503
530