@sap/cds-compiler 2.15.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +102 -1590
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +61 -46
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  6. package/doc/CHANGELOG_BETA.md +26 -5
  7. package/doc/CHANGELOG_DEPRECATED.md +55 -1
  8. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  9. package/doc/Versioning.md +20 -1
  10. package/lib/api/.eslintrc.json +2 -2
  11. package/lib/api/main.js +282 -156
  12. package/lib/api/options.js +17 -88
  13. package/lib/api/validate.js +6 -10
  14. package/lib/base/keywords.js +280 -110
  15. package/lib/base/message-registry.js +85 -25
  16. package/lib/base/messages.js +119 -89
  17. package/lib/base/model.js +46 -2
  18. package/lib/base/optionProcessorHelper.js +53 -21
  19. package/lib/checks/actionsFunctions.js +15 -12
  20. package/lib/checks/annotationsOData.js +1 -1
  21. package/lib/checks/cdsPersistence.js +1 -0
  22. package/lib/checks/elements.js +6 -6
  23. package/lib/checks/invalidTarget.js +1 -1
  24. package/lib/checks/nonexpandableStructured.js +1 -1
  25. package/lib/checks/queryNoDbArtifacts.js +2 -1
  26. package/lib/checks/selectItems.js +101 -15
  27. package/lib/checks/types.js +7 -8
  28. package/lib/checks/utils.js +2 -2
  29. package/lib/checks/validator.js +3 -3
  30. package/lib/compiler/assert-consistency.js +78 -21
  31. package/lib/compiler/base.js +6 -4
  32. package/lib/compiler/builtins.js +177 -10
  33. package/lib/compiler/checks.js +1 -1
  34. package/lib/compiler/define.js +28 -23
  35. package/lib/compiler/extend.js +75 -18
  36. package/lib/compiler/finalize-parse-cdl.js +25 -18
  37. package/lib/compiler/index.js +27 -11
  38. package/lib/compiler/moduleLayers.js +7 -0
  39. package/lib/compiler/populate.js +26 -39
  40. package/lib/compiler/propagator.js +12 -7
  41. package/lib/compiler/resolve.js +207 -236
  42. package/lib/compiler/shared.js +100 -93
  43. package/lib/compiler/tweak-assocs.js +13 -20
  44. package/lib/compiler/utils.js +20 -6
  45. package/lib/edm/annotations/preprocessAnnotations.js +12 -13
  46. package/lib/edm/csn2edm.js +35 -37
  47. package/lib/edm/edm.js +22 -13
  48. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  49. package/lib/edm/edmInboundChecks.js +85 -0
  50. package/lib/edm/edmPreprocessor.js +338 -689
  51. package/lib/edm/edmUtils.js +97 -67
  52. package/lib/gen/Dictionary.json +29 -9
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +8 -31
  55. package/lib/gen/language.tokens +105 -114
  56. package/lib/gen/languageLexer.interp +1 -34
  57. package/lib/gen/languageLexer.js +892 -1007
  58. package/lib/gen/languageLexer.tokens +95 -106
  59. package/lib/gen/languageParser.js +20629 -22474
  60. package/lib/inspect/.eslintrc.json +4 -0
  61. package/lib/inspect/index.js +14 -0
  62. package/lib/inspect/inspectModelStatistics.js +81 -0
  63. package/lib/inspect/inspectPropagation.js +189 -0
  64. package/lib/inspect/inspectUtils.js +44 -0
  65. package/lib/json/from-csn.js +74 -69
  66. package/lib/json/to-csn.js +17 -14
  67. package/lib/language/antlrParser.js +2 -2
  68. package/lib/language/docCommentParser.js +61 -38
  69. package/lib/language/errorStrategy.js +52 -40
  70. package/lib/language/genericAntlrParser.js +424 -292
  71. package/lib/language/language.g4 +604 -687
  72. package/lib/language/multiLineStringParser.js +14 -42
  73. package/lib/language/textUtils.js +44 -0
  74. package/lib/main.d.ts +28 -42
  75. package/lib/main.js +104 -81
  76. package/lib/model/api.js +1 -1
  77. package/lib/model/csnRefs.js +57 -30
  78. package/lib/model/csnUtils.js +189 -287
  79. package/lib/model/revealInternalProperties.js +32 -10
  80. package/lib/model/sortViews.js +32 -31
  81. package/lib/modelCompare/compare.js +3 -0
  82. package/lib/optionProcessor.js +91 -57
  83. package/lib/render/.eslintrc.json +1 -1
  84. package/lib/render/DuplicateChecker.js +4 -7
  85. package/lib/render/manageConstraints.js +70 -2
  86. package/lib/render/toCdl.js +387 -367
  87. package/lib/render/toHdbcds.js +20 -16
  88. package/lib/render/toRename.js +44 -22
  89. package/lib/render/toSql.js +81 -59
  90. package/lib/render/utils/common.js +16 -3
  91. package/lib/render/utils/sql.js +20 -19
  92. package/lib/sql-identifier.js +6 -0
  93. package/lib/transform/db/.eslintrc.json +3 -2
  94. package/lib/transform/db/associations.js +43 -35
  95. package/lib/transform/db/cdsPersistence.js +5 -16
  96. package/lib/transform/db/constraints.js +1 -1
  97. package/lib/transform/db/expansion.js +7 -6
  98. package/lib/transform/db/flattening.js +16 -18
  99. package/lib/transform/db/transformExists.js +7 -5
  100. package/lib/transform/db/views.js +3 -3
  101. package/lib/transform/draft/.eslintrc.json +2 -2
  102. package/lib/transform/draft/db.js +6 -6
  103. package/lib/transform/draft/odata.js +6 -7
  104. package/lib/transform/forHanaNew.js +30 -24
  105. package/lib/transform/forOdataNew.js +14 -16
  106. package/lib/transform/localized.js +35 -25
  107. package/lib/transform/odata/toFinalBaseType.js +10 -10
  108. package/lib/transform/odata/typesExposure.js +17 -8
  109. package/lib/transform/odata/utils.js +1 -38
  110. package/lib/transform/transformUtilsNew.js +63 -77
  111. package/lib/transform/translateAssocsToJoins.js +2 -2
  112. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  113. package/lib/transform/universalCsn/coreComputed.js +11 -6
  114. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  115. package/lib/utils/file.js +31 -21
  116. package/lib/utils/moduleResolve.js +0 -1
  117. package/lib/utils/timetrace.js +20 -21
  118. package/package.json +34 -4
  119. package/share/messages/syntax-expected-integer.md +9 -8
  120. package/doc/ApiMigration.md +0 -237
  121. package/doc/CommandLineMigration.md +0 -58
  122. package/doc/ErrorMessages.md +0 -175
  123. package/doc/FioriAnnotations.md +0 -94
  124. package/doc/ODataTransformation.md +0 -273
  125. package/lib/backends.js +0 -529
  126. package/lib/checks/unknownMagic.js +0 -41
  127. 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' },
@@ -112,10 +114,11 @@ const centralMessages = {
112
114
  'ref-undefined-def': { severity: 'Error' },
113
115
  'ref-undefined-var': { severity: 'Error' },
114
116
  'ref-undefined-element': { severity: 'Error' },
115
- 'ref-unknown-var': { severity: 'Info' },
117
+ 'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
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,15 @@ 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-duplicate-annotate' came late with v3 - make it configurable as
135
+ // fallback, but then parse.cdl is not supposed to work correctly (it can
136
+ // then either issue an error or produce a CSN missing some annotations):
137
+ 'syntax-duplicate-annotate': { severity: 'Error', configurableFor: true },
138
+ 'syntax-expected-cardinality': { severity: 'Error' },
139
+ 'syntax-expected-length': { severity: 'Error' },
140
+ 'syntax-expected-translation': { severity: 'Error' },
141
+ 'syntax-required-subproperty': { severity: 'Error' },
142
+ 'syntax-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
139
143
  'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
140
144
  'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
141
145
  'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
@@ -148,6 +152,8 @@ const centralMessages = {
148
152
  'syntax-missing-escape': { severity: 'Error' },
149
153
 
150
154
  'syntax-expected-integer': { severity: 'Error' },
155
+ 'syntax-invalid-masked': { severity: 'Error', configurableFor: true },
156
+ 'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
151
157
 
152
158
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
153
159
 
@@ -174,6 +180,15 @@ const centralMessages = {
174
180
  // The keys will be added to `oldNames` of the new message, which is used for reclassification.
175
181
  const oldMessageIds = createDict({
176
182
  'old-anno-duplicate': 'anno-duplicate', // Example
183
+
184
+ // These IDs are used by large stakeholders. If we change them, we should
185
+ // be backward-compatible.
186
+ // 'redirected-to-complex': 'TODO',
187
+ // 'wildcard-excluding-one': 'TODO',
188
+ // 'wildcard-excluding-many': 'TODO',
189
+ // 'assoc-outside-service': 'TODO',
190
+ // 'redirected-to-same': 'TODO',
191
+ // 'query-navigate-many': 'TODO',
177
192
  });
178
193
 
179
194
  // Set up the old-to-new message ID mapping in the message registry.
@@ -194,34 +209,50 @@ for (const oldName in oldMessageIds) {
194
209
  // For messageIds, where no text has been provided via code (central def)
195
210
  const centralMessageTexts = {
196
211
  'api-invalid-option': {
197
- std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
198
- magicVars: 'Option “magicVars” is deprecated! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
212
+ std: 'Option $(NAME) is no longer supported! Use SNAPI options instead',
213
+ magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
199
214
  user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
200
215
  locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
201
216
  'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
202
217
  },
203
218
 
204
219
  'anno-duplicate': 'Duplicate assignment with $(ANNO)',
220
+ 'anno-duplicate-unrelated-layer': 'Duplicate assignment with $(ANNO)',
221
+ 'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
205
222
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
206
223
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
207
224
  'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
208
225
  'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
209
- 'syntax-csn-expected-object': 'Expected object for property $(PROP)',
210
- 'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
211
- 'syntax-csn-expected-natnum': 'Expected non-negative number for property $(PROP)',
212
- 'syntax-csn-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
213
- 'syntax-csn-expected-reference': 'Expected non-empty string or object for property $(PROP)',
214
- 'syntax-csn-expected-term': 'Expected non-empty string or object for property $(PROP)',
215
- 'syntax-anno-after-struct': 'Avoid annotation assignments after structure definitions',
216
- 'syntax-anno-after-enum': 'Avoid annotation assignments after enum definitions',
217
- 'syntax-anno-after-params': 'Avoid annotation assignments after parameters',
226
+
227
+ 'name-duplicate-element': {
228
+ 'std': 'Generated element $(NAME) conflicts with another element',
229
+ 'flatten-element-gen': 'Generated element $(NAME) conflicts with other generated element',
230
+ 'flatten-element-exist': 'Flattened name of structured element conflicts with existing element $(NAME)',
231
+ 'flatten-fkey-gen': 'Duplicate definition of foreign key element $(NAME) for association $(ART)',
232
+ 'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
233
+ },
234
+
235
+ 'syntax-anno-ignored': {
236
+ std: 'Annotations can\'t be used at prefix references',
237
+ doc: 'Doc comments can\'t be used at prefix references',
238
+ },
239
+ 'syntax-unexpected-ellipsis': {
240
+ std: 'Expected no more than one $(CODE)',
241
+ 'nested-array': 'Unexpected $(CODE) in nested array'
242
+ },
243
+ 'syntax-expected-object': 'Expected object for property $(PROP)',
244
+ 'syntax-expected-column': 'Expected object or string \'*\' for property $(PROP)',
245
+ 'syntax-expected-natnum': 'Expected non-negative number for property $(PROP)',
246
+ 'syntax-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
247
+ 'syntax-expected-reference': 'Expected non-empty string or object for property $(PROP)',
248
+ 'syntax-expected-term': 'Expected non-empty string or object for property $(PROP)',
218
249
  'syntax-dollar-ident': {
219
250
  std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',
220
251
  $tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
221
252
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
222
253
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
223
254
  },
224
- 'syntax-csn-expected-length': {
255
+ 'syntax-expected-length': {
225
256
  std: 'Expected array in $(PROP) to have at least $(N) items',
226
257
  one: 'Expected array in $(PROP) to have at least one item',
227
258
  suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
@@ -253,6 +284,26 @@ const centralMessageTexts = {
253
284
  unknown: 'Unknown argument $(CODE)',
254
285
  duplicate: 'Duplicate argument $(CODE)',
255
286
  },
287
+ 'syntax-duplicate-annotate': 'You can\'t refer to $(NAME) repeatedly with property $(PROP) in the same annotate statement',
288
+ 'syntax-duplicate-extend': {
289
+ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
290
+ define: 'You can\'t refer to $(NAME) in the same extend statement where it was defined',
291
+ extend: 'You can\'t refer to $(NAME) repeatedly in the same extend statement',
292
+ },
293
+ 'syntax-invalid-literal': {
294
+ 'std': 'Invalid literal',
295
+ 'uneven-hex': 'A binary literal must have an even number of characters',
296
+ 'invalid-hex': 'A binary literal must only contain characters 0-9, a-f and A-F',
297
+ 'time': 'Expected time\'hh:mm:ss\' where hh, mm and the optional ss are numbers',
298
+ 'date': 'Expected date\'YYYY-MM-DD\' where YYYY, MM and DD are numbers',
299
+ '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)',
300
+ },
301
+ 'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
302
+ 'syntax-unexpected-vocabulary': {
303
+ std: 'Annotations can\'t be defined inside contexts or services',
304
+ service: 'Annotations can\'t be defined inside services',
305
+ context: 'Annotations can\'t be defined inside contexts',
306
+ },
256
307
  'ref-undefined-def': {
257
308
  std: 'Artifact $(ART) has not been found',
258
309
  // TODO: proposal 'No definition of $(NAME) found',
@@ -266,7 +317,7 @@ const centralMessageTexts = {
266
317
  aspect: 'Element $(ID) has not been found in the anonymous target aspect'
267
318
  },
268
319
  'ref-unknown-var': {
269
- std: 'Replacement $(ID) not found'
320
+ std: 'No replacement found for special variable $(ID)'
270
321
  },
271
322
  'ref-unexpected-draft-enabled': 'Composition in draft-enabled entity can\'t lead to another entity with $(ANNO)',
272
323
  'ref-rejected-on': {
@@ -274,12 +325,17 @@ const centralMessageTexts = {
274
325
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
275
326
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
276
327
  },
328
+ 'ref-expected-element': {
329
+ std: 'Expected element reference',
330
+ magicVar: 'Only elements of magic variable $(ID) can be selected',
331
+ },
277
332
  'type-unexpected-typeof': {
278
333
  std: 'Unexpected $(KEYWORD) for the type reference here',
279
334
  type: 'Unexpected $(KEYWORD) in type of a type definition',
280
335
  event: 'Unexpected $(KEYWORD) for the type of an event',
281
336
  param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
282
337
  select: 'Unexpected $(KEYWORD) for type references in queries',
338
+ annotation: '$(KEYWORD) can\'t be used in annotation definitions',
283
339
  },
284
340
 
285
341
  'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
@@ -419,7 +475,11 @@ const centralMessageTexts = {
419
475
  std: 'Entity can\'t be created due to name collision with existing definition $(NAME)',
420
476
  proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
421
477
  },
422
- 'odata-navigation': 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)'
478
+ 'odata-navigation': {
479
+ std: 'No OData navigation property generated, target $(TARGET) is outside of service $(SERVICE)',
480
+ oncond: 'No OData navigation property generated for association with arbitrary ON condition and target $(TARGET) outside of service $(SERVICE)'
481
+ },
482
+ 'odata-parameter-order': 'Unexpected mandatory after optional parameter',
423
483
  }
424
484
 
425
485
  /**
@@ -431,7 +491,7 @@ const centralMessageTexts = {
431
491
  * Whether the error can be reclassified to a warning or lower.
432
492
  * If not `true` then an array is expected with specified modules in which the error is downgradable.
433
493
  * Only has an effect if default severity is 'Error'.
434
- * 'deprecated': severity can only be changed with deprecated.downgradableErrors.
494
+ * 'deprecated': severity can only be changed with deprecated._downgradableErrors.
435
495
  * TODO: Value `true` is temporary. Use an array instead.
436
496
  * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.
437
497
  * @property {boolean} [throughMessageCall]
@@ -8,7 +8,6 @@ const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts, oldMessageIds } = require('./message-registry');
11
- const { copyPropIfExist } = require('../utils/objectUtils');
12
11
  const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
13
12
  const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
14
13
  const { CompilerAssertion } = require("./error");
@@ -27,7 +26,7 @@ let test$texts = null;
27
26
  /**
28
27
  * Returns true if at least one of the given messages is of severity "Error".
29
28
  *
30
- * @param {CSN.Message[]} messages
29
+ * @param {CompileMessage[]} messages
31
30
  * @returns {boolean}
32
31
  */
33
32
  function hasErrors( messages ) {
@@ -39,7 +38,7 @@ function hasErrors( messages ) {
39
38
  * and *cannot* be reclassified to a warning for the given module.
40
39
  * Won't detect already downgraded messages.
41
40
  *
42
- * @param {CSN.Message[]} messages
41
+ * @param {CompileMessage[]} messages
43
42
  * @param {string} moduleName
44
43
  * @returns {boolean}
45
44
  */
@@ -135,7 +134,7 @@ class CompileMessage {
135
134
  constructor(location, msg, severity = 'Error', id = null, home = null, moduleName = null) {
136
135
  this.message = msg;
137
136
  this.location = location;
138
- this.$location = dollarLocation( this.location );
137
+ this.$location = { ...this.location, address: undefined };
139
138
  this.validNames = null;
140
139
  if (home) // semantic location, e.g. 'entity:"E"/element:"x"'
141
140
  this.home = home;
@@ -152,33 +151,6 @@ class CompileMessage {
152
151
  }
153
152
  }
154
153
 
155
- /**
156
- * Temporary v1 function to convert an "old-style" location to "new-style".
157
- *
158
- * @param {CSN.Location} location
159
- * @return {CSN.Location}
160
- * @todo Remove
161
- */
162
- function dollarLocation( location ) {
163
- const file = location && location.file || undefined;
164
- if (!file)
165
- return {};
166
- const loc = {
167
- file,
168
- line: location.line,
169
- col: location.col,
170
- address: undefined,
171
- };
172
- copyPropIfExist(location, 'endLine', loc);
173
- copyPropIfExist(location, 'endCol', loc);
174
- // TODO:
175
- // return {
176
- // ...location,
177
- // address: undefined,
178
- // };
179
- return loc;
180
- }
181
-
182
154
  const severitySpecs = {
183
155
  error: { name: 'Error', level: 0 },
184
156
  warning: { name: 'Warning', level: 1 },
@@ -257,14 +229,16 @@ function compareSeverities( a, b ) {
257
229
  }
258
230
 
259
231
  /**
260
- * @todo This was copied from somewhere just to make CSN paths work.
232
+ * Find the nearest $location for the given CSN path in the model.
233
+ * If the path does not exist, the parent is used, and so on.
234
+ *
261
235
  * @param {CSN.Model} model
262
236
  * @param {CSN.Path} csnPath
237
+ * @returns {CSN.Location | null}
263
238
  */
264
- function searchForLocation( model, csnPath ) {
239
+ function findNearestLocationForPath( model, csnPath ) {
265
240
  if (!model)
266
241
  return null;
267
- // Don't display a location if we cannot find one!
268
242
  let lastLocation = null;
269
243
  /** @type {object} */
270
244
  let currentStep = model;
@@ -319,20 +293,23 @@ function createMessageFunctions( options, moduleName, model = null ) {
319
293
  * ```
320
294
  * @param {object} model
321
295
  * @param {CSN.Options} [options]
322
- * @param {string} [moduleName]
296
+ * @param {string|null} [moduleName]
323
297
  */
324
298
  function makeMessageFunction( model, options, moduleName = null ) {
325
- // ensure message consistency during runtime with --test-mode
326
- if (options.testMode)
299
+ if (options.testMode) {
300
+ // ensure message consistency during runtime with --test-mode
327
301
  _check$Init( options );
302
+ if (!options.messages)
303
+ throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
304
+ }
328
305
 
329
306
  const hasMessageArray = !!options.messages;
330
- const deprecatedDowngradable = isDeprecatedEnabled( options, 'downgradableErrors' );
307
+ const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
331
308
  /**
332
309
  * Array of collected compiler messages. Only use it for debugging. Will not
333
310
  * contain the messages created during a `callTransparently` call.
334
311
  *
335
- * @type {CSN.Message[]}
312
+ * @type {CompileMessage[]}
336
313
  */
337
314
  let messages = options.messages || [];
338
315
  /**
@@ -361,7 +338,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
361
338
  const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);
362
339
  const text = messageText( texts || centralMessageTexts[id], textOrArguments );
363
340
 
364
- /** @type {CSN.Message} */
341
+ /** @type {CompileMessage} */
365
342
  const msg = new CompileMessage( fileLocation, text, severity, id, semanticLocation, moduleName );
366
343
  if (options.internalMsg)
367
344
  msg.error = new Error( 'stack' );
@@ -432,10 +409,10 @@ function makeMessageFunction( model, options, moduleName = null ) {
432
409
  // CSN.Location (with line/endLine, col/endCol)
433
410
  return [ location, location.home || null, null ]
434
411
 
435
- const isCsnPath = (typeof location[0] === 'string');
412
+ const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
436
413
  if (isCsnPath) {
437
414
  return [
438
- searchForLocation( model, location ),
415
+ findNearestLocationForPath( model, location ),
439
416
  constructSemanticLocationFromCsnPath( location, model ),
440
417
  location[1] // location[0] is 'definitions'
441
418
  ];
@@ -553,7 +530,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
553
530
  *
554
531
  * @param {Function} callback
555
532
  * @param {...any} args
556
- * @returns {CSN.Message[]}
533
+ * @returns {CompileMessage[]}
557
534
  */
558
535
  function callTransparently(callback, ...args) {
559
536
  const backup = messages;
@@ -579,6 +556,22 @@ function _check$Init( options ) {
579
556
  }
580
557
  }
581
558
 
559
+ /**
560
+ * Check the consistency of the given message and run some basic lint checks. These include:
561
+ *
562
+ * - Long message IDs must be listed centrally.
563
+ * - Messages with the same ID must have the same severity (in a module).
564
+ * - Messages with the same ID must have the same message texts.
565
+ * This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
566
+ * use the same ID for different meanings, i.e. texts.
567
+ *
568
+ * @param {string} id
569
+ * @param {string} moduleName
570
+ * @param {string} severity
571
+ * @param {string|object} texts
572
+ * @param {CSN.Options} options
573
+ * @private
574
+ */
582
575
  function _check$Consistency( id, moduleName, severity, texts, options ) {
583
576
  if (id.length > 30 && !centralMessages[id])
584
577
  throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
@@ -589,6 +582,16 @@ function _check$Consistency( id, moduleName, severity, texts, options ) {
589
582
  _check$Texts( id, variant, text );
590
583
  }
591
584
 
585
+ /**
586
+ * Check the consistency of the message severity for the given message ID.
587
+ * Messages with the same ID must have the same severity (in a module).
588
+ * Non-downgradable errors must never be called with a lower severity.
589
+ *
590
+ * @param {string} id
591
+ * @param {string} moduleName
592
+ * @param {string} severity
593
+ * @private
594
+ */
592
595
  function _check$Severities( id, moduleName, severity ) {
593
596
  if (!severity) // if just used message(), we are automatically consistent
594
597
  return;
@@ -598,22 +601,34 @@ function _check$Severities( id, moduleName, severity ) {
598
601
  if (!expected)
599
602
  test$severities[id] = severity;
600
603
  else if (expected !== severity)
601
- throw new CompilerAssertion( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
604
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
602
605
  return;
603
606
  }
604
607
  // now try whether the message could be something less than an Error in the module due to user wishes
605
608
  if (!isDowngradable( id, moduleName )) { // always an error in module
606
609
  if (severity !== 'Error')
607
- throw new CompilerAssertion( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
610
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
608
611
  }
609
612
  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}"` );
613
+ throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
611
614
  }
612
615
  else if (spec.severity !== severity) {
613
- throw new CompilerAssertion( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
616
+ throw new CompilerAssertion( `Inconsistent severity: Expecting "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
614
617
  }
615
618
  }
616
619
 
620
+ /**
621
+ * Check the consistency of the message text for the given message ID.
622
+ *
623
+ * Messages with the same ID must have the same message texts.
624
+ * This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
625
+ * use the same ID for different meanings, i.e. texts.
626
+ *
627
+ * @param {string} id
628
+ * @param {string} prop
629
+ * @param {string} value
630
+ * @private
631
+ */
617
632
  function _check$Texts( id, prop, value ) {
618
633
  if (!test$texts[id])
619
634
  test$texts[id] = Object.create(null);
@@ -621,7 +636,7 @@ function _check$Texts( id, prop, value ) {
621
636
  if (!expected)
622
637
  test$texts[id][prop] = value;
623
638
  else if (expected !== value)
624
- throw new CompilerAssertion( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
639
+ throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
625
640
  }
626
641
 
627
642
  const quote = { // could be an option in the future
@@ -819,7 +834,7 @@ function weakLocation( loc ) {
819
834
  * Example:
820
835
  * <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
821
836
  *
822
- * @param {CSN.Message} err
837
+ * @param {CompileMessage} err
823
838
  * @param {boolean} [normalizeFilename]
824
839
  * @param {boolean} [noMessageId]
825
840
  * @param {boolean} [noHome]
@@ -841,7 +856,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
841
856
  * Return message hash which is either the message string without the file location,
842
857
  * or the full message string if no semantic location is provided.
843
858
  *
844
- * @param {CSN.Message} msg
859
+ * @param {CompileMessage} msg
845
860
  * @returns {string} can be used to uniquely identify a message
846
861
  */
847
862
  function messageHash(msg) {
@@ -867,7 +882,7 @@ function messageHash(msg) {
867
882
  * <source>.cds:3:11, at entity:“E”/element:“e”
868
883
  * ```
869
884
  *
870
- * @param {CSN.Message} err
885
+ * @param {CompileMessage} err
871
886
  * @param {object} [config = {}]
872
887
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
873
888
  * @param {boolean} [config.noMessageId]
@@ -915,7 +930,7 @@ function messageStringMultiline( err, config = {} ) {
915
930
  *
916
931
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
917
932
  * from `lib/utils/file.js`
918
- * @param {CSN.Message} err Error object containing all details like line, message, etc.
933
+ * @param {CompileMessage} err Error object containing all details like line, message, etc.
919
934
  * @param {object} [config = {}]
920
935
  * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
921
936
  * coloring will be used. If 'auto', we will decide based on certain factors such
@@ -994,8 +1009,8 @@ function messageContext(sourceLines, err, config) {
994
1009
  * larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
995
1010
  * are considered larger than messages with a location.
996
1011
  *
997
- * @param {CSN.Message} a
998
- * @param {CSN.Message} b
1012
+ * @param {CompileMessage} a
1013
+ * @param {CompileMessage} b
999
1014
  */
1000
1015
  function compareMessage( a, b ) {
1001
1016
  const aFile = a.$location && a.$location.file;
@@ -1029,8 +1044,8 @@ function compareMessage( a, b ) {
1029
1044
  * location and severity, >0 if `a` is larger than `b`, and <0 if `a` is smaller
1030
1045
  * than `b`. See `compareSeverities()` for how severities are compared.
1031
1046
  *
1032
- * @param {CSN.Message} a
1033
- * @param {CSN.Message} b
1047
+ * @param {CompileMessage} a
1048
+ * @param {CompileMessage} b
1034
1049
  */
1035
1050
  function compareMessageSeverityAware( a, b ) {
1036
1051
  const c = compareSeverities(a.severity, b.severity);
@@ -1042,12 +1057,13 @@ function compareMessageSeverityAware( a, b ) {
1042
1057
  * Messages without semantic locations are considered smaller (for syntax errors)
1043
1058
  * and (currently - should not happen in v2) larger for other messages.
1044
1059
  *
1045
- * @param {CSN.Message} msg
1060
+ * @param {CompileMessage} msg
1046
1061
  */
1047
1062
  function homeSortName( { home, messageId } ) {
1048
- return (!home)
1049
- ? (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1050
- : home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1063
+ if (!home)
1064
+ return (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
1065
+ else
1066
+ return home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there
1051
1067
  }
1052
1068
 
1053
1069
  /**
@@ -1061,7 +1077,7 @@ function homeSortName( { home, messageId } ) {
1061
1077
  * A message is more precise if it is contained in the other or if
1062
1078
  * the first does not have an endLine/endCol.
1063
1079
  *
1064
- * @param {CSN.Message[]} messages
1080
+ * @param {CompileMessage[]} messages
1065
1081
  */
1066
1082
  function deduplicateMessages( messages ) {
1067
1083
  const seen = new Map();
@@ -1133,7 +1149,7 @@ function homeName( art, absoluteOnly ) {
1133
1149
  return null;
1134
1150
  else if (art.kind === 'using')
1135
1151
  return 'using:' + quoted( art.name.id );
1136
- else if (art.kind === 'extend')
1152
+ else if (art.kind === 'extend' || art.kind === 'annotate')
1137
1153
  return !absoluteOnly && homeNameForExtend ( art );
1138
1154
  else if (art.name._artifact) // block, extend, annotate
1139
1155
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
@@ -1148,35 +1164,41 @@ function homeName( art, absoluteOnly ) {
1148
1164
  // The "home" for extensions is handled differently because `_artifact` is not
1149
1165
  // set for unknown extensions and we could have nested extensions.
1150
1166
  function homeNameForExtend( art ) {
1167
+ const kind = art.kind || 'extend';
1151
1168
  // 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('.'));
1169
+ // basically resolveUncheckedPath()
1170
+ const absoluteName = art.name.id != null ? art.name.id :
1171
+ (!art.name.element && art.name.absolute || art.name.path && art.name.path.map(s => s && s.id).join('.'));
1155
1172
 
1156
1173
  // Surrounding parent may be another extension.
1157
1174
  const parent = art._parent;
1158
1175
  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);
1176
+ return kind + ':' + quoted(absoluteName);
1177
+
1178
+ if (art.name.param && parent.params) {
1179
+ const fakeArt = { kind: 'param', name: { param: absoluteName } };
1180
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1173
1181
  }
1174
- else {
1175
- extensionName = 'extend:' + quoted(absoluteName);
1182
+ else if (art.name.action && parent.actions) {
1183
+ const type = art.name._artifact?.kind || 'action';
1184
+ const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1185
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1176
1186
  }
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;
1187
+ else if (parent.enum || parent.elements || parent.returns?.elements) {
1188
+ // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
1189
+ // so we need to look at the parent artifact.
1190
+ // For `extend <art> with enum`, there is `enum`.
1191
+ const parentArt = parent.name?._artifact;
1192
+ const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
1193
+ const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
1194
+ let parentOfElementChain = parent;
1195
+ while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1196
+ parentOfElementChain = parentOfElementChain._parent;
1197
+
1198
+ return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1199
+ }
1200
+ // This case should not happen, but just in case
1201
+ return kind + ':' + artName(parent);
1180
1202
  }
1181
1203
 
1182
1204
  function constructSemanticLocationFromCsnPath(csnPath, model) {
@@ -1189,10 +1211,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1189
1211
  ];
1190
1212
  const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1191
1213
 
1192
- let { query } = analyseCsnPath(
1193
- csnPath,
1194
- model
1195
- );
1214
+ if (csnPath[0] === 'extensions') {
1215
+ const ext = model.extensions && model.extensions[csnPath[1]] || {};
1216
+ if (ext.annotate)
1217
+ return 'annotate:' + quoted(ext.annotate);
1218
+ return 'extend:' + quoted(ext.extend);
1219
+ }
1220
+
1221
+ let { query } = analyseCsnPath(csnPath, model, false);
1196
1222
 
1197
1223
  // remove definitions
1198
1224
  csnPath.shift();
@@ -1332,8 +1358,12 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1332
1358
  if (typeof step === 'number') {
1333
1359
  if (currentThing.as)
1334
1360
  result += `:${ _quoted(currentThing.as) }`;
1361
+ else if (inRef)
1362
+ result += `:${ _quoted(currentThing) }`;
1363
+ else if (currentThing.ref)
1364
+ result += `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }`;
1335
1365
  else
1336
- result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }` : '';
1366
+ return'';
1337
1367
 
1338
1368
  break;
1339
1369
  }