@sap/cds-compiler 2.11.2 → 2.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -6,6 +6,8 @@
6
6
  // Please do not add functions “for completeness”, this is not an API file for
7
7
  // others but only by the core compiler.
8
8
 
9
+ // TODO: probably split this file into utils/….js
10
+
9
11
  'use strict';
10
12
 
11
13
  const { dictAdd, pushToDict } = require('../base/dictionaries');
@@ -30,6 +32,15 @@ function annotationIsFalse( anno ) { // falsy, but not null (u
30
32
  return anno && (anno.val === false || anno.val === 0 || anno.val === '');
31
33
  }
32
34
 
35
+ /**
36
+ * Set compiler-calculated annotation value.
37
+ *
38
+ * @param {XSN.Artifact} art
39
+ * @param {string} anno
40
+ * @param {XSN.Location} [location]
41
+ * @param {*} [val]
42
+ * @param {string} [literal]
43
+ */
33
44
  function annotateWith( art, anno, location = art.location, val = true, literal = 'boolean' ) {
34
45
  if (art[anno]) // do not overwrite user-defined including null
35
46
  return;
@@ -37,11 +48,11 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
37
48
  name: { path: [ { id: anno.slice(1), location } ], location },
38
49
  val,
39
50
  literal,
51
+ $inferred: '$generated',
40
52
  location,
41
53
  };
42
54
  }
43
55
 
44
- // TODO: define setLink() like the current setProp(), we might have setArtifactLink()
45
56
  // Do not share this function with CSN processors!
46
57
 
47
58
  // The link (_artifact,_effectiveType,...) usually has the artifact as value.
@@ -50,28 +61,13 @@ function annotateWith( art, anno, location = art.location, val = true, literal =
50
61
  // - null: no valid reference, param:true if that is not allowed
51
62
  // - false (only complete ref): multiple definitions, rejected
52
63
  // - 0 (for _effectiveType only): circular reference
53
- function setLink( obj, value = null, prop = '_artifact' ) {
64
+ function setLink( obj, prop, value ) {
54
65
  Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
55
66
  return value;
56
67
  }
57
-
58
- /**
59
- * Like `obj.prop = value`, but not contained in JSON / CSN
60
- * It's important to set enumerable explicitly to false (although 'false' is the default),
61
- * as else, if the property already exists, it keeps the old setting for enumerable.
62
- *
63
- * @param {object} obj
64
- * @param {string} prop
65
- * @param {any} value
66
- */
67
- function setProp(obj, prop, value) {
68
- const descriptor = {
69
- value,
70
- configurable: true,
71
- writable: true,
72
- enumerable: false,
73
- };
74
- Object.defineProperty( obj, prop, descriptor );
68
+ // And a variant with the most common `prop`:
69
+ function setArtifactLink( obj, value ) {
70
+ Object.defineProperty( obj, '_artifact', { value, configurable: true, writable: true } );
75
71
  return value;
76
72
  }
77
73
 
@@ -85,7 +81,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
85
81
  elem.name.$inferred = origin.name.$inferred;
86
82
  if (parent)
87
83
  setMemberParent( elem, name, parent, prop ); // TODO: redef in template
88
- setProp( elem, '_origin', origin );
84
+ setLink( elem, '_origin', origin );
89
85
  // TODO: should we use silent dependencies also for other things, like
90
86
  // included elements? (Currently for $inferred: 'expand-element' only)
91
87
  if (silentDep)
@@ -103,20 +99,21 @@ function setMemberParent( elem, name, parent, prop ) {
103
99
  p[prop] = Object.create(null);
104
100
  dictAdd( p[prop], name, elem );
105
101
  }
106
- if (parent._outer)
102
+ if (parent._outer && parent._outer.items) // TODO: remove for items, too
107
103
  parent = parent._outer;
108
- setProp( elem, '_parent', parent );
109
- setProp( elem, '_main', parent._main || parent );
110
- elem.name.absolute = elem._main.name.absolute;
104
+ setLink( elem, '_parent', parent );
105
+ setLink( elem, '_main', parent._main || parent );
106
+ const parentName = parent.name || parent._outer.name;
107
+ elem.name.absolute = parentName.absolute;
111
108
  if (name == null)
112
109
  return;
113
110
  const normalized = kindProperties[elem.kind].normalized || elem.kind;
114
111
  [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
115
112
  if (normalized === kind)
116
- elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
113
+ elem.name[kind] = (parentName[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parentName[kind] }.${ name }` : name;
117
114
 
118
- else if (parent.name[kind] != null)
119
- elem.name[kind] = parent.name[kind];
115
+ else if (parentName[kind] != null)
116
+ elem.name[kind] = parentName[kind];
120
117
 
121
118
  else
122
119
  delete elem.name[kind];
@@ -133,7 +130,7 @@ function setMemberParent( elem, name, parent, prop ) {
133
130
  */
134
131
  function dependsOn( user, art, location ) {
135
132
  if (!user._deps)
136
- setProp( user, '_deps', [] );
133
+ setLink( user, '_deps', [] );
137
134
  user._deps.push( { art, location } );
138
135
  }
139
136
 
@@ -146,17 +143,17 @@ function dependsOn( user, art, location ) {
146
143
  */
147
144
  function dependsOnSilent( user, art ) {
148
145
  if (!user._deps)
149
- setProp( user, '_deps', [] );
146
+ setLink( user, '_deps', [] );
150
147
  user._deps.push( { art } );
151
148
  }
152
149
 
153
150
  function storeExtension( elem, name, prop, parent, block ) {
154
151
  if (prop === 'enum')
155
152
  prop = 'elements';
156
- setProp( elem, '_block', block );
153
+ setLink( elem, '_block', block );
157
154
  const kind = `_${ elem.kind }`; // _extend or _annotate
158
155
  if (!parent[kind])
159
- setProp( parent, kind, {} );
156
+ setLink( parent, kind, {} );
160
157
  // if (name === '' && prop === 'params') {
161
158
  // pushToDict( parent[kind], 'returns', elem ); // not really a dict
162
159
  // return;
@@ -181,6 +178,16 @@ function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = fa
181
178
  return false;
182
179
  }
183
180
 
181
+ /**
182
+ * Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
183
+ * locations).
184
+ *
185
+ * @param {XSN.Path} path
186
+ */
187
+ function pathName(path) {
188
+ return (path.broken) ? '' : path.map( id => id.id ).join('.');
189
+ }
190
+
184
191
  /**
185
192
  * Generates an XSN path out of the given name. Path segments are delimited by a dot.
186
193
  * Each segment will have the given location assigned.
@@ -201,6 +208,174 @@ function augmentPath( location, ...args ) {
201
208
  return { path: args.map( id => ({ id, location }) ), location };
202
209
  }
203
210
 
211
+ function copyExpr( expr, location, skipUnderscored, rewritePath ) {
212
+ if (!expr || typeof expr !== 'object')
213
+ return expr;
214
+ else if (Array.isArray(expr))
215
+ return expr.map( e => copyExpr( e, location, skipUnderscored, rewritePath ) );
216
+
217
+ const proto = Object.getPrototypeOf( expr );
218
+ if (proto && proto !== Object.prototype) // do not copy object from special classes
219
+ return expr;
220
+ const r = Object.create( proto );
221
+ for (const prop of Object.getOwnPropertyNames( expr )) {
222
+ const pd = Object.getOwnPropertyDescriptor( expr, prop );
223
+ if (!pd.enumerable) { // should include all properties starting with _
224
+ if (!skipUnderscored ||
225
+ prop === '_artifact' || prop === '_navigation' || prop === '_effectiveType')
226
+ Object.defineProperty( r, prop, pd );
227
+ }
228
+ else if (!proto) {
229
+ r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
230
+ }
231
+ else if (prop === 'location') {
232
+ r[prop] = location || pd.value;
233
+ }
234
+ else if (prop.charAt(0) !== '$' || prop === '$inferred') {
235
+ r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
236
+ }
237
+ else if (!skipUnderscored) { // skip $ properties
238
+ Object.defineProperty( r, prop, pd );
239
+ }
240
+ }
241
+ return r;
242
+ }
243
+
244
+ function testExpr( expr, pathTest, queryTest ) {
245
+ // TODO: also check path arguments/filters
246
+ if (!expr || typeof expr === 'string') { // parse error or keywords in {xpr:...}
247
+ return false;
248
+ }
249
+ else if (Array.isArray(expr)) {
250
+ return expr.some( e => testExpr( e, pathTest, queryTest ) );
251
+ }
252
+ else if (expr.path) {
253
+ return pathTest( expr );
254
+ }
255
+ else if (expr.query) {
256
+ return queryTest( expr.query );
257
+ }
258
+ else if (expr.op && expr.args) {
259
+ // unnamed args => array
260
+ if (Array.isArray(expr.args))
261
+ return expr.args.some( e => testExpr( e, pathTest, queryTest ) );
262
+ // named args => dictionary
263
+ for (const namedArg of Object.keys(expr.args)) {
264
+ if (testExpr(expr.args[namedArg], pathTest, queryTest))
265
+ return true;
266
+ }
267
+ }
268
+ return false;
269
+ }
270
+
271
+ // Return true if the path `item` with a final type `assoc` has a max target
272
+ // cardinality greater than one - either specified on the path item or assoc type.
273
+ function targetMaxNotOne( assoc, item ) {
274
+ // Semantics of associations without provided cardinality: [*,0..1]
275
+ const cardinality = item.cardinality || assoc.cardinality;
276
+ return cardinality && cardinality.targetMax && cardinality.targetMax.val !== 1;
277
+ }
278
+
279
+ // Query tree post-order traversal - called for everything which contributes to the query
280
+ // i.e. is necessary to calculate the elements of the query
281
+ // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
282
+ // NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
283
+ function traverseQueryPost( query, simpleOnly, callback ) {
284
+ if (!query) // parser error
285
+ return;
286
+ if (!query.op) { // in FROM (not JOIN)
287
+ if (query.query) // subquery
288
+ traverseQueryPost( query.query, simpleOnly, callback );
289
+ return;
290
+ }
291
+ if (simpleOnly) {
292
+ const { from } = query;
293
+ if (!from || from.join) // parse error or join
294
+ return; // ok are: path or simple sub query (!)
295
+ }
296
+ if (query.from) { // SELECT
297
+ traverseQueryPost( query.from, simpleOnly, callback );
298
+ // console.log('FC:')
299
+ callback( query );
300
+ // console.log('FE:')
301
+ }
302
+ else if (query.args) { // JOIN, UNION, INTERSECT
303
+ if (!query.join && simpleOnly == null) {
304
+ // enough for elements: traverse only first args for UNION/INTERSECT
305
+ // TODO: we might use this also when we do not rewrite associations
306
+ // in non-referred sub queries
307
+ traverseQueryPost( query.args[0], simpleOnly, callback );
308
+ }
309
+ else {
310
+ for (const q of query.args)
311
+ traverseQueryPost( q, simpleOnly, callback );
312
+ // The ON condition has to be traversed extra, because it must be evaluated
313
+ // after the complete FROM has been traversed. It is also not necessary to
314
+ // evaluate it in populateQuery().
315
+ }
316
+ }
317
+ // else: with parse error (`select from <EOF>`, `select distinct from;`)
318
+ }
319
+
320
+ // Call callback on all queries in dependency order, i.e. starting with query Q
321
+ // 1. sub queries in FROM sources of Q
322
+ // 2. Q itself, except if non-referred query, but with right UNION parts
323
+ // 3. sub queries in ON in FROM of Q
324
+ // 4. sub queries in columns, WHERE, HAVING
325
+ function traverseQueryExtra( main, callback ) {
326
+ if (!main.$queries)
327
+ return;
328
+ // with a top-level UNION, $queries[0] is just the left
329
+ traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
330
+ setLink( q, '_status', 'extra' );
331
+ callback( q );
332
+ } );
333
+ for (const query of main.$queries.slice(1)) {
334
+ if (query._status === 'extra' || query._parent.kind === '$tableAlias')
335
+ continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
336
+ // we are now in the top-level (parent is entity) or a non-referred query (parent is query)
337
+ setLink( query, '_status', 'extra' ); // do not call callback() in non-referred query
338
+ // console.log( 'A:', query.name,query._status)
339
+ traverseQueryPost( query, null, (q) => {
340
+ if (q._status !== 'extra') {
341
+ // console.log( 'T:', q.name)
342
+ setLink( q, '_status', 'extra' );
343
+ callback( q );
344
+ }
345
+ // else console.log( 'E:', q.name)
346
+ } );
347
+ }
348
+ }
349
+
350
+ // About Helper property $expand for faster the XSN-to-CSN transformation
351
+ // - null/undefined: artifact, member, items does not contain expanded members
352
+ // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
353
+ // that value is only on elements, types, and params -> no other members
354
+ // when set, only on elem/art with expanded elements
355
+ // - 'target': all expanded (sub) elements might only have new target/on, but
356
+ // no indivual annotations on any (sub) member
357
+ // when set, traverse all parents where the value has been 'origin' before
358
+ // - 'annotate': at least one inferred (sub) member has an individual annotation,
359
+ // not counting propagated ones; set up to the definition (main artifact)
360
+ // (only set with anno on $inferred elem), annotate “beats” target
361
+ // Usage according to CSN flavor:
362
+ // - gensrc: do not render inferred elements (including expanded elements),
363
+ // collect annotate statements with value 'annotate'
364
+ // - client: do not render expanded sub elements if artifact/member is no type, has a type,
365
+ // has $expand = 'origin', and all its _origin also have $expand = 'origin'
366
+ // (might sometimes render the elements unnecessarily, which is not wrong)
367
+ // - universal: do not render expanded sub elements if $expand = 'origin'
368
+ function setExpandStatus( elem, status ) {
369
+ // set on element
370
+ while (elem._main) {
371
+ elem = elem._parent;
372
+ if (status === 'annotate' ? elem.$expand === 'annotate' : elem.$expand !== 'origin')
373
+ return;
374
+ elem.$expand = status; // meaning: expanded, containing assocs
375
+ for (let line = elem.items; line; line = line.items)
376
+ line.$expand = status; // to-csn just uses the innermost $expand
377
+ }
378
+ }
204
379
 
205
380
  module.exports = {
206
381
  pushLink,
@@ -208,13 +383,20 @@ module.exports = {
208
383
  annotationIsFalse,
209
384
  annotateWith,
210
385
  setLink,
211
- setProp,
386
+ setArtifactLink,
212
387
  linkToOrigin,
213
388
  dependsOn,
214
389
  dependsOnSilent,
215
390
  setMemberParent,
216
391
  storeExtension,
217
392
  withAssociation,
393
+ pathName,
218
394
  augmentPath,
219
395
  splitIntoPath,
396
+ copyExpr,
397
+ testExpr,
398
+ targetMaxNotOne,
399
+ traverseQueryPost,
400
+ traverseQueryExtra,
401
+ setExpandStatus,
220
402
  };
@@ -0,0 +1,5 @@
1
+ {
2
+ "rules": {
3
+ "no-shadow": "off"
4
+ }
5
+ }
@@ -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
  }
@@ -504,9 +504,18 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
504
504
  let hasAlternativeCarrier = false; // is the alternative annotation target available in the EDM?
505
505
  let testToAlternativeEdmTarget = null; // if true, assign to alternative Edm Target
506
506
 
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'));
507
+ if (carrier.kind === 'entity') {
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';
@@ -932,7 +948,7 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
932
948
  if (typeof val === 'string') {
933
949
  if (dTypeName === 'Edm.Boolean') {
934
950
  typeName = 'Bool';
935
- if (!['true','false'].includes(val)) {
951
+ if (val !== 'true' && val !== 'false') {
936
952
  message(warning, context, `found String, but expected type ${ dTypeName }`);
937
953
  }
938
954
  }
@@ -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 );
@@ -1267,14 +1284,10 @@ function csn2annotationEdm(csn, serviceName, Edm = undefined, options=undefined,
1267
1284
  });
1268
1285
  }
1269
1286
  else { // literal
1270
- let escaped = obj;
1271
- if (typeof escaped === 'string') {
1272
- escaped = escaped.replace(/&/g, '&amp;')
1273
- }
1274
1287
  edmNode = new Edm.ValueThing(v,
1275
- exprDef && exprDef.valueThingName || getXmlTypeName(escaped), escaped);
1288
+ exprDef && exprDef.valueThingName || getXmlTypeName(obj), obj);
1276
1289
  // typename for static expression rendering
1277
- edmNode.setJSON( { [getJsonTypeName(escaped)]: escaped } );
1290
+ edmNode.setJSON( { [getJsonTypeName(obj)]: obj } );
1278
1291
  }
1279
1292
  }
1280
1293
  }
@@ -101,7 +101,7 @@ function preprocessAnnotations(csn, serviceName, options) {
101
101
 
102
102
  // inner functions
103
103
  function draftAnnotations(carrier, aName, aNameWithoutQualifier) {
104
- if ((carrier.kind === 'entity' || carrier.kind === 'view') &&
104
+ if ((carrier.kind === 'entity') &&
105
105
  (aNameWithoutQualifier === '@Common.DraftRoot.PreparationAction' ||
106
106
  aNameWithoutQualifier === '@Common.DraftRoot.ActivationAction' ||
107
107
  aNameWithoutQualifier === '@Common.DraftRoot.EditAction' ||
@@ -136,7 +136,7 @@ function preprocessAnnotations(csn, serviceName, options) {
136
136
  return false;
137
137
  }
138
138
 
139
- if (carrier.kind === 'entity' || carrier.kind === 'view') {
139
+ if (carrier.kind === 'entity') {
140
140
  warning(null, null, `annotation preprocessing/${aNameWithoutQualifier}: annotation must not be used for an entity, ${ctx}`);
141
141
  return false;
142
142
  }
@@ -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
 
@@ -45,6 +45,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
45
45
  let [ allServices,
46
46
  allSchemas,
47
47
  whatsMyServiceRootName,
48
+ autoexposeSchemaName,
48
49
  options ] = initializeModel(csn, _options, messageFunctions);
49
50
 
50
51
  const Edm = require('./edm.js')(options, error);
@@ -188,9 +189,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
188
189
  fqSchemaXRef.sort((a,b) => b.length-a.length);
189
190
 
190
191
  // Bring the schemas in alphabetical order, service first, root last
191
- const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== 'root' && n !== serviceCsn.name).sort();
192
- if(subSchemaDictionary.root)
193
- sortedSchemaNames.push('root');
192
+ const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
193
+ if(subSchemaDictionary[autoexposeSchemaName])
194
+ sortedSchemaNames.push(autoexposeSchemaName);
194
195
 
195
196
  // Finally create the schemas and register them in the service.
196
197
  LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
@@ -234,7 +235,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
234
235
  Object.entries(csn.definitions).forEach(([fqName, art]) => {
235
236
  // Identify service members by their definition name only, this allows
236
237
  // to let the internal object.name have the sub-schema name.
237
- // With nested services we must do a longest path match and check wether
238
+ // With nested services we must do a longest path match and check whether
238
239
  // the current definition belongs to the current toplevel service definition.
239
240
 
240
241
  // Definition is to be considered if
@@ -246,7 +247,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
246
247
  // not marked to be ignored as schema member
247
248
  if(mySchemaName &&
248
249
  serviceCsn.name === whatsMyServiceRootName(fqName, false) &&
249
- ![ 'context', 'service' ].includes(art.kind)) {
250
+ art.kind !== 'context' && art.kind !== 'service') {
250
251
 
251
252
  // Strip the toplevel serviceName from object.name
252
253
  // except if the schema name is the service name itself.
@@ -420,7 +421,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
420
421
  /** @type {object} */
421
422
  let containerEntry;
422
423
 
423
- if(edmUtils.isSingleton(entityCsn, options.isV4())) {
424
+ if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
424
425
  containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
425
426
  if(entityCsn['@odata.singleton.nullable'])
426
427
  containerEntry.Nullable= true;
@@ -574,7 +575,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
574
575
  edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
575
576
  const paramLoc = [...actLoc, 'params', parameterName];
576
577
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
577
- if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
578
+ if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
578
579
  warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });
579
580
  }
580
581
  if(param._isCollection) {
@@ -591,8 +592,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
591
592
  // it is safe to assume that either type or items.type are set
592
593
  const returns = action.returns.items || action.returns;
593
594
  let type = returns.type;
594
- if(type){
595
- if(!isBuiltinType(type) && !['entity', 'view', 'type'].includes(csn.definitions[type].kind)){
595
+ if (type){
596
+ if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
596
597
  const returnsLoc = [ ...actLoc, 'returns'];
597
598
  warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
598
599
  }