@sap/cds-compiler 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +29 -9
  2. package/bin/cdsc.js +9 -16
  3. package/lib/api/main.js +92 -40
  4. package/lib/base/keywords.js +64 -1
  5. package/lib/base/message-registry.js +17 -1
  6. package/lib/base/messages.js +38 -28
  7. package/lib/base/optionProcessorHelper.js +53 -21
  8. package/lib/compiler/assert-consistency.js +1 -1
  9. package/lib/compiler/builtins.js +40 -1
  10. package/lib/compiler/define.js +4 -2
  11. package/lib/compiler/extend.js +4 -1
  12. package/lib/compiler/populate.js +3 -1
  13. package/lib/compiler/resolve.js +1 -4
  14. package/lib/compiler/shared.js +9 -0
  15. package/lib/compiler/utils.js +2 -2
  16. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  17. package/lib/edm/csn2edm.js +15 -14
  18. package/lib/edm/edm.js +13 -12
  19. package/lib/edm/edmPreprocessor.js +30 -33
  20. package/lib/edm/edmUtils.js +3 -39
  21. package/lib/gen/language.checksum +1 -1
  22. package/lib/gen/language.interp +1 -1
  23. package/lib/gen/languageParser.js +3311 -3289
  24. package/lib/json/from-csn.js +17 -19
  25. package/lib/json/to-csn.js +3 -2
  26. package/lib/language/genericAntlrParser.js +42 -42
  27. package/lib/language/language.g4 +28 -17
  28. package/lib/model/csnRefs.js +1 -0
  29. package/lib/model/csnUtils.js +19 -8
  30. package/lib/model/revealInternalProperties.js +4 -1
  31. package/lib/optionProcessor.js +54 -38
  32. package/lib/render/toHdbcds.js +1 -1
  33. package/lib/render/toSql.js +7 -3
  34. package/lib/transform/forOdataNew.js +3 -3
  35. package/lib/transform/localized.js +15 -11
  36. package/lib/utils/file.js +28 -18
  37. package/package.json +2 -3
  38. package/share/messages/syntax-expected-integer.md +9 -8
@@ -435,7 +435,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
435
435
  // CSN.Location (with line/endLine, col/endCol)
436
436
  return [ location, location.home || null, null ]
437
437
 
438
- const isCsnPath = (typeof location[0] === 'string');
438
+ const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
439
439
  if (isCsnPath) {
440
440
  return [
441
441
  searchForLocation( model, location ),
@@ -1174,7 +1174,7 @@ function homeName( art, absoluteOnly ) {
1174
1174
  return null;
1175
1175
  else if (art.kind === 'using')
1176
1176
  return 'using:' + quoted( art.name.id );
1177
- else if (art.kind === 'extend')
1177
+ else if (art.kind === 'extend' || art.kind === 'annotate')
1178
1178
  return !absoluteOnly && homeNameForExtend ( art );
1179
1179
  else if (art.name._artifact) // block, extend, annotate
1180
1180
  return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
@@ -1189,35 +1189,41 @@ function homeName( art, absoluteOnly ) {
1189
1189
  // The "home" for extensions is handled differently because `_artifact` is not
1190
1190
  // set for unknown extensions and we could have nested extensions.
1191
1191
  function homeNameForExtend( art ) {
1192
+ const kind = art.kind || 'extend';
1192
1193
  // TODO: fix the following - do like in collectArtifactExtensions() or
1193
- // basically resolveUncheckedPath()
1194
- const absoluteName = (art.name.id ? art.name.id :
1195
- art.name.path.map(s => s && s.id).join('.'));
1194
+ // basically resolveUncheckedPath()
1195
+ const absoluteName = art.name.id ? art.name.id :
1196
+ (!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));
1196
1197
 
1197
1198
  // Surrounding parent may be another extension.
1198
1199
  const parent = art._parent;
1199
1200
  if (!parent)
1200
- return 'extend:' + quoted(absoluteName);
1201
-
1202
- // And that extension's artifact could have been found.
1203
- const parentArt = parent.name && parent.name._artifact;
1204
- if (!parentArt)
1205
- return artName(parent) + '/' + quoted(absoluteName);
1206
-
1207
- let extensionName;
1208
- if (parentArt.enum || parentArt.elements) {
1209
- const fakeArt = {
1210
- kind: parentArt.enum ? 'enum' : 'element',
1211
- name: { element: absoluteName }
1212
- };
1213
- extensionName = artName(fakeArt);
1201
+ return kind + ':' + quoted(absoluteName);
1202
+
1203
+ if (art.name.param && parent.params) {
1204
+ const fakeArt = { kind: 'param', name: { param: absoluteName } };
1205
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1214
1206
  }
1215
- else {
1216
- extensionName = 'extend:' + quoted(absoluteName);
1207
+ else if (art.name.action && parent.actions) {
1208
+ const type = art.name._artifact?.kind || 'action';
1209
+ const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
1210
+ return homeNameForExtend(parent) + '/' + artName(fakeArt);
1211
+ }
1212
+ else if (parent.enum || parent.elements || parent.returns?.elements) {
1213
+ // For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
1214
+ // so we need to look at the parent artifact.
1215
+ // For `extend <art> with enum`, there is `enum`.
1216
+ const parentArt = parent.name?._artifact;
1217
+ const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
1218
+ const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
1219
+ let parentOfElementChain = parent;
1220
+ while (parentOfElementChain.name?.element && parentOfElementChain._parent)
1221
+ parentOfElementChain = parentOfElementChain._parent;
1222
+
1223
+ return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
1217
1224
  }
1218
- // Even though the parent artifact was found, we use kind 'extend'
1219
- // to make it clear that we are inside an (element) extension.
1220
- return 'extend:' + artName(parentArt) + '/' + extensionName;
1225
+ // This case should not happen, but just in case
1226
+ return kind + ':' + artName(parent);
1221
1227
  }
1222
1228
 
1223
1229
  function constructSemanticLocationFromCsnPath(csnPath, model) {
@@ -1230,10 +1236,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
1230
1236
  ];
1231
1237
  const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
1232
1238
 
1233
- let { query } = analyseCsnPath(
1234
- csnPath,
1235
- model
1236
- );
1239
+ if (csnPath[0] === 'extensions') {
1240
+ const ext = model.extensions && model.extensions[csnPath[1]] || {};
1241
+ if (ext.annotate)
1242
+ return 'annotate:' + quoted(ext.annotate);
1243
+ return 'extend:' + quoted(ext.extend);
1244
+ }
1245
+
1246
+ let { query } = analyseCsnPath(csnPath, model, false);
1237
1247
 
1238
1248
  // remove definitions
1239
1249
  csnPath.shift();
@@ -153,36 +153,67 @@ function createOptionProcessor() {
153
153
  * Internal: Define a general or command option.
154
154
  * Throws if the option is already registered in the given command context.
155
155
  * or in the given command.
156
+ *
156
157
  * @private
157
158
  * @see option()
158
159
  */
159
160
  function _addOption(cmd, optString, validValues, options) {
160
- const opt = _parseOptionString(optString, validValues);
161
- Object.assign(opt, options);
161
+ const cliOpt = _parseOptionString(optString, validValues);
162
+ Object.assign(cliOpt, options);
163
+ _addLongOption(cmd, cliOpt.longName, cliOpt);
164
+ _addShortOption(cmd, cliOpt.shortName, cliOpt);
165
+
166
+ for (const alias of cliOpt.aliases || []) {
167
+ const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
168
+ _addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
169
+ }
170
+
171
+ return cmd;
172
+ }
162
173
 
163
- if (cmd.options[opt.longName]) {
164
- throw new Error(`Duplicate assignment for long option ${opt.longName}`);
165
- } else if (optionProcessor.options[opt.longName]) {
174
+ /**
175
+ * Internal: Add longName to the list of options.
176
+ * Throws if the option is already registered in the given command context.
177
+ * or in the given command.
178
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
179
+ *
180
+ * @private
181
+ * @see _addOption()
182
+ */
183
+ function _addLongOption(cmd, longName, opt) {
184
+ if (cmd.options[longName]) {
185
+ throw new Error(`Duplicate assignment for long option ${longName}`);
186
+ } else if (optionProcessor.options[longName]) {
166
187
  // This path is only taken if optString is for commands
167
188
  optionProcessor.optionClashes.push({
168
- option: opt.longName,
169
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
189
+ option: longName,
190
+ description: `Command '${cmd.longName}' has option clash with general options for: ${longName}`
170
191
  });
171
192
  }
172
- cmd.options[opt.longName] = opt;
173
- if (opt.shortName) {
174
- if (cmd.options[opt.shortName]) {
175
- throw new Error(`Duplicate assignment for short option ${opt.shortName}`);
176
- } else if (optionProcessor.options[opt.shortName]) {
177
- // This path is only taken if optString is for commands
178
- optionProcessor.optionClashes.push({
179
- option: opt.shortName,
180
- description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
181
- });
182
- }
183
- cmd.options[opt.shortName] = opt;
193
+ cmd.options[longName] = opt;
194
+ }
195
+ /**
196
+ * Internal: Add shortName to the list of options.
197
+ * Throws if the option is already registered in the given command context.
198
+ * or in the given command.
199
+ * `longName` may differ from `opt.longName`, e.g. for aliases.
200
+ *
201
+ * @private
202
+ * @see _addOption()
203
+ */
204
+ function _addShortOption(cmd, shortName, opt) {
205
+ if (!shortName)
206
+ return;
207
+ if (cmd.options[shortName]) {
208
+ throw new Error(`Duplicate assignment for short option ${shortName}`);
209
+ } else if (optionProcessor.options[shortName]) {
210
+ // This path is only taken if optString is for commands
211
+ optionProcessor.optionClashes.push({
212
+ option: shortName,
213
+ description: `Command '${cmd.longName}' has option clash with general options for: ${shortName}`
214
+ });
184
215
  }
185
- return cmd;
216
+ cmd.options[shortName] = opt;
186
217
  }
187
218
 
188
219
  // Internal: Parse one command string like "F, toFoo". Return an object like this
@@ -275,7 +306,8 @@ function createOptionProcessor() {
275
306
  shortName,
276
307
  camelName: camelifyLongOption(longName),
277
308
  param,
278
- validValues
309
+ validValues,
310
+ isAlias: false, // default
279
311
  }
280
312
  }
281
313
 
@@ -585,7 +585,7 @@ function assertConsistency( model, stage ) {
585
585
  // (it can contain the artifact itself with no/failed autoexposure):
586
586
  _descendants: { kind: [ 'entity' ], test: isDictionary( isArray( TODO ) ) },
587
587
 
588
- $errorReported: { parser: true, test: isBoolean }, // to avoid duplicate messages
588
+ $errorReported: { parser: true, kind: true, test: isString }, // to avoid duplicate messages
589
589
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
590
590
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
591
591
  $inferred: { parser: true, kind: true, test: isString },
@@ -1,7 +1,7 @@
1
1
  // The builtin artifacts of CDS
2
2
 
3
3
  // TODO: split this file
4
- // - in base/: common definitions
4
+ // - in base/: common definitions, datetime formats
5
5
  // - in compiler/: XSN-specific
6
6
  // - in ?: CSN-specific
7
7
 
@@ -193,6 +193,44 @@ const magicVariables = {
193
193
 
194
194
  // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
195
195
 
196
+ /**
197
+ * Patterns for literal token tests and creation. The value is a map from the
198
+ * `prefix` argument of function `quotedliteral` to the following properties:
199
+ * - `test_msg`: error message which is issued if `test_fn` or `test_re` fail.
200
+ * - `test_fn`: function called with argument `value`, fails falsy return value
201
+ * - `test_re`: regular expression, fails if it does not match argument `value`
202
+ * - `unexpected_msg`: error message which is issued if `unexpected_char` matches
203
+ * - `unexpected_char`: regular expression matching an illegal character in `value`,
204
+ * the error location is only correct for a literal <prefix>'<value>'
205
+ * - `literal`: the value which is used instead of `prefix` in the AST
206
+ * TODO: we might do a range check (consider leap seconds, i.e. max value 60),
207
+ * but always allow Feb 29 (no leap year computation)
208
+ */
209
+ const quotedLiteralPatterns = {
210
+ x: {
211
+ test_variant: 'uneven-hex',
212
+ test_fn: (str => Number.isInteger(str.length / 2)),
213
+ unexpected_variant: 'invalid-hex',
214
+ unexpected_char: /[^0-9a-f]/i,
215
+ json_type: 'string',
216
+ },
217
+ time: {
218
+ test_variant: 'time',
219
+ test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
220
+ json_type: 'string',
221
+ },
222
+ date: {
223
+ test_variant: 'date',
224
+ test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
225
+ json_type: 'string',
226
+ },
227
+ timestamp: {
228
+ test_variant: 'timestamp',
229
+ test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
230
+ json_type: 'string',
231
+ },
232
+ };
233
+
196
234
  /** All types belong to one category. */
197
235
  const typeCategories = {
198
236
  string: [],
@@ -403,6 +441,7 @@ module.exports = {
403
441
  typeParameters,
404
442
  functionsWithoutParens,
405
443
  specialFunctions,
444
+ quotedLiteralPatterns,
406
445
  initBuiltins,
407
446
  isInReservedNamespace,
408
447
  isBuiltinType,
@@ -390,14 +390,14 @@ function define( model ) {
390
390
  setLink( vocab, '_block', block );
391
391
  const { name } = vocab;
392
392
  if (!name.absolute)
393
- name.absolute = prefix + name.path.map( id => id.id ).join('.');
393
+ name.absolute = prefix + pathName( name.path );
394
394
  dictAdd( model.vocabularies, name.absolute, vocab );
395
395
  }
396
396
 
397
397
  // Phase 2 ("init") --------------------------------------------------------
398
398
 
399
399
  function checkRedefinition( art ) {
400
- if (!art.$duplicates)
400
+ if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend')
401
401
  return;
402
402
  if (art._main) {
403
403
  error( 'duplicate-definition', [ art.name.location, art ], {
@@ -670,6 +670,8 @@ function define( model ) {
670
670
  * @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
671
671
  */
672
672
  function approveExistsInChildren(exprOrPathElement) {
673
+ if (!exprOrPathElement) // may be null in case of parse error
674
+ return;
673
675
  if (exprOrPathElement.$expected === 'exists')
674
676
  exprOrPathElement.$expected = 'approved-exists';
675
677
  // Drill down
@@ -389,7 +389,10 @@ function extend( model ) {
389
389
  for (const ext of exts) {
390
390
  delete ext.name.path[0]._artifact; // get message for root
391
391
  // TODO: make resolvePath('extend'/'annotate') ignore namespaces
392
- if (resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
392
+ // Don't try to apply annotations in the `localized.` namespace.
393
+ // That's done in `localized.js`.
394
+ if (!name.startsWith('localized.') &&
395
+ resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
393
396
  // should issue error for cds extensions (annotate ok)
394
397
  if (art.kind === 'namespace') {
395
398
  info( 'anno-namespace', [ ext.name.location, ext ], {},
@@ -246,7 +246,9 @@ function populate( model ) {
246
246
  let struct = user;
247
247
  while (struct.kind === 'element')
248
248
  struct = struct._parent;
249
- if (struct.kind === 'select') {
249
+ if (struct.kind === 'select' || struct.kind === 'annotation') {
250
+ // `type of` in annotation definitions can't work, because csn type refs
251
+ // always refer to definitions.
250
252
  message( 'type-unexpected-typeof', [ ref.location, user ],
251
253
  { keyword: 'type of', '#': struct.kind } );
252
254
  // we actually refer to an element in _combined; TODO: return null if
@@ -481,10 +481,7 @@ function resolve( model ) {
481
481
  if (ext.$extension) // extension for known artifact -> already applied
482
482
  return;
483
483
  annotateMembers( ext );
484
- for (const prop in ext) {
485
- if (prop.charAt(0) === '@')
486
- chooseAssignment( prop, ext );
487
- }
484
+ chooseAnnotationsInArtifact( ext );
488
485
  }
489
486
 
490
487
  /**
@@ -881,9 +881,18 @@ function fns( model ) {
881
881
  info( 'anno-builtin', [ construct.name.location, construct ], {},
882
882
  'Builtin types should not be annotated. Use custom type instead' );
883
883
  }
884
+ else if (construct.$syntax === 'returns' && art._block && art.kind !== 'action' &&
885
+ art.kind !== 'function' ) {
886
+ // `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
887
+ // for non-actions. We can't only check for !art.returns, because `action A();` is valid.
888
+ // `art._block` ensures that `art` is a defined def.
889
+ warning('anno-unexpected-returns', [ construct.name.location, construct ],
890
+ { keyword: 'returns', kind: art.kind }, 'Unexpected $(KEYWORD) for $(KIND)');
891
+ }
884
892
  }
885
893
  if (construct.doc)
886
894
  art.doc = construct.doc; // e.g. through `extensions` array in CSN
895
+
887
896
  // set _block (for layering) and $priority, shallow-copy from extension
888
897
  // TODO: think of removing $priority, then
889
898
  // no _block: define, _block: annotate/extend/edmx
@@ -188,8 +188,8 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
188
188
  *
189
189
  * @param {XSN.Path} path
190
190
  */
191
- function pathName(path) {
192
- return (path.broken) ? '' : path.map( id => id.id ).join('.');
191
+ function pathName( path ) {
192
+ return (path && !path.broken) ? path.map( id => id.id ).join('.') : '';
193
193
  }
194
194
 
195
195
  /**
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const edmUtils = require('../edmUtils.js');
4
3
  const { makeMessageFunction } = require('../../base/messages.js');
4
+ const { forEachDefinition } = require('../../model/csnUtils.js');
5
5
 
6
6
 
7
7
  /**************************************************************************************************
@@ -37,15 +37,14 @@ function preprocessAnnotations(csn, serviceName, options) {
37
37
  let targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
38
38
  let target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
39
39
 
40
- let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key);
40
+ let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
41
41
  if (keyNames.length === 0) {
42
42
  keyNames.push('MISSING');
43
43
  warning(null, null, `in annotation preprocessing: target ${targetName} has no key`);
44
44
  }
45
45
  else if (keyNames.length > 1)
46
46
  warning(null, null, `in annotation preprocessing: target ${targetName} has multiple key elements`);
47
-
48
- // TODO: what happens if key of target is itself a managed association?
47
+
49
48
  return keyNames[0];
50
49
  }
51
50
 
@@ -58,15 +57,15 @@ function preprocessAnnotations(csn, serviceName, options) {
58
57
  function resolveShortcuts() {
59
58
  let art = null;
60
59
 
61
- edmUtils.forAll(csn.definitions, (artifact, artifactName) => {
60
+ forEachDefinition(csn, (artifact, artifactName) => {
62
61
  if(artifactName == serviceName || artifactName.startsWith(serviceName + '.')) {
63
62
  art = artifactName;
64
63
  handleAnnotations(artifactName, artifact);
65
- edmUtils.forAll(artifact.elements, (element, elementName) => {
64
+ artifact.elements && Object.entries(artifact.elements).forEach(([elementName, element]) => {
66
65
  handleAnnotations(elementName, element);
67
66
  });
68
- edmUtils.forAll(artifact.actions, (action) => {
69
- edmUtils.forAll(action.params, (param, paramName) => {
67
+ artifact.actions && Object.values(artifact.actions).forEach(action => {
68
+ action.params && Object.entries(action.params).forEach(([paramName, param]) => {
70
69
  handleAnnotations(paramName, param);
71
70
  });
72
71
  });
@@ -155,7 +154,7 @@ function preprocessAnnotations(csn, serviceName, options) {
155
154
  return false;
156
155
  }
157
156
  let assoc = csn.definitions[art].elements[assocName];
158
- if (!assoc || !(assoc.type === 'cds.Association' || assoc.type === 'cds.Composition')) {
157
+ if (!assoc || !assoc.target) {
159
158
  warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);
160
159
  return false;
161
160
  }
@@ -196,7 +195,7 @@ function preprocessAnnotations(csn, serviceName, options) {
196
195
  // name of the element carrying the value help annotation
197
196
  // if this is a managed assoc, use fk field instead (if there is a single one)
198
197
  let localDataProp = carrierName.split('/').pop();
199
- if (edmUtils.isManagedAssociation(carrier)) {
198
+ if (carrier.target && carrier.on === undefined) {
200
199
  localDataProp = localDataProp + fkSeparator + getKeyOfTargetOfManagedAssoc(carrier);
201
200
  }
202
201
 
@@ -212,7 +211,7 @@ function preprocessAnnotations(csn, serviceName, options) {
212
211
  // valueListProp: the (single) key field of the value list entity
213
212
  // if no key or multiple keys -> warning
214
213
  let valueListProp = null;
215
- let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key );
214
+ let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
216
215
  if (keys.length === 0) {
217
216
  warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);
218
217
  return false;
@@ -330,11 +330,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
330
330
  Entity starts with 'localserviceNameized.' or ends with '_localized'
331
331
  */
332
332
  edmUtils.foreach(schemaCsn.definitions,
333
- a => edmUtils.isEntity(a) && !a.abstract && a.name.startsWith(schemaNamePrefix),
333
+ a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix),
334
334
  createEntityTypeAndSet
335
335
  );
336
336
  // create unbound actions/functions
337
- edmUtils.foreach(schemaCsn.definitions, a => edmUtils.isActionOrFunction(a) && a.name.startsWith(schemaNamePrefix),
337
+ edmUtils.foreach(schemaCsn.definitions,
338
+ a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix),
338
339
  (options.isV4()) ? createActionV4 : createActionV2);
339
340
 
340
341
  // create the complex types
@@ -346,7 +347,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
346
347
  {
347
348
  edmUtils.foreach(schemaCsn.definitions,
348
349
  artifact => edmUtils.isDerivedType(artifact) &&
349
- !edmUtils.isAssociationOrComposition(artifact) &&
350
+ !artifact.target &&
350
351
  artifact.name.startsWith(schemaNamePrefix),
351
352
  createTypeDefinition);
352
353
  }
@@ -414,7 +415,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
414
415
  if(p._edmAttributes.Name === EntityTypeName)
415
416
  warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
416
417
 
417
- if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
418
+ if(options.isV2() && p._isCollection && !p._csn.target)
418
419
  warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
420
 
420
421
  if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
@@ -461,7 +462,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
461
462
  }
462
463
 
463
464
  // put actions behind entity types in Schema/EntityContainer
464
- edmUtils.forAll(entityCsn.actions, (a, n) => {
465
+ entityCsn.actions && Object.entries(entityCsn.actions).forEach(([ n, a ]) => {
465
466
  (options.isV4()) ? createActionV4(a, n, entityCsn)
466
467
  : createActionV2(a, n, entityCsn)
467
468
  });
@@ -522,7 +523,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
522
523
  }
523
524
 
524
525
  // Parameter Nodes
525
- edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
526
+ actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
526
527
  const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
527
528
  const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
528
529
  if(!edmUtils.isODataSimpleIdentifier(parameterName))
@@ -573,7 +574,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
573
574
  if(rt) // add EntitySet attribute only if return type is an entity
574
575
  {
575
576
  const defintion = schemaCsn.definitions[rt];
576
- if(defintion && edmUtils.isEntity(defintion))
577
+ if(defintion && defintion.kind === 'entity')
577
578
  {
578
579
  functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
579
580
  }
@@ -597,7 +598,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
597
598
  // Binding Parameter: Primary Keys at first position in sequence, this is decisive!
598
599
  // V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
599
600
  edmUtils.foreach(entityCsn.elements,
600
- elementCsn => elementCsn.key && !edmUtils.isAssociationOrComposition(elementCsn),
601
+ elementCsn => elementCsn.key && !elementCsn.target,
601
602
  (elementCsn, elementName) => {
602
603
  functionImport.append(new Edm.Parameter(v, { Name: elementName }, elementCsn, 'In' ));
603
604
  }
@@ -605,7 +606,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
605
606
  }
606
607
 
607
608
  // is this still required?
608
- edmUtils.forAll(actionCsn, (v, p) => {
609
+ Object.entries(actionCsn).forEach(([p, v]) => {
609
610
  if (p.match(/^@sap\./))
610
611
  functionImport.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v });
611
612
  });
@@ -613,7 +614,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
613
614
  // V2 XML: Parameters that are not explicitly marked as Nullable or NotNullable in the CSN must become Nullable=true
614
615
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
615
616
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
616
- edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
617
+ actionCsn.params && Object.entries(actionCsn.params).forEach(([parameterName, parameterCsn]) => {
617
618
  const pLoc = [...loc, 'params', parameterName];
618
619
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
619
620
  edmTypeCompatibilityCheck(param, pLoc);
@@ -683,12 +684,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
683
684
  let hasStream = false;
684
685
  const streamProps = [];
685
686
 
686
- edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
687
+ elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) =>
687
688
  {
688
689
  if(elementCsn._edmParentCsn == undefined)
689
690
  setProp(elementCsn, '_edmParentCsn', edmParentCsn);
690
691
 
691
- if(edmUtils.isAssociationOrComposition(elementCsn)) {
692
+ if(elementCsn.target) {
692
693
  // Foreign keys are part of the generic elementCsn.elements property creation
693
694
 
694
695
  // This is the V4 edmx:NavigationProperty
@@ -769,10 +770,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
769
770
  message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
770
771
 
771
772
  if(options.isV2()) {
772
- if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
773
+ if(p._isCollection && !p._csn.target)
773
774
  warning('odata-spec-violation-array', pLoc, { version: '2.0' });
774
775
 
775
- if(edmUtils.isAssociationOrComposition(p._csn))
776
+ if(p._csn.target)
776
777
  warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
777
778
  }
778
779
  });
package/lib/edm/edm.js CHANGED
@@ -239,7 +239,7 @@ function getEdm(options, messageFunctions) {
239
239
  if(csn)
240
240
  {
241
241
  const attr = (useSetAttributes ? csn._SetAttributes : csn);
242
- edmUtils.forAll(attr, (v, p) => {
242
+ attr && Object.entries(attr).forEach(([p, v]) => {
243
243
  if (p.match(/^@sap./))
244
244
  this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : v } );
245
245
  });
@@ -332,7 +332,7 @@ function getEdm(options, messageFunctions) {
332
332
  if(what==='metadata' || what==='all')
333
333
  {
334
334
  xml += super.innerXML(indent);
335
- edmUtils.forAll(this._actions, actionArray => {
335
+ this._actions && Object.values(this._actions).forEach(actionArray => {
336
336
  actionArray.forEach(action => {
337
337
  xml += action.toXML(indent, what) + '\n'; });
338
338
  });
@@ -350,7 +350,7 @@ function getEdm(options, messageFunctions) {
350
350
  // no $Namespace
351
351
  toJSONattributes(json)
352
352
  {
353
- edmUtils.forAll(this._edmAttributes, (v,p) => {
353
+ this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
354
354
  if (p !== 'Name' && p !== 'Namespace')
355
355
  json[p[0] === '@' ? p : '$' + p] = v;
356
356
  });
@@ -371,7 +371,7 @@ function getEdm(options, messageFunctions) {
371
371
  if(Object.keys(json_Annotations).length)
372
372
  json['$Annotations'] = json_Annotations;
373
373
  }
374
- edmUtils.forAll(this._actions, (actionArray, actionName) => {
374
+ this._actions && Object.entries(this._actions).forEach(([actionName, actionArray]) => {
375
375
  json[actionName] = [];
376
376
  actionArray.forEach(action => {
377
377
  json[actionName].push(action.toJSON());
@@ -737,7 +737,7 @@ function getEdm(options, messageFunctions) {
737
737
  if(this._type !== 'Edm.String' && this._type) // Edm.String is default)
738
738
  json['$'+this._typeName] = this._type;
739
739
 
740
- edmUtils.forAll(this._edmAttributes, (v,p) => {
740
+ this._edmAttributes && Object.entries(this._edmAttributes).forEach(([p, v]) => {
741
741
  if (p !== 'Name' && p !== this._typeName
742
742
  // remove this line if Nullable=true becomes default
743
743
  && !(p === 'Nullable' && v == false))
@@ -840,8 +840,8 @@ function getEdm(options, messageFunctions) {
840
840
  super(v, attributes, csn);
841
841
 
842
842
  // array of enum not yet allowed
843
- let enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
844
- edmUtils.forAll(enumValues, (e, en) => {
843
+ const enumValues = /*(csn.items && csn.items.enum) ||*/ csn.enum;
844
+ enumValues && Object.entries(enumValues).forEach(([en, e]) => {
845
845
  this.append(new Member(v, { Name: en, Value: e.val } ));
846
846
  });
847
847
  }
@@ -1177,9 +1177,10 @@ function getEdm(options, messageFunctions) {
1177
1177
  _constraints = this._csn._constraints._partnerCsn._constraints;
1178
1178
  [i,j] = [1,0];
1179
1179
  }
1180
- edmUtils.forAll(_constraints.constraints,
1181
- c => this.append(new ReferentialConstraint(this._v,
1182
- { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) ) );
1180
+ _constraints.constraints && Object.values(_constraints.constraints).forEach(c =>
1181
+ this.append(new ReferentialConstraint(this._v,
1182
+ { Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) )
1183
+ );
1183
1184
  }
1184
1185
  }
1185
1186
 
@@ -1502,7 +1503,7 @@ function getEdm(options, messageFunctions) {
1502
1503
  }
1503
1504
  toJSONattributes(json) {
1504
1505
  super.toJSONattributes(json);
1505
- edmUtils.forAll(this._jsonOnlyAttributes, (v,p) => {
1506
+ this._jsonOnlyAttributes && Object.entries(this._jsonOnlyAttributes).forEach(([p, v]) => {
1506
1507
  json[p[0] === '@' ? p : '$' + p] = v;
1507
1508
  });
1508
1509
  return json;
@@ -1606,7 +1607,7 @@ function getEdm(options, messageFunctions) {
1606
1607
  node._d = new Dependent(v, { Role: from } );
1607
1608
  node._p = new Principal(v, { Role: to } );
1608
1609
 
1609
- edmUtils.forAll(c, cv => {
1610
+ c && Object.values(c).forEach(cv => {
1610
1611
  node._d.append(new PropertyRef(v, cv[0].join(options.pathDelimiter)));
1611
1612
  node._p.append(new PropertyRef(v, cv[1].join(options.pathDelimiter)));
1612
1613
  });