@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -1,27 +1,31 @@
1
1
  'use strict'
2
2
 
3
- // Create a command line option processor and define valid commands, options and parameters.
4
- // In order to understand a command line like this:
5
- // $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
6
- //
7
- // The following definitions should be made
8
- //
9
- // const optionProcessor = createOptionProcessor();
10
- // optionProcessor
11
- // .help(`General help text`);
12
- // .option('-x, --x-in-long-form <i>')
13
- // .option(' --foo')
14
- // optionProcessor.command('toXyz')
15
- // .help(`Help text for command "toXyz")
16
- // .option('-y --y-in-long-form')
17
- // .option(' --bar-wiz <w>', ['bla', 'foo'])
18
- //
19
- // Options *must* have a long form, can have at most one <param>, and optionally
20
- // an array of valid param values as strings. Commands and param values must not
21
- // start with '--'. The whole processor and each command may carry a help text.
22
- // To actually parse a command line, use
23
- // optionProcessor.processCmdLine(process.argv);
24
- // (see below)
3
+ /**
4
+ * Create a command line option processor and define valid commands, options and parameters.
5
+ * In order to understand a command line like this:
6
+ * $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
7
+ *
8
+ * The following definitions should be made:
9
+ *
10
+ * ```js
11
+ * const optionProcessor = createOptionProcessor();
12
+ * optionProcessor
13
+ * .help(`General help text`);
14
+ * .option('-x, --long-form <i>')
15
+ * .option(' --foo')
16
+ * optionProcessor.command('toXyz')
17
+ * .help(`Help text for command "toXyz")
18
+ * .option('-y --y-in-long-form')
19
+ * .option(' --bar-wiz <w>', ['bla', 'foo'])
20
+ * ```
21
+ *
22
+ * Options *must* have a long form, can have at most one <param>, and optionally
23
+ * an array of valid param values as strings. Commands and param values must not
24
+ * start with '-'. The whole processor and each command may carry a help text.
25
+ * To actually parse a command line, use
26
+ * const cli = optionProcessor.processCmdLine(process.argv);
27
+ * (see below)
28
+ */
25
29
  function createOptionProcessor() {
26
30
  const optionProcessor = {
27
31
  commands: {},
@@ -32,13 +36,14 @@ function createOptionProcessor() {
32
36
  command,
33
37
  positionalArgument: (argumentDefinition) => {
34
38
  // Default positional arguments; may be overwritten by commands.
35
- _positionalArguments(argumentDefinition);
39
+ _setPositionalArguments(argumentDefinition);
36
40
  return optionProcessor;
37
41
  },
38
42
  help,
39
43
  processCmdLine,
40
44
  verifyOptions,
41
45
  camelOptionsForCommand,
46
+ // TODO: Why exported?
42
47
  _parseCommandString,
43
48
  _parseOptionString,
44
49
  }
@@ -48,9 +53,10 @@ function createOptionProcessor() {
48
53
  * API: Define a general option.
49
54
  * @param {string} optString Option string describing the command line option.
50
55
  * @param {string[]} [validValues] Array of valid values for the options.
56
+ * @param {object} [options] Further options such as `ignoreCase: true`
51
57
  */
52
- function option(optString, validValues) {
53
- return _addOption(optionProcessor, optString, validValues);
58
+ function option(optString, validValues, options) {
59
+ return _addOption(optionProcessor, optString, validValues, options);
54
60
  }
55
61
 
56
62
  /**
@@ -64,43 +70,43 @@ function createOptionProcessor() {
64
70
 
65
71
  /**
66
72
  * API: Define a command
67
- * @param {string} cmdString Command name, e.g. 'S, toSql'
73
+ * @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
68
74
  */
69
75
  function command(cmdString) {
70
76
  /** @type {object} */
71
- const command = {
77
+ const cmd = {
72
78
  options: {},
73
79
  positionalArguments: [],
74
- option,
80
+ option: commandOption,
75
81
  positionalArgument: (argumentDefinition) => {
76
- _positionalArguments(argumentDefinition, command.positionalArguments);
77
- return command;
82
+ _setPositionalArguments(argumentDefinition, cmd.positionalArguments);
83
+ return cmd;
78
84
  },
79
- help,
85
+ help: commandHelp,
80
86
  ..._parseCommandString(cmdString)
81
87
  };
82
- if (optionProcessor.commands[command.longName]) {
83
- throw new Error(`Duplicate assignment for long command ${command.longName}`);
88
+ if (optionProcessor.commands[cmd.longName]) {
89
+ throw new Error(`Duplicate assignment for long command ${cmd.longName}`);
84
90
  }
85
- optionProcessor.commands[command.longName] = command;
91
+ optionProcessor.commands[cmd.longName] = cmd;
86
92
 
87
- if (command.shortName) {
88
- if (optionProcessor.commands[command.shortName]) {
89
- throw new Error(`Duplicate assignment for short command ${command.shortName}`);
93
+ if (cmd.shortName) {
94
+ if (optionProcessor.commands[cmd.shortName]) {
95
+ throw new Error(`Duplicate assignment for short command ${cmd.shortName}`);
90
96
  }
91
- optionProcessor.commands[command.shortName] = command;
97
+ optionProcessor.commands[cmd.shortName] = cmd;
92
98
  }
93
- return command;
99
+ return cmd;
94
100
 
95
- // API: Define a command option
96
- function option(optString, validValues) {
97
- return _addOption(command, optString, validValues);
101
+ // Command API: Define a command option
102
+ function commandOption(optString, validValues, options) {
103
+ return _addOption(cmd, optString, validValues, options);
98
104
  }
99
105
 
100
- // API: Define the command help text
101
- function help(text) {
102
- command.helpText = text;
103
- return command;
106
+ // Command API: Define the command help text
107
+ function commandHelp(text) {
108
+ cmd.helpText = text;
109
+ return cmd;
104
110
  }
105
111
  }
106
112
 
@@ -112,8 +118,9 @@ function createOptionProcessor() {
112
118
  *
113
119
  * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
114
120
  * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
121
+ * @private
115
122
  */
116
- function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
123
+ function _setPositionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
117
124
  if (argList.find((arg) => arg.isDynamic)) {
118
125
  throw new Error(`Can't add positional arguments after a dynamic one`);
119
126
  }
@@ -149,31 +156,33 @@ function createOptionProcessor() {
149
156
  * @private
150
157
  * @see option()
151
158
  */
152
- function _addOption(command, optString, validValues) {
159
+ function _addOption(cmd, optString, validValues, options) {
153
160
  const opt = _parseOptionString(optString, validValues);
154
- if (command.options[opt.longName]) {
161
+ Object.assign(opt, options);
162
+
163
+ if (cmd.options[opt.longName]) {
155
164
  throw new Error(`Duplicate assignment for long option ${opt.longName}`);
156
165
  } else if (optionProcessor.options[opt.longName]) {
157
166
  // This path is only taken if optString is for commands
158
167
  optionProcessor.optionClashes.push({
159
168
  option: opt.longName,
160
- description: `Command '${command.longName}' has option clash with general options for: ${opt.longName}`
169
+ description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
161
170
  });
162
171
  }
163
- command.options[opt.longName] = opt;
172
+ cmd.options[opt.longName] = opt;
164
173
  if (opt.shortName) {
165
- if (command.options[opt.shortName]) {
174
+ if (cmd.options[opt.shortName]) {
166
175
  throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
167
176
  } else if (optionProcessor.options[opt.shortName]) {
168
177
  // This path is only taken if optString is for commands
169
178
  optionProcessor.optionClashes.push({
170
179
  option: opt.shortName,
171
- description: `Command '${command.longName}' has option clash with general options for: ${opt.shortName}`
180
+ description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
172
181
  });
173
182
  }
174
- command.options[opt.shortName] = opt;
183
+ cmd.options[opt.shortName] = opt;
175
184
  }
176
- return command;
185
+ return cmd;
177
186
  }
178
187
 
179
188
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -217,7 +226,6 @@ function createOptionProcessor() {
217
226
  let longName;
218
227
  let shortName;
219
228
  let param;
220
- let camelName;
221
229
 
222
230
  // split at spaces (with optional preceding comma)
223
231
  const tokens = optString.trim().split(/,? +/);
@@ -261,26 +269,16 @@ function createOptionProcessor() {
261
269
  throw new Error(`Valid values must be of type string: ${optString}`);
262
270
  });
263
271
  }
264
- camelName = _camelify(longName);
272
+
265
273
  return {
266
274
  longName,
267
275
  shortName,
268
- camelName,
276
+ camelName: camelifyLongOption(longName),
269
277
  param,
270
278
  validValues
271
279
  }
272
280
  }
273
281
 
274
- // Return a camelCase name "fooBar" for a long option "--foo-bar"
275
- function _camelify(opt) {
276
- return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
277
- }
278
-
279
- // Return a long option name like "--foo-bar" for a camel-case name "fooBar"
280
- function _unCamelify(opt) {
281
- return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
282
- }
283
-
284
282
  // API: Let the option processor digest a command line 'argv'
285
283
  // The expectation is to get a commandline like this:
286
284
  // $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
@@ -336,52 +334,15 @@ function createOptionProcessor() {
336
334
  argv = [ ...argv.slice(0, i), ...arg.split('='), ...argv.slice(i + 1)];
337
335
  arg = argv[i];
338
336
  }
339
- if (!seenDashDash && arg.startsWith('-') && arg !== '--') {
340
- if (result.command) {
341
- // We already have a command
342
- const opt = optionProcessor.commands[result.command].options[arg];
343
- if (opt) {
344
- // Found as a command option
345
- i += processOption(i, opt, result.command);
346
- } else {
347
- // No command option, try general options as fallback
348
- const opt = optionProcessor.options[arg];
349
- if (opt) {
350
- i += processOption(i, opt, false);
351
- } else {
352
- // Not found at all, put into unknownOptions if it is an option
353
- // for another cdsc command.
354
- // We dig into the other cdsc commands in order to check if
355
- // the option expects a parameter and if so to take the next argument as a value
356
- if (Object.keys(optionProcessor.commands).some(cmd => optionProcessor.commands[cmd].options[arg])) {
357
- const cmd = optionProcessor.commands[
358
- Object.keys(optionProcessor.commands).find(cmd => optionProcessor.commands[cmd].options[arg])
359
- ];
360
- i += processOption(i, cmd.options[arg], optionProcessor.commands[result.command], true);
361
- } else { // still add it to the unknownOptions
362
- result.unknownOptions.push(`Unknown option "${arg}" for the command "${result.command}"`);
363
- // if the next argument looks like an argument for this unknown option => skip it
364
- if ((i + 1) < argv.length && !argv[i + 1].match('(^[.-])|[.](csn|cds|json)$'))
365
- i++;
366
- }
367
- }
368
- }
369
- } else {
370
- // We don't have a command
371
- const opt = optionProcessor.options[arg];
372
- if (opt) {
373
- // Found as a general option
374
- i += processOption(i, opt, false);
375
- } else {
376
- // Not found, complain
377
- result.unknownOptions.push(`Unknown option "${arg}"`);
378
- }
379
- }
380
- }
381
- else if (arg === '--') {
337
+
338
+ if (arg === '--') {
382
339
  // No more options after '--'
383
340
  seenDashDash = true;
384
- } else {
341
+ }
342
+ else if (!seenDashDash && arg.startsWith('-')) {
343
+ i += processOption(i);
344
+ }
345
+ else {
385
346
  // Command or arg
386
347
  if (result.command === undefined) {
387
348
  if (optionProcessor.commands[arg]) {
@@ -407,7 +368,11 @@ function createOptionProcessor() {
407
368
  const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
408
369
  if (missingArg) {
409
370
  const forCommand = result.command ? ` for '${ result.command }'` : '';
410
- result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
371
+ const errorMsg = `Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`;
372
+ if (forCommand)
373
+ result.cmdErrors.push(errorMsg)
374
+ else
375
+ result.errors.push(errorMsg)
411
376
  }
412
377
 
413
378
  return result;
@@ -420,8 +385,7 @@ function createOptionProcessor() {
420
385
  */
421
386
  function getCurrentPositionArguments() {
422
387
  const cmd = optionProcessor.commands[result.command];
423
- const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
- return args;
388
+ return ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
425
389
  }
426
390
 
427
391
  function processPositionalArgument(argumentValue) {
@@ -449,57 +413,127 @@ function createOptionProcessor() {
449
413
 
450
414
  // (Note that this works on 'argv' and 'result' from above).
451
415
  // Process 'argv[i]' as an option.
452
- // Check the option definition in 'opt' to see if a parameter is expected.
416
+ // Check the option definition to see if a parameter is expected.
453
417
  // If so, take it (complain if one is found in 'argv').
454
418
  // Populate 'result.options' with the result. Return the number params found (0 or 1).
455
- function processOption(i, opt, command, unknownOption = false) {
456
- // Does this option expect a parameter?
457
- if (opt.param) {
458
- if (i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
459
- // There should be a param but isn't - complain
460
- let error = `Missing param "${opt.param}" for option "${opt.shortName ? opt.shortName + ', ': ''}${opt.longName}"`;
461
- if (command && unknownOption) {
462
- result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
463
- } else if (command) {
464
- error = `${error} of command "${command}"`;
465
- result.cmdErrors.push(error);
466
- } else {
467
- result.errors.push(error);
419
+ function processOption(i) {
420
+ const arg = argv[i];
421
+ let currentCommand = result.command;
422
+
423
+ // First check top-level options
424
+ let currentOption = optionProcessor.options[arg];
425
+ if (currentCommand) {
426
+ // If there is a command and it has an option that overrides it, use it instead.
427
+ const cmdOpt = optionProcessor.commands[currentCommand].options[arg];
428
+ if (cmdOpt)
429
+ currentOption = cmdOpt;
430
+ else if (currentOption)
431
+ // Otherwise, if there exist a top-level option, set 'command' to null.
432
+ currentCommand = null;
433
+ }
434
+
435
+ if (!currentOption)
436
+ return reportUnknown();
437
+
438
+ if (!currentOption.param) {
439
+ setCurrentOption(true);
440
+ return 0;
441
+ }
442
+
443
+ const param = paramForOption(currentOption);
444
+ if (param === null)
445
+ return 0;
446
+ setCurrentOption(param);
447
+ return 1;
448
+
449
+ /**
450
+ * Report that an option is unknown. If the option exists for other
451
+ * commands or if the next argument looks like a param, return 1,
452
+ * otherwise 0, indicating how many argv fields have been consumed.
453
+ *
454
+ * @returns {number}
455
+ */
456
+ function reportUnknown() {
457
+ if (currentCommand)
458
+ result.unknownOptions.push(`Unknown option "${arg}" for the command "${currentCommand}"`);
459
+ else
460
+ result.unknownOptions.push(`Unknown option "${arg}"`);
461
+
462
+ if (currentCommand) {
463
+ // Not found at all. We dig into the other cdsc commands in order to check if
464
+ // the option expects a parameter and if so to take the next argument as a value
465
+ const otherCmd = Object.keys(optionProcessor.commands).find(cmd => optionProcessor.commands[cmd].options[arg]);
466
+ const otherCmdOpt = otherCmd && optionProcessor.commands[otherCmd].options[arg];
467
+ if (otherCmdOpt && hasParamForUnknown(otherCmdOpt)) {
468
+ return 1
468
469
  }
469
- return 0;
470
470
  }
471
- else {
472
- // Take the option with the parameter
473
- const value = argv[i + 1];
474
- const shortOption = opt.shortName ? `${opt.shortName}, ` : ''
475
- if (command) {
476
- // if an unknown option for a command => add it to the array and warn about
477
- if (unknownOption) {
478
- result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
479
- } else {
480
- result.options[command][opt.camelName] = value;
481
- if (opt.validValues && !opt.validValues.includes(value)) {
482
- result.cmdErrors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
483
- }
484
- }
485
- } else {
486
- result.options[opt.camelName] = value;
487
- if (opt.validValues && !opt.validValues.includes(value)) {
488
- result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
489
- }
490
- }
471
+
472
+ if (hasParamForUnknown(null))
491
473
  return 1;
474
+
475
+ return 0;
476
+ }
477
+
478
+ function setCurrentOption(val) {
479
+ if (currentCommand) {
480
+ if (!result.options[currentCommand])
481
+ result.options[currentCommand] = {};
482
+ result.options[currentCommand][currentOption.camelName] = val;
483
+ }
484
+ else {
485
+ result.options[currentOption.camelName] = val;
492
486
  }
493
487
  }
494
- // No parameter, take option as bool
495
- if (command) {
496
- unknownOption
497
- ? result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`)
498
- : result.options[command][opt.camelName] = true;
499
- } else {
500
- result.options[opt.camelName] = true;
488
+
489
+ function reportMissingParam(opt) {
490
+ let error = `Missing param "${opt.param}" for option "${opt.shortName ? opt.shortName + ', ': ''}${opt.longName}"`;
491
+ if (currentCommand) {
492
+ error = `${error} of command "${currentCommand}"`;
493
+ result.cmdErrors.push(error);
494
+ } else {
495
+ result.errors.push(error);
496
+ }
497
+ }
498
+
499
+ function reportInvalidValue(opt, value) {
500
+ const shortOption = opt.shortName ? `${opt.shortName}, ` : ''
501
+ const errors = currentCommand ? result.cmdErrors : result.errors;
502
+ errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
503
+ }
504
+
505
+ /**
506
+ * Get the value for the option's parameter. If the option does not require one,
507
+ * returns `null`. Reports missing parameters and invalid values.
508
+ *
509
+ * @returns {null|*}
510
+ */
511
+ function paramForOption(opt, reportMissing = true) {
512
+ if (i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
513
+ if (reportMissing)
514
+ reportMissingParam(opt)
515
+ return null;
516
+ }
517
+
518
+ const value = argv[i + 1];
519
+ if (!isValidOptionValue(opt, value) && reportMissing) {
520
+ reportInvalidValue(opt, value);
521
+ }
522
+ return value;
523
+ }
524
+
525
+ /**
526
+ * Returns true if:
527
+ * - we didn't find an option (opt === null) _or_
528
+ * - we found an option and it requires a param
529
+ * _and_ if the next arg looks like an argument.
530
+ *
531
+ * @param {object|null} opt
532
+ * @returns {boolean}
533
+ */
534
+ function hasParamForUnknown(opt) {
535
+ return ((!opt || opt.param) && (i + 1) < argv.length && !argv[i + 1].match('(^[.-])|[.](csn|cdl|cds|json)$'));
501
536
  }
502
- return 0;
503
537
  }
504
538
  }
505
539
 
@@ -507,16 +541,16 @@ function createOptionProcessor() {
507
541
  // If 'command' is supplied, check only 'options.command', otherwise check
508
542
  // only top-level options
509
543
  // Return an array of complaints (possibly empty)
510
- function verifyOptions(options, command = undefined, silent = false) {
544
+ function verifyOptions(options, commandName = undefined, silent = false) {
511
545
  const result = [];
512
546
  let opts;
513
547
 
514
- if((options.betaMode || options.beta) && !options.testMode && !silent) {
548
+ if ((options.betaMode || options.beta) && !options.testMode && !silent) {
515
549
  const mode = options.beta ? 'beta' : 'beta-mode';
516
550
  result.push(`Option --${mode} was used. This option should not be used in productive scenarios!`)
517
551
  }
518
552
 
519
- if(options) {
553
+ if (options) {
520
554
  [
521
555
  'defaultBinaryLength', 'defaultStringLength',
522
556
  /*'length', 'precision', 'scale'*/
@@ -529,13 +563,13 @@ function createOptionProcessor() {
529
563
  });
530
564
  }
531
565
 
532
- if (command) {
533
- const cmd = optionProcessor.commands[command];
566
+ if (commandName) {
567
+ const cmd = optionProcessor.commands[commandName];
534
568
  if (!cmd) {
535
- throw new Error(`Expected existing command: "${command}"`);
569
+ throw new Error(`Expected existing command: "${cmd}"`);
536
570
  }
537
571
  opts = cmd.options;
538
- options = options[command] || {};
572
+ options = options[cmd] || {};
539
573
  if (typeof options === 'boolean') {
540
574
  // Special case: command without options
541
575
  options = {};
@@ -545,16 +579,16 @@ function createOptionProcessor() {
545
579
  }
546
580
  // Look at each supplied option
547
581
  for (const camelName in options) {
548
- const opt = opts[_unCamelify(camelName)];
582
+ const opt = opts[uncamelifyLongOption(camelName)];
549
583
  let error;
550
584
  if (!opt) {
551
585
  // Don't report commands in top-level options
552
- if ((command || !optionProcessor.commands[camelName]) && !silent) {
553
- error = `Unknown option "${command ? command + '.' : ''}${camelName}"`;
586
+ if ((commandName || !optionProcessor.commands[camelName]) && !silent) {
587
+ error = `Unknown option "${commandName ? commandName + '.' : ''}${camelName}"`;
554
588
  }
555
589
  } else {
556
590
  const param = options[camelName];
557
- error = verifyOptionParam(param, opt, command ? command + '.' : '');
591
+ error = verifyOptionParam(param, opt, commandName ? commandName + '.' : '');
558
592
  }
559
593
  if (error) {
560
594
  result.push(error);
@@ -570,7 +604,7 @@ function createOptionProcessor() {
570
604
  // Parameter is required for this option
571
605
  if (typeof param === 'boolean') {
572
606
  return `Missing value for option "${prefix}${opt.camelName}"`;
573
- } else if (opt.validValues && !opt.validValues.includes(String(param))) {
607
+ } else if (!isValidOptionValue(opt, param)) {
574
608
  return `Invalid value "${param}" for option "${prefix}${opt.camelName}" - use one of [${opt.validValues}]`;
575
609
  }
576
610
  return false;
@@ -585,36 +619,65 @@ function createOptionProcessor() {
585
619
  }
586
620
  }
587
621
 
622
+ function isValidOptionValue(opt, value) {
623
+ // Explicitly convert to string, input 'value' may be boolean
624
+ value = String(value);
625
+ if (!opt.validValues || !opt.validValues.length)
626
+ return true;
627
+ if (opt.ignoreCase)
628
+ return opt.validValues.some( valid => valid.toLowerCase() === value.toLowerCase() );
629
+ return opt.validValues.includes(value);
630
+ }
631
+
588
632
  // Return an array of unique camelNames of the options for the specified command
589
633
  // If invalid command -> an empty array
590
- function camelOptionsForCommand(command) {
591
- if (command && optionProcessor.commands[command]) {
592
- const cmd = optionProcessor.commands[command];
593
- return [... new Set(
594
- Object.keys(cmd.options).map(optName => cmd.options[optName].camelName)
595
- )];
596
- } else {
597
- return [];
598
- }
634
+ function camelOptionsForCommand(cmd) {
635
+ if (!cmd || !optionProcessor.commands[cmd])
636
+ return []
637
+ cmd = optionProcessor.commands[cmd];
638
+ const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
639
+ return [...new Set(names)];
599
640
  }
600
641
  }
601
642
 
602
- // Check if 'opt' looks like a "-f" short option
643
+ /**
644
+ * Return a camelCase name "fooBar" for a long option "--foo-bar"
645
+ */
646
+ function camelifyLongOption(opt) {
647
+ return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
648
+ }
649
+
650
+ /**
651
+ * Return a long option name like "--foo-bar" for a camel-case name "fooBar"
652
+ */
653
+ function uncamelifyLongOption(opt) {
654
+ return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
655
+ }
656
+
657
+ /**
658
+ * Check if 'opt' looks like a "-f" short option
659
+ */
603
660
  function isShortOption(opt) {
604
661
  return /^-[a-zA-Z?]$/.test(opt);
605
662
  }
606
663
 
607
- // Check if 'opt' looks like a "--foo-bar" long option
664
+ /**
665
+ * Check if 'opt' looks like a "--foo-bar" long option
666
+ */
608
667
  function isLongOption(opt) {
609
668
  return /^--[a-zA-Z0-9-]+$/.test(opt);
610
669
  }
611
670
 
612
- // Check if 'opt' looks like a "<foobar>" parameter
671
+ /**
672
+ * Check if 'opt' looks like a "<foobar>" parameter
673
+ */
613
674
  function isParam(opt) {
614
675
  return /^<[a-zA-Z-]+>$/.test(opt);
615
676
  }
616
677
 
617
- // Check if 'arg' looks like "<foobar...>"
678
+ /**
679
+ * Check if 'arg' looks like "<foobar...>"
680
+ */
618
681
  function isDynamicPositionalArgument(arg) {
619
682
  return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
620
683
  }
@@ -12,6 +12,8 @@
12
12
  "jsdoc/no-undefined-types": 0,
13
13
  // eslint-plugin-jsdoc warning
14
14
  "jsdoc/require-property": 0,
15
+ // most of the main functions have the normal forEachArtifact/Member signature anyway
16
+ "jsdoc/require-param-description": 0,
15
17
  // =airbnb, >eslint:
16
18
  "max-len": [ "error", {
17
19
  "code": 110,
@@ -110,7 +110,8 @@ function checkActionOrFunction(art, artName, prop, path) {
110
110
  * @param {CSN.Path} currPath The current path
111
111
  */
112
112
  function checkUserDefinedType(type, typeName, currPath) {
113
- if (!isBuiltinType(type) && type.kind && type.kind !== 'type') {
113
+ // TODO: isBuiltinType does not resolve any type-chains.
114
+ if (!isBuiltinType(type.type) && type.kind && type.kind !== 'type') {
114
115
  const serviceOfType = this.csnUtils.getServiceName(typeName);
115
116
  if (serviceName && serviceName !== serviceOfType) {
116
117
  // if (!(isMultiSchema && serviceOfType)) {
@@ -82,7 +82,7 @@ function checkAtSapAnnotations(node) {
82
82
  function checkReadOnlyAndInsertOnly(artifact, artifactName) {
83
83
  if (!this.csnUtils.getServiceName(artifactName))
84
84
  return;
85
- if (artifact.kind && [ 'entity', 'view' ].includes(artifact.kind) && artifact['@readonly'] && artifact['@insertonly'])
85
+ if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
86
86
  this.warning(null, artifact.$path, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
87
87
  }
88
88