@sap/cds-compiler 5.5.2 → 5.7.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 (57) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/bin/cdsse.js +4 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +170 -143
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +4 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +8 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +142 -103
  27. package/lib/gen/CdlParser.js +2240 -2201
  28. package/lib/gen/Dictionary.json +185 -6
  29. package/lib/json/from-csn.js +2 -0
  30. package/lib/json/to-csn.js +13 -4
  31. package/lib/language/docCommentParser.js +11 -5
  32. package/lib/language/errorStrategy.js +3 -3
  33. package/lib/language/genericAntlrParser.js +2 -0
  34. package/lib/model/csnUtils.js +6 -1
  35. package/lib/optionProcessor.js +5 -1
  36. package/lib/parsers/AstBuildingParser.js +200 -86
  37. package/lib/parsers/CdlGrammar.g4 +142 -86
  38. package/lib/parsers/Lexer.js +5 -3
  39. package/lib/parsers/index.js +1 -1
  40. package/lib/render/toCdl.js +6 -5
  41. package/lib/render/toHdbcds.js +1 -1
  42. package/lib/render/toSql.js +5 -3
  43. package/lib/render/utils/common.js +19 -6
  44. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  45. package/lib/transform/addTenantFields.js +2 -1
  46. package/lib/transform/db/expansion.js +3 -0
  47. package/lib/transform/db/flattening.js +18 -77
  48. package/lib/transform/db/groupByOrderBy.js +2 -2
  49. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  50. package/lib/transform/db/temporal.js +2 -1
  51. package/lib/transform/forOdata.js +1 -1
  52. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  53. package/lib/transform/odata/createForeignKeys.js +25 -9
  54. package/lib/transform/odata/flattening.js +11 -1
  55. package/lib/transform/transformUtils.js +20 -85
  56. package/package.json +2 -1
  57. package/bin/cds_update_annotations.js +0 -180
@@ -17,7 +17,7 @@ const meta = require('./meta');
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const { inspect } = require('util')
20
+ const { inspect } = require('util');
21
21
 
22
22
  // term instance for messages
23
23
  const colorTerm = term();
@@ -89,7 +89,7 @@ function isDowngradable( messageId, moduleName, options ) {
89
89
  *
90
90
  * @returns {string}
91
91
  */
92
- function severityChangeMarker(msg, config) {
92
+ function severityChangeMarker( msg, config ) {
93
93
  const severity = msg.severity || 'Error';
94
94
  if (config.moduleForMarker) {
95
95
  if (severity === 'Error' &&
@@ -118,7 +118,7 @@ class CompilationError extends Error {
118
118
  // no proper message about _what_ the root cause of the exception was.
119
119
  // To mitigate that, we serialize the first error in the message as well.
120
120
  const firstError = messages.find( m => m.severity === 'Error' )?.toString() || '';
121
- super( `CDS compilation failed (@sap/cds-compiler v${ meta.version() })\n${firstError}` );
121
+ super( `CDS compilation failed (@sap/cds-compiler v${ meta.version() })\n${ firstError }` );
122
122
 
123
123
  /** @since v4.0.0 */
124
124
  this.code = 'ERR_CDS_COMPILATION_FAILURE';
@@ -153,8 +153,7 @@ class CompilationError extends Error {
153
153
  if (this.messages) {
154
154
  messages = messages.concat(this.messages
155
155
  .filter(msg => msg.severity === 'Error')
156
- .map( m => m.toString())
157
- );
156
+ .map( m => m.toString()));
158
157
  }
159
158
  return messages.join('\n');
160
159
  }
@@ -235,7 +234,7 @@ const severitySpecs = {
235
234
  */
236
235
  function reclassifiedSeverity( msg, options, moduleName ) {
237
236
  const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
238
- let severity = spec.severity;
237
+ let { severity } = spec;
239
238
 
240
239
  if (spec.severity === 'Error') {
241
240
  if (!isDowngradable(msg.messageId, moduleName, options))
@@ -430,7 +429,7 @@ function makeMessageFunction( model, options, _moduleName = null ) {
430
429
  hasNewError = hasNewError || msg.severity === 'Error' &&
431
430
  !(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
432
431
  if (!hasMessageArray)
433
- console.error( messageString( msg ) );
432
+ console.error( messageString( msg ) ); // eslint-disable-line no-console
434
433
  return msg;
435
434
  }
436
435
 
@@ -500,7 +499,7 @@ function makeMessageFunction( model, options, _moduleName = null ) {
500
499
 
501
500
  let semanticLocation = location[1] ? homeName( location[1], false ) : null;
502
501
  if (location[2]) { // optional suffix, e.g. annotation
503
- semanticLocation += `/${ (typeof location[2] === 'string') ? location[2]: homeName(location[2]) }`;
502
+ semanticLocation += `/${ (typeof location[2] === 'string') ? location[2] : homeName(location[2]) }`;
504
503
  }
505
504
 
506
505
  const definition = location[1] ? homeName( location[1], true ) : null;
@@ -564,9 +563,8 @@ function makeMessageFunction( model, options, _moduleName = null ) {
564
563
  }
565
564
 
566
565
  function throwWithError() {
567
- if (hasNewError) {
566
+ if (hasNewError)
568
567
  throw new CompilationError(messages, options.attachValidNames && model);
569
- }
570
568
  }
571
569
 
572
570
  /**
@@ -581,9 +579,8 @@ function makeMessageFunction( model, options, _moduleName = null ) {
581
579
  if (!messages || !messages.length)
582
580
  return;
583
581
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
584
- if (hasError( messages, moduleName, options )) {
582
+ if (hasError( messages, moduleName, options ))
585
583
  throw new CompilationError(messages, options.attachValidNames && model);
586
- }
587
584
  }
588
585
 
589
586
  /**
@@ -635,11 +632,10 @@ function makeMessageFunction( model, options, _moduleName = null ) {
635
632
  }
636
633
 
637
634
  /**
638
- *
639
635
  * Change the moduleName used for reclassifying messages.
640
636
  * Needed for to.sql.migration + script
641
- *
642
- * @param {string} __moduleName
637
+ *
638
+ * @param {string} __moduleName
643
639
  */
644
640
  function setModuleName( __moduleName ) {
645
641
  moduleName = __moduleName;
@@ -654,8 +650,6 @@ function makeMessageFunction( model, options, _moduleName = null ) {
654
650
  function setOptions( _options ) {
655
651
  options = _options;
656
652
  }
657
-
658
-
659
653
  }
660
654
 
661
655
  /**
@@ -789,9 +783,13 @@ const paramsTransform = {
789
783
  // more complex convenience:
790
784
  names: transformManyWith( quoted ),
791
785
  number: quote.single, // number cited from source or expected in source
786
+ location: ({ line, col }) => `${ line }:${ col }`,
792
787
  count: quote.direct,
793
788
  line: quote.direct,
794
789
  col: quote.direct,
790
+ literal: quote.direct,
791
+ n: quote.direct,
792
+ m: quote.direct,
795
793
  value,
796
794
  rawvalue: quote.single,
797
795
  rawvalues: transformManyWith( quote.single ), // no 'double' quotes for strings
@@ -812,9 +810,9 @@ const paramsTransform = {
812
810
  version: quote.single, // TODO delete: just use for OData $(VERSION), with version: 2.0
813
811
  };
814
812
 
815
- function asDelimitedId(id) {
813
+ function asDelimitedId( id ) {
816
814
  // Same as in toCdl, but we don't want cyclic dependencies to toCdl.
817
- return `![${id.replace(/]/g, ']]')}]`;
815
+ return `![${ id.replace(/]/g, ']]') }]`;
818
816
  }
819
817
 
820
818
  function anno( name ) {
@@ -912,9 +910,10 @@ function transformElementRef( arg ) {
912
910
  ((arg.scope === 'param' || arg.param) ? ':' : '') +
913
911
  ref.map(
914
912
  item => (typeof item !== 'string'
915
- ? `${ item.id }${item.args ? '(…)' : ''}${item.where ? '[…]' : ''}`
916
- : item) )
917
- .join('.') );
913
+ ? `${ item.id }${ item.args ? '(…)' : '' }${ item.where ? '[…]' : '' }`
914
+ : item)
915
+ ).join('.')
916
+ );
918
917
  }
919
918
 
920
919
  function transformArg( arg, r, args, texts ) {
@@ -936,7 +935,7 @@ function transformArg( arg, r, args, texts ) {
936
935
  return quoted( arg.name );
937
936
  const name = getArtifactName( arg );
938
937
  const prop = [ 'element', 'param', 'action', 'alias' ].find( p => name[p] );
939
- //if (!prop) throw Error()
938
+ // if (!prop) throw Error()
940
939
  if (!prop || !texts[prop] )
941
940
  return shortArtName( arg );
942
941
  r['#'] = texts[name.$variant] && name.$variant || prop; // text variant (set by searchName)
@@ -1047,14 +1046,16 @@ function replaceInString( text, params ) {
1047
1046
  *
1048
1047
  * @returns {string}
1049
1048
  */
1050
- function messageString( err, config ) {
1049
+ function messageString( err, config ) {
1051
1050
  // backwards compatibility <v4
1052
1051
  if (!config || typeof config === 'boolean' || arguments.length > 2) {
1053
1052
  config = {
1053
+ /* eslint-disable prefer-rest-params */
1054
1054
  normalizeFilename: arguments[1],
1055
1055
  noMessageId: arguments[2],
1056
1056
  noHome: arguments[3],
1057
1057
  moduleForMarker: arguments[4],
1058
+ /* eslint-enable prefer-rest-params */
1058
1059
  };
1059
1060
  }
1060
1061
  config.moduleForMarker ??= config.module; // v4.8.0 or earlier compatibility
@@ -1064,7 +1065,7 @@ function messageString( err, config ) {
1064
1065
  const downgradable = severityChangeMarker(err, config);
1065
1066
  // even with noHome, print err.home if the location is weak
1066
1067
  const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
1067
- const msgId = (err.messageId && !config.noMessageId) ? `[${err.messageId}]` : '';
1068
+ const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }]` : '';
1068
1069
  return `${ location }${ severity }${ downgradable }${ msgId }: ${ err.message }${ home }`;
1069
1070
  }
1070
1071
 
@@ -1142,7 +1143,7 @@ function messageStringMultiline( err, config = {} ) {
1142
1143
  const home = !err.home ? '' : (`at ${ err.home }`);
1143
1144
  const severity = err.severity || 'Error';
1144
1145
  const downgradable = severityChangeMarker(err, config);
1145
- const msgId = (err.messageId && !config.noMessageId) ? `${downgradable}[${ err.messageId }${ explainHelp }]` : '';
1146
+ const msgId = (err.messageId && !config.noMessageId) ? `${ downgradable }[${ err.messageId }${ explainHelp }]` : '';
1146
1147
 
1147
1148
  let location = '';
1148
1149
  let context = '';
@@ -1152,7 +1153,7 @@ function messageStringMultiline( err, config = {} ) {
1152
1153
  location += ', ';
1153
1154
  context = _messageContext(err, config);
1154
1155
  if (context !== '')
1155
- context = `\n${context}`;
1156
+ context = `\n${ context }`;
1156
1157
  }
1157
1158
  else if (!home) {
1158
1159
  return `${ colorTerm.severity(severity, severity + msgId) } ${ err.message }`;
@@ -1161,7 +1162,7 @@ function messageStringMultiline( err, config = {} ) {
1161
1162
  const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
1162
1163
  const lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
1163
1164
 
1164
- return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }${context}`;
1165
+ return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }${ context }`;
1165
1166
  }
1166
1167
 
1167
1168
  /**
@@ -1173,14 +1174,14 @@ function messageStringMultiline( err, config = {} ) {
1173
1174
  * @return {number[]}
1174
1175
  * @private
1175
1176
  */
1176
- function _createSourceLineMap(source) {
1177
+ function _createSourceLineMap( source ) {
1177
1178
  const newlines = [ 0 ];
1178
1179
 
1179
1180
  const re = new RegExp(cdlNewLineRegEx, 'g');
1180
1181
  let line;
1181
- while((line = re.exec(source)) !== null) {
1182
+ while ((line = re.exec(source)) !== null)
1182
1183
  newlines.push(line.index + line[0].length);
1183
- }
1184
+
1184
1185
  newlines.push(source.length); // EOF marker
1185
1186
 
1186
1187
  return newlines;
@@ -1252,7 +1253,7 @@ function _messageContext( err, config ) {
1252
1253
  // print source line(s)
1253
1254
  for (let line = startLine; line <= maxLine; line++) {
1254
1255
  // Replaces tabs with 1 space
1255
- let sourceCode = source.substring(sourceLines[line], sourceLines[line+1] || source.length).trimEnd();
1256
+ let sourceCode = source.substring(sourceLines[line], sourceLines[line + 1] || source.length).trimEnd();
1256
1257
  sourceCode = sourceCode.replace(/\t/g, ' ');
1257
1258
  if (sourceCode.length >= MAX_COL_LENGTH)
1258
1259
  sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
@@ -1303,7 +1304,7 @@ function _messageContext( err, config ) {
1303
1304
  */
1304
1305
  function messageContext( sourceLines, err, config ) {
1305
1306
  const loc = err.$location;
1306
- if (!loc || !loc.line|| !loc.file)
1307
+ if (!loc || !loc.line || !loc.file)
1307
1308
  return '';
1308
1309
 
1309
1310
  colorTerm.changeColorMode(config ? config.color : 'auto');
@@ -1323,8 +1324,8 @@ function compareMessage( a, b ) {
1323
1324
  const aFile = a.$location && a.$location.file;
1324
1325
  const bFile = b.$location && b.$location.file;
1325
1326
  if (aFile && bFile) {
1326
- const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
1327
- const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
1327
+ const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1328
+ const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER }; // eslint-disable-line @stylistic/js/max-len
1328
1329
  return ( c( aFile, bFile ) ||
1329
1330
  c( a.$location.line, b.$location.line ) ||
1330
1331
  c( a.$location.col, b.$location.col ) ||
@@ -1344,7 +1345,9 @@ function compareMessage( a, b ) {
1344
1345
  return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;
1345
1346
 
1346
1347
  function c( x, y ) {
1347
- return (x === y) ? 0 : (x > y) ? 1 : -1;
1348
+ if (x === y)
1349
+ return 0;
1350
+ return (x > y) ? 1 : -1;
1348
1351
  }
1349
1352
  }
1350
1353
 
@@ -1435,8 +1438,9 @@ function artName( art, omit ) {
1435
1438
  const loc = art.location ? ` at ${ locationString( art.location ) }` : '';
1436
1439
  throw new CompilerAssertion(
1437
1440
  art.path
1438
- ? `No artifact for ${ art.path.map( i => i.id ).join( '.' )}${ loc }`
1439
- : `No name found in ${ Object.keys( art ).join( '+' )}${ loc }` );
1441
+ ? `No artifact for ${ art.path.map( i => i.id ).join( '.' ) }${ loc }`
1442
+ : `No name found in ${ Object.keys( art ).join( '+' ) }${ loc }`
1443
+ );
1440
1444
  }
1441
1445
 
1442
1446
  const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
@@ -1451,7 +1455,7 @@ function artName( art, omit ) {
1451
1455
 
1452
1456
  if (name.element != null && omit !== 'element') {
1453
1457
  if (name.select != null && !art.$inferred)
1454
- r.push( 'column:' + quoted( name.element ) );
1458
+ r.push( `column:${ quoted( name.element ) }` );
1455
1459
  else if (art.kind === 'builtin')
1456
1460
  return `$var:${ quoted( name.element ) }`;
1457
1461
  else
@@ -1484,20 +1488,25 @@ function homeName( art, absoluteOnly ) {
1484
1488
  return homeName( art._user, absoluteOnly );
1485
1489
  if (art._outer) { // in items property, or annotation with path
1486
1490
  const outer = homeName( art._outer, absoluteOnly );
1487
- return (art.kind === '$annotation')
1488
- ? `${ outer }/${ quoted( '@' + art.name.id ) }`
1489
- : outer;
1491
+ if (art.kind === '$annotation') // eslint-disable-next-line sonarjs/no-nested-template-literals
1492
+ return `${ outer }/${ quoted( `@${ art.name.id }` ) }`;
1493
+ return outer;
1490
1494
  }
1491
- else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
1495
+ else if (art.kind === 'source' || !art.name) { // error reported in parser or on source level
1492
1496
  return null;
1493
- else if (art.kind === 'using')
1497
+ }
1498
+ else if (art.kind === 'using') {
1494
1499
  return `using:${ quoted( art.name.id ) }`;
1495
- else if (art.kind === 'extend' || art.kind === 'annotate')
1500
+ }
1501
+ else if (art.kind === 'extend' || art.kind === 'annotate') {
1496
1502
  return !absoluteOnly && homeNameForExtend( art );
1497
- else if (!art.kind) // annotation assignments are not really supported
1503
+ }
1504
+ else if (!art.kind) { // annotation assignments are not really supported
1498
1505
  return (absoluteOnly) ? art.name.id : quoted( `@${ art.name.id }` );
1499
- else if (art.name._artifact) // block, extend, annotate
1506
+ }
1507
+ else if (art.name._artifact) { // block, extend, annotate
1500
1508
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
1509
+ }
1501
1510
  let main = art._main || art;
1502
1511
  while (main._outer) // anonymous aspect
1503
1512
  main = main._outer._main || main._outer; // w/o `_main` if wrongly in `type`
@@ -1591,7 +1600,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1591
1600
  let step = csnPath[index];
1592
1601
  let currentThing = model?.[step];
1593
1602
 
1594
- function next(steps = 1) {
1603
+ function next( steps = 1 ) {
1595
1604
  for (; steps > 0; --steps) {
1596
1605
  ++index;
1597
1606
  step = csnPath[index];
@@ -1599,7 +1608,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1599
1608
  }
1600
1609
  }
1601
1610
 
1602
- function peek(steps = 1) {
1611
+ function peek( steps = 1 ) {
1603
1612
  return csnPath[index + steps];
1604
1613
  }
1605
1614
 
@@ -1674,13 +1683,12 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1674
1683
  else if (step === 'cast') {
1675
1684
  // To shorten the location, only print 'cast' if it's not a
1676
1685
  // redirection target.
1677
- if (csnPath[index+1] !== 'on' && csnPath[index+1] !== 'target') {
1686
+ if (csnPath[index + 1] !== 'on' && csnPath[index + 1] !== 'target')
1678
1687
  result += '/cast';
1679
- }
1680
1688
  }
1681
1689
  // @ts-ignore
1682
1690
  else if (queryPropsLast.includes(step)) {
1683
- result += '/' + step;
1691
+ result += `/${ step }`;
1684
1692
  break; // always last step
1685
1693
  }
1686
1694
  else if (step === 'on') {
@@ -1689,7 +1697,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1689
1697
  }
1690
1698
  else if (step === 'target') {
1691
1699
  if (currentThing)
1692
- result += '/target:' + _quoted(currentThing);
1700
+ result += `/target:${ _quoted(currentThing) }`;
1693
1701
  else
1694
1702
  result += '/target';
1695
1703
  break;
@@ -1782,13 +1790,14 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1782
1790
  // the last SELECT, e.g. "columns".
1783
1791
  for (let j = csnPath.length - 1; j > index; --j) {
1784
1792
  // @ts-ignore
1785
- if (csnPath[j] === 'SELECT' && !csnDictionaries.includes(csnPath[j-1])) {
1793
+ if (csnPath[j] === 'SELECT' && !csnDictionaries.includes(csnPath[j - 1])) {
1786
1794
  next(j - index); // found last SELECT
1787
1795
  break;
1788
1796
  }
1789
1797
  }
1790
1798
  result += `/select:${ selectDepth }`;
1791
- } else {
1799
+ }
1800
+ else {
1792
1801
  result += '/select';
1793
1802
  }
1794
1803
  }
@@ -1815,7 +1824,8 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1815
1824
  if (peek() === 'expand' || peek() === 'inline') {
1816
1825
  next(); // skip expand/inline
1817
1826
  next(); // go to next column
1818
- } else {
1827
+ }
1828
+ else {
1819
1829
  break;
1820
1830
  }
1821
1831
  } while (index < csnPath.length);
@@ -1823,7 +1833,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1823
1833
  if (elementHierarchy.length > 0)
1824
1834
  result += `/column:${ _quoted(elementHierarchy.join('.')) }`;
1825
1835
  }
1826
- else if(step === 'args') {
1836
+ else if (step === 'args') {
1827
1837
  // Should only be reached for cases, where no SELECT in a union is picked.
1828
1838
  next(); // skip index
1829
1839
  }
@@ -1868,10 +1878,10 @@ function queryDepthForMessage( csnPath, model, view ) {
1868
1878
  return 0;
1869
1879
  }
1870
1880
 
1871
- function sanitizeCsnPath(csnPath) {
1881
+ function sanitizeCsnPath( csnPath ) {
1872
1882
  for (const step of csnPath) {
1873
1883
  if (typeof step !== 'string' && typeof step !== 'number')
1874
- throw new CompilerAssertion(`Found CSN path step that is neither string nor number: ${step} ${ JSON.stringify(csnPath) }`)
1884
+ throw new CompilerAssertion(`Found CSN path step that is neither string nor number: ${ step } ${ JSON.stringify(csnPath) }`);
1875
1885
  }
1876
1886
  }
1877
1887
 
package/lib/base/model.js CHANGED
@@ -38,9 +38,9 @@ const availableDeprecatedFlags = {
38
38
  eagerPersistenceForGeneratedEntities: true,
39
39
  noKeyPropagationWithExpansions: true,
40
40
  ignoreSpecifiedQueryElements: true,
41
- }
41
+ };
42
42
 
43
- const oldDeprecatedFlags_v2 = [
43
+ const oldDeprecatedFlagsV2 = [
44
44
  'createLocalizedViews',
45
45
  // 'downgradableErrors', // re-added in v4
46
46
  'generatedEntityNameWithUnderscore',
@@ -117,7 +117,7 @@ function checkRemovedDeprecatedFlags( options, { error } ) {
117
117
  return;
118
118
 
119
119
  forEach(options.deprecated, (key, val) => {
120
- if (val && oldDeprecatedFlags_v2.includes(key)) {
120
+ if (val && oldDeprecatedFlagsV2.includes(key)) {
121
121
  error('api-invalid-deprecated', null, { name: key },
122
122
  'Deprecated flag $(NAME) has been removed in CDS compiler v3');
123
123
  }
@@ -2,16 +2,24 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- // Version of Promise.all() which does not reject immediately, i.e. after one
6
- // promise rejects, the others are still resolved.
5
+ class PromiseAllError extends Error {
6
+ constructor(subs, ...args) {
7
+ super(...args);
8
+ this.valuesOrErrors = subs;
9
+ }
10
+ }
7
11
 
8
- // If rejected, we reject with a PromiseAllError containing all promise values
9
- // (is Error in case of rejection). Compare that with Promise.all() which
10
- // rejects with the result of the first rejected promise.
11
- //
12
- // This function only works as intended if no promise in `promises` fulfill
13
- // with a value which is an instance of Error.
14
- //
12
+ /**
13
+ * Version of Promise.all() which does not reject immediately, i.e. after one
14
+ * promise rejects, the others are still resolved.
15
+ *
16
+ * If rejected, we reject with a PromiseAllError containing all promise values
17
+ * (is Error in case of rejection). Compare that with Promise.all() which
18
+ * rejects with the result of the first rejected promise.
19
+ *
20
+ * This function only works as intended if no promise in `promises` fulfill
21
+ * with a value which is an instance of Error.
22
+ */
15
23
  function promiseAllDoNotRejectImmediately( promises ) {
16
24
  return Promise.all( promises.map( p => p.catch(e => e) ) )
17
25
  .then( values => (values.some(e => e instanceof Error)
@@ -21,13 +29,6 @@ function promiseAllDoNotRejectImmediately( promises ) {
21
29
  : values));
22
30
  }
23
31
 
24
- class PromiseAllError extends Error {
25
- constructor(subs, ...args) {
26
- super(...args);
27
- this.valuesOrErrors = subs;
28
- }
29
- }
30
-
31
32
  module.exports = {
32
33
  promiseAllDoNotRejectImmediately,
33
34
  };
@@ -369,24 +369,21 @@ function createOptionProcessor() {
369
369
  splitSingleLetterOption(argv, i); // `-ab` -> `-a -b`
370
370
  i += processOption(i);
371
371
  }
372
- else {
373
- // Command or arg
374
- if (result.command === undefined) {
375
- if (optionProcessor.commands[arg]) {
376
- // Found as command
377
- result.command = optionProcessor.commands[arg].longName;
378
- result.options[result.command] = {};
379
- }
380
- else {
381
- // Not found as command, take as arg and stop looking for commands
382
- processPositionalArgument(arg);
383
- result.command = null;
384
- }
372
+ else if (result.command === undefined) { // Command or arg
373
+ if (optionProcessor.commands[arg]) {
374
+ // Found as command
375
+ result.command = optionProcessor.commands[arg].longName;
376
+ result.options[result.command] = {};
385
377
  }
386
378
  else {
379
+ // Not found as command, take as arg and stop looking for commands
387
380
  processPositionalArgument(arg);
381
+ result.command = null;
388
382
  }
389
383
  }
384
+ else {
385
+ processPositionalArgument(arg);
386
+ }
390
387
  }
391
388
  // Avoid 'toXyz: {}' for command without options
392
389
  if (result.command && Object.keys(result.options[result.command]).length === 0)
@@ -414,7 +411,9 @@ function createOptionProcessor() {
414
411
  */
415
412
  function getCurrentPositionArguments() {
416
413
  const cmd = optionProcessor.commands[result.command];
417
- return ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
414
+ return ( cmd && cmd.positionalArguments && cmd.positionalArguments.length )
415
+ ? cmd.positionalArguments
416
+ : optionProcessor.positionalArguments;
418
417
  }
419
418
 
420
419
  function processPositionalArgument( argumentValue ) {
@@ -1,7 +1,10 @@
1
- // Return shuffle functions using pseudo random functions
1
+ 'use strict';
2
2
 
3
+ // Return shuffle functions using pseudo random functions
3
4
  // By <https://github.com/bryc/code/blob/c97a26ad27a9f9d4f48cd3307fd8ee6f1772d4eb/jshash/PRNGs.md>:
4
5
 
6
+ /* eslint no-bitwise: 0, operator-assignment: 0 */
7
+
5
8
  // The random seed must be an integer between 1 and 4294967296 (= 2**32),
6
9
  // otherwise no shuffling takes place.
7
10
  function shuffleGen( seed ) {
@@ -13,7 +13,7 @@ function checkAnnotationExpression( member, _memberName, _prop, path ) {
13
13
  const { art, scope } = this.csnUtils.inspectRef(refPath);
14
14
  if (scope !== '$magic' && art) {
15
15
  const ft = this.csnUtils.getFinalTypeInfo(art.type);
16
- if (!isBuiltinType(ft?.type))
16
+ if (!isBuiltinType(ft?.type) && !ft?.items)
17
17
  this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
18
18
  }
19
19
  },
@@ -485,7 +485,7 @@ function assertConsistency( model, stage ) {
485
485
  'struct', 'array', 'enum', 'null', 'token',
486
486
  ],
487
487
  },
488
- sym: { requires: [ 'location', 'id' ], optional: [ '$delimited' ] },
488
+ sym: { requires: [ 'location', 'id' ], optional: [ '$delimited', '_artifact' ] },
489
489
  val: {
490
490
  test: isVal, // the following for array/struct value
491
491
  requires: [ 'location' ],
@@ -206,6 +206,8 @@ const magicVariables = {
206
206
  $draft: {
207
207
  elements: {
208
208
  IsActiveEntity: {},
209
+ HasActiveEntity: {},
210
+ HasDraftEntity: {},
209
211
  },
210
212
  // Require that elements are accessed, i.e. no $draft, only $draft.<element>.
211
213
  $requireElementAccess: true,
@@ -220,9 +222,10 @@ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
220
222
  // YYYY - MM - dd
221
223
  const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
222
224
  // T HH : mm : ss TZD
223
- // eslint-disable-next-line @stylistic/js/max-len
225
+ // eslint-disable-next-line @stylistic/js/max-len, sonarjs/regex-complexity
224
226
  const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
225
227
  // YYYY - MM - dd T HH : mm : ss . fraction TZD
228
+ // eslint-disable-next-line sonarjs/regex-complexity
226
229
  const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
227
230
 
228
231
  /**
@@ -487,14 +487,24 @@ function extend( model ) {
487
487
  return anno;
488
488
  const hasBase = previousAnno?.literal === 'array';
489
489
  if (!previousAnno) {
490
- const loc = firstEllipsis.location || anno.name.location;
491
- message( 'anno-unexpected-ellipsis', [ loc, art ], { code: '...' } );
492
- previousAnno = { val: [] };
490
+ const location = firstEllipsis.location || anno.name.location;
491
+ message( 'anno-unexpected-ellipsis', [ location, art ], { code: '...' } );
492
+ previousAnno = {
493
+ val: [],
494
+ literal: 'array',
495
+ name: { id: annoName.slice( 1 ) },
496
+ location,
497
+ };
493
498
  }
494
499
  else if (previousAnno.literal !== 'array') {
495
500
  // TODO: If we introduce sub-messages, point to the non-array base value.
496
501
  error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
497
- previousAnno = { val: [] };
502
+ previousAnno = {
503
+ val: [],
504
+ literal: 'array',
505
+ name: previousAnno.name,
506
+ location: previousAnno.location,
507
+ };
498
508
  }
499
509
  const previousValue = previousAnno.val;
500
510
  let prevPos = 0;
@@ -522,7 +532,12 @@ function extend( model ) {
522
532
  }
523
533
  }
524
534
  // console.log('TP:',previousValue.map(se),anno.val.map(se),'->',result.map(se))
525
- return { val: result, literal: 'array' };
535
+ return {
536
+ val: result,
537
+ literal: 'array',
538
+ name: previousAnno.name,
539
+ location: previousAnno.location,
540
+ };
526
541
  }
527
542
  // function se(a) { return a.upTo ? [a.val,a.upTo.val] : a.val ; }
528
543