@sap/cds-compiler 2.15.2 → 3.0.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 (111) hide show
  1. package/CHANGELOG.md +66 -1590
  2. package/bin/cdsc.js +42 -46
  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 +312 -143
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +280 -110
  13. package/lib/base/message-registry.js +80 -24
  14. package/lib/base/messages.js +103 -52
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +53 -21
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +1 -1
  19. package/lib/checks/cdsPersistence.js +1 -0
  20. package/lib/checks/elements.js +6 -6
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/nonexpandableStructured.js +1 -1
  23. package/lib/checks/queryNoDbArtifacts.js +2 -1
  24. package/lib/checks/selectItems.js +5 -1
  25. package/lib/checks/types.js +4 -2
  26. package/lib/checks/utils.js +2 -2
  27. package/lib/checks/validator.js +2 -1
  28. package/lib/compiler/assert-consistency.js +15 -10
  29. package/lib/compiler/builtins.js +127 -10
  30. package/lib/compiler/define.js +6 -4
  31. package/lib/compiler/extend.js +63 -12
  32. package/lib/compiler/finalize-parse-cdl.js +20 -9
  33. package/lib/compiler/index.js +25 -11
  34. package/lib/compiler/moduleLayers.js +7 -0
  35. package/lib/compiler/populate.js +16 -14
  36. package/lib/compiler/propagator.js +3 -3
  37. package/lib/compiler/resolve.js +194 -222
  38. package/lib/compiler/shared.js +56 -76
  39. package/lib/compiler/tweak-assocs.js +9 -10
  40. package/lib/compiler/utils.js +7 -2
  41. package/lib/edm/annotations/genericTranslation.js +60 -6
  42. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  43. package/lib/edm/csn2edm.js +39 -41
  44. package/lib/edm/edm.js +22 -15
  45. package/lib/edm/edmPreprocessor.js +66 -69
  46. package/lib/edm/edmUtils.js +12 -62
  47. package/lib/gen/Dictionary.json +8 -6
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +8 -30
  50. package/lib/gen/language.tokens +105 -114
  51. package/lib/gen/languageLexer.interp +1 -34
  52. package/lib/gen/languageLexer.js +889 -1007
  53. package/lib/gen/languageLexer.tokens +95 -106
  54. package/lib/gen/languageParser.js +20717 -22376
  55. package/lib/json/from-csn.js +73 -68
  56. package/lib/json/to-csn.js +13 -10
  57. package/lib/language/antlrParser.js +2 -2
  58. package/lib/language/docCommentParser.js +61 -38
  59. package/lib/language/errorStrategy.js +52 -40
  60. package/lib/language/genericAntlrParser.js +333 -259
  61. package/lib/language/language.g4 +600 -645
  62. package/lib/language/multiLineStringParser.js +14 -42
  63. package/lib/language/textUtils.js +44 -0
  64. package/lib/main.d.ts +27 -42
  65. package/lib/main.js +104 -81
  66. package/lib/model/csnRefs.js +2 -1
  67. package/lib/model/csnUtils.js +183 -285
  68. package/lib/model/revealInternalProperties.js +32 -9
  69. package/lib/model/sortViews.js +32 -31
  70. package/lib/optionProcessor.js +64 -57
  71. package/lib/render/.eslintrc.json +1 -1
  72. package/lib/render/DuplicateChecker.js +4 -7
  73. package/lib/render/manageConstraints.js +70 -2
  74. package/lib/render/toCdl.js +334 -339
  75. package/lib/render/toHdbcds.js +20 -16
  76. package/lib/render/toRename.js +44 -22
  77. package/lib/render/toSql.js +60 -54
  78. package/lib/render/utils/common.js +15 -1
  79. package/lib/render/utils/sql.js +20 -19
  80. package/lib/sql-identifier.js +6 -0
  81. package/lib/transform/db/.eslintrc.json +3 -2
  82. package/lib/transform/db/cdsPersistence.js +5 -15
  83. package/lib/transform/db/constraints.js +1 -1
  84. package/lib/transform/db/expansion.js +7 -6
  85. package/lib/transform/db/flattening.js +18 -19
  86. package/lib/transform/db/views.js +3 -3
  87. package/lib/transform/draft/.eslintrc.json +2 -2
  88. package/lib/transform/draft/db.js +6 -6
  89. package/lib/transform/draft/odata.js +6 -7
  90. package/lib/transform/forHanaNew.js +19 -22
  91. package/lib/transform/forOdataNew.js +13 -15
  92. package/lib/transform/localized.js +35 -25
  93. package/lib/transform/odata/toFinalBaseType.js +11 -9
  94. package/lib/transform/odata/typesExposure.js +3 -3
  95. package/lib/transform/odata/utils.js +1 -38
  96. package/lib/transform/transformUtilsNew.js +63 -77
  97. package/lib/transform/translateAssocsToJoins.js +6 -2
  98. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  99. package/lib/transform/universalCsn/coreComputed.js +11 -6
  100. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  101. package/lib/utils/file.js +31 -21
  102. package/lib/utils/timetrace.js +20 -21
  103. package/package.json +34 -4
  104. package/share/messages/syntax-expected-integer.md +9 -8
  105. package/doc/ApiMigration.md +0 -237
  106. package/doc/CommandLineMigration.md +0 -58
  107. package/doc/ErrorMessages.md +0 -175
  108. package/doc/FioriAnnotations.md +0 -94
  109. package/doc/ODataTransformation.md +0 -273
  110. package/lib/backends.js +0 -529
  111. 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
 
@@ -158,12 +160,12 @@ const centralMessages = {
158
160
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
159
161
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
160
162
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
161
- 'odata-spec-violation-id': { severity: 'Error' },
162
- 'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
163
+ 'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
164
+ 'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
163
165
  'odata-spec-violation-type-unknown': { severity: 'Warning' },
164
166
  'odata-spec-violation-no-key': { severity: 'Warning' },
165
- 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
166
- 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
167
+ 'odata-spec-violation-key-array': { severity: 'Error', configurableFor: true }, // more than 30 chars
168
+ 'odata-spec-violation-key-null': { severity: 'Error', configurableFor: true }, // more than 30 chars
167
169
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
168
170
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
169
171
  };
@@ -174,6 +176,15 @@ const centralMessages = {
174
176
  // The keys will be added to `oldNames` of the new message, which is used for reclassification.
175
177
  const oldMessageIds = createDict({
176
178
  'old-anno-duplicate': 'anno-duplicate', // Example
179
+
180
+ // These IDs are used by large stakeholders. If we change them, we should
181
+ // be backward-compatible.
182
+ // 'redirected-to-complex': 'TODO',
183
+ // 'wildcard-excluding-one': 'TODO',
184
+ // 'wildcard-excluding-many': 'TODO',
185
+ // 'assoc-outside-service': 'TODO',
186
+ // 'redirected-to-same': 'TODO',
187
+ // 'query-navigate-many': 'TODO',
177
188
  });
178
189
 
179
190
  // Set up the old-to-new message ID mapping in the message registry.
@@ -193,27 +204,47 @@ for (const oldName in oldMessageIds) {
193
204
 
194
205
  // For messageIds, where no text has been provided via code (central def)
195
206
  const centralMessageTexts = {
207
+ 'api-invalid-option': {
208
+ std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
209
+ magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
210
+ user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
211
+ locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
212
+ 'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
213
+ },
214
+
196
215
  'anno-duplicate': 'Duplicate assignment with $(ANNO)',
216
+ 'anno-duplicate-unrelated-layer': 'Duplicate assignment with $(ANNO)',
217
+ 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
197
218
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
198
219
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
199
220
  'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
200
221
  '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',
222
+
223
+ 'name-duplicate-element': {
224
+ 'std': 'Generated element $(NAME) conflicts with another element',
225
+ 'flatten-element-gen': 'Generated element $(NAME) conflicts with other generated element',
226
+ 'flatten-element-exist': 'Flattened name of structured element conflicts with existing element $(NAME)',
227
+ 'flatten-fkey-gen': 'Duplicate definition of foreign key element $(NAME) for association $(ART)',
228
+ 'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
229
+ },
230
+
231
+ 'syntax-unexpected-ellipsis': {
232
+ std: 'Expected no more than one $(CODE)',
233
+ 'nested-array': 'Unexpected $(CODE) in nested array'
234
+ },
235
+ 'syntax-expected-object': 'Expected object for property $(PROP)',
236
+ 'syntax-expected-column': 'Expected object or string \'*\' for property $(PROP)',
237
+ 'syntax-expected-natnum': 'Expected non-negative number for property $(PROP)',
238
+ 'syntax-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
239
+ 'syntax-expected-reference': 'Expected non-empty string or object for property $(PROP)',
240
+ 'syntax-expected-term': 'Expected non-empty string or object for property $(PROP)',
210
241
  'syntax-dollar-ident': {
211
242
  std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',
212
243
  $tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
213
244
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
214
245
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
215
246
  },
216
- 'syntax-csn-expected-length': {
247
+ 'syntax-expected-length': {
217
248
  std: 'Expected array in $(PROP) to have at least $(N) items',
218
249
  one: 'Expected array in $(PROP) to have at least one item',
219
250
  suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
@@ -245,6 +276,26 @@ const centralMessageTexts = {
245
276
  unknown: 'Unknown argument $(CODE)',
246
277
  duplicate: 'Duplicate argument $(CODE)',
247
278
  },
279
+ 'syntax-duplicate-annotate': 'You shouldn\'t refer to $(NAME) repeatedly in the same annotate statement',
280
+ 'syntax-duplicate-extend': {
281
+ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
282
+ define: 'You can\'t refer to $(NAME) in the same extend statement where it was defined',
283
+ extend: 'You can\'t refer to $(NAME) repeatedly in the same extend statement',
284
+ },
285
+ 'syntax-invalid-literal': {
286
+ 'std': 'Invalid literal',
287
+ 'uneven-hex': 'A binary literal must have an even number of characters',
288
+ 'invalid-hex': 'A binary literal must only contain characters 0-9, a-f and A-F',
289
+ 'time': 'Expected time\'hh:mm:ss\' where hh, mm and the optional ss are numbers',
290
+ 'date': 'Expected date\'YYYY-MM-DD\' where YYYY, MM and DD are numbers',
291
+ '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)',
292
+ },
293
+ 'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
294
+ 'syntax-unexpected-vocabulary': {
295
+ std: 'Annotations can\'t be defined inside contexts or services',
296
+ service: 'Annotations can\'t be defined inside services',
297
+ context: 'Annotations can\'t be defined inside contexts',
298
+ },
248
299
  'ref-undefined-def': {
249
300
  std: 'Artifact $(ART) has not been found',
250
301
  // TODO: proposal 'No definition of $(NAME) found',
@@ -266,12 +317,17 @@ const centralMessageTexts = {
266
317
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
267
318
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
268
319
  },
320
+ 'ref-expected-element': {
321
+ std: 'Expected element reference',
322
+ magicVar: 'Only elements of magic variable $(ID) can be selected',
323
+ },
269
324
  'type-unexpected-typeof': {
270
325
  std: 'Unexpected $(KEYWORD) for the type reference here',
271
326
  type: 'Unexpected $(KEYWORD) in type of a type definition',
272
327
  event: 'Unexpected $(KEYWORD) for the type of an event',
273
328
  param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
274
329
  select: 'Unexpected $(KEYWORD) for type references in queries',
330
+ annotation: '$(KEYWORD) can\'t be used in annotation definitions',
275
331
  },
276
332
 
277
333
  'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
@@ -423,7 +479,7 @@ const centralMessageTexts = {
423
479
  * Whether the error can be reclassified to a warning or lower.
424
480
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
425
481
  * Only has an effect if default severity is 'Error'.
426
- * 'deprecated': severity can only be changed with deprecated.downgradableErrors.
482
+ * 'deprecated': severity can only be changed with deprecated._downgradableErrors.
427
483
  * TODO: Value `true` is temporary. Use an array instead.
428
484
  * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.
429
485
  * @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' );
@@ -432,7 +435,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
432
435
  // CSN.Location (with line/endLine, col/endCol)
433
436
  return [ location, location.home || null, null ]
434
437
 
435
- const isCsnPath = (typeof location[0] === 'string');
438
+ const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
436
439
  if (isCsnPath) {
437
440
  return [
438
441
  searchForLocation( model, location ),
@@ -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();
@@ -1133,7 +1174,7 @@ function homeName( art, absoluteOnly ) {
1133
1174
  return null;
1134
1175
  else if (art.kind === 'using')
1135
1176
  return 'using:' + quoted( art.name.id );
1136
- else if (art.kind === 'extend')
1177
+ else if (art.kind === 'extend' || art.kind === 'annotate')
1137
1178
  return !absoluteOnly && homeNameForExtend ( art );
1138
1179
  else if (art.name._artifact) // block, extend, annotate
1139
1180
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
@@ -1148,35 +1189,41 @@ function homeName( art, absoluteOnly ) {
1148
1189
  // The "home" for extensions is handled differently because `_artifact` is not
1149
1190
  // set for unknown extensions and we could have nested extensions.
1150
1191
  function homeNameForExtend( art ) {
1192
+ const kind = art.kind || 'extend';
1151
1193
  // TODO: fix the following - do like in collectArtifactExtensions() or
1152
- // basically resolveUncheckedPath()
1153
- const absoluteName = (art.name.id ? art.name.id :
1154
- art.name.path.map(s => s && s.id).join('.'));
1194
+ // basically resolveUncheckedPath()
1195
+ const absoluteName = art.name.id ? art.name.id :
1196
+ (!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));
1155
1197
 
1156
1198
  // Surrounding parent may be another extension.
1157
1199
  const parent = art._parent;
1158
1200
  if (!parent)
1159
- return 'extend:' + quoted(absoluteName);
1160
-
1161
- // And that extension's artifact could have been found.
1162
- const parentArt = parent.name && parent.name._artifact;
1163
- if (!parentArt)
1164
- return artName(parent) + '/' + quoted(absoluteName);
1165
-
1166
- let extensionName;
1167
- if (parentArt.enum || parentArt.elements) {
1168
- const fakeArt = {
1169
- kind: parentArt.enum ? 'enum' : 'element',
1170
- name: { element: absoluteName }
1171
- };
1172
- extensionName = artName(fakeArt);
1201
+ return kind + ':' + quoted(absoluteName);
1202
+
1203
+ if (art.name.param && parent.params) {
1204
+ const fakeArt = { kind: 'param', name: { param: absoluteName } };
1205
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1173
1206
  }
1174
- else {
1175
- extensionName = 'extend:' + quoted(absoluteName);
1207
+ else if (art.name.action && parent.actions) {
1208
+ const type = art.name._artifact?.kind || 'action';
1209
+ const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1210
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1211
+ }
1212
+ else if (parent.enum || parent.elements || parent.returns?.elements) {
1213
+ // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
1214
+ // so we need to look at the parent artifact.
1215
+ // For `extend <art> with enum`, there is `enum`.
1216
+ const parentArt = parent.name?._artifact;
1217
+ const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
1218
+ const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
1219
+ let parentOfElementChain = parent;
1220
+ while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1221
+ parentOfElementChain = parentOfElementChain._parent;
1222
+
1223
+ return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1176
1224
  }
1177
- // Even though the parent artifact was found, we use kind 'extend'
1178
- // to make it clear that we are inside an (element) extension.
1179
- return 'extend:' + artName(parentArt) + '/' + extensionName;
1225
+ // This case should not happen, but just in case
1226
+ return kind + ':' + artName(parent);
1180
1227
  }
1181
1228
 
1182
1229
  function constructSemanticLocationFromCsnPath(csnPath, model) {
@@ -1189,10 +1236,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1189
1236
  ];
1190
1237
  const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1191
1238
 
1192
- let { query } = analyseCsnPath(
1193
- csnPath,
1194
- model
1195
- );
1239
+ if (csnPath[0] === 'extensions') {
1240
+ const ext = model.extensions && model.extensions[csnPath[1]] || {};
1241
+ if (ext.annotate)
1242
+ return 'annotate:' + quoted(ext.annotate);
1243
+ return 'extend:' + quoted(ext.extend);
1244
+ }
1245
+
1246
+ let { query } = analyseCsnPath(csnPath, model, false);
1196
1247
 
1197
1248
  // remove definitions
1198
1249
  csnPath.shift();
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,