@sap/cds-compiler 2.13.8 → 3.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 (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -7,10 +7,11 @@
7
7
  const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
- const { centralMessages, centralMessageTexts } = require('./message-registry');
10
+ const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
11
11
  const { copyPropIfExist } = require('../utils/objectUtils');
12
12
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
13
13
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
14
+ const { CompilerAssertion } = require("./error");
14
15
 
15
16
  const fs = require('fs');
16
17
  const path = require('path');
@@ -24,8 +25,9 @@ let test$severities = null;
24
25
  let test$texts = null;
25
26
 
26
27
  /**
27
- * Returns true if at least one of the given messages is of severity "Error"
28
- * @param {CSN.Message[]} messages
28
+ * Returns true if at least one of the given messages is of severity "Error".
29
+ *
30
+ * @param {CompileMessage[]} messages
29
31
  * @returns {boolean}
30
32
  */
31
33
  function hasErrors( messages ) {
@@ -34,9 +36,10 @@ function hasErrors( messages ) {
34
36
 
35
37
  /**
36
38
  * Returns true if at least one of the given messages is of severity "Error"
37
- * and *cannot* be reclassified to a warning.
39
+ * and *cannot* be reclassified to a warning for the given module.
40
+ * Won't detect already downgraded messages.
38
41
  *
39
- * @param {CSN.Message[]} messages
42
+ * @param {CompileMessage[]} messages
40
43
  * @param {string} moduleName
41
44
  * @returns {boolean}
42
45
  */
@@ -122,7 +125,7 @@ class CompileMessage {
122
125
  * Creates an instance of CompileMessage.
123
126
  * @param {any} location Location of the message
124
127
  * @param {string} msg The message text
125
- * @param {CSN.MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
128
+ * @param {MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
126
129
  * @param {string} [id] The ID of the message - visible as property messageId
127
130
  * @param {any} [home]
128
131
  * @param {string} [moduleName] Name of the module that created this message
@@ -141,7 +144,7 @@ class CompileMessage {
141
144
  Object.defineProperty( this, 'messageId', { value: id } );
142
145
  // this.messageId = id; // ids not yet finalized
143
146
  if (moduleName)
144
- Object.defineProperty( this, '$module', { value: moduleName } );
147
+ Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
145
148
  }
146
149
 
147
150
  toString() { // should have no argument...
@@ -184,7 +187,7 @@ const severitySpecs = {
184
187
  };
185
188
 
186
189
  /**
187
- * Reclassify the given message's severity using:
190
+ * Get the reclassified severity of the given message using:
188
191
  *
189
192
  * 1. The specified severity: either centrally provided or via the input severity
190
193
  * - when generally specified as 'Error', immediately return 'Error'
@@ -195,17 +198,14 @@ const severitySpecs = {
195
198
  * been returned according to 1, return the severity according to the user wishes.
196
199
  * 3. Otherwise, use the specified severity.
197
200
  *
198
- * @param {string} id
199
- * @param {CSN.MessageSeverity} severity
200
- * @param {object} severities
201
+ * @param {object} msg The CompileMessage.
202
+ * @param {CSN.Options} options
201
203
  * @param {string} moduleName
202
- * @returns {CSN.MessageSeverity}
203
- *
204
- * TODO: we should pass options as usual
205
- * TODO: should be part of the returned function
204
+ * @param {boolean} deprecatedDowngradable
205
+ * @returns {MessageSeverity}
206
206
  */
207
- function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable ) {
208
- const spec = centralMessages[id] || { severity };
207
+ function reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable ) {
208
+ const spec = centralMessages[msg.messageId] || { severity: msg.severity, configurableFor: null, errorFor: null };
209
209
  if (spec.severity === 'Error') {
210
210
  const { configurableFor } = spec;
211
211
  if (!(Array.isArray( configurableFor )
@@ -218,7 +218,17 @@ function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedD
218
218
  if (Array.isArray( errorFor ) && errorFor.includes( moduleName ))
219
219
  return 'Error';
220
220
  }
221
- return normalizedSeverity( severities[id] ) || spec.severity;
221
+
222
+ if (!options.severities)
223
+ return spec.severity;
224
+
225
+ let newSeverity = options.severities[msg.messageId];
226
+ // The user could have specified a severity through an old message ID.
227
+ if (!newSeverity && spec.oldNames) {
228
+ const oldName = spec.oldNames.find((name => options.severities[name]))
229
+ newSeverity = options.severities[oldName];
230
+ }
231
+ return normalizedSeverity( newSeverity ) || spec.severity;
222
232
  }
223
233
 
224
234
  function normalizedSeverity( severity ) {
@@ -228,24 +238,6 @@ function normalizedSeverity( severity ) {
228
238
  return s ? s.name : 'Error';
229
239
  }
230
240
 
231
- /**
232
- * Reclassifies all messages according to the current module.
233
- * This is required because if throwWithError() throws and the message's
234
- * severities has `errorFor` set, then the message may still appear to be a warning.
235
- *
236
- * TODO: this actually likely needs to be called by the backend module at the beginning!
237
- *
238
- * @param {CSN.Message[]} messages
239
- * @param {object} severities
240
- * @param {string} moduleName
241
- */
242
- function reclassifyMessagesForModule(messages, severities, moduleName, deprecatedDowngradable) {
243
- for (const msg of messages) {
244
- if (msg.messageId && msg.severity !== 'Error')
245
- msg.severity = reclassifiedSeverity(msg.messageId, msg.severity, severities, moduleName, deprecatedDowngradable);
246
- }
247
- }
248
-
249
241
  /**
250
242
  * Compare two severities. Returns 0 if they are the same, and <0 if
251
243
  * `a` has a lower `level` than `b` according to {@link severitySpecs},
@@ -253,8 +245,8 @@ function reclassifyMessagesForModule(messages, severities, moduleName, deprecate
253
245
  *
254
246
  * compareSeverities('Error', 'Info') => Error < Info => -1
255
247
  *
256
- * @param {CSN.MessageSeverity} a
257
- * @param {CSN.MessageSeverity} b
248
+ * @param {MessageSeverity} a
249
+ * @param {MessageSeverity} b
258
250
  * @see severitySpecs
259
251
  */
260
252
  function compareSeverities( a, b ) {
@@ -289,10 +281,9 @@ function searchForLocation( model, csnPath ) {
289
281
 
290
282
  /**
291
283
  * Create the `message` functions to emit messages.
292
- * See internalDoc/ReportingMessages.md for detail
293
284
  *
294
285
  * @example
295
- * ```
286
+ * ```js
296
287
  * const { createMessageFunctions } = require(‘../base/messages’);
297
288
  * function module( …, options ) {
298
289
  * const { message, info, throwWithError } = createMessageFunctions( options, moduleName );
@@ -308,14 +299,14 @@ function searchForLocation( model, csnPath ) {
308
299
  * @param {object} [model=null] the CSN or XSN model, used for convenience
309
300
  */
310
301
  function createMessageFunctions( options, moduleName, model = null ) {
311
- return makeMessageFunction( model, options, moduleName, true );
302
+ return makeMessageFunction( model, options, moduleName );
312
303
  }
313
304
 
314
305
  /**
315
306
  * Create the `message` function to emit messages.
316
307
  *
317
308
  * @example
318
- * ```
309
+ * ```js
319
310
  * const { makeMessageFunction } = require(‘../base/messages’);
320
311
  * function module( …, options ) {
321
312
  * const { message, info, throwWithError } = makeMessageFunction( model, options, moduleName );
@@ -328,28 +319,36 @@ function createMessageFunctions( options, moduleName, model = null ) {
328
319
  * ```
329
320
  * @param {object} model
330
321
  * @param {CSN.Options} [options]
331
- * @param {string} [moduleName]
332
- * @param {boolean} [throwOnlyWithNew=false] behave like createMessageFunctions
322
+ * @param {string|null} [moduleName]
333
323
  */
334
- function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNew = false ) {
335
- // ensure message consistency during runtime with --test-mode
336
- if (options.testMode)
324
+ function makeMessageFunction( model, options, moduleName = null ) {
325
+ if (options.testMode) {
326
+ // ensure message consistency during runtime with --test-mode
337
327
  _check$Init( options );
328
+ if (!options.messages)
329
+ throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
330
+ }
338
331
 
339
332
  const hasMessageArray = !!options.messages;
340
- const severities = options.severities || {};
341
- const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );
333
+ const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
342
334
  /**
343
335
  * Array of collected compiler messages. Only use it for debugging. Will not
344
336
  * contain the messages created during a `callTransparently` call.
345
337
  *
346
- * @type {CSN.Message[]}
338
+ * @type {CompileMessage[]}
347
339
  */
348
340
  let messages = options.messages || [];
341
+ /**
342
+ * Whether an error was emitted in the module. Also includes reclassified errors.
343
+ * @type {boolean}
344
+ */
349
345
  let hasNewError = false;
346
+
347
+ reclassifyMessagesForModule();
348
+
350
349
  return {
351
350
  message, error, warning, info, debug, messages,
352
- throwWithError: (throwOnlyWithNew ? throwWithError : throwWithAnyError),
351
+ throwWithError, throwWithAnyError,
353
352
  callTransparently, moduleName,
354
353
  };
355
354
 
@@ -361,22 +360,23 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
361
360
  texts = { std: textOrArguments };
362
361
  textOrArguments = {};
363
362
  }
364
- if (id) {
365
- if (options.testMode && !options.$recompile)
366
- _check$Consistency( id, moduleName, severity, texts, options )
367
- severity = reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable );
368
- }
369
363
 
370
364
  const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);
371
365
  const text = messageText( texts || centralMessageTexts[id], textOrArguments );
372
366
 
373
- /** @type {CSN.Message} */
367
+ /** @type {CompileMessage} */
374
368
  const msg = new CompileMessage( fileLocation, text, severity, id, semanticLocation, moduleName );
375
369
  if (options.internalMsg)
376
370
  msg.error = new Error( 'stack' );
377
371
  if (definition)
378
372
  msg.$location.address = { definition };
379
373
 
374
+ if (id) {
375
+ if (options.testMode && !options.$recompile)
376
+ _check$Consistency( id, moduleName, severity, texts, options )
377
+ msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
378
+ }
379
+
380
380
  messages.push( msg );
381
381
  hasNewError = hasNewError || msg.severity === 'Error' &&
382
382
  !(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
@@ -406,7 +406,7 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
406
406
 
407
407
  if (isShortSignature) {
408
408
  if (texts)
409
- throw new Error('No "texts" argument expected because text was already provided as third argument.');
409
+ throw new CompilerAssertion('No "texts" argument expected because text was already provided as third argument.');
410
410
  } else {
411
411
  if (textArguments !== undefined && typeof textArguments !== 'object')
412
412
  _expectedType('textArguments', textArguments, 'object')
@@ -415,7 +415,7 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
415
415
  }
416
416
 
417
417
  function _expectedType(field, value, type) {
418
- throw new Error(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
418
+ throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
419
419
  }
420
420
  }
421
421
 
@@ -470,9 +470,9 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
470
470
  */
471
471
  function message(id, location, textArguments = null, texts = null) {
472
472
  if (!id)
473
- throw new Error('A message id is missing!');
473
+ throw new CompilerAssertion('A message id is missing!');
474
474
  if (!centralMessages[id])
475
- throw new Error(`Message id '${ id }' is missing an entry in the central message register!`);
475
+ throw new CompilerAssertion(`Message id '${ id }' is missing an entry in the central message register!`);
476
476
  return _message(id, location, textArguments, null, texts);
477
477
  }
478
478
 
@@ -524,12 +524,31 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
524
524
  function throwWithAnyError() {
525
525
  if (!messages || !messages.length)
526
526
  return;
527
- reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
528
527
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
529
528
  if (hasError( messages, moduleName ))
530
529
  throw new CompilationError( messages, options.attachValidNames && model );
531
530
  }
532
531
 
532
+ /**
533
+ * Reclassifies all messages according to the current module.
534
+ * This is required because if throwWithError() throws and the message's
535
+ * severities has `errorFor` set, then the message may still appear to be a warning.
536
+ */
537
+ function reclassifyMessagesForModule() {
538
+ for (const msg of messages) {
539
+ if (msg.messageId && msg.severity !== 'Error') {
540
+ const severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable);
541
+ if (severity !== msg.severity) {
542
+ msg.severity = severity;
543
+ // Re-set the module regardless of severity, since we reclassified it.
544
+ Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
545
+ hasNewError = hasNewError || severity === 'Error' &&
546
+ !(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
547
+ }
548
+ }
549
+ }
550
+ }
551
+
533
552
  /**
534
553
  * Collects all messages during the call of the callback function instead of
535
554
  * storing them in the model. Returns the collected messages.
@@ -537,7 +556,7 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
537
556
  *
538
557
  * @param {Function} callback
539
558
  * @param {...any} args
540
- * @returns {CSN.Message[]}
559
+ * @returns {CompileMessage[]}
541
560
  */
542
561
  function callTransparently(callback, ...args) {
543
562
  const backup = messages;
@@ -563,9 +582,25 @@ function _check$Init( options ) {
563
582
  }
564
583
  }
565
584
 
585
+ /**
586
+ * Check the consistency of the given message and run some basic lint checks. These include:
587
+ *
588
+ * - Long message IDs must be listed centrally.
589
+ * - Messages with the same ID must have the same severity (in a module).
590
+ * - Messages with the same ID must have the same message texts.
591
+ * This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
592
+ * use the same ID for different meanings, i.e. texts.
593
+ *
594
+ * @param {string} id
595
+ * @param {string} moduleName
596
+ * @param {string} severity
597
+ * @param {string|object} texts
598
+ * @param {CSN.Options} options
599
+ * @private
600
+ */
566
601
  function _check$Consistency( id, moduleName, severity, texts, options ) {
567
602
  if (id.length > 30 && !centralMessages[id])
568
- throw new Error( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
603
+ throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
569
604
  if (!options.severities)
570
605
  _check$Severities( id, moduleName || '?', severity );
571
606
  for (const [variant, text] of
@@ -573,6 +608,16 @@ function _check$Consistency( id, moduleName, severity, texts, options ) {
573
608
  _check$Texts( id, variant, text );
574
609
  }
575
610
 
611
+ /**
612
+ * Check the consistency of the message severity for the given message ID.
613
+ * Messages with the same ID must have the same severity (in a module).
614
+ * Non-downgradable errors must never be called with a lower severity.
615
+ *
616
+ * @param {string} id
617
+ * @param {string} moduleName
618
+ * @param {string} severity
619
+ * @private
620
+ */
576
621
  function _check$Severities( id, moduleName, severity ) {
577
622
  if (!severity) // if just used message(), we are automatically consistent
578
623
  return;
@@ -582,23 +627,34 @@ function _check$Severities( id, moduleName, severity ) {
582
627
  if (!expected)
583
628
  test$severities[id] = severity;
584
629
  else if (expected !== severity)
585
- throw new Error( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
630
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
586
631
  return;
587
632
  }
588
633
  // now try whether the message could be something less than an Error in the module due to user wishes
589
- const user = reclassifiedSeverity( id, null, { [id]: 'Info' }, moduleName, false );
590
- if (user === 'Error') { // always an error in module
634
+ if (!isDowngradable( id, moduleName )) { // always an error in module
591
635
  if (severity !== 'Error')
592
- throw new Error( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
636
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
593
637
  }
594
638
  else if (spec.severity === 'Error') {
595
- throw new Error( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
639
+ throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
596
640
  }
597
641
  else if (spec.severity !== severity) {
598
- throw new Error( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
642
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
599
643
  }
600
644
  }
601
645
 
646
+ /**
647
+ * Check the consistency of the message text for the given message ID.
648
+ *
649
+ * Messages with the same ID must have the same message texts.
650
+ * This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
651
+ * use the same ID for different meanings, i.e. texts.
652
+ *
653
+ * @param {string} id
654
+ * @param {string} prop
655
+ * @param {string} value
656
+ * @private
657
+ */
602
658
  function _check$Texts( id, prop, value ) {
603
659
  if (!test$texts[id])
604
660
  test$texts[id] = Object.create(null);
@@ -606,7 +662,7 @@ function _check$Texts( id, prop, value ) {
606
662
  if (!expected)
607
663
  test$texts[id][prop] = value;
608
664
  else if (expected !== value)
609
- throw new Error( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
665
+ throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
610
666
  }
611
667
 
612
668
  const quote = { // could be an option in the future
@@ -649,6 +705,7 @@ const paramsTransform = {
649
705
  expecting: transformManyWith( tokenSymbol ),
650
706
  // msg: m => m,
651
707
  $reviewed: ignoreTextTransform,
708
+ version: quote.meta,
652
709
  };
653
710
 
654
711
  function ignoreTextTransform() {
@@ -803,7 +860,7 @@ function weakLocation( loc ) {
803
860
  * Example:
804
861
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
805
862
  *
806
- * @param {CSN.Message} err
863
+ * @param {CompileMessage} err
807
864
  * @param {boolean} [normalizeFilename]
808
865
  * @param {boolean} [noMessageId]
809
866
  * @param {boolean} [noHome]
@@ -813,7 +870,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
813
870
  return (err.$location && err.$location.file
814
871
  ? locationString( err.$location, normalizeFilename ) + ': '
815
872
  : '') +
816
- (err.severity||'Error') +
873
+ (err.severity || 'Error') +
817
874
  // TODO: use [message-id]
818
875
  (err.messageId && !noMessageId ? ' ' + err.messageId + ': ' : ': ') +
819
876
  err.message +
@@ -825,7 +882,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
825
882
  * Return message hash which is either the message string without the file location,
826
883
  * or the full message string if no semantic location is provided.
827
884
  *
828
- * @param {CSN.Message} msg
885
+ * @param {CompileMessage} msg
829
886
  * @returns {string} can be used to uniquely identify a message
830
887
  */
831
888
  function messageHash(msg) {
@@ -851,7 +908,7 @@ function messageHash(msg) {
851
908
  * <source>.cds:3:11, at entity:“E”/element:“e”
852
909
  * ```
853
910
  *
854
- * @param {CSN.Message} err
911
+ * @param {CompileMessage} err
855
912
  * @param {object} [config = {}]
856
913
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
857
914
  * @param {boolean} [config.noMessageId]
@@ -899,7 +956,7 @@ function messageStringMultiline( err, config = {} ) {
899
956
  *
900
957
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
901
958
  * from `lib/utils/file.js`
902
- * @param {CSN.Message} err Error object containing all details like line, message, etc.
959
+ * @param {CompileMessage} err Error object containing all details like line, message, etc.
903
960
  * @param {object} [config = {}]
904
961
  * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
905
962
  * coloring will be used. If 'auto', we will decide based on certain factors such
@@ -978,8 +1035,8 @@ function messageContext(sourceLines, err, config) {
978
1035
  * larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
979
1036
  * are considered larger than messages with a location.
980
1037
  *
981
- * @param {CSN.Message} a
982
- * @param {CSN.Message} b
1038
+ * @param {CompileMessage} a
1039
+ * @param {CompileMessage} b
983
1040
  */
984
1041
  function compareMessage( a, b ) {
985
1042
  const aFile = a.$location && a.$location.file;
@@ -1013,8 +1070,8 @@ function compareMessage( a, b ) {
1013
1070
  * location and severity, >0 if `a` is larger than `b`, and <0 if `a` is smaller
1014
1071
  * than `b`. See `compareSeverities()` for how severities are compared.
1015
1072
  *
1016
- * @param {CSN.Message} a
1017
- * @param {CSN.Message} b
1073
+ * @param {CompileMessage} a
1074
+ * @param {CompileMessage} b
1018
1075
  */
1019
1076
  function compareMessageSeverityAware( a, b ) {
1020
1077
  const c = compareSeverities(a.severity, b.severity);
@@ -1026,7 +1083,7 @@ function compareMessageSeverityAware( a, b ) {
1026
1083
  * Messages without semantic locations are considered smaller (for syntax errors)
1027
1084
  * and (currently - should not happen in v2) larger for other messages.
1028
1085
  *
1029
- * @param {CSN.Message} msg
1086
+ * @param {CompileMessage} msg
1030
1087
  */
1031
1088
  function homeSortName( { home, messageId } ) {
1032
1089
  return (!home)
@@ -1045,7 +1102,7 @@ function homeSortName( { home, messageId } ) {
1045
1102
  * A message is more precise if it is contained in the other or if
1046
1103
  * the first does not have an endLine/endCol.
1047
1104
  *
1048
- * @param {CSN.Message[]} messages
1105
+ * @param {CompileMessage[]} messages
1049
1106
  */
1050
1107
  function deduplicateMessages( messages ) {
1051
1108
  const seen = new Map();
@@ -1395,6 +1452,7 @@ function _quoted( name ) {
1395
1452
 
1396
1453
  /**
1397
1454
  * Get the explanation string for the given message-id.
1455
+ * Ensure to have called hasMessageExplanation() before.
1398
1456
  *
1399
1457
  * @param {string} messageId
1400
1458
  * @returns {string}
@@ -1402,18 +1460,22 @@ function _quoted( name ) {
1402
1460
  * @see hasMessageExplanation()
1403
1461
  */
1404
1462
  function explainMessage(messageId) {
1463
+ messageId = oldMessageIds[messageId] || messageId;
1405
1464
  const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);
1406
1465
  return fs.readFileSync(filename, 'utf8');
1407
1466
  }
1408
1467
 
1409
1468
  /**
1410
1469
  * Returns true if the given message has an explanation file.
1470
+ * Takes into account changed message ids, i.e. looks up if the new
1471
+ * message id has an explanation.
1411
1472
  *
1412
1473
  * @param {string} messageId
1413
1474
  * @returns {boolean}
1414
1475
  */
1415
1476
  function hasMessageExplanation(messageId) {
1416
- return messageId && _messageIdsWithExplanation.includes(messageId);
1477
+ const id = oldMessageIds[messageId] || messageId || false;
1478
+ return id && _messageIdsWithExplanation.includes(id);
1417
1479
  }
1418
1480
 
1419
1481
  /**
package/lib/base/model.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { forEach } = require("../utils/objectUtils");
4
+
3
5
  const queryOps = {
4
6
  query: 'select', // TODO: rename to SELECT
5
7
  union: 'union',
@@ -20,7 +22,6 @@ const queryOps = {
20
22
  const availableBetaFlags = {
21
23
  // enabled by --beta-mode
22
24
  toRename: true,
23
- addTextsLanguageAssoc: true,
24
25
  assocsWithParams: true,
25
26
  hanaAssocRealCardinality: true,
26
27
  mapAssocToJoinCardinality: true,
@@ -58,16 +59,56 @@ function isBetaEnabled( options, feature ) {
58
59
  * Please do not move this function to the "option processor" code.
59
60
  *
60
61
  * @param {object} options Options
61
- * @param {string} [feature] Feature to check for
62
+ * @param {string|null} [feature] Feature to check for
62
63
  * @returns {boolean}
63
64
  */
64
65
  function isDeprecatedEnabled( options, feature = null ) {
65
66
  const { deprecated } = options;
66
67
  if (!feature)
67
68
  return !!deprecated;
69
+
68
70
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
69
71
  }
70
72
 
73
+ const oldDeprecatedFlags_v2 = [
74
+ 'createLocalizedViews',
75
+ 'downgradableErrors',
76
+ 'generatedEntityNameWithUnderscore',
77
+ 'longAutoexposed',
78
+ 'noElementsExpansion',
79
+ 'noInheritedAutoexposeViaComposition',
80
+ 'noScopedRedirections',
81
+ 'oldVirtualNotNullPropagation',
82
+ 'parensAsStrings',
83
+ 'projectionAsQuery',
84
+ 'redirectInSubQueries',
85
+ 'renderVirtualElements',
86
+ 'shortAutoexposed',
87
+ 'unmanagedUpInComponent',
88
+ 'v1KeysForTemporal',
89
+ ];
90
+
91
+ /**
92
+ * In cds-compiler v3, we removed old v2 deprecated flags. That can lead to silent
93
+ * errors such as entity/view names changing. To ensure that the user is forced
94
+ * to change their code, emit an error if one of such removed flags was used.
95
+ *
96
+ * @param {CSN.Options} options
97
+ * @param error Error message function returned by makeMessageFunctions().
98
+ */
99
+ function checkRemovedDeprecatedFlags( options, { error } ) {
100
+ // Assume that we emitted these errors once if a message with this ID was found.
101
+ if (!options.deprecated || options.messages?.some(m => m.messageId === 'api-invalid-deprecated'))
102
+ return;
103
+
104
+ forEach(options.deprecated, (key, val) => {
105
+ if (val && oldDeprecatedFlags_v2.includes(key)) {
106
+ error('api-invalid-deprecated', null, { name: key },
107
+ 'Deprecated flag $(NAME) has been removed in CDS compiler v3');
108
+ }
109
+ });
110
+ }
111
+
71
112
  // Apply function `callback` to all artifacts in dictionary
72
113
  // `model.definitions`. See function `forEachGeneric` for details.
73
114
  function forEachDefinition( model, callback ) {
@@ -129,6 +170,7 @@ module.exports = {
129
170
  isBetaEnabled,
130
171
  availableBetaFlags,
131
172
  isDeprecatedEnabled,
173
+ checkRemovedDeprecatedFlags,
132
174
  queryOps,
133
175
  forEachDefinition,
134
176
  forEachMember,
@@ -339,7 +339,11 @@ function createOptionProcessor() {
339
339
  // No more options after '--'
340
340
  seenDashDash = true;
341
341
  }
342
+ else if (!seenDashDash && arg.startsWith('--')) {
343
+ i += processOption(i);
344
+ }
342
345
  else if (!seenDashDash && arg.startsWith('-')) {
346
+ splitSingleLetterOption(argv, i); // `-ab` -> `-a -b`
343
347
  i += processOption(i);
344
348
  }
345
349
  else {
@@ -640,6 +644,21 @@ function createOptionProcessor() {
640
644
  }
641
645
  }
642
646
 
647
+ /**
648
+ * Splits `-abc` into `-a -b -c`. Does this in-place on argv.
649
+ *
650
+ * @param {string[]} argv Argument array
651
+ * @param {number} i Current option index.
652
+ */
653
+ function splitSingleLetterOption(argv, i) {
654
+ const arg = argv[i];
655
+ if (arg.length > 2) { // must be at least `-ab`.
656
+ const rest = argv.slice(i + 1);
657
+ argv.length = i; // trim array
658
+ argv.push(...arg.split('').slice(1).map(a => `-${a}`), ...rest);
659
+ }
660
+ }
661
+
643
662
  /**
644
663
  * Return a camelCase name "fooBar" for a long option "--foo-bar"
645
664
  */
@@ -16,8 +16,8 @@ function checkActionOrFunction(art, artName, prop, path) {
16
16
  if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
17
17
  return;
18
18
 
19
- // const isMultiSchema = this.options.toOdata.odataFormat === 'structured' &&
20
- // (this.options.toOdata.odataProxies || this.options.toOdata.odataXServiceRefs);
19
+ // const isMultiSchema = this.options.odataFormat === 'structured' &&
20
+ // (this.options.odataProxies || this.options.odataXServiceRefs);
21
21
 
22
22
  const serviceName = this.csnUtils.getServiceName(artName);
23
23
  if (!serviceName)
@@ -85,9 +85,11 @@ function checkActionOrFunction(art, artName, prop, path) {
85
85
  * @param {string} actKind 'action' or 'function'
86
86
  */
87
87
  function checkReturns(returns, currPath, actKind) {
88
- const finalReturnType = returns.type ? this.csnUtils.getFinalBaseType(returns.type) : returns;
88
+ const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
89
+ if (!finalReturnType)
90
+ return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
89
91
 
90
- if (this.csnUtils.isAssocOrComposition(finalReturnType)) {
92
+ if (this.csnUtils.isAssocOrComposition(finalReturnType.type)) {
91
93
  this.error(null, currPath, { '#': actKind },
92
94
  {
93
95
  std: 'An association is not allowed as this artifact\'s return type', // Not used
@@ -98,7 +100,7 @@ function checkActionOrFunction(art, artName, prop, path) {
98
100
 
99
101
  if (finalReturnType.items) // check array return type
100
102
  checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);
101
- else // check if return type is user definited from the current service
103
+ else // check if return type is user defined from the current service
102
104
  checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath);
103
105
  }
104
106