@sap/cds-compiler 3.9.4 → 4.0.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 (94) hide show
  1. package/CHANGELOG.md +92 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +26 -8
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +12 -4
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +18 -17
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/modelCompare/compare.js +1 -1
  63. package/lib/modelCompare/utils/filter.js +40 -2
  64. package/lib/optionProcessor.js +0 -3
  65. package/lib/render/toCdl.js +247 -214
  66. package/lib/render/toHdbcds.js +197 -181
  67. package/lib/render/toSql.js +325 -289
  68. package/lib/render/utils/common.js +42 -4
  69. package/lib/render/utils/delta.js +1 -1
  70. package/lib/render/utils/sql.js +3 -3
  71. package/lib/transform/braceExpression.js +2 -2
  72. package/lib/transform/db/.eslintrc.json +1 -1
  73. package/lib/transform/db/applyTransformations.js +3 -3
  74. package/lib/transform/db/associations.js +24 -12
  75. package/lib/transform/db/expansion.js +17 -18
  76. package/lib/transform/db/flattening.js +17 -21
  77. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  78. package/lib/transform/db/views.js +3 -4
  79. package/lib/transform/draft/db.js +21 -12
  80. package/lib/transform/draft/odata.js +4 -0
  81. package/lib/transform/forOdataNew.js +11 -10
  82. package/lib/transform/forRelationalDB.js +12 -7
  83. package/lib/transform/localized.js +4 -2
  84. package/lib/transform/odata/toFinalBaseType.js +5 -5
  85. package/lib/transform/odata/typesExposure.js +3 -3
  86. package/lib/transform/parseExpr.js +3 -0
  87. package/lib/transform/transformUtilsNew.js +43 -23
  88. package/lib/transform/translateAssocsToJoins.js +7 -6
  89. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  90. package/lib/transform/universalCsn/coreComputed.js +7 -5
  91. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  92. package/package.json +2 -2
  93. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  94. package/share/messages/message-explanations.json +1 -1
@@ -12,9 +12,11 @@ const _messageIdsWithExplanation = require('../../share/messages/message-explana
12
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
13
13
  const { CompilerAssertion } = require('./error');
14
14
  const { getArtifactName } = require('../compiler/base');
15
+ const { cdlNewLineRegEx } = require('../language/textUtils');
15
16
 
16
17
  const fs = require('fs');
17
18
  const path = require('path');
19
+ const { inspect } = require('util')
18
20
 
19
21
  // term instance for messages
20
22
  const colorTerm = term();
@@ -41,12 +43,12 @@ function hasErrors( messages ) {
41
43
  *
42
44
  * @param {CompileMessage[]} messages
43
45
  * @param {string} moduleName
46
+ * @param {CSN.Options} options
44
47
  * @returns {boolean}
45
48
  */
46
- function hasNonDowngradableErrors( messages, moduleName, deprecatedDowngradable ) {
49
+ function hasNonDowngradableErrors( messages, moduleName, options ) {
47
50
  return messages &&
48
- messages.some( m => m.severity === 'Error' &&
49
- !isDowngradable( m.messageId, moduleName, deprecatedDowngradable ));
51
+ messages.some( m => m.severity === 'Error' && !isDowngradable( m.messageId, moduleName, options ));
50
52
  }
51
53
 
52
54
  /**
@@ -56,10 +58,10 @@ function hasNonDowngradableErrors( messages, moduleName, deprecatedDowngradable
56
58
  *
57
59
  * @param {string} messageId
58
60
  * @param {string} moduleName
59
- * @param {boolean} deprecatedDowngradable
61
+ * @param {CSN.Options} options Options used to check for test mode and beta flags.
60
62
  * @returns {boolean}
61
63
  */
62
- function isDowngradable( messageId, moduleName, deprecatedDowngradable ) {
64
+ function isDowngradable( messageId, moduleName, options ) {
63
65
  if (!messageId || !centralMessages[messageId])
64
66
  return false;
65
67
 
@@ -74,43 +76,40 @@ function isDowngradable( messageId, moduleName, deprecatedDowngradable ) {
74
76
  const { configurableFor } = msg;
75
77
  return (Array.isArray( configurableFor ))
76
78
  ? configurableFor.includes( moduleName )
77
- : configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable);
79
+ : configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
78
80
  }
79
81
 
80
82
  /**
81
83
  * Class for combined compiler errors. Additional members:
82
- * `errors`: vector of errors (CompileMessage and errors from peg.js)
83
- * `model`: the CSN model
84
- * TODO: standard param order
85
- * @class CompilationError
86
- * @extends {Error}
84
+ * - `messages`: array of compiler messages (CompileMessage)
85
+ * - `model`: the CSN model
87
86
  */
88
87
  class CompilationError extends Error {
89
88
  /**
90
- * Creates an instance of CompilationError.
91
- * @param {array} messages vector of errors
89
+ * @param {CompileMessage[]} messages
92
90
  * @param {XSN.Model} [model] the XSN model, only to be set with options.attachValidNames
93
- * @param {string} [text] Text of the error
94
- * @param {any} args Any args to pass to the super constructor
95
- *
96
- * @memberOf CompilationError
97
91
  */
98
- constructor(messages, model, text, ...args) {
99
- super( text || `CDS compilation failed\n${ messages.map( m => m.toString() ).join('\n') }`,
100
- // @ts-ignore Error does not take more arguments according to TypeScript...
101
- ...args );
92
+ constructor(messages, model) {
93
+ // TODO(v5): Don't store stringified messages.
94
+ super( `CDS compilation failed\n${messages?.map( m => m.toString() ).join('\n') || ''}` );
95
+ /** @since v4.0.0 */
96
+ this.code = 'ERR_CDS_COMPILATION_FAILURE';
102
97
  this.messages = messages;
103
98
 
104
- /** @type {boolean} model */
105
- this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics
99
+ // TODO: remove this bin/cdsc.js specifics
100
+ Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
106
101
  // property `model` is only set with options.attachValidNames:
107
102
  Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
108
103
  }
109
104
 
110
- toString() { // does not really help -> set message
111
- return this.message.includes('\n')
112
- ? this.message
113
- : `${ this.message }\n${ this.messages.map( m => m.toString() ).join('\n') }`;
105
+ /**
106
+ * Called by `console.*()` functions in NodeJs. To avoid `err.messages` being
107
+ * printed using `util.inspect()`.
108
+ *
109
+ * @return {string}
110
+ */
111
+ [inspect.custom]() {
112
+ return this.stack || this.message;
114
113
  }
115
114
 
116
115
  /**
@@ -140,8 +139,7 @@ class CompileMessage {
140
139
  */
141
140
  constructor(location, msg, severity = 'Error', id = null, home = null, moduleName = null) {
142
141
  this.message = msg;
143
- this.location = location;
144
- this.$location = { ...this.location, address: undefined };
142
+ this.$location = { ...location, address: undefined };
145
143
  this.validNames = null;
146
144
  this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
147
145
  this.severity = severity;
@@ -180,16 +178,12 @@ const severitySpecs = {
180
178
  * @param {object} msg The CompileMessage.
181
179
  * @param {CSN.Options} options
182
180
  * @param {string} moduleName
183
- * @param {boolean} deprecatedDowngradable
184
181
  * @returns {MessageSeverity}
185
182
  */
186
- function reclassifiedSeverity( msg, options, moduleName, deprecatedDowngradable ) {
183
+ function reclassifiedSeverity( msg, options, moduleName ) {
187
184
  const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
188
185
  if (spec.severity === 'Error') {
189
- const { configurableFor } = spec;
190
- if (!(Array.isArray( configurableFor )
191
- ? configurableFor.includes( moduleName )
192
- : configurableFor && (configurableFor !== 'deprecated' || deprecatedDowngradable)))
186
+ if (!isDowngradable(msg.messageId, moduleName, options))
193
187
  return 'Error';
194
188
  }
195
189
  else {
@@ -311,7 +305,6 @@ function makeMessageFunction( model, options, moduleName = null ) {
311
305
  }
312
306
 
313
307
  const hasMessageArray = !!options.messages;
314
- const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
315
308
  /**
316
309
  * Array of collected compiler messages. Only use it for debugging. Will not
317
310
  * contain the messages created during a `callTransparently` call.
@@ -362,12 +355,12 @@ function makeMessageFunction( model, options, moduleName = null ) {
362
355
  if (id) {
363
356
  if (options.testMode && !options.$recompile)
364
357
  _check$Consistency( id, moduleName, severity, texts, options );
365
- msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
358
+ msg.severity = reclassifiedSeverity( msg, options, moduleName );
366
359
  }
367
360
 
368
361
  messages.push( msg );
369
362
  hasNewError = hasNewError || msg.severity === 'Error' &&
370
- !(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
363
+ !(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
371
364
  if (!hasMessageArray)
372
365
  console.error( messageString( msg ) );
373
366
  return msg;
@@ -410,7 +403,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
410
403
 
411
404
  /**
412
405
  * Normalize the given location. Location may be a CSN path, XSN/CSN location or an
413
- * array of the form `[CSN.Location, user, suffix]`.
406
+ * array of the form `[CSN.Location, XSN user, suffix]`.
414
407
  *
415
408
  * @param {any} location
416
409
  * @returns {[CSN.Location, string, string]} Location, semantic location and definition.
@@ -514,7 +507,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
514
507
  if (!messages || !messages.length)
515
508
  return;
516
509
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
517
- if (hasError( messages, moduleName, deprecatedDowngradable ))
510
+ if (hasError( messages, moduleName, options ))
518
511
  throw new CompilationError( messages, options.attachValidNames && model );
519
512
  }
520
513
 
@@ -526,13 +519,13 @@ function makeMessageFunction( model, options, moduleName = null ) {
526
519
  function reclassifyMessagesForModule() {
527
520
  for (const msg of messages) {
528
521
  if (msg.messageId && msg.severity !== 'Error') {
529
- const severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable);
522
+ const severity = reclassifiedSeverity( msg, options, moduleName );
530
523
  if (severity !== msg.severity) {
531
524
  msg.severity = severity;
532
525
  // Re-set the module regardless of severity, since we reclassified it.
533
526
  Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
534
527
  hasNewError = hasNewError || severity === 'Error' &&
535
- !(options.testMode && isDowngradable( msg.messageId, moduleName, deprecatedDowngradable ));
528
+ !(options.testMode && isDowngradable( msg.messageId, moduleName, options ));
536
529
  }
537
530
  }
538
531
  }
@@ -621,7 +614,7 @@ function _check$Severities( id, moduleName, severity ) {
621
614
  return;
622
615
  }
623
616
  // now try whether the message could be something less than an Error in the module due to user wishes
624
- if (!isDowngradable( id, moduleName, true )) { // always an error in module
617
+ if (!isDowngradable(id, moduleName, { testMode: true, deprecated: { downgradableErrors: true } } )) { // always an error in module
625
618
  if (severity !== 'Error')
626
619
  throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${ severity }" for message ID "${ id }" in module "${ moduleName }"` );
627
620
  }
@@ -656,7 +649,7 @@ function _check$Texts( id, prop, val ) {
656
649
  }
657
650
 
658
651
  const quote = { // could be an option in the future
659
- double: p => `“${ p }”`, // for names, including annotation names (with preceeding `@`)
652
+ double: p => `“${ p }”`, // for names, including annotation names (with preceding `@`)
660
653
  single: p => `‘${ p }’`, // for other things cited from or expected in the model
661
654
  angle: p => `‹${ p }›`, // for tokens like ‹Identifier›, and similar
662
655
  direct: p => p, // e.g. for numbers _not cited from or expected in_ the source
@@ -786,7 +779,7 @@ function quoted( name ) {
786
779
 
787
780
  function tokenSymbol( token ) {
788
781
  if (token.match( /^[A-Z][A-Z]/ )) // keyword
789
- return quote.upper( token );
782
+ return keyword( token );
790
783
  else if (token.match( /^[A-Z][a-z]/ )) // Number, Identifier, ...
791
784
  return quote.angle( token );
792
785
  if (token.startsWith('\'') && token.endsWith('\'')) // operator token symbol
@@ -803,12 +796,8 @@ function transformElementRef( arg ) {
803
796
  if (arg.ref) {
804
797
  // Can be used by CSN backends to create a simple path such as E:elem
805
798
  return quoted(arg.ref.map((ref) => {
806
- if (ref.id) {
807
- // Indicate that the path has a filter.
808
- if (ref.where)
809
- return `${ ref.id }[…]`;
810
- return ref.id;
811
- }
799
+ if (ref.id)
800
+ return `${ ref.id }${ref.args ? '(…)' : ''}${ref.where ? '[…]' : ''}`;
812
801
  return ref;
813
802
  }).join('.'));
814
803
  }
@@ -914,26 +903,54 @@ function weakLocation( loc ) {
914
903
  }
915
904
 
916
905
  /**
917
- * Return message string with location if present in compact form (i.e. one line)
906
+ * Return message string with location if present in compact form (i.e. one line).
907
+ *
908
+ * IMPORTANT:
909
+ * cds-compiler <v4 used following signature:
910
+ * `messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) : string`
911
+ * This signature is still supported for backwards compatibility but is deprecated.
918
912
  *
919
913
  * Example:
920
914
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
921
915
  *
922
916
  * @param {CompileMessage} err
923
- * @param {boolean} [normalizeFilename]
924
- * @param {boolean} [noMessageId]
925
- * @param {boolean} [noHome]
917
+ *
918
+ * @param {object} [config = {}]
919
+ *
920
+ * @param {boolean} [config.normalizeFilename]
921
+ * If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
922
+ *
923
+ * @param {boolean} [config.noMessageId]
924
+ * If true, will _not_ show the message ID (+ explanation hint) in the output.
925
+ *
926
+ * @param {boolean} [config.noHome]
927
+ * If true, will _not_ show message's semantic location.
928
+ *
929
+ * @param {string} [config.module]
930
+ * If set, downgradable error messages will get a '‹↓›' marker, depending on whether
931
+ * the message can be downgraded for the given module.
932
+ *
926
933
  * @returns {string}
927
934
  */
928
- function messageString( err, normalizeFilename, noMessageId, noHome, moduleName = undefined ) {
929
- const location = (err.$location?.file ? `${ locationString( err.$location, normalizeFilename ) }: ` : '');
935
+ function messageString( err, config ) {
936
+ // backwards compatibility <v4
937
+ if (!config || typeof config === 'boolean' || arguments.length > 2) {
938
+ config = {
939
+ normalizeFilename: arguments[1],
940
+ noMessageId: arguments[2],
941
+ noHome: arguments[3],
942
+ module: arguments[4],
943
+ };
944
+ }
945
+
946
+ const location = (err.$location?.file ? `${ locationString( err.$location, config.normalizeFilename ) }: ` : '');
930
947
  const severity = err.severity || 'Error';
931
- const downgradable = severity === 'Error' && moduleName &&
932
- isDowngradable(err.messageId, moduleName, false) ? '‹↓›' : '';
948
+ const downgradable = severity === 'Error' && config.module &&
949
+ isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
933
950
  // even with noHome, print err.home if the location is weak
934
- const home = !err.home || noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
951
+ const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
935
952
  // TODO: the plan was with brackets = `Error[ref-undefined-def]`
936
- const id = err.messageId && !noMessageId ? ` ${ err.messageId }` : '';
953
+ const id = err.messageId && !config.noMessageId ? ` ${ err.messageId }` : '';
937
954
  return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;
938
955
  }
939
956
 
@@ -957,91 +974,151 @@ function messageHash( msg ) {
957
974
  }
958
975
 
959
976
  /**
960
- * Returns a message string with file- and semantic location if present
961
- * in multiline form.
962
- *
963
- * Example:
964
- * ```txt
965
- * Error[message-id]: Can't find type `nu` in this scope
966
- * |
967
- * <source>.cds:3:11, at entity:“E”/element:“e”
968
- * ```
977
+ * Returns a message string with file- and semantic location if present in multiline form
978
+ * with a source code snippet below that has highlights for the message's location.
979
+ * The message (+ message id) are colored according to their severity.
969
980
  *
970
981
  * @param {CompileMessage} err
982
+ *
971
983
  * @param {object} [config = {}]
972
- * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
984
+ *
985
+ * @param {boolean} [config.normalizeFilename]
986
+ * If true, the file path will be normalized to use `/` as the path separator (instead of `\` on Windows).
987
+ *
973
988
  * @param {boolean} [config.noMessageId]
974
- * @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
975
- * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
976
- * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
977
- * coloring will be used. If 'auto', we will decide based on certain factors such
978
- * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
979
- * unset.
989
+ * If true, will _not_ show the message ID (+ explanation hint) in the output.
990
+ *
991
+ * @param {boolean} [config.hintExplanation]
992
+ * If true, messages with explanations will get a "…" marker.
993
+ *
994
+ * @param {string} [config.module]
995
+ * If set, downgradable error messages will get a '‹↓›' marker, depending on whether
996
+ * the message can be downgraded for the given module.
997
+ *
998
+ * @param {Record<string, string>} [config.sourceMap]
999
+ * A dictionary of filename<->source-code entries. You can pass the `fileCache` that is used
1000
+ * by the compiler.
1001
+ *
1002
+ * @param {Record<string, number[]>} [config.sourceLineMap]
1003
+ * A dictionary of filename<->source-newline-indices entries. Is used to extract source code
1004
+ * snippets for message locations. If not set, will be set and filled by this function on-demand.
1005
+ * An entry is an array of character/byte offsets to new-lines, for example sourceLineMap[1] is the
1006
+ * end-newline for the second line.
1007
+ *
1008
+ * @param {string} [config.cwd]
1009
+ * The current working directory (cwd) that was passed to the compiler.
1010
+ * This value is only used if a source map is provided and relative paths needs to be
1011
+ * resolved to absolute ones.
1012
+ *
1013
+ * @param {boolean | 'auto' | 'never' | 'always'} [config.color]
1014
+ * If true/'always', ANSI escape codes will be used for coloring the severity. If false/'never',
1015
+ * no coloring will be used. If 'auto', we will decide based on certain factors such
1016
+ * as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
1017
+ * unset.
1018
+ *
980
1019
  * @returns {string}
981
1020
  */
982
1021
  function messageStringMultiline( err, config = {} ) {
983
1022
  colorTerm.changeColorMode(config ? config.color : 'auto');
984
1023
 
985
1024
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
986
- const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
987
1025
  const home = !err.home ? '' : (`at ${ err.home }`);
988
1026
  const severity = err.severity || 'Error';
1027
+ const downgradable = config.module && severity === 'Error' &&
1028
+ isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
1029
+ const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${downgradable}${ explainHelp }]` : '';
989
1030
 
990
1031
  let location = '';
991
- if (err.$location && err.$location.file) {
1032
+ let context = '';
1033
+ if (err.$location?.file) {
992
1034
  location += locationString( err.$location, config.normalizeFilename );
993
1035
  if (home)
994
1036
  location += ', ';
1037
+ context = _messageContext(err, config);
1038
+ if (context !== '')
1039
+ context = `\n${context}`;
995
1040
  }
996
1041
  else if (!home) {
997
1042
  return `${ colorTerm.severity(severity, severity + msgId) } ${ err.message }`;
998
1043
  }
999
1044
 
1000
- let lineSpacer = '';
1001
- if (config.withLineSpacer) {
1002
- const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
1003
- lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
1045
+ const additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
1046
+ const lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
1047
+
1048
+ return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }${context}`;
1049
+ }
1050
+
1051
+ /**
1052
+ * Used by _messageContext() to create an array of line start offsets.
1053
+ * Each entry in the returned array contains the offset for the start line,
1054
+ * where the line is the index in the array.
1055
+ *
1056
+ * @param source
1057
+ * @return {number[]}
1058
+ * @private
1059
+ */
1060
+ function _createSourceLineMap(source) {
1061
+ const newlines = [ 0 ];
1062
+
1063
+ const re = new RegExp(cdlNewLineRegEx, 'g');
1064
+ let line;
1065
+ while((line = re.exec(source)) !== null) {
1066
+ newlines.push(line.index + line[0].length);
1004
1067
  }
1068
+ newlines.push(source.length); // EOF marker
1005
1069
 
1006
- return `${ colorTerm.severity(severity, severity + msgId) }: ${ err.message }${ lineSpacer }\n ${ location }${ home }`;
1070
+ return newlines;
1007
1071
  }
1008
1072
 
1009
1073
  /**
1010
- * Returns a context (code) string that is human readable (similar to rust's compiler)
1074
+ * Returns a context (code) string that is human-readable (similar to rust's compiler).
1075
+ *
1076
+ * IMPORTANT: In case that `config.sourceMap[err.loc.file]` does not exist, this function
1077
+ * uses `path.resolve()` to get the absolute filename.
1011
1078
  *
1012
1079
  * Example Output:
1013
1080
  * |
1014
1081
  * 3 | num * nu
1015
1082
  * | ^^
1016
1083
  *
1017
- * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
1018
- * from `lib/utils/file.js`
1019
1084
  * @param {CompileMessage} err Error object containing all details like line, message, etc.
1020
- * @param {object} [config = {}]
1021
- * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
1022
- * coloring will be used. If 'auto', we will decide based on certain factors such
1023
- * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
1024
- * unset.
1085
+ * @param {object} [config = {}] See `messageStringMultiline()` for details.
1086
+ *
1025
1087
  * @returns {string}
1088
+ * @private
1026
1089
  */
1027
- function messageContext( sourceLines, err, config ) {
1028
- colorTerm.changeColorMode(config ? config.color : 'auto');
1090
+ function _messageContext( err, config ) {
1029
1091
  const MAX_COL_LENGTH = 100;
1030
1092
 
1031
1093
  const loc = err.$location;
1032
- if (!loc || !loc.line)
1094
+ if (!loc || !loc.line || !loc.file || !config.sourceMap)
1095
+ return '';
1096
+
1097
+ let filepath = config.sourceMap[loc.file]?.realname || loc.file;
1098
+ if (!config.sourceMap[filepath])
1099
+ filepath = path.resolve(config.cwd || '', filepath);
1100
+
1101
+ const source = config.sourceMap[filepath];
1102
+ if (!source || source === true) // true: file exists, no further knowledge
1033
1103
  return '';
1034
1104
 
1105
+ if (!config.sourceLineMap)
1106
+ config.sourceLineMap = Object.create(null);
1107
+ if (!config.sourceLineMap[filepath])
1108
+ config.sourceLineMap[filepath] = _createSourceLineMap(source);
1109
+
1110
+ const sourceLines = config.sourceLineMap[filepath];
1035
1111
 
1036
1112
  // Lines are 1-based, we need 0-based ones for arrays
1037
- const startLine = loc.line - 1;
1038
- const endLine = loc.endLine ? loc.endLine - 1 : startLine;
1113
+ const startLine = Math.min(sourceLines.length, loc.line - 1);
1114
+ const endLine = Math.min(sourceLines.length, loc.endLine ? loc.endLine - 1 : startLine);
1115
+ /** Only print N lines even if the error spans more lines. */
1116
+ const maxLine = Math.min((startLine + 2), endLine);
1039
1117
 
1040
1118
  // check that source lines exists
1041
- if (typeof sourceLines[startLine] !== 'string' || typeof sourceLines[endLine] !== 'string')
1119
+ if (typeof sourceLines[startLine] !== 'number')
1042
1120
  return '';
1043
1121
 
1044
-
1045
1122
  const digits = String(endLine + 1).length;
1046
1123
  const severity = err.severity || 'Error';
1047
1124
  const indent = ' '.repeat(2 + digits);
@@ -1054,15 +1131,13 @@ function messageContext( sourceLines, err, config ) {
1054
1131
  let endColumn = (loc.endCol && loc.endCol > loc.col) ? loc.endCol - 1 : loc.col;
1055
1132
  endColumn = Math.min(MAX_COL_LENGTH, endColumn);
1056
1133
 
1057
- /** Only print N lines even if the error spans more lines. */
1058
- const maxLine = Math.min((startLine + 2), endLine);
1059
-
1060
1134
  let msg = `${ indent }|\n`;
1061
1135
 
1062
1136
  // print source line(s)
1063
1137
  for (let line = startLine; line <= maxLine; line++) {
1064
1138
  // Replaces tabs with 1 space
1065
- let sourceCode = sourceLines[line].replace(/\t/g, ' ');
1139
+ let sourceCode = source.substring(sourceLines[line], sourceLines[line+1] || source.length).trimEnd();
1140
+ sourceCode = sourceCode.replace(/\t/g, ' ');
1066
1141
  if (sourceCode.length >= MAX_COL_LENGTH)
1067
1142
  sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
1068
1143
  // Only prepend space if the line contains any sources.
@@ -1081,7 +1156,7 @@ function messageContext( sourceLines, err, config ) {
1081
1156
  }
1082
1157
  else if (maxLine !== endLine) {
1083
1158
  // error spans more lines which we don't print
1084
- msg += `${ indent }| ...`;
1159
+ msg += `${ indent }| …`;
1085
1160
  }
1086
1161
  else {
1087
1162
  msg += `${ indent }|`;
@@ -1090,6 +1165,36 @@ function messageContext( sourceLines, err, config ) {
1090
1165
  return msg;
1091
1166
  }
1092
1167
 
1168
+ /**
1169
+ * Returns a context (code) string that is human-readable (similar to rust's compiler)
1170
+ *
1171
+ * Example Output:
1172
+ * |
1173
+ * 3 | num * nu
1174
+ * | ^^
1175
+ *
1176
+ * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
1177
+ * from `lib/utils/file.js`
1178
+ * @param {CompileMessage} err Error object containing all details like line, message, etc.
1179
+ * @param {object} [config = {}]
1180
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
1181
+ * coloring will be used. If 'auto', we will decide based on certain factors such
1182
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
1183
+ * unset.
1184
+ * @returns {string}
1185
+ *
1186
+ * @deprecated Use `messageStringMultiline()` with `config.sourceMap` and `config.sourceLineMap` instead!
1187
+ */
1188
+ function messageContext( sourceLines, err, config ) {
1189
+ const loc = err.$location;
1190
+ if (!loc || !loc.line|| !loc.file)
1191
+ return '';
1192
+
1193
+ colorTerm.changeColorMode(config ? config.color : 'auto');
1194
+ const sourceMap = { [err.$location.file]: sourceLines.join('\n') };
1195
+ return _messageContext(err, { ...config, sourceMap });
1196
+ }
1197
+
1093
1198
  /**
1094
1199
  * Compare two messages `a` and `b`. Return 0 if they are equal, 1 if `a` is
1095
1200
  * larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
@@ -1238,6 +1343,8 @@ function memberActionName( art ) {
1238
1343
  function homeName( art, absoluteOnly ) {
1239
1344
  if (!art)
1240
1345
  return art;
1346
+ if (art._user) // when providing a path item with filter as “user”
1347
+ return homeName( art._user, absoluteOnly );
1241
1348
  if (art._outer) // in items property
1242
1349
  return homeName( art._outer, absoluteOnly );
1243
1350
  else if (art.kind === 'source' || !art.name) // error reported in parser or on source level
@@ -1446,8 +1553,8 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1446
1553
  result += '/target';
1447
1554
  break;
1448
1555
  }
1449
- else if (step === 'xpr' || step === 'ref' || step === 'as') {
1450
- break; // don't go into xprs, refs, aliases, etc.
1556
+ else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
1557
+ break; // don't go into xprs, refs, aliases, values, etc.
1451
1558
  }
1452
1559
  else if (step === 'returns') {
1453
1560
  result += '/returns';
@@ -1675,7 +1782,6 @@ module.exports = {
1675
1782
  deduplicateMessages,
1676
1783
  CompileMessage,
1677
1784
  CompilationError,
1678
- isMessageDowngradable: isDowngradable,
1679
1785
  explainMessage,
1680
1786
  hasMessageExplanation,
1681
1787
  messageIdsWithExplanation,
package/lib/base/model.js CHANGED
@@ -22,31 +22,29 @@ const queryOps = {
22
22
  const availableBetaFlags = {
23
23
  // enabled by --beta-mode
24
24
  annotationExpressions: true,
25
- toRename: true,
26
- assocsWithParams: true,
25
+ toRename: true, // Removes once it's publicly documented
26
+ assocsWithParams: true, // beta, because runtimes don't support it, yet.
27
27
  hanaAssocRealCardinality: true,
28
- mapAssocToJoinCardinality: true,
29
- ignoreAssocPublishingInUnion: true,
28
+ mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
30
29
  enableUniversalCsn: true,
31
- postgres: true,
32
30
  aspectWithoutElements: true, // TODO: do not just remove beta flag - remove elements, too!
33
31
  odataTerms: true,
34
- optionalActionFunctionParameters: true,
35
- calculatedElementsOnWrite: true,
32
+ optionalActionFunctionParameters: true, // not supported by runtime, yet.
36
33
  // disabled by --beta-mode
37
34
  nestedServices: false,
38
- v4preview: false,
39
35
  };
40
36
 
37
+ // Used by isDeprecatedEnabled() to check if any flag ist set.
41
38
  const availableDeprecatedFlags = {
42
39
  // the old ones starting with _, : false
43
- autoCorrectOrderBySourceRefs: true,
40
+ downgradableErrors: true,
41
+ includesNonShadowedFirst: true,
44
42
  eagerPersistenceForGeneratedEntities: true,
45
43
  }
46
44
 
47
45
  const oldDeprecatedFlags_v2 = [
48
46
  'createLocalizedViews',
49
- 'downgradableErrors',
47
+ // 'downgradableErrors', // re-added in v4
50
48
  'generatedEntityNameWithUnderscore',
51
49
  'longAutoexposed',
52
50
  'noElementsExpansion',
@@ -60,13 +58,15 @@ const oldDeprecatedFlags_v2 = [
60
58
  'shortAutoexposed',
61
59
  'unmanagedUpInComponent',
62
60
  'v1KeysForTemporal',
61
+ // do not add old deprecated flags which should not lead to an error:
62
+ // autoCorrectOrderBySourceRefs - just info would be ok
63
63
  ];
64
64
 
65
65
  /**
66
66
  * Test for early-adaptor feature, stored in option `beta`(new-style) / `betaMode`(old-style)
67
67
  * With that, the value of `beta` is a dictionary of feature=>Boolean.
68
68
  *
69
- * Beta features cannot be used when options.deprecated is set.
69
+ * Beta features cannot be used when `options.deprecated` is set.
70
70
  *
71
71
  * A feature always needs to be provided - otherwise false will be returned.
72
72
  *
@@ -1,6 +1,6 @@
1
1
  {
2
2
  // we actually do not extend airbnb-base, as it weakens some eslint:recommended rules
3
- "extends": ["../../.eslintrc-ydkjsi.json", "plugin:jsdoc/recommended"],
3
+ "extends": ["plugin:jsdoc/recommended", "../../.eslintrc-ydkjsi.json"],
4
4
  "plugins": [
5
5
  "jsdoc"
6
6
  ],
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @param {CSN.Element} member Member to be checked
14
14
  */
15
- function checkCoreMediaTypeAllowence( member ) {
15
+ function checkCoreMediaTypeAllowance( member ) {
16
16
  const allowedCoreMediaTypes = {
17
17
  'cds.String': 1,
18
18
  'cds.LargeString': 1,
@@ -67,7 +67,7 @@ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
67
67
  }
68
68
 
69
69
  module.exports = {
70
- checkCoreMediaTypeAllowence,
70
+ checkCoreMediaTypeAllowance,
71
71
  checkAnalytics,
72
72
  checkAtSapAnnotations,
73
73
  checkReadOnlyAndInsertOnly,
@@ -153,7 +153,7 @@ function checkManagedAssoc( art ) {
153
153
  /**
154
154
  * All DB & OData flat mode must reject recursive type usages in entities
155
155
  * 'items' break recursion as 'items' will turn into an NCLOB and the path
156
- * prefix to 'items' can be flattend in the DB.
156
+ * prefix to 'items' can be flattened in the DB.
157
157
  * In OData flat mode the first appearance of 'items' breaks out into structured
158
158
  * mode producing (legal) recursive complex types.
159
159
  *