@sap/cds-compiler 4.4.4 → 4.6.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 (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/bin/cdsc.js +18 -11
  3. package/bin/cdsv2m.js +7 -5
  4. package/doc/CHANGELOG_BETA.md +22 -0
  5. package/lib/api/main.js +306 -144
  6. package/lib/api/options.js +18 -6
  7. package/lib/api/validate.js +1 -1
  8. package/lib/base/message-registry.js +45 -10
  9. package/lib/base/messages.js +33 -16
  10. package/lib/base/model.js +4 -0
  11. package/lib/base/optionProcessorHelper.js +45 -176
  12. package/lib/checks/annotationsOData.js +49 -0
  13. package/lib/checks/elements.js +32 -34
  14. package/lib/checks/enricher.js +39 -3
  15. package/lib/checks/validator.js +8 -7
  16. package/lib/compiler/assert-consistency.js +40 -17
  17. package/lib/compiler/builtins.js +30 -53
  18. package/lib/compiler/checks.js +46 -14
  19. package/lib/compiler/cycle-detector.js +1 -4
  20. package/lib/compiler/define.js +35 -10
  21. package/lib/compiler/extend.js +21 -7
  22. package/lib/compiler/generate.js +3 -0
  23. package/lib/compiler/populate.js +5 -1
  24. package/lib/compiler/propagator.js +46 -9
  25. package/lib/compiler/resolve.js +94 -35
  26. package/lib/compiler/shared.js +60 -33
  27. package/lib/compiler/tweak-assocs.js +188 -92
  28. package/lib/compiler/utils.js +11 -1
  29. package/lib/edm/annotations/edmJson.js +41 -66
  30. package/lib/edm/annotations/genericTranslation.js +27 -9
  31. package/lib/edm/annotations/preprocessAnnotations.js +2 -3
  32. package/lib/edm/csn2edm.js +28 -11
  33. package/lib/edm/edmInboundChecks.js +58 -15
  34. package/lib/edm/edmPreprocessor.js +12 -16
  35. package/lib/edm/edmUtils.js +5 -2
  36. package/lib/gen/Dictionary.json +10 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +15 -2
  39. package/lib/gen/language.tokens +1 -0
  40. package/lib/gen/languageParser.js +6557 -5618
  41. package/lib/json/from-csn.js +4 -5
  42. package/lib/json/to-csn.js +29 -4
  43. package/lib/language/antlrParser.js +19 -1
  44. package/lib/language/errorStrategy.js +28 -7
  45. package/lib/language/genericAntlrParser.js +118 -24
  46. package/lib/language/textUtils.js +16 -0
  47. package/lib/main.d.ts +28 -3
  48. package/lib/main.js +3 -0
  49. package/lib/model/csnRefs.js +4 -1
  50. package/lib/model/csnUtils.js +20 -14
  51. package/lib/model/revealInternalProperties.js +5 -2
  52. package/lib/optionProcessor.js +23 -22
  53. package/lib/render/manageConstraints.js +13 -29
  54. package/lib/render/toCdl.js +47 -26
  55. package/lib/render/toHdbcds.js +63 -42
  56. package/lib/render/toRename.js +6 -10
  57. package/lib/render/toSql.js +71 -117
  58. package/lib/render/utils/common.js +41 -6
  59. package/lib/transform/.eslintrc.json +9 -1
  60. package/lib/transform/addTenantFields.js +228 -0
  61. package/lib/transform/db/applyTransformations.js +57 -4
  62. package/lib/transform/db/assertUnique.js +4 -4
  63. package/lib/transform/db/backlinks.js +13 -1
  64. package/lib/transform/db/cdsPersistence.js +1 -1
  65. package/lib/transform/db/expansion.js +24 -3
  66. package/lib/transform/db/flattening.js +70 -71
  67. package/lib/transform/db/killAnnotations.js +37 -0
  68. package/lib/transform/db/rewriteCalculatedElements.js +46 -6
  69. package/lib/transform/db/temporal.js +1 -1
  70. package/lib/transform/draft/db.js +2 -16
  71. package/lib/transform/draft/odata.js +3 -3
  72. package/lib/transform/effective/associations.js +3 -5
  73. package/lib/transform/effective/main.js +6 -9
  74. package/lib/transform/forOdata.js +26 -55
  75. package/lib/transform/forRelationalDB.js +38 -18
  76. package/lib/transform/odata/toFinalBaseType.js +3 -3
  77. package/lib/transform/odata/typesExposure.js +14 -5
  78. package/lib/transform/transformUtils.js +47 -34
  79. package/lib/transform/translateAssocsToJoins.js +45 -11
  80. package/lib/transform/universalCsn/coreComputed.js +1 -1
  81. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  82. package/package.json +7 -6
@@ -376,7 +376,7 @@ const schema = compileSchema( {
376
376
  type: object,
377
377
  optional: [ 'src', 'min', 'max' ],
378
378
  inKind: [ 'element', 'type', 'mixin' ],
379
- onlyWith: [ 'target', 'targetAspect', 'id' ], // also in 'ref[]'
379
+ onlyWith: [ 'target', 'targetAspect', 'where' ], // also in 'ref[]'
380
380
  },
381
381
  items: {
382
382
  type: object,
@@ -1325,14 +1325,13 @@ function stringValOrNull( val, spec ) {
1325
1325
 
1326
1326
  function scalenum( val, spec ) {
1327
1327
  if ([ 'floating', 'variable' ].includes(val))
1328
- return { val, literal: 'string', location: location() };
1328
+ return { val, literal: 'string', location: location() }; // XSN TODO: remove `literal`
1329
1329
  return natnum(val, spec );
1330
1330
  }
1331
1331
 
1332
1332
  function natnum( val, spec ) {
1333
1333
  if (typeof val === 'number' && val >= 0 && Number.isSafeInteger( val ))
1334
- // XSN TODO: do not require literal
1335
- return { val, literal: 'number', location: location() };
1334
+ return { val, location: location() };
1336
1335
  const loc = location(true);
1337
1336
  error( 'syntax-expecting-unsigned-int', loc,
1338
1337
  { '#': spec.msgVariant || 'csn', prop: spec.msgProp, op: '*' } );
@@ -1342,7 +1341,7 @@ function natnum( val, spec ) {
1342
1341
  // Use with spec.msgVariant !
1343
1342
  function natnumOrStar( val, spec ) {
1344
1343
  return (val === '*')
1345
- ? { val, literal: 'string', location: location() }
1344
+ ? { val, location: location() }
1346
1345
  : natnum( val, spec );
1347
1346
  }
1348
1347
 
@@ -14,7 +14,7 @@
14
14
  const { locationString } = require('../base/messages');
15
15
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
16
16
  const { pathName } = require('../compiler/utils');
17
- const { CompilerAssertion } = require('../base/error');
17
+ const { CompilerAssertion, ModelError } = require('../base/error');
18
18
 
19
19
  const compilerVersion = require('../../package.json').version;
20
20
  const creator = `CDS Compiler v${ compilerVersion }`;
@@ -93,6 +93,7 @@ const transformers = {
93
93
  cardinality, // also in pathItem: after 'id', before 'where'
94
94
  targetAspect,
95
95
  target,
96
+ $filtered: value, // assoc+filter
96
97
  foreignKeys,
97
98
  enum: enumDict,
98
99
  items,
@@ -205,6 +206,10 @@ const typeProperties = [
205
206
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
206
207
  '$typeArgs', // for unresolved type arguments, e.g. through parseCql
207
208
  ];
209
+ // Properties which cause a `cast` property to be rendered
210
+ const castProperties = [
211
+ 'target', 'enum', 'items', 'type',
212
+ ];
208
213
 
209
214
  const csnDictionaries = [
210
215
  'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
@@ -305,8 +310,14 @@ function csnDictionary( csn, sort, cloneOptions = false ) {
305
310
  ? proto
306
311
  : (proto) ? Object.prototype : null;
307
312
  const r = Object.create( dictProto );
308
- for (const n of (sort) ? Object.keys(csn).sort() : Object.keys(csn))
309
- r[n] = sortCsn( csn[n], cloneOptions );
313
+ for (const n of (sort) ? Object.keys(csn).sort() : Object.keys(csn)) {
314
+ // CSN does not allow any dictionary that are not objects.
315
+ // The compiler handles it, but a pre-transformed OData CSN won't trigger recompilation.
316
+ if (csn[n] && typeof csn[n] === 'object')
317
+ r[n] = sortCsn(csn[n], cloneOptions);
318
+ else
319
+ throw new ModelError(`Found non-object dictionary entry: "${ n }" of type "${ typeof csn[n] }"`);
320
+ }
310
321
 
311
322
  return r;
312
323
  }
@@ -1176,6 +1187,18 @@ function args( node ) {
1176
1187
  }
1177
1188
 
1178
1189
  function anno( node ) {
1190
+ if (!node)
1191
+ return true; // `@aBool` short for `@aBool: true`
1192
+ if (universalCsn && node.$inferred) {
1193
+ // TODO: return undefined for all values of node.$inferred (except 'NULL')?
1194
+ if (node.$inferred === 'prop' || node.$inferred === '$generated' || // via propagator.js
1195
+ node.$inferred === 'parent-origin')
1196
+ return undefined;
1197
+ else if (node.$inferred === 'NULL')
1198
+ return null;
1199
+ }
1200
+ if (node.$inferred && gensrcFlavor)
1201
+ return undefined;
1179
1202
  if (node.$tokenTexts) // expressions in annotation values
1180
1203
  return Object.assign({ '=': node.$tokenTexts }, expression( node ));
1181
1204
  return value(node);
@@ -1476,7 +1499,9 @@ function addElementAsColumn( elem, cols ) {
1476
1499
  // elements of sub queries (in expr) are hidden (not set via Object.assign):
1477
1500
  if (!expr.cast && expr.elements)
1478
1501
  setHidden( col, 'elements', expr.elements );
1479
- if (elem.type && !elem.type.$inferred || elem.target && !elem.target.$inferred)
1502
+ // CDL-style cast with explicit type properties
1503
+ if (castProperties.findIndex(prop => (elem[prop] &&
1504
+ !elem[prop].$inferred && !elem[prop][$inferred])) > -1)
1480
1505
  cast( col, elem );
1481
1506
  }
1482
1507
  finally {
@@ -78,9 +78,11 @@ class RewriteTypeTokenStream extends antlr4.CommonTokenStream {
78
78
  function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
79
79
  ts.DOTbeforeBRACE = Parser.DOTbeforeBRACE;
80
80
  ts.BRACE = tokenTypeOf( recognizer, "'{'" );
81
+ ts.BRACE_CLOSE = tokenTypeOf( recognizer, "'}'" );
81
82
  ts.DOT = tokenTypeOf( recognizer, "'.'" );
82
83
  ts.ASTERISK = tokenTypeOf( recognizer, "'*'" );
83
84
  ts.AT = tokenTypeOf( recognizer, "'@'" );
85
+ ts.SEMICOLON = tokenTypeOf( recognizer, "';'" );
84
86
  ts.NEW = Parser.NEW;
85
87
  ts.Identifier = Parser.Identifier;
86
88
  ts.PAREN = tokenTypeOf( recognizer, "'('" );
@@ -90,7 +92,22 @@ function initTokenRewrite( recognizer, ts ) { // ts = tokenStream
90
92
  if (ts.DOT && ts.DOTbeforeBRACE)
91
93
  recognizer.tokenRewrite[ts.DOTbeforeBRACE - Parser.Identifier] = ts.DOT;
92
94
  }
93
- // console.log( ts.DOTbeforeBRACE, ts.BRACE, ts.DOT, recognizer.tokenRewrite );
95
+ }
96
+
97
+ function initCodeCompletionTokenArrays( parser ) {
98
+ // Set of top-level keywords used for code completion after token '}'
99
+ // belonging to a top-level definition
100
+ const startRuleIndex = parser.ruleNames.indexOf('start');
101
+ const startState = parser.atn.ruleToStartState[startRuleIndex].stateNumber;
102
+ const tokens = parser.atn.nextTokens(parser.atn.states[startState]);
103
+ tokens.removeOne(parser.symbolicNames.indexOf('NAMESPACE'));
104
+ tokens.removeOne(parser.symbolicNames.indexOf('HideAlternatives'));
105
+
106
+ parser.topLevelKeywords = [];
107
+ for (const interval of tokens.intervals) {
108
+ for (let i = interval.start; i < interval.stop; i++)
109
+ parser.topLevelKeywords.push(i);
110
+ }
94
111
  }
95
112
 
96
113
  function tokenTypeOf( recognizer, literalName ) {
@@ -123,6 +140,7 @@ function parse( source, filename = '<undefined>.cds',
123
140
  parser.$messageFunctions = messageFunctions;
124
141
 
125
142
  initTokenRewrite( parser, tokenStream );
143
+ initCodeCompletionTokenArrays(parser);
126
144
  // comment the following 2 lines if you want to output the parser errors directly:
127
145
  parser.messageErrorListener = errorListener;
128
146
  parser._errHandler = new errorStrategy.KeywordErrorStrategy();
@@ -447,16 +447,33 @@ function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
447
447
  for (let j = v.start; j < v.stop; j++) {
448
448
  // a generic keyword as such does not appear in messages, only its replacements,
449
449
  // which are function name and argument position dependent:
450
- if (j === pc.GenericExpr)
450
+ if (j === pc.GenericExpr) {
451
451
  names.push( ...recognizer.$genericKeywords.expr );
452
- else if (j === pc.GenericSeparator)
452
+ }
453
+ else if (j === pc.GenericSeparator) {
453
454
  names.push( ...recognizer.$genericKeywords.separator );
454
- else if (j === pc.GenericIntro)
455
+ }
456
+ else if (j === pc.GenericIntro) {
455
457
  names.push( ...recognizer.$genericKeywords.introMsg );
458
+ }
459
+ else if (j === pc.SemicolonTopLevel) {
460
+ // We only insert a semikolon (i.e. make it optional) after a closing brace.
461
+ // If the previous token is not `}`, don't propose these keywords, as ';' is required.
462
+ if (recognizer._input.LA(-1) === recognizer._input.BRACE_CLOSE) {
463
+ const name = recognizer.topLevelKeywords.map(i => expected
464
+ .elementName(recognizer.literalNames, recognizer.symbolicNames, i));
465
+ names.push(...name);
466
+ if (recognizer._ctx.outer?.kind !== 'source') {
467
+ if (names.includes('<EOF>'))
468
+ names.splice(names.indexOf('<EOF>'), 1);
469
+ }
470
+ }
471
+ }
456
472
  // other expected tokens usually appear in messages, except the helper tokens
457
473
  // which are used to solve ambiguities via the parser method setLocalToken():
458
- else if (j !== pc.HelperToken1 && j !== pc.HelperToken2)
474
+ else if (j !== pc.HelperToken1 && j !== pc.HelperToken2) {
459
475
  names.push( expected.elementName(recognizer.literalNames, recognizer.symbolicNames, j ) );
476
+ }
460
477
  }
461
478
  }
462
479
  // The parser method excludeExpected() additionally removes some tokens from the message:
@@ -524,10 +541,14 @@ function getTokenDisplay( token, recognizer ) {
524
541
  if (!token)
525
542
  return '<EOF>';
526
543
  const t = token.type;
527
- if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON )
544
+ if (t === antlr4.Token.EOF || t === antlr4.Token.EPSILON ) {
528
545
  return '<EOF>';
529
- else if (token.text === '.') // also for DOTbeforeBRACE
530
- return "'.'";
546
+ }
547
+ else if (t === recognizer.constructor.DOTbeforeBRACE) {
548
+ if (recognizer.getTokenStream().LT(2).text === '{')
549
+ return "'.{'";
550
+ return "'.*'";
551
+ }
531
552
  return recognizer.literalNames[t] || recognizer.symbolicNames[t];
532
553
  }
533
554
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  const antlr4 = require('antlr4');
10
10
  const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
11
+ const { DEFAULT: CommonTokenFactory } = require('antlr4/src/antlr4/CommonTokenFactory');
11
12
  const { dictAdd, dictAddArray } = require('../base/dictionaries');
12
13
  const locUtils = require('../base/location');
13
14
  const { parseDocComment } = require('./docCommentParser');
@@ -23,7 +24,7 @@ const {
23
24
  } = require('../compiler/classes');
24
25
  const { isBetaEnabled } = require('../base/model');
25
26
  const { weakLocation } = require('../base/location');
26
- const { normalizeNewLine } = require('./textUtils');
27
+ const { normalizeNewLine, normalizeNumberString } = require('./textUtils');
27
28
 
28
29
  const $location = Symbol.for('cds.$location');
29
30
 
@@ -107,6 +108,8 @@ Object.assign(GenericAntlrParser.prototype, {
107
108
  fixNewKeywordPlacement,
108
109
  signedExpression,
109
110
  numberLiteral,
111
+ unsignedIntegerLiteral,
112
+ assignAnnotationValue,
110
113
  quotedLiteral,
111
114
  pathName,
112
115
  docComment,
@@ -117,13 +120,14 @@ Object.assign(GenericAntlrParser.prototype, {
117
120
  createDict,
118
121
  createArray,
119
122
  finalizeDictOrArray,
120
- createPrefixOp,
123
+ insertSemicolon,
121
124
  setMaxCardinality,
122
125
  setNullability,
123
126
  reportDuplicateClause,
124
127
  reportUnexpectedExtension,
125
128
  reportUnexpectedSpace,
126
129
  pushIdent,
130
+ pushItem,
127
131
  handleComposition,
128
132
  associationInSelectItem,
129
133
  reportExpandInline,
@@ -911,10 +915,6 @@ function expressionAsAnnotationValue( assignment, cond ) {
911
915
  return;
912
916
  Object.assign(assignment, cond.cond);
913
917
  assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
914
- if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
915
- this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
916
- 'Expressions in annotation values are not supported' );
917
- }
918
918
  }
919
919
 
920
920
  // If a '-' is directly before an unsigned number, consider it part of the number;
@@ -941,12 +941,27 @@ function signedExpression( args, expr ) {
941
941
  }
942
942
  }
943
943
 
944
- // Return AST for number token `token` with optional token `sign`. Represent
945
- // the number as number in property `val` if the number can safely be
946
- // represented as an integer. Otherwise, represent the number by a string, the
947
- // token lexeme.
948
- function numberLiteral( token, sign, text = token.text ) {
944
+ /**
945
+ * Return number literal (XSN) for number token `token` with optional token `sign`.
946
+ * Represent the number as a JS number in property `val` if the number can safely be
947
+ * represented as one. Represent the number by a string, the token lexeme, if the
948
+ * stringified version of the number does not match the token lexeme.
949
+ *
950
+ * TODO: Always use text !== `${ num }`
951
+ */
952
+ function numberLiteral( sign, text = this._input.LT(-1).text ) {
953
+ const token = this._input.LT(-1);
949
954
  let location = this.tokenLocation( token );
955
+ const nextToken = this._input.LT(1);
956
+ if (token.type === this.constructor.Number &&
957
+ token.stop + 1 === nextToken.start &&
958
+ (nextToken.type === this.constructor.Identifier ||
959
+ nextToken.type < this.constructor.Identifier && /^[a-z]+$/i.test( nextToken.text ))) {
960
+ // TODO: Make it an error in v5
961
+ this.warning('syntax-expecting-space', nextToken, {},
962
+ 'Expecting a space between a number and a keyword/identifier');
963
+ }
964
+
950
965
  if (sign) {
951
966
  const { endLine, endCol } = location;
952
967
  location = this.startLocation( sign );
@@ -957,17 +972,65 @@ function numberLiteral( token, sign, text = token.text ) {
957
972
  }
958
973
 
959
974
  const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
975
+ const normalized = normalizeNumberString(text);
976
+ if (normalized !== `${ num }` && normalized !== `${ sign.text }${ num }`)
977
+ return { literal: 'number', val: normalized, location };
978
+ return { literal: 'number', val: num, location };
979
+ }
980
+
981
+ /**
982
+ * Given `token`, return a number literal (XSN). If the number is not an unsigned integer
983
+ * or it can't be represented in JS, emit an error.
984
+ */
985
+ function unsignedIntegerLiteral() {
986
+ const token = this._input.LT(-1);
987
+ const location = this.tokenLocation( token );
988
+ const text = token.text || '0';
989
+ const num = Number.parseFloat( text ); // not Number.parseInt() !
960
990
  if (!Number.isSafeInteger(num)) {
961
- if (sign == null) {
962
- this.error( 'syntax-expecting-unsigned-int', token,
963
- { '#': !text.match(/^\d*$/) ? 'normal' : 'unsafe' } );
964
- }
991
+ this.error( 'syntax-expecting-unsigned-int', token,
992
+ { '#': !text.match(/^\d*$/) ? 'normal' : 'unsafe' } );
993
+ }
994
+ else if (text.match(/^\d+[.]\d+$/)) {
995
+ // More restrictive check: 10.0 emits a message, because we don't expect
996
+ // any decimal places.
997
+ const dotLoc = { ...location };
998
+ dotLoc.col += text.indexOf('.');
999
+ dotLoc.endCol = dotLoc.col + 1;
1000
+ this.info( 'syntax-ignoring-decimal', dotLoc );
1001
+ }
1002
+ return { literal: 'number', val: num, location };
1003
+ }
965
1004
 
966
- else if (text !== `${ num }`) {
967
- return { literal: 'number', val: text, location };
1005
+ // Make the annotation `anno` have `value` as value. This function is basically
1006
+ // just `Object.assign`, but we really try to represent the provided CDL number as
1007
+ // JSON number. We give a warning if this is not possible or leads to a precision
1008
+ // loss.
1009
+ function assignAnnotationValue( anno, value ) {
1010
+ const { val } = value;
1011
+ if (value.literal === 'number' && typeof val !== 'number') {
1012
+ // a number in CDL, but stored as string in `val` - due to rounding or scientific notation
1013
+ let num = Number.parseFloat( val || '0' );
1014
+ const inf = !Number.isFinite( num );
1015
+ if (inf)
1016
+ num = val;
1017
+ if (inf || relevantDigits( val ) !== relevantDigits( num.toString() )) {
1018
+ this.warning( 'syntax-invalid-anno-number', value.location,
1019
+ { '#': (inf ? 'infinite' : 'rounded' ), rawvalue: val, value: num },
1020
+ {
1021
+ std: 'Annotation number $(RAWVALUE) is put as $(VALUE) into the CSN',
1022
+ rounded: 'Annotation number $(RAWVALUE) is rounded to $(VALUE)',
1023
+ // eslint-disable-next-line max-len
1024
+ infinite: 'Annotation value $(RAWVALUE) is infinite as number and put as string into the CSN',
1025
+ } );
968
1026
  }
1027
+ value.val = num;
969
1028
  }
970
- return { literal: 'number', val: num, location };
1029
+ Object.assign( anno, value );
1030
+ }
1031
+
1032
+ function relevantDigits( val ) {
1033
+ return val.replace( /0*(e.+)?$/i, '' ).replace( /\./, '' ).replace( /^[-+0]+/, '' );
971
1034
  }
972
1035
 
973
1036
  // Create AST node for quoted literals like string and e.g. date'2017-02-22'.
@@ -1036,6 +1099,16 @@ function pushIdent( path, ident, prefix ) {
1036
1099
  }
1037
1100
  }
1038
1101
 
1102
+ function pushItem( array, val ) {
1103
+ if (!array)
1104
+ return;
1105
+
1106
+ if (val != null)
1107
+ array.push(val);
1108
+ else
1109
+ array.broken = true;
1110
+ }
1111
+
1039
1112
  // For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
1040
1113
  function reportUnexpectedSpace( prefix = this._input.LT(-1),
1041
1114
  location = this.tokenLocation( this._input.LT(1) ) ) {
@@ -1173,14 +1246,35 @@ function finalizeDictOrArray( dict ) {
1173
1246
  loc.endCol = stop.stop - stop.start + stop.column + 2;
1174
1247
  }
1175
1248
 
1176
- function createSource() {
1177
- return new XsnSource();
1249
+ function insertSemicolon() {
1250
+ const currentToken = this._input.tokens[this._input.index];
1251
+ const requireSemicolon = this.topLevelKeywords.includes(currentToken.type);
1252
+
1253
+ if (requireSemicolon) {
1254
+ this.noAssignmentInSameLine();
1255
+ const prev = this._input.LT(-1);
1256
+ const t = CommonTokenFactory.create(
1257
+ currentToken.source,
1258
+ this.literalNames.indexOf( "';'" ),
1259
+ '', antlr4.Token.DEFAULT_CHANNEL,
1260
+ prev.stop, prev.stop,
1261
+ prev.line, prev.column
1262
+ );
1263
+
1264
+ t.tokenIndex = prev.tokenIndex + 1;
1265
+
1266
+ this._input.tokens.splice(t.tokenIndex, 0, t);
1267
+
1268
+ // Update tokenIndex: There could have been comments between two non-hidden tokens.
1269
+ for (let tokenIndex = t.tokenIndex + 1; tokenIndex < this._input.tokens.length; tokenIndex++)
1270
+ this._input.tokens[tokenIndex].tokenIndex += 1;
1271
+
1272
+ this._input.index = t.tokenIndex;
1273
+ }
1178
1274
  }
1179
1275
 
1180
- // Create AST node for prefix operator `op` and arguments `args`
1181
- function createPrefixOp( token, args ) {
1182
- const op = this.valueWithTokenLocation( token.text.toLowerCase(), token );
1183
- return { op, args, location: this.combinedLocation( op, args[args.length - 1] ) };
1276
+ function createSource() {
1277
+ return new XsnSource();
1184
1278
  }
1185
1279
 
1186
1280
  // Create AST node for binary operator `op` and arguments `args`
@@ -49,9 +49,25 @@ function normalizeNewLine( str ) {
49
49
  return str.replace(new RegExp(cdlNewLineRegEx, 'ug'), '\n');
50
50
  }
51
51
 
52
+ /**
53
+ * Normalizes the given number (as a string):
54
+ *
55
+ * - removes leading zeroes (`0`) to avoid accidental octal-conversion
56
+ *
57
+ * @param {string} str
58
+ * @return {string}
59
+ */
60
+ function normalizeNumberString( str ) {
61
+ const num = str.replace(/^([+-]?)0+(\d)/, '$1$2');
62
+ if (!num.includes('.'))
63
+ return num;
64
+ return num.replace(/([.]\d)0+$/, '$1');
65
+ }
66
+
52
67
  module.exports = {
53
68
  isWhitespaceOrNewLineOnly,
54
69
  isWhitespaceCharacterNoNewline,
55
70
  cdlNewLineRegEx,
56
71
  normalizeNewLine,
72
+ normalizeNumberString,
57
73
  };
package/lib/main.d.ts CHANGED
@@ -27,9 +27,9 @@ declare namespace compiler {
27
27
  messages?: object[]
28
28
  /**
29
29
  * Dictionary of message-ids and their reclassified severity. This option
30
- * can be used to increase the severity of messages. The compiler will
31
- * ignore decreased severities as this may lead to issues during
32
- * compilation otherwise.
30
+ * can be used to increase the severity of messages. The compiler may
31
+ * ignore decreased severities of error messages as this may lead to issues
32
+ * during compilation otherwise.
33
33
  */
34
34
  severities?: { [messageId: string]: MessageSeverity}
35
35
  /**
@@ -1023,6 +1023,31 @@ declare namespace compiler {
1023
1023
  function migration(csn: CSN, options: SqlOptions, beforeImage: CSN): SqlMigrationResult;
1024
1024
  }
1025
1025
 
1026
+ /**
1027
+ * Renders the given CSN into EDM in the JSON format _and_ XML format.
1028
+ * That is, it is a combination of `to.edm()` and `to.edmx()`.
1029
+ * Requires `options.service` to be set.
1030
+ *
1031
+ * Not to be confused with `for.odata()`, which returns an OData transformed CSN.
1032
+ *
1033
+ * @returns An object `'<protocol>': object` where the value is `'<serviceName>': object` entry
1034
+ * which consists of `{edmx: string, edm?: object}`.
1035
+ * @since v4.6.0
1036
+ */
1037
+ function odata(csn: CSN, options?: ODataOptions): Record<string, object>;
1038
+ namespace odata {
1039
+ /**
1040
+ * Renders the given CSN into EDM in JSON format _and_ XML format for each service.
1041
+ * That is, it is a combination of `to.edm.all()` and `to.edmx.all()`.
1042
+ * If `options.serviceNames` is not set, all services will be rendered.
1043
+ *
1044
+ * @returns A map of `'<protocol>': object` where each entry is `'<serviceName>': object` entries where
1045
+ * each entry consists of `{edmx: string, edm?: object}`.
1046
+ * @since v4.6.0
1047
+ */
1048
+ function all(csn: CSN, options: ODataOptions): Record<string, object>;
1049
+ }
1050
+
1026
1051
  /**
1027
1052
  * Renders the given CSN into EDM (JSON format). Requires `options.service` to be set.
1028
1053
  *
package/lib/main.js CHANGED
@@ -158,6 +158,9 @@ module.exports = {
158
158
  edmx: Object.assign((...args) => snapi.edmx(...args), {
159
159
  all: (...args) => snapi.edmx.all(...args)
160
160
  }),
161
+ odata: Object.assign((...args) => snapi.odata2(...args), {
162
+ all: (...args) => snapi.odata2.all(...args)
163
+ }),
161
164
  },
162
165
  // Convenience for hdbtabledata calculation in @sap/cds
163
166
  getArtifactCdsPersistenceName: (...args) => csnUtils.getArtifactDatabaseNameOf(...args),
@@ -613,7 +613,7 @@ function csnRefs( csn, universalReady ) {
613
613
  return resolvePath( path, alias._select || alias._ref, null,
614
614
  'alias', ncache.$queryNumber );
615
615
  }
616
- const mixin = ncache._select.mixin && ncache._select.mixin[head];
616
+ const mixin = ncache._select.mixin?.[head];
617
617
  if (mixin && {}.hasOwnProperty.call( ncache._select.mixin, head )) {
618
618
  setCache( mixin, '_parent', qcache._select );
619
619
  return resolvePath( path, mixin, null, 'mixin', ncache.$queryNumber );
@@ -818,6 +818,9 @@ function csnRefs( csn, universalReady ) {
818
818
  as = `$_column_${ colIndex + 1 }`;
819
819
  setCache( col, '$as', as );
820
820
  let type = parentElementOrQueryCache;
821
+ if (col.cast)
822
+ traverseType( col.cast, col, 'column', colIndex, initNode );
823
+
821
824
  while (type.items)
822
825
  type = type.items;
823
826
  const elem = setCache( col, '_element', type.elements[as] );
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { csnRefs, implicitAs } = require('../model/csnRefs');
3
+ const { csnRefs, implicitAs, pathId } = require('./csnRefs');
4
4
  const { applyTransformations, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary } = require('../transform/db/applyTransformations');
5
- const { isBuiltinType } = require('../compiler/builtins.js');
5
+ const { isBuiltinType, isMagicVariable, isAnnotationExpression } = require('../compiler/builtins.js');
6
6
  const {
7
7
  sortCsn,
8
8
  cloneCsnDictionary: _cloneCsnDictionary,
@@ -1088,18 +1088,21 @@ function getLastPartOfRef( ref ) {
1088
1088
  * @param {object} toNode
1089
1089
  * @param {boolean} [overwrite]
1090
1090
  * @param {object} excludes
1091
+ * @param {array} annoNames (copy only these annotations or all if undefined)
1092
+ * @returns {array} copiedAnnoNames
1091
1093
  */
1092
- function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {} ) {
1094
+ function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {}, annoNames = undefined ) {
1093
1095
  // Ignore if no toNode (in case of errors)
1094
1096
  if (!toNode)
1095
1097
  return;
1096
1098
 
1097
- const annotations = Object.keys(fromNode).filter(key => key.startsWith('@'));
1099
+ if (annoNames == null)
1100
+ annoNames = Object.keys(fromNode).filter(key => key.startsWith('@'));
1098
1101
 
1099
- for (const anno of annotations) {
1102
+ annoNames.forEach((anno) => {
1100
1103
  if ((toNode[anno] === undefined || overwrite) && !excludes[anno])
1101
- toNode[anno] = fromNode[anno];
1102
- }
1104
+ toNode[anno] = cloneAnnotationValue(fromNode[anno]);
1105
+ });
1103
1106
  }
1104
1107
 
1105
1108
 
@@ -1118,13 +1121,9 @@ function copyAnnotationsAndDoc( fromNode, toNode, overwrite = false ) {
1118
1121
  if (!toNode)
1119
1122
  return;
1120
1123
 
1121
- const annotations = Object.keys(fromNode)
1122
- .filter(key => key.startsWith('@') || key === 'doc');
1123
-
1124
- for (const anno of annotations) {
1125
- if (toNode[anno] === undefined || overwrite)
1126
- toNode[anno] = fromNode[anno];
1127
- }
1124
+ copyAnnotations(fromNode, toNode, overwrite);
1125
+ if (toNode.doc === undefined || overwrite)
1126
+ toNode.doc = fromNode.doc;
1128
1127
  }
1129
1128
 
1130
1129
  /**
@@ -1449,6 +1448,10 @@ function isAssociationOperand( arg, path, inspectRef ) {
1449
1448
  return art && art.target !== undefined;
1450
1449
  }
1451
1450
 
1451
+ function pathName( ref ) {
1452
+ return ref ? ref.map( pathId ).join( '.' ) : '';
1453
+ }
1454
+
1452
1455
  module.exports = {
1453
1456
  getUtils,
1454
1457
  cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
@@ -1456,6 +1459,8 @@ module.exports = {
1456
1459
  cloneCsnDictionary,
1457
1460
  cloneAnnotationValue,
1458
1461
  isBuiltinType,
1462
+ isMagicVariable,
1463
+ isAnnotationExpression,
1459
1464
  applyAnnotationsFromExtensions,
1460
1465
  forEachGeneric,
1461
1466
  forEachDefinition,
@@ -1496,4 +1501,5 @@ module.exports = {
1496
1501
  cardinality2str,
1497
1502
  isAssociationOperand,
1498
1503
  isDollarSelfOrProjectionOperand,
1504
+ pathName,
1499
1505
  };
@@ -321,8 +321,11 @@ function artifactIdentifier( node, parent ) {
321
321
  Object.defineProperty( node, '__unique_id__', { value: uniqueId-- } );
322
322
  const outerNum = node.$effectiveSeqNo || node.__unique_id__;
323
323
  let outer = outerNum != null ? `##${ outerNum }` : '';
324
- if (node._outer) { // anon aspect in targetAspect | items
325
- outer = (node._outer.targetAspect === node) ? `/target${ outer }` : `/items${ outer }`;
324
+ if (node._outer) { // anon aspect in targetAspect | items | $calcDepElement
325
+ outer = (node.kind === '$annotation')
326
+ // eslint-disable-next-line prefer-template
327
+ ? `/${ quoted( '@' + node.name.id ) }`
328
+ : `/${ node.kind || 'items' }${ outer }`;
326
329
  node = node._outer;
327
330
  }
328
331
  if (node === parent)