@sap/cds-compiler 2.13.8 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +109 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +89 -14
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +33 -15
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +298 -225
  28. package/lib/edm/edmPreprocessor.js +486 -415
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +90 -16
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +94 -64
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +5 -5
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +1 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -30,6 +30,9 @@
30
30
 
31
31
  'use strict';
32
32
 
33
+ const { CompilerAssertion } = require("./error");
34
+ const { createDict } = require("../utils/objectUtils");
35
+
33
36
  /**
34
37
  * Central register of messages and their configuration.
35
38
  * Group by id-category.
@@ -62,7 +65,7 @@ const centralMessages = {
62
65
  'assoc-as-type': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: allow more, but not all
63
66
  'def-unexpected-paramview-assoc': { severity: 'Error' },
64
67
  'def-unexpected-calcview-assoc': { severity: 'Error' },
65
-
68
+ 'chained-array-of': { severity: 'Error' },
66
69
  'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
67
70
  'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },
68
71
 
@@ -77,6 +80,7 @@ const centralMessages = {
77
80
  'expected-type': { severity: 'Error' },
78
81
  'ref-sloppy-type': { severity: 'Error' },
79
82
  'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
83
+ 'type-expected-builtin': { severity: 'Error', configurableFor: true },
80
84
  'expected-actionparam-type': { severity: 'Error' },
81
85
  'ref-sloppy-actionparam-type': { severity: 'Error' },
82
86
  'expected-event-type': { severity: 'Error' },
@@ -97,6 +101,7 @@ const centralMessages = {
97
101
  'query-unexpected-assoc-hdbcds': { severity: 'Error' },
98
102
  'query-unexpected-structure-hdbcds': { severity: 'Error' },
99
103
  'query-ignoring-param-nullability': { severity: 'Info' },
104
+ 'query-expected-identifier': { severity: 'Error' },
100
105
 
101
106
  'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
102
107
  'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
@@ -142,6 +147,8 @@ const centralMessages = {
142
147
  'syntax-invalid-escape': { severity: 'Error' },
143
148
  'syntax-missing-escape': { severity: 'Error' },
144
149
 
150
+ 'syntax-expected-integer': { severity: 'Error' },
151
+
145
152
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
146
153
 
147
154
  'def-missing-element': { severity: 'Error' },
@@ -151,22 +158,46 @@ const centralMessages = {
151
158
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
152
159
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
153
160
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
161
+ 'odata-spec-violation-id': { severity: 'Error' },
154
162
  'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
163
+ 'odata-spec-violation-type-unknown': { severity: 'Warning' },
155
164
  'odata-spec-violation-no-key': { severity: 'Warning' },
156
165
  'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
157
166
  'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
158
167
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
159
168
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
160
- 'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
161
169
  };
162
170
 
171
+ // Old/Deprecated message IDs that we only still use for backwards-compatibility.
172
+ // We keep them in a separate array for easier access. No need to go through all
173
+ // existing messages and search for the old one in `oldNames` property.
174
+ // The keys will be added to `oldNames` of the new message, which is used for reclassification.
175
+ const oldMessageIds = createDict({
176
+ 'old-anno-duplicate': 'anno-duplicate', // Example
177
+ });
178
+
179
+ // Set up the old-to-new message ID mapping in the message registry.
180
+ for (const oldName in oldMessageIds) {
181
+ const newName = oldMessageIds[oldName];
182
+ if (centralMessages[oldName])
183
+ throw new CompilerAssertion(`Mapping from ${oldName} not possible: ID is still used in message registry.`);
184
+ if (!centralMessages[newName])
185
+ throw new CompilerAssertion(`Mapping from ${oldName} to new message ID ${newName} does not exist!`);
186
+
187
+ if (!centralMessages[newName].oldNames)
188
+ centralMessages[newName].oldNames = [ oldName ];
189
+ else
190
+ centralMessages[newName].oldNames.push(oldName);
191
+ }
192
+
193
+
163
194
  // For messageIds, where no text has been provided via code (central def)
164
195
  const centralMessageTexts = {
165
196
  'anno-duplicate': 'Duplicate assignment with $(ANNO)',
166
197
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
167
198
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
168
199
  'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
169
- 'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
200
+ 'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
170
201
  'syntax-csn-expected-object': 'Expected object for property $(PROP)',
171
202
  'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
172
203
  'syntax-csn-expected-natnum': 'Expected non-negative number for property $(PROP)',
@@ -204,6 +235,16 @@ const centralMessageTexts = {
204
235
  std: 'Missing escape. Replace $(CODE) with $(NEWCODE)',
205
236
  placeholder: 'Placeholders are not supported. Replace $(CODE) with $(NEWCODE)',
206
237
  },
238
+ 'syntax-expected-integer': {
239
+ std: 'A safe integer is expected here',
240
+ normal: 'An integer number is expected here',
241
+ unsafe: 'The provided integer is too large',
242
+ },
243
+ 'syntax-duplicate-argument': {
244
+ std: 'Unexpected argument $(CODE)',
245
+ unknown: 'Unknown argument $(CODE)',
246
+ duplicate: 'Duplicate argument $(CODE)',
247
+ },
207
248
  'ref-undefined-def': {
208
249
  std: 'Artifact $(ART) has not been found',
209
250
  // TODO: proposal 'No definition of $(NAME) found',
@@ -213,7 +254,8 @@ const centralMessageTexts = {
213
254
  // TODO: proposal 'No definition found for $(NAME)',
214
255
  'ref-undefined-element': {
215
256
  std: 'Element $(ART) has not been found',
216
- element: 'Artifact $(ART) has no element $(MEMBER)'
257
+ element: 'Artifact $(ART) has no element $(MEMBER)',
258
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect'
217
259
  },
218
260
  'ref-unknown-var': {
219
261
  std: 'Replacement $(ID) not found'
@@ -231,6 +273,19 @@ const centralMessageTexts = {
231
273
  param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
232
274
  select: 'Unexpected $(KEYWORD) for type references in queries',
233
275
  },
276
+
277
+ 'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
278
+ 'type-ignoring-argument': 'Too many arguments for type $(ART)',
279
+ 'type-unexpected-argument': {
280
+ std: 'Too many arguments for type $(ART)',
281
+ type: 'Unexpected argument $(PROP) for type $(ART) with base type $(TYPE)',
282
+ builtin: 'Unexpected argument $(PROP) for type $(ART)',
283
+ 'non-scalar': 'Only scalar types can have arguments',
284
+ max: 'Expecting argument $(PROP) for type $(TYPE) to not exceed $(NUMBER)',
285
+ min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',
286
+ 'incorrect-type': 'Expected $(NAMES) for argument $(PROP), but found $(CODE)',
287
+ },
288
+
234
289
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
235
290
  'anno-undefined-def': 'Artifact $(ART) has not been found',
236
291
  'anno-undefined-art': 'No artifact has been found with name $(NAME)',
@@ -296,6 +351,10 @@ const centralMessageTexts = {
296
351
  std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
297
352
  sql: 'Ignoring nullability constraint on parameter when generating SQL view'
298
353
  },
354
+ 'query-expected-identifier': {
355
+ std: 'Expected identifier for select item',
356
+ assoc: 'Expected identifier as the association\'s name',
357
+ },
299
358
 
300
359
  'ref-sloppy-type': 'A type or an element is expected here',
301
360
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -312,11 +371,16 @@ const centralMessageTexts = {
312
371
  'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
313
372
 
314
373
  // OData version dependent messages
315
- 'odata-spec-violation-array': 'Unexpected array type for $(API)',
316
- 'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for $(API)',
317
- 'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for $(API)',
318
- 'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
319
- 'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
374
+ 'odata-spec-violation-array': 'Unexpected array type for OData $(VERSION)',
375
+ 'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for OData $(VERSION)',
376
+ 'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for OData $(VERSION)',
377
+ 'odata-spec-violation-assoc': 'Unexpected association in structured type for OData $(VERSION)',
378
+ 'odata-spec-violation-constraints': 'Partial referential constraints produced for OData $(VERSION)',
379
+ 'odata-spec-violation-id': {
380
+ std: 'Expected EDM name $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
381
+ 'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
382
+ 'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
383
+ },
320
384
  // version independent messages
321
385
  'odata-spec-violation-key-array': {
322
386
  std: 'Unexpected array type for element $(NAME)',
@@ -331,21 +395,30 @@ const centralMessageTexts = {
331
395
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
332
396
  },
333
397
  'odata-spec-violation-no-key': 'Expected entity to have a primary key',
334
- 'odata-spec-violation-type': 'Expected element to have a type',
398
+ 'odata-spec-violation-type-unknown': 'Unknown Edm Type $(TYPE)',
399
+ 'odata-spec-violation-type': {
400
+ std: 'Expected element to have a type',
401
+ incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
402
+ facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)',
403
+ },
335
404
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
336
- 'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
405
+ 'odata-spec-violation-namespace': {
406
+ std: 'Expected service name not to be one of the reserved names $(NAMES)',
407
+ length: 'Expected service name not to exceed 511 characters',
408
+ },
337
409
  // Other odata/edm errors
338
410
  'odata-definition-exists': {
339
411
  std: 'Entity can\'t be created due to name collision with existing definition $(NAME)',
340
412
  proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
341
- }
413
+ },
414
+ 'odata-navigation': 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)'
342
415
  }
343
416
 
344
417
  /**
345
418
  * Configuration for a message in the central message register.
346
419
  *
347
420
  * @typedef {object} MessageConfig
348
- * @property {CSN.MessageSeverity} severity Default severity for the message.
421
+ * @property {MessageSeverity} severity Default severity for the message.
349
422
  * @property {string[]|'deprecated'|true} [configurableFor]
350
423
  * Whether the error can be reclassified to a warning or lower.
351
424
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
@@ -356,6 +429,8 @@ const centralMessageTexts = {
356
429
  * @property {boolean} [throughMessageCall]
357
430
  * If set, it means that a message-id was added to the registry in test-mode through a `message.<severity>()`
358
431
  * call. Used for ensuring that all calls with the same message-id have the same severity.
432
+ * @property {string[]} [oldNames] Aliases for the message id. Used for reclassification as well as "explain" messages.
433
+ * Don't set this property directly! Append to object oldMessageIds instead!
359
434
  */
360
435
 
361
- module.exports = { centralMessages, centralMessageTexts };
436
+ module.exports = { centralMessages, centralMessageTexts, oldMessageIds };
@@ -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,7 +25,8 @@ 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
+ * Returns true if at least one of the given messages is of severity "Error".
29
+ *
28
30
  * @param {CSN.Message[]} messages
29
31
  * @returns {boolean}
30
32
  */
@@ -34,7 +36,8 @@ 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
42
  * @param {CSN.Message[]} messages
40
43
  * @param {string} moduleName
@@ -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 );
@@ -329,15 +320,13 @@ function createMessageFunctions( options, moduleName, model = null ) {
329
320
  * @param {object} model
330
321
  * @param {CSN.Options} [options]
331
322
  * @param {string} [moduleName]
332
- * @param {boolean} [throwOnlyWithNew=false] behave like createMessageFunctions
333
323
  */
334
- function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNew = false ) {
324
+ function makeMessageFunction( model, options, moduleName = null ) {
335
325
  // ensure message consistency during runtime with --test-mode
336
326
  if (options.testMode)
337
327
  _check$Init( options );
338
328
 
339
329
  const hasMessageArray = !!options.messages;
340
- const severities = options.severities || {};
341
330
  const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );
342
331
  /**
343
332
  * Array of collected compiler messages. Only use it for debugging. Will not
@@ -346,10 +335,17 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
346
335
  * @type {CSN.Message[]}
347
336
  */
348
337
  let messages = options.messages || [];
338
+ /**
339
+ * Whether an error was emitted in the module. Also includes reclassified errors.
340
+ * @type {boolean}
341
+ */
349
342
  let hasNewError = false;
343
+
344
+ reclassifyMessagesForModule();
345
+
350
346
  return {
351
347
  message, error, warning, info, debug, messages,
352
- throwWithError: (throwOnlyWithNew ? throwWithError : throwWithAnyError),
348
+ throwWithError, throwWithAnyError,
353
349
  callTransparently, moduleName,
354
350
  };
355
351
 
@@ -361,11 +357,6 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
361
357
  texts = { std: textOrArguments };
362
358
  textOrArguments = {};
363
359
  }
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
360
 
370
361
  const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);
371
362
  const text = messageText( texts || centralMessageTexts[id], textOrArguments );
@@ -377,6 +368,12 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
377
368
  if (definition)
378
369
  msg.$location.address = { definition };
379
370
 
371
+ if (id) {
372
+ if (options.testMode && !options.$recompile)
373
+ _check$Consistency( id, moduleName, severity, texts, options )
374
+ msg.severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable );
375
+ }
376
+
380
377
  messages.push( msg );
381
378
  hasNewError = hasNewError || msg.severity === 'Error' &&
382
379
  !(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
@@ -406,7 +403,7 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
406
403
 
407
404
  if (isShortSignature) {
408
405
  if (texts)
409
- throw new Error('No "texts" argument expected because text was already provided as third argument.');
406
+ throw new CompilerAssertion('No "texts" argument expected because text was already provided as third argument.');
410
407
  } else {
411
408
  if (textArguments !== undefined && typeof textArguments !== 'object')
412
409
  _expectedType('textArguments', textArguments, 'object')
@@ -415,7 +412,7 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
415
412
  }
416
413
 
417
414
  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?`);
415
+ throw new CompilerAssertion(`Invalid argument type for ${ field }! Expected ${ type } but got ${ typeof value }. Do you use the old function signature?`);
419
416
  }
420
417
  }
421
418
 
@@ -470,9 +467,9 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
470
467
  */
471
468
  function message(id, location, textArguments = null, texts = null) {
472
469
  if (!id)
473
- throw new Error('A message id is missing!');
470
+ throw new CompilerAssertion('A message id is missing!');
474
471
  if (!centralMessages[id])
475
- throw new Error(`Message id '${ id }' is missing an entry in the central message register!`);
472
+ throw new CompilerAssertion(`Message id '${ id }' is missing an entry in the central message register!`);
476
473
  return _message(id, location, textArguments, null, texts);
477
474
  }
478
475
 
@@ -524,12 +521,31 @@ function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNe
524
521
  function throwWithAnyError() {
525
522
  if (!messages || !messages.length)
526
523
  return;
527
- reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
528
524
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
529
525
  if (hasError( messages, moduleName ))
530
526
  throw new CompilationError( messages, options.attachValidNames && model );
531
527
  }
532
528
 
529
+ /**
530
+ * Reclassifies all messages according to the current module.
531
+ * This is required because if throwWithError() throws and the message's
532
+ * severities has `errorFor` set, then the message may still appear to be a warning.
533
+ */
534
+ function reclassifyMessagesForModule() {
535
+ for (const msg of messages) {
536
+ if (msg.messageId && msg.severity !== 'Error') {
537
+ const severity = reclassifiedSeverity(msg, options, moduleName, deprecatedDowngradable);
538
+ if (severity !== msg.severity) {
539
+ msg.severity = severity;
540
+ // Re-set the module regardless of severity, since we reclassified it.
541
+ Object.defineProperty( msg, '$module', { value: moduleName, configurable: true } );
542
+ hasNewError = hasNewError || severity === 'Error' &&
543
+ !(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
544
+ }
545
+ }
546
+ }
547
+ }
548
+
533
549
  /**
534
550
  * Collects all messages during the call of the callback function instead of
535
551
  * storing them in the model. Returns the collected messages.
@@ -565,7 +581,7 @@ function _check$Init( options ) {
565
581
 
566
582
  function _check$Consistency( id, moduleName, severity, texts, options ) {
567
583
  if (id.length > 30 && !centralMessages[id])
568
- throw new Error( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
584
+ throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
569
585
  if (!options.severities)
570
586
  _check$Severities( id, moduleName || '?', severity );
571
587
  for (const [variant, text] of
@@ -582,20 +598,19 @@ function _check$Severities( id, moduleName, severity ) {
582
598
  if (!expected)
583
599
  test$severities[id] = severity;
584
600
  else if (expected !== severity)
585
- throw new Error( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
601
+ throw new CompilerAssertion( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
586
602
  return;
587
603
  }
588
604
  // 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
605
+ if (!isDowngradable( id, moduleName )) { // always an error in module
591
606
  if (severity !== 'Error')
592
- throw new Error( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
607
+ throw new CompilerAssertion( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
593
608
  }
594
609
  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}"` );
610
+ throw new CompilerAssertion( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
596
611
  }
597
612
  else if (spec.severity !== severity) {
598
- throw new Error( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
613
+ throw new CompilerAssertion( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
599
614
  }
600
615
  }
601
616
 
@@ -606,7 +621,7 @@ function _check$Texts( id, prop, value ) {
606
621
  if (!expected)
607
622
  test$texts[id][prop] = value;
608
623
  else if (expected !== value)
609
- throw new Error( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
624
+ throw new CompilerAssertion( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
610
625
  }
611
626
 
612
627
  const quote = { // could be an option in the future
@@ -649,6 +664,7 @@ const paramsTransform = {
649
664
  expecting: transformManyWith( tokenSymbol ),
650
665
  // msg: m => m,
651
666
  $reviewed: ignoreTextTransform,
667
+ version: quote.meta,
652
668
  };
653
669
 
654
670
  function ignoreTextTransform() {
@@ -813,7 +829,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
813
829
  return (err.$location && err.$location.file
814
830
  ? locationString( err.$location, normalizeFilename ) + ': '
815
831
  : '') +
816
- (err.severity||'Error') +
832
+ (err.severity || 'Error') +
817
833
  // TODO: use [message-id]
818
834
  (err.messageId && !noMessageId ? ' ' + err.messageId + ': ' : ': ') +
819
835
  err.message +
@@ -1395,6 +1411,7 @@ function _quoted( name ) {
1395
1411
 
1396
1412
  /**
1397
1413
  * Get the explanation string for the given message-id.
1414
+ * Ensure to have called hasMessageExplanation() before.
1398
1415
  *
1399
1416
  * @param {string} messageId
1400
1417
  * @returns {string}
@@ -1402,18 +1419,22 @@ function _quoted( name ) {
1402
1419
  * @see hasMessageExplanation()
1403
1420
  */
1404
1421
  function explainMessage(messageId) {
1422
+ messageId = oldMessageIds[messageId] || messageId;
1405
1423
  const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);
1406
1424
  return fs.readFileSync(filename, 'utf8');
1407
1425
  }
1408
1426
 
1409
1427
  /**
1410
1428
  * Returns true if the given message has an explanation file.
1429
+ * Takes into account changed message ids, i.e. looks up if the new
1430
+ * message id has an explanation.
1411
1431
  *
1412
1432
  * @param {string} messageId
1413
1433
  * @returns {boolean}
1414
1434
  */
1415
1435
  function hasMessageExplanation(messageId) {
1416
- return messageId && _messageIdsWithExplanation.includes(messageId);
1436
+ const id = oldMessageIds[messageId] || messageId || false;
1437
+ return id && _messageIdsWithExplanation.includes(id);
1417
1438
  }
1418
1439
 
1419
1440
  /**
@@ -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
  */