@sap/cds-compiler 4.0.2 → 4.2.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 (101) hide show
  1. package/CHANGELOG.md +200 -5
  2. package/bin/cdsc.js +18 -15
  3. package/doc/CHANGELOG_BETA.md +16 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +33 -13
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +25 -25
  8. package/lib/base/location.js +6 -7
  9. package/lib/base/message-registry.js +123 -42
  10. package/lib/base/messages.js +18 -10
  11. package/lib/base/model.js +43 -10
  12. package/lib/checks/defaultValues.js +6 -6
  13. package/lib/checks/elements.js +11 -10
  14. package/lib/checks/foreignKeys.js +0 -5
  15. package/lib/checks/manyNavigations.js +33 -0
  16. package/lib/checks/onConditions.js +22 -14
  17. package/lib/checks/queryNoDbArtifacts.js +132 -73
  18. package/lib/checks/selectItems.js +4 -55
  19. package/lib/checks/sql-snippets.js +15 -4
  20. package/lib/checks/types.js +3 -3
  21. package/lib/checks/utils.js +4 -3
  22. package/lib/checks/validator.js +3 -1
  23. package/lib/compiler/.eslintrc.json +2 -1
  24. package/lib/compiler/assert-consistency.js +71 -40
  25. package/lib/compiler/base.js +7 -2
  26. package/lib/compiler/builtins.js +40 -41
  27. package/lib/compiler/checks.js +415 -367
  28. package/lib/compiler/classes.js +62 -0
  29. package/lib/compiler/cycle-detector.js +9 -9
  30. package/lib/compiler/define.js +124 -90
  31. package/lib/compiler/extend.js +115 -88
  32. package/lib/compiler/finalize-parse-cdl.js +26 -25
  33. package/lib/compiler/generate.js +57 -49
  34. package/lib/compiler/index.js +56 -56
  35. package/lib/compiler/kick-start.js +10 -7
  36. package/lib/compiler/moduleLayers.js +1 -1
  37. package/lib/compiler/populate.js +180 -144
  38. package/lib/compiler/propagator.js +10 -9
  39. package/lib/compiler/resolve.js +321 -246
  40. package/lib/compiler/shared.js +812 -433
  41. package/lib/compiler/tweak-assocs.js +114 -50
  42. package/lib/compiler/utils.js +241 -46
  43. package/lib/edm/.eslintrc.json +40 -1
  44. package/lib/edm/annotations/genericTranslation.js +721 -707
  45. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  46. package/lib/edm/csn2edm.js +389 -378
  47. package/lib/edm/edm.js +679 -770
  48. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  49. package/lib/edm/edmInboundChecks.js +29 -27
  50. package/lib/edm/edmPreprocessor.js +689 -648
  51. package/lib/edm/edmUtils.js +279 -300
  52. package/lib/gen/Dictionary.json +34 -10
  53. package/lib/gen/language.checksum +1 -1
  54. package/lib/gen/language.interp +1 -1
  55. package/lib/gen/languageParser.js +2857 -2856
  56. package/lib/json/from-csn.js +77 -51
  57. package/lib/json/to-csn.js +15 -15
  58. package/lib/language/antlrParser.js +2 -1
  59. package/lib/language/genericAntlrParser.js +52 -43
  60. package/lib/language/language.g4 +61 -64
  61. package/lib/language/multiLineStringParser.js +2 -0
  62. package/lib/main.d.ts +65 -0
  63. package/lib/model/csnRefs.js +37 -19
  64. package/lib/model/csnUtils.js +51 -18
  65. package/lib/model/revealInternalProperties.js +30 -22
  66. package/lib/modelCompare/compare.js +149 -41
  67. package/lib/modelCompare/utils/filter.js +55 -25
  68. package/lib/optionProcessor.js +21 -9
  69. package/lib/render/manageConstraints.js +20 -17
  70. package/lib/render/toCdl.js +63 -23
  71. package/lib/render/toHdbcds.js +2 -2
  72. package/lib/render/toRename.js +4 -9
  73. package/lib/render/toSql.js +82 -35
  74. package/lib/render/utils/common.js +11 -9
  75. package/lib/render/utils/unique.js +52 -0
  76. package/lib/transform/db/applyTransformations.js +62 -21
  77. package/lib/transform/db/assertUnique.js +7 -8
  78. package/lib/transform/db/associations.js +2 -2
  79. package/lib/transform/db/cdsPersistence.js +9 -9
  80. package/lib/transform/db/constraints.js +47 -17
  81. package/lib/transform/db/expansion.js +138 -68
  82. package/lib/transform/db/flattening.js +98 -30
  83. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  84. package/lib/transform/db/temporal.js +1 -1
  85. package/lib/transform/db/transformExists.js +8 -7
  86. package/lib/transform/db/views.js +73 -33
  87. package/lib/transform/draft/db.js +11 -9
  88. package/lib/transform/draft/odata.js +1 -1
  89. package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
  90. package/lib/transform/forRelationalDB.js +148 -136
  91. package/lib/transform/localized.js +92 -54
  92. package/lib/transform/odata/toFinalBaseType.js +3 -3
  93. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
  94. package/lib/transform/translateAssocsToJoins.js +14 -28
  95. package/lib/utils/file.js +7 -7
  96. package/lib/utils/moduleResolve.js +210 -121
  97. package/lib/utils/objectUtils.js +1 -1
  98. package/package.json +5 -5
  99. package/share/messages/check-proper-type-of.md +1 -1
  100. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  101. package/share/messages/message-explanations.json +1 -1
@@ -1,27 +1,28 @@
1
1
  'use strict';
2
+
2
3
  const { setProp } = require('../base/model');
3
- const { isBuiltinType, isEdmPropertyRendered, applyTransformations, cloneAnnotationValue } = require('../model/csnUtils');
4
+ const {
5
+ isBuiltinType, isEdmPropertyRendered, applyTransformations, cloneAnnotationValue,
6
+ } = require('../model/csnUtils');
4
7
  const { escapeString, hasControlCharacters, hasUnpairedUnicodeSurrogate } = require('../render/utils/stringEscapes');
5
- const {CompilerAssertion} = require('../base/error');
8
+ const { CompilerAssertion } = require('../base/error');
6
9
 
7
10
  /* eslint max-statements-per-line:off */
8
- function validateOptions(_options)
9
- {
10
- if(!_options.isV2 && !_options.isV4)
11
- {
11
+ function validateOptions( _options ) {
12
+ if (!_options.isV2 && !_options.isV4) {
12
13
  // csn2edm expects "odataVersion" to be a top-level property of options
13
14
  // set to 'v4' as default, override with value from incoming options
14
- const options = Object.assign({ odataVersion: 'v4'}, _options);
15
+ const options = Object.assign({ odataVersion: 'v4' }, _options);
15
16
  // global flag that indicates whether or not FKs shall be rendered in general
16
17
  // V2/V4 flat: yes
17
18
  // V4/struct: depending on odataForeignKeys
18
- options.renderForeignKeys =
19
- options.odataVersion === 'v4' ? options.odataFormat === 'structured' && !!options.odataForeignKeys : true;
19
+ options.renderForeignKeys
20
+ = options.odataVersion === 'v4' ? options.odataFormat === 'structured' && !!options.odataForeignKeys : true;
20
21
 
21
22
  const v2 = options.odataVersion.match(/v2/i) !== null;
22
23
  const v4 = options.odataVersion.match(/v4/i) !== null;
23
24
 
24
- options.v = [v2, v4];
25
+ options.v = [ v2, v4 ];
25
26
  options.isStructFormat = options.odataFormat && options.odataFormat === 'structured';
26
27
  options.isFlatFormat = !options.isStructFormat;
27
28
 
@@ -32,81 +33,80 @@ function validateOptions(_options)
32
33
 
33
34
  return options;
34
35
  }
35
- else
36
- return _options;
36
+ return _options;
37
37
  }
38
38
 
39
39
  // returns intersection of two arrays
40
- function intersect(a,b)
41
- {
42
- return [...new Set(a)].filter(x => new Set(b).has(x));
40
+ function intersect( a, b ) {
41
+ return [ ...new Set(a) ].filter(x => new Set(b).has(x));
43
42
  }
44
43
 
45
44
  // Call func(art, name) for each artifact 'art' with name 'name' in 'dictionary' that returns true for 'filter(art)'
46
- function foreach(dictionary, filter, func) {
47
- dictionary && Object.entries(dictionary).forEach(([name, value]) => {
48
- if (filter(value)) {
49
- if(Array.isArray(func))
50
- func.forEach(f=>f(value, name));
51
- else
45
+ function foreach( dictionary, filter, func ) {
46
+ if (dictionary) {
47
+ Object.entries(dictionary).forEach(([ name, value ]) => {
48
+ if (filter(value)) {
49
+ if (Array.isArray(func))
50
+ func.forEach(f => f(value, name));
51
+ else
52
52
  func(value, name);
53
- }
54
- });
53
+ }
54
+ });
55
+ }
55
56
  }
56
57
 
57
58
  // true if _containerEntity is unequal to artifact name (non-recursive containment association)
58
59
  // or if artifact belongs to an artificial parameter entity
59
- function isContainee(artifact) {
60
+ function isContainee( artifact ) {
60
61
  // if $containerNames is present, it is guaranteed that it has at least one entry
61
- return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] != artifact.name));
62
+ return (artifact.$containerNames && (artifact.$containerNames.length > 1 || artifact.$containerNames[0] !== artifact.name));
62
63
  }
63
64
 
64
65
  // Return true if the association 'assoc' has cardinality 'to-many'
65
- function isToMany(assoc) {
66
- if (!assoc.cardinality) {
66
+ function isToMany( assoc ) {
67
+ if (!assoc.cardinality)
67
68
  return false;
68
- }
69
+
69
70
  // Different representations possible: array or targetMax property
70
- let targetMax = assoc.cardinality[1] ||assoc.cardinality.max;
71
- if (!targetMax) {
71
+ const targetMax = assoc.cardinality[1] || assoc.cardinality.max;
72
+ if (!targetMax)
72
73
  return false;
73
- }
74
+
74
75
  return targetMax === '*' || Number(targetMax) > 1;
75
76
  }
76
77
 
77
- function isNavigable(assoc) {
78
+ function isNavigable( assoc ) {
78
79
  return (assoc.target && (assoc['@odata.navigable'] == null || assoc['@odata.navigable']));
79
80
  }
80
- function isSingleton(entityCsn) {
81
+ function isSingleton( entityCsn ) {
81
82
  const singleton = entityCsn['@odata.singleton'];
82
83
  const hasNullable = entityCsn['@odata.singleton.nullable'] != null;
83
84
  return singleton || (singleton == null && hasNullable);
84
85
  }
85
86
 
86
- function isParameterizedEntity(artifact) {
87
+ function isParameterizedEntity( artifact ) {
87
88
  return artifact.kind === 'entity' && artifact.params;
88
89
  }
89
90
 
90
91
  // Return true if 'artifact' is structured (i.e. has elements, like a structured type or an entity)
91
- function isStructuredArtifact(artifact) {
92
+ function isStructuredArtifact( artifact ) {
92
93
  // FIXME: No derived types etc yet
93
94
  return (artifact.items && artifact.items.elements || artifact.elements);
94
95
  }
95
96
 
96
97
  // Return true if 'artifact' is a real structured type (not an entity)
97
- function isStructuredType(artifact) {
98
+ function isStructuredType( artifact ) {
98
99
  return artifact.kind === 'type' && isStructuredArtifact(artifact);
99
100
  }
100
101
 
101
- function isDerivedType(artifact) {
102
+ function isDerivedType( artifact ) {
102
103
  return artifact.kind === 'type' && !isStructuredArtifact(artifact);
103
104
  }
104
105
 
105
- function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions) {
106
+ function resolveOnConditionAndPrepareConstraints( csn, assocCsn, messageFunctions ) {
106
107
  const { info, warning } = messageFunctions;
107
108
 
108
- if(assocCsn.on)
109
- {
109
+ if (assocCsn.on) {
110
110
  // fill constraint array with [prop, depProp]
111
111
  getExpressionArguments(assocCsn.on);
112
112
 
@@ -124,37 +124,36 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
124
124
  ON Condition back.toE => parter=toE cannot be resolved in EParameters, _originalTarget 'E' is
125
125
  required for that
126
126
  */
127
- assocCsn._constraints.selfs.filter(p => p).forEach(partnerPath => {
127
+ assocCsn._constraints.selfs.filter(p => p).forEach((partnerPath) => {
128
128
  // resolve partner path in target
129
129
  const originAssocCsn = resolveOriginAssoc(csn, (assocCsn._originalTarget || assocCsn._target), partnerPath);
130
130
  const parentName = assocCsn.$abspath[0];
131
131
  const parent = csn.definitions[parentName];
132
- if(originAssocCsn && originAssocCsn.$abspath) {
132
+ if (originAssocCsn && originAssocCsn.$abspath) {
133
133
  const originParentName = originAssocCsn.$abspath[0];
134
- if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
134
+ if (parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
135
135
  isBacklink = false;
136
136
  // Partnership is ambiguous
137
137
  setProp(originAssocCsn, '$noPartner', true);
138
- info(null, ['definitions', parentName, 'elements', assocCsn.name],
139
- `"${originParentName}:${partnerPath.join('.')}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentName}"`);
138
+ info(null, [ 'definitions', parentName, 'elements', assocCsn.name ],
139
+ `"${originParentName}:${partnerPath.join('.')}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentName}"`);
140
140
  }
141
- if(originAssocCsn.target) {
141
+ if (originAssocCsn.target) {
142
142
  // Mark this association as backlink if $self appears exactly once
143
143
  // to suppress edm:Association generation in V2 mode
144
- if(isBacklink) {
144
+ if (isBacklink) {
145
145
  // establish partnership with origin assoc but only if this association is the first one
146
- if(originAssocCsn._selfReferences.length === 0) {
146
+ if (originAssocCsn._selfReferences.length === 0)
147
147
  assocCsn._constraints._partnerCsn = originAssocCsn;
148
- }
149
- else {
148
+
149
+ else
150
150
  isBacklink = false;
151
- }
152
151
  }
153
152
  // store all backlinks at forward, required to calculate rendering of foreign keys
154
153
  // if the termCount != 1 or more than one $self compare this is not a backlink
155
- if(parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
154
+ if (parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1)
156
155
  originAssocCsn._selfReferences.push(assocCsn);
157
- }
156
+
158
157
  assocCsn._constraints._origins.push(originAssocCsn);
159
158
  }
160
159
  else {
@@ -163,78 +162,72 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
163
162
  key id : Integer;
164
163
  toMe: association to E on toMe.id = $self; };
165
164
  */
166
- throw new CompilerAssertion('Backlink association element is not an association or composition: "' + originAssocCsn.name);
165
+ throw new CompilerAssertion(`Backlink association element is not an association or composition: "${originAssocCsn.name}`);
167
166
  }
168
167
  }
169
- else
170
- {
171
- warning(null, ['definitions', parentName],
172
- { partner: `${assocCsn._target.name}/${partnerPath}`, name: `${parentName}/${assocCsn.name}` },
173
- 'Can\'t resolve backlink to $(PARTNER) from $(NAME)');
168
+ else {
169
+ warning(null, [ 'definitions', parentName ],
170
+ { partner: `${assocCsn._target.name}/${partnerPath}`, name: `${parentName}/${assocCsn.name}` },
171
+ 'Can\'t resolve backlink to $(PARTNER) from $(NAME)');
174
172
  }
175
173
  });
176
174
  }
177
175
 
178
176
  // nested functions
179
- function getExpressionArguments(expr)
180
- {
181
- let allowedTokens = [ '=', 'and', '(', ')' ];
182
- if(expr && Array.isArray(expr))
183
- // if some returns true, this term is not usable as a constraint term
184
- if(!expr.some(isNotAConstraintTerm))
185
- expr.forEach(fillConstraints)
177
+ function getExpressionArguments( expr ) {
178
+ const allowedTokens = [ '=', 'and', '(', ')' ];
179
+ if (expr && Array.isArray(expr) && !expr.some(isNotAConstraintTerm))
180
+ // if some returns true, this term is not usable as a constraint term
181
+ expr.forEach(fillConstraints);
182
+
186
183
 
187
184
  // return true if token is not one of '=', 'and', '(', ')' or object
188
- function isNotAConstraintTerm(tok)
189
- {
190
- if(tok.xpr)
185
+ function isNotAConstraintTerm( tok ) {
186
+ if (tok.xpr)
191
187
  return tok.xpr.some(isNotAConstraintTerm);
192
- if(Array.isArray(tok))
188
+ if (Array.isArray(tok))
193
189
  return tok.some(isNotAConstraintTerm);
194
190
  return !(typeof tok === 'object' && tok != null || allowedTokens.includes(tok));
195
191
  }
196
192
 
197
193
  // fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms
198
- function fillConstraints(arg, pos)
199
- {
200
- if(arg.xpr)
194
+ function fillConstraints( arg, pos ) {
195
+ if (arg.xpr) {
201
196
  getExpressionArguments(arg.xpr);
202
- else if(pos > 0 && pos < expr.length)
203
- {
204
- let lhs = expr[pos-1];
205
- let rhs = expr[pos+1];
206
- if(arg === '=')
207
- {
197
+ }
198
+ else if (pos > 0 && pos < expr.length) {
199
+ let lhs = expr[pos - 1];
200
+ let rhs = expr[pos + 1];
201
+ if (arg === '=') {
208
202
  assocCsn._constraints.termCount++;
209
- if(lhs.ref && rhs.ref) // ref is a path
210
- {
203
+ if (lhs.ref && rhs.ref) { // ref is a path
211
204
  lhs = lhs.ref;
212
205
  rhs = rhs.ref;
213
206
  // if exactly one operand starts with the prefix then this is potentially a constraint
214
207
 
215
208
  // strip of prefix '$self's
216
- if(lhs[0] === '$self' && lhs.length > 1)
209
+ if (lhs[0] === '$self' && lhs.length > 1)
217
210
  lhs = lhs.slice(1);
218
- if(rhs[0] === '$self' && rhs.length > 1)
211
+ if (rhs[0] === '$self' && rhs.length > 1)
219
212
  rhs = rhs.slice(1);
220
213
 
221
- if((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
222
- (lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name))
223
- {
214
+ if ((lhs[0] === assocCsn.name && rhs[0] !== assocCsn.name) ||
215
+ (lhs[0] !== assocCsn.name && rhs[0] === assocCsn.name)) {
224
216
  // order is always [ property, referencedProperty ]
225
- //backlink [ self, assocName ]
217
+ // backlink [ self, assocName ]
226
218
 
227
219
  let c;
228
- if(lhs[0] === assocCsn.name)
229
- c = [rhs, lhs.slice(1)];
220
+ if (lhs[0] === assocCsn.name)
221
+ c = [ rhs, lhs.slice(1) ];
230
222
  else
231
- c = [lhs, rhs.slice(1)];
223
+ c = [ lhs, rhs.slice(1) ];
232
224
 
233
225
  // do we have a $self id?
234
226
  // if so, store partner in selfs array
235
- if(c[0][0] === '$self' && c[0].length === 1) {
227
+ if (c[0][0] === '$self' && c[0].length === 1) {
236
228
  assocCsn._constraints.selfs.push(c[1]);
237
- } else {
229
+ }
230
+ else {
238
231
  const key = c.join(',');
239
232
  assocCsn._constraints.constraints[key] = c;
240
233
  }
@@ -246,8 +239,7 @@ function resolveOnConditionAndPrepareConstraints(csn, assocCsn, messageFunctions
246
239
  }
247
240
  }
248
241
 
249
- function finalizeReferentialConstraints(csn, assocCsn, options, info)
250
- {
242
+ function finalizeReferentialConstraints( csn, assocCsn, options, info ) {
251
243
  if (assocCsn.on) {
252
244
  /* example for originalTarget:
253
245
  entity E (with parameters) {
@@ -260,15 +252,14 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
260
252
  ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
261
253
  required for that
262
254
  */
263
- assocCsn._constraints._origins.forEach(originAssocCsn => {
255
+ assocCsn._constraints._origins.forEach((originAssocCsn) => {
264
256
  // if the origin assoc is marked as primary key and if it's managed, add all its foreign keys as constraint
265
257
  // as they are also primary keys of the origin entity as well
266
- if(!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
267
- for(let fk of originAssocCsn.keys) {
268
- let realFk = originAssocCsn._parent.elements[fk.$generatedFieldName];
269
- let pk = assocCsn._parent.elements[fk.ref[0]];
270
- if(isConstraintCandidate(pk) && isConstraintCandidate(realFk))
271
- {
258
+ if (!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
259
+ for (const fk of originAssocCsn.keys) {
260
+ const realFk = originAssocCsn._parent.elements[fk.$generatedFieldName];
261
+ const pk = assocCsn._parent.elements[fk.ref[0]];
262
+ if (isConstraintCandidate(pk) && isConstraintCandidate(realFk)) {
272
263
  const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
273
264
  const key = c.join(',');
274
265
  assocCsn._constraints.constraints[key] = c;
@@ -277,87 +268,88 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
277
268
  }
278
269
  });
279
270
 
280
- if(!assocCsn._target.$isParamEntity) {
271
+ if (!assocCsn._target.$isParamEntity) {
281
272
  // Use $path to identify main artifact in case assocs parent was a nested type and deanonymized
282
273
  // Some (draft) associations don't have a $path, use _parent as last resort
283
274
  let dependentEntity = assocCsn.$path ? csn.definitions[assocCsn.$path[1]] : assocCsn._parent;
284
- let localDepEntity = assocCsn._parent;
275
+ let localDepEntity = assocCsn._parent;
285
276
  // _target must always be a main artifact
286
277
  let principalEntity = assocCsn._target;
287
- if(assocCsn.type === 'cds.Composition') {
278
+ if (assocCsn.type === 'cds.Composition') {
288
279
  // Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
289
280
  principalEntity = dependentEntity;
290
281
  localDepEntity = undefined;
291
282
  dependentEntity = assocCsn._target;
292
283
  // Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
293
- Object.keys(assocCsn._constraints.constraints).forEach(cn => {
294
- assocCsn._constraints.constraints[cn] = [ assocCsn._constraints.constraints[cn][1], assocCsn._constraints.constraints[cn][0] ] } );
284
+ Object.keys(assocCsn._constraints.constraints).forEach((cn) => {
285
+ assocCsn._constraints.constraints[cn] = [ assocCsn._constraints.constraints[cn][1], assocCsn._constraints.constraints[cn][0] ];
286
+ } );
295
287
  }
296
288
  // Remove all target elements that are not key in the principal entity
297
289
  // and all elements that annotated with '@cds.api.ignore'
298
290
  const remainingPrincipalRefs = [];
299
291
  foreach(assocCsn._constraints.constraints,
300
- c => {
301
- // rc === true will remove the constraint (positive filter expression)
302
- let rc = true;
303
- // concatenate all paths in flat mode to identify the correct element
304
- // in structured mode only resolve top level element (path rewriting is done elsewhere)
305
- const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
306
- const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
307
- const fk = (dependentEntity.kind === 'entity' && dependentEntity.elements[ depEltName ]) ||
308
- (localDepEntity && localDepEntity.elements && localDepEntity.elements[ depEltName ]);
309
- const pk = principalEntity.$keys && principalEntity.$keys[ principalEltName ];
310
- if(isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
311
- if(options.isStructFormat) {
312
- // In structured mode it might be the association has a new _parent due to
313
- // type de-anonymization.
314
- // There are three cases for dependent ON condition paths:
315
- // 1) path is relative to assoc in same sub structure
316
- // 2) path is absolute and ends up in a different environment
317
- // 3) path is absolute and touches in assoc's environment
318
-
319
- // => 1) if _parents are equal, fk path is relative to assoc
320
- if(fk._parent === assocCsn._parent) {
321
- rc = false;
322
- }
323
- // => 2) & 3) if path is not relative to assoc, remove main entity (pos=0) and assoc (pos=n-1)
324
- // and check path identity: If absolute path touches assoc's _parent, add it
325
- else if(!assocCsn.$abspath.slice(1, assocCsn.$abspath.length-1).some((p,i) => c[0][i] !== p)) {
326
- // this was an absolute addressed path, remove environment prefix
327
- c[0].splice(0, assocCsn.$abspath.length-2);
328
- rc = false;
329
- }
330
- }
331
- else {
332
- // for flat mode isConstraintCandidate(fk) && isConstraintCandidate(pk) is sufficient
333
- rc = false;
334
- }
335
- }
336
- if(!rc)
337
- remainingPrincipalRefs.push(principalEltName);
338
- return rc;
339
- },
340
- (c, cn) => { delete assocCsn._constraints.constraints[cn]; }
341
- );
292
+ (c) => {
293
+ // rc === true will remove the constraint (positive filter expression)
294
+ let rc = true;
295
+ // concatenate all paths in flat mode to identify the correct element
296
+ // in structured mode only resolve top level element (path rewriting is done elsewhere)
297
+ const depEltName = ( options.isFlatFormat ? c[0].join('_') : c[0][0] );
298
+ const principalEltName = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
299
+ const fk = (dependentEntity.kind === 'entity' && dependentEntity.elements[depEltName]) ||
300
+ (localDepEntity && localDepEntity.elements && localDepEntity.elements[depEltName]);
301
+ const pk = principalEntity.$keys && principalEntity.$keys[principalEltName];
302
+ if (isConstraintCandidate(fk) && isConstraintCandidate(pk)) {
303
+ if (options.isStructFormat) {
304
+ // In structured mode it might be the association has a new _parent due to
305
+ // type de-anonymization.
306
+ // There are three cases for dependent ON condition paths:
307
+ // 1) path is relative to assoc in same sub structure
308
+ // 2) path is absolute and ends up in a different environment
309
+ // 3) path is absolute and touches in assoc's environment
310
+
311
+ // => 1) if _parents are equal, fk path is relative to assoc
312
+ if (fk._parent === assocCsn._parent) {
313
+ rc = false;
314
+ }
315
+ // => 2) & 3) if path is not relative to assoc, remove main entity (pos=0) and assoc (pos=n-1)
316
+ // and check path identity: If absolute path touches assoc's _parent, add it
317
+ else if (!assocCsn.$abspath.slice(1, assocCsn.$abspath.length - 1).some((p, i) => c[0][i] !== p)) {
318
+ // this was an absolute addressed path, remove environment prefix
319
+ c[0].splice(0, assocCsn.$abspath.length - 2);
320
+ rc = false;
321
+ }
322
+ }
323
+ else {
324
+ // for flat mode isConstraintCandidate(fk) && isConstraintCandidate(pk) is sufficient
325
+ rc = false;
326
+ }
327
+ }
328
+ if (!rc)
329
+ remainingPrincipalRefs.push(principalEltName);
330
+ return rc;
331
+ },
332
+ (c, cn) => {
333
+ delete assocCsn._constraints.constraints[cn];
334
+ });
342
335
 
343
336
  // V2 check that ALL primary keys are constraints
344
- if(principalEntity.$keys) {
345
- const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v=>v.name);
346
- if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length)
347
- if(options.odataV2PartialConstr) {
337
+ if (principalEntity.$keys) {
338
+ const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v => v.name);
339
+ if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
340
+ if (options.odataV2PartialConstr) {
348
341
  info('odata-spec-violation-constraints',
349
- ['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { version: '2.0' });
342
+ [ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' });
350
343
  }
351
344
  else {
352
345
  assocCsn._constraints.constraints = {};
353
346
  }
347
+ }
354
348
  }
355
349
  }
356
-
357
350
  }
358
351
  // Handle managed association, a managed composition is treated as association
359
- else
360
- {
352
+ else if (!assocCsn._target.$isParamEntity && assocCsn.keys) {
361
353
  // If FK is key in target => constraint
362
354
  // Don't consider primary key associations (fks become keys on the source entity) as
363
355
  // this would impose a constraint against the target.
@@ -365,40 +357,36 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
365
357
 
366
358
  // In structured format, foreign keys of managed associations are never rendered, so
367
359
  // there are no constraints for them.
368
- if(!assocCsn._target.$isParamEntity && assocCsn.keys) {
369
- const remainingPrincipalRefs = [];
370
- for(let fk of assocCsn.keys) {
371
- let realFk = assocCsn._parent.items ? assocCsn._parent.items.elements[fk.$generatedFieldName] : assocCsn._parent.elements[fk.$generatedFieldName];
372
- let pk = assocCsn._target.elements[fk.ref[0]];
373
- if(pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk))
374
- {
375
- remainingPrincipalRefs.push(fk.ref[0]);
376
- const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
377
- const key = c.join(',');
378
- assocCsn._constraints.constraints[key] = c;
379
- }
360
+ const remainingPrincipalRefs = [];
361
+ for (const fk of assocCsn.keys) {
362
+ const realFk = assocCsn._parent.items ? assocCsn._parent.items.elements[fk.$generatedFieldName] : assocCsn._parent.elements[fk.$generatedFieldName];
363
+ const pk = assocCsn._target.elements[fk.ref[0]];
364
+ if (pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk)) {
365
+ remainingPrincipalRefs.push(fk.ref[0]);
366
+ const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
367
+ const key = c.join(',');
368
+ assocCsn._constraints.constraints[key] = c;
380
369
  }
370
+ }
381
371
 
382
- // V2 check that ALL primary keys are constraints
383
- const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v=>v.name);
384
- if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
385
- if(options.odataV2PartialConstr) {
386
- info('odata-spec-violation-constraints',
387
- ['definitions', assocCsn._parent.name, 'elements', assocCsn.name], { version: '2.0' } );
388
- }
389
- else {
390
- assocCsn._constraints.constraints = {};
391
- }
372
+ // V2 check that ALL primary keys are constraints
373
+ const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v => v.name);
374
+ if (options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length) {
375
+ if (options.odataV2PartialConstr) {
376
+ info('odata-spec-violation-constraints',
377
+ [ 'definitions', assocCsn._parent.name, 'elements', assocCsn.name ], { version: '2.0' } );
378
+ }
379
+ else {
380
+ assocCsn._constraints.constraints = {};
392
381
  }
393
382
  }
394
383
  }
395
384
 
396
385
  // If this association points to a redirected Parameter EntityType, do not calculate any constraints,
397
386
  // continue with multiplicity
398
- if(assocCsn._target.$isParamEntity)
399
- {
387
+ if (assocCsn._target.$isParamEntity)
400
388
  assocCsn._constraints.constraints = Object.create(null);
401
- }
389
+
402
390
  return assocCsn._constraints;
403
391
 
404
392
  /*
@@ -408,7 +396,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
408
396
  * type definition (alias to a scalar type).
409
397
  * The element must never be an association or composition and be renderable.
410
398
  */
411
- function isConstraintCandidate(elt) {
399
+ function isConstraintCandidate( elt ) {
412
400
  return (elt &&
413
401
  elt.type &&
414
402
  (!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
@@ -417,8 +405,7 @@ function finalizeReferentialConstraints(csn, assocCsn, options, info)
417
405
  }
418
406
  }
419
407
 
420
- function determineMultiplicity(csn)
421
- {
408
+ function determineMultiplicity( csn ) {
422
409
  /*
423
410
  => SRC Cardinality
424
411
  CDS => EDM
@@ -450,67 +437,66 @@ function determineMultiplicity(csn)
450
437
  */
451
438
 
452
439
  const isAssoc = csn.type === 'cds.Association';
453
- if(!csn.cardinality)
440
+ if (!csn.cardinality)
454
441
  csn.cardinality = Object.create(null);
455
442
 
456
- if(!csn.cardinality.src)
443
+ if (!csn.cardinality.src)
457
444
  csn.cardinality.src = isAssoc ? '*' : '1';
458
- if(!csn.cardinality.min)
445
+ if (!csn.cardinality.min)
459
446
  csn.cardinality.min = 0;
460
- if(!csn.cardinality.max)
447
+ if (!csn.cardinality.max)
461
448
  csn.cardinality.max = 1;
462
449
 
463
- const srcCardinality =
464
- (csn.cardinality.src == 1)
465
- ? (!isAssoc || csn.cardinality.srcmin == 1)
466
- ? '1'
467
- : '0..1'
450
+ const srcCardinality
451
+ = (csn.cardinality.src == 1) // eslint-disable-line eqeqeq
452
+ ? (!isAssoc || csn.cardinality.srcmin == 1) // eslint-disable-line eqeqeq
453
+ ? '1'
454
+ : '0..1'
468
455
  : '*';
469
- const tgtCardinality =
470
- (csn.cardinality.max > 1 || csn.cardinality.max === '*')
456
+ const tgtCardinality
457
+ = (csn.cardinality.max > 1 || csn.cardinality.max === '*')
471
458
  ? '*'
472
- : (csn.cardinality.min == 1)
459
+ : (csn.cardinality.min == 1) // eslint-disable-line eqeqeq
473
460
  ? '1'
474
461
  : '0..1';
475
462
 
476
- return [srcCardinality, tgtCardinality];
463
+ return [ srcCardinality, tgtCardinality ];
477
464
  }
478
465
 
479
466
  // return effective target cardinality
480
467
  // If csn is a backlink, return the source cardinality (including srcmin/src) from
481
468
  // the forward association
482
469
  // This function works only after finalizeConstraints
483
- function getEffectiveTargetCardinality(csn) {
470
+ function getEffectiveTargetCardinality( csn ) {
484
471
  const rc = { min: 0, max: 1 };
485
- if(!csn._constraints || !csn._constraints.$finalized)
486
- throw new CompilerAssertion('_constraints missing or not finalized: "' + csn.name);
472
+ if (!csn._constraints || !csn._constraints.$finalized)
473
+ throw new CompilerAssertion(`_constraints missing or not finalized: "${csn.name}`);
487
474
  // partner (forward) cardinality has precedence
488
- if(csn._constraints._partnerCsn) {
489
- if(csn._constraints._partnerCsn.cardinality?.srcmin)
475
+ if (csn._constraints._partnerCsn) {
476
+ if (csn._constraints._partnerCsn.cardinality?.srcmin)
490
477
  rc.min = csn._constraints._partnerCsn.cardinality.srcmin;
491
- if(csn._constraints._partnerCsn.cardinality?.src)
478
+ if (csn._constraints._partnerCsn.cardinality?.src)
492
479
  rc.max = csn._constraints._partnerCsn.cardinality.src;
493
480
  }
494
- else if(csn.cardinality) {
495
- if(csn.cardinality.min)
481
+ else if (csn.cardinality) {
482
+ if (csn.cardinality.min)
496
483
  rc.min = csn.cardinality.min;
497
- if(csn.cardinality.max)
498
- rc.max = csn.cardinality.max
484
+ if (csn.cardinality.max)
485
+ rc.max = csn.cardinality.max;
499
486
  }
500
487
  return rc;
501
488
  }
502
489
 
503
- function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, location=undefined)
504
- {
505
- if(location === undefined)
490
+ function mapCdsToEdmType( csn, messageFunctions, isV2 = false, isMediaType = false, location = undefined ) {
491
+ if (location === undefined)
506
492
  location = csn.$path;
507
- const { error } = messageFunctions || { error: ()=>true };
508
- let cdsType = csn.type;
509
- if(cdsType === undefined) {
493
+ const { error } = messageFunctions || { error: () => true };
494
+ const cdsType = csn.type;
495
+ if (cdsType === undefined) {
510
496
  error(null, location, 'no type found');
511
497
  return '<NOTYPE>';
512
498
  }
513
- if(!isBuiltinType(cdsType))
499
+ if (!isBuiltinType(cdsType))
514
500
  return cdsType;
515
501
 
516
502
  let edmType = {
@@ -565,29 +551,25 @@ function mapCdsToEdmType(csn, messageFunctions, isV2=false, isMediaType=false, l
565
551
  Edm.GeometryCollection
566
552
  */
567
553
  }[cdsType];
568
- if (edmType == undefined) {
554
+ if (!edmType)
569
555
  error(null, location, { type: cdsType }, 'No EDM type available for $(TYPE)');
570
- }
571
- if(isV2)
572
- {
556
+
557
+ if (isV2) {
573
558
  if (edmType === 'Edm.Date')
574
559
  edmType = 'Edm.DateTime';
575
560
  if (edmType === 'Edm.TimeOfDay')
576
561
  edmType = 'Edm.Time';
577
562
  }
578
- else // isV4
579
- {
580
- // CDXCORE-CDXCORE-173
581
- if(isMediaType)
582
- edmType = 'Edm.Stream';
563
+ else if (isMediaType) { // isV4
564
+ // CDXCORE-CDXCORE-173
565
+ edmType = 'Edm.Stream';
583
566
  }
584
567
  return edmType;
585
568
  }
586
569
 
587
- function addTypeFacets(node, csn)
588
- {
570
+ function addTypeFacets( node, csn ) {
589
571
  const isV2 = node.v2;
590
- const decimalTypes = {'cds.Decimal':1, 'cds.DecimalFloat':1, 'cds.hana.SMALLDECIMAL':1};
572
+ const decimalTypes = { 'cds.Decimal': 1, 'cds.DecimalFloat': 1, 'cds.hana.SMALLDECIMAL': 1 };
591
573
  if (csn.length != null)
592
574
  node.setEdmAttribute('MaxLength', csn.length);
593
575
  if (csn.scale !== undefined)
@@ -601,32 +583,32 @@ function addTypeFacets(node, csn)
601
583
  // node.Precision = 16;
602
584
  else if (csn.type === 'cds.Timestamp' && node._edmAttributes.Type === 'Edm.DateTimeOffset')
603
585
  node.setEdmAttribute('Precision', 7);
604
- if(csn.type in decimalTypes) {
605
- if(isV2) {
586
+ if (csn.type in decimalTypes) {
587
+ if (isV2) {
606
588
  // no prec/scale or scale is 'floating'/'variable'
607
- if(!(csn.precision || csn.scale) || (csn.scale === 'floating' || csn.scale === 'variable')) {
589
+ if (!(csn.precision || csn.scale) || (csn.scale === 'floating' || csn.scale === 'variable')) {
608
590
  node.setXml( { 'sap:variable-scale': true } );
609
591
  node.removeEdmAttribute('Scale');
610
592
  }
611
593
  }
612
594
  else {
613
595
  // map both floating and variable to => variable
614
- if(node._edmAttributes.Scale === 'floating')
596
+ if (node._edmAttributes.Scale === 'floating')
615
597
  node.setEdmAttribute('Scale', 'variable');
616
- if(!csn.precision && !csn.scale)
598
+ if (csn.precision == null && csn.scale == null)
617
599
  // if Decimal has no p, s set scale 'variable'
618
600
  node.setXml( { Scale: 'variable' } ); // floating is V4.01
619
601
  }
620
602
  }
621
603
  // Unicode unused today
622
- if(csn.unicode)
604
+ if (csn.unicode)
623
605
  node.setEdmAttribute('Unicode', csn.unicode);
624
- if(csn.srid)
606
+ if (csn.srid)
625
607
  node.setEdmAttribute('SRID', csn.srid);
626
608
  }
627
609
 
628
610
 
629
- /**
611
+ /**
630
612
  * A simple identifier is a Unicode character sequence with the following restrictions:
631
613
  * - The first character MUST be the underscore character (U+005F) or any character in the Unicode category “Letter (L)” or “Letter number (Nl)”
632
614
  * - The remaining characters MUST be the underscore character (U+005F) or any character in the Unicode category:
@@ -641,9 +623,9 @@ function addTypeFacets(node, csn)
641
623
  *
642
624
  * @param {string} identifier
643
625
  */
644
- function isODataSimpleIdentifier(identifier){
626
+ function isODataSimpleIdentifier( identifier ) {
645
627
  // this regular expression reflects the specification from above
646
- const regex = /^[\p{Letter}\p{Nl}_][_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu
628
+ const regex = /^[\p{Letter}\p{Nl}_][_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu;
647
629
  return identifier && identifier.match(regex);
648
630
  }
649
631
 
@@ -669,7 +651,7 @@ function isODataSimpleIdentifier(identifier){
669
651
  * @param {string} str
670
652
  * @returns {string}
671
653
  */
672
- function escapeStringForAttributeValue(str) {
654
+ function escapeStringForAttributeValue( str ) {
673
655
  if (typeof str !== 'string')
674
656
  return str;
675
657
 
@@ -718,7 +700,7 @@ function escapeStringForAttributeValue(str) {
718
700
  * @param {string} str
719
701
  * @returns {string}
720
702
  */
721
- function escapeStringForText(str) {
703
+ function escapeStringForText( str ) {
722
704
  if (typeof str !== 'string')
723
705
  return str;
724
706
 
@@ -749,70 +731,70 @@ function escapeStringForText(str) {
749
731
  * @param {number} codePoint
750
732
  * @returns {string}
751
733
  */
752
- function encodeNonCharacters(codePoint) {
734
+ function encodeNonCharacters( codePoint ) {
753
735
  const hex = codePoint.toString(16).toUpperCase();
754
736
  return `&#x${hex};`;
755
737
  }
756
738
 
757
739
  // return the path prefix of a given name or if no prefix available 'root'
758
- function getSchemaPrefix(name) {
740
+ function getSchemaPrefix( name ) {
759
741
  const lastDotIdx = name.lastIndexOf('.');
760
742
  return (lastDotIdx > 0 ) ? name.substring(0, lastDotIdx) : 'root';
761
743
  }
762
744
 
763
745
  // get artifacts base name
764
- function getBaseName(name) {
746
+ function getBaseName( name ) {
765
747
  const lastDotIdx = name.lastIndexOf('.');
766
- return (lastDotIdx > 0 ) ? name.substring(lastDotIdx+1, name.length) : name;
748
+ return (lastDotIdx > 0 ) ? name.substring(lastDotIdx + 1, name.length) : name;
767
749
  }
768
750
 
769
751
  // This is a poor mans path resolver for $self partner paths only
770
- function resolveOriginAssoc(csn, env, path) {
771
- for(const segment of path) {
772
- let elements = (env.items && env.items.elements || env.elements);
773
- if(elements)
774
- env = env.elements[segment];
775
- let type = (env.items && env.items.type || env.type);
776
- if(type && !isBuiltinType(type) && !(env.items && env.items.elements || env.elements))
777
- env = csn.definitions[env.type];
752
+ function resolveOriginAssoc( csn, env, path ) {
753
+ for (const segment of path) {
754
+ const elements = (env?.items?.elements || env?.elements);
755
+ if (elements)
756
+ env = elements[segment];
757
+ const type = (env?.items?.type || env?.type);
758
+ if (type && !isBuiltinType(type) && !(env?.items?.elements || env?.elements))
759
+ env = csn.definitions[type];
778
760
  }
779
761
  return env;
780
762
  }
781
763
 
782
- function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
764
+ function mergeIntoNavPropEntry( annoPrefix, navPropEntry, prefix, props ) {
783
765
  let newEntry = false;
784
766
 
785
767
  // Filter properties with prefix and reduce them into a new dictionary
786
- const o = props.filter(p => p[0].startsWith(annoPrefix+'.')).reduce((a,c) => {
768
+ const o = props.filter(p => p[0].startsWith(`${annoPrefix}.`)).reduce((a, c) => {
787
769
  // clone the annotation value to avoid side effects with rewritten paths
788
- a[c[0].replace(annoPrefix+'.', '')] = cloneAnnotationValue(c[1]);
770
+ a[c[0].replace(`${annoPrefix}.`, '')] = cloneAnnotationValue(c[1]);
789
771
  return a;
790
772
  }, { });
791
773
 
792
774
  // BEFORE merging found capabilities, prefix the paths
793
- applyTransformations({ definitions: { o }}, {
775
+ applyTransformations({ definitions: { o } }, {
794
776
  '=': (parent, prop, value) => {
795
777
  parent[prop] = prefix.concat(value).join('.');
796
- }
778
+ },
797
779
  });
798
780
  // don't overwrite existing restrictions
799
781
  const prop = annoPrefix.split('.')[1];
800
- if(!navPropEntry[prop]) {
782
+ if (!navPropEntry[prop]) {
801
783
  // if dictionary has entries, add them to navPropEntry
802
- if(Object.keys(o).length) {
784
+ if (Object.keys(o).length) {
803
785
  // ReadRestrictions may have sub type ReadByKeyRestrictions { Description, LongDescription }
804
786
  // chop annotations into dictionaries
805
- if(annoPrefix === '@Capabilities.ReadRestrictions' &&
787
+ if (annoPrefix === '@Capabilities.ReadRestrictions' &&
806
788
  Object.keys(o).some(k => k.startsWith('ReadByKeyRestrictions.'))) {
807
789
  const no = {};
808
- Object.entries(o).forEach(([k,v]) => {
809
- const [head, ...tail] = k.split('.');
810
- if(head === 'ReadByKeyRestrictions' && tail.length) {
811
- if(!no['ReadByKeyRestrictions'])
812
- no['ReadByKeyRestrictions'] = {};
813
- // Don't try to add entry into non object
814
- if(typeof no['ReadByKeyRestrictions'] === 'object')
815
- no['ReadByKeyRestrictions'][tail.join('.')] = v;
790
+ Object.entries(o).forEach(([ k, v ]) => {
791
+ const [ head, ...tail ] = k.split('.');
792
+ if (head === 'ReadByKeyRestrictions' && tail.length) {
793
+ if (!no.ReadByKeyRestrictions)
794
+ no.ReadByKeyRestrictions = {};
795
+ // Don't try to add entry into non object
796
+ if (typeof no.ReadByKeyRestrictions === 'object')
797
+ no.ReadByKeyRestrictions[tail.join('.')] = v;
816
798
  }
817
799
  else {
818
800
  no[k] = v;
@@ -828,8 +810,8 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
828
810
  }
829
811
  else {
830
812
  // merge but don't overwrite into existing navprop
831
- Object.entries(o).forEach(([k,v]) => {
832
- if(!navPropEntry[prop][k])
813
+ Object.entries(o).forEach(([ k, v ]) => {
814
+ if (!navPropEntry[prop][k])
833
815
  navPropEntry[prop][k] = v;
834
816
  });
835
817
  }
@@ -837,57 +819,54 @@ function mergeIntoNavPropEntry(annoPrefix, navPropEntry, prefix, props) {
837
819
  }
838
820
 
839
821
  // Assign but not overwrite annotation
840
- function assignAnnotation(node, name, value) {
841
- if(value !== undefined &&
842
- name !== undefined && name[0] === '@' &&
843
- (node[name] === undefined || node[name] === null)) {
844
- node[name] = value;
845
- }
822
+ function assignAnnotation( node, name, value ) {
823
+ if (value !== undefined &&
824
+ name !== undefined && name[0] === '@')
825
+ node[name] ??= value;
846
826
  }
847
827
 
848
828
  // Set non enumerable property if it doesn't exist yet
849
- function assignProp(obj, prop, value) {
850
- if(obj[prop] === undefined) {
829
+ function assignProp( obj, prop, value ) {
830
+ if (obj[prop] === undefined)
851
831
  setProp(obj, prop, value);
852
- }
853
832
  }
854
833
 
855
834
  //
856
- // create Cross Schema Reference object
857
- //
858
- function createSchemaRef(serviceRoots, targetSchemaName) {
859
- // prepend as many path ups '..' as there are path steps in the service ref
860
- let serviceRef = path4(serviceRoots[targetSchemaName]).split('/').filter(c=>c.length);
835
+ // create Cross Schema Reference object
836
+ //
837
+ function createSchemaRef( serviceRoots, targetSchemaName ) {
838
+ // prepend as many path ups '..' as there are path steps in the service ref
839
+ const serviceRef = path4(serviceRoots[targetSchemaName]).split('/').filter(c => c.length);
861
840
  serviceRef.splice(0, 0, ...Array(serviceRef.length).fill('..'));
862
- // uncomment this to make $metadata absolute
863
- // if(serviceRef.length===0)
864
- // serviceRef.push('');
865
- if(serviceRef[serviceRef.length-1] !== '$metadata')
841
+ // uncomment this to make $metadata absolute
842
+ // if(serviceRef.length===0)
843
+ // serviceRef.push('');
844
+ if (serviceRef[serviceRef.length - 1] !== '$metadata')
866
845
  serviceRef.push('$metadata');
867
- let sc = { kind: 'reference',
846
+ const sc = {
847
+ kind: 'reference',
868
848
  name: targetSchemaName,
869
849
  ref: { Uri: serviceRef.join('/') },
870
- inc: { Namespace: targetSchemaName }
850
+ inc: { Namespace: targetSchemaName },
871
851
  };
872
852
  setProp(sc, '$mySchemaName', targetSchemaName);
873
853
  return sc;
874
854
 
875
- /**
855
+ /**
876
856
  * Resolve a service endpoint path to mount it to as follows...
877
857
  * Use _path or def[@path] if given (and remove leading '/')
878
858
  * Otherwise, use the service definition name with stripped 'Service'
879
859
  */
880
- function path4 (def, _path = def['@path']) {
860
+ function path4( def, _path = def['@path'] ) {
881
861
  if (_path)
882
862
  return _path.replace(/^\//, '');
883
- else
884
- return ( // generate one from the service's name
885
- /[^.]+$/.exec(def.name)[0] //> my.very.CatalogService --> CatalogService
886
- .replace(/Service$/,'') //> CatalogService --> Catalog
887
- .replace(/([a-z0-9])([A-Z])/g, (_,c,C) => c+'-'+C.toLowerCase()) //> ODataFooBarX9 --> odata-foo-bar-x9
888
- .replace(/_/g,'-') //> foo_bar_baz --> foo-bar-baz
889
- .toLowerCase() //> FOO --> foo
890
- )
863
+ return ( // generate one from the service's name
864
+ /[^.]+$/.exec(def.name)[0] // > my.very.CatalogService --> CatalogService
865
+ .replace(/Service$/, '') // > CatalogService --> Catalog
866
+ .replace(/([a-z0-9])([A-Z])/g, (_, c, C) => `${c}-${C.toLowerCase()}`) // > ODataFooBarX9 --> odata-foo-bar-x9
867
+ .replace(/_/g, '-') // > foo_bar_baz --> foo-bar-baz
868
+ .toLowerCase() // > FOO --> foo
869
+ );
891
870
  }
892
871
  }
893
872
 
@@ -918,5 +897,5 @@ module.exports = {
918
897
  escapeStringForText,
919
898
  getSchemaPrefix,
920
899
  getBaseName,
921
- mergeIntoNavPropEntry
922
- }
900
+ mergeIntoNavPropEntry,
901
+ };