@sap/cds-compiler 4.8.0 → 4.9.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 (92) hide show
  1. package/CHANGELOG.md +29 -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 +14 -1
  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 +32 -19
  12. package/lib/base/messages.js +50 -19
  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 +8 -2
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +34 -22
  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/propagator.js +21 -18
  26. package/lib/compiler/resolve.js +44 -28
  27. package/lib/compiler/shared.js +60 -20
  28. package/lib/compiler/tweak-assocs.js +13 -88
  29. package/lib/compiler/xpr-rewrite.js +689 -0
  30. package/lib/edm/annotations/genericTranslation.js +80 -60
  31. package/lib/edm/edm.js +4 -4
  32. package/lib/edm/edmInboundChecks.js +33 -0
  33. package/lib/edm/edmPreprocessor.js +9 -6
  34. package/lib/gen/Dictionary.json +129 -14
  35. package/lib/gen/language.checksum +1 -1
  36. package/lib/gen/language.interp +1 -1
  37. package/lib/gen/languageParser.js +1523 -1518
  38. package/lib/json/from-csn.js +13 -4
  39. package/lib/json/to-csn.js +10 -11
  40. package/lib/language/genericAntlrParser.js +14 -6
  41. package/lib/main.d.ts +67 -14
  42. package/lib/main.js +1 -0
  43. package/lib/model/cloneCsn.js +6 -3
  44. package/lib/model/csnRefs.js +12 -7
  45. package/lib/model/csnUtils.js +13 -7
  46. package/lib/model/enrichCsn.js +3 -1
  47. package/lib/model/revealInternalProperties.js +2 -1
  48. package/lib/model/sortViews.js +14 -6
  49. package/lib/modelCompare/compare.js +33 -34
  50. package/lib/optionProcessor.js +27 -2
  51. package/lib/render/DuplicateChecker.js +6 -6
  52. package/lib/render/manageConstraints.js +1 -0
  53. package/lib/render/toCdl.js +3 -1
  54. package/lib/transform/db/applyTransformations.js +33 -0
  55. package/lib/transform/db/constraints.js +1 -1
  56. package/lib/transform/db/expansion.js +8 -3
  57. package/lib/transform/db/groupByOrderBy.js +2 -2
  58. package/lib/transform/db/temporal.js +6 -3
  59. package/lib/transform/db/transformExists.js +2 -2
  60. package/lib/transform/effective/annotations.js +194 -0
  61. package/lib/transform/effective/main.js +6 -8
  62. package/lib/transform/effective/misc.js +31 -10
  63. package/lib/transform/forOdata.js +23 -7
  64. package/lib/transform/forRelationalDB.js +1 -1
  65. package/lib/transform/localized.js +7 -6
  66. package/lib/transform/odata/flattening.js +189 -106
  67. package/lib/transform/odata/toFinalBaseType.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +15 -12
  69. package/lib/transform/parseExpr.js +4 -4
  70. package/lib/transform/transformUtils.js +40 -37
  71. package/lib/transform/translateAssocsToJoins.js +47 -47
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  73. package/package.json +1 -1
  74. package/share/messages/anno-missing-rewrite.md +45 -0
  75. package/share/messages/message-explanations.json +1 -0
  76. package/bin/.eslintrc.json +0 -17
  77. package/lib/api/.eslintrc.json +0 -37
  78. package/lib/checks/.eslintrc.json +0 -31
  79. package/lib/compiler/.eslintrc.json +0 -8
  80. package/lib/edm/.eslintrc.json +0 -46
  81. package/lib/inspect/.eslintrc.json +0 -4
  82. package/lib/json/.eslintrc.json +0 -4
  83. package/lib/language/.eslintrc.json +0 -4
  84. package/lib/model/.eslintrc.json +0 -13
  85. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  86. package/lib/render/.eslintrc.json +0 -22
  87. package/lib/transform/.eslintrc.json +0 -13
  88. package/lib/transform/db/.eslintrc.json +0 -41
  89. package/lib/transform/draft/.eslintrc.json +0 -4
  90. package/lib/transform/effective/.eslintrc.json +0 -4
  91. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  92. package/lib/utils/.eslintrc.json +0 -7
@@ -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,8 @@ 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) would contain invalid or unrelated references like $(ELEMREF)',
302
+ unsupported: 'Assign a value for $(ANNO); the value inherited from $(ART) can\'t be rewritten due to unsupported $(ELEMREF)',
296
303
  },
297
304
 
298
305
  'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
@@ -568,13 +575,17 @@ const centralMessageTexts = {
568
575
  entity: 'Entity $(ART) has no parameter $(ID)',
569
576
  action: 'Action $(ART) has no parameter $(ID)',
570
577
  },
571
- 'ref-undefined-art': 'No artifact has been found with name $(ART)',
578
+ 'ref-undefined-art': {
579
+ std: 'No artifact has been found with name $(ART)',
580
+ namespace: 'No artifact has been found with name $(ART) which can be extended with annotations',
581
+ localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
582
+ },
572
583
  // TODO: proposal 'No definition found for $(NAME)',
573
584
  'ref-undefined-element': {
574
585
  std: 'Element $(ART) has not been found',
575
586
  element: 'Artifact $(ART) has no element $(MEMBER)',
576
587
  target: 'Target entity $(ART) has no element $(ID)',
577
- aspect: 'Element $(ID) has not been found in the anonymous target aspect', // TODO: still?
588
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect',
578
589
  query: 'The current query has no element $(ART)',
579
590
  alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
580
591
  virtual: 'Element $(ART) has not been found. Use $(CODE) to add virtual elements in queries'
@@ -585,6 +596,7 @@ const centralMessageTexts = {
585
596
  target: 'Target entity $(ART) has no element $(ID)',
586
597
  query: 'The current query has no element $(ART)',
587
598
  alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
599
+ aspect: 'Element $(ID) has not been found in the anonymous target aspect',
588
600
  },
589
601
  'ref-undefined-var': {
590
602
  std: 'Variable $(ID) has not been found',
@@ -721,7 +733,7 @@ const centralMessageTexts = {
721
733
  'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
722
734
  },
723
735
 
724
- 'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
736
+ 'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
725
737
  'ext-undefined-def': 'Artifact $(ART) has not been found',
726
738
  'ext-undefined-art': 'No artifact has been found with name $(ART)',
727
739
  'ext-undefined-element': {
@@ -907,8 +919,8 @@ const centralMessageTexts = {
907
919
  },
908
920
 
909
921
  '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)',
922
+ std: 'Target $(TARGET) of $(NAME) is missing element $(ID); use $(KEYWORD) with an explicit ON-condition',
923
+ redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
912
924
  },
913
925
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
914
926
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
@@ -1032,10 +1044,10 @@ const centralMessageTexts = {
1032
1044
  'odata-spec-violation-assoc': 'Unexpected association in structured type for OData $(VERSION)',
1033
1045
  'odata-spec-violation-constraints': 'Partial referential constraints produced for OData $(VERSION)',
1034
1046
  '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',
1047
+ 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
1048
  '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'
1049
+ 'qualifier': 'Expected annotation qualifier $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
1050
+ '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
1051
  },
1040
1052
  // version independent messages
1041
1053
  'odata-spec-violation-key-array': {
@@ -1051,7 +1063,7 @@ const centralMessageTexts = {
1051
1063
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
1052
1064
  },
1053
1065
  'odata-spec-violation-no-key': 'Expected entity to have a primary key',
1054
- 'odata-spec-violation-type-unknown': 'Unknown Edm Type $(TYPE)',
1066
+ 'odata-spec-violation-type-unknown': 'Unknown EDM Type $(TYPE)',
1055
1067
  'odata-spec-violation-type': {
1056
1068
  std: 'Expected element to have a type',
1057
1069
  incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
@@ -1148,7 +1160,7 @@ const centralMessageTexts = {
1148
1160
  'odata-anno-xpr': {
1149
1161
  'std': 'unused',
1150
1162
  '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',
1163
+ 'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO), use $(CODE) instead',
1152
1164
  'canonfuncalias': 'Expected function name $(CODE) to be of the form $(META).$(OTHERMETA) for $(OP) in $(ANNO)',
1153
1165
  'unexpected': 'Unexpected expression in $(ANNO)',
1154
1166
  }
@@ -1169,14 +1181,15 @@ const centralMessageTexts = {
1169
1181
  'odata-anno-xpr-ref': {
1170
1182
  'std': '$(ANNO) can\'t be propagated to $(NAME) because path $(ELEMREF) is not resolvable via type reference $(CODE)',
1171
1183
  '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',
1184
+ 'flatten_builtin': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening $(NAME)',
1185
+ 'flatten_builtin_type': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening',
1174
1186
  'invalid': 'Invalid path $(ELEMREF) in $(ANNO)',
1175
1187
  // 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)',
1188
+ 'notaparam': 'EDM Element path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a parameter entity',
1189
+ 'notaneelement': 'EDM Parameter path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a type entity',
1190
+ 'notrendered': 'EDM Path step $(COUNT) of $(ELEMREF) in $(ANNO) refers to an unrendered property in the OData API',
1191
+ 'magic': 'Unexpected magic variable $(ELEMREF) in $(ANNO)',
1192
+ 'bparam_v2': 'Unexpected explicit binding parameter path $(ELEMREF) for OData $(VERSION) in $(ANNO)',
1180
1193
  },
1181
1194
  // -----------------------------------------------------------------------------------
1182
1195
  // 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
 
@@ -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 {
package/lib/base/model.js CHANGED
@@ -26,9 +26,6 @@ const queryOps = {
26
26
  */
27
27
  const availableBetaFlags = {
28
28
  // enabled by --beta-mode
29
- annotationExpressions: true,
30
- odataPathsInAnnotationExpressions: true,
31
- odataAnnotationExpressions: true,
32
29
  hanaAssocRealCardinality: true,
33
30
  mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
34
31
  enableUniversalCsn: true,
@@ -39,8 +36,10 @@ const availableBetaFlags = {
39
36
  tenantVariable: true,
40
37
  calcAssoc: true,
41
38
  v5preview: true,
39
+ temporalRawProjection: true,
42
40
  // disabled by --beta-mode
43
41
  nestedServices: false,
42
+ rewriteAnnotationExpressionsViaType: false,
44
43
  };
45
44
 
46
45
  // Used by isDeprecatedEnabled() to check if any flag ist set.
@@ -81,7 +80,7 @@ const oldDeprecatedFlags_v2 = [
81
80
  *
82
81
  * A feature always needs to be provided - otherwise false will be returned.
83
82
  *
84
- * Please do not move this function to the "option processor" code.
83
+ * Do not move this function to the "option processor" code.
85
84
  *
86
85
  * @param {object} options Options
87
86
  * @param {string} feature Feature to check for
@@ -101,7 +100,7 @@ function isBetaEnabled( options, feature ) {
101
100
  * Useful for newer functionality which might not work with some
102
101
  * deprecated feature turned on.
103
102
  *
104
- * Please do not move this function to the "option processor" code.
103
+ * Do not move this function to the "option processor" code.
105
104
  *
106
105
  * @param {object} options Options
107
106
  * @param {string|null} [feature] Feature to check for
@@ -53,8 +53,8 @@ function checkActionOrFunction( art, artName, prop, path ) {
53
53
  Object.entries(params).forEach(([ pn, p ], i) => {
54
54
  const type = p.items?.type || p.type;
55
55
  if (type === '$self' && !this.csn.definitions.$self && i > 0) {
56
- this.error(null, currPath.concat(pn),
57
- 'Binding parameter is expected to appear on first position only');
56
+ this.error('def-invalid-param', currPath.concat(pn),
57
+ 'Binding parameter is expected to appear in first position only');
58
58
  }
59
59
  });
60
60
  }
@@ -37,6 +37,7 @@ function checkCoreMediaTypeAllowance( member ) {
37
37
  function checkAnalytics( member ) {
38
38
  if (member['@Analytics.Measure'] && !member['@Aggregation.default']) {
39
39
  this.info(null, member.$path, {},
40
+ // eslint-disable-next-line cds-compiler/message-no-quotes
40
41
  'Annotation “@Analytics.Measure” expects “@Aggregation.default” to be assigned for the same element as well');
41
42
  }
42
43
  }
@@ -63,6 +64,7 @@ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
63
64
  if (!this.csnUtils.getServiceName(artifactName))
64
65
  return;
65
66
  if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
67
+ // eslint-disable-next-line cds-compiler/message-no-quotes
66
68
  this.warning(null, artifact.$path, {}, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
67
69
  }
68
70
 
@@ -93,6 +95,7 @@ function checkTemporalAnnotationsAssignment( artifact, artifactName ) {
93
95
 
94
96
  // if @cds.valid.key is defined, check whether @cds.valid.from and @cds.valid.to are also there
95
97
  if (valid.key.length && !(valid.from.length && valid.to.length))
98
+ // eslint-disable-next-line cds-compiler/message-no-quotes
96
99
  this.error(null, [ 'definitions', artifactName ], 'Annotation “@cds.valid.key” was used but “@cds.valid.from” and “@cds.valid.to” are missing');
97
100
 
98
101
  /**
@@ -23,6 +23,7 @@ function validateDefaultValues( member, memberName, prop, path ) {
23
23
  // TODO: This check only counts the number of leading signs, not inbetween (e.g. 1 - - 1).
24
24
  // The message also needs to be improved.
25
25
  if (i > 1)
26
+ // eslint-disable-next-line cds-compiler/message-no-quotes
26
27
  this.error(null, path, {}, 'Illegal number of unary ‘+’/‘-’ operators');
27
28
  }
28
29
  }
@@ -37,8 +38,10 @@ function validateDefaultValues( member, memberName, prop, path ) {
37
38
  * @param {CSN.Path} path Path to the member
38
39
  */
39
40
  function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
40
- if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
41
- this.error(null, path, {}, 'Parameter default values are not supported in SAP HANA CDS');
41
+ if (member.default && prop === 'params' && this.options.transformation === 'hdbcds') {
42
+ this.error('def-unsupported-param', path, {},
43
+ 'Parameter default values are not supported in SAP HANA CDS');
44
+ }
42
45
  }
43
46
 
44
47
  /**
@@ -167,12 +167,13 @@ function _checkRef( ref, _links, $path, inColumns ) {
167
167
  const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
168
168
  this.error( null, $path, {
169
169
  '#': cdsPersistenceSkipped ? 'std' : 'abstract',
170
+ anno: '@cds.persistence.skip',
170
171
  id: nonPersistedTarget.pathStep,
171
172
  elemref: { ref },
172
173
  name: nonPersistedTarget.name,
173
174
  }, {
174
- std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
175
- abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
175
+ std: 'Unexpected $(ANNO) annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
176
+ abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
176
177
  } );
177
178
  break; // only one error per path
178
179
  }
@@ -3,7 +3,7 @@
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively, forAllQueries,
5
5
  forEachMember, getNormalizedQuery, hasAnnotationValue,
6
- applyTransformations, functionList,
6
+ applyTransformations, functionList, mergeTransformers,
7
7
  } = require('../model/csnUtils');
8
8
  const enrichCsn = require('./enricher');
9
9
 
@@ -156,7 +156,7 @@ function _validate( csn, that,
156
156
  // TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
157
157
  // const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
158
158
 
159
- applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
159
+ applyTransformations(csn, mergeTransformers(csnValidators, that), [], { drillRef: true });
160
160
 
161
161
  forEachDefinition(csn, (artifact, artifactName, prop, path) => {
162
162
  artifactValidators.forEach((artifactValidator) => {
@@ -180,38 +180,6 @@ function _validate( csn, that,
180
180
  return cleanup;
181
181
  }
182
182
 
183
- /**
184
- * Ensure the CSN validators adhere to the applyTransformation format - also, supply correct this value for each subfunction
185
- *
186
- * @param {object[]} csnValidators Validators
187
- * @param {object} that Value for this
188
- * @returns {object} Remapped validators.
189
- */
190
- function mergeCsnValidators( csnValidators, that ) {
191
- const remapped = {};
192
- for (const validator of csnValidators) {
193
- for (const [ n, fns ] of Object.entries(validator)) {
194
- if (!remapped[n])
195
- remapped[n] = [];
196
-
197
- if (Array.isArray(fns)) {
198
- remapped[n].push((parent, name, prop, path) => fns.forEach(
199
- fn => fn.bind(that)(parent, name, prop, path)
200
- ));
201
- }
202
- else {
203
- remapped[n].push((parent, name, prop, path) => fns.bind(that)(parent, name, prop, path));
204
- }
205
- }
206
- }
207
-
208
- for (const [ n, fns ] of Object.entries(remapped))
209
- remapped[n] = (parent, name, prop, path) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path));
210
-
211
-
212
- return remapped;
213
- }
214
-
215
183
  /**
216
184
  * Depending on the dialect we need to run different validations.
217
185
  *
@@ -129,6 +129,7 @@ function assertConsistency( model, stage ) {
129
129
  ],
130
130
  instanceOf: XsnSource,
131
131
  },
132
+ tokenIndex: { test: isNumber },
132
133
  location: {
133
134
  // every thing with a $location in CSN must have a XSN location even
134
135
  // with syntax errors (currently even internal artifacts like $using):
@@ -136,7 +137,10 @@ function assertConsistency( model, stage ) {
136
137
  kind: true,
137
138
  instanceOf: Location,
138
139
  requires: [ 'file' ], // line is optional in top-level location
139
- optional: [ 'line', 'col', 'endLine', 'endCol', '$notFound' ],
140
+ optional: [
141
+ 'line', 'col', 'endLine', 'endCol', '$notFound',
142
+ 'tokenIndex', // in parser for $lsp
143
+ ],
140
144
  schema: {
141
145
  line: { test: isNumber },
142
146
  col: { test: isNumber },
@@ -232,7 +236,7 @@ function assertConsistency( model, stage ) {
232
236
  test: (model.$frontend !== 'json') ? standard : TODO,
233
237
  // TODO: the JSON parser should augment 'namespace' correctly or better: hide it
234
238
  requires: [ 'location' ],
235
- optional: [ 'path' ],
239
+ optional: [ 'kind', 'name' ],
236
240
  },
237
241
  usings: {
238
242
  test: isArray(),
@@ -515,11 +519,13 @@ function assertConsistency( model, stage ) {
515
519
  '_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
516
520
  // CSN parser may let these properties slip through to XSN, even if input is invalid.
517
521
  'args', 'op', 'func', 'suffix',
522
+ '$invalidPaths',
518
523
  ],
519
524
  // TODO: name requires if not in parser?
520
525
  },
521
526
  $priority: { test: isOneOf( [ undefined, false, 'extend', 'annotate' ] ) },
522
527
  $annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
528
+ $invalidPaths: { test: isBoolean },
523
529
  name: {
524
530
  isRequired: stageParser && (() => false), // not required in parser
525
531
  kind: true,