@sap/cds-compiler 4.5.0 → 4.6.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 (65) hide show
  1. package/CHANGELOG.md +50 -7
  2. package/bin/cdsc.js +13 -11
  3. package/doc/CHANGELOG_BETA.md +6 -0
  4. package/lib/api/main.js +256 -115
  5. package/lib/api/options.js +8 -0
  6. package/lib/base/message-registry.js +17 -4
  7. package/lib/base/messages.js +15 -3
  8. package/lib/base/model.js +1 -0
  9. package/lib/base/optionProcessorHelper.js +45 -176
  10. package/lib/checks/elements.js +32 -34
  11. package/lib/checks/enricher.js +39 -3
  12. package/lib/checks/validator.js +2 -3
  13. package/lib/compiler/assert-consistency.js +2 -1
  14. package/lib/compiler/builtins.js +20 -4
  15. package/lib/compiler/checks.js +30 -6
  16. package/lib/compiler/define.js +31 -9
  17. package/lib/compiler/populate.js +5 -1
  18. package/lib/compiler/resolve.js +26 -21
  19. package/lib/compiler/shared.js +19 -9
  20. package/lib/compiler/tweak-assocs.js +82 -107
  21. package/lib/compiler/utils.js +2 -1
  22. package/lib/edm/annotations/edmJson.js +23 -22
  23. package/lib/edm/annotations/genericTranslation.js +14 -4
  24. package/lib/edm/csn2edm.js +24 -10
  25. package/lib/edm/edmInboundChecks.js +1 -2
  26. package/lib/edm/edmPreprocessor.js +11 -9
  27. package/lib/edm/edmUtils.js +5 -2
  28. package/lib/gen/Dictionary.json +3 -1
  29. package/lib/gen/language.checksum +1 -1
  30. package/lib/gen/language.interp +4 -1
  31. package/lib/gen/language.tokens +1 -0
  32. package/lib/gen/languageParser.js +5253 -5214
  33. package/lib/json/to-csn.js +7 -1
  34. package/lib/language/antlrParser.js +19 -1
  35. package/lib/language/errorStrategy.js +21 -4
  36. package/lib/language/genericAntlrParser.js +9 -11
  37. package/lib/main.d.ts +28 -3
  38. package/lib/main.js +3 -0
  39. package/lib/model/csnRefs.js +4 -1
  40. package/lib/model/csnUtils.js +12 -7
  41. package/lib/optionProcessor.js +21 -19
  42. package/lib/render/manageConstraints.js +13 -29
  43. package/lib/render/toCdl.js +18 -15
  44. package/lib/render/toHdbcds.js +59 -28
  45. package/lib/render/toRename.js +6 -10
  46. package/lib/render/toSql.js +57 -82
  47. package/lib/render/utils/common.js +17 -0
  48. package/lib/transform/.eslintrc.json +9 -1
  49. package/lib/transform/addTenantFields.js +228 -0
  50. package/lib/transform/db/applyTransformations.js +27 -31
  51. package/lib/transform/db/assertUnique.js +4 -4
  52. package/lib/transform/db/cdsPersistence.js +1 -1
  53. package/lib/transform/db/flattening.js +68 -69
  54. package/lib/transform/db/temporal.js +1 -1
  55. package/lib/transform/draft/db.js +2 -16
  56. package/lib/transform/draft/odata.js +3 -3
  57. package/lib/transform/effective/associations.js +3 -5
  58. package/lib/transform/effective/main.js +6 -9
  59. package/lib/transform/forOdata.js +13 -9
  60. package/lib/transform/forRelationalDB.js +36 -17
  61. package/lib/transform/odata/toFinalBaseType.js +3 -3
  62. package/lib/transform/odata/typesExposure.js +14 -5
  63. package/lib/transform/transformUtils.js +47 -34
  64. package/lib/transform/translateAssocsToJoins.js +33 -8
  65. package/package.json +2 -2
@@ -67,6 +67,7 @@ const privateOptions = [
67
67
  'noRecompile',
68
68
  'internalMsg',
69
69
  'disableHanaComments', // in case of issues with hana comment rendering
70
+ 'tenantAsColumn', // not published yet
70
71
  'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(v5): Remove option
71
72
  ];
72
73
 
@@ -158,6 +159,13 @@ module.exports = {
158
159
  };
159
160
  return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.edmx');
160
161
  },
162
+ odata: (options) => {
163
+ const hardOptions = { combined: true, toOdata: true };
164
+ const defaultOptions = {
165
+ odataVersion: 'v4', odataFormat: 'flat',
166
+ };
167
+ return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.odata');
168
+ },
161
169
  },
162
170
  for: { // TODO: Rename version to oDataVersion
163
171
  odata: (options) => {
@@ -122,7 +122,7 @@ const centralMessages = {
122
122
  'ref-undefined-var': { severity: 'Error' },
123
123
  'ref-undefined-element': { severity: 'Error' },
124
124
  'anno-undefined-element': { severity: 'Error' },
125
- 'ref-unknown-var': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
125
+ 'ref-unknown-var': { severity: 'Info' },
126
126
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: 'v4' },
127
127
  // does not hurt us, but makes it tedious to detect parameter refs
128
128
  'ref-undefined-param': { severity: 'Error' },
@@ -254,6 +254,7 @@ const centralMessageTexts = {
254
254
  std: 'Unexpected option combination: $(OPTION) and $(PROP)', // unused
255
255
  'beta-no-test':'Option $(OPTION) was used. This option should not be used in productive scenarios!',
256
256
  },
257
+ 'api-unexpected-option': 'Option $(OPTION) can\'t be used in backend $(MODULE)',
257
258
  'api-invalid-lookup-dir': {
258
259
  std: '',
259
260
  slash: 'Expected directory $(VALUE) in option $(OPTION) to end with $(OTHERVALUE)',
@@ -585,6 +586,7 @@ const centralMessageTexts = {
585
586
  std: 'Variable $(ID) has not been found',
586
587
  alias: 'Variable $(ID) has not been found. Use table alias $(ALIAS) to refer an element with the same name',
587
588
  self: 'Variable $(ID) has not been found. Use $(ALIAS) to refer an element with the same name',
589
+ value: 'No value found for variable $(ID). Use option $(OPTION) to specify a value for $(ID)',
588
590
  },
589
591
  'ref-unknown-var': {
590
592
  std: 'No replacement found for special variable $(ID)'
@@ -659,6 +661,12 @@ const centralMessageTexts = {
659
661
  'on-condition': 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)',
660
662
  calc: 'Unexpected arguments in path $(ELEMREF) of stored calculated element; only simple paths can be used here'
661
663
  },
664
+ 'ref-unsupported-type': {
665
+ std: 'Type $(TYPE) is not supported',
666
+ hana: 'Type $(TYPE) is only supported for SQL dialect $(VALUE), not $(OTHERVALUE)',
667
+ hdbcds:'Type $(TYPE) is not supported in HDBCDS',
668
+ odata: 'Type $(TYPE) is not supported for OData'
669
+ },
662
670
 
663
671
  // TODO: Better text ?
664
672
  'rewrite-not-supported': 'The ON-condition is not rewritten here - provide an explicit ON-condition',
@@ -852,8 +860,10 @@ const centralMessageTexts = {
852
860
  param: 'A type, an element, or a service entity is expected here',
853
861
  event: 'A type, an element, an event, or a service entity is expected here',
854
862
  },
855
- // TODO: text variant if the association does not start an entity
856
- 'ref-invalid-source': 'A query source must be an entity or an association',
863
+ 'ref-invalid-source': {
864
+ std: 'A query source must be an entity or an association',
865
+ event: 'An event\'s projection source must be an entity, structured type, event or an association',
866
+ },
857
867
  'extend-columns': {
858
868
  std: 'Artifact $(ART) can\'t be extended with columns, only simple views/projections without JOINs and UNIONs can',
859
869
  join: 'Artifact $(ART) can\'t be extended with columns, because it contains a JOIN',
@@ -910,6 +920,7 @@ const centralMessageTexts = {
910
920
  std: 'Specified element $(NAME) differs from inferred element in property $(PROP)',
911
921
  type: 'Expected type of specified element $(NAME) to be the same as the inferred element\'s type',
912
922
  typeName: 'Expected type $(TYPE) of specified element $(NAME) to be the same as the inferred element\'s type $(OTHERTYPE)',
923
+ typeExtra: 'Element $(NAME) does not have an inferred type property, but an unexpected type $(TYPE) was specified',
913
924
  missing: 'Specified element $(NAME) differs from inferred element: it is missing property $(PROP)',
914
925
  extra: 'Specified element $(NAME) differs from inferred element: it has an additional property $(PROP)',
915
926
  target: 'Expected target $(TARGET) of specified element $(NAME) to be the same as the inferred element\'s target $(ART)',
@@ -958,7 +969,8 @@ const centralMessageTexts = {
958
969
  'to-structure': 'Can\'t cast to a structured type',
959
970
  'from-structure': 'Structured elements can\'t be cast to a different type',
960
971
  'expr-to-structure': 'Can\'t cast an expression to a structured type',
961
- 'val-to-structure': 'Can\'t cast $(VALUE) to a structured type'
972
+ 'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
973
+ 'from-assoc': 'Invalid type cast on an association'
962
974
  },
963
975
 
964
976
  // -----------------------------------------------------------------------------------
@@ -1096,6 +1108,7 @@ const centralMessageTexts = {
1096
1108
  'unknown': '$(TYPE) is not a known vocabulary type for $(ANNO)',
1097
1109
  'abstract': 'Unexpected abstract type $(TYPE) for $(ANNO), use $(CODE) to specify a concrete type',
1098
1110
  'derived': 'Expected specified $(TYPE) to be derived from $(NAME) for $(ANNO)',
1111
+ 'literal': 'Expected value $(RAWVALUE) of specified $(CODE) to be a string literal for $(ANNO)'
1099
1112
  },
1100
1113
  'odata-anno-def': {
1101
1114
  // All $(ANNO) w/o sub elements, term qualifiers and context stack
@@ -305,14 +305,14 @@ function makeMessageFunction( model, options, moduleName = null ) {
305
305
  throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
306
306
  }
307
307
 
308
- const hasMessageArray = !!options.messages;
308
+ const hasMessageArray = Array.isArray(options.messages);
309
309
  /**
310
310
  * Array of collected compiler messages. Only use it for debugging. Will not
311
311
  * contain the messages created during a `callTransparently` call.
312
312
  *
313
313
  * @type {CompileMessage[]}
314
314
  */
315
- let messages = options.messages || [];
315
+ let messages = hasMessageArray ? options.messages : [];
316
316
  /**
317
317
  * Whether an error was emitted in the module. Also includes reclassified errors.
318
318
  * @type {boolean}
@@ -332,6 +332,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
332
332
  throwWithAnyError,
333
333
  callTransparently,
334
334
  moduleName,
335
+ setModel,
335
336
  };
336
337
 
337
338
  function _message( id, location, textOrArguments, severity, texts = null ) {
@@ -550,6 +551,16 @@ function makeMessageFunction( model, options, moduleName = null ) {
550
551
  messages = backup;
551
552
  return collected;
552
553
  }
554
+
555
+ /**
556
+ * Change the model used to calculate CSN locations.
557
+ * This is necessary if you change the model heavily and rely on $paths relative to the new model.
558
+ *
559
+ * @param {CSN.Model} _model
560
+ */
561
+ function setModel( _model ) {
562
+ model = _model;
563
+ }
553
564
  }
554
565
 
555
566
  /**
@@ -679,6 +690,7 @@ const paramsTransform = {
679
690
  meta: quote.angle,
680
691
  othermeta: quote.angle,
681
692
  keyword,
693
+ module: quote.single,
682
694
  // more complex convenience:
683
695
  names: transformManyWith( quoted ),
684
696
  number: quote.single, // number cited from source or expected in source
@@ -1583,7 +1595,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
1583
1595
  else if (step === 'targetAspect') {
1584
1596
  // skip
1585
1597
  }
1586
- else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
1598
+ else if (step === 'xpr' || step === 'default' || step === 'ref' || step === 'as' || step === 'value') {
1587
1599
  break; // don't go into xprs, refs, aliases, values, etc.
1588
1600
  }
1589
1601
  else if (step === 'returns') {
package/lib/base/model.js CHANGED
@@ -38,6 +38,7 @@ const availableBetaFlags = {
38
38
  effectiveCsn: true,
39
39
  tenantVariable: true,
40
40
  calcAssoc: true,
41
+ vectorType: true,
41
42
  // disabled by --beta-mode
42
43
  nestedServices: false,
43
44
  };
@@ -16,7 +16,8 @@
16
16
  * optionProcessor.command('toXyz')
17
17
  * .help(`Help text for command "toXyz")
18
18
  * .option('-y --y-in-long-form')
19
- * .option(' --bar-wiz <w>', ['bla', 'foo'])
19
+ * .option(' --bar-wiz <w>', { valid: ['bla', 'foo'] })
20
+ * .option('-z --name-in-cdsc', { optionName: 'nameInOptions' })
20
21
  * ```
21
22
  *
22
23
  * Options *must* have a long form, can have at most one <param>, and optionally
@@ -34,29 +35,27 @@ function createOptionProcessor() {
34
35
  optionClashes: [],
35
36
  option,
36
37
  command,
37
- positionalArgument: (argumentDefinition) => {
38
+ positionalArgument(argumentDefinition) {
38
39
  // Default positional arguments; may be overwritten by commands.
39
40
  _setPositionalArguments(argumentDefinition);
40
41
  return optionProcessor;
41
42
  },
42
43
  help,
43
44
  processCmdLine,
44
- verifyOptions,
45
- camelOptionsForCommand,
46
- // TODO: Why exported?
47
- _parseCommandString,
48
- _parseOptionString,
49
45
  };
50
46
  return optionProcessor;
51
47
 
52
48
  /**
53
49
  * API: Define a general option.
54
50
  * @param {string} optString Option string describing the command line option.
55
- * @param {string[]} [validValues] Array of valid values for the options.
56
- * @param {object} [options] Further options such as `ignoreCase: true`
51
+ * @param {object} [options] Further options such as `ignoreCase: true` and `valid: []`.
52
+ * @param {string[]} [options.valid] Valid values for the option.
53
+ * @param {string[]} [options.ignoreCase] Ignore the case for "options.valid".
54
+ * @param {string[]} [options.optionName] Name of the option after parsing CLI arguments.
55
+ * Defaults to the camelified name of the long name.
57
56
  */
58
- function option( optString, validValues, options ) {
59
- return _addOption(optionProcessor, optString, validValues, options);
57
+ function option( optString, options ) {
58
+ return _addOption(optionProcessor, optString, options);
60
59
  }
61
60
 
62
61
  /**
@@ -78,7 +77,7 @@ function createOptionProcessor() {
78
77
  options: {},
79
78
  positionalArguments: [],
80
79
  option: commandOption,
81
- positionalArgument: (argumentDefinition) => {
80
+ positionalArgument(argumentDefinition) {
82
81
  _setPositionalArguments(argumentDefinition, cmd.positionalArguments);
83
82
  return cmd;
84
83
  },
@@ -99,8 +98,8 @@ function createOptionProcessor() {
99
98
  return cmd;
100
99
 
101
100
  // Command API: Define a command option
102
- function commandOption( optString, validValues, options ) {
103
- return _addOption(cmd, optString, validValues, options);
101
+ function commandOption( optString, options ) {
102
+ return _addOption(cmd, optString, options);
104
103
  }
105
104
 
106
105
  // Command API: Define the command help text
@@ -124,13 +123,15 @@ function createOptionProcessor() {
124
123
  if (argList.find(arg => arg.isDynamic))
125
124
  throw new Error('Can\'t add positional arguments after a dynamic one');
126
125
 
127
-
128
126
  const registeredNames = argList.map(arg => arg.name);
129
127
  const args = argumentDefinition.split(' ');
130
128
 
131
129
  for (const arg of args) {
132
130
  // Remove braces, dots and camelify.
133
- const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
131
+ const argName = arg.replace('<', '')
132
+ .replace('>', '')
133
+ .replace('...', '')
134
+ .replace(/[ -]./g, s => s.substring(1).toUpperCase());
134
135
 
135
136
  if (registeredNames.includes(argName))
136
137
  throw new Error(`Duplicate positional argument: ${ arg }`);
@@ -138,7 +139,6 @@ function createOptionProcessor() {
138
139
  if (!isParam(arg) && !isDynamicPositionalArgument(arg))
139
140
  throw new Error(`Unknown positional argument syntax: ${ arg }`);
140
141
 
141
-
142
142
  argList.push({
143
143
  name: argName,
144
144
  isDynamic: isDynamicPositionalArgument(arg),
@@ -157,65 +157,42 @@ function createOptionProcessor() {
157
157
  * @private
158
158
  * @see option()
159
159
  */
160
- function _addOption( cmd, optString, validValues, options ) {
161
- const cliOpt = _parseOptionString(optString, validValues);
160
+ function _addOption( cmd, optString, options ) {
161
+ const cliOpt = _parseOptionString(optString, options);
162
162
  Object.assign(cliOpt, options);
163
- _addLongOption(cmd, cliOpt.longName, cliOpt);
164
- _addShortOption(cmd, cliOpt.shortName, cliOpt);
163
+ _addOptionName(cmd, cliOpt.longName, cliOpt);
164
+ _addOptionName(cmd, cliOpt.shortName, cliOpt);
165
165
 
166
166
  for (const alias of cliOpt.aliases || []) {
167
167
  const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
168
- _addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
168
+ _addOptionName(cmd, alias, aliasOpt); // use same optionName, etc. for alias
169
169
  }
170
170
 
171
171
  return cmd;
172
172
  }
173
173
 
174
174
  /**
175
- * Internal: Add longName to the list of options.
176
- * Throws if the option is already registered in the given command context.
177
- * or in the given command.
178
- * `longName` may differ from `opt.longName`, e.g. for aliases.
179
- *
180
- * @private
181
- * @see _addOption()
182
- */
183
- function _addLongOption( cmd, longName, opt ) {
184
- if (cmd.options[longName]) {
185
- throw new Error(`Duplicate assignment for long option ${ longName }`);
186
- }
187
- else if (optionProcessor.options[longName]) {
188
- // This path is only taken if optString is for commands
189
- optionProcessor.optionClashes.push({
190
- option: longName,
191
- description: `Command '${ cmd.longName }' has option clash with general options for: ${ longName }`,
192
- });
193
- }
194
- cmd.options[longName] = opt;
195
- }
196
- /**
197
- * Internal: Add shortName to the list of options.
175
+ * Internal: Add a name to the list of options.
198
176
  * Throws if the option is already registered in the given command context.
199
177
  * or in the given command.
200
- * `longName` may differ from `opt.longName`, e.g. for aliases.
201
178
  *
202
179
  * @private
203
180
  * @see _addOption()
204
181
  */
205
- function _addShortOption( cmd, shortName, opt ) {
206
- if (!shortName)
182
+ function _addOptionName( cmd, name, opt ) {
183
+ if (!name)
207
184
  return;
208
- if (cmd.options[shortName]) {
209
- throw new Error(`Duplicate assignment for short option ${ shortName }`);
185
+ if (cmd.options[name]) {
186
+ throw new Error(`Duplicate assignment for option ${ name }`);
210
187
  }
211
- else if (optionProcessor.options[shortName]) {
188
+ else if (optionProcessor.options[name]) {
212
189
  // This path is only taken if optString is for commands
213
190
  optionProcessor.optionClashes.push({
214
- option: shortName,
215
- description: `Command '${ cmd.longName }' has option clash with general options for: ${ shortName }`,
191
+ option: name,
192
+ description: `Command '${ cmd.longName }' has option clash with general options for: ${ name }`,
216
193
  });
217
194
  }
218
- cmd.options[shortName] = opt;
195
+ cmd.options[name] = opt;
219
196
  }
220
197
 
221
198
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -251,11 +228,11 @@ function createOptionProcessor() {
251
228
  // {
252
229
  // longName: '--foo-bar',
253
230
  // shortName: '-f',
254
- // camelName: 'fooBar',
231
+ // optionName: 'fooBar', // or options.optionName if provided
255
232
  // param: '<p>'
256
- // validValues
233
+ // valid
257
234
  // }
258
- function _parseOptionString( optString, validValues ) {
235
+ function _parseOptionString( optString, options ) {
259
236
  let longName;
260
237
  let shortName;
261
238
  let param;
@@ -294,11 +271,11 @@ function createOptionProcessor() {
294
271
  if (!longName)
295
272
  throw new Error(`Invalid option description, missing long name: ${ optString }`);
296
273
 
297
- if (!param && validValues)
274
+ if (!param && options?.valid)
298
275
  throw new Error(`Option description has valid values but no param: ${ optString }`);
299
276
 
300
- if (validValues) {
301
- validValues.forEach((value) => {
277
+ if (options?.valid) {
278
+ options.valid.forEach((value) => {
302
279
  if (typeof value !== 'string')
303
280
  throw new Error(`Valid values must be of type string: ${ optString }`);
304
281
  });
@@ -307,9 +284,9 @@ function createOptionProcessor() {
307
284
  return {
308
285
  longName,
309
286
  shortName,
310
- camelName: camelifyLongOption(longName),
287
+ optionName: options?.option ?? camelifyLongOption(longName),
311
288
  param,
312
- validValues,
289
+ valid: options?.valid,
313
290
  isAlias: false, // default
314
291
  };
315
292
  }
@@ -520,10 +497,10 @@ function createOptionProcessor() {
520
497
  if (currentCommand) {
521
498
  if (!result.options[currentCommand])
522
499
  result.options[currentCommand] = {};
523
- result.options[currentCommand][currentOption.camelName] = val;
500
+ result.options[currentCommand][currentOption.optionName] = val;
524
501
  }
525
502
  else {
526
- result.options[currentOption.camelName] = val;
503
+ result.options[currentOption.optionName] = val;
527
504
  }
528
505
  }
529
506
 
@@ -542,7 +519,7 @@ function createOptionProcessor() {
542
519
  function reportInvalidValue( opt, value ) {
543
520
  const shortOption = opt.shortName ? `${ opt.shortName }, ` : '';
544
521
  const errors = currentCommand ? result.cmdErrors : result.errors;
545
- errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.validValues }]`);
522
+ errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.valid }]`);
546
523
  }
547
524
 
548
525
  /**
@@ -580,114 +557,14 @@ function createOptionProcessor() {
580
557
  }
581
558
  }
582
559
 
583
- // Assuming that 'options' came from this option processor, verify options therein.
584
- // If 'command' is supplied, check only 'options.command', otherwise check
585
- // only top-level options
586
- // Return an array of complaints (possibly empty)
587
- function verifyOptions( options, commandName = undefined, silent = false ) {
588
- const result = [];
589
- let opts;
590
-
591
- if ((options.betaMode || options.beta) && !options.testMode && !silent) {
592
- const mode = options.beta ? 'beta' : 'beta-mode';
593
- result.push(`Option --${ mode } was used. This option should not be used in productive scenarios!`);
594
- }
595
-
596
- if (options) {
597
- [
598
- 'defaultBinaryLength', 'defaultStringLength',
599
- /* 'length', 'precision', 'scale' */
600
- ].forEach((facet) => {
601
- if (options[facet] && isNaN(options[facet]))
602
- result.push(`Invalid value "${ options[facet] }" for option "--${ facet }" - not an Integer`);
603
- else
604
- options[facet] = parseInt(options[facet]);
605
- });
606
- }
607
-
608
- if (commandName) {
609
- const cmd = optionProcessor.commands[commandName];
610
- if (!cmd)
611
- throw new Error(`Expected existing command: "${ cmd }"`);
612
-
613
- opts = cmd.options;
614
- options = options[cmd] || {};
615
- if (typeof options === 'boolean') {
616
- // Special case: command without options
617
- options = {};
618
- }
619
- }
620
- else {
621
- opts = optionProcessor.options;
622
- }
623
- // Look at each supplied option
624
- for (const camelName in options) {
625
- const opt = opts[uncamelifyLongOption(camelName)];
626
- let error;
627
- if (!opt) {
628
- // Don't report commands in top-level options
629
- if ((commandName || !optionProcessor.commands[camelName]) && !silent) {
630
- const prefix = commandName ? `${commandName}.` : '';
631
- error = `Unknown option "${prefix}${camelName}"`;
632
- }
633
- }
634
- else {
635
- const param = options[camelName];
636
- error = verifyOptionParam(param, opt, commandName ? `${ commandName }.` : '');
637
- }
638
- if (error)
639
- result.push(error);
640
- }
641
- // hard-coded option dependencies (they disappear with command)
642
- return result;
643
-
644
- /**
645
- * Verify parameter value 'param' against option definition 'opt'. Return an error
646
- * string or false for an accepted param. Use 'prefix' when mentioning the option name.
647
- *
648
- * @param param
649
- * @param opt
650
- * @param prefix
651
- * @return {string|boolean}
652
- */
653
- function verifyOptionParam( param, opt, prefix ) {
654
- if (opt.param) {
655
- // Parameter is required for this option
656
- if (typeof param === 'boolean')
657
- return `Missing value for option "${ prefix }${ opt.camelName }"`;
658
- else if (!isValidOptionValue(opt, param))
659
- return `Invalid value "${ param }" for option "${ prefix }${ opt.camelName }" - use one of [${ opt.validValues }]`;
660
-
661
- return false;
662
- }
663
- // Option does not expect a parameter
664
- if (typeof param !== 'boolean') {
665
- // FIXME: Might be a bit too strict in case of internal sub-options like 'forHana' etc...
666
- return `Expecting boolean value for option "${ prefix }${ opt.camelName }"`;
667
- }
668
-
669
- return false;
670
- }
671
- }
672
-
673
560
  function isValidOptionValue( opt, value ) {
674
561
  // Explicitly convert to string, input 'value' may be boolean
675
562
  value = String(value);
676
- if (!opt.validValues || !opt.validValues.length)
563
+ if (!opt.valid?.length)
677
564
  return true;
678
565
  if (opt.ignoreCase)
679
- return opt.validValues.some( valid => valid.toLowerCase() === value.toLowerCase() );
680
- return opt.validValues.includes(value);
681
- }
682
-
683
- // Return an array of unique camelNames of the options for the specified command
684
- // If invalid command -> an empty array
685
- function camelOptionsForCommand( cmd ) {
686
- if (!cmd || !optionProcessor.commands[cmd])
687
- return [];
688
- cmd = optionProcessor.commands[cmd];
689
- const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
690
- return [ ...new Set(names) ];
566
+ return opt.valid.some( valid => valid.toLowerCase() === value.toLowerCase() );
567
+ return opt.valid.includes(value);
691
568
  }
692
569
  }
693
570
 
@@ -713,14 +590,6 @@ function camelifyLongOption( opt ) {
713
590
  return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
714
591
  }
715
592
 
716
- /**
717
- * Return a long option name like "--foo-bar" for a camel-case name "fooBar"
718
- */
719
- function uncamelifyLongOption( opt ) {
720
- const longForm = opt.replace(/[A-Z]/g, s => `-${ s.toLowerCase() }`);
721
- return `--${ longForm }`;
722
- }
723
-
724
593
  /**
725
594
  * Check if 'opt' looks like a "-f" short option
726
595
  */
@@ -112,45 +112,43 @@ function checkVirtualElement( member ) {
112
112
  * Checks whether managed associations
113
113
  * with cardinality 'to many' have an on-condition.
114
114
  *
115
- * @param {CSN.Artifact} art The artifact
116
- * @todo this is a member validator, is it not?
115
+ * @param {CSN.Artifact} member The member (e.g. element artifact)
117
116
  */
118
- function checkManagedAssoc( art ) {
119
- forEachMemberRecursively(art, (member) => {
120
- if (this.csnUtils.isAssocOrComposition(member) &&
121
- !isManagedComposition.bind(this)(member)) {
122
- // Implementation note: Imported services (i.e. external ones) may contain to-many associations
123
- // with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
124
- // foreign key array, we won't emit a warning to avoid spamming the user.
125
- const max = member.cardinality?.max ? member.cardinality.max : 1;
126
- if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
127
- const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
128
- this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
129
- value: cardinality2str(member, false),
130
- '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
131
- }, {
132
- std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
133
- comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
134
- });
135
- }
136
- }
137
- });
117
+ function checkManagedAssoc( member ) {
118
+ if (!member.target || isManagedComposition.bind(this)(member))
119
+ return;
138
120
 
139
- /**
140
- *
141
- * @param {CSN.Element} member The member
142
- * @returns {boolean} Whether the member is managed composition
143
- */
144
- function isManagedComposition( member ) {
145
- if (member.targetAspect)
146
- return true;
147
- if (!member.target)
148
- return false;
149
- const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
150
- return target.kind !== 'entity';
121
+ // Implementation note: Imported services (i.e. external ones) may contain to-many associations
122
+ // with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
123
+ // foreign key array, we won't emit a warning to avoid spamming the user.
124
+ const max = member.cardinality?.max ? member.cardinality.max : 1;
125
+ if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
126
+ const isNoDb = this.artifact['@cds.persistence.skip'] || this.artifact['@cds.persistence.exists'];
127
+ this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
128
+ value: cardinality2str(member, false),
129
+ '#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
130
+ }, {
131
+ std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
132
+ comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
133
+ });
151
134
  }
152
135
  }
153
136
 
137
+ /**
138
+ *
139
+ * @param {CSN.Element} member The member
140
+ * @returns {boolean} Whether the member is managed composition
141
+ */
142
+ function isManagedComposition( member ) {
143
+ if (member.targetAspect)
144
+ return true;
145
+ if (!member.target)
146
+ return false;
147
+ const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
148
+ return target.kind !== 'entity';
149
+ }
150
+
151
+
154
152
  /**
155
153
  * All DB & OData flat mode must reject recursive type usages in entities
156
154
  * 'items' break recursion as 'items' will turn into an NCLOB and the path