@sap/cds-compiler 6.3.6 → 6.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/LICENSE +32 -0
  3. package/README.md +14 -2
  4. package/bin/cdsse.js +0 -3
  5. package/doc/CHANGELOG_BETA.md +1 -1
  6. package/doc/CHANGELOG_DEPRECATED.md +1 -1
  7. package/lib/base/message-registry.js +9 -2
  8. package/lib/base/messages.js +1 -1
  9. package/lib/base/model.js +2 -0
  10. package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
  11. package/lib/checks/existsMustEndInAssoc.js +1 -1
  12. package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
  13. package/lib/checks/validator.js +4 -2
  14. package/lib/compiler/assert-consistency.js +3 -2
  15. package/lib/compiler/builtins.js +5 -6
  16. package/lib/compiler/checks.js +37 -26
  17. package/lib/compiler/define.js +1 -1
  18. package/lib/compiler/extend.js +39 -50
  19. package/lib/compiler/finalize-parse-cdl.js +1 -1
  20. package/lib/compiler/lsp-api.js +1 -1
  21. package/lib/compiler/populate.js +2 -2
  22. package/lib/compiler/propagator.js +29 -6
  23. package/lib/compiler/resolve.js +13 -3
  24. package/lib/compiler/shared.js +157 -133
  25. package/lib/compiler/tweak-assocs.js +87 -29
  26. package/lib/compiler/xpr-rewrite.js +164 -160
  27. package/lib/edm/annotations/edmJson.js +206 -37
  28. package/lib/edm/csn2edm.js +13 -0
  29. package/lib/edm/edmUtils.js +2 -2
  30. package/lib/gen/BaseParser.js +106 -72
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +1501 -1509
  33. package/lib/json/to-csn.js +8 -5
  34. package/lib/language/genericAntlrParser.js +0 -0
  35. package/lib/main.js +19 -16
  36. package/lib/model/csnRefs.js +589 -521
  37. package/lib/model/csnUtils.js +8 -5
  38. package/lib/model/enrichCsn.js +1 -0
  39. package/lib/parsers/AstBuildingParser.js +73 -28
  40. package/lib/render/toCdl.js +2 -1
  41. package/lib/render/toHdbcds.js +6 -3
  42. package/lib/render/toSql.js +5 -0
  43. package/lib/transform/db/applyTransformations.js +1 -1
  44. package/lib/transform/db/assertUnique.js +4 -1
  45. package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
  46. package/lib/transform/db/assocsToQueries/utils.js +0 -5
  47. package/lib/transform/db/cdsPersistence.js +17 -18
  48. package/lib/transform/db/expansion.js +179 -3
  49. package/lib/transform/db/flattening.js +16 -5
  50. package/lib/transform/db/rewriteCalculatedElements.js +79 -283
  51. package/lib/transform/effective/main.js +8 -1
  52. package/lib/transform/forOdata.js +1 -1
  53. package/lib/transform/forRelationalDB.js +21 -80
  54. package/lib/transform/localized.js +75 -127
  55. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
  56. package/lib/transform/transformUtils.js +23 -21
  57. package/lib/transform/translateAssocsToJoins.js +7 -5
  58. package/lib/transform/tupleExpansion.js +16 -3
  59. package/package.json +3 -3
  60. package/doc/DeprecatedOptions_v2.md +0 -150
  61. package/doc/NameResolution.md +0 -837
  62. package/lib/transform/parseExpr.js +0 -415
@@ -40,6 +40,7 @@ function fns( model ) {
40
40
  } = model.$messageFunctions;
41
41
  const Functions = model.$functions;
42
42
 
43
+ // Map `exprCtx` (is a param of traversal functions) to reference semantics
43
44
  const referenceSemantics = {
44
45
  // global: ------------------------------------------------------------------
45
46
  using: { // only used to produce error message
@@ -48,6 +49,13 @@ function fns( model ) {
48
49
  dynamic: modelDefinitions,
49
50
  notFound: undefinedDefinition,
50
51
  },
52
+ // scope:'global': for cds.Association and auto-redirected targets
53
+ $global: {
54
+ isMainRef: 'all',
55
+ lexical: null,
56
+ dynamic: modelDefinitions,
57
+ notFound: undefinedDefinition,
58
+ },
51
59
  // only used for the main annotate/extend statements, not inner ones:
52
60
  annotate: {
53
61
  isMainRef: 'all',
@@ -60,10 +68,10 @@ function fns( model ) {
60
68
  isMainRef: 'no-generated',
61
69
  lexical: userBlock,
62
70
  dynamic: modelDefinitions,
63
- notFound: undefinedDefinition,
71
+ notFound: undefinedForExtend,
64
72
  accept: extendableArtifact,
65
73
  },
66
- _extensions: {
74
+ _uncheckedExtension: { // to be used only with resolveUncheckedPath()
67
75
  isMainRef: 'all',
68
76
  lexical: userBlock,
69
77
  dynamic: modelDefinitions,
@@ -76,12 +84,6 @@ function fns( model ) {
76
84
  notFound: undefinedDefinition,
77
85
  accept: acceptStructOrBare,
78
86
  },
79
- _include: { // cyclic include: no accept
80
- isMainRef: 'no-generated',
81
- lexical: userBlock,
82
- dynamic: modelBuiltinsOrDefinitions,
83
- notFound: undefinedDefinition,
84
- },
85
87
  target: {
86
88
  isMainRef: 'no-autoexposed',
87
89
  lexical: userBlock,
@@ -90,7 +92,7 @@ function fns( model ) {
90
92
  accept: acceptEntity,
91
93
  noDep: true,
92
94
  // special `scope`s for auto-redirections:
93
- global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
95
+ global: () => '$global',
94
96
  },
95
97
  targetAspect: {
96
98
  isMainRef: 'no-autoexposed',
@@ -107,6 +109,7 @@ function fns( model ) {
107
109
  notFound: undefinedDefinition,
108
110
  accept: acceptQuerySource,
109
111
  noDep: '', // dependency special for from
112
+ args: () => 'from-args',
110
113
  },
111
114
  type: {
112
115
  isMainRef: 'no-autoexposed',
@@ -117,11 +120,11 @@ function fns( model ) {
117
120
  accept: acceptTypeOrElement,
118
121
  // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
119
122
  typeOf: typeOfSemantics,
120
- global: () => ({
121
- isMainRef: 'no-autoexposed',
122
- dynamic: modelDefinitions,
123
- navigation: staticTarget, // TODO: Object.assign() with main
124
- }),
123
+ global: () => '$global', // TODO: do we need `navigation: staticTarget`?
124
+ },
125
+ $typeOf: {
126
+ dynamic: typeOfParentDict,
127
+ navigation: staticTarget,
125
128
  },
126
129
  // element references without lexical scope (except $self/$projection): -----
127
130
  targetElement: {
@@ -130,22 +133,24 @@ function fns( model ) {
130
133
  dynamic: targetElements,
131
134
  navigation: targetNavigation,
132
135
  notFound: undefinedTargetElement,
133
- param: paramSemantics,
136
+ param: () => '$scopePar',
134
137
  },
135
138
  filter: {
136
139
  lexical: justDollarAliases,
137
140
  dollar: true,
138
141
  dynamic: targetElements,
139
142
  notFound: undefinedTargetElement,
140
- param: paramSemantics,
143
+ param: () => '$scopePar',
144
+ nestedColumn: () => 'filter',
141
145
  },
142
- 'calc-filter': {
146
+ 'calc-filter': { // TODO: what is so special about this?
143
147
  lexical: justDollarAliases,
144
148
  dollar: true,
145
149
  dynamic: targetElements,
146
150
  navigation: calcElemNavigation,
147
151
  notFound: undefinedTargetElement,
148
152
  param: paramUnsupported,
153
+ filter: () => 'calc-filter',
149
154
  },
150
155
  default: {
151
156
  lexical: null,
@@ -159,7 +164,7 @@ function fns( model ) {
159
164
  dollar: true,
160
165
  dynamic: () => Object.create( null ),
161
166
  notFound: undefinedVariable,
162
- param: paramSemantics,
167
+ param: () => '$scopePar',
163
168
  },
164
169
  'limit-offset': 'limit-rows',
165
170
  // general element / variable references --------------------------------------
@@ -169,7 +174,7 @@ function fns( model ) {
169
174
  dynamic: combinedSourcesOrParentElements,
170
175
  notFound: undefinedSourceElement,
171
176
  check: checkRefInQuery,
172
- param: paramSemantics,
177
+ param: () => '$scopePar',
173
178
  },
174
179
  having: 'where',
175
180
  groupBy: 'where',
@@ -179,22 +184,15 @@ function fns( model ) {
179
184
  dynamic: combinedSourcesOrParentElements,
180
185
  notFound: undefinedSourceElement,
181
186
  check: checkColumnRef,
182
- param: paramSemantics,
183
- nestedColumn: () => ({ // in expand and inline
184
- lexical: justDollarAliases,
185
- dollar: true,
186
- dynamic: nestedElements,
187
- notFound: undefinedNestedElement,
188
- check: checkColumnRef,
189
- param: paramSemantics,
190
- }),
187
+ param: () => '$scopePar',
188
+ nestedColumn: () => '$srcRefInNestedColumn',
191
189
  },
192
190
  'from-args': {
193
191
  lexical: null,
194
192
  dollar: true,
195
193
  dynamic: () => Object.create( null ),
196
194
  notFound: undefinedVariable,
197
- param: paramSemantics,
195
+ param: () => '$scopePar',
198
196
  },
199
197
  calc: {
200
198
  lexical: justDollarAliases,
@@ -203,6 +201,7 @@ function fns( model ) {
203
201
  navigation: calcElemNavigation,
204
202
  notFound: undefinedParentElement,
205
203
  param: paramUnsupported,
204
+ filter: () => 'calc-filter',
206
205
  },
207
206
  'join-on': {
208
207
  lexical: tableAliasesAndSelf,
@@ -210,7 +209,7 @@ function fns( model ) {
210
209
  dynamic: combinedSourcesOrParentElements,
211
210
  rejectRoot: rejectOwnExceptVisibleAliases,
212
211
  notFound: undefinedSourceElement,
213
- param: paramSemantics,
212
+ param: () => '$scopePar',
214
213
  },
215
214
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
216
215
  lexical: justDollarAliases,
@@ -221,15 +220,8 @@ function fns( model ) {
221
220
  accept: acceptElemOrVarOrSelf,
222
221
  check: checkAssocOn,
223
222
  param: paramUnsupported,
224
- nestedColumn: () => ({ // in expand and inline
225
- lexical: justDollarAliases,
226
- dollar: true,
227
- dynamic: parentElements,
228
- navigation: assocOnNavigation,
229
- notFound: undefinedParentElement,
230
- rewriteProjectionToSelf: true,
231
- }),
232
223
  rewriteProjectionToSelf: true,
224
+ nestedColumn: () => '$projRefInNestedColumn',
233
225
  },
234
226
  'mixin-on': {
235
227
  lexical: tableAliasesAndSelf,
@@ -239,7 +231,7 @@ function fns( model ) {
239
231
  notFound: undefinedSourceElement,
240
232
  accept: acceptElemOrVarOrSelf,
241
233
  check: checkAssocOn,
242
- param: paramSemantics, // TODO: check that assocs containing param in ON is not published
234
+ param: () => '$scopePar', // TODO: check that assocs containing param in ON is not published
243
235
  },
244
236
  'orderBy-ref': {
245
237
  lexical: tableAliasesAndSelf,
@@ -247,7 +239,7 @@ function fns( model ) {
247
239
  dynamic: parentElements,
248
240
  notFound: undefinedOrderByElement,
249
241
  check: checkOrderByRef,
250
- param: paramSemantics,
242
+ param: () => '$scopePar',
251
243
  },
252
244
  'orderBy-expr': {
253
245
  lexical: tableAliasesAndSelf,
@@ -255,7 +247,7 @@ function fns( model ) {
255
247
  dynamic: combinedSourcesOrParentElements,
256
248
  notFound: undefinedSourceElement,
257
249
  check: checkRefInQuery,
258
- param: paramSemantics,
250
+ param: () => '$scopePar',
259
251
  },
260
252
  'orderBy-set-ref': {
261
253
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -264,7 +256,7 @@ function fns( model ) {
264
256
  rejectRoot: rejectOwnAliasesAndMixins,
265
257
  notFound: undefinedParentElement,
266
258
  check: checkOrderByRef,
267
- param: paramSemantics,
259
+ param: () => '$scopePar',
268
260
  },
269
261
  'orderBy-set-expr': {
270
262
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -273,7 +265,7 @@ function fns( model ) {
273
265
  rejectRoot: rejectAllOwn,
274
266
  notFound: undefinedVariable,
275
267
  check: checkRefInQuery,
276
- param: paramSemantics,
268
+ param: () => '$scopePar',
277
269
  },
278
270
  annotation: { // annotation assignments
279
271
  lexical: justDollarAliases,
@@ -282,19 +274,14 @@ function fns( model ) {
282
274
  navigation: assocOnNavigation,
283
275
  noDep: true,
284
276
  notFound: undefinedParentElement,
277
+ accept: acceptElemOrAnyVar,
278
+ variableFilter: (dict => dict),
285
279
  messageMap: {
286
280
  'ref-undefined-element': 'anno-undefined-element',
287
281
  'ref-undefined-param': 'anno-undefined-param',
288
282
  },
289
- param: paramSemantics,
290
- nestedColumn: () => ({
291
- lexical: justDollarAliases,
292
- dollar: true,
293
- dynamic: parentElements,
294
- navigation: assocOnNavigation,
295
- notFound: undefinedParentElement,
296
- rewriteProjectionToSelf: true,
297
- }),
283
+ param: () => '$annotationScopePar',
284
+ nestedColumn: () => '$projRefInNestedColumn',
298
285
  },
299
286
  // TODO: introduce some kind of inheritance
300
287
  // used by xpr-rewrite.js to resolve rewritten path roots.
@@ -305,15 +292,35 @@ function fns( model ) {
305
292
  navigation: assocOnNavigation,
306
293
  noDep: true,
307
294
  notFound: null, // no error, just falsy links
308
- param: paramSemantics,
309
- nestedColumn: () => ({
310
- lexical: justDollarAliases,
311
- dollar: true,
312
- dynamic: parentElements,
313
- navigation: assocOnNavigation,
314
- notFound: undefinedParentElement,
315
- rewriteProjectionToSelf: true,
316
- }),
295
+ accept: acceptElemOrAnyVar,
296
+ param: () => '$scopePar',
297
+ nestedColumn: () => '$projRefInNestedColumn',
298
+ },
299
+ $scopePar: {
300
+ dynamic: artifactParams,
301
+ notFound: undefinedParam,
302
+ },
303
+ $annotationScopePar: {
304
+ messageMap: {
305
+ 'ref-undefined-element': 'anno-undefined-element',
306
+ 'ref-undefined-param': 'anno-undefined-param',
307
+ },
308
+ dynamic: artifactParams,
309
+ notFound: undefinedParam,
310
+ },
311
+ // for `nestedColumn`, these two will be merged with base semantics:
312
+ $projRefInNestedColumn: { // for assoc-`on` and annotations
313
+ lexical: justDollarAliases,
314
+ dynamic: parentElements,
315
+ navigation: assocOnNavigation, // like std `environment`, but no dependency
316
+ rewriteProjectionToSelf: true,
317
+ },
318
+ $srcRefInNestedColumn: { // for column refs
319
+ lexical: justDollarAliases,
320
+ dollar: true,
321
+ dynamic: nestedElements,
322
+ navigation: environment,
323
+ notFound: undefinedNestedElement,
317
324
  },
318
325
  };
319
326
 
@@ -393,16 +400,13 @@ function fns( model ) {
393
400
  const exit = callback( step, exprCtx, user );
394
401
  if (exit === traverseExpr.STOP)
395
402
  return true;
396
- if (step.where && exit !== traverseExpr.SKIP &&
397
- traverseExpr( step.where,
398
- // TODO: use property in fn dictionary above
399
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
400
- ? 'calc-filter'
401
- : 'filter' ),
402
- step, callback ) === traverseExpr.STOP)
403
- return true;
403
+ if (step.where && exit !== traverseExpr.SKIP) {
404
+ const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
405
+ if (traverseExpr( step.where, ctx, step, callback ) === traverseExpr.STOP)
406
+ return true;
407
+ }
404
408
  if (step.args) {
405
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
409
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
406
410
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
407
411
  // TODO: there should be no array `args` on path item
408
412
  for (const arg of args) {
@@ -416,7 +420,7 @@ function fns( model ) {
416
420
  // Special expression traversal function for `resolveExpr`. Let's see
417
421
  // later whether we can use this version as the general one.
418
422
  // If we continue to have separate ones, remove the STOP stuff – it is not
419
- // needed for `resolveExpr`.
423
+ // needed for `resolveExpr`; SKIP is used, though.
420
424
 
421
425
  function traverseTypedExpr( expr, exprCtx, user, type, callback ) {
422
426
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
@@ -569,16 +573,13 @@ function fns( model ) {
569
573
  const exit = callback( step, exprCtx, user, null );
570
574
  if (exit === traverseExpr.STOP)
571
575
  return true;
572
- if (step.where && exit !== traverseExpr.SKIP &&
573
- traverseTypedExpr( step.where,
574
- // TODO: use property in fn dictionary above
575
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
576
- ? 'calc-filter'
577
- : 'filter' ),
578
- step, null, callback ) === traverseExpr.STOP)
579
- return true;
576
+ if (step.where && exit !== traverseExpr.SKIP) {
577
+ const ctx = referenceSemantics[exprCtx].filter?.() || 'filter';
578
+ if (traverseTypedExpr( step.where, ctx, step, null, callback ) === traverseExpr.STOP)
579
+ return true;
580
+ }
580
581
  if (step.args) {
581
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
582
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
582
583
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
583
584
  // TODO: there should be no array `args` on path item
584
585
  for (const arg of args) {
@@ -641,7 +642,6 @@ function fns( model ) {
641
642
 
642
643
  const s = referenceSemantics[expected];
643
644
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
644
- semantics.name = expected;
645
645
 
646
646
  const r = getPathRoot( ref, semantics, origUser );
647
647
  const root = r && acceptPathRoot( r, ref, semantics, origUser );
@@ -782,19 +782,26 @@ function fns( model ) {
782
782
  ruser = ruser._outer._outer;
783
783
  }
784
784
 
785
- // Handle expand/inline, `type of`, :param, global (internally for CDL):
785
+ // Handle expand/inline before `type of`, :param, global (internally for CDL):
786
786
  if (user._columnParent && !semantics.isMainRef) { // in expand/inline
787
- const { name } = semantics;
788
- semantics = semantics.nestedColumn();
789
- semantics.name = name;
787
+ const func = semantics.nestedColumn;
788
+ if (!func)
789
+ throw new CompilerAssertion( 'Unexpected ref context in nested column' );
790
+ const ctx = func();
791
+ semantics = (typeof ctx === 'string')
792
+ ? ({ ...semantics, ...referenceSemantics[ctx] })
793
+ : ctx;
790
794
  }
791
795
  if (typeof scope === 'string') { // typeOf, param, global
792
- semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
793
- if (!semantics) {
794
- if (semantics == null)
795
- throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
796
+ const func = semantics[scope] || scope === 'param' && paramUnsupported;
797
+ // 'param' is a user scope → useful default (error msg ref-unexpected-param)
798
+ // 'global' and 'typeOf' are internal scopes of the compiler → dump if not provided
799
+ if (!func)
800
+ throw new CompilerAssertion( `Unexpected scope ${ scope }, no handler defined in context` );
801
+ const ctx = func( ruser, path, location, semantics );
802
+ semantics = (typeof ctx === 'string') ? referenceSemantics[ctx] : ctx;
803
+ if (!semantics)
796
804
  return setArtifactLink( head, null );
797
- }
798
805
  }
799
806
  const valid = [];
800
807
 
@@ -829,7 +836,8 @@ function fns( model ) {
829
836
  valid.push( dynamicDict );
830
837
  }
831
838
  else {
832
- valid.push( removeInvalidMagicVariables( model.$magicVariables.elements, semantics ),
839
+ const filterFn = semantics.variableFilter || removeRestrictedVariables;
840
+ valid.push( filterFn( model.$magicVariables.elements ),
833
841
  removeDollarNames( dynamicDict ) );
834
842
  }
835
843
  // TODO: streamline function arguments (probably: user, path, semantics )
@@ -853,7 +861,7 @@ function fns( model ) {
853
861
  const { isMainRef } = semantics;
854
862
  if (isMainRef) {
855
863
  artItemsCount = (typeof ref.scope === 'number' && ref.scope) ||
856
- (ref.scope ? 1 : path.length);
864
+ (ref.scope ? 1 : path.length);
857
865
  }
858
866
  let art = null;
859
867
  const elementsEnv = semantics.navigation || environment;
@@ -1024,11 +1032,17 @@ function fns( model ) {
1024
1032
  return art;
1025
1033
  }
1026
1034
  case 'builtin': {
1035
+ // TODO: use properties in builtins
1027
1036
  if (art.name.id === '$at') {
1028
1037
  message( 'ref-deprecated-variable', [ head.location, user ],
1029
1038
  { code: '$at', newcode: '$valid' },
1030
1039
  '$(CODE) is deprecated; use $(NEWCODE) instead' );
1031
1040
  }
1041
+ else if (art.$restricted && semantics.accept !== acceptElemOrAnyVar) {
1042
+ error( 'ref-unexpected-var', [ head.location, user ],
1043
+ { '#': 'annotation', name: head.id } );
1044
+ return null; // no further error on `unknown` for $draft.unknown
1045
+ }
1032
1046
  return art;
1033
1047
  }
1034
1048
  default:
@@ -1063,20 +1077,12 @@ function fns( model ) {
1063
1077
  while (struct.kind === 'element')
1064
1078
  struct = struct._parent;
1065
1079
  if (struct === user._main && struct.kind !== 'annotation')
1066
- return { dynamic: typeOfParentDict, navigation: staticTarget };
1080
+ return '$typeOf';
1067
1081
  error( 'type-unexpected-typeof', [ head.location, user ],
1068
1082
  { keyword: 'type of', '#': struct.kind } );
1069
1083
  return false;
1070
1084
  }
1071
1085
 
1072
- function paramSemantics( _user, _path, _loction, semantics ) {
1073
- return {
1074
- messageMap: semantics.messageMap,
1075
- dynamic: artifactParams,
1076
- notFound: undefinedParam,
1077
- };
1078
- }
1079
-
1080
1086
  function paramUnsupported( user, _path, location ) {
1081
1087
  error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
1082
1088
  // why an extra text for calculated elements? or separate for all?
@@ -1287,26 +1293,36 @@ function fns( model ) {
1287
1293
 
1288
1294
  function undefinedForAnnotate( user, item, valid, _dict, prev, path ) {
1289
1295
  // in a CSN source, only one env was tested (valid.length 1):
1290
- const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
1296
+ const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
1291
1297
  if (!user.elements && !user.actions && !user.enum && !user.params &&
1292
- couldBeDraftsEntity( item.id, valid, prev, path ))
1293
- return;
1294
- if (couldBeDraftAdminDataEntity( item ) )
1298
+ endsWithSuffix( name, '.drafts', art => art?._service && art.kind === 'entity' ) ||
1299
+ endsWithSuffix( name, '.DraftAdministrativeData',
1300
+ ( art, prefix ) => (art?.kind === 'service' || prefix === 'DRAFT') ) ||
1301
+ name === 'DRAFT' && path?.length === 2 && path[1].id === 'DraftAdministrativeData' ||
1302
+ name.startsWith( 'localized.' )) // TODO: only if suffix is defined
1295
1303
  return;
1296
1304
  signalNotFound( (valid.length > 1 ? 'ext-undefined-art' : 'ext-undefined-def'),
1297
1305
  // TODO: ext-undefined-xyz
1298
- [ item.location, user ], valid, { art } );
1306
+ [ item.location, user ], valid, { art: name } );
1299
1307
  }
1300
1308
 
1301
- function couldBeDraftsEntity( id, valid, prev, path ) {
1302
- const entity = prev
1303
- ? prev === path[path.length - 2]._artifact && prev // TODO: Should check for '.drafts'?
1304
- : path.length === 1 && id.endsWith( '.drafts' ) && model.definitions[id.slice( 0, -7 )];
1305
- return entity?.kind === 'entity' && !!entity._service;
1309
+ function undefinedForExtend( user, item, valid, _dict, prev ) {
1310
+ // in a CSN source, only one env was tested (valid.length 1):
1311
+ const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
1312
+ if (name.startsWith( 'localized.' )) {
1313
+ error( 'ref-undefined-art', [ user.name.location || user.location, user ],
1314
+ { '#': 'localized', keyword: 'annotate' } );
1315
+ }
1316
+ else {
1317
+ undefinedDefinition( user, item, valid, _dict, prev );
1318
+ }
1306
1319
  }
1307
1320
 
1308
- function couldBeDraftAdminDataEntity( item ) {
1309
- return item.id === 'DraftAdministrativeData' && !item._artifact;
1321
+ function endsWithSuffix( name, suffix, cond ) {
1322
+ if (!name.endsWith( suffix ))
1323
+ return false;
1324
+ const prefix = name.slice( 0, -suffix.length );
1325
+ return cond( model.definitions[prefix], prefix, name );
1310
1326
  }
1311
1327
 
1312
1328
  function undefinedParam( user, head, valid, _dict, _art, _path, semantics ) {
@@ -1424,8 +1440,7 @@ function fns( model ) {
1424
1440
  while ((head = head?._parent) && head.kind === 'builtin')
1425
1441
  id = `${ head.name.id }.${ id }`;
1426
1442
  const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
1427
- signalNotFound( msgId, [ item.location, user ],
1428
- removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
1443
+ signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
1429
1444
  }
1430
1445
  else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
1431
1446
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
@@ -1507,16 +1522,17 @@ function fns( model ) {
1507
1522
  : acceptElemOrVar( art, user, ref );
1508
1523
  }
1509
1524
 
1510
- function acceptElemOrVar( art, user, ref, semantics ) {
1525
+ function acceptElemOrVar( art, user, ref ) {
1526
+ if (art.kind !== 'builtin' || !art.$restricted)
1527
+ return acceptElemOrAnyVar( art, user, ref );
1528
+ error( 'ref-unexpected-var', [ ref.location, user ],
1529
+ { '#': 'annotation', name: pathName( ref.path ) } );
1530
+ return null;
1531
+ }
1532
+
1533
+ function acceptElemOrAnyVar( art, user, ref ) {
1511
1534
  const { path } = ref;
1512
1535
  if (art.kind === 'builtin') {
1513
- if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
1514
- error( 'ref-unexpected-var', [ ref.location, user ], {
1515
- '#': art.$onlyInExprCtx[0], name: pathName( path ),
1516
- });
1517
- return null;
1518
- }
1519
-
1520
1536
  if (user.expand || user.inline) {
1521
1537
  const location = (user.expand || user.inline)[$location];
1522
1538
  const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
@@ -1537,7 +1553,7 @@ function fns( model ) {
1537
1553
  }
1538
1554
  // TODO: combine $requireElementAccess/$autoElement to $bareRoot ?
1539
1555
  else if (!user.expand && !user.inline && // $self._artifact to main artifact
1540
- !(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
1556
+ !(art._main && art.kind !== 'select') && ref.path[0]._navigation?.kind === '$self') {
1541
1557
  // TODO: better ref-invalid-self
1542
1558
  const { location, id } = path[0];
1543
1559
  error( 'ref-unexpected-self', [ location, user ], { id } );
@@ -1655,7 +1671,7 @@ function fns( model ) {
1655
1671
  }
1656
1672
  }
1657
1673
  else if (source.kind !== 'entity' &&
1658
- !acceptEventProjectionSource( source, user )) {
1674
+ !acceptEventProjectionSource( source, user )) {
1659
1675
  signalNotFound( 'ref-invalid-source', [ ref.location, user ], null,
1660
1676
  { '#': user._main.kind } );
1661
1677
  return (source === art) ? art : false; // art to show cyclic issues
@@ -1937,8 +1953,8 @@ function fns( model ) {
1937
1953
  // has to be run after foreign-key rewrite
1938
1954
  const outer = user._columnParent?._origin;
1939
1955
  let assoc = outer?.foreignKeys &&
1940
- pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
1941
- outer;
1956
+ pathStartsWithSelf( { path } ) == null && // not $self or CDS var like $now
1957
+ outer;
1942
1958
  for (let index = startIndex; index < path.length; ++index) {
1943
1959
  if (assoc?.target) {
1944
1960
  if (!assoc.foreignKeys) {
@@ -2029,7 +2045,8 @@ function fns( model ) {
2029
2045
  const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
2030
2046
  if (valid) {
2031
2047
  const user = Array.isArray( location ) && location[1];
2032
- err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
2048
+ // TODO: see TODO missing param `viaCdl` in attachAndEmitValidNames:
2049
+ err.validNames = (user && definedViaCdl( user ));
2033
2050
  valid.reverse();
2034
2051
  attachAndEmitValidNames( err, ...valid );
2035
2052
  }
@@ -2070,6 +2087,8 @@ function fns( model ) {
2070
2087
  const valid = Object.assign( Object.create( null ), ...validDicts );
2071
2088
  msg.validNames = Object.create( null );
2072
2089
  for (const name of Object.keys( valid )) {
2090
+ if (!name)
2091
+ continue;
2073
2092
  const art = valid[name];
2074
2093
  // ignore internal types such as cds.Association, ignore names with dot for
2075
2094
  // CDL references to main artifacts:
@@ -2094,14 +2113,19 @@ function fns( model ) {
2094
2113
  }
2095
2114
  }
2096
2115
 
2097
- function removeInvalidMagicVariables( variables, semantics ) {
2098
- if (Array.isArray(variables))
2099
- return variables.map(variable => removeInvalidMagicVariables( variable, semantics ));
2100
-
2116
+ /**
2117
+ * Filter out restricted variables from a dictionary of variables.
2118
+ * Variables with the `$restricted` property set to true are excluded.
2119
+ *
2120
+ * @param {object} variables - Dictionary of variable objects
2121
+ * @returns {object} New dictionary with restricted variables removed
2122
+ */
2123
+ function removeRestrictedVariables( variables ) {
2124
+ // TODO: we could also do something with `deprecated`, $requiresBetaFlag, …
2101
2125
  const valid = Object.create(null);
2102
2126
  for (const name in variables) {
2103
2127
  const variable = variables[name];
2104
- if (!variable.$onlyInExprCtx || variable.$onlyInExprCtx.includes( semantics.name ))
2128
+ if (!variable.$restricted)
2105
2129
  valid[name] = variable;
2106
2130
  }
2107
2131
  return valid;