@sap/cds-compiler 3.1.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -10,7 +10,6 @@ function kickStart( model ) {
10
10
  const { message } = model.$messageFunctions;
11
11
 
12
12
  const { resolveUncheckedPath, resolvePath } = model.$functions;
13
- Object.assign( model.$functions, { projectionAncestor } );
14
13
 
15
14
  // Set _service link (sorted to set it on parent first). Could be set
16
15
  // directly, but beware a namespace becoming a service later.
@@ -68,7 +67,7 @@ function kickStart( model ) {
68
67
  // no redirection target for E if Service2.E = projection on Service1.E and
69
68
  // Service1.E = projection on E
70
69
  const chain = [];
71
- const autoexposed = art.$generated || annotationVal( art['@cds.autoexposed'] );
70
+ const autoexposed = annotationVal( art['@cds.autoexposed'] );
72
71
  const preferredRedirectionTarget = annotationVal( art['@cds.redirection.target'] );
73
72
  // no need to set preferredRedirectionTarget in the while loop as we would
74
73
  // use the projection having @cds.redirection.target anyhow instead of
@@ -80,7 +79,7 @@ function kickStart( model ) {
80
79
  chain.push( art );
81
80
  setLink( art, '_ancestors', null ); // avoid infloop with cyclic from
82
81
  const name = resolveUncheckedPath( art._from[0], 'include', art ); // TODO: 'include'?
83
- art = name && projectionAncestor( model.definitions[name], art.params );
82
+ art = name && model.definitions[name];
84
83
  if (autoexposed)
85
84
  break; // only direct projection for auto-exposed
86
85
  }
@@ -93,38 +92,6 @@ function kickStart( model ) {
93
92
  }
94
93
  }
95
94
 
96
- // Return argument `source` if entity `source` has parameters like `params`
97
- // - same parameters, although `params` can contain a new optional one (with DEFAULT)
98
- // - a parameter in `params` can be optional which is not in `source.params`, but not vice versa
99
- // - exactly the same types (type argument do not matter)
100
- function projectionAncestor( source, params ) {
101
- if (!source)
102
- return source;
103
- if (!params) // proj has no params => ok if source has no params
104
- return !source.params && source;
105
- const sourceParams = source.params || Object.create(null);
106
- for (const n in sourceParams) {
107
- if (!(n in params)) // source param is not projection param
108
- return null; // -> can't be used as implicit redirection target
109
- }
110
- for (const n in params) {
111
- const pp = params[n];
112
- const sp = sourceParams[n];
113
- if (sp) {
114
- if (sp.default && !pp.default) // param DEFAULT clause not supported yet
115
- return null; // param is not optional anymore
116
- const pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );
117
- const st = sp.type && resolveUncheckedPath( sp.type, 'type', sp );
118
- if ((pt || null) !== (st || null))
119
- return null; // params have different type
120
- }
121
- else if (!pp.default) {
122
- return null;
123
- } // non-optional param in projection, but not source
124
- }
125
- return source;
126
- }
127
-
128
95
  function postProcessArtifact( art ) {
129
96
  tagCompositionTargets( art );
130
97
  if (art.$queries) {
@@ -19,7 +19,6 @@
19
19
 
20
20
  const {
21
21
  isDeprecatedEnabled,
22
- isBetaEnabled,
23
22
  forEachDefinition,
24
23
  forEachMember,
25
24
  forEachGeneric,
@@ -29,6 +28,7 @@ const {
29
28
  } = require('../base/dictionaries');
30
29
  const { dictLocation } = require('../base/location');
31
30
  const { weakLocation } = require('../base/messages');
31
+ const { CompilerAssertion } = require('../base/error');
32
32
 
33
33
  const { kindProperties } = require('./base');
34
34
  const {
@@ -36,6 +36,8 @@ const {
36
36
  setLink,
37
37
  setArtifactLink,
38
38
  annotationVal,
39
+ annotationIsFalse,
40
+ annotationLocation,
39
41
  augmentPath,
40
42
  splitIntoPath,
41
43
  linkToOrigin,
@@ -59,7 +61,6 @@ function populate( model ) {
59
61
  resolvePath,
60
62
  attachAndEmitValidNames,
61
63
  initArtifact,
62
- projectionAncestor,
63
64
  } = model.$functions;
64
65
  model.$volatileFunctions.environment = environment;
65
66
  Object.assign( model.$functions, {
@@ -103,8 +104,8 @@ function populate( model ) {
103
104
  function traverseElementEnvironments( art ) {
104
105
  populateView( art );
105
106
  environment( art );
106
- if (art.elements$)
107
- mergeSpecifiedElements(art);
107
+ if (art.elements$ || art.enum$)
108
+ mergeSpecifiedElementsOrEnum(art);
108
109
  forEachMember( art, traverseElementEnvironments );
109
110
  }
110
111
 
@@ -188,9 +189,6 @@ function populate( model ) {
188
189
  setLink( a, '_effectiveType', art );
189
190
  }
190
191
  else {
191
- let eType = art;
192
- if (eType._outer)
193
- eType = effectiveType( eType._outer );
194
192
  // collect the "latest" cardinality (calculate lazily if necessary)
195
193
  let cardinality = art.cardinality ||
196
194
  art._effectiveType && (() => getCardinality( art._effectiveType ));
@@ -200,8 +198,8 @@ function populate( model ) {
200
198
  cardinality = a.cardinality;
201
199
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
202
200
  art.target && redirectImplicitly( a, art ) ||
203
- art.elements && expandElements( a, art, eType ) ||
204
- art.items && expandItems( a, art, eType ))
201
+ art.elements && expandElements( a, art ) ||
202
+ art.items && expandItems( a, art ))
205
203
  art = a;
206
204
  else if (art.enum && expandEnum( a, prev ))
207
205
  prev = a; // do not set art - effective type is base
@@ -292,31 +290,32 @@ function populate( model ) {
292
290
  // Expansiosn --------------------------------------------------------------
293
291
 
294
292
 
295
- function expandItems( art, origin, eType ) {
293
+ function expandItems( art, origin ) {
296
294
  if (art.items)
297
295
  return false;
298
- if (isInParents( art, eType )) {
296
+ if (origin.items === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
299
297
  art.items = 0; // circular
300
298
  return true;
301
299
  }
302
300
  const ref = art.type || art.value || art.name;
303
301
  const location = ref && ref.location || art.location;
304
- art.items = { $inferred: 'expand-element', location };
302
+ art.items = { $inferred: 'expanded', location };
305
303
  setLink( art.items, '_outer', art );
304
+ setLink( art.items, '_parent', art._parent );
306
305
  setLink( art.items, '_origin', origin.items );
307
306
  if (!art.$expand)
308
307
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
309
308
  return true;
310
309
  }
311
310
 
312
- function expandElements( art, struct, eType ) {
313
- if (art.elements || art.kind === '$tableAlias' ||
311
+ function expandElements( art, struct ) {
312
+ if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
314
313
  // no element expansions for "non-proper" types like
315
314
  // entities (as parameter types) etc:
316
315
  struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
317
316
  !struct._outer)
318
317
  return false;
319
- if (struct.elements === 0 || isInParents( art, eType )) {
318
+ if (struct.elements === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
320
319
  art.elements = 0; // circular
321
320
  return true;
322
321
  }
@@ -331,14 +330,14 @@ function populate( model ) {
331
330
  continue;
332
331
  linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
333
332
  // or should we use orig.location? - TODO: try to find test to see message
334
- .$inferred = 'expand-element';
333
+ .$inferred = 'expanded';
335
334
  }
336
335
  // Set elements expansion status (the if condition is always true, as no
337
336
  // elements expansion will take place on artifact with existing other
338
337
  // member property):
339
338
  if (!art.$expand)
340
339
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
341
- // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
340
+ // TODO: have some art.elements[SYM.$inferred] = 'expanded';
342
341
  return true;
343
342
  }
344
343
 
@@ -352,39 +351,56 @@ function populate( model ) {
352
351
  const orig = origin.enum[name];
353
352
  linkToOrigin( orig, name, art, 'enum', location, true )
354
353
  // or should we use orig.location? - TODO: try to find test to see message
355
- .$inferred = 'expand-element';
354
+ .$inferred = 'expanded';
356
355
  }
357
356
  // Set elements expansion status (the if condition is always true, as no
358
357
  // elements expansion will take place on artifact with existing other
359
358
  // member property):
360
359
  if (!art.$expand)
361
360
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
362
- art.enum[$inferred] = 'expand-element';
361
+ art.enum[$inferred] = 'expanded';
363
362
  return true;
364
363
  }
365
364
 
366
365
  /**
367
- * Return true iff `struct` is `art` or a direct or indirect parent of `art`,
368
- * we also check for the outer objects of `items`. (There is no need to
369
- * check the parents of main artifacts, as these are contexts, services or
370
- * namespaces, and do not serve as type.)
366
+ * Return true iff `art` is from a recursive expansion, i.e. if any of its
367
+ * expanded parents (including _outer) has the same non-expansion-origin.
371
368
  */
372
- function isInParents( art, struct ) {
373
- if (art === struct)
369
+ function isInRecursiveExpansion( art ) {
370
+ const current = nonExpandedArtifact( art );
371
+ if (current.$inCycle)
374
372
  return true;
375
- while (art._outer) { // for items
376
- art = art._outer;
377
- if (art === struct)
378
- return true;
379
- }
380
- while (art._main) {
381
- art = art._parent;
382
- if (art === struct)
373
+ const cycle = [ current ];
374
+ while (art.$inferred === 'expanded') {
375
+ art = outerOrParent( art );
376
+ const origin = nonExpandedArtifact( art );
377
+ cycle.push( origin );
378
+ if (origin.$inCycle || origin === current) {
379
+ for (const a of cycle)
380
+ a.$inCycle = true;
383
381
  return true;
382
+ }
384
383
  }
385
384
  return false;
386
385
  }
387
386
 
387
+ function outerOrParent( art ) {
388
+ if (art._outer)
389
+ return art._outer;
390
+ art = art._parent;
391
+ // TODO: think about setting _parent of elements in `items` object holding
392
+ // `elements`, not the most outer `items` -> return art._outer || art._parent
393
+ while (art.items)
394
+ art = art.items;
395
+ return art;
396
+ }
397
+
398
+ function nonExpandedArtifact( art ) {
399
+ while (art.$inferred === 'expanded')
400
+ art = art._origin;
401
+ return art;
402
+ }
403
+
388
404
  //--------------------------------------------------------------------------
389
405
  // Views
390
406
  //--------------------------------------------------------------------------
@@ -442,12 +458,12 @@ function populate( model ) {
442
458
  *
443
459
  * @param art
444
460
  */
445
- function mergeSpecifiedElements( art ) {
461
+ function mergeSpecifiedElementsOrEnum( art ) {
446
462
  // Later we use specified elements as proxies to inferred of leading query
447
463
  // (No, we probably do not.)
448
- for (const id in art.elements) {
449
- const ielem = art.elements[id]; // inferred element
450
- const selem = art.elements$[id]; // specified element
464
+ for (const id in (art.elements || art.enum)) {
465
+ const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
466
+ const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
451
467
  if (!selem) {
452
468
  info( 'query-missing-element', [ ielem.name.location, art ], { id },
453
469
  'Element $(ID) is missing in specified elements' );
@@ -463,8 +479,14 @@ function populate( model ) {
463
479
  setLink(ielem, 'elements$', selem.elements);
464
480
  delete selem.elements;
465
481
  }
482
+ if (selem.enum) {
483
+ setLink(ielem, 'enum$', selem.enum);
484
+ delete selem.enum;
485
+ }
466
486
  }
467
487
  }
488
+ // TODO: We don't check enum$, yet! We first need to fix expansion for
489
+ // `cast(elem as EnumType)` (see #9421)
468
490
  for (const id in art.elements$) {
469
491
  const selem = art.elements$[id]; // specified element
470
492
  if (!selem.$replacement) {
@@ -569,10 +591,6 @@ function populate( model ) {
569
591
  const siblings = wildcardSiblings( columns, query );
570
592
  expandWildcard( col, siblings, inlineHead, query );
571
593
  }
572
- if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
573
- error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
574
- 'Unsupported nested $(PROP)' );
575
- }
576
594
  // If neither expression (value), expand nor new association.
577
595
  if (!col.value && !col.expand && !(col.target && col.type))
578
596
  continue; // error should have been reported by parser
@@ -608,10 +626,10 @@ function populate( model ) {
608
626
  return '';
609
627
  const path = col.value &&
610
628
  (col.value.path || !col.value.args && col.value.func && col.value.func.path);
611
- if (path) {
629
+ if (path && path.length) {
612
630
  const last = !path.broken && path.length && path[path.length - 1];
613
631
  if (last) {
614
- col.name = { id: last.id, location: last.location, $inferred: 'as' };
632
+ col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
615
633
  return col.name.id;
616
634
  }
617
635
  }
@@ -645,18 +663,28 @@ function populate( model ) {
645
663
  // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
646
664
  // elem.value)
647
665
  // TODO: make this resolvePath() also part of directType() ?!
648
- if (!origin)
666
+ if (!origin || elem.expand)
649
667
  return;
668
+ // TODO: or should we push elems with `expand` sibling to extra list for better messages?
650
669
  if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
651
670
  forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
652
671
  }
653
672
 
654
673
  // now set things which are necessary for later sub phases:
655
674
  const nav = pathNavigation( elem.value );
656
- if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
675
+ const item = elem.value.path[elem.value.path.length - 1];
676
+ if (nav.navigation && nav.item === item) {
677
+ // sourceElem, alias.sourceElem, mixin:
657
678
  // redirectImplicitly( elem, origin );
658
679
  pushLink( nav.navigation, '_projections', elem );
659
680
  }
681
+ else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
682
+ const hpath = elem._pathHead.value?.path;
683
+ const head = hpath?.length === 1 && hpath[0]._navigation;
684
+ // Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
685
+ if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
686
+ pushLink( head.elements[item.id], '_projections', elem );
687
+ }
660
688
  }
661
689
 
662
690
  function initKey( key, name, elem ) {
@@ -701,7 +729,7 @@ function populate( model ) {
701
729
  // Object.keys(env),Object.keys(elements))
702
730
  for (const name in env) {
703
731
  const navElem = env[name];
704
- // TODO: if it is an array, filter out those with masked
732
+ // TODO: remove all access to masked (use 'grep')
705
733
  if (excludingDict[name] || navElem.masked && navElem.masked.val)
706
734
  continue;
707
735
  const sibling = siblingElements[name];
@@ -770,13 +798,17 @@ function populate( model ) {
770
798
  path && path[path.length - 1].id !== sibling.name.id) { // or renamed
771
799
  const { id } = sibling.name;
772
800
  if (Array.isArray(navElem)) {
801
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
773
802
  info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
774
- 'This select item replaces $(ID) from two or more sources' );
803
+ // eslint-disable-next-line max-len
804
+ 'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
775
805
  }
776
806
  else {
807
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
777
808
  info( 'wildcard-excluding-one', [ sibling.name.location, query ],
778
- { id, alias: navElem._parent.name.id },
779
- 'This select item replaces $(ID) from table alias $(ALIAS)' );
809
+ { id, alias: navElem._parent.name.id, keyword: 'excluding' },
810
+ // eslint-disable-next-line max-len
811
+ 'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
780
812
  }
781
813
  }
782
814
  }
@@ -787,9 +819,13 @@ function populate( model ) {
787
819
  queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
788
820
  setArtifactLink( path[0], origin );
789
821
  setLink( queryElem, '_origin', origin );
790
- // TODO: set _projections when top-level?
822
+ // set _projections when inline with table alias:
823
+ const alias = pathHead?.value?.path?.[0]?._navigation;
824
+ if (alias?.kind === '$tableAlias')
825
+ pushLink( alias.elements[name], '_projections', queryElem );
791
826
  }
792
827
 
828
+ // called by expandWildcard():
793
829
  function setElementOrigin( queryElem, navElem, name, location ) {
794
830
  const sourceElem = navElem._origin;
795
831
  const alias = navElem._parent;
@@ -892,7 +928,7 @@ function populate( model ) {
892
928
  preferredElemScope( target, service, elem, assoc._main || assoc );
893
929
  const exposed = minimalExposure( target, service, elemScope );
894
930
 
895
- if (!exposed.length && elemScope !== true) {
931
+ if (!exposed.length) {
896
932
  const origTarget = target;
897
933
  if (isAutoExposed( target ))
898
934
  target = createAutoExposed( origTarget, service, elemScope );
@@ -930,7 +966,7 @@ function populate( model ) {
930
966
  for (const proj of exposed) {
931
967
  // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
932
968
  message( 'redirected-implicitly-ambiguous',
933
- [ weakLocation( proj.location ), proj ],
969
+ [ weakLocation( proj.name.location ), proj ],
934
970
  {
935
971
  '#': withAnno && 'justOne',
936
972
  target,
@@ -989,10 +1025,12 @@ function populate( model ) {
989
1025
  const targetScope = definitionScope( target );
990
1026
  if (targetScope === assocScope) { // intra-scope in model
991
1027
  const elemScope = definitionScope( elem._main || elem );
992
- if (targetScope === target || // unscoped target in model
1028
+ // without the if, compile.recompile.json versus expected csn.json in
1029
+ // test3/Redirections/AutoExposeDeepScoped would fail
1030
+ if (targetScope === target || // model target is scope root
993
1031
  assocScope === assocMain || // unscoped assoc source in model
994
1032
  elemScope !== (elem._main || elem)) // scoped assoc source in service
995
- return elemScope; // own scope, then global
1033
+ return elemScope; // own scope, then global
996
1034
  }
997
1035
  if (targetScope === target) // unscoped target in model / other service
998
1036
  return false; // all (there could be no scoped autoexposed)
@@ -1021,17 +1059,6 @@ function populate( model ) {
1021
1059
  function scopedExposure( descendants, elemScope, target ) {
1022
1060
  if (!elemScope) // no scoped redirections
1023
1061
  return descendants;
1024
- if (elemScope === true || elemScope === 'auto') {
1025
- // cross-scope navigation, scoped model target, but there is no unique
1026
- // redirection target for target model scope -> unsure redirection scope
1027
- const unscoped = descendants.filter( d => d === definitionScope( d ) );
1028
- if (unscoped.length) // use unscoped new targets if present
1029
- return unscoped;
1030
- // Need to filter out auto-exposed, otherwise the behavior is
1031
- // processing-order dependent (not storing the autoexposed in
1032
- // _descendents would only be an alternative w/o recompilation)
1033
- return descendants.filter( d => !d.$generated && !annotationVal( d['@cds.autoexposed'] ) );
1034
- }
1035
1062
  // try scope as target first, even if it has @cds.redirection.target: false
1036
1063
  if (isDirectProjection( elemScope, target ))
1037
1064
  return [ elemScope ];
@@ -1059,7 +1086,6 @@ function populate( model ) {
1059
1086
 
1060
1087
  function isDirectProjection( proj, base ) {
1061
1088
  return proj.kind === 'entity' && // not event
1062
- projectionAncestor( base, proj.params ) && // same params
1063
1089
  // direct proj (TODO: or should we add them to another list?)
1064
1090
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1065
1091
  proj._from && proj._from.length === 1 &&
@@ -1137,8 +1163,25 @@ function populate( model ) {
1137
1163
  const autoexposed = model.definitions[absolute];
1138
1164
  if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
1139
1165
  if (isDirectProjection( autoexposed, target )) {
1140
- if (options.testMode)
1141
- throw new Error( `Tried to auto-expose ${ target.name.absolute } twice`);
1166
+ const anno = autoexposed['@cds.redirection.target'];
1167
+ if (annotationIsFalse( anno )) {
1168
+ // It would probably be cleaner to ignore a dubious
1169
+ // `@cds.redirection.target: false` earlier, but that is not easy to detect
1170
+ // due to the name of the autoexposed entity with scoped redirections
1171
+ if (!anno.$errorReported) {
1172
+ info( 'anno-redirecting-anyway',
1173
+ [ annotationLocation( anno ), autoexposed ],
1174
+ { target, art: absolute, code: '@cds.redirection.target: false' },
1175
+ '$(TARGET) is auto-redirected to $(ART) even with $(CODE)' );
1176
+ anno.$errorReported = 'anno-redirecting-anyway';
1177
+ }
1178
+ }
1179
+ else if (autoexposed._parent === service ||
1180
+ !annotationVal( autoexposed['@cds.autoexposed'] )) {
1181
+ // existing def not auto-exposed, or un-scoped auto-exposed: should not happen
1182
+ if (options.testMode)
1183
+ throw new CompilerAssertion( `Tried to auto-expose ${ target.name.absolute } twice`);
1184
+ }
1142
1185
  return autoexposed;
1143
1186
  }
1144
1187
  error( 'duplicate-autoexposed', [ service.name.location, service ],
@@ -93,12 +93,14 @@ function propagate( model ) {
93
93
  source = getOrigin( target );
94
94
  }
95
95
  if (source) { // the source has fully propagated properties
96
- step({ target, source });
96
+ chain.push({ target, source });
97
97
  }
98
98
  else if (target._main) { // source is element, which has not inherited props yet
99
99
  run( target._main ); // run on main artifact first
100
100
  }
101
- else if (target.includes) {
101
+
102
+ // Even with a query source, go through `includes`. `source` is propagated first, i.e. wins.
103
+ if (target.includes) {
102
104
  let targets = [ target ];
103
105
  while (targets.length) {
104
106
  const news = [];
@@ -233,7 +235,7 @@ function propagate( model ) {
233
235
  }
234
236
 
235
237
  function onlyViaParent( prop, target, source ) {
236
- if (target.$inferred === 'proxy' || target.$inferred === 'expand-element')
238
+ if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
237
239
  // assocs and enums do not have 'include'
238
240
  always( prop, target, source );
239
241
  }