@sap/cds-compiler 6.9.3 → 7.0.1

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 (69) hide show
  1. package/CHANGELOG.md +76 -2
  2. package/bin/cdsc.js +4 -33
  3. package/doc/IncompatibleChanges_v7.md +639 -0
  4. package/lib/api/main.js +4 -56
  5. package/lib/api/options.js +5 -15
  6. package/lib/api/validate.js +1 -0
  7. package/lib/base/builtins.js +1 -2
  8. package/lib/base/csnRefs.js +2 -6
  9. package/lib/base/message-registry.js +82 -76
  10. package/lib/base/messages.js +8 -5
  11. package/lib/base/optionProcessor.js +2 -72
  12. package/lib/base/specialOptions.js +20 -17
  13. package/lib/checks/defaultValues.js +1 -39
  14. package/lib/checks/hasPersistedElements.js +19 -3
  15. package/lib/checks/parameters.js +0 -34
  16. package/lib/checks/selectItems.js +2 -38
  17. package/lib/checks/typeParameters.js +162 -0
  18. package/lib/checks/validator.js +5 -8
  19. package/lib/compiler/assert-consistency.js +19 -5
  20. package/lib/compiler/checks.js +47 -43
  21. package/lib/compiler/define.js +6 -6
  22. package/lib/compiler/extend.js +102 -111
  23. package/lib/compiler/generate.js +4 -8
  24. package/lib/compiler/populate.js +4 -7
  25. package/lib/compiler/propagator.js +9 -9
  26. package/lib/compiler/resolve.js +205 -7
  27. package/lib/compiler/shared.js +76 -82
  28. package/lib/compiler/tweak-assocs.js +102 -22
  29. package/lib/compiler/utils.js +57 -12
  30. package/lib/compiler/xpr-rewrite.js +2 -15
  31. package/lib/edm/annotations/edmJson.js +14 -10
  32. package/lib/edm/annotations/genericTranslation.js +3 -1
  33. package/lib/edm/annotations/preprocessAnnotations.js +9 -26
  34. package/lib/edm/csn2edm.js +27 -20
  35. package/lib/edm/edmUtils.js +25 -0
  36. package/lib/gen/CdlGrammar.checksum +1 -1
  37. package/lib/gen/CdlParser.js +2237 -2241
  38. package/lib/gen/Dictionary.json +17 -2
  39. package/lib/json/from-csn.js +67 -52
  40. package/lib/json/to-csn.js +28 -25
  41. package/lib/language/textUtils.js +0 -13
  42. package/lib/main.d.ts +22 -59
  43. package/lib/main.js +1 -1
  44. package/lib/model/csnUtils.js +9 -8
  45. package/lib/parsers/AstBuildingParser.js +45 -55
  46. package/lib/parsers/Lexer.js +2 -0
  47. package/lib/parsers/identifiers.js +0 -9
  48. package/lib/render/toCdl.js +41 -40
  49. package/lib/render/toSql.js +8 -1
  50. package/lib/render/utils/common.js +1 -1
  51. package/lib/render/utils/sql.js +2 -3
  52. package/lib/tool-lib/enrichCsn.js +1 -2
  53. package/lib/transform/db/applyTransformations.js +7 -5
  54. package/lib/transform/db/assertUnique.js +8 -51
  55. package/lib/transform/db/associations.js +1 -1
  56. package/lib/transform/db/cdsPersistence.js +1 -15
  57. package/lib/transform/db/expansion.js +9 -12
  58. package/lib/transform/db/flattening.js +1 -1
  59. package/lib/transform/db/groupByOrderBy.js +0 -16
  60. package/lib/transform/db/views.js +57 -161
  61. package/lib/transform/draft/db.js +2 -2
  62. package/lib/transform/forOdata.js +25 -14
  63. package/lib/transform/forRelationalDB.js +93 -301
  64. package/lib/transform/localized.js +33 -102
  65. package/lib/transform/odata/flattening.js +11 -2
  66. package/lib/transform/transformUtils.js +25 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
  68. package/package.json +2 -2
  69. package/lib/render/toHdbcds.js +0 -1810
@@ -5,6 +5,7 @@
5
5
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
6
6
 
7
7
  const {
8
+ pushLink,
8
9
  setLink,
9
10
  setArtifactLink,
10
11
  linkToOrigin,
@@ -28,7 +29,7 @@ const $inferred = Symbol.for( 'cds.$inferred' );
28
29
  function tweakAssocs( model ) {
29
30
  // Get shared functionality and the message function:
30
31
  const {
31
- info, warning, error,
32
+ info, warning, error, message,
32
33
  } = model.$messageFunctions;
33
34
  const {
34
35
  traverseExpr,
@@ -42,6 +43,7 @@ function tweakAssocs( model ) {
42
43
  navigationEnv,
43
44
  redirectionChain,
44
45
  resolveExprInAnnotations,
46
+ checkBacklinkPartner,
45
47
  } = model.$functions;
46
48
 
47
49
  Object.assign(model.$functions, {
@@ -52,15 +54,18 @@ function tweakAssocs( model ) {
52
54
  // Phase 5: rewrite associations
53
55
  model._entities.forEach( rewriteArtifact ); // _entities contains all definitions, sorted.
54
56
  // Think hard whether an on condition rewrite can lead to a new cyclic
55
- // dependency. If so, we need other messages anyway. TODO: probably dox
57
+ // dependency. If so, we need other messages anyway. TODO: probably do
56
58
  // another cyclic check with testMode.js
57
59
  forEachUserArtifact( model, 'definitions', function check( art ) {
58
- checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
60
+ if (art.on && !art.on.$inferred)
61
+ checkOnCondition( art.on, (art.kind !== 'mixin' ? 'on' : 'mixin-on'), art );
59
62
  checkExpr( art.value, (art.$syntax === 'calc' ? 'calc' : 'column'), art );
60
63
 
61
64
  if (art.kind === 'select')
62
65
  forEachQueryExpr( art, checkExpr );
63
66
  } );
67
+ if (model._associations)
68
+ model._associations.forEach( rewriteBacklink );
64
69
 
65
70
 
66
71
  // create “super” ANNOTATE statements for annotations on unknown artifacts:
@@ -281,8 +286,12 @@ function tweakAssocs( model ) {
281
286
  forEachGeneric( elem, 'elements', rewriteAssociation );
282
287
  if (elem.targetAspect?.elements)
283
288
  forEachGeneric( elem.targetAspect, 'elements', rewriteAssociation );
284
- if (!originTarget( elem ))
289
+
290
+ if (!originTarget( elem )) {
291
+ if (elem.$backlink?._redirected)
292
+ pushLink( model, '_associations', elem );
285
293
  return;
294
+ }
286
295
 
287
296
  // console.log(message( null, elem.location, elem,
288
297
  // {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
@@ -304,13 +313,22 @@ function tweakAssocs( model ) {
304
313
  if (!elem || elem.builtin) // safety
305
314
  return;
306
315
  }
316
+ // console.log('PBI:',elem.kind,elem._main?.name.id,elem.name.id,!!elem.$backlink)
317
+ if (elem.$backlink?._redirected)
318
+ pushLink( model, '_associations', elem );
319
+
307
320
  chain.reverse();
308
321
  for (const art of chain) {
322
+ // console.log('PBC:',art.kind,art._main.name.id,art.name.id,elem.name.id)
309
323
  setLink( elem, '_status', null );
310
- if (elem.on)
324
+ if (elem.on) {
311
325
  rewriteCondition( art, elem );
312
- else if (elem.foreignKeys)
326
+ if (elem.$backlink?._redirected && art.on)
327
+ propagateBacklink( art, elem );
328
+ }
329
+ else if (elem.foreignKeys) {
313
330
  rewriteKeys( art, elem );
331
+ }
314
332
 
315
333
  if (art.on)
316
334
  removeManagedPropsFromUnmanaged( art );
@@ -319,6 +337,52 @@ function tweakAssocs( model ) {
319
337
  }
320
338
  }
321
339
 
340
+ function propagateBacklink( assoc, _originAssoc ) {
341
+ // const origin = originAssoc.$backlink;
342
+ // if (assoc.target._artifact === originAssoc.target._artifact) {
343
+ // // no redirection → backlink is the same → no need to traverse ON-condition
344
+ // const backlink = { location: weakLocation( origin.location ), target: {} };
345
+ // setLink( backlink, '_origin', origin._origin );
346
+ // setLink( backlink, '_outer', assoc );
347
+ // assoc.$backlink = backlink;
348
+ // // TODO: _redirected
349
+ // }
350
+ // else {
351
+ checkBacklinkPartner( assoc );
352
+ // console.log('PB:',assoc._main.name.id,assoc.name.id,originAssoc.name.id,
353
+ // !!originAssoc.on,!!assoc.on,!!assoc.$backlink)
354
+ // TODO check: there should no reason for a new message (we might need to
355
+ // omit calling checkBacklinkPartner() if rewriting the ON-condition failed)
356
+ model._associations.push( assoc );
357
+ }
358
+
359
+ function rewriteBacklink( elem ) {
360
+ const backlink = elem.$backlink;
361
+ // console.log('RW:',elem._main?.name?.id,elem.name.id,!!backlink)
362
+ if (!backlink)
363
+ return;
364
+ setLink( backlink.target, '_artifact', elem._main ); // TODO: mixin
365
+ // already the fkeys/ON of the backlink association checks that all matches:
366
+ const origin = backlink._origin;
367
+ if (origin.$backlink) {
368
+ message( 'ref-invalid-backlink', [ backlink.location, elem ],
369
+ { '#': 'backlink', art: origin } );
370
+ setLink( backlink, '_redirected', null ); // no further processing
371
+ return;
372
+ }
373
+ if (backlink._redirected?.length === 0)
374
+ return;
375
+ // all of the following necessary?
376
+ setLink( backlink, '_main', elem._main );
377
+ setLink( backlink, '_parent', elem._parent );
378
+ setLink( backlink, '_effectiveType', backlink );
379
+ backlink.name = origin.name;
380
+ if (origin.on)
381
+ rewriteCondition( backlink, origin );
382
+ else if (origin.foreignKeys)
383
+ rewriteKeys( backlink, origin );
384
+ }
385
+
322
386
  /**
323
387
  * Remove properties from unmanaged association `elem` that are only valid
324
388
  * on managed associations. Only set to `NULL` (special value for propagator),
@@ -395,12 +459,12 @@ function tweakAssocs( model ) {
395
459
  elem;
396
460
  // TODO: probably better to collect the non-projected foreign keys
397
461
  // and have one message for all
398
- error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
399
- '#': 'std',
462
+ error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
463
+ '#': (elem.name ? 'std' : 'backlink'),
400
464
  id: targetElement.path.map(p => p.id).join('.'),
401
465
  target: alias._main,
402
- name: elem.name.id,
403
- });
466
+ name: elem.name?.id,
467
+ } );
404
468
  return null;
405
469
  }
406
470
  }
@@ -442,6 +506,7 @@ function tweakAssocs( model ) {
442
506
  // TODO: re-check $self rewrite (with managed composition of aspects),
443
507
  // and actually also $self inside anonymous aspect definitions
444
508
  // (not entirely urgent as we do not analyse it further, at least sole "$self")
509
+ // `assoc` is the "origin" association whose ON-condition we rewrite
445
510
  function rewriteCondition( elem, assoc ) {
446
511
  // the ON condition might need to be rewritten even if the target stays the
447
512
  // same (TODO later: set status whether rewrite changes anything),
@@ -464,6 +529,8 @@ function tweakAssocs( model ) {
464
529
  const nav = (elem._main?.query && elem.value)
465
530
  ? pathNavigation( elem.value ) // redirected source elem or mixin
466
531
  : { navigation: assoc }; // redirected user-provided
532
+ // console.log('RC:',elem._main.name.id,(elem._outer||elem).name.id,
533
+ // assoc.name.id,!!nav.navigation,elem._redirected?.length)
467
534
  elem.on = copyExpr( assoc.on,
468
535
  // replace location in ON except if from mixin element
469
536
  nav.tableAlias && elem.name.location );
@@ -722,18 +789,19 @@ function tweakAssocs( model ) {
722
789
  }
723
790
 
724
791
  /**
725
- * @param expr
726
- * @param assoc
727
- * @param tableAlias
728
- * @param navEnv Navigation element / table alias, used to traverse/rewrite the path.
792
+ * Rewrite reference `expr` in-place (this function does nothing if `expr` is
793
+ * not a reference). `expr` is part of the potentially to-be-rewritten
794
+ * ON-condition of the element `assoc`. `assoc` can be:
795
+ *
796
+ * - a column projecting an association of a query source: then `tableAlias`
797
+ * is the table alias for that query source
798
+ * - a column projecting a mixin definition
799
+ * - an included element (the "copy")
800
+ * - a backlink with an imaginary redirection to the current entity
801
+ *
802
+ * TODO: describe when `navEnv` is different to `tableAlias`.
729
803
  */
730
804
  function rewriteExpr( expr, assoc, tableAlias, navEnv = tableAlias ) {
731
- // Rewrite ON condition (resulting in outside perspective) for association
732
- // 'assoc' in query or including entity from ON cond of mixin element /
733
- // element in included structure / element in source ref/d by table alias.
734
-
735
- // TODO: complain about $self (unclear semantics)
736
-
737
805
  if (!expr.path || !expr._artifact)
738
806
  return;
739
807
  if (!assoc._main)
@@ -749,6 +817,17 @@ function tweakAssocs( model ) {
749
817
 
750
818
  rewritePathForEnv( expr, navEnv, assoc );
751
819
  }
820
+ else if (assoc._outer?.$backlink === assoc) { // backlink proxy
821
+ const root = expr.path[0]._navigation || expr.path[0]._artifact;
822
+ if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
823
+ return;
824
+ const item = expr.path[root.kind === '$self' ? 1 : 0];
825
+ if (!item)
826
+ return; // just $self
827
+ // console.log('REB:',assoc.name?.id,expr.path.map(i=>i.id))
828
+ if (item.id === assoc.name.id)
829
+ rewritePath( expr, item, assoc, assoc, null );
830
+ }
752
831
  else if (assoc._main.query) { // from ON cond of mixin element in query
753
832
  // here also $calc
754
833
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
@@ -963,8 +1042,8 @@ function tweakAssocs( model ) {
963
1042
 
964
1043
  /**
965
1044
  * Rewrite the reference `ref` with first elem/mixin ref item `item` for user
966
- * `assoc` (the query element), `elem` is the first (or preferred) query element
967
- * for item.
1045
+ * `assoc` (the query element). `elem` is usually the first (or preferred)
1046
+ * query element for item.
968
1047
  */
969
1048
  function rewritePath( ref, item, assoc, elem, location ) {
970
1049
  const { path } = ref;
@@ -1035,6 +1114,7 @@ function tweakAssocs( model ) {
1035
1114
  * @param assoc Published association of query.
1036
1115
  */
1037
1116
  function rewriteItem( elem, item, assoc, noError ) {
1117
+ // console.log('RPI:',elem._redirected?.map(r=>`${r.name.id}@${r._main?.name?.id||''}`))
1038
1118
  if (!elem._redirected)
1039
1119
  return true;
1040
1120
  let name = item.id;
@@ -111,7 +111,7 @@ function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
111
111
  return elem;
112
112
  }
113
113
 
114
- function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDeprecated ) {
114
+ function proxyCopyMembers( art, dictProp, originDict, location, kind ) {
115
115
  art[dictProp] = Object.create( null );
116
116
  // TODO: set $inferred ? for dict?
117
117
  for (const name in originDict) {
@@ -121,13 +121,10 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
121
121
  location || origin.location, true );
122
122
  member.$inferred = 'expanded';
123
123
  // TODO throughout the compiler: do not set art.‹prop›.$inferred if art.$inferred
124
- if (kind)
124
+ if (kind) // e.g. for dictionary of '$navElement's
125
125
  member.kind = kind;
126
- else if (origin.key && !tmpDeprecated)
127
- // TODO(v6): remove tmpDeprecated once `_noKeyPropagationWithExpansions` is removed
126
+ else if (origin.key)
128
127
  member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
129
- if (kind && origin.masked) // TODO: remove!
130
- member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
131
128
  }
132
129
  }
133
130
  }
@@ -183,7 +180,7 @@ function createAndLinkCalcDepElement( elem ) {
183
180
  setLink( r, '_outer', elem );
184
181
  }
185
182
 
186
- function initExprAnnoBlock( art, block ) {
183
+ function initExprAnnoBlock( art, block, error ) {
187
184
  // remark: `art` could also be the extension
188
185
  for (const prop in art) {
189
186
  if (prop.charAt(0) !== '@')
@@ -192,10 +189,23 @@ function initExprAnnoBlock( art, block ) {
192
189
  // _block links needed for `cast( … as Type )`, `[ ..., cast( … as Type ) ]`
193
190
  if (anno.literal === 'array') {
194
191
  anno.kind = '$annotation';
195
- for (const item of anno.val)
192
+ for (const item of anno.val) {
196
193
  setLink( item, '_block', block );
194
+ if (item.literal !== 'token' || item.val !== '...' || item.$errorReported != null)
195
+ continue;
196
+ item.$errorReported = art.kind && art.kind !== 'annotate' && art.kind !== 'extend';
197
+ if (item.$errorReported) {
198
+ error( null, [ item.location, art ], { offending: '...' },
199
+ 'Unexpected $(OFFENDING) in the array of an annotation on a definition' );
200
+ item.$errorReported = 'syntax';
201
+ }
202
+ else if (item.upTo) {
203
+ item.$errorReported
204
+ = !checkUpToSpec( item.upTo, art, prop, true, error ) && 'syntax';
205
+ }
206
+ }
197
207
  }
198
- else if (anno.$tokenTexts) {
208
+ else if (anno.$tokenTexts || anno.sym || anno.struct) {
199
209
  // remark: it wouldn't hurt to set it always...
200
210
  anno.kind = '$annotation';
201
211
  setLink( anno, '_block', block );
@@ -203,6 +213,34 @@ function initExprAnnoBlock( art, block ) {
203
213
  }
204
214
  }
205
215
 
216
+ function checkUpToSpec( upToSpec, art, annoName, noBoolOrNull, error ) {
217
+ const { literal, path } = upToSpec;
218
+ if (literal === 'struct') {
219
+ return Object.values( upToSpec.struct )
220
+ .reduce( ( ok, v ) => checkUpToSpec( v, art, annoName, false, error ) && ok, true );
221
+ // .reduce instead of .every because we want to see every sub error
222
+ }
223
+ else if (literal
224
+ ? literal !== 'array' &&
225
+ (!noBoolOrNull || literal !== 'boolean' && literal !== 'null')
226
+ : path && !upToSpec.scope && !path.some( hasSpecialPathItemProp )) {
227
+ return true;
228
+ }
229
+ error( null, [ upToSpec.location, art ],
230
+ { anno: annoName, code: '... up to', '#': literal || (path ? 'ref' : 'expr') },
231
+ {
232
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
233
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
234
+ // eslint-disable-next-line @stylistic/max-len
235
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
236
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
237
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
238
+ ref: 'Unexpected complex reference as $(CODE) value in the assignment of $(ANNO)',
239
+ expr: 'Unexpected expression as $(CODE) value in the assignment of $(ANNO)',
240
+ } );
241
+ return false;
242
+ }
243
+
206
244
  function initDollarSelf( art ) {
207
245
  // TODO: use setMemberParent() ?
208
246
  // TODO: also on 'namespace's? (test with annotation with checked ref on them)
@@ -314,6 +352,13 @@ function pathName( path ) {
314
352
  return (path && !path.broken) ? path.map( id => id.id ).join( '.' ) : '';
315
353
  }
316
354
 
355
+ function hasSpecialPathItemProp( item ) {
356
+ return item.args ||
357
+ item.where || item.groupBy || item.having || item.limit || item.orderBy ||
358
+ item.cardinality || item.$extra || item.$syntax;
359
+ }
360
+
361
+
317
362
  /**
318
363
  * Generates an XSN path out of the given name. Path segments are delimited by a dot.
319
364
  * Each segment will have the given location assigned.
@@ -459,10 +504,9 @@ function forEachUserArtifact( model, prop, callback ) { // not enums
459
504
  function forEachUserElementAndFKey( art, callback ) {
460
505
  while (art.items)
461
506
  art = art.items;
462
- if (art.target) {
507
+ if (art.target)
463
508
  forEachUserDict( art, 'foreignKeys', callback );
464
- return;
465
- }
509
+
466
510
  if (art.targetAspect)
467
511
  art = art.targetAspect;
468
512
  forEachUserDict( art, 'elements', function element( elem ) {
@@ -855,6 +899,7 @@ module.exports = {
855
899
  initBoundSelfParam,
856
900
  withAssociation,
857
901
  pathName,
902
+ hasSpecialPathItemProp,
858
903
  augmentPath,
859
904
  splitIntoPath,
860
905
  copyExpr,
@@ -152,7 +152,6 @@ const {
152
152
  } = require('./utils');
153
153
  const { CompilerAssertion } = require('../base/error');
154
154
  const { isBetaEnabled } = require('../base/specialOptions');
155
- const { isSimpleCdlIdentifier } = require('../parsers/identifiers');
156
155
 
157
156
  // Config object passed around all "rewrite" functions.
158
157
  class AnnoRewriteConfig {
@@ -276,7 +275,7 @@ function xprRewriteFns( model ) {
276
275
  const struct = Object.values(expr.struct);
277
276
  return !!struct.find(val => rewriteAnnotationExpr( val, config ));
278
277
  }
279
- else if (expr.$tokenTexts) {
278
+ else if (expr.$tokenTexts) { // no expr.sym check needed here
280
279
  // used to set `$tokenText` to true in case of rewritten annotation
281
280
  config.tokenExpr = expr;
282
281
  return traverseExpr( expr, 'annoRewrite', config.destination,
@@ -329,18 +328,6 @@ function xprRewriteFns( model ) {
329
328
  if (hasError)
330
329
  return traverseExpr.STOP;
331
330
 
332
- if (expr.$tokenTexts === true) {
333
- // TODO: do not do with Universal-CSN (and gensrc, but that does not matter)
334
- // We rewrite the string value for backward compatibility with "old-school" tools that
335
- // only understand the string representation. But we only do so for simple strings, which
336
- // is the case if this path expression has $tokenTexts. It then is of the form `@a: (path)`.
337
- const isSimpleStep = step => !step.where && !step.args && isSimpleCdlIdentifier( step.id );
338
- if (expr.path.every(isSimpleStep)) {
339
- expr.$tokenTexts = (expr.scope === 'param' ? ':' : '') +
340
- expr.path.map( step => step.id ).join('.');
341
- }
342
- }
343
-
344
331
  if (model.options.testMode) {
345
332
  // re-resolve the modified path; all paths steps must match what we rewrote
346
333
  const ref = { ...expr, path: [ ...expr.path.map(item => ({ ...item })) ] };
@@ -758,7 +745,7 @@ function isReturnParam( art ) {
758
745
 
759
746
  /**
760
747
  * Gets the artifact (e.g. element) that was expanded.
761
- * `destination` is a sub-artifact of that root and is an expanded element.
748
+ * `destination` is a sub artifact of that root and is an expanded element.
762
749
  *
763
750
  * - expandedRoot: Top-most structure that was expanded.
764
751
  * - expandedRootType: The type of expandedRoot.
@@ -307,7 +307,7 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions, generi
307
307
  // LITERALS
308
308
  transform.val = (parent, prop, xpr, csnPath, parentParent, parentProp) => {
309
309
  if (xpr === null)
310
- parent.$Null = true;
310
+ parentParent[parentProp] = { $Null: true };
311
311
  else
312
312
  parentParent[parentProp] = xpr;
313
313
  };
@@ -852,16 +852,20 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions, generi
852
852
  delete parent[prop];
853
853
  };
854
854
 
855
+ const exprHandler = (parent, _prop, _xpr, csnPath, parentParent, parentProp) => {
856
+ if (isAnnotationExpression(parent)) {
857
+ const edmJson = preTransformXpr(parent);
858
+ // csnPath to this certain expression is passed inside of the 'ctx' variable, in case it is needed for resolving types from the
859
+ // dictionary, for instance in enum symbols resolving, see transform['#']
860
+ parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform, csnPath, { xprCsnPath: csnPath });
861
+ }
862
+ };
855
863
  return transformExpression(carrier, anno, {
856
- '=': (parent, prop, xpr, csnPath, parentParent, parentProp) => {
857
- if (isAnnotationExpression(parent)) {
858
- delete parent['='];
859
- const edmJson = preTransformXpr(parent);
860
- // csnPath to this certain expression is passed inside of the 'ctx' variable, in case it is needed for resolving types from the
861
- // dictionary, for instance in enum symbols resolving, see transform['#']
862
- parentParent[parentProp] = transformExpression({ $edmJson: edmJson }, undefined, transform, csnPath, { xprCsnPath: csnPath });
863
- }
864
- },
864
+ ref: exprHandler,
865
+ xpr: exprHandler,
866
+ list: exprHandler,
867
+ val: exprHandler,
868
+ func: exprHandler,
865
869
  }, location);
866
870
 
867
871
  /**
@@ -855,7 +855,9 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
855
855
  const qualifier = i >= 0 ? termName.substring(i + 1) : undefined;
856
856
 
857
857
  termNameWithoutQualifiers.split('.').forEach((id) => {
858
- if (!edmUtils.isODataSimpleIdentifier(id))
858
+ if (isV2() && !edmUtils.isODataV2SimpleIdentifier(id))
859
+ message('odata-invalid-name-v2', msg.location, { id });
860
+ else if (!edmUtils.isODataSimpleIdentifier(id))
859
861
  message('odata-invalid-name', msg.location, { id });
860
862
  });
861
863
  newAnno = new Edm.Annotation(v, termNameWithoutQualifiers);
@@ -30,24 +30,6 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
30
30
  return options.v && options.v[0];
31
31
  }
32
32
 
33
- // return value can be null is target has no key
34
- function getKeyOfTargetOfManagedAssoc( anno, assoc ) {
35
- // assoc.target can be the name of the target or the object itself
36
- const targetName = (typeof assoc.target === 'object') ? assoc.target.name : assoc.target;
37
- const target = (typeof assoc.target === 'object') ? assoc.target : csn.definitions[assoc.target];
38
-
39
- const keyNames = Object.keys(target.elements).filter(x => target.elements[x].key && !target.elements[x].target);
40
- if (keyNames.length === 0) {
41
- keyNames.push('MISSING');
42
- message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'nokey' } );
43
- }
44
- else if (keyNames.length > 1) {
45
- message('odata-anno-preproc', assoc.$path, { anno, name: targetName, '#': 'multkeys' });
46
- }
47
-
48
- return keyNames[0];
49
- }
50
-
51
33
  // ----------------------------------------------------------------------------------------------
52
34
  // main annotation processors
53
35
  // ----------------------------------------------------------------------------------------------
@@ -143,6 +125,11 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
143
125
  return false;
144
126
  }
145
127
 
128
+ // If carrier is an association, do nothing, as the anno only applies to Property, not to NavigationProperty.
129
+ // For a managed association, the anno has also been propagated to the generated FKs, which are processed here, too.
130
+ if (carrier.target)
131
+ return false;
132
+
146
133
  // check on "type"? e.g. if present, it must be #fixed ... ?
147
134
 
148
135
  // value list entity
@@ -200,14 +187,8 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
200
187
  const label = carrier['@Common.ValueList.Label'] ||
201
188
  carrier['@Common.Label'] || vlEntity['@Common.Label'] || enameShort;
202
189
 
203
- // localDataProp
204
- // name of the element carrying the value help annotation
205
- // if this is a managed assoc, use fk field instead (if there is a single one)
190
+ // localDataProp = name of the element carrying the value help annotation
206
191
  let localDataProp = carrierName.split('/').pop();
207
- if (carrier.target && carrier.on === undefined) {
208
- localDataProp = localDataProp + fkSeparator +
209
- getKeyOfTargetOfManagedAssoc(anno, carrier);
210
- }
211
192
 
212
193
  // if this carrier is a generated foreign key field and the association is marked @cds.api.ignore
213
194
  // rename the localDataProp to be 'assocName/key'
@@ -218,9 +199,11 @@ function preprocessAnnotations( csn, serviceName, options, messageFunctions ) {
218
199
  }
219
200
 
220
201
  // valueListProp: the (single) key field of the value list entity
202
+ // ignore the artificial key "IsActiveEntity" for draft-enabled entities
221
203
  // if no key or multiple keys -> message
222
204
  let valueListProp = null;
223
- const keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target );
205
+ const keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key && !vlEntity.elements[x].target &&
206
+ !(vlEntity['@odata.draft.enabled'] && x === 'IsActiveEntity')); // ignore added key for draft entities
224
207
  if (keys.length === 0) {
225
208
  message('odata-anno-preproc', [ ...location, anno ], { anno, name: enameFull, '#': 'vhlnokey' });
226
209
  return false;
@@ -412,7 +412,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
412
412
  }
413
413
  else {
414
414
  schema.name.split('.').forEach((id) => {
415
- if (!edmUtils.isODataSimpleIdentifier(id))
415
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(id))
416
+ message('odata-invalid-name-v2', loc, { id });
417
+ else if (!edmUtils.isODataSimpleIdentifier(id))
416
418
  message('odata-invalid-name', loc, { id });
417
419
  });
418
420
  }
@@ -420,6 +422,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
420
422
  /** @type {any} */
421
423
  const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
422
424
  const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
425
+ if (options.isV2() && EntityContainer && !edmUtils.isODataV2SimpleIdentifier(EntityContainer._edmAttributes.Name))
426
+ message('odata-invalid-name-v2', loc, { id: EntityContainer._edmAttributes.Name });
423
427
  // now namespace and alias are used to create the fullQualified(name)
424
428
  const schemaNamePrefix = `${ schema.name }.`;
425
429
  const schemaAliasPrefix = schemaNamePrefix;
@@ -526,7 +530,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
526
530
  else if (entityCsn.$hasEntitySet && entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
527
531
  message('odata-missing-key', location);
528
532
 
529
- if (!edmUtils.isODataSimpleIdentifier(EntityTypeName))
533
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(EntityTypeName))
534
+ message('odata-invalid-name-v2', location, { id: EntityTypeName });
535
+ else if (!edmUtils.isODataSimpleIdentifier(EntityTypeName))
530
536
  message('odata-invalid-name', location, { id: EntityTypeName });
531
537
 
532
538
  properties.forEach((p) => {
@@ -538,18 +544,10 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
538
544
  if (options.isV2() && p.$isCollection && !p._csn.target)
539
545
  message('odata-unexpected-array', pLoc, { version: '2.0' });
540
546
 
541
- if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name)) {
547
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(p._edmAttributes.Name))
548
+ message('odata-invalid-name-v2', pLoc, { id: p._edmAttributes.Name });
549
+ else if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
542
550
  message('odata-invalid-name', pLoc, { id: p._edmAttributes.Name });
543
- }
544
- else if (options.isV2() && /^(_|\d)/.test(p._edmAttributes.Name)) {
545
- // FIXME: Rewrite signalIllegalIdentifier function to be more flexible
546
- message('odata-invalid-name', pLoc, {
547
- '#': 'v2firstChar',
548
- prop: p._edmAttributes.Name[0],
549
- id: p._edmAttributes.Name,
550
- version: '2.0',
551
- });
552
- }
553
551
  });
554
552
 
555
553
  // construct EntityType attributes
@@ -605,7 +603,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
605
603
  const properties = createProperties(elementsCsn, structuredTypeCsn)[0];
606
604
  const location = [ 'definitions', structuredTypeCsn.name ];
607
605
 
608
- if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
606
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(attributes.Name))
607
+ message('odata-invalid-name-v2', location, { id: attributes.Name });
608
+ else if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
609
609
  message('odata-invalid-name', location, { id: attributes.Name });
610
610
 
611
611
  properties.forEach((p) => {
@@ -614,7 +614,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
614
614
  if (p._edmAttributes.Name === complexType._edmAttributes.Name)
615
615
  message('odata-invalid-property-name', pLoc, { meta: structuredTypeCsn.kind });
616
616
 
617
- if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
617
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(p._edmAttributes.Name))
618
+ message('odata-invalid-name-v2', pLoc, { id: p._edmAttributes.Name });
619
+ else if (!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
618
620
  message('odata-invalid-name', pLoc, { id: p._edmAttributes.Name });
619
621
 
620
622
  if (options.isV2()) {
@@ -740,7 +742,6 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
740
742
  ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
741
743
  : [ 'definitions', actionCsn.name ];
742
744
 
743
-
744
745
  if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
745
746
  message('odata-invalid-name', location, { id: attributes.Name });
746
747
 
@@ -893,8 +894,8 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
893
894
  ? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
894
895
  : [ 'definitions', actionCsn.name ];
895
896
 
896
- if (!edmUtils.isODataSimpleIdentifier(attributes.Name))
897
- message('odata-invalid-name', location, { id: attributes.Name });
897
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(attributes.Name))
898
+ message('odata-invalid-name-v2', location, { id: attributes.Name });
898
899
 
899
900
  const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
900
901
  if (rt) { // add EntitySet attribute only if return type is an entity
@@ -951,8 +952,9 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
951
952
  const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
952
953
  collectUsedType(parameterCsn);
953
954
  edmTypeCompatibilityCheck(param, pLoc);
954
- if (!edmUtils.isODataSimpleIdentifier(parameterName))
955
- message('odata-invalid-name', pLoc, { id: parameterName });
955
+
956
+ if (options.isV2() && !edmUtils.isODataV2SimpleIdentifier(parameterName))
957
+ message('odata-invalid-name-v2', pLoc, { id: parameterName });
956
958
 
957
959
  // only scalar or structured type in V2 (not entity)
958
960
  if (param._type &&
@@ -1096,6 +1098,11 @@ function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
1096
1098
  if (reuseAssoc)
1097
1099
  return;
1098
1100
 
1101
+ const assocLocation = [ 'definitions', navigationProperty._csn._edmParentCsn.name,
1102
+ 'elements', navigationProperty._edmAttributes.Name ];
1103
+ if (!edmUtils.isODataV2SimpleIdentifier(assocName))
1104
+ message('odata-invalid-name-v2', assocLocation, { id: assocName });
1105
+
1099
1106
  // Create Association and AssociationSet if this is not a backlink association.
1100
1107
  // Store association at navigation property because in case the Ends must be modified
1101
1108
  // later by the partner (backlink) association