@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -207,7 +207,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
207
207
  // Note: we assume that all objects ly flat in the service, i.e. objName always
208
208
  // looks like <service name, can contain dots>.<id>
209
209
  forEachDefinition(csn, (object, objName) => {
210
- if(objName == serviceName || objName.startsWith(serviceName + '.')) {
210
+ if (objName === serviceName || objName.startsWith(serviceName + '.')) {
211
211
  if (object.kind === 'action' || object.kind === 'function') {
212
212
  handleAction(objName, object, null);
213
213
  }
@@ -505,8 +505,17 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
505
505
  let testToAlternativeEdmTarget = null; // if true, assign to alternative Edm Target
506
506
 
507
507
  if (carrier.kind === 'entity' || carrier.kind === 'view') {
508
- // If AppliesTo=[EntitySet/Singleton, EntityType], EntitySet/Singleton has precedence
509
- testToAlternativeEdmTarget = (x => x.includes('EntitySet') || x.includes('Singleton'));
508
+ // If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence
509
+ testToAlternativeEdmTarget = (x => {
510
+ if(options.isV2()) {
511
+ return ['Singleton', 'EntitySet', 'Collection'].some(y => x.includes(y));
512
+ }
513
+ else {
514
+ return edmUtils.isSingleton(carrier)
515
+ ? x.includes('Singleton')
516
+ : ['EntitySet', 'Collection'].some(y => x.includes(y));
517
+ }
518
+ });
510
519
  testToStandardEdmTarget = (x => x ? x.includes('EntityType') : true);
511
520
  // if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
512
521
  // (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts
@@ -534,15 +543,21 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
534
543
  alternativeEdmTargetName = edmTargetName;
535
544
  hasAlternativeCarrier = true; // EntityContainer is always available
536
545
  }
537
- //element => decide if navprop or normal property
546
+ //element => decide if navprop or normal property
538
547
  else if(!carrier.kind) {
539
- // if appliesTo is undefined, return true
548
+ // if appliesTo is undefined, return true
540
549
  if(carrier.target) {
541
- testToStandardEdmTarget = (x=> x ? x.includes('NavigationProperty') : true);
550
+ testToStandardEdmTarget = (x => x
551
+ ? x.includes('NavigationProperty') ||
552
+ carrier.cardinality && carrier.cardinality.max === '*' && x.includes('Collection')
553
+ : true);
542
554
  }
543
555
  else {
544
556
  // this might be more precise if handleAnnotation would know more about the carrier
545
- testToStandardEdmTarget = (x => x ? ['Parameter', 'Property'].some(y => x.includes(y)): true);
557
+ testToStandardEdmTarget = (x => x
558
+ ? ['Parameter', 'Property'].some(y => x.includes(y) ||
559
+ carrier._isCollection && x.includes('Collection'))
560
+ : true);
546
561
  }
547
562
  }
548
563
  return [
@@ -851,7 +866,6 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
851
866
  else if (!expectedType['Members'].includes(enumValue)) {
852
867
  message(warning, context, `enumeration type ${ dTypeName } has no value ${ enumValue }`);
853
868
  }
854
- return;
855
869
  }
856
870
 
857
871
  // cAnnoValue: array
@@ -865,8 +879,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
865
879
  }
866
880
 
867
881
  let index = 0;
868
- for (let e of cAnnoValue) {
869
- context.stack.push('[' + index++ + ']');
882
+ for (const e of cAnnoValue) {
883
+ context.stack.push('[' + index + ']');
884
+ index++;
870
885
  if (e['#']) {
871
886
  checkEnumValue(e['#'], dTypeName, context);
872
887
  }
@@ -901,7 +916,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
901
916
  }
902
917
  else {
903
918
  // replace all occurrences of '.' by '/' up to first '@'
904
- val = expr.split('@').map((o,i) => (i==0 ? o.replace(/\./g, '/') : o)).join('@');
919
+ val = expr.split('@').map((o,i) => (i === 0 ? o.replace(/\./g, '/') : o)).join('@');
905
920
  }
906
921
 
907
922
  return {
@@ -922,9 +937,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
922
937
  // caller already made sure that val is neither object nor array
923
938
  dTypeName = resolveType(dTypeName);
924
939
 
925
- if(isEnumType(dTypeName)) {
940
+ if (isEnumType(dTypeName)) {
926
941
  const type = getDictType(dTypeName);
927
- message(warning, context, `found non-enum value "${val}", expected ${type.Members.map(m=>`"#${m}"`).join(', ')} for ${dTypeName}`);
942
+ const expected = type.Members.map(m => `"#${m}"`).join(', ');
943
+ message(warning, context, `found non-enum value "${val}", expected ${expected} for ${dTypeName}`);
928
944
  }
929
945
 
930
946
  let typeName = 'String';
@@ -1119,7 +1135,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1119
1135
  let dictPropertyTypeName = null;
1120
1136
  if (dictProperties) {
1121
1137
  dictPropertyTypeName = dictProperties[i];
1122
- if (!dictPropertyTypeName){
1138
+ if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
1123
1139
  message(warning, context, `record type '${ actualTypeName }' doesn't have a property '${ i }'`);
1124
1140
  }
1125
1141
  }
@@ -1154,8 +1170,9 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1154
1170
  }
1155
1171
 
1156
1172
  let index = 0;
1157
- for (let value of annoValue) {
1158
- context.stack.push('[' + index++ + ']');
1173
+ for (const value of annoValue) {
1174
+ context.stack.push('[' + index + ']');
1175
+ index++
1159
1176
 
1160
1177
  // for dealing with the single array entries we unfortunately cannot call handleValue(),
1161
1178
  // as the values inside an array are represented differently from the values
@@ -1214,8 +1231,8 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1214
1231
  return edmNode;
1215
1232
  }
1216
1233
 
1217
- if(dynExprs.length === 0) {
1218
- if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length==1) {
1234
+ if (dynExprs.length === 0) {
1235
+ if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 1) {
1219
1236
  const k = Object.keys(obj)[0];
1220
1237
  const val = obj[k];
1221
1238
  edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
@@ -236,7 +236,7 @@ function preprocessAnnotations(csn, serviceName, options) {
236
236
  } else {
237
237
  let stringFields = Object.keys(vlEntity.elements).filter(
238
238
  x => !vlEntity.elements[x].key && vlEntity.elements[x].type === 'cds.String')
239
- if (stringFields.length == 1)
239
+ if (stringFields.length === 1)
240
240
  textField = stringFields[0];
241
241
  }
242
242
 
@@ -142,7 +142,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
142
142
  definitions: Object.create(null)
143
143
  }
144
144
  };
145
-
145
+
146
146
  if(options.isV4()) {
147
147
  // tunnel schema xref and servicename in options to edm.Typebase to rectify
148
148
  // type references that are eventually also prefixed with the service schema name.
@@ -304,6 +304,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
304
304
  warning('odata-spec-violation-namespace',
305
305
  [ 'definitions', schema.name ], { names: reservedNames });
306
306
  }
307
+ /** @type {any} */
307
308
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
308
309
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
309
310
  // now namespace and alias are used to create the fullQualified(name)
@@ -390,7 +391,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
390
391
  if(properties.length === 0) {
391
392
  warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
392
393
  } else if(entityCsn.$edmKeyPaths.length === 0) {
393
- warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no primary key');
394
+ message('odata-spec-violation-no-key', loc);
394
395
  }
395
396
  properties.forEach(p => {
396
397
  const pLoc = [...loc, 'elements', p.Name];
@@ -419,7 +420,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
419
420
  /** @type {object} */
420
421
  let containerEntry;
421
422
 
422
- if(edmUtils.isSingleton(entityCsn, options.isV4())) {
423
+ if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
423
424
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
424
425
  if(entityCsn['@odata.singleton.nullable'])
425
426
  containerEntry.Nullable= true;
package/lib/edm/edm.js CHANGED
@@ -665,7 +665,7 @@ module.exports = function (options, error) {
665
665
  json['$'+this._typeName] = this._type;
666
666
 
667
667
  edmUtils.forAll(this, (v,p) => {
668
- if (p !== 'Name' && p != this._typeName
668
+ if (p !== 'Name' && p !== this._typeName
669
669
  // remove this line if Nullable=true becomes default
670
670
  && !(p === 'Nullable' && v == false))
671
671
  {
@@ -711,7 +711,7 @@ module.exports = function (options, error) {
711
711
  if(alias.length > 28) {
712
712
  alias = alias.substr(0, 13)+ '__' +alias.substr(alias.length-13, alias.length);
713
713
  }
714
- alias = alias+'_'+c.toString().padStart(3,0);
714
+ alias = alias+'_'+c.toString().padStart(3, '0');
715
715
  }
716
716
  else if(alias.length > 32) {
717
717
  alias = alias.substr(0, 15)+ '__' +alias.substr(alias.length-15, alias.length);
@@ -835,7 +835,7 @@ module.exports = function (options, error) {
835
835
  // Nullable=false is default for EDM JSON representation 4.01
836
836
  // When a key explicitly (!) has 'notNull = false', it stays nullable
837
837
  return (nodeCsn._NotNullCollection !== undefined ? nodeCsn._NotNullCollection :
838
- (nodeCsn.key && !(nodeCsn.notNull === false)) || nodeCsn.notNull === true);
838
+ (nodeCsn.key && nodeCsn.notNull !== false) || nodeCsn.notNull === true);
839
839
  }
840
840
 
841
841
  toJSONattributes(json)
@@ -1159,7 +1159,7 @@ module.exports = function (options, error) {
1159
1159
  /* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project:
1160
1160
  edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
1161
1161
  */
1162
- case 'Edm.Boolean':
1162
+ case 'Edm.Boolean':
1163
1163
  v = (v=='true'?true:(v=='false'?false:v));
1164
1164
  // eslint-no-fallthrough
1165
1165
  case 'Edm.String':
@@ -1275,8 +1275,8 @@ module.exports = function (options, error) {
1275
1275
  if(this.Type)
1276
1276
  json['@type'] = this.Type;
1277
1277
  let keys = Object.keys(this).filter(k => k !== 'Type');
1278
- for(let i = 0; i < keys.length; i++)
1279
- json['$'+keys[i]] = this[keys[i]];
1278
+ for(const key of keys)
1279
+ json['$'+key] = this[key];
1280
1280
  }
1281
1281
 
1282
1282
  toJSONchildren(json)
@@ -1377,7 +1377,7 @@ module.exports = function (options, error) {
1377
1377
  }
1378
1378
 
1379
1379
  toJSON()
1380
- {
1380
+ {
1381
1381
  // toJSON: depending on number of children unary or n-ary expr
1382
1382
  const json = this.mergeJSONAnnotations();
1383
1383
  const e = this._children.filter(c=>c.kind !== 'Annotation');
@@ -1459,7 +1459,7 @@ module.exports = function (options, error) {
1459
1459
  return json;
1460
1460
  }
1461
1461
  }
1462
- // LabeledElementReference is a
1462
+ // LabeledElementReference is a
1463
1463
  class LabeledElementReference extends ValueThing {
1464
1464
  constructor(v, val) {
1465
1465
  super(v, 'LabeledElementReference', val);
@@ -39,7 +39,7 @@ function initializeModel(csn, _options, messageFunctions)
39
39
  if (!_options)
40
40
  throw Error('Please debug me: initializeModel must be invoked with options');
41
41
 
42
- const { info, warning, error, throwWithError } = messageFunctions;
42
+ const { info, warning, error, message, throwWithError } = messageFunctions;
43
43
 
44
44
  const csnUtils = getUtils(csn);
45
45
  const {
@@ -54,7 +54,7 @@ function initializeModel(csn, _options, messageFunctions)
54
54
  let options = validateOptions(_options);
55
55
 
56
56
  // Fetch service definitions
57
- const serviceRoots = Object.keys(csn.definitions).reduce((serviceRoots, artName) => {
57
+ const serviceRoots = Object.keys(csn.definitions || {}).reduce((serviceRoots, artName) => {
58
58
  const art = csn.definitions[artName];
59
59
  if(art.kind === 'service') {
60
60
  serviceRoots[artName] = Object.assign(art, { name: artName });
@@ -67,6 +67,9 @@ function initializeModel(csn, _options, messageFunctions)
67
67
  function whatsMyServiceRootName(n, self=true) {
68
68
  return serviceRootNames.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') || (n === sn && self) ? sn : rc, undefined);
69
69
  }
70
+ if(serviceRootNames.length === 0) {
71
+ return [serviceRoots, Object.create(null), whatsMyServiceRootName, options];
72
+ }
70
73
 
71
74
  // Structural CSN inbound QA checks
72
75
  inboundQualificationChecks();
@@ -452,8 +455,8 @@ function initializeModel(csn, _options, messageFunctions)
452
455
  // propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity
453
456
  if(entityCsn._containerEntity) {
454
457
  setProp(parameterCsn, '_containerEntity', []);
455
- for(let c of entityCsn._containerEntity) {
456
- parameterCsn._containerEntity.push((c==entityCsn.name)?parameterCsn.name:c);
458
+ for(const c of entityCsn._containerEntity) {
459
+ parameterCsn._containerEntity.push((c === entityCsn.name) ? parameterCsn.name : c);
457
460
  }
458
461
  }
459
462
  entityCsn._containerEntity = [ parameterCsn ];
@@ -486,12 +489,18 @@ function initializeModel(csn, _options, messageFunctions)
486
489
  member.$path[1] = parameterEntityName;
487
490
  });
488
491
 
489
-
490
- csn.definitions[parameterCsn.name] = parameterCsn;
492
+ if(csn.definitions[parameterCsn.name])
493
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: parameterCsn.name });
494
+ else
495
+ csn.definitions[parameterCsn.name] = parameterCsn;
491
496
  // modify the original parameter entity with backlink and new name
492
- csn.definitions[originalEntityName] = entityCsn;
493
- delete csn.definitions[entityCsn.name];
494
- entityCsn.name = originalEntityName;
497
+ if(csn.definitions[originalEntityName])
498
+ error('odata-definition-exists', [ 'definitions', entityName ], { '#': 'std', name: originalEntityName });
499
+ else {
500
+ csn.definitions[originalEntityName] = entityCsn;
501
+ delete csn.definitions[entityCsn.name];
502
+ entityCsn.name = originalEntityName;
503
+ }
495
504
  setProp(entityCsn, '$entitySetName', originalEntitySetName);
496
505
  // add backlink association
497
506
  if(hasBacklink) {
@@ -543,6 +552,7 @@ function initializeModel(csn, _options, messageFunctions)
543
552
  // convert $path to path starting at main artifact
544
553
  function $path2path(p) {
545
554
  const path = [];
555
+ /** @type {object} */
546
556
  let env = csn;
547
557
  for (let i = 0; p && env && i < p.length; i++) {
548
558
  const ps = p[i];
@@ -594,6 +604,16 @@ function initializeModel(csn, _options, messageFunctions)
594
604
  if(element['@cds.valid.from']) {
595
605
  validFrom.push(element);
596
606
  }
607
+ //forward annotations from managed association element to its foreign keys
608
+ const elements = construct.items && construct.items.elements || construct.elements;
609
+ forAll(elements[element['@odata.foreignKey4']], (attr, attrName) => {
610
+ if(attrName[0] === '@') {
611
+ element[attrName] = attr;
612
+ }
613
+ });
614
+ // and eventually remove some afterwards:)
615
+ if(options.isV2())
616
+ setSAPSpecificV2AnnotationsToAssociation(element);
597
617
 
598
618
  // initialize an association
599
619
  if(isAssociationOrComposition(element)) {
@@ -602,20 +622,6 @@ function initializeModel(csn, _options, messageFunctions)
602
622
  assignProp(element._target, '$proxies', []);
603
623
  // $abspath is used as partner path
604
624
  assignProp(element, '$abspath', $path2path(element.$path));
605
-
606
- //forward annotations from managed association element to its foreign keys
607
- if(element.keys && options.isFlatFormat) {
608
- const elements = construct.items && construct.items.elements || construct.elements;
609
- for(let fk of element.keys) {
610
- forAll(element, (attr, attrName) => {
611
- if(attrName[0] === '@' && fk.$generatedFieldName && elements && elements[fk.$generatedFieldName]) {
612
- elements[fk.$generatedFieldName][attrName] = attr;
613
- }
614
- });
615
- }
616
- }
617
- // and afterwards eventually remove some :)
618
- setSAPSpecificV2AnnotationsToAssociation(options, element, def);
619
625
  }
620
626
 
621
627
  // Collect keys
@@ -770,7 +776,7 @@ function initializeModel(csn, _options, messageFunctions)
770
776
  if(element._constraints._partnerCsn.cardinality.src) {
771
777
  let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
772
778
  let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
773
- if(options.isV2() && srcMult != newMult) {
779
+ if(options.isV2() && srcMult !== newMult) {
774
780
  // Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
775
781
  warning(null, null, `Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
776
782
  }
@@ -1269,9 +1275,8 @@ function initializeModel(csn, _options, messageFunctions)
1269
1275
  }
1270
1276
  else if(alreadyRegistered && !alreadyRegistered.$proxy &&
1271
1277
  !['entity', 'view'].includes(alreadyRegistered.kind)) {
1272
- warning(null, ['definitions', element._parent.name, 'elements', element.name],
1273
- { name: fqProxyName, kind: alreadyRegistered.kind },
1274
- 'No proxy EDM entity type created due to name collision with $(NAME) of kind $(KIND)');
1278
+ warning('odata-definition-exists', ['definitions', element._parent.name, 'elements', element.name],
1279
+ { '#': 'proxy', name: fqProxyName, kind: alreadyRegistered.kind });
1275
1280
  return undefined;
1276
1281
  }
1277
1282
  }
@@ -1351,7 +1356,7 @@ function initializeModel(csn, _options, messageFunctions)
1351
1356
  // OData requires all elements along the path to be nullable: false (that is either key or notNull)
1352
1357
 
1353
1358
  const finalType = getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type);
1354
- const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1359
+ const elements = eltCsn.elements || eltCsn.items && eltCsn.items.elements ||
1355
1360
  (finalType && (finalType.elements || finalType.items && finalType.items.elements));
1356
1361
  if(elements) {
1357
1362
  Object.entries(elements).forEach(([eltName, elt]) => {
@@ -1416,7 +1421,7 @@ function initializeModel(csn, _options, messageFunctions)
1416
1421
  if(options.isV4() && type && !isAssociationOrComposition(type) && isBuiltinType(type.type)) {
1417
1422
  const edmType = edmUtils.mapCdsToEdmType(type);
1418
1423
  const legalEdmTypes = [
1419
- 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1424
+ 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Duration',
1420
1425
  'Edm.Guid', 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte', 'Edm.String', 'Edm.TimeOfDay' ];
1421
1426
  if(!legalEdmTypes.includes(edmType)) {
1422
1427
  warning('odata-spec-violation-key-type', location,
@@ -1518,8 +1523,8 @@ function initializeModel(csn, _options, messageFunctions)
1518
1523
  // end point reached but must not be an external reference nor a proxy nor a composition itself
1519
1524
  // last assoc step must not be to-n and target a singleton
1520
1525
  let p = undefined;
1521
- if (!elt._target.$externalRef &&
1522
- !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target, options.isV4()))) {
1526
+ if (!elt._target.$externalRef &&
1527
+ !(edmUtils.isToMany(elt) && edmUtils.isSingleton(elt._target) && options.isV4())) {
1523
1528
  if(elt._target.$edmTgtPaths && elt._target.$edmTgtPaths.length) {
1524
1529
  p = elt._target.$edmTgtPaths.find(p => p[0] === edmUtils.getBaseName(struct.name)) || elt._target.$edmTgtPaths[0];
1525
1530
  }
@@ -1527,7 +1532,7 @@ function initializeModel(csn, _options, messageFunctions)
1527
1532
  const baseName = edmUtils.getBaseName(elt._target.$entitySetName || elt._target.name);
1528
1533
  // if own struct and target have a set they either are in the same $mySchemaName or not
1529
1534
  // if target is in another schema, target the full qualified entity set
1530
- p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1535
+ p = (elt._target.$mySchemaName === struct.$mySchemaName) ?
1531
1536
  [ baseName ] : [elt._target.$mySchemaName + '.EntityContainer', baseName];
1532
1537
  }
1533
1538
  if(p) {
@@ -1642,7 +1647,7 @@ function initializeModel(csn, _options, messageFunctions)
1642
1647
  !isBetaEnabled(options, 'nestedServices') && serviceRootNames.forEach(sn => {
1643
1648
  const parent = whatsMyServiceRootName(sn, false);
1644
1649
  if(parent && parent !== sn) {
1645
- error( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1650
+ message( 'service-nested-service', [ 'definitions', sn ], { art: parent },
1646
1651
  'A service can\'t be nested within a service $(ART)' );
1647
1652
  }
1648
1653
  });
@@ -1651,7 +1656,7 @@ function initializeModel(csn, _options, messageFunctions)
1651
1656
  if(art.kind === 'context') {
1652
1657
  const parent = whatsMyServiceRootName(fqName);
1653
1658
  if(parent) {
1654
- error( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1659
+ message( 'service-nested-context', [ 'definitions', fqName ], { art: parent },
1655
1660
  'A context can\'t be nested within a service $(ART)' );
1656
1661
  }
1657
1662
  }
@@ -1789,8 +1794,8 @@ function initializeModel(csn, _options, messageFunctions)
1789
1794
  if(!navPropEntry[prop]) {
1790
1795
  // Filter properties with prefix and reduce them into a new dictionary
1791
1796
  const o = props.filter(p => p[0].startsWith(prefix+'.')).reduce((a,c) => {
1792
- a[c[0].replace(prefix+'.', '')] = c[1];
1793
- return a;
1797
+ a[c[0].replace(prefix+'.', '')] = c[1];
1798
+ return a;
1794
1799
  }, { });
1795
1800
  // if dictionary has entries, add them to navPropEnty
1796
1801
  if(Object.keys(o).length) {
@@ -1926,8 +1931,9 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
1926
1931
  // nested functions begin
1927
1932
  function PDMSemantics()
1928
1933
  {
1929
- let dict = Object.create(null);
1930
1934
  /*
1935
+ let dict = Object.create(null);
1936
+
1931
1937
  dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
1932
1938
  dict['@PDM.xxx2'] = [ '@sap.pdm-propery' ];
1933
1939
  dict['@PDM.xxx3'] = [ '@sap.pdm-display-sq-no' ];
@@ -1941,7 +1947,7 @@ function applyAppSpecificLateCsnTransformationOnElement(options, element, struct
1941
1947
  // respect flattened annotation $value
1942
1948
  Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
1943
1949
  */
1944
- return dict;
1950
+ return Object.create(null);
1945
1951
  }
1946
1952
 
1947
1953
  function AnalyticalAnnotations()
@@ -2159,19 +2165,17 @@ function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
2159
2165
  }
2160
2166
  }
2161
2167
 
2162
- function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
2163
- if(!options.isV2())
2164
- return;
2168
+ function setSAPSpecificV2AnnotationsToAssociation(carrier) {
2165
2169
  // documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
2166
2170
  const SetAttributes = {
2167
2171
  // Applicable to NavProp and foreign keys, add to AssociationSet
2168
- '@sap.creatable' : (struct, c,pn, pv) => { addToSetAttr(struct, c, pn, pv, false); },
2172
+ '@sap.creatable' : (c, pn, pv) => { addToAssociationSet(c, pn, pv, false); },
2169
2173
  // Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
2170
- '@sap.updatable' : addToSetAttr,
2174
+ '@sap.updatable' : addToAssociationSet,
2171
2175
  // Not applicable to NavProp, not applicable to foreign key, add to AssociationSet
2172
- '@sap.deletable': (struct, c, pn, pv) => {
2173
- addToSetAttr(struct, c, pn, pv);
2174
- removeFromForeignKey(struct, c, pn);
2176
+ '@sap.deletable': (c, pn, pv) => {
2177
+ addToAssociationSet(c, pn, pv);
2178
+ removeFromForeignKey(c, pn);
2175
2179
  },
2176
2180
  // applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
2177
2181
  '@sap.creatable.path': removeFromForeignKey,
@@ -2179,24 +2183,22 @@ function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
2179
2183
  };
2180
2184
 
2181
2185
  Object.entries(carrier).forEach(([p, v]) => {
2182
- (SetAttributes[p] || function() {/* no-op */})(struct, carrier, p, v);
2186
+ (SetAttributes[p] || function() {/* no-op */})(carrier, p, v);
2183
2187
  });
2184
2188
 
2185
- function addToSetAttr(struct, carrier, propName, propValue, removeFromType=true) {
2186
- assignProp(carrier, '_SetAttributes', Object.create(null));
2187
- assignAnnotation(carrier._SetAttributes, propName, propValue);
2188
- if(removeFromType) {
2189
- delete carrier[propName];
2189
+ function addToAssociationSet(carrier, propName, propValue, removeFromType=true) {
2190
+ if(isAssociationOrComposition(carrier)) {
2191
+ assignProp(carrier, '_SetAttributes', Object.create(null));
2192
+ assignAnnotation(carrier._SetAttributes, propName, propValue);
2193
+ if(removeFromType) {
2194
+ delete carrier[propName];
2195
+ }
2190
2196
  }
2191
2197
  }
2192
2198
 
2193
- function removeFromForeignKey(struct, carrier, propName) {
2194
- if(carrier.target && carrier.keys) {
2195
- struct.elements && Object.values(struct.elements).forEach(e => {
2196
- if(e['@odata.foreignKey4'] === carrier.name) {
2197
- delete e[propName];
2198
- }
2199
- });
2199
+ function removeFromForeignKey(carrier, propName) {
2200
+ if(carrier['@odata.foreignKey4'] && carrier[propName] !== undefined) {
2201
+ delete carrier[propName];
2200
2202
  }
2201
2203
  }
2202
2204
  }
@@ -30,18 +30,18 @@ function validateOptions(_options)
30
30
 
31
31
  }
32
32
 
33
- const v2 = options.version.match(/v2/i) != undefined;
34
- const v4 = options.version.match(/v4/i) != undefined;
33
+ const v2 = options.version.match(/v2/i) !== null;
34
+ const v4 = options.version.match(/v4/i) !== null;
35
35
 
36
36
  options.v = [v2, v4];
37
37
  options.isStructFormat = options.odataFormat && options.odataFormat === 'structured';
38
38
  options.isFlatFormat = !options.isStructFormat;
39
39
 
40
- if(options.v.filter(v=>v).length != 1)
40
+ if(options.v.filter(v=>v).length !== 1)
41
41
  throw Error(`Please debug me: EDM V2:${v2}, V4:${v4}`);
42
42
 
43
- options.isV2 = function() { return this.v[0] == true; }
44
- options.isV4 = function() { return this.v[1] == true; }
43
+ options.isV2 = function() { return this.v[0]; }
44
+ options.isV4 = function() { return this.v[1]; }
45
45
 
46
46
  options.pathDelimiter = options.isStructFormat ? '/' : '_';
47
47
 
@@ -115,10 +115,10 @@ function isToMany(assoc) {
115
115
  return targetMax === '*' || Number(targetMax) > 1;
116
116
  }
117
117
 
118
- function isSingleton(entityCsn, v) {
118
+ function isSingleton(entityCsn) {
119
119
  const singleton = entityCsn['@odata.singleton'];
120
120
  const hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined && entityCsn['@odata.singleton.nullable'] !== null;
121
- return v && singleton || ((singleton === undefined || singleton === null) && hasNullable);
121
+ return singleton || ((singleton === undefined || singleton === null) && hasNullable);
122
122
  }
123
123
 
124
124
  function isEntity(artifact)
@@ -181,7 +181,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
181
181
  const parent = csn.definitions[parentName];
182
182
  if(originAssocCsn) {
183
183
  const originParentName = originAssocCsn.$abspath[0];
184
- if(originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
184
+ if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
185
185
  isBacklink = false;
186
186
  // Partnership is ambiguous
187
187
  setProp(originAssocCsn, '$noPartner', true);
@@ -192,7 +192,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
192
192
  // Mark this association as backlink if $self appears exactly once
193
193
  // to surpress edm:Association generation in V2 mode
194
194
  if(isBacklink) {
195
- // use first backlink as partner
195
+ // establish partnership with origin assoc but only if this association is the first one
196
196
  if(originAssocCsn._selfReferences.length === 0) {
197
197
  assocCsn._constraints._partnerCsn = originAssocCsn;
198
198
  }
@@ -202,7 +202,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
202
202
  }
203
203
  // store all backlinks at forward, required to calculate rendering of foreign keys
204
204
  // if the termCount != 1 or more than one $self compare this is not a backlink
205
- if(assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
205
+ if(parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
206
206
  originAssocCsn._selfReferences.push(assocCsn);
207
207
  }
208
208
  assocCsn._constraints._origins.push(originAssocCsn);
@@ -358,7 +358,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
358
358
  // in structured mode only resolve top level element (path rewriting is done elsewhere)
359
359
  const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
360
360
  const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
361
- const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
361
+ const fk = (isEntity(dependentEntity) && dependentEntity.elements[ depEltName ]) ||
362
362
  (localDepEntity && localDepEntity.elements && localDepEntity.elements[ depEltName ]);
363
363
  const pk = principalEntity.$keys && principalEntity.$keys[ principalEltName ];
364
364
  if(isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
@@ -463,12 +463,11 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
463
463
  * The element must never be an association or composition and be renderable.
464
464
  */
465
465
  function isConstraintCandidate(elt) {
466
- let rc= (elt &&
466
+ return (elt &&
467
467
  elt.type &&
468
468
  (!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
469
469
  !['cds.Association', 'cds.Composition'].includes(elt.type) &&
470
470
  isEdmPropertyRendered(elt, options));
471
- return rc;
472
471
  }
473
472
  }
474
473
 
@@ -694,10 +693,10 @@ function getBaseName(name) {
694
693
 
695
694
  // This is a poor mans path resolver for $self partner paths only
696
695
  function resolveOriginAssoc(csn, env, path) {
697
- for(let i = 0; i < path.length; i++) {
696
+ for(const segment of path) {
698
697
  let elements = (env.items && env.items.elements || env.elements);
699
698
  if(elements)
700
- env = env.elements[path[i]];
699
+ env = env.elements[segment];
701
700
  let type = (env.items && env.items.type || env.type);
702
701
  if(type && !isBuiltinType(type) && !(env.items && env.items.elements || env.elements))
703
702
  env = csn.definitions[env.type];