@sap/cds-compiler 2.15.4 → 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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  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 +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -45,6 +45,7 @@ const centralMessages = {
45
45
  'anno-definition': { severity: 'Warning' },
46
46
  'anno-duplicate': { severity: 'Error', configurableFor: true }, // does not hurt us
47
47
  'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
48
+ 'anno-unstable-array': { severity: 'Warning' },
48
49
  'anno-invalid-sql-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
49
50
  'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
50
51
  'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
@@ -67,7 +68,7 @@ const centralMessages = {
67
68
  'def-unexpected-calcview-assoc': { severity: 'Error' },
68
69
  'chained-array-of': { severity: 'Error' },
69
70
  'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
70
- 'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },
71
+ 'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
71
72
 
72
73
  'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
73
74
 
@@ -80,6 +81,7 @@ const centralMessages = {
80
81
  'expected-type': { severity: 'Error' },
81
82
  'ref-sloppy-type': { severity: 'Error' },
82
83
  'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
84
+ 'type-ignoring-argument': { severity: 'Error', configurableFor: true },
83
85
  'type-expected-builtin': { severity: 'Error', configurableFor: true },
84
86
  'expected-actionparam-type': { severity: 'Error' },
85
87
  'ref-sloppy-actionparam-type': { severity: 'Error' },
@@ -116,6 +118,7 @@ const centralMessages = {
116
118
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
117
119
  'ref-undefined-param': { severity: 'Error' },
118
120
  'ref-rejected-on': { severity: 'Error' },
121
+ 'ref-expected-element': { severity: 'Error' },
119
122
 
120
123
  'rewrite-key-not-covered-explicit': { severity: 'Error', configurableFor: 'deprecated' },
121
124
  'rewrite-key-not-covered-implicit': { severity: 'Error', configurableFor: 'deprecated' },
@@ -128,14 +131,11 @@ const centralMessages = {
128
131
  'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
129
132
  'service-nested-service': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
130
133
 
131
- 'syntax-anno-after-enum': { severity: 'Error', configurableFor: true }, // does not hurt
132
- 'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
133
- 'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
134
- 'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
135
- 'syntax-csn-expected-length': { severity: 'Error' },
136
- 'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
137
- 'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
138
- 'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
134
+ 'syntax-expected-cardinality': { severity: 'Error' },
135
+ 'syntax-expected-length': { severity: 'Error' },
136
+ 'syntax-expected-translation': { severity: 'Error' },
137
+ 'syntax-required-subproperty': { severity: 'Error' },
138
+ 'syntax-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
139
139
  'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
140
140
  'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
141
141
  'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
@@ -148,6 +148,8 @@ const centralMessages = {
148
148
  'syntax-missing-escape': { severity: 'Error' },
149
149
 
150
150
  'syntax-expected-integer': { severity: 'Error' },
151
+ 'syntax-invalid-masked': { severity: 'Error', configurableFor: true },
152
+ 'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
151
153
 
152
154
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
153
155
 
@@ -193,27 +195,47 @@ for (const oldName in oldMessageIds) {
193
195
 
194
196
  // For messageIds, where no text has been provided via code (central def)
195
197
  const centralMessageTexts = {
198
+ 'api-invalid-option': {
199
+ std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
200
+ magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
201
+ user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
202
+ locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
203
+ 'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
204
+ },
205
+
196
206
  'anno-duplicate': 'Duplicate assignment with $(ANNO)',
207
+ 'anno-duplicate-unrelated-layer': 'Duplicate assignment with $(ANNO)',
208
+ 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
197
209
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
198
210
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
199
211
  'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
200
212
  'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
201
- 'syntax-csn-expected-object': 'Expected object for property $(PROP)',
202
- 'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
203
- 'syntax-csn-expected-natnum': 'Expected non-negative number for property $(PROP)',
204
- 'syntax-csn-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
205
- 'syntax-csn-expected-reference': 'Expected non-empty string or object for property $(PROP)',
206
- 'syntax-csn-expected-term': 'Expected non-empty string or object for property $(PROP)',
207
- 'syntax-anno-after-struct': 'Avoid annotation assignments after structure definitions',
208
- 'syntax-anno-after-enum': 'Avoid annotation assignments after enum definitions',
209
- 'syntax-anno-after-params': 'Avoid annotation assignments after parameters',
213
+
214
+ 'name-duplicate-element': {
215
+ 'std': 'Generated element $(NAME) conflicts with another element',
216
+ 'flatten-element-gen': 'Generated element $(NAME) conflicts with other generated element',
217
+ 'flatten-element-exist': 'Flattened name of structured element conflicts with existing element $(NAME)',
218
+ 'flatten-fkey-gen': 'Duplicate definition of foreign key element $(NAME) for association $(ART)',
219
+ 'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
220
+ },
221
+
222
+ 'syntax-unexpected-ellipsis': {
223
+ std: 'Expected no more than one $(CODE)',
224
+ 'nested-array': 'Unexpected $(CODE) in nested array'
225
+ },
226
+ 'syntax-expected-object': 'Expected object for property $(PROP)',
227
+ 'syntax-expected-column': 'Expected object or string \'*\' for property $(PROP)',
228
+ 'syntax-expected-natnum': 'Expected non-negative number for property $(PROP)',
229
+ 'syntax-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
230
+ 'syntax-expected-reference': 'Expected non-empty string or object for property $(PROP)',
231
+ 'syntax-expected-term': 'Expected non-empty string or object for property $(PROP)',
210
232
  'syntax-dollar-ident': {
211
233
  std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',
212
234
  $tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
213
235
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
214
236
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
215
237
  },
216
- 'syntax-csn-expected-length': {
238
+ 'syntax-expected-length': {
217
239
  std: 'Expected array in $(PROP) to have at least $(N) items',
218
240
  one: 'Expected array in $(PROP) to have at least one item',
219
241
  suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
@@ -245,6 +267,20 @@ const centralMessageTexts = {
245
267
  unknown: 'Unknown argument $(CODE)',
246
268
  duplicate: 'Duplicate argument $(CODE)',
247
269
  },
270
+ 'syntax-invalid-literal': {
271
+ 'std': 'Invalid literal',
272
+ 'uneven-hex': 'A binary literal must have an even number of characters',
273
+ 'invalid-hex': 'A binary literal must only contain characters 0-9, a-f and A-F',
274
+ 'time': 'Expected time\'hh:mm:ss\' where hh, mm and the optional ss are numbers',
275
+ 'date': 'Expected date\'YYYY-MM-DD\' where YYYY, MM and DD are numbers',
276
+ 'timestamp': 'Expected timestamp\'YYYY-MM-DD hh:mm:ss.u…u\' where YYYY, MM, DD, hh, mm, ss and u are numbers (optional 1-7×u)',
277
+ },
278
+ 'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
279
+ 'syntax-unexpected-vocabulary': {
280
+ std: 'Annotations can\'t be defined inside contexts or services',
281
+ service: 'Annotations can\'t be defined inside services',
282
+ context: 'Annotations can\'t be defined inside contexts',
283
+ },
248
284
  'ref-undefined-def': {
249
285
  std: 'Artifact $(ART) has not been found',
250
286
  // TODO: proposal 'No definition of $(NAME) found',
@@ -266,6 +302,10 @@ const centralMessageTexts = {
266
302
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
267
303
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
268
304
  },
305
+ 'ref-expected-element': {
306
+ std: 'Expected element reference',
307
+ magicVar: 'Only elements of magic variable $(ID) can be selected',
308
+ },
269
309
  'type-unexpected-typeof': {
270
310
  std: 'Unexpected $(KEYWORD) for the type reference here',
271
311
  type: 'Unexpected $(KEYWORD) in type of a type definition',
@@ -423,7 +463,7 @@ const centralMessageTexts = {
423
463
  * Whether the error can be reclassified to a warning or lower.
424
464
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
425
465
  * Only has an effect if default severity is 'Error'.
426
- * 'deprecated': severity can only be changed with deprecated.downgradableErrors.
466
+ * 'deprecated': severity can only be changed with deprecated._downgradableErrors.
427
467
  * TODO: Value `true` is temporary. Use an array instead.
428
468
  * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.
429
469
  * @property {boolean} [throughMessageCall]
@@ -27,7 +27,7 @@ let test$texts = null;
27
27
  /**
28
28
  * Returns true if at least one of the given messages is of severity "Error".
29
29
  *
30
- * @param {CSN.Message[]} messages
30
+ * @param {CompileMessage[]} messages
31
31
  * @returns {boolean}
32
32
  */
33
33
  function hasErrors( messages ) {
@@ -39,7 +39,7 @@ function hasErrors( messages ) {
39
39
  * and *cannot* be reclassified to a warning for the given module.
40
40
  * Won't detect already downgraded messages.
41
41
  *
42
- * @param {CSN.Message[]} messages
42
+ * @param {CompileMessage[]} messages
43
43
  * @param {string} moduleName
44
44
  * @returns {boolean}
45
45
  */
@@ -319,20 +319,23 @@ function createMessageFunctions( options, moduleName, model = null ) {
319
319
  * ```
320
320
  * @param {object} model
321
321
  * @param {CSN.Options} [options]
322
- * @param {string} [moduleName]
322
+ * @param {string|null} [moduleName]
323
323
  */
324
324
  function makeMessageFunction( model, options, moduleName = null ) {
325
- // ensure message consistency during runtime with --test-mode
326
- if (options.testMode)
325
+ if (options.testMode) {
326
+ // ensure message consistency during runtime with --test-mode
327
327
  _check$Init( options );
328
+ if (!options.messages)
329
+ throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
330
+ }
328
331
 
329
332
  const hasMessageArray = !!options.messages;
330
- const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );
333
+ const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
331
334
  /**
332
335
  * Array of collected compiler messages. Only use it for debugging. Will not
333
336
  * contain the messages created during a `callTransparently` call.
334
337
  *
335
- * @type {CSN.Message[]}
338
+ * @type {CompileMessage[]}
336
339
  */
337
340
  let messages = options.messages || [];
338
341
  /**
@@ -361,7 +364,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
361
364
  const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);
362
365
  const text = messageText( texts || centralMessageTexts[id], textOrArguments );
363
366
 
364
- /** @type {CSN.Message} */
367
+ /** @type {CompileMessage} */
365
368
  const msg = new CompileMessage( fileLocation, text, severity, id, semanticLocation, moduleName );
366
369
  if (options.internalMsg)
367
370
  msg.error = new Error( 'stack' );
@@ -553,7 +556,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
553
556
  *
554
557
  * @param {Function} callback
555
558
  * @param {...any} args
556
- * @returns {CSN.Message[]}
559
+ * @returns {CompileMessage[]}
557
560
  */
558
561
  function callTransparently(callback, ...args) {
559
562
  const backup = messages;
@@ -579,6 +582,22 @@ function _check$Init( options ) {
579
582
  }
580
583
  }
581
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
+ */
582
601
  function _check$Consistency( id, moduleName, severity, texts, options ) {
583
602
  if (id.length > 30 && !centralMessages[id])
584
603
  throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
@@ -589,6 +608,16 @@ function _check$Consistency( id, moduleName, severity, texts, options ) {
589
608
  _check$Texts( id, variant, text );
590
609
  }
591
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
+ */
592
621
  function _check$Severities( id, moduleName, severity ) {
593
622
  if (!severity) // if just used message(), we are automatically consistent
594
623
  return;
@@ -598,22 +627,34 @@ function _check$Severities( id, moduleName, severity ) {
598
627
  if (!expected)
599
628
  test$severities[id] = severity;
600
629
  else if (expected !== severity)
601
- throw new CompilerAssertion( `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}"` );
602
631
  return;
603
632
  }
604
633
  // now try whether the message could be something less than an Error in the module due to user wishes
605
634
  if (!isDowngradable( id, moduleName )) { // always an error in module
606
635
  if (severity !== 'Error')
607
- throw new CompilerAssertion( `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}"` );
608
637
  }
609
638
  else if (spec.severity === 'Error') {
610
- throw new CompilerAssertion( `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}"` );
611
640
  }
612
641
  else if (spec.severity !== severity) {
613
- throw new CompilerAssertion( `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}"` );
614
643
  }
615
644
  }
616
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
+ */
617
658
  function _check$Texts( id, prop, value ) {
618
659
  if (!test$texts[id])
619
660
  test$texts[id] = Object.create(null);
@@ -621,7 +662,7 @@ function _check$Texts( id, prop, value ) {
621
662
  if (!expected)
622
663
  test$texts[id][prop] = value;
623
664
  else if (expected !== value)
624
- throw new CompilerAssertion( `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}"`);
625
666
  }
626
667
 
627
668
  const quote = { // could be an option in the future
@@ -819,7 +860,7 @@ function weakLocation( loc ) {
819
860
  * Example:
820
861
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
821
862
  *
822
- * @param {CSN.Message} err
863
+ * @param {CompileMessage} err
823
864
  * @param {boolean} [normalizeFilename]
824
865
  * @param {boolean} [noMessageId]
825
866
  * @param {boolean} [noHome]
@@ -841,7 +882,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
841
882
  * Return message hash which is either the message string without the file location,
842
883
  * or the full message string if no semantic location is provided.
843
884
  *
844
- * @param {CSN.Message} msg
885
+ * @param {CompileMessage} msg
845
886
  * @returns {string} can be used to uniquely identify a message
846
887
  */
847
888
  function messageHash(msg) {
@@ -867,7 +908,7 @@ function messageHash(msg) {
867
908
  * <source>.cds:3:11, at entity:“E”/element:“e”
868
909
  * ```
869
910
  *
870
- * @param {CSN.Message} err
911
+ * @param {CompileMessage} err
871
912
  * @param {object} [config = {}]
872
913
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
873
914
  * @param {boolean} [config.noMessageId]
@@ -915,7 +956,7 @@ function messageStringMultiline( err, config = {} ) {
915
956
  *
916
957
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
917
958
  * from `lib/utils/file.js`
918
- * @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.
919
960
  * @param {object} [config = {}]
920
961
  * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
921
962
  * coloring will be used. If 'auto', we will decide based on certain factors such
@@ -994,8 +1035,8 @@ function messageContext(sourceLines, err, config) {
994
1035
  * larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
995
1036
  * are considered larger than messages with a location.
996
1037
  *
997
- * @param {CSN.Message} a
998
- * @param {CSN.Message} b
1038
+ * @param {CompileMessage} a
1039
+ * @param {CompileMessage} b
999
1040
  */
1000
1041
  function compareMessage( a, b ) {
1001
1042
  const aFile = a.$location && a.$location.file;
@@ -1029,8 +1070,8 @@ function compareMessage( a, b ) {
1029
1070
  * location and severity, >0 if `a` is larger than `b`, and <0 if `a` is smaller
1030
1071
  * than `b`. See `compareSeverities()` for how severities are compared.
1031
1072
  *
1032
- * @param {CSN.Message} a
1033
- * @param {CSN.Message} b
1073
+ * @param {CompileMessage} a
1074
+ * @param {CompileMessage} b
1034
1075
  */
1035
1076
  function compareMessageSeverityAware( a, b ) {
1036
1077
  const c = compareSeverities(a.severity, b.severity);
@@ -1042,7 +1083,7 @@ function compareMessageSeverityAware( a, b ) {
1042
1083
  * Messages without semantic locations are considered smaller (for syntax errors)
1043
1084
  * and (currently - should not happen in v2) larger for other messages.
1044
1085
  *
1045
- * @param {CSN.Message} msg
1086
+ * @param {CompileMessage} msg
1046
1087
  */
1047
1088
  function homeSortName( { home, messageId } ) {
1048
1089
  return (!home)
@@ -1061,7 +1102,7 @@ function homeSortName( { home, messageId } ) {
1061
1102
  * A message is more precise if it is contained in the other or if
1062
1103
  * the first does not have an endLine/endCol.
1063
1104
  *
1064
- * @param {CSN.Message[]} messages
1105
+ * @param {CompileMessage[]} messages
1065
1106
  */
1066
1107
  function deduplicateMessages( messages ) {
1067
1108
  const seen = new Map();
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,
@@ -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
 
@@ -23,7 +23,7 @@ function checkCoreMediaTypeAllowence(member) {
23
23
  'cds.hana.CLOB': 1,
24
24
  'cds.hana.BINARY': 1,
25
25
  };
26
- if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseType(member.type) in allowedCoreMediaTypes)) {
26
+ if (member['@Core.MediaType'] && member.type && !(this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type in allowedCoreMediaTypes)) {
27
27
  this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
28
28
  'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
29
29
  }
@@ -14,6 +14,7 @@ function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
14
14
  if (artifact.kind === 'entity') {
15
15
  // filter for 'table', 'udf', 'calcview' === true
16
16
  const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
17
+ // TODO: Why not filter over persistenceAnnos, is shorter!
17
18
  const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
18
19
  if (TableUdfCv.length > 1)
19
20
  this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
@@ -36,10 +36,10 @@ function checkPrimaryKey(art) {
36
36
  */
37
37
  function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
38
38
  if (member.key || parentIsKey) {
39
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
40
- if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType)) {
39
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
40
+ if (isGeoTypeName(finalBaseType?.type)) {
41
41
  this.error(null, parentPath || member.$path,
42
- { type: finalBaseType, name: elemFqName },
42
+ { type: finalBaseType.type, name: elemFqName },
43
43
  'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
44
44
  }
45
45
  else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
63
63
  */
64
64
  function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
65
65
  if (member.key || parentIsKey) {
66
- const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
66
+ const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
67
67
  if (member.items || (finalBaseType && finalBaseType.items)) {
68
68
  this.error(null, parentPath || member.$path, { name: elemFqName },
69
69
  'Array-like type in element $(NAME) can\'t be used as primary key');
@@ -96,10 +96,10 @@ function checkVirtualElement(member) {
96
96
 
97
97
  /**
98
98
  * Checks whether managed associations
99
- * with cardinality 'to many' have an on-condition
100
- * and if managed associations have foreign keys.
99
+ * with cardinality 'to many' have an on-condition.
101
100
  *
102
101
  * @param {CSN.Artifact} art The artifact
102
+ * @todo this is a member validator, is it not?
103
103
  */
104
104
  function checkManagedAssoc(art) {
105
105
  forEachMemberRecursively(art, (member) => {
@@ -26,7 +26,7 @@ function invalidTarget(member) {
26
26
  if (!target)
27
27
  throw new ModelError(`Expected target ${ mem.target }`);
28
28
  if (target.kind !== 'entity') {
29
- const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
29
+ const isAssoc = this.csnUtils.getFinalBaseTypeWithProps(member.type)?.type !== 'cds.Composition';
30
30
  this.error(
31
31
  null,
32
32
  member.$path,
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
23
  // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
- if (_art.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
24
+ if (_art?.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
25
  this.error(null, expression[i].$path, { elemref: { ref } },
26
26
  'Unexpected usage of structured type $(ELEMREF)');
27
27
  }
@@ -3,7 +3,8 @@
3
3
  const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
4
4
  /**
5
5
  * Make sure that all source artifacts and association targets reach the database
6
- * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database
6
+ * (otherwise the view can't be activated), but only if the source artifact is NOT activated against the database // <- what does this mean?
7
+ *
7
8
  * Check the given query for:
8
9
  * - Associations-traversal over skipped/abstract things
9
10
  * - Associations (indirectly) using managed associations without foreign keys
@@ -5,9 +5,13 @@ const { forEachGeneric } = require('../model/csnUtils');
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
7
7
  /**
8
- * Validate select items of a query.
8
+ * Validate select items of a query. If a column reference starts with $self or $projection, it must not contain association steps.
9
+ * Furthermore, for to.hdbcds, window functions are not allowed.
10
+ *
11
+ * For to.hdbcds-hdbcds, structures and managed associations are not allowed as they are not flattened - @see rejectManagedAssociationsAndStructuresForHdbcdsNames
9
12
  *
10
13
  * @param {CSN.Query} query query object
14
+ * @todo Why do we care about this with $self?
11
15
  */
12
16
  function validateSelectItems(query) {
13
17
  const { SELECT } = query;
@@ -129,8 +129,8 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
129
129
  if (!artOrElement.type)
130
130
  return;
131
131
 
132
- const { getFinalBaseType } = getUtils(model);
133
- const typeOfType = getFinalBaseType(artOrElement.type, path);
132
+ const { getFinalBaseTypeWithProps } = getUtils(model);
133
+ const typeOfType = getFinalBaseTypeWithProps(artOrElement.type);
134
134
 
135
135
  if (typeOfType === null) {
136
136
  if (artOrElement.type.ref) {
@@ -157,6 +157,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
157
157
  * @param {CSN.Path} path the path to the element or the artifact
158
158
  * @param {string} name of the element or the artifact which is dubious
159
159
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
160
+ * @todo Rename, is an error not a warning
160
161
  */
161
162
  function warnAboutMissingType(error, path, name, isElement = false) {
162
163
  error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
@@ -173,6 +174,7 @@ function warnAboutMissingType(error, path, name, isElement = false) {
173
174
  *
174
175
  * @param {CSN.Artifact} artifact the artifact to check
175
176
  * @returns {boolean} indicates whether the artifact has type information
177
+ * @todo What is the point of isBuiltinType here if we check for artifact.type at the end?
176
178
  */
177
179
  function hasArtifactTypeInformation(artifact) {
178
180
  // When is what property set?
@@ -31,7 +31,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
31
31
  return false;
32
32
 
33
33
  /**
34
- * Artifact is structured or a managed association/compoisition
34
+ * Artifact is structured or a managed association/composition
35
35
  *
36
36
  * @param {CSN.Artifact} art Artifact
37
37
  * @returns {boolean} True if expandable
@@ -49,7 +49,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
49
49
  */
50
50
  function resolveArtifactType(art) {
51
51
  if (art && art.type && !isBuiltinType(art.type))
52
- return this.getFinalBaseType(art);
52
+ return this.csnUtils.getFinalBaseTypeWithProps(art.type);
53
53
 
54
54
  return art;
55
55
  }