@sap/cds-compiler 2.13.8 → 2.15.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 (78) hide show
  1. package/CHANGELOG.md +128 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +63 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +100 -17
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +36 -17
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +93 -21
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +305 -226
  28. package/lib/edm/edmPreprocessor.js +499 -423
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +98 -22
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +94 -64
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +8 -6
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +5 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
package/CHANGELOG.md CHANGED
@@ -7,27 +7,151 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 2.15.6 - 2022-07-26
11
+
12
+ ### Fixed
13
+
14
+ - Annotations on sub-elements were lost during re-compilation.
15
+
16
+ ## Version 2.15.4 - 2022-06-09
17
+
18
+ ### Fixed
19
+
20
+ - for.odata:
21
+ + Fix derived type to scalar type resolution with intermediate `many`.
22
+ - to.edm(x):
23
+ + (V4 structured) Fix key paths in combination with `--odata-foreign-keys`.
24
+ + Add `Edm.PrimitiveType` to `@odata.Type`.
25
+ + (V4 JSON) Render constant expressions for `Edm.Stream` and `Edm.Untyped`.
26
+ + Fix a bug in target path calculation for `NavigationPropertyBinding`s to external references.
27
+ + Render inner annotations even if `$value` is missing.
28
+ - Update OData vocabularies 'Common', 'UI'.
29
+
30
+ ## Version 2.15.2 - 2022-05-12
31
+
32
+ ### Fixed
33
+
34
+ - Option `cdsHome` can be used instead of `global.cds.home` to specify the path to `@sap/cds/`.
35
+ - to.edm(x):
36
+ + Set anonymous nested proxy key elements to `Nullable:false` until first named type is reached.
37
+ + Enforce `odata-spec-violation-key-null` on explicit foreign keys of managed primary key associations.
38
+ + Proxies/service cross references are no longer created for associations with arbitrary ON conditions.
39
+ Only managed or `$self` backlink association targets are proxy/service cross reference candidates.
40
+ + Explicit foreign keys of a managed association that are not a primary key in the target are exposed in the the proxy.
41
+ + If an association is primary key, the resulting navigation property is set to `Nullable:false` in structured mode.
42
+
43
+ ## Version 2.15.0 - 2022-05-06
44
+
45
+ ### Added
46
+
47
+ - A new warning is emitted if `excluding` is used without a wildcard, since this does
48
+ not have any effect.
49
+ - All scalar types can now take named arguments, e.g. `MyString(length: 10)`.
50
+ For custom scalar types, one unnamed arguments is interpreted as length, two arguments are interpreted
51
+ as precision and scale, e.g. `MyDecimal(3,3)`.
52
+ - If the type `sap.common.Locale` exists, it will be used as type for the `locale` element
53
+ of generated texts entities. The type must be a `cds.String`.
54
+ - to.cdl: Extend statements (from `extensions`) can now be rendered.
55
+ - Add OData vocabulary 'Hierarchy'.
56
+ - CDL: New associations can be published in queries, e.g. `assoc : Association to Target on assoc.id = id`
57
+
58
+ ### Changed
59
+
60
+ - to.edm(x):
61
+ + perform inbound qualification and spec violation checks as well as most/feasible EDM preprocessing steps
62
+ on requested services only.
63
+ + Open up `@odata { Type, MaxLength, Precision, Scale, SRID }` annotation.
64
+ The annotations behavior is defined as follows:
65
+ + The element/parameter must have a scalar CDS type. The annotation is not applied on named types
66
+ (With the V2 exception where derived type chains terminating in a scalar type are resolved).
67
+ + The value of `@odata.Type` must be a valid `EDM` type for the rendered protocol version.
68
+ + If `@odata.Type` can be applied, all canonic type facets (`MaxLength`, `Precision`, `Scale`, `SRID`) are
69
+ removed from the Edm Node and the new facets `@odata { MaxLength, Precision, Scale, SRID }` are applied.
70
+ Non Edm type conformant facets are ignored (eg. `@odata { Type: 'Edm.Decimal', MaxLength: 10, SRID: 0 }`).
71
+ + Type facet values are not evaluated.
72
+ + V2: Propagate `@Core.MediaType` annotation from stream element to entity type if not set.
73
+ - to.edm: Render constant expressions in short notation.
74
+ - Update OData Vocabularies: 'Common', 'Graph', 'Validation'.
75
+
76
+ ### Fixed
77
+
78
+ - to.cdl:
79
+ + Annotations of elements of action `returns` are now rendered as `annotate` statements.
80
+ + Annotations on columns (query sub-elements) were not always rendered.
81
+ + Doc comments on bound actions were rendered twice.
82
+ + Unapplied annotations for action parameters were not rendered.
83
+ + Unions and joins are correctly put into parentheses.
84
+ + Add parentheses around certain expressions in function bodies that require it, such as `fct((1=1))`.
85
+ - to.edm(x):
86
+ + Fix a bug in top level and derived type `items` exposure leading to undefined type rendering.
87
+ + Fix a naming bug in type exposure leading to false reuse types, disguising invididual type
88
+ modifications (such as annotations, (auto-)redirections, element extensions).
89
+ + Ignore `@Aggregation.default`.
90
+ + Consolidate message texts and formatting.
91
+ + Fix navigation property binding in cross service rendering mode.
92
+ + Remove partner attribute in proxy/cross service navigations.
93
+ - Core engine (function `compile`):
94
+ + Annotations for new columns inside `extend projection` blocks were not used.
95
+ + Extending an unknown select item resulted in a crash.
96
+ + Extending a context/service with columns now correctly emits an error.
97
+ + Unmanaged `redirected to` in queries did not check whether the source is an association.
98
+ - parseCdl: `extend <art> with enum {...}` incorrectly threw a compiler error.
99
+ - API: `compile()` used a synchronous call `fs.realpathSync()` on the input filename array.
100
+ Now the asynchronous `fs.realpath()` is used.
101
+ - On-conditions in localized convenience views may be incorrectly rewritten if an element
102
+ has the same as a localized entity.
103
+ - to.sql/hdi/hdbcds:
104
+ + No referential constraint is generated for an association if its parent
105
+ or target entity are annotated with `@cds.persistence.exists: true`.
106
+ + Fix rendering of virtual elements in subqueries
107
+ + Correctly process subqueries in JOINs
108
+ - to.sql/hdi: Queries with `UNION`, `INTERSECT` and similar in expressions are now enclosed in parentheses.
109
+
110
+ ## Version 2.14.0 - 2022-04-08
111
+
112
+ ### Added
113
+
114
+ - cdsc:
115
+ + `--quiet` can now be used to suppress compiler output, including messages.
116
+ + `--options <file.json>` can be used to load compiler options. A JSON file is expected. Is compatible to CDS `package.json`
117
+ and `.cdsrc.json` by first looking for `cdsc` key in `cds`, then for a `cdsc` key and otherwise uses the full JSON file.
118
+ + `--[error|warn|info|debug] id1,id2` can be used to reclassify specific messages.
119
+ - Add OData Vocabularies: 'DataIntegration', 'JSON'.
120
+
121
+ ### Changed
122
+
123
+ - Update OData Vocabularies: 'UI'.
124
+
125
+ ### Fixed
126
+
127
+ - to.cdl:
128
+ + Delimited identifiers as the last elements of arrays in annotation values are now
129
+ rendered with spaces in between, to avoid accidentally escaping `]`.
130
+ + Identifiers in includes and redirection targets were not quoted if they are reserved keywords.
131
+ - to.edm(x): Correctly rewrite `@Capabilities.ReadRestrictions.ReadByKeyRestrictions` into
132
+ `@Capabilities.NavigationPropertyRestriction` in containment mode.
133
+
10
134
  ## Version 2.13.8 - 2022-03-29
11
135
 
12
136
  ### Fixed
13
137
 
14
138
  - to.hdbcds/hdi/sql: Correctly handle `localized` in conjunction with `@cds.persistence.exists` and `@cds.persistence.skip`
15
139
 
16
- ## Version 2.13.6 - 2022-25-03
140
+ ## Version 2.13.6 - 2022-03-25
17
141
 
18
142
  ### Fixed
19
143
 
20
144
  - to.hdbcds/hdi/sql: Correctly handle `localized` in conjunction with `@cds.persistence.exists`
21
145
 
22
- ## Version 2.13.4 - 2022-22-03
146
+ ## Version 2.13.4 - 2022-03-22
23
147
 
24
148
  No changes compared to Version 2.13.0; fixes latest NPM tag
25
149
 
26
- ## Version 2.13.2 - 2022-22-03
150
+ ## Version 2.13.2 - 2022-03-22
27
151
 
28
152
  No changes compared to Version 2.13.0; fixes latest NPM tag
29
153
 
30
- ## Version 2.13.0 - 2022-22-03
154
+ ## Version 2.13.0 - 2022-03-22
31
155
 
32
156
  ### Added
33
157
 
package/bin/cdsc.js CHANGED
@@ -44,6 +44,18 @@ class ProcessExitError extends Error {
44
44
  }
45
45
  }
46
46
 
47
+ try {
48
+ cdsc_main();
49
+ }
50
+ catch (err) {
51
+ // This whole try/catch is only here because process.exit does not work in combination with
52
+ // stdout/err - see comment at ProcessExitError
53
+ if (err instanceof ProcessExitError)
54
+ process.exitCode = err.exitCode;
55
+ else
56
+ throw err;
57
+ }
58
+
47
59
  function remapCmdOptions(options, cmdOptions) {
48
60
  if (!cmdOptions)
49
61
  return options;
@@ -73,6 +85,9 @@ function remapCmdOptions(options, cmdOptions) {
73
85
  options.variableReplacements.$user = {};
74
86
  options.variableReplacements.$user.locale = value;
75
87
  break;
88
+ case 'serviceNames':
89
+ options.serviceNames = value.split(',');
90
+ break;
76
91
  default:
77
92
  options[key] = value;
78
93
  }
@@ -80,8 +95,9 @@ function remapCmdOptions(options, cmdOptions) {
80
95
  return options;
81
96
  }
82
97
 
83
- // Parse the command line and translate it into options
84
- try {
98
+ function cdsc_main() {
99
+ // Parse the command line and translate it into options
100
+
85
101
  const cmdLine = optionProcessor.processCmdLine(process.argv);
86
102
  // Deal with '--version' explicitly
87
103
  if (cmdLine.options.version) {
@@ -115,6 +131,11 @@ try {
115
131
  displayUsage(cmdLine.errors, optionProcessor.helpText, 2);
116
132
  }
117
133
 
134
+ if (cmdLine.options.options) {
135
+ if (!loadOptionsFromJson(cmdLine))
136
+ return;
137
+ }
138
+
118
139
  // Default warning level is 2 (info)
119
140
  // FIXME: Is that not set anywhere in the API?
120
141
  if (!cmdLine.options.warning)
@@ -125,11 +146,7 @@ try {
125
146
  cmdLine.options.out = '-';
126
147
 
127
148
  // --cds-home <dir>: modules starting with '@sap/cds/' are searched in <dir>
128
- if (cmdLine.options.cdsHome) {
129
- if (!global.cds)
130
- global.cds = {};
131
- global.cds.home = cmdLine.options.cdsHome;
132
- }
149
+ // -> cmdLine.options.cdsHome is passed down to moduleResolve
133
150
 
134
151
  // Set default command if required
135
152
  cmdLine.command = cmdLine.command || 'toCsn';
@@ -147,8 +164,8 @@ try {
147
164
  if (cmdLine.options.directBackend)
148
165
  validateDirectBackendOption(cmdLine.command, cmdLine.options, cmdLine.args);
149
166
 
150
-
151
- if (cmdLine.options.beta) {
167
+ // If set through CLI (and not options file), `beta` is a string and needs processing.
168
+ if (cmdLine.options.beta && typeof cmdLine.options.beta === 'string') {
152
169
  const features = cmdLine.options.beta.split(',');
153
170
  cmdLine.options.beta = {};
154
171
  features.forEach((val) => {
@@ -156,35 +173,32 @@ try {
156
173
  });
157
174
  }
158
175
 
176
+ const to = cmdLine.options.toSql ? 'toSql' : 'toHana';
159
177
  // remap string values for `assertIntegrity` option to boolean
160
- if (cmdLine.options.assertIntegrity &&
161
- cmdLine.options.assertIntegrity === 'true' ||
162
- cmdLine.options.assertIntegrity === 'false'
178
+ if (cmdLine.options[to] && cmdLine.options[to].assertIntegrity &&
179
+ (cmdLine.options[to].assertIntegrity === 'true' ||
180
+ cmdLine.options[to].assertIntegrity === 'false')
163
181
  )
164
- cmdLine.options.assertIntegrity = cmdLine.options.assertIntegrity === 'true';
182
+ cmdLine.options[to].assertIntegrity = cmdLine.options[to].assertIntegrity === 'true';
165
183
 
166
184
  // Enable all beta-flags if betaMode is set to true
167
185
  if (cmdLine.options.betaMode)
168
186
  cmdLine.options.beta = availableBetaFlags;
169
187
 
170
- if (cmdLine.options.deprecated) {
188
+ // If set through CLI (and not options file), `deprecated` is a string and needs processing.
189
+ if (cmdLine.options.deprecated && typeof cmdLine.options.deprecated === 'string') {
171
190
  const features = cmdLine.options.deprecated.split(',');
172
191
  cmdLine.options.deprecated = {};
173
192
  features.forEach((val) => {
174
193
  cmdLine.options.deprecated[val] = true;
175
194
  });
176
195
  }
196
+
197
+ parseSeverityOptions(cmdLine);
198
+
177
199
  // Do the work for the selected command
178
200
  executeCommandLine(cmdLine.command, cmdLine.options, cmdLine.args);
179
201
  }
180
- catch (err) {
181
- // This whole try/catch is only here because process.exit does not work in combination with
182
- // stdout/err - see comment at ProcessExitError
183
- if (err instanceof ProcessExitError)
184
- process.exitCode = err.exitCode;
185
- else
186
- throw err;
187
- }
188
202
 
189
203
  /**
190
204
  * `--direct-backend` can only be used with certain backends and with certain files.
@@ -230,10 +244,10 @@ function displayUsage(error, helpText, code) {
230
244
  function executeCommandLine(command, options, args) {
231
245
  const normalizeFilename = options.testMode && process.platform === 'win32';
232
246
  const messageLevels = {
233
- Error: 0, Warning: 1, Info: 2, None: 3,
247
+ Error: 0, Warning: 1, Info: 2, Debug: 3,
234
248
  };
235
249
  // All messages are put into the message array, even those which should not
236
- // been displayed (severity 'None')
250
+ // been displayed (severity 'Debug')
237
251
 
238
252
  // Create output directory if necessary
239
253
  if (options.out && options.out !== '-' && !fs.existsSync(options.out))
@@ -329,17 +343,18 @@ function executeCommandLine(command, options, args) {
329
343
  options.toOdata.odataContainment = true;
330
344
  }
331
345
  const csn = options.directBackend ? model : compactModel(model, options);
332
- const odataCsn = main.for.odata(csn, remapCmdOptions(options, options.toOdata));
346
+ const odataOptions = remapCmdOptions(options, options.toOdata);
333
347
  if (options.toOdata && options.toOdata.csn) {
348
+ const odataCsn = main.for.odata(csn, odataOptions);
334
349
  displayNamedCsn(odataCsn, 'odata_csn');
335
350
  }
336
351
  else if (options.toOdata && options.toOdata.json) {
337
- const result = main.to.edm.all(odataCsn, options);
352
+ const result = main.to.edm.all(csn, odataOptions);
338
353
  for (const serviceName in result)
339
354
  writeToFileOrDisplay(options.out, `${serviceName}.json`, result[serviceName]);
340
355
  }
341
356
  else {
342
- const result = main.to.edmx.all(odataCsn, options);
357
+ const result = main.to.edmx.all(csn, odataOptions);
343
358
  for (const serviceName in result)
344
359
  writeToFileOrDisplay(options.out, `${serviceName}.xml`, result[serviceName]);
345
360
  }
@@ -447,7 +462,7 @@ function executeCommandLine(command, options, args) {
447
462
  * @param {CompileMessage[]} messages
448
463
  */
449
464
  function displayMessages( model, messages = options.messages ) {
450
- if (!Array.isArray(messages))
465
+ if (!Array.isArray(messages) || options.quiet)
451
466
  return model;
452
467
 
453
468
  const log = console.error;
@@ -552,6 +567,8 @@ function executeCommandLine(command, options, args) {
552
567
  content = JSON.stringify(content, null, 2);
553
568
 
554
569
  if (dir === '-') {
570
+ if (options.quiet)
571
+ return;
555
572
  if (!omitHeadline) {
556
573
  const sqlTypes = {
557
574
  sql: true, hdbconstraint: true, hdbtable: true, hdbview: true,
@@ -569,15 +586,73 @@ function executeCommandLine(command, options, args) {
569
586
  fs.writeFileSync(path.join(dir, fileName), content);
570
587
  }
571
588
  }
589
+ }
572
590
 
573
- function catchErrors(err) {
574
- // @ts-ignore
575
- if (err instanceof Error && err.hasBeenReported)
576
- return;
577
- console.error( '' );
578
- console.error( 'INTERNAL ERROR:' );
579
- console.error( util.inspect(err, false, null) );
580
- console.error( '' );
581
- process.exitCode = 70;
591
+ function loadOptionsFromJson(cmdLine) {
592
+ try {
593
+ let opt = JSON.parse(fs.readFileSync(cmdLine.options.options, 'utf-8'));
594
+ if (opt.cds)
595
+ opt = opt.cds;
596
+ if (opt.cdsc)
597
+ opt = opt.cdsc;
598
+ Object.assign(cmdLine.options, opt);
599
+ return true;
600
+ }
601
+ catch (e) {
602
+ catchErrors(e);
603
+ return false;
604
+ }
605
+ }
606
+
607
+ function catchErrors(err) {
608
+ // @ts-ignore
609
+ if (err instanceof Error && err.hasBeenReported)
610
+ return;
611
+ console.error( '' );
612
+ console.error( 'INTERNAL ERROR:' );
613
+ console.error( util.inspect(err, false, null) );
614
+ console.error( '' );
615
+ process.exitCode = 70;
616
+ }
617
+
618
+ /**
619
+ * Parses the options `--error` and similar.
620
+ * Sets the dictionary `severities` on the given options.
621
+ *
622
+ * @param {object} options
623
+ */
624
+ function parseSeverityOptions({ options }) {
625
+ if (!options.severities)
626
+ options.severities = Object.create(null);
627
+
628
+ const severityMap = {
629
+ error: 'Error',
630
+ warn: 'Warning',
631
+ info: 'Info',
632
+ debug: 'Debug',
633
+ };
634
+
635
+ // Note: We use a for loop to ensure that the order of the options on the command line is respected, i.e.
636
+ // `--warn id --error id` would lead to `id` being reclassified as an error and not a warning.
637
+ for (const key in options) {
638
+ switch (key) {
639
+ case 'error':
640
+ case 'warn':
641
+ case 'info':
642
+ case 'debug':
643
+ parseSeverityOption(options[key], severityMap[key]);
644
+ break;
645
+ default:
646
+ break;
647
+ }
648
+ }
649
+
650
+ function parseSeverityOption(list, severity) {
651
+ const ids = list.split(',');
652
+ for (let id of ids) {
653
+ id = id.trim();
654
+ if (id)
655
+ options.severities[id] = severity;
656
+ }
582
657
  }
583
658
  }
package/lib/api/main.js CHANGED
@@ -33,9 +33,10 @@ const propertyToCheck = {
33
33
  odata: 'toOdata',
34
34
  };
35
35
 
36
- const { cloneCsn } = require('../model/csnUtils');
36
+ const { cloneCsnNonDict } = require('../model/csnUtils');
37
37
  const { toHdbcdsSource } = require('../render/toHdbcds');
38
38
  const { ModelError } = require('../base/error');
39
+ const { forEach, forEachKey } = require('../utils/objectUtils');
39
40
 
40
41
  const relevantGeneralOptions = [ /* for future generic options */ ];
41
42
  const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];
@@ -91,7 +92,7 @@ function checkPreTransformedCsn(csn, options, relevantOptionNames, warnAboutMism
91
92
  // Not able to check
92
93
  return;
93
94
  }
94
- const { error, warning, throwWithError } = makeMessageFunction(csn, options, module);
95
+ const { error, warning, throwWithAnyError } = makeMessageFunction(csn, options, module);
95
96
 
96
97
  for (const name of relevantOptionNames ) {
97
98
  if (options[name] !== csn.meta.options[name])
@@ -103,7 +104,7 @@ function checkPreTransformedCsn(csn, options, relevantOptionNames, warnAboutMism
103
104
  warning('options-mismatch-pretransformed-csn', null, `Expected pre-processed CSN to have option "${ name }" set to "${ options[name] }". Found: "${ csn.meta.options[name] }"`);
104
105
  }
105
106
 
106
- throwWithError();
107
+ throwWithAnyError();
107
108
  }
108
109
 
109
110
  /**
@@ -153,7 +154,7 @@ function odata(csn, options = {}) {
153
154
  */
154
155
  function cdl(csn, externalOptions = {}) {
155
156
  const internalOptions = prepareOptions.to.cdl(externalOptions);
156
- const { result } = backends.toCdlWithCsn(cloneCsn(csn, internalOptions), internalOptions);
157
+ const { result } = backends.toCdlWithCsn(cloneCsnNonDict(csn, internalOptions), internalOptions);
157
158
  return result;
158
159
  }
159
160
  /**
@@ -251,7 +252,7 @@ function hdi(csn, options = {}) {
251
252
  const sqlArtifactsWithCSNNamesToSort = Object.create(null);
252
253
  const sqlArtifactsNotToSort = Object.create(null);
253
254
 
254
- Object.keys(flat).forEach((key) => {
255
+ forEach(flat, (key) => {
255
256
  const artifactNameLikeInCsn = key.replace(/\.[^/.]+$/, '');
256
257
  nameMapping[artifactNameLikeInCsn] = key;
257
258
  if (key.endsWith('.hdbtable') || key.endsWith('.hdbview'))
@@ -269,7 +270,7 @@ function hdi(csn, options = {}) {
269
270
  }, Object.create(null));
270
271
 
271
272
  // now add the not-sorted stuff, like indices
272
- Object.keys(sqlArtifactsNotToSort).forEach((key) => {
273
+ forEach(sqlArtifactsNotToSort, (key) => {
273
274
  sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlArtifactsNotToSort[key];
274
275
  });
275
276
 
@@ -291,10 +292,10 @@ function hdi(csn, options = {}) {
291
292
  function remapNames(dict, csn, filter) {
292
293
  const result = Object.create(null);
293
294
 
294
- for (const [ key, value ] of Object.entries(dict)) {
295
+ forEach(dict, (key, value) => {
295
296
  const name = remapName(key, csn, filter);
296
297
  result[name] = value;
297
- }
298
+ });
298
299
 
299
300
  return result;
300
301
  }
@@ -396,15 +397,15 @@ function hdiMigration(csn, options, beforeImage) {
396
397
  */
397
398
  function createDefinitions() {
398
399
  const result = [];
399
- for (const [ kind, artifacts ] of Object.entries(hdbkinds)) {
400
+ forEach(hdbkinds, (kind, artifacts) => {
400
401
  const suffix = `.${ kind }`;
401
- for (const [ name, sqlStatement ] of Object.entries(artifacts)) {
402
+ forEach(artifacts, (name, sqlStatement) => {
402
403
  if ( kind !== 'hdbindex' )
403
404
  result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
404
405
  else
405
406
  result.push({ name, suffix, sql: sqlStatement });
406
- }
407
- }
407
+ });
408
+ });
408
409
  return result;
409
410
  }
410
411
  /**
@@ -414,9 +415,7 @@ function hdiMigration(csn, options, beforeImage) {
414
415
  */
415
416
  function createDeletions() {
416
417
  const result = [];
417
- for (const [ name ] of Object.entries(deletions))
418
- result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' });
419
-
418
+ forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
420
419
  return result;
421
420
  }
422
421
  /**
@@ -426,9 +425,7 @@ function hdiMigration(csn, options, beforeImage) {
426
425
  */
427
426
  function createMigrations() {
428
427
  const result = [];
429
- for (const [ name, changeset ] of Object.entries(migrations))
430
- result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset });
431
-
428
+ forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
432
429
  return result;
433
430
  }
434
431
  }
@@ -586,12 +583,13 @@ function edmxall(csn, options = {}) {
586
583
  */
587
584
  function flattenResultStructure(toProcess) {
588
585
  const result = {};
589
- for (const [ fileType, artifacts ] of Object.entries(toProcess)) {
586
+ forEach(toProcess, (fileType, artifacts) => {
590
587
  if (fileType === 'messages')
591
- continue;
592
- for (const filename of Object.keys(artifacts))
588
+ return;
589
+ forEach(artifacts, (filename) => {
593
590
  result[`${ filename }.${ fileType }`] = artifacts[filename];
594
- }
591
+ });
592
+ });
595
593
 
596
594
  return result;
597
595
  }
@@ -641,6 +639,7 @@ function publishCsnProcessor( processor, _name ) {
641
639
  */
642
640
  function api( csn, options = {}, ...args ) {
643
641
  try {
642
+ checkOutdatedOptions( options );
644
643
  return processor( csn, options, ...args );
645
644
  }
646
645
  catch (err) {
@@ -664,6 +663,48 @@ function publishCsnProcessor( processor, _name ) {
664
663
  }
665
664
  }
666
665
 
666
+ // Note: No toCsn, because @sap/cds may still use it (2022-06-15)
667
+ const oldBackendOptionNames = [ 'toSql', 'toOdata', 'toHana', 'forHana' ];
668
+ /**
669
+ * Checks if outdated options are used and if so, throw a compiler error.
670
+ * These include:
671
+ * - magicVars (now variableReplacements)
672
+ * - toOdata/toSql/toHana/forHana -> now flat options
673
+ *
674
+ * @param {CSN.Options} options Backend options
675
+ */
676
+ function checkOutdatedOptions(options) {
677
+ if (!options)
678
+ return;
679
+ const { warning } = makeMessageFunction(null, options, 'api');
680
+
681
+ // This warning has been emitted once, we don't need to emit it again.
682
+ if (options.messages && options.messages.some(m => m.messageId === 'api-invalid-option'))
683
+ return;
684
+
685
+ for (const name of oldBackendOptionNames) {
686
+ if (typeof options[name] === 'object') // may be a boolean due to internal options
687
+ warning('api-invalid-option', null, { '#': 'std', name });
688
+ }
689
+
690
+ if (options.magicVars)
691
+ warning('api-invalid-option', null, { '#': 'magicVars' });
692
+
693
+ // Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
694
+ // forgot about user -> $user and locale -> $user.locale
695
+ if (options.variableReplacements) {
696
+ if (options.variableReplacements.user)
697
+ warning('api-invalid-option', null, { '#': 'user' });
698
+ if (options.variableReplacements.locale)
699
+ warning('api-invalid-option', null, { '#': 'locale' });
700
+ }
701
+
702
+ forEachKey(options.variableReplacements || {}, (name) => {
703
+ if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
704
+ warning('api-invalid-option', null, { '#': 'noDollar', name });
705
+ });
706
+ }
707
+
667
708
 
668
709
  /**
669
710
  * Option format used by the old API, where they are grouped thematically.
@@ -80,13 +80,12 @@ const overallOptions = publicOptionsNewAPI.concat(privateOptions);
80
80
  function translateOptions(input = {}, defaults = {}, hardRequire = {},
81
81
  customValidators = {}, combinationValidators = [], moduleName = '') {
82
82
  const options = Object.assign({}, defaults);
83
- const inputOptionNames = Object.keys(input);
84
83
  for (const name of overallOptions) {
85
84
  // Ensure that arrays are not passed as a reference!
86
85
  // This caused issues with the way messages are handled in processMessages
87
- if (Array.isArray(input[name]) && inputOptionNames.includes(name))
86
+ if (Array.isArray(input[name]))
88
87
  options[name] = [ ...input[name] ];
89
- else if (inputOptionNames.includes(name))
88
+ else if (Object.hasOwnProperty.call(input, name))
90
89
  options[name] = input[name];
91
90
  }
92
91
 
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
+ const { forEach } = require('../utils/objectUtils');
4
5
 
5
6
  /* eslint-disable arrow-body-style */
6
7
  const booleanValidator = {
@@ -159,16 +160,15 @@ function validate(options, moduleName, customValidators = {}, combinationValidat
159
160
  // TODO: issuing messages in this function looks very strange...
160
161
  {
161
162
  const messageCollector = { messages: [] };
162
- const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
163
+ const { error, throwWithAnyError } = makeMessageFunction(null, messageCollector, moduleName);
163
164
 
164
- for (const optionName of Object.keys(options)) {
165
- const optionValue = options[optionName];
165
+ forEach(options, (optionName, optionValue) => {
166
166
  const validator = customValidators[optionName] || validators[optionName] || booleanValidator;
167
167
 
168
168
  if (!validator.validate(optionValue))
169
169
  error('invalid-option', null, {}, `Expected option "${ optionName }" to have "${ validator.expected(optionValue) }". Found: "${ validator.found(optionValue) }"`);
170
- }
171
- throwWithError();
170
+ });
171
+ throwWithAnyError();
172
172
  }
173
173
 
174
174
  const message = makeMessageFunction(null, options, moduleName);
@@ -179,7 +179,7 @@ function validate(options, moduleName, customValidators = {}, combinationValidat
179
179
  message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
180
180
  }
181
181
 
182
- message.throwWithError();
182
+ message.throwWithAnyError();
183
183
  }
184
184
  /* eslint-enable jsdoc/no-undefined-types */
185
185