@sap/cds-compiler 4.8.0 → 4.9.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 (95) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +30 -17
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +38 -21
  12. package/lib/base/messages.js +51 -20
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
package/lib/api/main.js CHANGED
@@ -26,10 +26,7 @@ const baseError = lazyload('../base/error');
26
26
  const csnToEdm = lazyload('../edm/csn2edm');
27
27
  const trace = lazyload('./trace');
28
28
  const cloneCsn = lazyload('../model/cloneCsn');
29
-
30
- const { forEach, forEachKey } = require('../utils/objectUtils');
31
- const { makeMessageFunction } = require('../base/messages');
32
- const { sortCsnForTests } = require('../model/cloneCsn');
29
+ const objectUtils = lazyload('../utils/objectUtils');
33
30
 
34
31
  /**
35
32
  * Return the artifact name for use for the hdbresult object
@@ -59,18 +56,18 @@ const warnAboutMismatchOdata = [ 'odataVersion' ];
59
56
  function attachTransformerCharacteristics( csn, transformation, options,
60
57
  relevantOptionNames, optionalOptionNames = [] ) {
61
58
  const relevant = {};
62
- for (const name of relevantOptionNames ) {
59
+ for (const name of relevantOptionNames) {
63
60
  if (options[name] !== undefined)
64
61
  relevant[name] = options[name];
65
62
  }
66
63
 
67
- for (const name of optionalOptionNames ) {
64
+ for (const name of optionalOptionNames) {
68
65
  if (options[name] !== undefined)
69
66
  relevant[name] = options[name];
70
67
  }
71
68
 
72
69
  // eslint-disable-next-line sonarjs/no-empty-collection
73
- for (const name of relevantGeneralOptions ) {
70
+ for (const name of relevantGeneralOptions) {
74
71
  if (options[name] !== undefined)
75
72
  relevant[name] = options[name];
76
73
  }
@@ -142,7 +139,7 @@ function isPreTransformed( csn, transformation ) {
142
139
  function odataInternal( csn, internalOptions, messageFunctions ) {
143
140
  internalOptions.transformation = 'odata';
144
141
  let oDataCsn = forOdataNew.transform4odataWithCsn(csn, internalOptions, messageFunctions);
145
- oDataCsn = sortCsnForTests(oDataCsn, internalOptions);
142
+ oDataCsn = cloneCsn.sortCsnForTests(oDataCsn, internalOptions);
146
143
  messageFunctions.setModel(oDataCsn);
147
144
  attachTransformerCharacteristics(oDataCsn, 'odata', internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
148
145
  return oDataCsn;
@@ -273,13 +270,12 @@ function forHdbcds( csn, options, messageFunctions ) {
273
270
  *
274
271
  * @param {CSN.Model} csn Plain input CSN
275
272
  * @param {EffectiveCsnOptions} options Options
273
+ * @param {EffectiveCsnOptions} internalOptions Options that were already processed
276
274
  * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
277
275
  * @returns {CSN.Model} CSN transformed
278
276
  * @private
279
277
  */
280
- function forEffective( csn, options, messageFunctions ) {
281
- const internalOptions = prepareOptions.for.effective(options);
282
- internalOptions.transformation = 'effective';
278
+ function forEffectiveInternal( csn, options, internalOptions, messageFunctions ) {
283
279
  messageFunctions.setOptions( internalOptions );
284
280
  if (options.tenantDiscriminator) {
285
281
  messageFunctions.error('api-invalid-option', null, {
@@ -294,6 +290,40 @@ function forEffective( csn, options, messageFunctions ) {
294
290
  return cloneCsn.sortCsnForTests(eCsn, internalOptions);
295
291
  }
296
292
 
293
+ /**
294
+ * SEAL CSN transformation
295
+ *
296
+ * @param {CSN.Model} csn Plain input CSN
297
+ * @param {EffectiveCsnOptions} options Options
298
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
299
+ * @returns {CSN.Model} CSN transformed
300
+ * @private
301
+ */
302
+ function forSeal( csn, options, messageFunctions ) {
303
+ const internalOptions = prepareOptions.for.seal(options);
304
+ internalOptions.transformation = 'effective';
305
+ return forEffectiveInternal(csn, options, internalOptions, messageFunctions);
306
+ }
307
+
308
+ /**
309
+ * Effective CSN transformation
310
+ *
311
+ * @param {CSN.Model} csn Plain input CSN
312
+ * @param {EffectiveCsnOptions} options Options
313
+ * @param {object} messageFunctions Message functions such as `error()`, `info()`, …
314
+ * @returns {CSN.Model} CSN transformed
315
+ * @private
316
+ */
317
+ function forEffective( csn, options, messageFunctions ) {
318
+ const internalOptions = prepareOptions.for.effective(options);
319
+ internalOptions.transformation = 'effective';
320
+ // for.effective is still beta mode
321
+ if (!baseModel.isBetaEnabled(options, 'effectiveCsn'))
322
+ throw new baseError.CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
323
+
324
+ return forEffectiveInternal(csn, options, internalOptions, messageFunctions);
325
+ }
326
+
297
327
  /**
298
328
  * Process the given CSN into SQL.
299
329
  *
@@ -352,7 +382,7 @@ function hdi( csn, options, messageFunctions ) {
352
382
  const sqlArtifactsWithCSNNamesToSort = Object.create(null);
353
383
  const sqlArtifactsNotToSort = Object.create(null);
354
384
 
355
- forEach(flat, (key) => {
385
+ objectUtils.forEach(flat, (key) => {
356
386
  const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
357
387
  nameMapping[artifactNameLikeInCsn] = key;
358
388
  if (key.endsWith('.hdbtable') || key.endsWith('.hdbview'))
@@ -370,7 +400,7 @@ function hdi( csn, options, messageFunctions ) {
370
400
  }, Object.create(null));
371
401
 
372
402
  // now add the not-sorted stuff, like indices
373
- forEach(sqlArtifactsNotToSort, (key) => {
403
+ objectUtils.forEach(sqlArtifactsNotToSort, (key) => {
374
404
  sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlArtifactsNotToSort[key];
375
405
  });
376
406
 
@@ -392,7 +422,7 @@ function hdi( csn, options, messageFunctions ) {
392
422
  function remapNames( dict, csn, filter ) {
393
423
  const result = Object.create(null);
394
424
 
395
- forEach(dict, (key, value) => {
425
+ objectUtils.forEach(dict, (key, value) => {
396
426
  const name = remapName(key, csn, filter);
397
427
  result[name] = value;
398
428
  });
@@ -540,7 +570,7 @@ function sqlMigration( csn, options, messageFunctions, beforeImage ) {
540
570
  const createAndAlterSqls = [];
541
571
  // Turn the structured result into just a flat dictionary of "artifact name": "sql"
542
572
  const flatSqlDict = Object.values(hdbkinds).reduce((prev, curr) => {
543
- forEach(curr, (name, value) => {
573
+ objectUtils.forEach(curr, (name, value) => {
544
574
  prev[name] = value;
545
575
  });
546
576
  return prev;
@@ -629,9 +659,9 @@ function hdiMigration( csn, options, messageFunctions, beforeImage ) {
629
659
  */
630
660
  function createSqlDefinitions( hdbkinds, afterImage ) {
631
661
  const result = [];
632
- forEach(hdbkinds, (kind, artifacts) => {
662
+ objectUtils.forEach(hdbkinds, (kind, artifacts) => {
633
663
  const suffix = `.${ kind }`;
634
- forEach(artifacts, (name, sqlStatement) => {
664
+ objectUtils.forEach(artifacts, (name, sqlStatement) => {
635
665
  if ( kind !== 'hdbindex' )
636
666
  result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
637
667
  else
@@ -649,7 +679,7 @@ function createSqlDefinitions( hdbkinds, afterImage ) {
649
679
  */
650
680
  function createSqlDeletions( deletions, beforeImage ) {
651
681
  const result = [];
652
- forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
682
+ objectUtils.forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
653
683
  return result;
654
684
  }
655
685
  /**
@@ -661,7 +691,7 @@ function createSqlDeletions( deletions, beforeImage ) {
661
691
  */
662
692
  function createSqlMigrations( migrations, afterImage ) {
663
693
  const result = [];
664
- forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
694
+ objectUtils.forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
665
695
  return result;
666
696
  }
667
697
 
@@ -682,6 +712,10 @@ function hdbcds( csn, options, messageFunctions ) {
682
712
  internalOptions.transformation = 'hdbcds';
683
713
  messageFunctions.setOptions( internalOptions );
684
714
 
715
+ // no "isBetaEnabled", because this warning must also appear with "deprecated" flags
716
+ if (internalOptions.betaMode || internalOptions.beta?.v5preview)
717
+ messageFunctions.warning('api-deprecated-v5', null, null);
718
+
685
719
  if (options.tenantDiscriminator) {
686
720
  messageFunctions.error('api-invalid-option', null, {
687
721
  '#': 'forbidden',
@@ -1012,10 +1046,10 @@ function preparedCsnToEdmAll( csn, options, messageFunctions ) {
1012
1046
  */
1013
1047
  function flattenResultStructure( toProcess ) {
1014
1048
  const result = {};
1015
- forEach(toProcess, (fileType, artifacts) => {
1049
+ objectUtils.forEach(toProcess, (fileType, artifacts) => {
1016
1050
  if (fileType === 'messages')
1017
1051
  return;
1018
- forEach(artifacts, (filename) => {
1052
+ objectUtils.forEach(artifacts, (filename) => {
1019
1053
  result[`${ filename }.${ fileType }`] = artifacts[filename];
1020
1054
  });
1021
1055
  });
@@ -1039,14 +1073,15 @@ module.exports = {
1039
1073
  for_hdi: publishCsnProcessor(forHdi, 'for.hdi'),
1040
1074
  for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
1041
1075
  for_effective: publishCsnProcessor(forEffective, 'for.effective'),
1076
+ for_seal: publishCsnProcessor(forSeal, 'for.seal'),
1042
1077
 
1043
1078
  /* Deprecated, will be removed in cds-compiler@v5 */ // TODO(v5): Remove
1044
1079
  preparedCsnToEdmx(csn, service, options) {
1045
- preparedCsnToEdmx(csn, service, options, makeMessageFunction( csn, options, 'to.edmx' ));
1080
+ preparedCsnToEdmx(csn, service, options, messages.makeMessageFunction( csn, options, 'to.edmx' ));
1046
1081
  },
1047
1082
  /* Deprecated, will be removed in cds-compiler@v5 */ // TODO(v5): Remove
1048
1083
  preparedCsnToEdm(csn, service, options) {
1049
- preparedCsnToEdm(csn, service, options, makeMessageFunction( csn, options, 'to.edm' ));
1084
+ preparedCsnToEdm(csn, service, options, messages.makeMessageFunction( csn, options, 'to.edm' ));
1050
1085
  },
1051
1086
  };
1052
1087
 
@@ -1160,7 +1195,7 @@ function checkOutdatedOptions( options, messageFunctions ) {
1160
1195
  });
1161
1196
  }
1162
1197
 
1163
- forEachKey(options.variableReplacements || {}, (name) => {
1198
+ objectUtils.forEachKey(options.variableReplacements || {}, (name) => {
1164
1199
  if (!name.startsWith('$') && name !== 'user' && name !== 'locale') {
1165
1200
  messageFunctions.error('api-invalid-variable-replacement', null, {
1166
1201
  '#': 'noDollar', option: 'variableReplacements', code: '$', name,
@@ -52,6 +52,8 @@ const publicOptionsNewAPI = [
52
52
  // for.effective
53
53
  'resolveSimpleTypes',
54
54
  'resolveProjections',
55
+ 'remapOdataAnnotations',
56
+ 'keepLocalized',
55
57
  ];
56
58
 
57
59
  // Internal options used for testing/debugging etc.
@@ -214,12 +216,21 @@ module.exports = {
214
216
  effective: (options) => {
215
217
  const hardOptions = {};
216
218
  const defaultOptions = {
217
- sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true,
219
+ sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, remapOdataAnnotations: false, keepLocalized: false,
218
220
  };
219
221
  const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
220
222
 
221
223
  return Object.assign({}, processed);
222
224
  },
225
+ seal: (options) => {
226
+ const hardOptions = {
227
+ sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, keepLocalized: false,
228
+ };
229
+ const defaultOptions = { remapOdataAnnotations: true };
230
+ const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
231
+
232
+ return Object.assign({}, processed);
233
+ },
223
234
  java: options => translateOptions(options, { sqlMapping: 'plain' }, {}, undefined, undefined, 'for.java'),
224
235
  },
225
236
  overallOptions, // exported for testing
@@ -140,17 +140,13 @@ const allCombinationValidators = {
140
140
  if (options.sqlDialect && options.sqlMapping && options.sqlDialect !== 'hana' && [ 'quoted', 'hdbcds' ].includes(options.sqlMapping))
141
141
  message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-naming', name: options.sqlDialect, prop: options.sqlMapping });
142
142
  },
143
- 'sql-dialect-and-localized': (options, message) => {
144
- if (options.fewerLocalizedViews && options.sqlDialect === 'hana' && (options.withHanaAssociations || options.withHanaAssociations === undefined))
145
- message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-localized', option: 'fewerLocalizedViews', value: 'hana' });
146
- },
147
143
  'beta-no-test': (options, message) => {
148
144
  if (options.beta && !options.testMode)
149
145
  message.warning('api-unexpected-combination', null, { '#': 'beta-no-test', option: 'beta' });
150
146
  },
151
147
  };
152
148
 
153
- const alwaysRunValidators = [ 'beta-no-test', 'sql-dialect-and-localized' ];
149
+ const alwaysRunValidators = [ 'beta-no-test' ];
154
150
 
155
151
  /**
156
152
  * Run the validations for each option.
@@ -4,6 +4,32 @@
4
4
  // It should not contain any specific to XSN, i.e. neither XSN structures
5
5
  // nor any other XSN properties.
6
6
 
7
+ /**
8
+ * Map of propagation rules for annotations.
9
+ * Does not include rules for standard propagation of annotations or other properties.
10
+ *
11
+ * @type {Record<string, string>}
12
+ */
13
+ const propagationRules = {
14
+ __proto__: null,
15
+ '@Analytics.hidden': 'never',
16
+ '@Analytics.visible': 'never',
17
+ '@cds.autoexpose': 'onlyViaArtifact',
18
+ '@cds.autoexposed': 'never',
19
+ '@cds.external': 'never',
20
+ '@cds.persistence.calcview': 'never',
21
+ '@cds.persistence.exists': 'never',
22
+ '@cds.persistence.skip': 'notWithPersistenceTable',
23
+ '@cds.persistence.table': 'never',
24
+ '@cds.persistence.udf': 'never',
25
+ '@cds.redirection.target': 'never',
26
+ '@com.sap.gtt.core.CoreModel.Indexable': 'never',
27
+ '@fiori.draft.enabled': 'onlyViaArtifact',
28
+ '@sql.append': 'never',
29
+ '@sql.prepend': 'never',
30
+ '@sql.replace': 'never',
31
+ }
32
+
7
33
  /**
8
34
  * Checks whether the given absolute path is inside a reserved namespace.
9
35
  *
@@ -80,6 +106,7 @@ function isAnnotationExpression( val ) {
80
106
  }
81
107
 
82
108
  module.exports = {
109
+ propagationRules,
83
110
  xprInAnnoProperties,
84
111
  functionsWithoutParens,
85
112
  isInReservedNamespace,
@@ -64,8 +64,10 @@ const centralMessages = {
64
64
  'ext-undefined-param': { severity: 'Warning' },
65
65
  'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
66
66
  'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
67
+ 'anno-unexpected-mixin': { severity: 'Warning', errorFor: [ 'v5' ] },
67
68
 
68
69
  'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
70
+ 'name-deprecated-$self': { severity: 'Warning', errorFor: [ 'v5' ], configurableFor: true },
69
71
 
70
72
  'type-invalid-items': { severity: 'Error' }, // not supported yet
71
73
  'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
@@ -84,6 +86,7 @@ const centralMessages = {
84
86
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
85
87
 
86
88
  'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
89
+ 'ref-deprecated-self-element': { severity: 'Warning', configurableFor: true, errorFor: [ 'v5' ] },
87
90
  'ref-invalid-type': { severity: 'Error' },
88
91
  'ref-unexpected-self': { severity: 'Error' },
89
92
  'ref-invalid-include': { severity: 'Error' },
@@ -159,6 +162,7 @@ const centralMessages = {
159
162
  'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
160
163
  'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
161
164
  'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
165
+ 'syntax-ignoring-anno': { severity: 'Warning', errorFor: [ 'v5' ] },
162
166
 
163
167
  'type-unsupported-precision-change': { severity: 'Error' },
164
168
  'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
@@ -230,6 +234,9 @@ for (const oldName in oldMessageIds) {
230
234
  // If you change it, keep in sync with scripts/eslint/rules/message-text.js
231
235
 
232
236
  const centralMessageTexts = {
237
+ 'api-deprecated-v5': {
238
+ std: 'Support for generating hdbcds output is deprecated with @sap/cds-compiler v5 and later'
239
+ },
233
240
  'api-invalid-option': {
234
241
  std: 'Invalid option $(NAME)!',
235
242
  deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
@@ -251,7 +258,6 @@ const centralMessageTexts = {
251
258
  std: 'Invalid option combination found: $(OPTION) and $(PROP)', // unused
252
259
  'valid-structured': 'Structured OData is only supported with OData version v4',
253
260
  'sql-dialect-and-naming': 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
254
- 'sql-dialect-and-localized': 'Option $(OPTION) can\'t be combined with SQL dialect $(VALUE) or the to.hdi()/to.hdbcds() backend',
255
261
  'tenant-and-naming': 'Option $(OPTION) can\'t be combined with sqlMapping $(PROP) - expected sqlMapping $(VALUE)'
256
262
  },
257
263
  'api-unexpected-combination': {
@@ -284,6 +290,7 @@ const centralMessageTexts = {
284
290
  'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO)',
285
291
  'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
286
292
  'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
293
+ 'anno-unexpected-mixin': 'Unexpected annotation on mixin definition',
287
294
 
288
295
  'anno-unexpected-localized-skip': {
289
296
  std: 'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped',
@@ -291,8 +298,9 @@ const centralMessageTexts = {
291
298
  },
292
299
 
293
300
  'anno-missing-rewrite': {
294
- std: 'Assign a value for $(ANNO), the value inherited from $(ART) would contain invalid references like $(ELEMREF)',
295
- unrelated: 'Assign a value for $(ANNO), the value inherited from $(ART) would contain references like $(ELEMREF) to unrelated elements',
301
+ std: 'Assign a value for $(ANNO); the value inherited from $(ART) can\'t be rewritten due to $(ELEMREF)',
302
+ unsupported: 'Assign a value for $(ANNO); the value inherited from $(ART) can\'t be rewritten due to unsupported $(ELEMREF)',
303
+ param: 'Assign a value for $(ANNO); the value inherited from $(ART) can\'t be rewritten due to parameter reference $(ELEMREF)',
296
304
  },
297
305
 
298
306
  'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
@@ -568,13 +576,17 @@ const centralMessageTexts = {
568
576
  entity: 'Entity $(ART) has no parameter $(ID)',
569
577
  action: 'Action $(ART) has no parameter $(ID)',
570
578
  },
571
- 'ref-undefined-art': 'No artifact has been found with name $(ART)',
579
+ 'ref-undefined-art': {
580
+ std: 'No artifact has been found with name $(ART)',
581
+ namespace: 'No artifact has been found with name $(ART) which can be extended with annotations',
582
+ localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
583
+ },
572
584
  // TODO: proposal 'No definition found for $(NAME)',
573
585
  'ref-undefined-element': {
574
586
  std: 'Element $(ART) has not been found',
575
587
  element: 'Artifact $(ART) has no element $(MEMBER)',
576
588
  target: 'Target entity $(ART) has no element $(ID)',
577
- aspect: 'Element $(ID) has not been found in the anonymous target aspect', // TODO: still?
589
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect',
578
590
  query: 'The current query has no element $(ART)',
579
591
  alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
580
592
  virtual: 'Element $(ART) has not been found. Use $(CODE) to add virtual elements in queries'
@@ -585,6 +597,7 @@ const centralMessageTexts = {
585
597
  target: 'Target entity $(ART) has no element $(ID)',
586
598
  query: 'The current query has no element $(ART)',
587
599
  alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
600
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect',
588
601
  },
589
602
  'ref-undefined-var': {
590
603
  std: 'Variable $(ID) has not been found',
@@ -705,6 +718,8 @@ const centralMessageTexts = {
705
718
  min: 'Expecting argument $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)',
706
719
  'incorrect-type': 'Expected $(NAMES) for argument $(PROP), but found $(CODE)',
707
720
  },
721
+ 'type-unexpected-foreign-keys': 'A managed aspect composition can\'t have a foreign keys specification. Use composition-of-entity or remove foreign keys',
722
+ 'type-unexpected-on-condition': 'A managed aspect composition can\'t have a specified ON-condition. Use composition-of-entity or remove the ON-condition',
708
723
 
709
724
  'type-invalid-items': {
710
725
  std: 'Unexpected $(PROP)', // unused
@@ -721,7 +736,7 @@ const centralMessageTexts = {
721
736
  'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
722
737
  },
723
738
 
724
- 'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
739
+ 'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
725
740
  'ext-undefined-def': 'Artifact $(ART) has not been found',
726
741
  'ext-undefined-art': 'No artifact has been found with name $(ART)',
727
742
  'ext-undefined-element': {
@@ -864,7 +879,7 @@ const centralMessageTexts = {
864
879
  sub: 'Expecting an entity as target; a target aspect can\'t be specified for a sub element',
865
880
  },
866
881
  'ref-invalid-include': {
867
- std: 'A type, entity, aspect or event with direct elements is expected here',
882
+ std: 'An explicitly structured entity, type, aspect, or event is expected here',
868
883
  bare : 'An aspect without elements is expected here',
869
884
  param: 'A type, entity, aspect or event without parameters is expected here',
870
885
  },
@@ -907,8 +922,8 @@ const centralMessageTexts = {
907
922
  },
908
923
 
909
924
  'query-undefined-element': {
910
- std: 'Target $(TARGET) of $(NAME) is missing element $(ID); please use $(KEYWORD) with an explicit ON-condition',
911
- redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); please add an ON-condition to $(KEYWORD)',
925
+ std: 'Target $(TARGET) of $(NAME) is missing element $(ID); use $(KEYWORD) with an explicit ON-condition',
926
+ redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
912
927
  },
913
928
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
914
929
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
@@ -1032,10 +1047,10 @@ const centralMessageTexts = {
1032
1047
  'odata-spec-violation-assoc': 'Unexpected association in structured type for OData $(VERSION)',
1033
1048
  'odata-spec-violation-constraints': 'Partial referential constraints produced for OData $(VERSION)',
1034
1049
  'odata-spec-violation-id': {
1035
- std: 'Expected EDM name $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
1050
+ std: 'Expected EDM name $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
1036
1051
  'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
1037
- 'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
1038
- 'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
1052
+ 'qualifier': 'Expected annotation qualifier $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
1053
+ 'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
1039
1054
  },
1040
1055
  // version independent messages
1041
1056
  'odata-spec-violation-key-array': {
@@ -1051,7 +1066,7 @@ const centralMessageTexts = {
1051
1066
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
1052
1067
  },
1053
1068
  'odata-spec-violation-no-key': 'Expected entity to have a primary key',
1054
- 'odata-spec-violation-type-unknown': 'Unknown Edm Type $(TYPE)',
1069
+ 'odata-spec-violation-type-unknown': 'Unknown EDM Type $(TYPE)',
1055
1070
  'odata-spec-violation-type': {
1056
1071
  std: 'Expected element to have a type',
1057
1072
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
@@ -1148,13 +1163,14 @@ const centralMessageTexts = {
1148
1163
  'odata-anno-xpr': {
1149
1164
  'std': 'unused',
1150
1165
  'notadynexpr': '$(OP) is not a renderable dynamic expression in $(ANNO)',
1151
- 'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO), please use $(CODE) instead',
1166
+ 'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO), use $(CODE) instead',
1152
1167
  'canonfuncalias': 'Expected function name $(CODE) to be of the form $(META).$(OTHERMETA) for $(OP) in $(ANNO)',
1153
1168
  'unexpected': 'Unexpected expression in $(ANNO)',
1154
1169
  }
1155
1170
  ,
1156
1171
  'odata-anno-xpr-type': {
1157
- 'std': 'Expected one qualified type name for $(OP) in $(ANNO)'
1172
+ 'std': 'Expected one qualified type name for $(OP) in $(ANNO)',
1173
+ 'edm': 'Expected a qualified EDM type name for $(OP) in $(ANNO) but found $(TYPE)'
1158
1174
  },
1159
1175
  'odata-anno-xpr-args': {
1160
1176
  'std': 'Unexpected arguments for $(OP) in $(ANNO)',
@@ -1169,14 +1185,15 @@ const centralMessageTexts = {
1169
1185
  'odata-anno-xpr-ref': {
1170
1186
  'std': '$(ANNO) can\'t be propagated to $(NAME) because path $(ELEMREF) is not resolvable via type reference $(CODE)',
1171
1187
  'args': 'Unexpected arguments or filters in $(ELEMREF) in $(ANNO)',
1172
- 'flatten_builtin': 'Expected path $(ELEMREF) in $(ANNO) to end with a leaf element while flattening $(NAME)',
1173
- 'flatten_builtin_type': 'Expected path $(ELEMREF) in $(ANNO) to end with a leaf element while flattening',
1188
+ 'flatten_builtin': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening $(NAME)',
1189
+ 'flatten_builtin_type': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening',
1174
1190
  'invalid': 'Invalid path $(ELEMREF) in $(ANNO)',
1175
1191
  // genericTranslation
1176
- 'notaparam': 'Element path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a parameter entity',
1177
- 'notaneelement': 'Parameter path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a type entity',
1178
- 'notrendered': 'Path step $(COUNT) of $(ELEMREF) in $(ANNO) refers to an unrendered propery in the OData API',
1179
- 'tomany': 'Unexpected to-many transition in path step $(COUNT) of $(ELEMREF) in $(ANNO)',
1192
+ 'notaparam': 'EDM Element path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a parameter entity',
1193
+ 'notaneelement': 'EDM Parameter path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a type entity',
1194
+ 'notrendered': 'EDM Path step $(COUNT) of $(ELEMREF) in $(ANNO) refers to an unrendered property in the OData API',
1195
+ 'magic': 'Unexpected magic variable $(ELEMREF) in $(ANNO)',
1196
+ 'bparam_v2': 'Unexpected explicit binding parameter path $(ELEMREF) for OData $(VERSION) in $(ANNO)',
1180
1197
  },
1181
1198
  // -----------------------------------------------------------------------------------
1182
1199
  // OData Message section ends here, no messages below this line
@@ -82,6 +82,23 @@ function isDowngradable( messageId, moduleName, options ) {
82
82
  : configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
83
83
  }
84
84
 
85
+ /**
86
+ * Returns a marker for messages strings indicating whether the message can be downgraded
87
+ * or whether it will be an error in the next cds-compiler release.
88
+ *
89
+ * @returns {string}
90
+ */
91
+ function severityChangeMarker(msg, config) {
92
+ const severity = msg.severity || 'Error';
93
+ if (config.moduleForMarker) {
94
+ if (severity === 'Error' && isDowngradable(msg.messageId, config.moduleForMarker, {}))
95
+ return '‹↓›';
96
+ if (msg.messageId && centralMessages[msg.messageId]?.errorFor?.includes('v5'))
97
+ return '‹↑›';
98
+ }
99
+ return '';
100
+ }
101
+
85
102
  /**
86
103
  * Class for combined compiler errors. Additional members:
87
104
  * - `messages`: array of compiler messages (CompileMessage)
@@ -100,8 +117,6 @@ class CompilationError extends Error {
100
117
  this.code = 'ERR_CDS_COMPILATION_FAILURE';
101
118
  this.messages = [ ...messages ].sort(compareMessageSeverityAware);
102
119
 
103
- // TODO: remove this bin/cdsc.js specifics
104
- Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
105
120
  // property `model` is only set with options.attachValidNames:
106
121
  Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
107
122
  }
@@ -176,8 +191,14 @@ class CompileMessage {
176
191
  // this.error = null;
177
192
  }
178
193
 
179
- toString() { // should have no argument...
180
- return messageString( this, undefined, true ); // no message-id before finalization!
194
+ toString() {
195
+ // Used by cds-dk in their own `toString` wrapper.
196
+ return messageString( this, {
197
+ normalizeFilename: false,
198
+ noMessageId: true, // no message-id before finalization!
199
+ noHome: false,
200
+ module: null,
201
+ });
181
202
  }
182
203
  }
183
204
 
@@ -869,7 +890,7 @@ function transformElementRef( arg ) {
869
890
  return quoted( arg );
870
891
  // Can be used by CSN backends or compiler to create a simple path such as E:elem
871
892
  return quoted(
872
- (arg.param ? ':' : '') +
893
+ ((arg.scope === 'param' || arg.param) ? ':' : '') +
873
894
  ref.map(
874
895
  item => (typeof item !== 'string'
875
896
  ? `${ item.id }${item.args ? '(…)' : ''}${item.where ? '[…]' : ''}`
@@ -997,12 +1018,17 @@ function replaceInString( text, params ) {
997
1018
  * @param {boolean} [config.noMessageId]
998
1019
  * If true, will _not_ show the message ID (+ explanation hint) in the output.
999
1020
  *
1021
+ * @param {boolean} [config.idInBrackets]
1022
+ * If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
1023
+ * This will be the default in cds-compiler v5.
1024
+ *
1000
1025
  * @param {boolean} [config.noHome]
1001
1026
  * If true, will _not_ show message's semantic location.
1002
1027
  *
1003
- * @param {string} [config.module]
1028
+ * @param {string} [config.moduleForMarker]
1004
1029
  * If set, downgradable error messages will get a '‹↓›' marker, depending on whether
1005
- * the message can be downgraded for the given module.
1030
+ * the message can be downgraded for the given module. A `‹↑›` is used if the message
1031
+ * will be an error in the next major cds-compiler release.
1006
1032
  *
1007
1033
  * @returns {string}
1008
1034
  */
@@ -1013,19 +1039,22 @@ function messageString( err, config ) {
1013
1039
  normalizeFilename: arguments[1],
1014
1040
  noMessageId: arguments[2],
1015
1041
  noHome: arguments[3],
1016
- module: arguments[4],
1042
+ moduleForMarker: arguments[4],
1017
1043
  };
1018
1044
  }
1045
+ config.moduleForMarker ??= config.module; // v4.8.0 or earlier compatibility
1019
1046
 
1020
1047
  const location = (err.$location?.file ? `${ locationString( err.$location, config.normalizeFilename ) }: ` : '');
1021
1048
  const severity = err.severity || 'Error';
1022
- const downgradable = severity === 'Error' && config.module &&
1023
- isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
1049
+ const downgradable = severityChangeMarker(err, config);
1024
1050
  // even with noHome, print err.home if the location is weak
1025
1051
  const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
1026
- // TODO: the plan was with brackets = `Error[ref-undefined-def]`
1027
- const id = err.messageId && !config.noMessageId ? ` ${ err.messageId }` : '';
1028
- return `${ location }${ severity }${ downgradable }${ id }: ${ err.message }${ home }`;
1052
+
1053
+ let msgId = ''; // TODO(v5): Use brackets only
1054
+ if (err.messageId && !config.noMessageId)
1055
+ msgId = (config.idInBrackets) ? `[${err.messageId}]` : ` ${err.messageId}`;
1056
+
1057
+ return `${ location }${ severity }${ downgradable }${ msgId }: ${ err.message }${ home }`;
1029
1058
  }
1030
1059
 
1031
1060
  /**
@@ -1065,9 +1094,10 @@ function messageHash( msg ) {
1065
1094
  * @param {boolean} [config.hintExplanation]
1066
1095
  * If true, messages with explanations will get a "…" marker.
1067
1096
  *
1068
- * @param {string} [config.module]
1097
+ * @param {string} [config.moduleForMarker]
1069
1098
  * If set, downgradable error messages will get a '‹↓›' marker, depending on whether
1070
- * the message can be downgraded for the given module.
1099
+ * the message can be downgraded for the given module. A `‹↑›` is used if the message
1100
+ * will be an error in the next major cds-compiler release.
1071
1101
  *
1072
1102
  * @param {Record<string, string>} [config.sourceMap]
1073
1103
  * A dictionary of filename<->source-code entries. You can pass the `fileCache` that is used
@@ -1095,12 +1125,13 @@ function messageHash( msg ) {
1095
1125
  function messageStringMultiline( err, config = {} ) {
1096
1126
  colorTerm.changeColorMode(config ? config.color : 'auto');
1097
1127
 
1128
+ config.moduleForMarker ??= config.module; // v4.8.0 or earlier compatibility
1129
+
1098
1130
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
1099
1131
  const home = !err.home ? '' : (`at ${ err.home }`);
1100
1132
  const severity = err.severity || 'Error';
1101
- const downgradable = config.module && severity === 'Error' &&
1102
- isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
1103
- const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${downgradable}${ explainHelp }]` : '';
1133
+ const downgradable = severityChangeMarker(err, config);
1134
+ const msgId = (err.messageId && !config.noMessageId) ? `${downgradable}[${ err.messageId }${ explainHelp }]` : '';
1104
1135
 
1105
1136
  let location = '';
1106
1137
  let context = '';
@@ -1590,7 +1621,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1590
1621
  if (!currentThing)
1591
1622
  return result;
1592
1623
 
1593
- let selectDepth = (csnPath[0] !== 'extensions') ? queryDepthForMessage(csnPath, model, currentThing) : null;
1624
+ const selectDepth = (csnPath[0] !== 'extensions') ? queryDepthForMessage(csnPath, model, currentThing) : null;
1594
1625
 
1595
1626
  // Artifact ref -------------------------------------
1596
1627
 
@@ -1759,7 +1790,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1759
1790
  if (index >= csnPath.length)
1760
1791
  continue; // no column name
1761
1792
 
1762
- let elementHierarchy = [];
1793
+ const elementHierarchy = [];
1763
1794
 
1764
1795
  // Concat column+expand/inline to get a name similar to elements.
1765
1796
  do {