@sap/cds-compiler 2.10.4 → 2.11.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 (70) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +4 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +9 -23
  7. package/lib/api/options.js +12 -4
  8. package/lib/api/validate.js +23 -2
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/message-registry.js +10 -2
  12. package/lib/base/messages.js +23 -9
  13. package/lib/base/model.js +5 -4
  14. package/lib/base/optionProcessorHelper.js +56 -22
  15. package/lib/checks/selectItems.js +4 -0
  16. package/lib/checks/unknownMagic.js +6 -3
  17. package/lib/compiler/assert-consistency.js +7 -0
  18. package/lib/compiler/base.js +65 -0
  19. package/lib/compiler/builtins.js +28 -1
  20. package/lib/compiler/checks.js +2 -1
  21. package/lib/compiler/definer.js +58 -91
  22. package/lib/compiler/index.js +16 -4
  23. package/lib/compiler/propagator.js +5 -2
  24. package/lib/compiler/resolver.js +93 -34
  25. package/lib/compiler/shared.js +29 -202
  26. package/lib/compiler/utils.js +173 -0
  27. package/lib/edm/annotations/genericTranslation.js +1 -1
  28. package/lib/edm/csn2edm.js +3 -2
  29. package/lib/edm/edmPreprocessor.js +31 -36
  30. package/lib/edm/edmUtils.js +3 -3
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +17 -1
  33. package/lib/gen/language.tokens +79 -73
  34. package/lib/gen/languageLexer.interp +19 -1
  35. package/lib/gen/languageLexer.js +779 -731
  36. package/lib/gen/languageLexer.tokens +71 -65
  37. package/lib/gen/languageParser.js +4668 -4072
  38. package/lib/json/from-csn.js +10 -10
  39. package/lib/json/to-csn.js +169 -34
  40. package/lib/language/antlrParser.js +11 -0
  41. package/lib/language/genericAntlrParser.js +72 -14
  42. package/lib/language/language.g4 +73 -0
  43. package/lib/main.d.ts +136 -17
  44. package/lib/main.js +3 -1
  45. package/lib/model/api.js +2 -2
  46. package/lib/model/csnRefs.js +108 -31
  47. package/lib/model/csnUtils.js +63 -29
  48. package/lib/model/enrichCsn.js +36 -9
  49. package/lib/model/revealInternalProperties.js +20 -4
  50. package/lib/modelCompare/compare.js +2 -1
  51. package/lib/optionProcessor.js +29 -18
  52. package/lib/render/DuplicateChecker.js +1 -1
  53. package/lib/render/toCdl.js +9 -3
  54. package/lib/render/toHdbcds.js +16 -36
  55. package/lib/render/toSql.js +23 -5
  56. package/lib/transform/db/constraints.js +278 -119
  57. package/lib/transform/db/draft.js +3 -2
  58. package/lib/transform/db/expansion.js +6 -4
  59. package/lib/transform/db/flattening.js +17 -1
  60. package/lib/transform/db/transformExists.js +61 -2
  61. package/lib/transform/db/views.js +438 -0
  62. package/lib/transform/forHanaNew.js +56 -435
  63. package/lib/transform/forOdataNew.js +9 -2
  64. package/lib/transform/localized.js +2 -0
  65. package/lib/transform/transformUtilsNew.js +10 -0
  66. package/lib/transform/translateAssocsToJoins.js +5 -13
  67. package/lib/utils/file.js +5 -3
  68. package/lib/utils/term.js +65 -42
  69. package/lib/utils/timetrace.js +48 -26
  70. package/package.json +1 -1
package/lib/base/model.js CHANGED
@@ -19,7 +19,6 @@ const queryOps = {
19
19
  */
20
20
  const availableBetaFlags = {
21
21
  // enabled by --beta-mode
22
- foreignKeyConstraints: true,
23
22
  toRename: true,
24
23
  addTextsLanguageAssoc: true,
25
24
  assocsWithParams: true,
@@ -28,7 +27,6 @@ const availableBetaFlags = {
28
27
  ignoreAssocPublishingInUnion: true,
29
28
  nestedProjections: true,
30
29
  enableUniversalCsn: true,
31
- windowFunctions: true,
32
30
  // disabled by --beta-mode
33
31
  nestedServices: false,
34
32
  };
@@ -55,15 +53,18 @@ function isBetaEnabled( options, feature ) {
55
53
  /**
56
54
  * Test for deprecated feature, stored in option `deprecated`.
57
55
  * With that, the value of `deprecated` is a dictionary of feature=>Boolean.
56
+ * If no `feature` is provided, checks if any deprecated option is set.
58
57
  *
59
58
  * Please do not move this function to the "option processor" code.
60
59
  *
61
60
  * @param {object} options Options
62
- * @param {string} feature Feature to check for
61
+ * @param {string} [feature] Feature to check for
63
62
  * @returns {boolean}
64
63
  */
65
- function isDeprecatedEnabled( options, feature ) {
64
+ function isDeprecatedEnabled( options, feature = null ) {
66
65
  const { deprecated } = options;
66
+ if(!feature)
67
+ return !!deprecated;
67
68
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
68
69
  }
69
70
 
@@ -30,7 +30,11 @@ function createOptionProcessor() {
30
30
  optionClashes: [],
31
31
  option,
32
32
  command,
33
- positionalArgument,
33
+ positionalArgument: (argumentDefinition) => {
34
+ // Default positional arguments; may be overwritten by commands.
35
+ _positionalArguments(argumentDefinition);
36
+ return optionProcessor;
37
+ },
34
38
  help,
35
39
  processCmdLine,
36
40
  verifyOptions,
@@ -66,7 +70,12 @@ function createOptionProcessor() {
66
70
  /** @type {object} */
67
71
  const command = {
68
72
  options: {},
73
+ positionalArguments: [],
69
74
  option,
75
+ positionalArgument: (argumentDefinition) => {
76
+ _positionalArguments(argumentDefinition, command.positionalArguments);
77
+ return command;
78
+ },
70
79
  help,
71
80
  ..._parseCommandString(cmdString)
72
81
  };
@@ -96,28 +105,34 @@ function createOptionProcessor() {
96
105
  }
97
106
 
98
107
  /**
99
- * Adds positional arguments to the command line processor. Instructs the processor
100
- * to either require N positional arguments or a dynamic number (but at least one)
101
- * @param {string} positionalArgumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
108
+ * Set the positional arguments to the command line processor. Instructs the processor
109
+ * to either require N positional arguments or a dynamic number (but at least one).
110
+ * Note that you can only call this function once. Only the last invocation sets
111
+ * the positional arguments.
112
+ *
113
+ * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
114
+ * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
102
115
  */
103
- function positionalArgument(positionalArgumentDefinition) {
104
- if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
116
+ function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
117
+ if (argList.find((arg) => arg.isDynamic)) {
105
118
  throw new Error(`Can't add positional arguments after a dynamic one`);
106
119
  }
107
120
 
108
- const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
109
- const args = positionalArgumentDefinition.split(' ');
121
+ const registeredNames = argList.map((arg) => arg.name);
122
+ const args = argumentDefinition.split(' ');
110
123
 
111
124
  for (const arg of args) {
112
- const argName = arg.replace('<', '').replace('>', '').replace('...', '');
125
+ // Remove braces, dots and camelify.
126
+ const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
127
+
113
128
  if (registeredNames.includes(argName)) {
114
- throw new Error(`Duplicate positional argument ${arg}`);
129
+ throw new Error(`Duplicate positional argument: ${arg}`);
115
130
  }
116
131
  if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
117
132
  throw new Error(`Unknown positional argument syntax: ${arg}`)
118
133
  }
119
134
 
120
- optionProcessor.positionalArguments.push({
135
+ argList.push({
121
136
  name: argName,
122
137
  isDynamic: isDynamicPositionalArgument(arg),
123
138
  required: true
@@ -125,7 +140,6 @@ function createOptionProcessor() {
125
140
 
126
141
  registeredNames.push(argName);
127
142
  }
128
- return optionProcessor;
129
143
  }
130
144
 
131
145
  /**
@@ -390,21 +404,38 @@ function createOptionProcessor() {
390
404
  }
391
405
 
392
406
  // Complain about first missing positional arguments
393
- const missingArg = optionProcessor.positionalArguments.find((arg) => arg.required && !result.args[arg.name]);
407
+ const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
394
408
  if (missingArg) {
395
- result.errors.push(`Missing positional argument: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
409
+ const forCommand = result.command ? ` for '${ result.command }'` : '';
410
+ result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
396
411
  }
397
412
 
398
413
  return result;
399
414
 
415
+ /**
416
+ * Specific commands may have custom positional arguments.
417
+ * If the current one does, use it instead of the defaults.
418
+ *
419
+ * @returns {object[]} Array of positional argument configurations.
420
+ */
421
+ function getCurrentPositionArguments() {
422
+ const cmd = optionProcessor.commands[result.command];
423
+ const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
+ return args;
425
+ }
426
+
400
427
  function processPositionalArgument(argumentValue) {
401
- if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
428
+ const argList = getCurrentPositionArguments();
429
+ if ( result.args.length === 0 && argList.length === 0 )
402
430
  return;
403
- const inBounds = result.args.length < optionProcessor.positionalArguments.length;
404
- const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;
405
- const nextUnsetArgument = optionProcessor.positionalArguments[lastIndex];
431
+ const inBounds = result.args.length < argList.length;
432
+ const lastIndex = inBounds ? result.args.length : argList.length - 1;
433
+ const nextUnsetArgument = argList[lastIndex];
406
434
  if (!inBounds && !nextUnsetArgument.isDynamic) {
407
- result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
435
+ if (result.command)
436
+ result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
437
+ else
438
+ result.errors.push(`Too many arguments. Expected ${argList.length}`);
408
439
  return;
409
440
  }
410
441
  result.args.length += 1;
@@ -486,7 +517,10 @@ function createOptionProcessor() {
486
517
  }
487
518
 
488
519
  if(options) {
489
- ['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
520
+ [
521
+ 'defaultBinaryLength', 'defaultStringLength',
522
+ /*'length', 'precision', 'scale'*/
523
+ ].forEach(facet => {
490
524
  if(options[facet] && isNaN(options[facet])) {
491
525
  result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
492
526
  } else {
@@ -577,12 +611,12 @@ function isLongOption(opt) {
577
611
 
578
612
  // Check if 'opt' looks like a "<foobar>" parameter
579
613
  function isParam(opt) {
580
- return /^<[a-zA-Z]+>$/.test(opt);
614
+ return /^<[a-zA-Z-]+>$/.test(opt);
581
615
  }
582
616
 
583
617
  // Check if 'arg' looks like "<foobar...>"
584
618
  function isDynamicPositionalArgument(arg) {
585
- return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
619
+ return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
586
620
  }
587
621
 
588
622
  module.exports = {
@@ -23,6 +23,10 @@ function validateSelectItems(query) {
23
23
  'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
24
24
  }
25
25
  }
26
+ else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
27
+ this.error(null, selectItem.$path,
28
+ 'Window functions are not supported by SAP HANA CDS');
29
+ }
26
30
  });
27
31
  // .call() with 'this' to ensure we have access to the options
28
32
  rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { getVariableReplacement } = require('../model/csnUtils');
4
+
3
5
  // We only care about the "wild" ones - $at is validated by the compiler
4
6
  const magicVariables = {
5
7
  $user: [
@@ -17,7 +19,7 @@ const magicVariables = {
17
19
  *
18
20
  * Valid ways:
19
21
  * - We know what to do -> $user.id on HANA
20
- * - The user tells us what to do -> options.magicVars
22
+ * - The user tells us what to do -> options.variableReplacements
21
23
  *
22
24
  * @param {object} parent Object with the ref as a property
23
25
  * @param {string} name Name of the ref property on parent
@@ -28,8 +30,9 @@ function unknownMagicVariable(parent, name, ref) {
28
30
  const [ head, ...rest ] = ref;
29
31
  const tail = rest.join('.');
30
32
  const magicVariable = magicVariables[head];
31
- if (magicVariable && magicVariable.indexOf(tail) === -1)
32
- this.error(null, parent.$location, { id: tail, elemref: parent }, 'Magic variable is not supported - path $(ELEMREF), step $(ID)');
33
+ if (magicVariable && magicVariable.indexOf(tail) === -1 &&
34
+ getVariableReplacement(ref, this.options) === null)
35
+ this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
33
36
  }
34
37
  }
35
38
 
@@ -98,6 +98,8 @@ function assertConsistency( model, stage ) {
98
98
  '$blocks',
99
99
  '$newfeatures',
100
100
  '$messageFunctions',
101
+ '$functions',
102
+ '$volatileFunctions',
101
103
  ],
102
104
  },
103
105
  ':parser': { // top-level from parser
@@ -479,6 +481,7 @@ function assertConsistency( model, stage ) {
479
481
  },
480
482
  items: {
481
483
  kind: true,
484
+ also: [ 0 ], // 0 for cyclic expansions
482
485
  requires: [ 'location' ],
483
486
  optional: [
484
487
  'enum',
@@ -588,6 +591,8 @@ function assertConsistency( model, stage ) {
588
591
  $sources: { parser: true, test: isArray( isString ) },
589
592
  $expected: { parser: true, test: isString },
590
593
  $messageFunctions: { test: TODO },
594
+ $functions: { test: TODO },
595
+ $volatileFunctions: { test: TODO },
591
596
  };
592
597
  let _noSyntaxErrors = null;
593
598
  assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
@@ -659,6 +664,8 @@ function assertConsistency( model, stage ) {
659
664
  }
660
665
 
661
666
  function standard( node, parent, prop, spec, name ) {
667
+ if (spec.also && spec.also.includes( node ))
668
+ return;
662
669
  isObject( node, parent, prop, spec, name );
663
670
 
664
671
  const names = Object.getOwnPropertyNames( node );
@@ -0,0 +1,65 @@
1
+ // Base Definitions for the Core Compiler
2
+
3
+
4
+ 'use strict';
5
+
6
+ const dictKinds = {
7
+ definitions: 'absolute',
8
+ elements: 'element',
9
+ enum: 'enum',
10
+ foreignKeys: 'key',
11
+ actions: 'action',
12
+ params: 'param',
13
+ };
14
+
15
+ const kindProperties = {
16
+ // TODO: also foreignKeys ?
17
+ namespace: { artifacts: true }, // on-the-fly context
18
+ context: { artifacts: true, normalized: 'namespace' },
19
+ service: { artifacts: true, normalized: 'namespace' },
20
+ entity: { elements: true, actions: true, params: () => false },
21
+ select: { normalized: 'select', elements: true },
22
+ $join: { normalized: 'select' },
23
+ $tableAlias: { normalized: 'alias' }, // table alias in select
24
+ $self: { normalized: 'alias' }, // table alias in select
25
+ $navElement: { normalized: 'element' },
26
+ $inline: { normalized: 'element' }, // column with inline property
27
+ event: { elements: true },
28
+ type: { elements: propExists, enum: propExists },
29
+ aspect: { elements: propExists },
30
+ annotation: { elements: propExists, enum: propExists },
31
+ enum: { normalized: 'element' },
32
+ element: { elements: propExists, enum: propExists, dict: 'elements' },
33
+ mixin: { normalized: 'alias' },
34
+ action: {
35
+ params: () => false, elements: () => false, enum: () => false, dict: 'actions',
36
+ }, // no extend params, only annotate
37
+ function: {
38
+ params: () => false, elements: () => false, enum: () => false, normalized: 'action',
39
+ }, // no extend params, only annotate
40
+ key: { normalized: 'element' },
41
+ param: { elements: () => false, enum: () => false, dict: 'params' },
42
+ source: { artifacts: true }, // TODO -> $source
43
+ using: {},
44
+ extend: {
45
+ isExtension: true,
46
+ noDep: 'special',
47
+ elements: true, /* only for parse-cdl */
48
+ actions: true, /* only for parse-cdl */
49
+ },
50
+ annotate: {
51
+ isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
52
+ },
53
+ builtin: {}, // = CURRENT_DATE, TODO: improve
54
+ $parameters: {}, // $parameters in query entities
55
+ };
56
+
57
+ function propExists( prop, parent ) {
58
+ const obj = parent.returns || parent;
59
+ return (obj.items || obj.targetAspect || obj)[prop];
60
+ }
61
+
62
+ module.exports = {
63
+ dictKinds,
64
+ kindProperties,
65
+ };
@@ -4,7 +4,7 @@
4
4
 
5
5
  const { forEachInDict } = require('../base/dictionaries');
6
6
  const { builtinLocation } = require('../base/location');
7
- const { setProp } = require('../base/model');
7
+ const { setProp } = require('./utils');
8
8
 
9
9
  const core = {
10
10
  String: { parameters: [ 'length' ], category: 'string' },
@@ -167,6 +167,31 @@ function isRelationTypeName(typeName) {
167
167
  return typeCategories.relation.includes(typeName);
168
168
  }
169
169
 
170
+ /**
171
+ * Checks whether the given absolute path is inside a reserved namespace.
172
+ *
173
+ * @param {string} absolute
174
+ * @returns {boolean}
175
+ */
176
+ function isInReservedNamespace(absolute) {
177
+ return absolute.startsWith( 'cds.') &&
178
+ !absolute.match(/^cds\.foundation(\.|$)/) &&
179
+ !absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
180
+ }
181
+
182
+ /**
183
+ * Tell if a type is (directly) a builtin type
184
+ * Note that in CSN builtins are not in the definition of the model, so we can only
185
+ * check against their absolute names. Builtin types are "cds.<something>", i.e. they
186
+ * are directly in 'cds', but not for example in 'cds.foundation'.
187
+ *
188
+ * @param {string} type
189
+ * @returns {boolean}
190
+ */
191
+ function isBuiltinType(type) {
192
+ return typeof type === 'string' && isInReservedNamespace(type);
193
+ }
194
+
170
195
  /**
171
196
  * Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
172
197
  * `definitions` of the XSN model as well as to `$builtins`.
@@ -269,6 +294,8 @@ module.exports = {
269
294
  functionsWithoutParens,
270
295
  specialFunctions,
271
296
  initBuiltins,
297
+ isInReservedNamespace,
298
+ isBuiltinType,
272
299
  isIntegerTypeName,
273
300
  isDecimalTypeName,
274
301
  isNumericTypeName,
@@ -632,8 +632,9 @@ function check( model ) { // = XSN
632
632
  * @returns {void}
633
633
  */
634
634
  function checkTokenStreamExpression(xpr, allowAssocTail) {
635
+ const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
635
636
  // Check for illegal argument usage within the expression
636
- for (const arg of xpr.args || []) {
637
+ for (const arg of args) {
637
638
  if (isVirtualElement(arg))
638
639
  error(null, arg.location, 'Virtual elements can\'t be used in an expression');
639
640