@sap/cds-compiler 3.1.0 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +90 -3
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +18 -0
  4. package/lib/api/main.js +8 -13
  5. package/lib/base/error.js +2 -2
  6. package/lib/base/keywords.js +2 -24
  7. package/lib/base/message-registry.js +43 -14
  8. package/lib/base/messages.js +20 -10
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/actionsFunctions.js +1 -1
  11. package/lib/checks/annotationsOData.js +2 -2
  12. package/lib/checks/arrayOfs.js +15 -7
  13. package/lib/checks/cdsPersistence.js +1 -1
  14. package/lib/checks/checkForTypes.js +48 -0
  15. package/lib/checks/defaultValues.js +2 -2
  16. package/lib/checks/elements.js +81 -6
  17. package/lib/checks/foreignKeys.js +12 -13
  18. package/lib/checks/invalidTarget.js +10 -11
  19. package/lib/checks/managedInType.js +21 -15
  20. package/lib/checks/nullableKeys.js +1 -1
  21. package/lib/checks/onConditions.js +9 -9
  22. package/lib/checks/parameters.js +21 -0
  23. package/lib/checks/selectItems.js +1 -1
  24. package/lib/checks/types.js +2 -2
  25. package/lib/checks/utils.js +17 -7
  26. package/lib/checks/validator.js +26 -14
  27. package/lib/compiler/assert-consistency.js +13 -6
  28. package/lib/compiler/builtins.js +8 -0
  29. package/lib/compiler/checks.js +40 -33
  30. package/lib/compiler/define.js +50 -44
  31. package/lib/compiler/extend.js +303 -37
  32. package/lib/compiler/kick-start.js +2 -35
  33. package/lib/compiler/populate.js +83 -62
  34. package/lib/compiler/propagator.js +1 -1
  35. package/lib/compiler/resolve.js +61 -104
  36. package/lib/compiler/shared.js +16 -6
  37. package/lib/compiler/tweak-assocs.js +25 -12
  38. package/lib/compiler/utils.js +2 -2
  39. package/lib/edm/annotations/genericTranslation.js +15 -5
  40. package/lib/edm/csn2edm.js +10 -10
  41. package/lib/edm/edm.js +17 -9
  42. package/lib/edm/edmPreprocessor.js +82 -42
  43. package/lib/edm/edmUtils.js +18 -16
  44. package/lib/gen/Dictionary.json +14 -0
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +3 -2
  47. package/lib/gen/languageParser.js +4205 -4100
  48. package/lib/inspect/inspectModelStatistics.js +1 -1
  49. package/lib/inspect/inspectPropagation.js +23 -9
  50. package/lib/json/csnVersion.js +1 -1
  51. package/lib/json/from-csn.js +26 -19
  52. package/lib/json/to-csn.js +47 -5
  53. package/lib/language/antlrParser.js +1 -1
  54. package/lib/language/genericAntlrParser.js +29 -13
  55. package/lib/language/language.g4 +28 -8
  56. package/lib/main.d.ts +3 -6
  57. package/lib/model/.eslintrc.json +13 -0
  58. package/lib/model/api.js +4 -2
  59. package/lib/model/csnRefs.js +74 -47
  60. package/lib/model/csnUtils.js +236 -218
  61. package/lib/model/enrichCsn.js +41 -31
  62. package/lib/model/revealInternalProperties.js +61 -57
  63. package/lib/model/sortViews.js +31 -31
  64. package/lib/modelCompare/compare.js +6 -6
  65. package/lib/optionProcessor.js +5 -0
  66. package/lib/render/manageConstraints.js +2 -2
  67. package/lib/render/toCdl.js +31 -44
  68. package/lib/render/toHdbcds.js +7 -5
  69. package/lib/render/toRename.js +4 -4
  70. package/lib/render/toSql.js +11 -5
  71. package/lib/render/utils/common.js +20 -9
  72. package/lib/render/utils/sql.js +5 -5
  73. package/lib/transform/db/applyTransformations.js +32 -3
  74. package/lib/transform/db/expansion.js +81 -37
  75. package/lib/transform/db/flattening.js +1 -1
  76. package/lib/transform/db/temporal.js +1 -1
  77. package/lib/transform/db/transformExists.js +1 -1
  78. package/lib/transform/forOdataNew.js +10 -7
  79. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
  80. package/lib/transform/localized.js +28 -19
  81. package/lib/transform/odata/toFinalBaseType.js +8 -11
  82. package/lib/transform/odata/typesExposure.js +1 -1
  83. package/lib/transform/transformUtilsNew.js +101 -39
  84. package/lib/transform/translateAssocsToJoins.js +5 -4
  85. package/lib/utils/moduleResolve.js +5 -5
  86. package/lib/utils/objectUtils.js +3 -3
  87. package/package.json +2 -2
  88. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  89. package/share/messages/check-proper-type-of.md +4 -4
  90. package/share/messages/check-proper-type.md +2 -2
  91. package/share/messages/duplicate-autoexposed.md +4 -4
  92. package/share/messages/extend-repeated-intralayer.md +4 -5
  93. package/share/messages/extend-unrelated-layer.md +4 -4
  94. package/share/messages/message-explanations.json +3 -1
  95. package/share/messages/redirected-to-ambiguous.md +7 -6
  96. package/share/messages/redirected-to-complex.md +63 -0
  97. package/share/messages/redirected-to-unrelated.md +6 -5
  98. package/share/messages/rewrite-not-supported.md +4 -4
  99. package/share/messages/syntax-expected-integer.md +3 -3
  100. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -19,7 +19,6 @@
19
19
 
20
20
  const {
21
21
  isDeprecatedEnabled,
22
- isBetaEnabled,
23
22
  forEachDefinition,
24
23
  forEachMember,
25
24
  forEachGeneric,
@@ -59,7 +58,6 @@ function populate( model ) {
59
58
  resolvePath,
60
59
  attachAndEmitValidNames,
61
60
  initArtifact,
62
- projectionAncestor,
63
61
  } = model.$functions;
64
62
  model.$volatileFunctions.environment = environment;
65
63
  Object.assign( model.$functions, {
@@ -103,8 +101,8 @@ function populate( model ) {
103
101
  function traverseElementEnvironments( art ) {
104
102
  populateView( art );
105
103
  environment( art );
106
- if (art.elements$)
107
- mergeSpecifiedElements(art);
104
+ if (art.elements$ || art.enum$)
105
+ mergeSpecifiedElementsOrEnum(art);
108
106
  forEachMember( art, traverseElementEnvironments );
109
107
  }
110
108
 
@@ -188,9 +186,6 @@ function populate( model ) {
188
186
  setLink( a, '_effectiveType', art );
189
187
  }
190
188
  else {
191
- let eType = art;
192
- if (eType._outer)
193
- eType = effectiveType( eType._outer );
194
189
  // collect the "latest" cardinality (calculate lazily if necessary)
195
190
  let cardinality = art.cardinality ||
196
191
  art._effectiveType && (() => getCardinality( art._effectiveType ));
@@ -200,8 +195,8 @@ function populate( model ) {
200
195
  cardinality = a.cardinality;
201
196
  if (a.expand && expandFromColumns( a, art, cardinality ) ||
202
197
  art.target && redirectImplicitly( a, art ) ||
203
- art.elements && expandElements( a, art, eType ) ||
204
- art.items && expandItems( a, art, eType ))
198
+ art.elements && expandElements( a, art ) ||
199
+ art.items && expandItems( a, art ))
205
200
  art = a;
206
201
  else if (art.enum && expandEnum( a, prev ))
207
202
  prev = a; // do not set art - effective type is base
@@ -292,31 +287,32 @@ function populate( model ) {
292
287
  // Expansiosn --------------------------------------------------------------
293
288
 
294
289
 
295
- function expandItems( art, origin, eType ) {
290
+ function expandItems( art, origin ) {
296
291
  if (art.items)
297
292
  return false;
298
- if (isInParents( art, eType )) {
293
+ if (origin.items === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
299
294
  art.items = 0; // circular
300
295
  return true;
301
296
  }
302
297
  const ref = art.type || art.value || art.name;
303
298
  const location = ref && ref.location || art.location;
304
- art.items = { $inferred: 'expand-element', location };
299
+ art.items = { $inferred: 'expanded', location };
305
300
  setLink( art.items, '_outer', art );
301
+ setLink( art.items, '_parent', art._parent );
306
302
  setLink( art.items, '_origin', origin.items );
307
303
  if (!art.$expand)
308
304
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
309
305
  return true;
310
306
  }
311
307
 
312
- function expandElements( art, struct, eType ) {
313
- if (art.elements || art.kind === '$tableAlias' ||
308
+ function expandElements( art, struct ) {
309
+ if (art.elements || art.kind === '$tableAlias' || art.kind === '$inline' ||
314
310
  // no element expansions for "non-proper" types like
315
311
  // entities (as parameter types) etc:
316
312
  struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
317
313
  !struct._outer)
318
314
  return false;
319
- if (struct.elements === 0 || isInParents( art, eType )) {
315
+ if (struct.elements === 0 || art.$inferred === 'expanded' && isInRecursiveExpansion( art )) {
320
316
  art.elements = 0; // circular
321
317
  return true;
322
318
  }
@@ -331,14 +327,14 @@ function populate( model ) {
331
327
  continue;
332
328
  linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
333
329
  // or should we use orig.location? - TODO: try to find test to see message
334
- .$inferred = 'expand-element';
330
+ .$inferred = 'expanded';
335
331
  }
336
332
  // Set elements expansion status (the if condition is always true, as no
337
333
  // elements expansion will take place on artifact with existing other
338
334
  // member property):
339
335
  if (!art.$expand)
340
336
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
341
- // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
337
+ // TODO: have some art.elements[SYM.$inferred] = 'expanded';
342
338
  return true;
343
339
  }
344
340
 
@@ -352,39 +348,56 @@ function populate( model ) {
352
348
  const orig = origin.enum[name];
353
349
  linkToOrigin( orig, name, art, 'enum', location, true )
354
350
  // or should we use orig.location? - TODO: try to find test to see message
355
- .$inferred = 'expand-element';
351
+ .$inferred = 'expanded';
356
352
  }
357
353
  // Set elements expansion status (the if condition is always true, as no
358
354
  // elements expansion will take place on artifact with existing other
359
355
  // member property):
360
356
  if (!art.$expand)
361
357
  art.$expand = 'origin'; // if value stays, elements won't appear in CSN
362
- art.enum[$inferred] = 'expand-element';
358
+ art.enum[$inferred] = 'expanded';
363
359
  return true;
364
360
  }
365
361
 
366
362
  /**
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.)
363
+ * Return true iff `art` is from a recursive expansion, i.e. if any of its
364
+ * expanded parents (including _outer) has the same non-expansion-origin.
371
365
  */
372
- function isInParents( art, struct ) {
373
- if (art === struct)
366
+ function isInRecursiveExpansion( art ) {
367
+ const current = nonExpandedArtifact( art );
368
+ if (current.$inCycle)
374
369
  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)
370
+ const cycle = [ current ];
371
+ while (art.$inferred === 'expanded') {
372
+ art = outerOrParent( art );
373
+ const origin = nonExpandedArtifact( art );
374
+ cycle.push( origin );
375
+ if (origin.$inCycle || origin === current) {
376
+ for (const a of cycle)
377
+ a.$inCycle = true;
383
378
  return true;
379
+ }
384
380
  }
385
381
  return false;
386
382
  }
387
383
 
384
+ function outerOrParent( art ) {
385
+ if (art._outer)
386
+ return art._outer;
387
+ art = art._parent;
388
+ // TODO: think about setting _parent of elements in `items` object holding
389
+ // `elements`, not the most outer `items` -> return art._outer || art._parent
390
+ while (art.items)
391
+ art = art.items;
392
+ return art;
393
+ }
394
+
395
+ function nonExpandedArtifact( art ) {
396
+ while (art.$inferred === 'expanded')
397
+ art = art._origin;
398
+ return art;
399
+ }
400
+
388
401
  //--------------------------------------------------------------------------
389
402
  // Views
390
403
  //--------------------------------------------------------------------------
@@ -442,12 +455,12 @@ function populate( model ) {
442
455
  *
443
456
  * @param art
444
457
  */
445
- function mergeSpecifiedElements( art ) {
458
+ function mergeSpecifiedElementsOrEnum( art ) {
446
459
  // Later we use specified elements as proxies to inferred of leading query
447
460
  // (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
461
+ for (const id in (art.elements || art.enum)) {
462
+ const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
463
+ const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
451
464
  if (!selem) {
452
465
  info( 'query-missing-element', [ ielem.name.location, art ], { id },
453
466
  'Element $(ID) is missing in specified elements' );
@@ -463,8 +476,14 @@ function populate( model ) {
463
476
  setLink(ielem, 'elements$', selem.elements);
464
477
  delete selem.elements;
465
478
  }
479
+ if (selem.enum) {
480
+ setLink(ielem, 'enum$', selem.enum);
481
+ delete selem.enum;
482
+ }
466
483
  }
467
484
  }
485
+ // TODO: We don't check enum$, yet! We first need to fix expansion for
486
+ // `cast(elem as EnumType)` (see #9421)
468
487
  for (const id in art.elements$) {
469
488
  const selem = art.elements$[id]; // specified element
470
489
  if (!selem.$replacement) {
@@ -569,10 +588,6 @@ function populate( model ) {
569
588
  const siblings = wildcardSiblings( columns, query );
570
589
  expandWildcard( col, siblings, inlineHead, query );
571
590
  }
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
591
  // If neither expression (value), expand nor new association.
577
592
  if (!col.value && !col.expand && !(col.target && col.type))
578
593
  continue; // error should have been reported by parser
@@ -645,18 +660,28 @@ function populate( model ) {
645
660
  // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
646
661
  // elem.value)
647
662
  // TODO: make this resolvePath() also part of directType() ?!
648
- if (!origin)
663
+ if (!origin || elem.expand)
649
664
  return;
665
+ // TODO: or should we push elems with `expand` sibling to extra list for better messages?
650
666
  if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
651
667
  forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
652
668
  }
653
669
 
654
670
  // now set things which are necessary for later sub phases:
655
671
  const nav = pathNavigation( elem.value );
656
- if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
672
+ const item = elem.value.path[elem.value.path.length - 1];
673
+ if (nav.navigation && nav.item === item) {
674
+ // sourceElem, alias.sourceElem, mixin:
657
675
  // redirectImplicitly( elem, origin );
658
676
  pushLink( nav.navigation, '_projections', elem );
659
677
  }
678
+ else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
679
+ const hpath = elem._pathHead.value?.path;
680
+ const head = hpath?.length === 1 && hpath[0]._navigation;
681
+ // Alias .{ elem }
682
+ if (head?.kind === '$tableAlias')
683
+ pushLink( head.elements[item.id], '_projections', elem );
684
+ }
660
685
  }
661
686
 
662
687
  function initKey( key, name, elem ) {
@@ -701,7 +726,7 @@ function populate( model ) {
701
726
  // Object.keys(env),Object.keys(elements))
702
727
  for (const name in env) {
703
728
  const navElem = env[name];
704
- // TODO: if it is an array, filter out those with masked
729
+ // TODO: remove all access to masked (use 'grep')
705
730
  if (excludingDict[name] || navElem.masked && navElem.masked.val)
706
731
  continue;
707
732
  const sibling = siblingElements[name];
@@ -770,13 +795,17 @@ function populate( model ) {
770
795
  path && path[path.length - 1].id !== sibling.name.id) { // or renamed
771
796
  const { id } = sibling.name;
772
797
  if (Array.isArray(navElem)) {
798
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
773
799
  info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
774
- 'This select item replaces $(ID) from two or more sources' );
800
+ // eslint-disable-next-line max-len
801
+ 'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
775
802
  }
776
803
  else {
804
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
777
805
  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)' );
806
+ { id, alias: navElem._parent.name.id, keyword: 'excluding' },
807
+ // eslint-disable-next-line max-len
808
+ 'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
780
809
  }
781
810
  }
782
811
  }
@@ -787,9 +816,13 @@ function populate( model ) {
787
816
  queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
788
817
  setArtifactLink( path[0], origin );
789
818
  setLink( queryElem, '_origin', origin );
790
- // TODO: set _projections when top-level?
819
+ // set _projections when inline with table alias:
820
+ const alias = pathHead?.value?.path?.[0]?._navigation;
821
+ if (alias?.kind === '$tableAlias')
822
+ pushLink( alias.elements[name], '_projections', queryElem );
791
823
  }
792
824
 
825
+ // called by expandWildcard():
793
826
  function setElementOrigin( queryElem, navElem, name, location ) {
794
827
  const sourceElem = navElem._origin;
795
828
  const alias = navElem._parent;
@@ -892,7 +925,7 @@ function populate( model ) {
892
925
  preferredElemScope( target, service, elem, assoc._main || assoc );
893
926
  const exposed = minimalExposure( target, service, elemScope );
894
927
 
895
- if (!exposed.length && elemScope !== true) {
928
+ if (!exposed.length) {
896
929
  const origTarget = target;
897
930
  if (isAutoExposed( target ))
898
931
  target = createAutoExposed( origTarget, service, elemScope );
@@ -930,7 +963,7 @@ function populate( model ) {
930
963
  for (const proj of exposed) {
931
964
  // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
932
965
  message( 'redirected-implicitly-ambiguous',
933
- [ weakLocation( proj.location ), proj ],
966
+ [ weakLocation( proj.name.location ), proj ],
934
967
  {
935
968
  '#': withAnno && 'justOne',
936
969
  target,
@@ -1021,17 +1054,6 @@ function populate( model ) {
1021
1054
  function scopedExposure( descendants, elemScope, target ) {
1022
1055
  if (!elemScope) // no scoped redirections
1023
1056
  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
1057
  // try scope as target first, even if it has @cds.redirection.target: false
1036
1058
  if (isDirectProjection( elemScope, target ))
1037
1059
  return [ elemScope ];
@@ -1059,7 +1081,6 @@ function populate( model ) {
1059
1081
 
1060
1082
  function isDirectProjection( proj, base ) {
1061
1083
  return proj.kind === 'entity' && // not event
1062
- projectionAncestor( base, proj.params ) && // same params
1063
1084
  // direct proj (TODO: or should we add them to another list?)
1064
1085
  proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1065
1086
  proj._from && proj._from.length === 1 &&
@@ -233,7 +233,7 @@ function propagate( model ) {
233
233
  }
234
234
 
235
235
  function onlyViaParent( prop, target, source ) {
236
- if (target.$inferred === 'proxy' || target.$inferred === 'expand-element')
236
+ if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
237
237
  // assocs and enums do not have 'include'
238
238
  always( prop, target, source );
239
239
  }
@@ -69,7 +69,6 @@ const {
69
69
  } = require('./utils');
70
70
 
71
71
  const detectCycles = require('./cycle-detector');
72
- const layers = require('./moduleLayers');
73
72
 
74
73
  const $location = Symbol.for('cds.$location');
75
74
 
@@ -91,11 +90,14 @@ function resolve( model ) {
91
90
  defineAnnotations,
92
91
  attachAndEmitValidNames,
93
92
  lateExtensions,
93
+ applyTypeExtensions,
94
94
  effectiveType,
95
95
  directType,
96
96
  resolveType,
97
97
  resolveTypeArgumentsUnchecked,
98
98
  populateQuery,
99
+ layeredAssignments,
100
+ assignmentsOfHighestLayers,
99
101
  } = model.$functions;
100
102
  const { environment } = model.$volatileFunctions;
101
103
  Object.assign( model.$functions, {
@@ -168,15 +170,7 @@ function resolve( model ) {
168
170
  for (const name in query.elements) {
169
171
  const elem = query.elements[name];
170
172
  // no key prop for duplicate elements or additional specified elements:
171
- if (elem.$duplicates || !elem.value)
172
- continue;
173
- const nav = pathNavigation( elem.value );
174
- if (!nav.navigation)
175
- continue; // undefined, expr, $magic, :const, $self (!), $self.elem
176
- const { item } = nav;
177
- if (item !== elem.value.path[elem.value.path.length - 1])
178
- continue; // having selected a sub elem / navigated along assoc
179
- const { key } = item._artifact;
173
+ const key = !elem.$duplicates && !elem.expand && inheritedSourceKeyProp( elem );
180
174
  if (key) {
181
175
  if (!doIt)
182
176
  return true;
@@ -186,6 +180,20 @@ function resolve( model ) {
186
180
  return false;
187
181
  }
188
182
 
183
+ function inheritedSourceKeyProp( { value, _pathHead } ) {
184
+ if (!value || !value.path)
185
+ return null;
186
+ const nav = pathNavigation( value );
187
+ const item = value.path[value.path.length - 1];
188
+ if (nav.navigation && nav.item === item)
189
+ return item._artifact?.key;
190
+ if (value.path.length !== 1 || _pathHead?.kind !== '$inline')
191
+ return null;
192
+ const hpath = _pathHead.value?.path;
193
+ const head = hpath?.length === 1 && hpath[0]._navigation;
194
+ return head?.kind === '$tableAlias' && item._artifact?.key;
195
+ }
196
+
189
197
  function primarySourceNavigation( aliases ) {
190
198
  for (const name in aliases)
191
199
  return aliases[name].elements;
@@ -240,6 +248,7 @@ function resolve( model ) {
240
248
  function selectTest( expr ) {
241
249
  const art = withAssociation( expr, targetMaxNotOne );
242
250
  if (art) {
251
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
243
252
  info( 'query-navigate-many', [ art.location, query ], { art },
244
253
  {
245
254
  // eslint-disable-next-line max-len
@@ -314,9 +323,10 @@ function resolve( model ) {
314
323
  // TODO: shouldn't be this part of populate.js ?
315
324
  const items = {
316
325
  location: weakLocation( (obj.type || obj).location ),
317
- $inferred: 'expand-items',
326
+ $inferred: 'expanded',
318
327
  };
319
328
  setLink( items, '_outer', obj );
329
+ setLink( items, '_parent', obj._parent );
320
330
  setLink( items, '_origin', type.items );
321
331
  obj.items = items;
322
332
  obj.$expand = 'origin';
@@ -511,7 +521,7 @@ function resolve( model ) {
511
521
  setArtifactLink( ext.name, art );
512
522
 
513
523
  if (art) {
514
- if (art.kind === 'annotate')
524
+ if (ext.kind === 'annotate' || ext.kind === 'extend')
515
525
  checkAnnotate( ext, art );
516
526
  defineAnnotations( ext, art, ext._block, ext.kind );
517
527
  // eslint-disable-next-line no-shadow
@@ -555,7 +565,7 @@ function resolve( model ) {
555
565
  }
556
566
  }
557
567
  }
558
- if (art && art._annotate) {
568
+ if (art?._annotate) {
559
569
  if (art.kind === 'action' || art.kind === 'function') {
560
570
  expandParameters( art );
561
571
  if (art.returns)
@@ -579,6 +589,15 @@ function resolve( model ) {
579
589
  // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
580
590
  // }
581
591
  }
592
+
593
+ if (art?._extendType) {
594
+ // Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
595
+ // TODO: If we allow extending included elements, we may need custom $expand,
596
+ // similar to annotate above.
597
+ art._extendType.forEach(resolveRefs);
598
+ applyTypeExtensions(art);
599
+ }
600
+
582
601
  return;
583
602
 
584
603
  function notFound( msgId, location, address, args, validDict ) {
@@ -632,7 +651,7 @@ function resolve( model ) {
632
651
  // not to create proxies
633
652
  const orig = origin.params[name];
634
653
  linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
635
- .$inferred = 'expand-param';
654
+ .$inferred = 'expanded';
636
655
  }
637
656
  }
638
657
  if (!art.returns && origin.returns) {
@@ -642,7 +661,7 @@ function resolve( model ) {
642
661
  name: Object.assign( {}, art.name, { id: '', param: '', location } ),
643
662
  kind: 'param',
644
663
  location,
645
- $inferred: 'expand-param',
664
+ $inferred: 'expanded',
646
665
  };
647
666
  setLink( art.returns, '_parent', art );
648
667
  setLink( art.returns, '_main', art._main || art );
@@ -751,90 +770,21 @@ function resolve( model ) {
751
770
  : Object.assign( {}, scheduledAssignments[scheduledAssignments.length - 1], result );
752
771
  }
753
772
 
754
- // Group assignments by their layers. An assignment provided with a definition
755
- // is considered to be provided in a layer named '', the lowest layer.
756
- // TODO: make this usable for extend (elements), too =
757
- // do not use $priority, make assignments on define do not have own _block
758
- function layeredAssignments( assignment ) {
759
- const layered = Object.create(null);
760
- for (const a of assignment) {
761
- const layer = a.$priority && layers.layer( a );
762
- // just consider layer if Extend/Annotate, not Define
763
- const name = (layer) ? layer.realname : '';
764
- const done = layered[name];
765
- if (done)
766
- done.assignments.push( a );
767
- else
768
- layered[name] = { name, layer, assignments: [ a ] };
769
- // TODO: file - if set: unique in layer
770
- }
771
- return layered;
772
- }
773
-
774
- // Return assignments of the highest layers.
775
- // Also return whether there could be an issue:
776
- // - false: there is just one assignment
777
- // - 'unrelated': there is just one assignment per layer
778
- // - true: there is at least one layer with two or more assignments
779
- // TODO: make this usable for extend (elements), too
780
- function assignmentsOfHighestLayers( layeredAnnos ) {
781
- const layerNames = Object.keys( layeredAnnos );
782
- // console.log('HIB:',layerNames)
783
- if (layerNames.length <= 1) {
784
- const name = layerNames[0];
785
- const { assignments } = layeredAnnos[name] || { assignments: [] };
786
- delete layeredAnnos[name];
787
- return { assignments, issue: assignments.length > 1 };
788
- }
789
-
790
- // collect all layers which are lower than another layer
791
- const allExtends = Object.create(null);
792
- allExtends[''] = {}; // the "Define" layer
793
- for (const name of layerNames) {
794
- if (name) // not the "Define" layer
795
- Object.assign( allExtends, layeredAnnos[name].layer._layerExtends );
796
- }
797
- // console.log('HIE:',Object.keys(allExtends))
798
- const assignments = [];
799
- const highest = [];
800
- for (const name of layerNames) {
801
- if (!(name in allExtends)) {
802
- const layer = layeredAnnos[name];
803
- delete layeredAnnos[name];
804
- highest.push( layer );
805
- assignments.push( ...layer.assignments );
806
- }
807
- }
808
- assignments.sort( compareAssignments );
809
- const good = highest.every( layer => layer.assignments.length === 1 );
810
- // TODO: use layer.file instead
811
- const issue = !good || highest.length > 1 && 'unrelated';
812
- // console.log('HI:',highest.map(l=>l.name),issue,issue&&assignments)
813
- return { assignments, issue };
814
- }
815
-
816
- function compareAssignments( a, b ) {
817
- const fileA = layers.realname( a._block );
818
- const fileB = layers.realname( b._block );
819
- if (fileA !== fileB)
820
- return (fileA > fileB) ? 1 : -1;
821
- return (a?.location?.line || 0) - (b?.location?.line || 0) ||
822
- (a?.location?.col || 0) - (b?.location?.col || 0);
823
- }
824
-
825
773
  function applyAssignment( previousAnno, anno, art, annoName ) {
774
+ const hasBase = previousAnno?.literal === 'array';
826
775
  if (!previousAnno) {
827
- if (!annotationHasEllipsis( anno ))
776
+ const firstEllipsis = annotationHasEllipsis( anno );
777
+ if (!firstEllipsis)
828
778
  return anno;
829
779
  if (anno.$priority) { // already complained about with Define
830
- message( 'anno-unexpected-ellipsis-layers', // TODO: better location
831
- [ anno.name.location, art ], { code: '...' } );
780
+ const loc = firstEllipsis.location || anno.name.location;
781
+ message( 'anno-unexpected-ellipsis-layers', [ loc, art ], { code: '...' } );
832
782
  }
833
783
  previousAnno = { val: [] };
834
784
  }
835
785
  else if (previousAnno.literal !== 'array') {
836
- error( 'anno-mismatched-ellipsis', // TODO: better location
837
- [ anno.name.location, art ], { code: '...' } );
786
+ // TODO: If we introduce sub-messages, point to the non-array base value.
787
+ error( 'anno-mismatched-ellipsis', [ anno.name.location, art ], { code: '...' } );
838
788
  previousAnno = { val: [] };
839
789
  }
840
790
  const previousValue = previousAnno.val;
@@ -855,7 +805,8 @@ function resolve( model ) {
855
805
  break;
856
806
  }
857
807
  }
858
- if (upToSpec) { // non-matched UP TO
808
+ if (upToSpec && hasBase) {
809
+ // non-matched UP TO; if there is no base to apply to, there is already an error.
859
810
  warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
860
811
  'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
861
812
  }
@@ -1045,7 +996,7 @@ function resolve( model ) {
1045
996
  else if (obj.type && !obj.type.$inferred && art._parent && art._parent.kind === 'select') {
1046
997
  // New association in views, i.e. parent is a query.
1047
998
  error( 'query-expected-on-condition', [ obj.target.location, art ], {},
1048
- 'Expected on-condition for published association' );
999
+ 'Expected ON-condition for published association' );
1049
1000
  return; // avoid subsequent errors
1050
1001
  }
1051
1002
  else if (target && !obj.foreignKeys && target.kind === 'entity') {
@@ -1135,7 +1086,7 @@ function resolve( model ) {
1135
1086
  if (!elem.on && origType.on) {
1136
1087
  error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1137
1088
  // TODO: Better text ?
1138
- 'The ON condition is not rewritten here - provide an explicit ON condition' );
1089
+ 'The ON-condition is not rewritten here - provide an explicit ON-condition' );
1139
1090
  return;
1140
1091
  }
1141
1092
  }
@@ -1148,6 +1099,7 @@ function resolve( model ) {
1148
1099
  if (!elem.target.$inferred && !elem.on && !elem.foreignKeys) {
1149
1100
  // Only a managed redirection gets this info message. Because otherwise
1150
1101
  // we'd have to check whether on-condition/foreignKeys are the same.
1102
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1151
1103
  info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1152
1104
  'The redirected target is the original $(ART)' );
1153
1105
  }
@@ -1162,12 +1114,17 @@ function resolve( model ) {
1162
1114
  if (!from)
1163
1115
  return; // parse error - TODO: or UNION?
1164
1116
  if (!from.path) {
1165
- warning( 'redirected-to-complex', [ elem.target.location, elem ],
1166
- { art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
1167
- {
1168
- std: 'Redirection involves the complex view $(ART)',
1169
- target: 'The redirected target $(ART) is a complex view',
1170
- });
1117
+ const isTarget = target === elem.target._artifact;
1118
+ const op = from.op?.val || target.query.op?.val;
1119
+ const variant = (!isTarget && 'std') || (op && 'targetOp') || 'target';
1120
+ // ID published! Used in stakeholder project; if renamed, add to oldMessageIds
1121
+ info( 'redirected-to-complex', [ elem.target.location, elem ],
1122
+ { art: target, '#': variant, keyword: op || '' }, {
1123
+ std: 'Redirection involves the complex view $(ART)',
1124
+ target: 'The redirected target $(ART) is a complex view',
1125
+ // eslint-disable-next-line max-len
1126
+ targetOp: 'The redirected target $(ART) is a complex view with $(KEYWORD)',
1127
+ });
1171
1128
  break;
1172
1129
  }
1173
1130
  target = from._artifact;
@@ -1301,13 +1258,13 @@ function resolve( model ) {
1301
1258
 
1302
1259
  let variant;
1303
1260
  if (type.builtin)
1304
- // `.type` is already a builtin: use a nicer message.
1261
+ // `.type` is already a builtin: use a nicer message.
1305
1262
  variant = 'builtin';
1306
1263
  else if (effectiveTypeArt.builtin)
1307
- // base type is a builtin, i.e. a scalar
1264
+ // base type is a builtin, i.e. a scalar
1308
1265
  variant = 'type';
1309
1266
  else
1310
- // effectiveType is not a builtin -> array or structured
1267
+ // effectiveType is not a builtin -> array or structured
1311
1268
  variant = 'non-scalar';
1312
1269
 
1313
1270
  error('type-unexpected-argument', [ artWithType[param].location, artWithType ], {
@@ -1432,7 +1389,7 @@ function resolve( model ) {
1432
1389
  }
1433
1390
  const exp = (expected === 'from') ? 'expr' : expected;
1434
1391
  if (Array.isArray(dict)) {
1435
- message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
1392
+ message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ], {},
1436
1393
  'Named parameters must be provided for the entity' );
1437
1394
  for (const a of dict)
1438
1395
  resolveExpr( a, exp, user, extDict );