@sap/cds-compiler 2.10.2 → 2.11.4

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 (82) hide show
  1. package/CHANGELOG.md +90 -5
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +49 -25
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_BETA.md +10 -0
  8. package/lib/api/.eslintrc.json +2 -0
  9. package/lib/api/main.js +8 -36
  10. package/lib/api/options.js +15 -6
  11. package/lib/api/validate.js +30 -3
  12. package/lib/backends.js +12 -13
  13. package/lib/base/dictionaries.js +2 -1
  14. package/lib/base/keywords.js +3 -2
  15. package/lib/base/message-registry.js +34 -10
  16. package/lib/base/messages.js +38 -18
  17. package/lib/base/model.js +5 -4
  18. package/lib/base/optionProcessorHelper.js +57 -23
  19. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  20. package/lib/checks/selectItems.js +4 -0
  21. package/lib/checks/unknownMagic.js +6 -3
  22. package/lib/compiler/assert-consistency.js +9 -2
  23. package/lib/compiler/base.js +65 -0
  24. package/lib/compiler/builtins.js +62 -16
  25. package/lib/compiler/checks.js +2 -1
  26. package/lib/compiler/definer.js +66 -108
  27. package/lib/compiler/index.js +29 -29
  28. package/lib/compiler/propagator.js +5 -2
  29. package/lib/compiler/resolver.js +225 -58
  30. package/lib/compiler/shared.js +53 -229
  31. package/lib/compiler/utils.js +184 -0
  32. package/lib/edm/annotations/genericTranslation.js +1 -1
  33. package/lib/edm/csn2edm.js +3 -2
  34. package/lib/edm/edmPreprocessor.js +34 -38
  35. package/lib/edm/edmUtils.js +3 -3
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +17 -1
  38. package/lib/gen/language.tokens +79 -73
  39. package/lib/gen/languageLexer.interp +19 -1
  40. package/lib/gen/languageLexer.js +779 -731
  41. package/lib/gen/languageLexer.tokens +71 -65
  42. package/lib/gen/languageParser.js +4668 -4072
  43. package/lib/json/from-csn.js +10 -10
  44. package/lib/json/to-csn.js +228 -47
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +26 -8
  47. package/lib/language/genericAntlrParser.js +73 -14
  48. package/lib/language/language.g4 +79 -3
  49. package/lib/main.d.ts +215 -18
  50. package/lib/main.js +3 -1
  51. package/lib/model/api.js +2 -2
  52. package/lib/model/csnRefs.js +117 -33
  53. package/lib/model/csnUtils.js +65 -133
  54. package/lib/model/enrichCsn.js +62 -37
  55. package/lib/model/revealInternalProperties.js +25 -8
  56. package/lib/model/sortViews.js +8 -1
  57. package/lib/modelCompare/compare.js +2 -1
  58. package/lib/optionProcessor.js +33 -18
  59. package/lib/render/.eslintrc.json +1 -2
  60. package/lib/render/DuplicateChecker.js +1 -1
  61. package/lib/render/toCdl.js +15 -8
  62. package/lib/render/toHdbcds.js +26 -49
  63. package/lib/render/toSql.js +61 -39
  64. package/lib/render/utils/common.js +1 -1
  65. package/lib/transform/db/applyTransformations.js +189 -0
  66. package/lib/transform/db/constraints.js +273 -119
  67. package/lib/transform/db/draft.js +3 -2
  68. package/lib/transform/db/expansion.js +6 -4
  69. package/lib/transform/db/flattening.js +19 -3
  70. package/lib/transform/db/transformExists.js +102 -9
  71. package/lib/transform/db/views.js +485 -0
  72. package/lib/transform/forHanaNew.js +93 -448
  73. package/lib/transform/forOdataNew.js +9 -2
  74. package/lib/transform/localized.js +2 -0
  75. package/lib/transform/odata/structuralPath.js +1 -5
  76. package/lib/transform/transformUtilsNew.js +22 -8
  77. package/lib/transform/translateAssocsToJoins.js +7 -15
  78. package/lib/utils/file.js +11 -5
  79. package/lib/utils/term.js +65 -42
  80. package/lib/utils/timetrace.js +48 -26
  81. package/package.json +1 -1
  82. package/lib/transform/db/helpers.js +0 -58
package/lib/backends.js CHANGED
@@ -14,7 +14,7 @@ const { csn2edm, csn2edmAll } = require('./edm/csn2edm');
14
14
  const { mergeOptions } = require('./model/csnUtils');
15
15
  const { isBetaEnabled } = require('./base/model');
16
16
  const { optionProcessor } = require('./optionProcessor')
17
- const timetrace = require('./utils/timetrace');
17
+ const { timetrace } = require('./utils/timetrace');
18
18
  const { makeMessageFunction } = require('./base/messages');
19
19
  const { forEachDefinition } = require('./model/csnUtils');
20
20
 
@@ -92,9 +92,9 @@ function toOdataWithCsn(csn, options) {
92
92
  // Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
93
93
  // using 'options'
94
94
  function preparedCsnToEdmx(csn, service, options) {
95
- let edmx = csn2edm(csn, service, options).toXML('all');
95
+ const e = csn2edm(csn, service, options)
96
96
  return {
97
- edmx,
97
+ edmx: (e ? e.toXML('all') : undefined)
98
98
  };
99
99
  }
100
100
 
@@ -115,9 +115,9 @@ function preparedCsnToEdmxAll(csn, options) {
115
115
  function preparedCsnToEdm(csn, service, options) {
116
116
  // Merge options; override OData version as edm json is always v4
117
117
  options = mergeOptions(options, { toOdata : { version : 'v4' }});
118
- const edmj = csn2edm(csn, service, options).toJSON();
118
+ const e = csn2edm(csn, service, options);
119
119
  return {
120
- edmj,
120
+ edmj: (e ? e.toJSON() : undefined)
121
121
  };
122
122
  }
123
123
 
@@ -303,9 +303,9 @@ function transformSQLOptions(model, options) {
303
303
  }
304
304
  // move the locale option(if provided) under user.locale
305
305
  if (options.locale) {
306
- options.user
306
+ options.user = options.user
307
307
  ? Object.assign(options.user, { locale: options.locale })
308
- : options.user = { locale: options.locale };
308
+ : { locale: options.locale };
309
309
  delete options.locale;
310
310
  }
311
311
  }
@@ -395,19 +395,14 @@ function toRenameWithCsn(csn, options) {
395
395
  });
396
396
 
397
397
  // Assemble result
398
- let result = {
398
+ return {
399
399
  rename : toRenameDdl(forHanaCsn, options),
400
400
  options
401
401
  };
402
-
403
- return result;
404
402
  }
405
403
 
406
404
  function alterConstraintsWithCsn(csn, options) {
407
405
  const { error } = makeMessageFunction(csn, options, 'manageConstraints');
408
- // Requires beta mode
409
- if (!isBetaEnabled(options, 'foreignKeyConstraints'))
410
- error(null, null, 'ALTER TABLE statements for adding/modifying referential constraints are only available in beta mode');
411
406
 
412
407
  const {
413
408
  drop, alter, names, src, violations
@@ -420,6 +415,10 @@ function alterConstraintsWithCsn(csn, options) {
420
415
  dialect: 'hana',
421
416
  names: names || 'plain'
422
417
  }
418
+
419
+ // Of course we want the database constraints
420
+ options.assertIntegrityType = 'DB';
421
+
423
422
  const transformedOptions = transformSQLOptions(csn, options);
424
423
  const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
425
424
  const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions, 'to.sql');
@@ -28,6 +28,7 @@ function dictAdd( dict, name, entry, duplicateCallback ) {
28
28
  }
29
29
 
30
30
  function dictForEach( dict, callback ) {
31
+ // TODO: probably define an extra dictForEachArray()
31
32
  for (const name in dict) {
32
33
  const entry = dict[name];
33
34
  if (Array.isArray(entry)) {
@@ -35,7 +36,7 @@ function dictForEach( dict, callback ) {
35
36
  }
36
37
  else {
37
38
  callback( entry );
38
- if (Array.isArray(entry.$duplicates))
39
+ if (Array.isArray( entry.$duplicates ))
39
40
  entry.$duplicates.forEach( callback );
40
41
  }
41
42
  }
@@ -11,7 +11,7 @@ module.exports = {
11
11
  'DISTINCT',
12
12
  'EXISTS',
13
13
  'EXTRACT',
14
- 'FALSE',
14
+ 'FALSE', // boolean
15
15
  'FROM',
16
16
  'IN',
17
17
  'KEY',
@@ -22,8 +22,9 @@ module.exports = {
22
22
  'ON',
23
23
  'SELECT',
24
24
  'SOME',
25
+ 'WHEN',
25
26
  'TRIM',
26
- 'TRUE',
27
+ 'TRUE', // boolean
27
28
  'WHERE',
28
29
  'WITH',
29
30
  ],
@@ -47,6 +47,7 @@ const centralMessages = {
47
47
  'anno-undefined-def': { severity: 'Info' }, // for annotate statement (for CSN or CDL path cont)
48
48
  'anno-undefined-element': { severity: 'Info' },
49
49
  'anno-undefined-param': { severity: 'Info' },
50
+ 'anno-unstable-hdbcds': { severity: 'Warning' },
50
51
 
51
52
  'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
52
53
  'args-no-params': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
@@ -60,7 +61,6 @@ const centralMessages = {
60
61
 
61
62
  'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
62
63
 
63
- 'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
64
64
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
65
65
 
66
66
  // Structured types were warned about but made CSN un-recompilable.
@@ -69,7 +69,7 @@ const centralMessages = {
69
69
  // TODO: rename to ref-expected-XYZ
70
70
  'expected-type': { severity: 'Error' },
71
71
  'ref-sloppy-type': { severity: 'Error' },
72
- 'ref-invalid-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
72
+ 'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
73
73
  'expected-actionparam-type': { severity: 'Error' },
74
74
  'ref-sloppy-actionparam-type': { severity: 'Error' },
75
75
  'expected-event-type': { severity: 'Error' },
@@ -89,6 +89,7 @@ const centralMessages = {
89
89
  'query-undefined-element': { severity: 'Error' },
90
90
  'query-unexpected-assoc-hdbcds': { severity: 'Error' },
91
91
  'query-unexpected-structure-hdbcds': { severity: 'Error' },
92
+ 'query-ignoring-param-nullability': { severity: 'Info' },
92
93
 
93
94
  'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
94
95
  'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
@@ -99,6 +100,7 @@ const centralMessages = {
99
100
  'ref-undefined-def': { severity: 'Error' },
100
101
  'ref-undefined-var': { severity: 'Error' },
101
102
  'ref-undefined-element': { severity: 'Error' },
103
+ 'ref-unknown-var': { severity: 'Info' },
102
104
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
103
105
  'ref-undefined-param': { severity: 'Error' },
104
106
  'ref-rejected-on': { severity: 'Error' },
@@ -118,6 +120,7 @@ const centralMessages = {
118
120
  'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
119
121
  'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
120
122
  'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
123
+ 'syntax-csn-expected-length': { severity: 'Error' },
121
124
  'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
122
125
  'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
123
126
  'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
@@ -127,12 +130,15 @@ const centralMessages = {
127
130
 
128
131
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
129
132
 
133
+ 'def-missing-element': { severity: 'Error' },
134
+
130
135
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
131
136
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
132
137
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
133
138
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
134
139
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
135
140
  'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
141
+ 'odata-spec-violation-no-key': { severity: 'Warning' },
136
142
  'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
137
143
  'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
138
144
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
@@ -160,6 +166,11 @@ const centralMessageTexts = {
160
166
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
161
167
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
162
168
  },
169
+ 'syntax-csn-expected-length': {
170
+ std: 'Expected array in $(PROP) to have at least $(N) items',
171
+ one: 'Expected array in $(PROP) to have at least one item',
172
+ suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
173
+ },
163
174
  'ref-undefined-def': {
164
175
  std: 'Artifact $(ART) has not been found',
165
176
  // TODO: proposal 'No definition of $(NAME) found',
@@ -171,17 +182,20 @@ const centralMessageTexts = {
171
182
  std: 'Element $(ART) has not been found',
172
183
  element: 'Artifact $(ART) has no element $(MEMBER)'
173
184
  },
185
+ 'ref-unknown-var': {
186
+ std: 'Replacement $(ID) not found'
187
+ },
174
188
  'ref-rejected-on': {
175
189
  std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
176
190
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
177
191
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
178
192
  },
179
- 'ref-invalid-typeof': {
180
- std: 'Do not use $(KEYWORD) for the type reference here',
181
- type: 'Do not use $(KEYWORD) for the type of a type',
182
- event: 'Do not use $(KEYWORD) for the type of an event',
183
- param: 'Do not use $(KEYWORD) for the type of a parameter',
184
- select: 'Do not use $(KEYWORD) for type references in queries',
193
+ 'type-unexpected-typeof': {
194
+ std: 'Unexpected $(KEYWORD) for the type reference here',
195
+ type: 'Unexpected $(KEYWORD) in type of a type definition',
196
+ event: 'Unexpected $(KEYWORD) for the type of an event',
197
+ param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
198
+ select: 'Unexpected $(KEYWORD) for type references in queries',
185
199
  },
186
200
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
187
201
  'anno-undefined-def': 'Artifact $(ART) has not been found',
@@ -200,16 +214,21 @@ const centralMessageTexts = {
200
214
  param: 'Artifact $(ART) has no parameter $(MEMBER)'
201
215
  },
202
216
 
217
+ 'def-missing-element': {
218
+ std: 'Expecting entity to have at least one non-virtual element',
219
+ view: 'Expecting view to have at least one non-virtual element'
220
+ },
221
+
203
222
  'duplicate-definition': {
204
223
  std: 'Duplicate definition of $(NAME)',
205
224
  absolute: 'Duplicate definition of artifact $(NAME)',
206
- namespace: 'Other definition blocks $(NAME) for namespace name',
225
+ annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
207
226
  element: 'Duplicate definition of element $(NAME)',
208
227
  enum: 'Duplicate definition of enum $(NAME)',
209
228
  key: 'Duplicate definition of key $(NAME)',
210
229
  action: 'Duplicate definition of action or function $(NAME)',
211
230
  param: 'Duplicate definition of parameter $(NAME)',
212
- $tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
231
+ alias: 'Duplicate definition of table alias or mixin $(NAME)',
213
232
  },
214
233
 
215
234
  // TODO: rename to ref-expected-XYZ
@@ -228,6 +247,10 @@ const centralMessageTexts = {
228
247
 
229
248
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
230
249
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
250
+ 'query-ignoring-param-nullability': {
251
+ std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
252
+ sql: 'Ignoring nullability constraint on parameter when generating SQL view'
253
+ },
231
254
 
232
255
  'ref-sloppy-type': 'A type or an element is expected here',
233
256
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -262,6 +285,7 @@ const centralMessageTexts = {
262
285
  std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
263
286
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
264
287
  },
288
+ 'odata-spec-violation-no-key': 'Expected entity to have a primary key',
265
289
  'odata-spec-violation-type': 'Expected element to have a type',
266
290
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
267
291
  'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
@@ -4,7 +4,7 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const term = require('../utils/term');
7
+ const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts } = require('./message-registry');
@@ -15,6 +15,8 @@ const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
+ // term instance for messages
19
+ const colorTerm = term();
18
20
 
19
21
  // Functions ensuring message consistency during runtime with --test-mode
20
22
 
@@ -670,13 +672,16 @@ function ignoreTextTransform() {
670
672
 
671
673
  function transformManyWith( t, sorted ) {
672
674
  return function transformMany( many, r, args, texts ) {
673
- const prop = ['none','one'][ many.length ];
674
- if (!prop || !texts[prop] || args['#'] ) {
675
- const names = many.map(t);
676
- return (sorted ? names.sort() : names).join(', ');
677
- }
675
+ const prop = ['none','one','two'][ many.length ];
676
+ const names = many.map(t);
677
+ if (sorted)
678
+ names.sort();
679
+ if (!prop || !texts[prop] || args['#'] )
680
+ return names.join(', ');
678
681
  r['#'] = prop; // text variant
679
- return many.length && t( many[0] );
682
+ if (many.length === 2)
683
+ r.second = names[1];
684
+ return many.length && names[0];
680
685
  };
681
686
  }
682
687
 
@@ -791,7 +796,7 @@ function replaceInString( text, params ) {
791
796
  start = pattern.lastIndex;
792
797
  }
793
798
  parts.push( text.substring( start ) );
794
- let remain = ('#' in params) ? [] : Object.keys( params ).filter( n => params[n] );
799
+ let remain = (params['#']) ? [] : Object.keys( params ).filter( n => params[n] );
795
800
  return (remain.length)
796
801
  ? parts.join('') + '; ' +
797
802
  remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
@@ -843,7 +848,10 @@ function messageHash(msg) {
843
848
  if(!msg.home)
844
849
  return messageString(msg);
845
850
  const copy = {...msg};
846
- copy.$location = undefined;
851
+ // Note: This is a hack. deduplicateMessages() would otherwise remove
852
+ // all but one message about duplicated artifacts.
853
+ if (!msg.messageId || !msg.messageId.includes('duplicate'))
854
+ copy.$location = undefined;
847
855
  return messageString(copy);
848
856
  }
849
857
 
@@ -853,19 +861,26 @@ function messageHash(msg) {
853
861
  *
854
862
  * Example:
855
863
  * ```txt
856
- * Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
864
+ * Error[message-id]: Can't find type `nu` in this scope
857
865
  * |
858
- * <source>.cds:3:11, at entity:“E”
866
+ * <source>.cds:3:11, at entity:“E”/element:“e
859
867
  * ```
868
+ *
860
869
  * @param {CSN.Message} err
861
870
  * @param {object} [config = {}]
862
871
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
863
872
  * @param {boolean} [config.noMessageId]
864
873
  * @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
865
- * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
874
+ * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
875
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
876
+ * coloring will be used. If 'auto', we will decide based on certain factors such
877
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
878
+ * unset.
866
879
  * @returns {string}
867
880
  */
868
881
  function messageStringMultiline( err, config = {} ) {
882
+ colorTerm.changeColorMode(config ? config.color : 'auto');
883
+
869
884
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
870
885
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
871
886
  const home = !err.home ? '' : ('at ' + err.home);
@@ -878,7 +893,7 @@ function messageStringMultiline( err, config = {} ) {
878
893
  location += ', '
879
894
  }
880
895
  else if (!home)
881
- return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
896
+ return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
882
897
 
883
898
  let lineSpacer = '';
884
899
  if (config.withLineSpacer) {
@@ -886,8 +901,7 @@ function messageStringMultiline( err, config = {} ) {
886
901
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
887
902
  }
888
903
 
889
- // TODO: use ':' before text
890
- return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
904
+ return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
891
905
  }
892
906
 
893
907
  /**
@@ -901,9 +915,15 @@ function messageStringMultiline( err, config = {} ) {
901
915
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
902
916
  * from `lib/utils/file.js`
903
917
  * @param {CSN.Message} err Error object containing all details like line, message, etc.
918
+ * @param {object} [config = {}]
919
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
920
+ * coloring will be used. If 'auto', we will decide based on certain factors such
921
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
922
+ * unset.
904
923
  * @returns {string}
905
924
  */
906
- function messageContext(sourceLines, err) {
925
+ function messageContext(sourceLines, err, config) {
926
+ colorTerm.changeColorMode(config ? config.color : 'auto');
907
927
  const MAX_COL_LENGTH = 100;
908
928
 
909
929
  const loc = err.$location;
@@ -955,7 +975,7 @@ function messageContext(sourceLines, err) {
955
975
  // Indicate that the error is further to the right.
956
976
  if (endColumn === MAX_COL_LENGTH)
957
977
  highlighter = highlighter.replace(' ^', '..^');
958
- msg += indent + '| ' + term.asSeverity(severity, highlighter);
978
+ msg += indent + '| ' + colorTerm.severity(severity, highlighter);
959
979
 
960
980
  } else if (maxLine !== endLine) {
961
981
  // error spans more lines which we don't print
@@ -1068,7 +1088,7 @@ function deduplicateMessages( messages ) {
1068
1088
 
1069
1089
  function shortArtName( art ) {
1070
1090
  const { name } = art;
1071
- if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null ) &&
1091
+ if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1072
1092
  !name.absolute.includes(':'))
1073
1093
  return quote.name( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
1074
1094
  return artName( art );
package/lib/base/model.js CHANGED
@@ -19,7 +19,6 @@ const queryOps = {
19
19
  */
20
20
  const availableBetaFlags = {
21
21
  // enabled by --beta-mode
22
- foreignKeyConstraints: true,
23
22
  toRename: true,
24
23
  addTextsLanguageAssoc: true,
25
24
  assocsWithParams: true,
@@ -28,7 +27,6 @@ const availableBetaFlags = {
28
27
  ignoreAssocPublishingInUnion: true,
29
28
  nestedProjections: true,
30
29
  enableUniversalCsn: true,
31
- windowFunctions: true,
32
30
  // disabled by --beta-mode
33
31
  nestedServices: false,
34
32
  };
@@ -55,15 +53,18 @@ function isBetaEnabled( options, feature ) {
55
53
  /**
56
54
  * Test for deprecated feature, stored in option `deprecated`.
57
55
  * With that, the value of `deprecated` is a dictionary of feature=>Boolean.
56
+ * If no `feature` is provided, checks if any deprecated option is set.
58
57
  *
59
58
  * Please do not move this function to the "option processor" code.
60
59
  *
61
60
  * @param {object} options Options
62
- * @param {string} feature Feature to check for
61
+ * @param {string} [feature] Feature to check for
63
62
  * @returns {boolean}
64
63
  */
65
- function isDeprecatedEnabled( options, feature ) {
64
+ function isDeprecatedEnabled( options, feature = null ) {
66
65
  const { deprecated } = options;
66
+ if(!feature)
67
+ return !!deprecated;
67
68
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
68
69
  }
69
70
 
@@ -30,7 +30,11 @@ function createOptionProcessor() {
30
30
  optionClashes: [],
31
31
  option,
32
32
  command,
33
- positionalArgument,
33
+ positionalArgument: (argumentDefinition) => {
34
+ // Default positional arguments; may be overwritten by commands.
35
+ _positionalArguments(argumentDefinition);
36
+ return optionProcessor;
37
+ },
34
38
  help,
35
39
  processCmdLine,
36
40
  verifyOptions,
@@ -66,7 +70,12 @@ function createOptionProcessor() {
66
70
  /** @type {object} */
67
71
  const command = {
68
72
  options: {},
73
+ positionalArguments: [],
69
74
  option,
75
+ positionalArgument: (argumentDefinition) => {
76
+ _positionalArguments(argumentDefinition, command.positionalArguments);
77
+ return command;
78
+ },
70
79
  help,
71
80
  ..._parseCommandString(cmdString)
72
81
  };
@@ -96,28 +105,34 @@ function createOptionProcessor() {
96
105
  }
97
106
 
98
107
  /**
99
- * Adds positional arguments to the command line processor. Instructs the processor
100
- * to either require N positional arguments or a dynamic number (but at least one)
101
- * @param {string} positionalArgumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
108
+ * Set the positional arguments to the command line processor. Instructs the processor
109
+ * to either require N positional arguments or a dynamic number (but at least one).
110
+ * Note that you can only call this function once. Only the last invocation sets
111
+ * the positional arguments.
112
+ *
113
+ * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
114
+ * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
102
115
  */
103
- function positionalArgument(positionalArgumentDefinition) {
104
- if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
116
+ function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
117
+ if (argList.find((arg) => arg.isDynamic)) {
105
118
  throw new Error(`Can't add positional arguments after a dynamic one`);
106
119
  }
107
120
 
108
- const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
109
- const args = positionalArgumentDefinition.split(' ');
121
+ const registeredNames = argList.map((arg) => arg.name);
122
+ const args = argumentDefinition.split(' ');
110
123
 
111
124
  for (const arg of args) {
112
- const argName = arg.replace('<', '').replace('>', '').replace('...', '');
125
+ // Remove braces, dots and camelify.
126
+ const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
127
+
113
128
  if (registeredNames.includes(argName)) {
114
- throw new Error(`Duplicate positional argument ${arg}`);
129
+ throw new Error(`Duplicate positional argument: ${arg}`);
115
130
  }
116
131
  if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
117
132
  throw new Error(`Unknown positional argument syntax: ${arg}`)
118
133
  }
119
134
 
120
- optionProcessor.positionalArguments.push({
135
+ argList.push({
121
136
  name: argName,
122
137
  isDynamic: isDynamicPositionalArgument(arg),
123
138
  required: true
@@ -125,7 +140,6 @@ function createOptionProcessor() {
125
140
 
126
141
  registeredNames.push(argName);
127
142
  }
128
- return optionProcessor;
129
143
  }
130
144
 
131
145
  /**
@@ -390,21 +404,38 @@ function createOptionProcessor() {
390
404
  }
391
405
 
392
406
  // Complain about first missing positional arguments
393
- const missingArg = optionProcessor.positionalArguments.find((arg) => arg.required && !result.args[arg.name]);
407
+ const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
394
408
  if (missingArg) {
395
- result.errors.push(`Missing positional argument: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
409
+ const forCommand = result.command ? ` for '${ result.command }'` : '';
410
+ result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
396
411
  }
397
412
 
398
413
  return result;
399
414
 
415
+ /**
416
+ * Specific commands may have custom positional arguments.
417
+ * If the current one does, use it instead of the defaults.
418
+ *
419
+ * @returns {object[]} Array of positional argument configurations.
420
+ */
421
+ function getCurrentPositionArguments() {
422
+ const cmd = optionProcessor.commands[result.command];
423
+ const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
+ return args;
425
+ }
426
+
400
427
  function processPositionalArgument(argumentValue) {
401
- if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
428
+ const argList = getCurrentPositionArguments();
429
+ if ( result.args.length === 0 && argList.length === 0 )
402
430
  return;
403
- const inBounds = result.args.length < optionProcessor.positionalArguments.length;
404
- const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;
405
- const nextUnsetArgument = optionProcessor.positionalArguments[lastIndex];
431
+ const inBounds = result.args.length < argList.length;
432
+ const lastIndex = inBounds ? result.args.length : argList.length - 1;
433
+ const nextUnsetArgument = argList[lastIndex];
406
434
  if (!inBounds && !nextUnsetArgument.isDynamic) {
407
- result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
435
+ if (result.command)
436
+ result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
437
+ else
438
+ result.errors.push(`Too many arguments. Expected ${argList.length}`);
408
439
  return;
409
440
  }
410
441
  result.args.length += 1;
@@ -453,7 +484,7 @@ function createOptionProcessor() {
453
484
  }
454
485
  } else {
455
486
  result.options[opt.camelName] = value;
456
- if (opt.validValues && !opt.validValues.includes(value)) {
487
+ if (opt.validValues && !opt.validValues.some( validValue => validValue.toLowerCase() === value.toLowerCase() ) ) {
457
488
  result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
458
489
  }
459
490
  }
@@ -486,7 +517,10 @@ function createOptionProcessor() {
486
517
  }
487
518
 
488
519
  if(options) {
489
- ['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
520
+ [
521
+ 'defaultBinaryLength', 'defaultStringLength',
522
+ /*'length', 'precision', 'scale'*/
523
+ ].forEach(facet => {
490
524
  if(options[facet] && isNaN(options[facet])) {
491
525
  result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
492
526
  } else {
@@ -577,12 +611,12 @@ function isLongOption(opt) {
577
611
 
578
612
  // Check if 'opt' looks like a "<foobar>" parameter
579
613
  function isParam(opt) {
580
- return /^<[a-zA-Z]+>$/.test(opt);
614
+ return /^<[a-zA-Z-]+>$/.test(opt);
581
615
  }
582
616
 
583
617
  // Check if 'arg' looks like "<foobar...>"
584
618
  function isDynamicPositionalArgument(arg) {
585
- return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
619
+ return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
586
620
  }
587
621
 
588
622
  module.exports = {
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  * @param {CSN.Path} path Path to the artifact
13
13
  */
14
14
  function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
15
- if (artifact.kind === 'entity' && !artifact.query && isPersistedOnDatabase(artifact)) {
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
16
  if (!artifact.elements || !hasRealElements(artifact.elements))
17
- this.error(null, path, "Artifacts containing only virtual or empty elements can't be deployed");
17
+ this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
18
18
  }
19
19
  }
20
20
 
@@ -23,6 +23,10 @@ function validateSelectItems(query) {
23
23
  'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
24
24
  }
25
25
  }
26
+ else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
27
+ this.error(null, selectItem.$path,
28
+ 'Window functions are not supported by SAP HANA CDS');
29
+ }
26
30
  });
27
31
  // .call() with 'this' to ensure we have access to the options
28
32
  rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { getVariableReplacement } = require('../model/csnUtils');
4
+
3
5
  // We only care about the "wild" ones - $at is validated by the compiler
4
6
  const magicVariables = {
5
7
  $user: [
@@ -17,7 +19,7 @@ const magicVariables = {
17
19
  *
18
20
  * Valid ways:
19
21
  * - We know what to do -> $user.id on HANA
20
- * - The user tells us what to do -> options.magicVars
22
+ * - The user tells us what to do -> options.variableReplacements
21
23
  *
22
24
  * @param {object} parent Object with the ref as a property
23
25
  * @param {string} name Name of the ref property on parent
@@ -28,8 +30,9 @@ function unknownMagicVariable(parent, name, ref) {
28
30
  const [ head, ...rest ] = ref;
29
31
  const tail = rest.join('.');
30
32
  const magicVariable = magicVariables[head];
31
- if (magicVariable && magicVariable.indexOf(tail) === -1)
32
- this.error(null, parent.$location, { id: tail, elemref: parent }, 'Magic variable is not supported - path $(ELEMREF), step $(ID)');
33
+ if (magicVariable && magicVariable.indexOf(tail) === -1 &&
34
+ getVariableReplacement(ref, this.options) === null)
35
+ this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
33
36
  }
34
37
  }
35
38