@sap/cds-compiler 4.8.0 → 4.9.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 (92) hide show
  1. package/CHANGELOG.md +29 -4
  2. package/bin/cds_remove_invalid_whitespace.js +135 -0
  3. package/bin/cds_update_annotations.js +180 -0
  4. package/bin/cds_update_identifiers.js +3 -4
  5. package/bin/cdsc.js +14 -1
  6. package/doc/CHANGELOG_BETA.md +19 -0
  7. package/lib/api/main.js +59 -24
  8. package/lib/api/options.js +12 -1
  9. package/lib/api/validate.js +1 -5
  10. package/lib/base/builtins.js +27 -0
  11. package/lib/base/message-registry.js +32 -19
  12. package/lib/base/messages.js +50 -19
  13. package/lib/base/model.js +4 -5
  14. package/lib/checks/actionsFunctions.js +2 -2
  15. package/lib/checks/annotationsOData.js +3 -0
  16. package/lib/checks/defaultValues.js +5 -2
  17. package/lib/checks/queryNoDbArtifacts.js +3 -2
  18. package/lib/checks/validator.js +2 -34
  19. package/lib/compiler/assert-consistency.js +8 -2
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +34 -22
  22. package/lib/compiler/extend.js +33 -10
  23. package/lib/compiler/index.js +0 -1
  24. package/lib/compiler/lsp-api.js +5 -0
  25. package/lib/compiler/propagator.js +21 -18
  26. package/lib/compiler/resolve.js +44 -28
  27. package/lib/compiler/shared.js +60 -20
  28. package/lib/compiler/tweak-assocs.js +13 -88
  29. package/lib/compiler/xpr-rewrite.js +689 -0
  30. package/lib/edm/annotations/genericTranslation.js +80 -60
  31. package/lib/edm/edm.js +4 -4
  32. package/lib/edm/edmInboundChecks.js +33 -0
  33. package/lib/edm/edmPreprocessor.js +9 -6
  34. package/lib/gen/Dictionary.json +129 -14
  35. package/lib/gen/language.checksum +1 -1
  36. package/lib/gen/language.interp +1 -1
  37. package/lib/gen/languageParser.js +1523 -1518
  38. package/lib/json/from-csn.js +13 -4
  39. package/lib/json/to-csn.js +10 -11
  40. package/lib/language/genericAntlrParser.js +14 -6
  41. package/lib/main.d.ts +67 -14
  42. package/lib/main.js +1 -0
  43. package/lib/model/cloneCsn.js +6 -3
  44. package/lib/model/csnRefs.js +12 -7
  45. package/lib/model/csnUtils.js +13 -7
  46. package/lib/model/enrichCsn.js +3 -1
  47. package/lib/model/revealInternalProperties.js +2 -1
  48. package/lib/model/sortViews.js +14 -6
  49. package/lib/modelCompare/compare.js +33 -34
  50. package/lib/optionProcessor.js +27 -2
  51. package/lib/render/DuplicateChecker.js +6 -6
  52. package/lib/render/manageConstraints.js +1 -0
  53. package/lib/render/toCdl.js +3 -1
  54. package/lib/transform/db/applyTransformations.js +33 -0
  55. package/lib/transform/db/constraints.js +1 -1
  56. package/lib/transform/db/expansion.js +8 -3
  57. package/lib/transform/db/groupByOrderBy.js +2 -2
  58. package/lib/transform/db/temporal.js +6 -3
  59. package/lib/transform/db/transformExists.js +2 -2
  60. package/lib/transform/effective/annotations.js +194 -0
  61. package/lib/transform/effective/main.js +6 -8
  62. package/lib/transform/effective/misc.js +31 -10
  63. package/lib/transform/forOdata.js +23 -7
  64. package/lib/transform/forRelationalDB.js +1 -1
  65. package/lib/transform/localized.js +7 -6
  66. package/lib/transform/odata/flattening.js +189 -106
  67. package/lib/transform/odata/toFinalBaseType.js +1 -1
  68. package/lib/transform/odata/typesExposure.js +15 -12
  69. package/lib/transform/parseExpr.js +4 -4
  70. package/lib/transform/transformUtils.js +40 -37
  71. package/lib/transform/translateAssocsToJoins.js +47 -47
  72. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
  73. package/package.json +1 -1
  74. package/share/messages/anno-missing-rewrite.md +45 -0
  75. package/share/messages/message-explanations.json +1 -0
  76. package/bin/.eslintrc.json +0 -17
  77. package/lib/api/.eslintrc.json +0 -37
  78. package/lib/checks/.eslintrc.json +0 -31
  79. package/lib/compiler/.eslintrc.json +0 -8
  80. package/lib/edm/.eslintrc.json +0 -46
  81. package/lib/inspect/.eslintrc.json +0 -4
  82. package/lib/json/.eslintrc.json +0 -4
  83. package/lib/language/.eslintrc.json +0 -4
  84. package/lib/model/.eslintrc.json +0 -13
  85. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  86. package/lib/render/.eslintrc.json +0 -22
  87. package/lib/transform/.eslintrc.json +0 -13
  88. package/lib/transform/db/.eslintrc.json +0 -41
  89. package/lib/transform/draft/.eslintrc.json +0 -4
  90. package/lib/transform/effective/.eslintrc.json +0 -4
  91. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  92. package/lib/utils/.eslintrc.json +0 -7
@@ -19,6 +19,7 @@ const {
19
19
  } = require('../base/model');
20
20
  const { CompilerAssertion } = require('../base/error');
21
21
  const { typeParameters } = require('./builtins');
22
+ const { propagationRules } = require('../base/builtins');
22
23
 
23
24
  const $location = Symbol.for( 'cds.$location' );
24
25
 
@@ -69,6 +70,13 @@ function check( model ) {
69
70
  } );
70
71
  }
71
72
 
73
+ function* iterateAnnotations( art ) {
74
+ for (const prop in art) {
75
+ if (prop.charAt(0) === '@')
76
+ yield prop;
77
+ }
78
+ }
79
+
72
80
  function checkGenericConstruct( art ) {
73
81
  checkName( art );
74
82
  checkTypeArguments( art );
@@ -76,11 +84,9 @@ function check( model ) {
76
84
  if (art.value && !art.$calcDepElement && art.type)
77
85
  checkTypeCast( art.value, art );
78
86
 
79
- if (model.vocabularies) {
80
- Object.keys( art )
81
- .filter( a => a.startsWith( '@' ) )
82
- .forEach( a => checkAnnotationAssignment1( art, art[a] ) );
83
- }
87
+ for (const anno of iterateAnnotations( art ))
88
+ checkAnnotationAssignment1( art, art[anno] );
89
+
84
90
  checkTypeStructure( art );
85
91
  checkAssociation( art ); // type def could be assoc
86
92
  if (art.kind === 'enum')
@@ -236,6 +242,8 @@ function check( model ) {
236
242
  ? xpr.args[0]?._artifact
237
243
  : xpr._artifact;
238
244
  const type = isCast ? xpr.type : user.type;
245
+ if (!isCast && type.$inferred)
246
+ return; // e.g. $inferred:'generated'
239
247
  if (elem && type) { // has explicit type
240
248
  if (type._artifact?.elements)
241
249
  error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
@@ -264,9 +272,11 @@ function check( model ) {
264
272
  // "key" keyword at localized element in SELECT list.
265
273
  // TODO: not in inferred elements, but also inside aspects
266
274
  if (elem.key?.val && elem._main?.query) {
275
+ // either the element was casted to localized (no `_origin`) or
267
276
  // original element is localized but not key, as that would have
268
277
  // already resulted in a warning by localized.js
269
- if (elem._origin?.localized?.val && !elem._origin.key?.val) {
278
+ if ((!elem._origin && elem.localized?.val) ||
279
+ (elem._origin?.localized?.val && !elem._origin.key?.val)) {
270
280
  warning( 'def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
271
281
  'Keyword $(KEYWORD) is ignored for primary keys' );
272
282
  }
@@ -860,21 +870,37 @@ function check( model ) {
860
870
  return false;
861
871
  }
862
872
 
873
+ /**
874
+ * Returns true if the given annotation accepts expressions as values.
875
+ *
876
+ * @param {object} anno
877
+ * @param {XSN.Artifact} art
878
+ * @returns {boolean}
879
+ */
880
+ function checkAnnotationAcceptsExpressions( anno, art ) {
881
+ const name = anno.name?.id;
882
+ if (!name)
883
+ return true;
884
+ if (!propagationRules[`@${ name }`])
885
+ return true;
886
+ error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
887
+ 'Unexpected expression as value for $(ANNO)' );
888
+ return false;
889
+ }
863
890
 
864
- // Former checkAnnotationAssignments.js ------------------------------------
865
-
866
- // Check the annotation assignments (if any) of 'annotatable', possibly using annotation
867
- // definitions from 'model'. Report errors on 'options.messages.
868
- //
869
- // TODO: rework completely!
870
- // TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
871
-
872
- // Has been slightly adapted for model.vocabularies but comments need to be
873
- // adapted, etc.
874
891
  function checkAnnotationAssignment1( art, anno ) {
875
- if (art.$contains?.$annotation)
876
- checkAnnotationExpressions( anno, art );
892
+ if (art.$contains?.$annotation && anno.kind === '$annotation') {
893
+ if (checkAnnotationAcceptsExpressions( anno, art ))
894
+ checkAnnotationExpressions( anno, art );
895
+ }
896
+
897
+ if (!model.vocabularies)
898
+ return;
877
899
 
900
+ // Has been slightly adapted for model.vocabularies but comments need to be
901
+ // adapted, etc.
902
+ // TODO: rework completely!
903
+ // TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
878
904
  // Sanity checks (ignore broken assignments)
879
905
  if (!anno.name?.id)
880
906
  return;
@@ -213,6 +213,12 @@ function define( model ) {
213
213
  dictForEach( model.$collectedExtensions, e => e._extensions.forEach( initExtension ) );
214
214
 
215
215
  addI18nBlocks();
216
+
217
+ const { $self } = model.definitions;
218
+ if ($self) {
219
+ message( 'name-deprecated-$self', [ $self.location, $self ], { name: '$self' },
220
+ 'Do not use $(NAME) as name for an artifact definition' );
221
+ }
216
222
  }
217
223
 
218
224
  // Phase 1: ----------------------------------------------------------------
@@ -228,14 +234,15 @@ function define( model ) {
228
234
  if (!src.kind)
229
235
  src.kind = 'source';
230
236
 
231
- let { namespace } = src;
237
+ let namespace = src.namespace?.name;
232
238
  let prefix = '';
233
239
  if (namespace?.path && !namespace.path.broken) {
234
240
  namespace.id = pathName( namespace.path );
235
241
  prefix = `${ namespace.id }.`;
236
242
  }
237
243
  if (isInReservedNamespace( prefix )) {
238
- error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], { name: 'cds' },
244
+ error( 'reserved-namespace-cds', [ src.namespace.name.location, src.namespace.name ],
245
+ { name: 'cds' },
239
246
  'The namespace $(NAME) is reserved for CDS builtins' );
240
247
  namespace = null;
241
248
  }
@@ -523,7 +530,7 @@ function define( model ) {
523
530
  if (src.$frontend && src.$frontend !== 'cdl')
524
531
  return;
525
532
  if (src.namespace) {
526
- const decl = src.namespace;
533
+ const decl = src.namespace.name;
527
534
  if (!decl.id) // parsing may have failed
528
535
  return;
529
536
  if (!model.definitions[decl.id]) {
@@ -562,8 +569,8 @@ function define( model ) {
562
569
  initArtifactParentLink( art, model.definitions );
563
570
  const block = art._block;
564
571
  checkRedefinition( art );
565
- initMembers( art, art, block );
566
572
  initDollarSelf( art ); // $self
573
+ initMembers( art, art, block );
567
574
  if (art.params)
568
575
  initDollarParameters( art ); // $parameters
569
576
 
@@ -636,6 +643,7 @@ function define( model ) {
636
643
  name: { id: '$parameters' },
637
644
  kind: '$parameters',
638
645
  location: art.location,
646
+ deprecated: true, // hide in code completion
639
647
  };
640
648
  setLink( parameters, '_parent', art );
641
649
  setLink( parameters, '_main', art );
@@ -679,8 +687,14 @@ function define( model ) {
679
687
  setLink( self, '_origin', query );
680
688
  setLink( self, '_parent', query );
681
689
  setLink( self, '_main', query._main );
690
+
691
+ const projection = { ...self, deprecated: true }; // hide in code completion
692
+ setLink( projection, '_origin', query );
693
+ setLink( projection, '_parent', query );
694
+ setLink( projection, '_main', query._main );
695
+
682
696
  query.$tableAliases.$self = self;
683
- query.$tableAliases.$projection = self;
697
+ query.$tableAliases.$projection = projection;
684
698
  }
685
699
  initSubQuery( query ); // check for SELECT clauses after from / mixin
686
700
  }
@@ -858,12 +872,13 @@ function define( model ) {
858
872
  }
859
873
 
860
874
  function initMixins( query, art ) {
861
- // TODO: re-check if mixins have already duplicates
862
- for (const name in query.mixin) {
863
- const mixin = query.mixin[name];
875
+ forEachInOrder( query, 'mixin', initMixin );
876
+
877
+ function initMixin( mixin, name ) {
878
+ setLink( mixin, '_block', art._block );
879
+ setMemberParent( mixin, name, query );
880
+ checkRedefinition( mixin );
864
881
  if (!(mixin.$duplicates)) {
865
- setMemberParent( mixin, name, query );
866
- setLink( mixin, '_block', art._block );
867
882
  // TODO: do some initMembers() ? If people had annotation
868
883
  // assignments on the mixin... (also for future mixin definitions
869
884
  // with generated values)
@@ -924,14 +939,14 @@ function define( model ) {
924
939
  setLink( col, '_block', parent._block );
925
940
  if (col.inline) { // `@anno elem.{ * }` does not work
926
941
  if (col.doc) {
927
- warning( 'syntax-ignoring-anno', [ col.doc.location, col ],
942
+ message( 'syntax-ignoring-anno', [ col.doc.location, col ],
928
943
  { '#': 'doc', code: '.{ ‹inline› }' } );
929
944
  }
930
945
  // col.$annotations no available for CSN input, have to search.
931
- // Warning about first annotation should be enough to avoid spam.
946
+ // Message about first annotation should be enough to avoid spam.
932
947
  const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
933
948
  if (firstAnno) {
934
- warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
949
+ message( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
935
950
  { code: '.{ ‹inline› }' } );
936
951
  }
937
952
  }
@@ -1151,7 +1166,7 @@ function define( model ) {
1151
1166
  checkRedefinition( elem );
1152
1167
  initMembers( elem, elem, bl, initExtensions );
1153
1168
  if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
1154
- initBoundSelfParam( elem.params );
1169
+ initBoundSelfParam( elem.params, elem._main );
1155
1170
 
1156
1171
  // for a correct home path, setMemberParent needed to be called
1157
1172
 
@@ -1177,27 +1192,24 @@ function define( model ) {
1177
1192
  }
1178
1193
  }
1179
1194
 
1180
- function initBoundSelfParam( params ) {
1195
+ function initBoundSelfParam( params, main ) {
1181
1196
  if (!params)
1182
1197
  return;
1183
1198
  if (boundSelfParamType === true) { // first try
1184
1199
  const def = model.definitions.$self;
1185
1200
  if (def) {
1186
- // TODO v5: bring this always, probably even as error
1187
- warning( 'name-deprecated-for-artifacts', [ def.location, def ], { name: '$self' },
1188
- 'Do not use $(NAME) as name for an artifact definition' );
1189
1201
  boundSelfParamType = false;
1190
1202
  return;
1191
1203
  }
1192
- const location = { file: '' };
1193
- boundSelfParamType = { name: { id: '$self', location }, location };
1204
+ boundSelfParamType = '$self';
1194
1205
  }
1195
1206
  const first = params[Object.keys( params )[0] || ''];
1196
1207
  const type = first?.type || first?.items?.type; // this sequence = no derived type
1197
1208
  const path = type?.path;
1198
1209
  if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
1199
- setLink( type, '_artifact', boundSelfParamType );
1200
- setLink( path[0], '_artifact', boundSelfParamType );
1210
+ const { $self } = main.$tableAliases;
1211
+ setLink( type, '_artifact', $self );
1212
+ setLink( path[0], '_artifact', $self );
1201
1213
  }
1202
1214
  }
1203
1215
 
@@ -9,6 +9,7 @@ const {
9
9
  forEachDefinition,
10
10
  forEachMember,
11
11
  isDeprecatedEnabled,
12
+ isBetaEnabled,
12
13
  } = require('../base/model');
13
14
  const { dictAdd, pushToDict } = require('../base/dictionaries');
14
15
  const { kindProperties, dictKinds } = require('./base');
@@ -71,6 +72,7 @@ function extend( model ) {
71
72
  } );
72
73
 
73
74
  const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
75
+ const isV5preview = isBetaEnabled( model.options, 'v5preview' );
74
76
 
75
77
  sortModelSources();
76
78
  const extensionsDict = Object.create( null ); // TODO TMP
@@ -222,6 +224,7 @@ function extend( model ) {
222
224
  code: 'extend … with definitions',
223
225
  keyword: 'extend service',
224
226
  };
227
+ // TODO(v5): Discuss: make this an error?
225
228
  warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
226
229
  std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
227
230
  annotate: 'There is no artifact $(ART), use $(CODE) instead',
@@ -818,7 +821,7 @@ function extend( model ) {
818
821
  annotate[prop] = art[prop];
819
822
  }
820
823
  }
821
- if (extensions.length === 1) { // i.e. no proper location if from more than one extensions
824
+ if (extensions.length === 1) { // i.e. no proper location if from more than one extension
822
825
  annotate.location = extensions[0].location;
823
826
  annotate.name.location = extensions[0].name.location;
824
827
  }
@@ -829,18 +832,38 @@ function extend( model ) {
829
832
  }
830
833
 
831
834
  function checkRemainingMainExtensions( art, ext, localized ) {
832
- if (localized) // TODO v5: ignore only for annotate
835
+ if (localized) {
836
+ if (isV5preview && ext.kind === 'extend') {
837
+ // In v5, reject any `extend` on localized.
838
+ error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
839
+ { '#': 'localized', keyword: 'annotate' } );
840
+ }
833
841
  return;
842
+ }
843
+
834
844
  if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
835
845
  return;
836
- if (art?.kind === 'namespace') {
837
- // TODO: not at all different to having no definition
838
- info( 'anno-namespace', [ ext.name.location, ext ], {}, // TODO: better location?
839
- 'Namespaces can\'t be annotated' );
840
- }
841
- else if (art?.builtin) {
842
- info( 'anno-builtin', [ ext.name.location, ext ], {}, // TODO: better location?
843
- 'Builtin types should not be annotated. Use custom type instead' );
846
+
847
+ if (art?.builtin) {
848
+ info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
849
+ }
850
+ else if (art?.kind === 'namespace') {
851
+ const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
852
+ const firstAnno = ext[hasAnnotations];
853
+ // In v5, extending namespaces is only allowed for `extend with definitions`.
854
+ // Neither annotations nor other extensions are allowed.
855
+ // Non-artifact extensions are reported in resolvePath() already (for v5).
856
+ if ((hasAnnotations || !ext.artifacts) ) {
857
+ if (isV5preview) {
858
+ error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
859
+ '#': 'namespace', art: ext,
860
+ } );
861
+ }
862
+ else {
863
+ info( 'anno-namespace', [ (firstAnno?.name || ext.name).location, ext ], {},
864
+ 'Namespaces can\'t be annotated nor extended' );
865
+ }
866
+ }
844
867
  }
845
868
  }
846
869
 
@@ -56,7 +56,6 @@ class InvocationError extends Error {
56
56
  super( ...args );
57
57
  this.code = 'ERR_CDS_COMPILER_INVOCATION';
58
58
  this.errors = errs;
59
- this.hasBeenReported = false;
60
59
  }
61
60
  }
62
61
 
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ // Placeholder for future LSP API.
4
+
5
+ module.exports = {};
@@ -20,30 +20,16 @@ const {
20
20
  linkToOrigin,
21
21
  withAssociation,
22
22
  viewFromPrimary,
23
+ copyExpr,
23
24
  } = require('./utils');
25
+ const { propagationRules } = require('../base/builtins');
24
26
  const $inferred = Symbol.for( 'cds.$inferred' );
27
+ const { xprRewriteFns } = require('./xpr-rewrite');
25
28
  // const { ref } = require( '../model/revealInternalProperties' )
26
29
 
27
30
  // Note that propagation here is also used for deep-copying (function `onlyViaParent`)
28
31
  function propagate( model ) {
29
32
  const props = {
30
- '@com.sap.gtt.core.CoreModel.Indexable': never,
31
- '@cds.persistence.exists': never, // also copied in generate.js
32
- '@cds.persistence.table': never,
33
- '@cds.persistence.calcview': never,
34
- '@cds.persistence.udf': never,
35
- '@cds.persistence.skip': notWithPersistenceTable, // also copied in generate.js
36
- // '@cds.tenant.independent' is propagated as normal, but also copied in generate.js
37
- '@sql.append': never,
38
- '@sql.prepend': never,
39
- '@sql.replace': never,
40
- '@Analytics.hidden': never,
41
- '@Analytics.visible': never,
42
- '@cds.autoexpose': onlyViaArtifact,
43
- '@cds.autoexposed': never, // in case people set it themselves
44
- '@cds.external': never,
45
- '@cds.redirection.target': never,
46
- '@fiori.draft.enabled': onlyViaArtifact,
47
33
  '@': annotation, // always except in 'items' (and parameters for entity return types)
48
34
  doc: annotation, // always except in 'items' (and parameters for entity return types)
49
35
  default: withKind, // always except in 'items'
@@ -72,7 +58,17 @@ function propagate( model ) {
72
58
  returns,
73
59
  $filtered: annotation,
74
60
  };
61
+ const ruleToFunction = {
62
+ __proto__: null,
63
+ never,
64
+ onlyViaArtifact,
65
+ notWithPersistenceTable,
66
+ };
67
+ for (const rule in propagationRules)
68
+ props[rule] = ruleToFunction[propagationRules[rule]];
69
+
75
70
  const { options } = model;
71
+ const { rewriteAnnotationsRefs } = xprRewriteFns( model );
76
72
  // eslint-disable-next-line max-len
77
73
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
78
74
  const { warning, throwWithError } = model.$messageFunctions;
@@ -103,7 +99,7 @@ function propagate( model ) {
103
99
  const news = [];
104
100
  for (const target of targets) {
105
101
  const origin = getOrigin( target );
106
- if (origin) {
102
+ if (origin && origin.kind !== '$self') {
107
103
  // Calculated elements that are simple references: `calc = field;`.
108
104
  // Respect sibling properties in inheritance.
109
105
  if (target._calcOrigin?._origin && target.value?._artifact) {
@@ -202,6 +198,10 @@ function propagate( model ) {
202
198
  target[prop] = [ ...val ];
203
199
  target[prop].$inferred = 'prop';
204
200
  }
201
+ else if (prop.charAt(0) === '@' && val?.kind === '$annotation') {
202
+ target[prop] = Object.assign( copyExpr( val ), { $inferred: 'prop' } );
203
+ rewriteAnnotationsRefs( target, source, prop );
204
+ }
205
205
  else {
206
206
  target[prop] = Object.assign( {}, val, { $inferred: 'prop' } );
207
207
  if (val._artifact !== undefined)
@@ -270,6 +270,7 @@ function propagate( model ) {
270
270
  target[prop][$inferred] = 'prop';
271
271
  }
272
272
 
273
+ // Only propagate if parent object (which is not necessarily `_parent`) was propagated.
273
274
  function onlyViaParent( prop, target, source ) {
274
275
  if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
275
276
  // assocs and enums do not have 'include'
@@ -279,6 +280,8 @@ function propagate( model ) {
279
280
  function targetAspect( prop, target, source ) {
280
281
  if (target.targetAspect)
281
282
  return;
283
+ if (target.type?._artifact === model.definitions['cds.Association'])
284
+ return; // don't propagate targetAspect to associations (e.g. via $filtered)
282
285
  const ta = source.targetAspect;
283
286
  if (!ta.elements && !ta._origin) { // _origin set for elements in source
284
287
  notWithExpand( prop, target, source );
@@ -44,7 +44,6 @@ const {
44
44
  forEachGeneric,
45
45
  forEachInOrder,
46
46
  isDeprecatedEnabled,
47
- isBetaEnabled,
48
47
  } = require('../base/model');
49
48
  const { dictAdd } = require('../base/dictionaries');
50
49
  const { dictLocation, weakLocation } = require('../base/location');
@@ -126,6 +125,11 @@ function resolve( model ) {
126
125
  // Phase 4: resolve all artifacts:
127
126
  forEachDefinition( model, resolveRefs );
128
127
  forEachGeneric( model, 'vocabularies', resolveRefs );
128
+ if (model.options.lspMode) {
129
+ for (const name in model.sources)
130
+ resolveDefinitionName( model.sources[name].namespace );
131
+ }
132
+
129
133
  // create “super” ANNOTATE statements for annotations on unknown artifacts:
130
134
  createRemainingAnnotateStatements();
131
135
  // report cyclic dependencies:
@@ -363,6 +367,9 @@ function resolve( model ) {
363
367
  const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
364
368
  const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
365
369
 
370
+ if (model.options.lspMode && art.name && !art._main)
371
+ resolveDefinitionName( art );
372
+
366
373
  // Check KEY (TODO: make this an extra function)
367
374
  const { key } = art;
368
375
  if (key?.val && !key.$inferred) {
@@ -557,10 +564,12 @@ function resolve( model ) {
557
564
  const sType = specifiedElement.type?._artifact;
558
565
  const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
559
566
  const iType = iTypeArt || inferredElement;
560
-
561
- // xor: could be missing a type;
562
567
  // FIXME: The coding above returns incorrect iType for expand on associations
563
568
 
569
+ // $filtered may change composition to association; we allow that change here.
570
+ const compToAssoc = sType === model.definitions['cds.Association'] && inferredElement.target;
571
+
572
+ // xor: could be missing a type;
564
573
  if (!specifiedElement.type && inferredElement.type) {
565
574
  error( 'query-mismatched-element', [ specifiedElement.location, user ], {
566
575
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
@@ -568,7 +577,7 @@ function resolve( model ) {
568
577
  return;
569
578
  }
570
579
  // If specified type is `null`, type could not be resolved.
571
- else if (sType && sType !== iType &&
580
+ else if (!compToAssoc && sType && sType !== iType &&
572
581
  // Special case for $recompilation: allow one level of type indirection. See #12113.
573
582
  (!model.options.$recompile || sType !== iType.type?._artifact)) {
574
583
  const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
@@ -1348,6 +1357,19 @@ function resolve( model ) {
1348
1357
  // General resolver functions
1349
1358
  //--------------------------------------------------------------------------
1350
1359
 
1360
+ // Resolve the n-1 path steps before the definition name for LSP.
1361
+ function resolveDefinitionName( art ) {
1362
+ const path = art?.name?.path;
1363
+ if (!art || art._main || !path || path.length <= 1)
1364
+ return;
1365
+
1366
+ let name = art.name.id;
1367
+ for (let i = path.length - 1; i > 0; --i) {
1368
+ name = name.substring(0, name.length - path[i].id.length - 1);
1369
+ setArtifactLink( path[i - 1], model.definitions[name] || false );
1370
+ }
1371
+ }
1372
+
1351
1373
  // Resolve the type and its arguments if applicable.
1352
1374
  function resolveTypeExpr( art, user ) {
1353
1375
  const typeArt = resolvePath( art.type, 'type', user );
@@ -1367,7 +1389,6 @@ function resolve( model ) {
1367
1389
  if (!anno.kind)
1368
1390
  initAnnotationForExpression( anno, art );
1369
1391
  resolveExpr( expr, 'annotation', anno );
1370
- reportUnsupportedAnnoExpr( expr );
1371
1392
  }
1372
1393
  else if (expr.literal === 'array') {
1373
1394
  expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
@@ -1377,32 +1398,22 @@ function resolve( model ) {
1377
1398
  }
1378
1399
  }
1379
1400
 
1380
- function reportUnsupportedAnnoExpr( expr ) {
1381
- if (isBetaEnabled( model.options, 'annotationExpressions' ))
1382
- return;
1383
- const alreadyReported = model.$messageFunctions.messages
1384
- .find(msg => msg.messageId === 'anno-experimental-expressions');
1385
- if (!alreadyReported) {
1386
- // due to test mode and shuffling, we would get a different location on each compilation.
1387
- const loc = model.options.testMode ? null : [ expr.location ];
1388
- info( 'anno-experimental-expressions', loc, {
1389
- option: 'annotationExpressions',
1390
- // eslint-disable-next-line max-len
1391
- }, 'Expressions in annotation values are a beta feature. Use at your own risk. (This message can be suppressed with beta flag $(OPTION))' );
1392
- }
1393
- }
1394
-
1395
- // for faster processing, mark artifacts and annotations which contain anno expressions
1401
+ /**
1402
+ * For faster processing, mark artifacts and annotations which contain anno expressions
1403
+ *
1404
+ * @param {object} anno
1405
+ * @param {XSN.Artifact} art
1406
+ */
1396
1407
  function initAnnotationForExpression( anno, art ) {
1397
1408
  anno.kind = '$annotation';
1398
1409
  setLink( anno, '_outer', art );
1399
- while (!art.$contains?.$annotation) {
1400
- art.$contains ??= {};
1401
- art.$contains.$annotation = true; // TODO: extra values for elem and $self refs
1402
- if (!art._main)
1403
- break;
1404
- art = art._parent; // TODO: really go up?
1405
- }
1410
+ art.$contains ??= {};
1411
+ art.$contains.$annotation = { // set in resolveExprItem
1412
+ $path: false,
1413
+ $self: false,
1414
+ };
1415
+ // Think about tagging parents too (like before #12636).
1416
+ // Might be useful for future recursive types.
1406
1417
  }
1407
1418
 
1408
1419
  function resolveExpr( expr, exprCtx, user ) {
@@ -1434,6 +1445,11 @@ function resolve( model ) {
1434
1445
  resolveParamsAndWhere( step, expected, user, step === last );
1435
1446
  // TODO: delete 4th arg
1436
1447
  }
1448
+
1449
+ if (expected === 'annotation') {
1450
+ user._outer.$contains.$annotation.$path = true;
1451
+ user._outer.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1452
+ }
1437
1453
  }
1438
1454
  else if (expr.query) {
1439
1455
  const { query } = expr;