@sap/cds-compiler 2.13.8 → 3.0.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 (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -7,12 +7,13 @@ const NAVPROP_TRENNER = '_';
7
7
  const VALUELIST_NAVPROP_PREFIX = '';
8
8
 
9
9
  const edmUtils = require('./edmUtils.js')
10
- const { initializeModel } = require('./edmPreprocessor.js');
10
+ const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
11
11
  const translate = require('./annotations/genericTranslation.js');
12
12
  const { setProp } = require('../base/model');
13
- const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
13
+ const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
14
14
  const { checkCSNVersion } = require('../json/csnVersion');
15
15
  const { makeMessageFunction } = require('../base/messages');
16
+ const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
16
17
 
17
18
  /*
18
19
  OData V2 spec 06/01/2017 PDF version is available from here:
@@ -25,11 +26,11 @@ function csn2edm(_csn, serviceName, _options) {
25
26
 
26
27
  function csn2edmAll(_csn, _options, serviceNames=undefined) {
27
28
  // get us a fresh model copy that we can work with
28
- const csn = cloneCsn(_csn, _options);
29
+ const csn = cloneCsnNonDict(_csn, _options);
29
30
 
30
31
  // use original options for messages; cloned CSN for semantic location
31
32
  const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
32
- const { info, warning, error, message, throwWithError } = messageFunctions;
33
+ const { info, warning, error, message, throwWithAnyError } = messageFunctions;
33
34
  checkCSNVersion(csn, _options);
34
35
 
35
36
  let rc = Object.create(null);
@@ -42,13 +43,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
42
43
  setProp(csn.meta, 'options', _csn.meta.options);
43
44
  }
44
45
 
45
- let [ allServices,
46
+ const [ allServices,
46
47
  allSchemas,
48
+ reqDefs,
47
49
  whatsMyServiceRootName,
48
- autoexposeSchemaName,
49
- options ] = initializeModel(csn, _options, messageFunctions);
50
+ fallBackSchemaName,
51
+ options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
50
52
 
51
- const Edm = require('./edm.js')(options, error);
53
+ const Edm = getEdm(options, messageFunctions);
52
54
 
53
55
  const v = options.v;
54
56
  if(Object.keys(allServices).length === 0) {
@@ -74,7 +76,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
74
76
  services[serviceCsn.name] = createEdm(serviceCsn);
75
77
  return services; }, rc);
76
78
  }
77
- throwWithError();
79
+ throwWithAnyError();
78
80
  return rc;
79
81
 
80
82
  //--------------------------------------------------------------------------------
@@ -189,9 +191,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
189
191
  fqSchemaXRef.sort((a,b) => b.length-a.length);
190
192
 
191
193
  // Bring the schemas in alphabetical order, service first, root last
192
- const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
193
- if(subSchemaDictionary[autoexposeSchemaName])
194
- sortedSchemaNames.push(autoexposeSchemaName);
194
+ const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
195
+ if(subSchemaDictionary[fallBackSchemaName])
196
+ sortedSchemaNames.push(fallBackSchemaName);
195
197
 
196
198
  // Finally create the schemas and register them in the service.
197
199
  LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
@@ -221,7 +223,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
221
223
  service._children.forEach(c => {
222
224
  c._ec && Object.entries(c._ec._registry).forEach((([setName, arr]) => {
223
225
  if(arr.length > 1) {
224
- error(null, null, { name: c.Namespace, id: setName },
226
+ error(null, null, { name: c._edmAttributes.Namespace, id: setName },
225
227
  `Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `);
226
228
  }
227
229
  }));
@@ -232,7 +234,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
232
234
 
233
235
  // Sort definitions into their schema container
234
236
  function populateSchemas(schemas) {
235
- Object.entries(csn.definitions).forEach(([fqName, art]) => {
237
+ Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
236
238
  // Identify service members by their definition name only, this allows
237
239
  // to let the internal object.name have the sub-schema name.
238
240
  // With nested services we must do a longest path match and check whether
@@ -301,10 +303,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
301
303
 
302
304
  // Same check for alias (if supported by us)
303
305
  const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
304
- if(reservedNames.includes(schema.name)) {
305
- warning('odata-spec-violation-namespace',
306
- [ 'definitions', schema.name ], { names: reservedNames });
306
+ const loc = ['definitions', schema.name];
307
+ if(reservedNames.includes(schema.name))
308
+ warning('odata-spec-violation-namespace', loc, { names: reservedNames });
309
+ if (schema.name.length > 511)
310
+ warning('odata-spec-violation-namespace', loc, { '#': 'length' });
311
+ else {
312
+ schema.name.split('.').forEach(id => {
313
+ if (!edmUtils.isODataSimpleIdentifier(id))
314
+ message('odata-spec-violation-id', loc, { id });
315
+ });
307
316
  }
317
+
308
318
  /** @type {any} */
309
319
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
310
320
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
@@ -343,10 +353,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
343
353
 
344
354
  // fetch all exising children names in a map
345
355
  const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
346
- if(acc[cur.Name] === undefined) {
347
- acc[cur.Name] = [ cur ];
356
+ const name = cur._edmAttributes.Name;
357
+ if(acc[name] === undefined) {
358
+ acc[name] = [ cur ];
348
359
  } else {
349
- acc[cur.Name].push(cur);
360
+ acc[name].push(cur);
350
361
  }
351
362
  return acc;
352
363
  }, Object.create(null) );
@@ -368,12 +379,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
368
379
  }
369
380
  if(Schema._children.length === 0) {
370
381
  // FIXME: Location for sub schemas?
371
- warning(null, ['definitions', Schema.Namespace], { name: Schema.Namespace }, 'Schema $(NAME) is empty');
382
+ warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
372
383
  }
373
384
 
374
385
  Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
375
386
  if(refs.length > 1) {
376
- error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
387
+ error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
377
388
  'Duplicate name in Schema $(NAME)');
378
389
  }
379
390
  });
@@ -384,26 +395,34 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
384
395
  {
385
396
  const EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
386
397
  const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
387
-
398
+ const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
388
399
  const [ properties, hasStream ] = createProperties(entityCsn);
389
400
 
390
401
  const loc = ['definitions', entityCsn.name];
391
402
  const type = `${schema.name}.${EntityTypeName}`;
392
- if(properties.length === 0) {
403
+ if(properties.length === 0)
393
404
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
394
- } else if(entityCsn.$edmKeyPaths.length === 0) {
405
+ else if(entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
395
406
  message('odata-spec-violation-no-key', loc);
396
- }
407
+
408
+ if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
409
+ message('odata-spec-violation-id', loc, { id: EntityTypeName });
410
+
397
411
  properties.forEach(p => {
398
- const pLoc = [...loc, 'elements', p.Name];
399
- if(!p[p._typeName]) {
400
- message('odata-spec-violation-type', pLoc);
401
- }
402
- if(p.Name === EntityTypeName) {
412
+ const pLoc = [...loc, 'elements', p._edmAttributes.Name];
413
+ edmTypeCompatibilityCheck(p, pLoc);
414
+ if(p._edmAttributes.Name === EntityTypeName)
403
415
  warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
404
- }
405
- if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
406
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
416
+
417
+ if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
418
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
419
+
420
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
421
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
422
+ else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
423
+ // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
424
+ message('odata-spec-violation-id', pLoc,
425
+ { prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
407
426
  }
408
427
  });
409
428
 
@@ -411,8 +430,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
411
430
  const attributes = { Name : EntityTypeName };
412
431
 
413
432
  // CDXCORE-CDXCORE-173
414
- if(options.isV2() && hasStream)
415
- attributes['m:HasStream'] = hasStream;
433
+ if(options.isV2() && hasStream) {
434
+ attributes['m:HasStream'] = true;
435
+ assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
436
+ }
416
437
 
417
438
  Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
418
439
 
@@ -424,7 +445,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
424
445
  if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
425
446
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
426
447
  if(entityCsn['@odata.singleton.nullable'])
427
- containerEntry.Nullable= true;
448
+ containerEntry._edmAttributes.Nullable= true;
428
449
  }
429
450
  else {
430
451
  containerEntry = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);
@@ -447,14 +468,19 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
447
468
  }
448
469
 
449
470
  // add bound/unbound actions/functions for V4
450
- function createActionV4(actionCsn, name, entityCsn=undefined)
471
+ function createActionV4(actionCsn, _name, entityCsn=undefined)
451
472
  {
452
473
  const iAmAnAction = actionCsn.kind === 'action';
453
-
454
474
  const actionName = edmUtils.getBaseName(actionCsn.name);
455
-
456
475
  const attributes = { Name: actionName, IsBound : false };
457
476
 
477
+ const loc = entityCsn
478
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
479
+ : [ 'definitions', actionCsn.name ];
480
+
481
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
482
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
483
+
458
484
  if(!iAmAnAction)
459
485
  attributes.IsComposable = false;
460
486
 
@@ -468,7 +494,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
468
494
 
469
495
  if(entityCsn != undefined)
470
496
  {
471
- actionNode.IsBound = true;
497
+ actionNode.setEdmAttribute('IsBound', true);
472
498
  const bpType = fullQualified(entityCsn.name);
473
499
  // Binding Parameter: 'in' at first position in sequence, this is decisive!
474
500
  if(actionCsn['@cds.odata.bindingparameter.collection'])
@@ -489,7 +515,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
489
515
  const definition = schemaCsn.definitions[rt];
490
516
  if(definition && definition.kind === 'entity' && !definition.abstract)
491
517
  {
492
- actionImport.EntitySet = edmUtils.getBaseName(rt);
518
+ actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
493
519
  }
494
520
  }
495
521
  EntityContainer.register(actionImport);
@@ -497,15 +523,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
497
523
 
498
524
  // Parameter Nodes
499
525
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
500
- actionNode.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn ));
526
+ const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
527
+ const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
528
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
529
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
530
+
531
+ edmTypeCompatibilityCheck(p, pLoc);
532
+ actionNode.append(p);
501
533
  });
502
534
 
503
535
  // return type if any
504
536
  if(actionCsn.returns) {
505
537
  actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
538
+ edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);
506
539
  // if binding type matches return type add attribute EntitySetPath
507
540
  if(entityCsn != undefined && fullQualified(entityCsn.name) === actionNode._returnType._type) {
508
- actionNode.EntitySetPath = bpName;
541
+ actionNode.setEdmAttribute('EntitySetPath', bpName);
509
542
  }
510
543
  }
511
544
  Schema.addAction(actionNode);
@@ -515,7 +548,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
515
548
  function createActionV2(actionCsn, name, entityCsn=undefined)
516
549
  {
517
550
  /** @type {object} */
518
- const functionImport = new Edm.FunctionImport(v, { Name: name.replace(schemaNamePrefix, '') } );
551
+ const attributes = { Name: name.replace(schemaNamePrefix, '') };
552
+ const functionImport = new Edm.FunctionImport(v, attributes );
519
553
 
520
554
  // inserted now to maintain attribute order with old odata generator...
521
555
  /*
@@ -528,19 +562,25 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
528
562
  in the spec and advised mention it as in V4
529
563
  */
530
564
 
531
- const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
565
+ const loc = entityCsn
566
+ ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
567
+ : [ 'definitions', actionCsn.name ];
568
+
569
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
570
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
571
+
532
572
  const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
533
573
  if(rt) // add EntitySet attribute only if return type is an entity
534
574
  {
535
575
  const defintion = schemaCsn.definitions[rt];
536
576
  if(defintion && edmUtils.isEntity(defintion))
537
577
  {
538
- functionImport.EntitySet = rt.replace(schemaNamePrefix, '');
578
+ functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
539
579
  }
540
580
  }
541
581
 
542
582
  if(actionCsn.returns)
543
- functionImport.ReturnType = getReturnType(actionCsn);
583
+ functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));
544
584
 
545
585
  if(actionCsn.kind === 'function')
546
586
  functionImport.setXml( {'m:HttpMethod': 'GET' });
@@ -551,7 +591,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
551
591
  {
552
592
  // Make bound function names always unique as per Ralf's recommendation
553
593
  functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
554
- functionImport.Name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport.Name;
594
+ const name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport._edmAttributes.Name;
595
+ functionImport.setEdmAttribute('Name', name);
555
596
 
556
597
  // Binding Parameter: Primary Keys at first position in sequence, this is decisive!
557
598
  // V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
@@ -573,14 +614,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
573
614
  // V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
574
615
  // the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
575
616
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
576
- const paramLoc = [...actLoc, 'params', parameterName];
617
+ const pLoc = [...loc, 'params', parameterName];
577
618
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
578
- if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
579
- warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
580
- }
581
- if(param._isCollection) {
582
- warning('odata-spec-violation-array', paramLoc, { api: 'OData V2' });
583
- }
619
+ edmTypeCompatibilityCheck(param, pLoc);
620
+ if(!edmUtils.isODataSimpleIdentifier(parameterName))
621
+ message('odata-spec-violation-id', pLoc, { id: parameterName });
622
+
623
+ // only scalar or structured type in V2 (not entity)
624
+ if(param._type &&
625
+ !param._type.startsWith('Edm.') &&
626
+ csn.definitions[param._type] &&
627
+ !edmUtils.isStructuredType(csn.definitions[param._type]))
628
+ warning('odata-spec-violation-param', pLoc, { version: '2.0' });
629
+
630
+ if(param._isCollection)
631
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
632
+
584
633
  functionImport.append(param);
585
634
  });
586
635
 
@@ -590,19 +639,33 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
590
639
  function getReturnType(action)
591
640
  {
592
641
  // it is safe to assume that either type or items.type are set
642
+ const returnsLoc = [ ...loc, 'returns'];
593
643
  const returns = action.returns.items || action.returns;
594
644
  let type = returns.type;
595
- if (type){
645
+ if (type) {
596
646
  if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
597
- const returnsLoc = [ ...actLoc, 'returns'];
598
- warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
647
+ warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
599
648
  }
600
- type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
649
+ else if(isBuiltinType(type)) {
650
+ type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
651
+ if(type) {
652
+ const td = EdmPrimitiveTypeMap[type];
653
+ if(td && !td.v2) {
654
+ message('odata-spec-violation-type', returnsLoc,
655
+ { type, version: '2.0', '#': 'incompatible' });
656
+ }
657
+ }
658
+ else {
659
+ message('odata-spec-violation-type-unknown', returnsLoc, { type });
660
+ }
661
+ }
662
+ if(action.returns._isCollection)
663
+ type = `Collection(${type})`
664
+ }
665
+ else {
666
+ // type is missing
667
+ message('odata-spec-violation-type', returnsLoc);
601
668
  }
602
-
603
- if(action.returns._isCollection)
604
- type = `Collection(${type})`
605
-
606
669
  return type;
607
670
  }
608
671
  }
@@ -610,22 +673,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
610
673
  /**
611
674
  * @param {object} elementsCsn
612
675
  * @param {object} edmParentCsn
613
- * @returns {[object[], boolean]} Returns a [ [ Edm Properties ], boolean hasStream ]:
676
+ * @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
614
677
  * array of Edm Properties
615
- * boolean hasStream : true if at least one element has @Core.MediaType assignment
678
+ * hasStream : value of @Core.MediaType assignment
616
679
  */
617
680
  function createProperties(elementsCsn, edmParentCsn=elementsCsn)
618
681
  {
619
682
  const props = [];
620
683
  let hasStream = false;
684
+ const streamProps = [];
685
+
621
686
  edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
622
687
  {
623
688
  if(elementCsn._edmParentCsn == undefined)
624
689
  setProp(elementCsn, '_edmParentCsn', edmParentCsn);
625
690
 
626
- if(!elementCsn._ignore) {
627
- if(edmUtils.isAssociationOrComposition(elementCsn))
628
- {
691
+ if(edmUtils.isAssociationOrComposition(elementCsn)) {
629
692
  // Foreign keys are part of the generic elementCsn.elements property creation
630
693
 
631
694
  // This is the V4 edmx:NavigationProperty
@@ -633,40 +696,49 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
633
696
 
634
697
  // suppress navprop creation only if @odata.navigable:false is not annotated.
635
698
  // (undefined !== false) still evaluates to true
636
- if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
699
+ if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
637
700
  {
638
- const navProp = new Edm.NavigationProperty(v, {
639
- Name: elementName,
640
- Type: elementCsn._target.name
641
- }, elementCsn);
642
- props.push(navProp);
701
+ const navProp = new Edm.NavigationProperty(v, {
702
+ Name: elementName,
703
+ Type: elementCsn._target.name
704
+ }, elementCsn);
705
+ props.push(navProp);
643
706
  // save the navProp in the global array for late constraint building
644
- navigationProperties.push(navProp);
645
- }
707
+ navigationProperties.push(navProp);
646
708
  }
709
+ }
647
710
  // render ordinary property if element is NOT ...
648
711
  // 1) ... annotated @cds.api.ignore
649
712
  // 2) ... annotated @odata.foreignKey4 and odataFormat: structured
650
713
 
651
- else if(isEdmPropertyRendered(elementCsn, options))
714
+ else if(isEdmPropertyRendered(elementCsn, options))
652
715
  {
653
716
  // CDXCORE-CDXCORE-173
654
717
  // V2: filter @Core.MediaType
655
- if ( options.isV2() && elementCsn['@Core.MediaType']) {
718
+ if ( options.isV2() && elementCsn['@Core.MediaType']) {
719
+ hasStream = elementCsn['@Core.MediaType'];
720
+ delete elementCsn['@Core.MediaType'];
656
721
  // CDXCORE-CDXCORE-177:
657
722
  // V2: don't render element but add attribute 'm:HasStream="true' to EntityType
658
723
  // V4: render property type 'Edm.Stream'
659
- hasStream = true;
660
- info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
661
- '$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
724
+ streamProps.push(elementName);
662
725
 
663
- } else {
664
- props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
665
- }
726
+ } else {
727
+ props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
666
728
  }
667
729
  }
668
730
 
669
731
  });
732
+ if(options.isV2()) {
733
+ if(streamProps.length > 1) {
734
+ error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
735
+ `Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
736
+ }
737
+ else if(streamProps.length === 1) {
738
+ info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
739
+ 'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
740
+ }
741
+ }
670
742
  return [ props, hasStream ];
671
743
  }
672
744
 
@@ -681,24 +753,27 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
681
753
  const loc = ['definitions', structuredTypeCsn.name];
682
754
 
683
755
  if(properties.length === 0) {
684
- warning(null, ['definitions', structuredTypeCsn.name], { name: structuredTypeCsn.name },
756
+ warning(null, loc, { name: structuredTypeCsn.name },
685
757
  'EDM ComplexType $(NAME) has no properties');
686
758
  }
759
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
760
+ message('odata-spec-violation-id', loc, { id: attributes.Name });
761
+
687
762
  properties.forEach(p => {
688
- const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
689
- if(!p[p._typeName]) {
690
- message('odata-spec-violation-type', pLoc);
691
- }
692
- if(p.Name === complexType.Name) {
763
+ const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
764
+ edmTypeCompatibilityCheck(p, pLoc);
765
+ if(p._edmAttributes.Name === complexType._edmAttributes.Name)
693
766
  warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
694
- }
767
+
768
+ if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
769
+ message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
770
+
695
771
  if(options.isV2()) {
696
- if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
697
- warning('odata-spec-violation-array', pLoc, { api: 'OData V2' });
698
- }
699
- if(edmUtils.isAssociationOrComposition(p._csn)) {
700
- warning('odata-spec-violation-assoc', pLoc, { api: 'OData V2' });
701
- }
772
+ if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
773
+ warning('odata-spec-violation-array', pLoc, { version: '2.0' });
774
+
775
+ if(edmUtils.isAssociationOrComposition(p._csn))
776
+ warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
702
777
  }
703
778
  });
704
779
 
@@ -712,8 +787,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
712
787
  function createTypeDefinition(typeCsn)
713
788
  {
714
789
  // derived types are already resolved to base types
715
- const props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
716
- const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
790
+ const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
791
+ if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
792
+ message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
793
+
794
+ const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
795
+ edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
717
796
  Schema.append(typeDef);
718
797
  }
719
798
 
@@ -735,7 +814,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
735
814
  {
736
815
  let constraints = navigationProperty._csn._constraints;
737
816
  let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
738
- let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
817
+ let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
739
818
  let assocName = plainAssocName;
740
819
  let i = 1;
741
820
  while(NamesInSchemaXRef[assocName] !== undefined) {
@@ -743,7 +822,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
743
822
  }
744
823
 
745
824
  let fromRole = parentName;
746
- let toRole = navigationProperty.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
825
+ let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
747
826
 
748
827
  let fromEntityType = fromRole;
749
828
  let toEntityType = toRole;
@@ -763,17 +842,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
763
842
  }
764
843
 
765
844
  // add V2 attributes to navigationProperty
766
- navigationProperty.Relationship = fullQualified(assocName);
767
- navigationProperty.FromRole = fromRole;
768
- navigationProperty.ToRole = toRole;
845
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
846
+ navigationProperty.setEdmAttribute('FromRole', fromRole);
847
+ navigationProperty.setEdmAttribute('ToRole', toRole);
769
848
 
770
849
  // remove V4 attributes
771
- if(navigationProperty.Type != undefined)
772
- delete navigationProperty.Type;
773
- if(navigationProperty.Partner != undefined)
774
- delete navigationProperty.Partner;
775
- if(navigationProperty.ContainsTarget != undefined)
776
- delete navigationProperty.ContainsTarget;
850
+ navigationProperty.removeEdmAttribute('Type');
851
+ navigationProperty.removeEdmAttribute('Partner');
852
+ navigationProperty.removeEdmAttribute('ContainsTarget');
777
853
 
778
854
  /*
779
855
  If NavigationProperty is a backlink association (constraints._originAssocCsn is set), then there are two options:
@@ -799,7 +875,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
799
875
  assocName = plainAssocName + '_' + i++;
800
876
  }
801
877
 
802
- navigationProperty.Relationship = fullQualified(assocName)
878
+ navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
803
879
 
804
880
  reuseAssoc = !!forwardAssocCsn._NavigationProperty;
805
881
  constraints = forwardAssocCsn._constraints;
@@ -854,17 +930,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
854
930
 
855
931
  // generate the Edm.Annotations tree and append it to the corresponding schema
856
932
  function addAnnotations() {
857
- let { annos, usedVocabularies } = translate.csn2annotationEdm(csn, serviceCsn.name, Edm, options, messageFunctions);
933
+ let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
858
934
  // distribute edm:Annotations into the schemas
859
935
  // Distribute each anno into Schema
860
936
  annos.forEach(anno => {
861
- let targetSchema = whatsMySchemaName(anno.Target);
937
+ let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
862
938
  // if no target schema has been found, it's a service annotation that applies to the service schema
863
939
  if(targetSchema === undefined)
864
940
  targetSchema = serviceCsn.name;
865
941
  if(targetSchema) {
866
942
  if(targetSchema !== serviceCsn.name) {
867
- anno.Target = anno.Target.replace(serviceCsn.name + '.', '');
943
+ const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
944
+ anno.setEdmAttribute('Target', newTarget);
868
945
  }
869
946
  edm._service._schemas[targetSchema]._annotations.push(anno);
870
947
  }
@@ -877,6 +954,44 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
877
954
  edm._defaultRefs.push(r);
878
955
  })
879
956
  }
957
+
958
+ function edmTypeCompatibilityCheck(p, pLoc) {
959
+ const edmType = p._type;
960
+ if(!edmType) {
961
+ message('odata-spec-violation-type', pLoc);
962
+ }
963
+ else if(p._scalarType) {
964
+ const td = EdmPrimitiveTypeMap[edmType];
965
+ if(td) {
966
+ // The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
967
+ // Only the unknown type warning may be triggered by an unknown @odata.Type override.
968
+ if(td.v2 !== p.v2 && td.v4 !== p.v4)
969
+ message('odata-spec-violation-type', pLoc,
970
+ { type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
971
+ EdmTypeFacetNames.forEach(name => {
972
+ const facet = EdmTypeFacetMap[name];
973
+ const optional =
974
+ (facet.optional !== undefined)
975
+ ? (Array.isArray(facet.optional)
976
+ ? facet.optional.includes(edmType)
977
+ : facet.optional)
978
+ : false;
979
+
980
+ // facet is not in attributes
981
+ // facet is member of type definition and mandatory
982
+ // node and facet version match
983
+ if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
984
+ message('odata-spec-violation-type', pLoc,
985
+ { type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
986
+ }
987
+ });
988
+ }
989
+ else {
990
+ message('odata-spec-violation-type-unknown', pLoc,
991
+ { type:edmType });
992
+ }
993
+ }
994
+ }
880
995
  }
881
996
  }
882
997
  module.exports = { csn2edm, csn2edmAll };