@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -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: {},
@@ -30,11 +34,16 @@ function createOptionProcessor() {
30
34
  optionClashes: [],
31
35
  option,
32
36
  command,
33
- positionalArgument,
37
+ positionalArgument: (argumentDefinition) => {
38
+ // Default positional arguments; may be overwritten by commands.
39
+ _setPositionalArguments(argumentDefinition);
40
+ return optionProcessor;
41
+ },
34
42
  help,
35
43
  processCmdLine,
36
44
  verifyOptions,
37
45
  camelOptionsForCommand,
46
+ // TODO: Why exported?
38
47
  _parseCommandString,
39
48
  _parseOptionString,
40
49
  }
@@ -44,9 +53,10 @@ function createOptionProcessor() {
44
53
  * API: Define a general option.
45
54
  * @param {string} optString Option string describing the command line option.
46
55
  * @param {string[]} [validValues] Array of valid values for the options.
56
+ * @param {object} [options] Further options such as `ignoreCase: true`
47
57
  */
48
- function option(optString, validValues) {
49
- return _addOption(optionProcessor, optString, validValues);
58
+ function option(optString, validValues, options) {
59
+ return _addOption(optionProcessor, optString, validValues, options);
50
60
  }
51
61
 
52
62
  /**
@@ -60,13 +70,18 @@ function createOptionProcessor() {
60
70
 
61
71
  /**
62
72
  * API: Define a command
63
- * @param {string} cmdString Command name, e.g. 'S, toSql'
73
+ * @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
64
74
  */
65
75
  function command(cmdString) {
66
76
  /** @type {object} */
67
77
  const command = {
68
78
  options: {},
79
+ positionalArguments: [],
69
80
  option,
81
+ positionalArgument: (argumentDefinition) => {
82
+ _setPositionalArguments(argumentDefinition, command.positionalArguments);
83
+ return command;
84
+ },
70
85
  help,
71
86
  ..._parseCommandString(cmdString)
72
87
  };
@@ -83,12 +98,12 @@ function createOptionProcessor() {
83
98
  }
84
99
  return command;
85
100
 
86
- // API: Define a command option
87
- function option(optString, validValues) {
88
- return _addOption(command, optString, validValues);
101
+ // Command API: Define a command option
102
+ function option(optString, validValues, options) {
103
+ return _addOption(command, optString, validValues, options);
89
104
  }
90
105
 
91
- // API: Define the command help text
106
+ // Command API: Define the command help text
92
107
  function help(text) {
93
108
  command.helpText = text;
94
109
  return command;
@@ -96,28 +111,35 @@ function createOptionProcessor() {
96
111
  }
97
112
 
98
113
  /**
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...>'
114
+ * Set the positional arguments to the command line processor. Instructs the processor
115
+ * to either require N positional arguments or a dynamic number (but at least one).
116
+ * Note that you can only call this function once. Only the last invocation sets
117
+ * the positional arguments.
118
+ *
119
+ * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
120
+ * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
121
+ * @private
102
122
  */
103
- function positionalArgument(positionalArgumentDefinition) {
104
- if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
123
+ function _setPositionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
124
+ if (argList.find((arg) => arg.isDynamic)) {
105
125
  throw new Error(`Can't add positional arguments after a dynamic one`);
106
126
  }
107
127
 
108
- const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
109
- const args = positionalArgumentDefinition.split(' ');
128
+ const registeredNames = argList.map((arg) => arg.name);
129
+ const args = argumentDefinition.split(' ');
110
130
 
111
131
  for (const arg of args) {
112
- const argName = arg.replace('<', '').replace('>', '').replace('...', '');
132
+ // Remove braces, dots and camelify.
133
+ const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
134
+
113
135
  if (registeredNames.includes(argName)) {
114
- throw new Error(`Duplicate positional argument ${arg}`);
136
+ throw new Error(`Duplicate positional argument: ${arg}`);
115
137
  }
116
138
  if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
117
139
  throw new Error(`Unknown positional argument syntax: ${arg}`)
118
140
  }
119
141
 
120
- optionProcessor.positionalArguments.push({
142
+ argList.push({
121
143
  name: argName,
122
144
  isDynamic: isDynamicPositionalArgument(arg),
123
145
  required: true
@@ -125,7 +147,6 @@ function createOptionProcessor() {
125
147
 
126
148
  registeredNames.push(argName);
127
149
  }
128
- return optionProcessor;
129
150
  }
130
151
 
131
152
  /**
@@ -135,8 +156,10 @@ function createOptionProcessor() {
135
156
  * @private
136
157
  * @see option()
137
158
  */
138
- function _addOption(command, optString, validValues) {
159
+ function _addOption(command, optString, validValues, options) {
139
160
  const opt = _parseOptionString(optString, validValues);
161
+ Object.assign(opt, options);
162
+
140
163
  if (command.options[opt.longName]) {
141
164
  throw new Error(`Duplicate assignment for long option ${opt.longName}`);
142
165
  } else if (optionProcessor.options[opt.longName]) {
@@ -203,7 +226,6 @@ function createOptionProcessor() {
203
226
  let longName;
204
227
  let shortName;
205
228
  let param;
206
- let camelName;
207
229
 
208
230
  // split at spaces (with optional preceding comma)
209
231
  const tokens = optString.trim().split(/,? +/);
@@ -247,26 +269,16 @@ function createOptionProcessor() {
247
269
  throw new Error(`Valid values must be of type string: ${optString}`);
248
270
  });
249
271
  }
250
- camelName = _camelify(longName);
272
+
251
273
  return {
252
274
  longName,
253
275
  shortName,
254
- camelName,
276
+ camelName: camelifyLongOption(longName),
255
277
  param,
256
278
  validValues
257
279
  }
258
280
  }
259
281
 
260
- // Return a camelCase name "fooBar" for a long option "--foo-bar"
261
- function _camelify(opt) {
262
- return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
263
- }
264
-
265
- // Return a long option name like "--foo-bar" for a camel-case name "fooBar"
266
- function _unCamelify(opt) {
267
- return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
268
- }
269
-
270
282
  // API: Let the option processor digest a command line 'argv'
271
283
  // The expectation is to get a commandline like this:
272
284
  // $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
@@ -322,7 +334,12 @@ function createOptionProcessor() {
322
334
  argv = [ ...argv.slice(0, i), ...arg.split('='), ...argv.slice(i + 1)];
323
335
  arg = argv[i];
324
336
  }
325
- if (!seenDashDash && arg.startsWith('-') && arg !== '--') {
337
+
338
+ if (arg === '--') {
339
+ // No more options after '--'
340
+ seenDashDash = true;
341
+ }
342
+ else if (!seenDashDash && arg.startsWith('-')) {
326
343
  if (result.command) {
327
344
  // We already have a command
328
345
  const opt = optionProcessor.commands[result.command].options[arg];
@@ -364,10 +381,7 @@ function createOptionProcessor() {
364
381
  }
365
382
  }
366
383
  }
367
- else if (arg === '--') {
368
- // No more options after '--'
369
- seenDashDash = true;
370
- } else {
384
+ else {
371
385
  // Command or arg
372
386
  if (result.command === undefined) {
373
387
  if (optionProcessor.commands[arg]) {
@@ -390,21 +404,37 @@ 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
+ return ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
+ }
425
+
400
426
  function processPositionalArgument(argumentValue) {
401
- if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
427
+ const argList = getCurrentPositionArguments();
428
+ if ( result.args.length === 0 && argList.length === 0 )
402
429
  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];
430
+ const inBounds = result.args.length < argList.length;
431
+ const lastIndex = inBounds ? result.args.length : argList.length - 1;
432
+ const nextUnsetArgument = argList[lastIndex];
406
433
  if (!inBounds && !nextUnsetArgument.isDynamic) {
407
- result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
434
+ if (result.command)
435
+ result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
436
+ else
437
+ result.errors.push(`Too many arguments. Expected ${argList.length}`);
408
438
  return;
409
439
  }
410
440
  result.args.length += 1;
@@ -447,13 +477,13 @@ function createOptionProcessor() {
447
477
  result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
448
478
  } else {
449
479
  result.options[command][opt.camelName] = value;
450
- if (opt.validValues && !opt.validValues.includes(value)) {
480
+ if (!isValidOptionValue(opt, value)) {
451
481
  result.cmdErrors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
452
482
  }
453
483
  }
454
484
  } else {
455
485
  result.options[opt.camelName] = value;
456
- if (opt.validValues && !opt.validValues.includes(value)) {
486
+ if (!isValidOptionValue(opt, value)) {
457
487
  result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
458
488
  }
459
489
  }
@@ -486,7 +516,10 @@ function createOptionProcessor() {
486
516
  }
487
517
 
488
518
  if(options) {
489
- ['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
519
+ [
520
+ 'defaultBinaryLength', 'defaultStringLength',
521
+ /*'length', 'precision', 'scale'*/
522
+ ].forEach(facet => {
490
523
  if(options[facet] && isNaN(options[facet])) {
491
524
  result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
492
525
  } else {
@@ -511,7 +544,7 @@ function createOptionProcessor() {
511
544
  }
512
545
  // Look at each supplied option
513
546
  for (const camelName in options) {
514
- const opt = opts[_unCamelify(camelName)];
547
+ const opt = opts[uncamelifyLongOption(camelName)];
515
548
  let error;
516
549
  if (!opt) {
517
550
  // Don't report commands in top-level options
@@ -536,7 +569,7 @@ function createOptionProcessor() {
536
569
  // Parameter is required for this option
537
570
  if (typeof param === 'boolean') {
538
571
  return `Missing value for option "${prefix}${opt.camelName}"`;
539
- } else if (opt.validValues && !opt.validValues.includes(String(param))) {
572
+ } else if (!isValidOptionValue(opt, param)) {
540
573
  return `Invalid value "${param}" for option "${prefix}${opt.camelName}" - use one of [${opt.validValues}]`;
541
574
  }
542
575
  return false;
@@ -551,38 +584,67 @@ function createOptionProcessor() {
551
584
  }
552
585
  }
553
586
 
587
+ function isValidOptionValue(opt, value) {
588
+ // Explicitly convert to string, input 'value' may be boolean
589
+ value = String(value);
590
+ if (!opt.validValues || !opt.validValues.length)
591
+ return true;
592
+ if (opt.ignoreCase)
593
+ return opt.validValues.some( valid => valid.toLowerCase() === value.toLowerCase() );
594
+ return opt.validValues.includes(value);
595
+ }
596
+
554
597
  // Return an array of unique camelNames of the options for the specified command
555
598
  // If invalid command -> an empty array
556
599
  function camelOptionsForCommand(command) {
557
- if (command && optionProcessor.commands[command]) {
558
- const cmd = optionProcessor.commands[command];
559
- return [... new Set(
560
- Object.keys(cmd.options).map(optName => cmd.options[optName].camelName)
561
- )];
562
- } else {
563
- return [];
564
- }
600
+ if (!command || !optionProcessor.commands[command])
601
+ return []
602
+ const cmd = optionProcessor.commands[command];
603
+ const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
604
+ return [...new Set(names)];
565
605
  }
566
606
  }
567
607
 
568
- // Check if 'opt' looks like a "-f" short option
608
+ /**
609
+ * Return a camelCase name "fooBar" for a long option "--foo-bar"
610
+ */
611
+ function camelifyLongOption(opt) {
612
+ return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
613
+ }
614
+
615
+ /**
616
+ * Return a long option name like "--foo-bar" for a camel-case name "fooBar"
617
+ */
618
+ function uncamelifyLongOption(opt) {
619
+ return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
620
+ }
621
+
622
+ /**
623
+ * Check if 'opt' looks like a "-f" short option
624
+ */
569
625
  function isShortOption(opt) {
570
626
  return /^-[a-zA-Z?]$/.test(opt);
571
627
  }
572
628
 
573
- // Check if 'opt' looks like a "--foo-bar" long option
629
+ /**
630
+ * Check if 'opt' looks like a "--foo-bar" long option
631
+ */
574
632
  function isLongOption(opt) {
575
633
  return /^--[a-zA-Z0-9-]+$/.test(opt);
576
634
  }
577
635
 
578
- // Check if 'opt' looks like a "<foobar>" parameter
636
+ /**
637
+ * Check if 'opt' looks like a "<foobar>" parameter
638
+ */
579
639
  function isParam(opt) {
580
- return /^<[a-zA-Z]+>$/.test(opt);
640
+ return /^<[a-zA-Z-]+>$/.test(opt);
581
641
  }
582
642
 
583
- // Check if 'arg' looks like "<foobar...>"
643
+ /**
644
+ * Check if 'arg' looks like "<foobar...>"
645
+ */
584
646
  function isDynamicPositionalArgument(arg) {
585
- return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
647
+ return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
586
648
  }
587
649
 
588
650
  module.exports = {
@@ -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)) {
@@ -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
 
@@ -18,12 +18,12 @@ function validateForeignKeys(member) {
18
18
 
19
19
  // Declared as arrow-function to keep scope the same (this value)
20
20
  const handleAssociation = (mem) => {
21
- for (let i = 0; i < mem.keys.length; i++) {
22
- if (mem.keys[i].ref) {
23
- if (!mem.keys[i]._art)
21
+ for (const key of mem.keys) {
22
+ if (key.ref) {
23
+ if (!key._art)
24
24
  continue;
25
25
  // eslint-disable-next-line no-use-before-define
26
- checkForItems(mem.keys[i]._art);
26
+ checkForItems(key._art);
27
27
  }
28
28
  }
29
29
  };
@@ -13,11 +13,11 @@
13
13
  function checkUsedTypesForAnonymousAspectComposition(member) {
14
14
  // Declared as arrow-function to keep scope the same (this value)
15
15
  const handleAssociation = (mem, fn) => {
16
- for (let i = 0; i < mem.keys.length; i++) {
17
- if (mem.keys[i].ref) {
18
- if (!mem.keys[i]._art)
16
+ for (const key of mem.keys) {
17
+ if (key.ref) {
18
+ if (!key._art)
19
19
  continue;
20
- fn(mem.keys[i]._art);
20
+ fn(key._art);
21
21
  }
22
22
  }
23
23
  };
@@ -124,10 +124,8 @@ function checkQueryForNoDBArtifacts(query) {
124
124
  for (const prop of generalQueryProperties) {
125
125
  const queryPart = (query.SELECT || query.SET)[prop];
126
126
  if (Array.isArray(queryPart)) {
127
- for (let i = 0; i < queryPart.length; i++) {
128
- const part = queryPart[i];
127
+ for (const part of queryPart)
129
128
  checkRef(part, prop === 'columns');
130
- }
131
129
  }
132
130
  else if (typeof queryPart === 'object') {
133
131
  checkRef(queryPart, prop === 'columns');
@@ -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);
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const { isBetaEnabled } = require('../base/model');
4
+
5
+ // Only to be used with validator.js - a correct this value needs to be provided!
6
+
7
+ /**
8
+ * Check that @sql.prepend annotation is not used on any elements and @sql.append is not used on elements in views.
9
+ *
10
+ * @param {CSN.Element} member
11
+ * @param {string} memberName
12
+ * @param {string} prop
13
+ * @param {CSN.Path} path
14
+ * @returns {void}
15
+ */
16
+ function checkSqlAnnotationOnElement(member, memberName, prop, path) {
17
+ if (isBetaEnabled(this.options, 'sqlSnippets')) {
18
+ if (member['@sql.replace'])
19
+ this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
20
+ if (member['@sql.prepend'])
21
+ this.error('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
22
+
23
+ if (member['@sql.append']) {
24
+ if (this.artifact.query)
25
+ this.error('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
26
+ else if (this.csnUtils.isStructured(member))
27
+ this.error('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
28
+ else
29
+ checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @param {object} carrier element which has the annotation
36
+ * @param {string} annotation
37
+ * @param {CSN.Path} path
38
+ * @param {Function} error
39
+ * @param {CSN.Options} options
40
+ */
41
+ function checkValidAnnoValue(carrier, annotation, path, error, options) {
42
+ if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
43
+ if (typeof carrier[annotation] !== 'string')
44
+ error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, `Annotation $(ANNO) must be a string, found $(TYPE)` );
45
+ else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
46
+ guardAgainstInjection(annotation, carrier[annotation], path, error);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Check that @sql.prepend is not used on views - only supported for entities (tables)
52
+ *
53
+ * @param {CSN.Artifact} artifact
54
+ * @param {string} artifactName
55
+ */
56
+ function checkSqlAnnotationOnArtifact(artifact, artifactName) {
57
+ if (isBetaEnabled(this.options, 'sqlSnippets')) {
58
+ if (artifact['@sql.prepend']) {
59
+ if (artifact.query)
60
+ this.error('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
61
+ else
62
+ checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
63
+ }
64
+
65
+ if (artifact['@sql.replace'])
66
+ this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
67
+
68
+ checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
69
+ }
70
+ }
71
+
72
+ // Anything that could terminate the "old" statement and start a new one basically.
73
+ const invalidInSnippet = [ ';', '--', '/*', '*/' ];
74
+
75
+ /**
76
+ * Check that the common characters used to terminate the current statement and start a fresh one are not used.
77
+ *
78
+ * @param {string} annoName
79
+ * @param {string} annoValue
80
+ * @param {CSN.Path} path
81
+ * @param {Function} error
82
+ */
83
+ function guardAgainstInjection(annoName, annoValue, path, error) {
84
+ for (const invalid of invalidInSnippet) {
85
+ if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
86
+ error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
87
+ }
88
+ }
89
+
90
+ module.exports = {
91
+ checkSqlAnnotationOnArtifact,
92
+ checkSqlAnnotationOnElement,
93
+ };
@@ -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
 
@@ -35,6 +35,10 @@ const checkExplicitlyNullableKeys = require('./nullableKeys');
35
35
  const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
36
36
  const unknownMagic = require('./unknownMagic');
37
37
  const managedWithoutKeys = require('./managedWithoutKeys');
38
+ const {
39
+ checkSqlAnnotationOnArtifact,
40
+ checkSqlAnnotationOnElement,
41
+ } = require('./sql-snippets');
38
42
 
39
43
  const forHanaMemberValidators
40
44
  = [
@@ -45,6 +49,8 @@ const forHanaMemberValidators
45
49
  checkExplicitlyNullableKeys,
46
50
  managedWithoutKeys,
47
51
  warnAboutDefaultOnAssociationForHanaCds,
52
+ // sql.prepend/append
53
+ checkSqlAnnotationOnElement,
48
54
  ];
49
55
 
50
56
  const forHanaArtifactValidators
@@ -53,6 +59,8 @@ const forHanaArtifactValidators
53
59
  validateCdsPersistenceAnnotation,
54
60
  // virtual items are not persisted on the db
55
61
  checkForEmptyOrOnlyVirtual,
62
+ // sql.prepend/append
63
+ checkSqlAnnotationOnArtifact,
56
64
  ];
57
65
 
58
66
  const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];