@sap/cds-compiler 6.4.2 → 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.
@@ -49,6 +49,13 @@ function fns( model ) {
49
49
  dynamic: modelDefinitions,
50
50
  notFound: undefinedDefinition,
51
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
+ },
52
59
  // only used for the main annotate/extend statements, not inner ones:
53
60
  annotate: {
54
61
  isMainRef: 'all',
@@ -85,7 +92,7 @@ function fns( model ) {
85
92
  accept: acceptEntity,
86
93
  noDep: true,
87
94
  // special `scope`s for auto-redirections:
88
- global: () => ({ isMainRef: 'all', dynamic: modelDefinitions }),
95
+ global: () => '$global',
89
96
  },
90
97
  targetAspect: {
91
98
  isMainRef: 'no-autoexposed',
@@ -102,6 +109,7 @@ function fns( model ) {
102
109
  notFound: undefinedDefinition,
103
110
  accept: acceptQuerySource,
104
111
  noDep: '', // dependency special for from
112
+ args: () => 'from-args',
105
113
  },
106
114
  type: {
107
115
  isMainRef: 'no-autoexposed',
@@ -112,11 +120,11 @@ function fns( model ) {
112
120
  accept: acceptTypeOrElement,
113
121
  // special `scope`s for CDL parser - TYPE OF (TODO generated?), cds.Association:
114
122
  typeOf: typeOfSemantics,
115
- global: () => ({
116
- isMainRef: 'no-autoexposed',
117
- dynamic: modelDefinitions,
118
- navigation: staticTarget, // TODO: Object.assign() with main
119
- }),
123
+ global: () => '$global', // TODO: do we need `navigation: staticTarget`?
124
+ },
125
+ $typeOf: {
126
+ dynamic: typeOfParentDict,
127
+ navigation: staticTarget,
120
128
  },
121
129
  // element references without lexical scope (except $self/$projection): -----
122
130
  targetElement: {
@@ -125,22 +133,24 @@ function fns( model ) {
125
133
  dynamic: targetElements,
126
134
  navigation: targetNavigation,
127
135
  notFound: undefinedTargetElement,
128
- param: paramSemantics,
136
+ param: () => '$scopePar',
129
137
  },
130
138
  filter: {
131
139
  lexical: justDollarAliases,
132
140
  dollar: true,
133
141
  dynamic: targetElements,
134
142
  notFound: undefinedTargetElement,
135
- param: paramSemantics,
143
+ param: () => '$scopePar',
144
+ nestedColumn: () => 'filter',
136
145
  },
137
- 'calc-filter': {
146
+ 'calc-filter': { // TODO: what is so special about this?
138
147
  lexical: justDollarAliases,
139
148
  dollar: true,
140
149
  dynamic: targetElements,
141
150
  navigation: calcElemNavigation,
142
151
  notFound: undefinedTargetElement,
143
152
  param: paramUnsupported,
153
+ filter: () => 'calc-filter',
144
154
  },
145
155
  default: {
146
156
  lexical: null,
@@ -154,7 +164,7 @@ function fns( model ) {
154
164
  dollar: true,
155
165
  dynamic: () => Object.create( null ),
156
166
  notFound: undefinedVariable,
157
- param: paramSemantics,
167
+ param: () => '$scopePar',
158
168
  },
159
169
  'limit-offset': 'limit-rows',
160
170
  // general element / variable references --------------------------------------
@@ -164,7 +174,7 @@ function fns( model ) {
164
174
  dynamic: combinedSourcesOrParentElements,
165
175
  notFound: undefinedSourceElement,
166
176
  check: checkRefInQuery,
167
- param: paramSemantics,
177
+ param: () => '$scopePar',
168
178
  },
169
179
  having: 'where',
170
180
  groupBy: 'where',
@@ -174,22 +184,15 @@ function fns( model ) {
174
184
  dynamic: combinedSourcesOrParentElements,
175
185
  notFound: undefinedSourceElement,
176
186
  check: checkColumnRef,
177
- param: paramSemantics,
178
- nestedColumn: () => ({ // in expand and inline
179
- lexical: justDollarAliases,
180
- dollar: true,
181
- dynamic: nestedElements,
182
- notFound: undefinedNestedElement,
183
- check: checkColumnRef,
184
- param: paramSemantics,
185
- }),
187
+ param: () => '$scopePar',
188
+ nestedColumn: () => '$srcRefInNestedColumn',
186
189
  },
187
190
  'from-args': {
188
191
  lexical: null,
189
192
  dollar: true,
190
193
  dynamic: () => Object.create( null ),
191
194
  notFound: undefinedVariable,
192
- param: paramSemantics,
195
+ param: () => '$scopePar',
193
196
  },
194
197
  calc: {
195
198
  lexical: justDollarAliases,
@@ -198,6 +201,7 @@ function fns( model ) {
198
201
  navigation: calcElemNavigation,
199
202
  notFound: undefinedParentElement,
200
203
  param: paramUnsupported,
204
+ filter: () => 'calc-filter',
201
205
  },
202
206
  'join-on': {
203
207
  lexical: tableAliasesAndSelf,
@@ -205,7 +209,7 @@ function fns( model ) {
205
209
  dynamic: combinedSourcesOrParentElements,
206
210
  rejectRoot: rejectOwnExceptVisibleAliases,
207
211
  notFound: undefinedSourceElement,
208
- param: paramSemantics,
212
+ param: () => '$scopePar',
209
213
  },
210
214
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
211
215
  lexical: justDollarAliases,
@@ -216,15 +220,8 @@ function fns( model ) {
216
220
  accept: acceptElemOrVarOrSelf,
217
221
  check: checkAssocOn,
218
222
  param: paramUnsupported,
219
- nestedColumn: () => ({ // in expand and inline
220
- lexical: justDollarAliases,
221
- dollar: true,
222
- dynamic: parentElements,
223
- navigation: assocOnNavigation,
224
- notFound: undefinedParentElement,
225
- rewriteProjectionToSelf: true,
226
- }),
227
223
  rewriteProjectionToSelf: true,
224
+ nestedColumn: () => '$projRefInNestedColumn',
228
225
  },
229
226
  'mixin-on': {
230
227
  lexical: tableAliasesAndSelf,
@@ -234,7 +231,7 @@ function fns( model ) {
234
231
  notFound: undefinedSourceElement,
235
232
  accept: acceptElemOrVarOrSelf,
236
233
  check: checkAssocOn,
237
- 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
238
235
  },
239
236
  'orderBy-ref': {
240
237
  lexical: tableAliasesAndSelf,
@@ -242,7 +239,7 @@ function fns( model ) {
242
239
  dynamic: parentElements,
243
240
  notFound: undefinedOrderByElement,
244
241
  check: checkOrderByRef,
245
- param: paramSemantics,
242
+ param: () => '$scopePar',
246
243
  },
247
244
  'orderBy-expr': {
248
245
  lexical: tableAliasesAndSelf,
@@ -250,7 +247,7 @@ function fns( model ) {
250
247
  dynamic: combinedSourcesOrParentElements,
251
248
  notFound: undefinedSourceElement,
252
249
  check: checkRefInQuery,
253
- param: paramSemantics,
250
+ param: () => '$scopePar',
254
251
  },
255
252
  'orderBy-set-ref': {
256
253
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -259,7 +256,7 @@ function fns( model ) {
259
256
  rejectRoot: rejectOwnAliasesAndMixins,
260
257
  notFound: undefinedParentElement,
261
258
  check: checkOrderByRef,
262
- param: paramSemantics,
259
+ param: () => '$scopePar',
263
260
  },
264
261
  'orderBy-set-expr': {
265
262
  lexical: tableAliasesAndSelf, // TODO: reject own tab aliases
@@ -268,7 +265,7 @@ function fns( model ) {
268
265
  rejectRoot: rejectAllOwn,
269
266
  notFound: undefinedVariable,
270
267
  check: checkRefInQuery,
271
- param: paramSemantics,
268
+ param: () => '$scopePar',
272
269
  },
273
270
  annotation: { // annotation assignments
274
271
  lexical: justDollarAliases,
@@ -277,19 +274,14 @@ function fns( model ) {
277
274
  navigation: assocOnNavigation,
278
275
  noDep: true,
279
276
  notFound: undefinedParentElement,
277
+ accept: acceptElemOrAnyVar,
278
+ variableFilter: (dict => dict),
280
279
  messageMap: {
281
280
  'ref-undefined-element': 'anno-undefined-element',
282
281
  'ref-undefined-param': 'anno-undefined-param',
283
282
  },
284
- param: paramSemantics,
285
- nestedColumn: () => ({
286
- lexical: justDollarAliases,
287
- dollar: true,
288
- dynamic: parentElements,
289
- navigation: assocOnNavigation,
290
- notFound: undefinedParentElement,
291
- rewriteProjectionToSelf: true,
292
- }),
283
+ param: () => '$annotationScopePar',
284
+ nestedColumn: () => '$projRefInNestedColumn',
293
285
  },
294
286
  // TODO: introduce some kind of inheritance
295
287
  // used by xpr-rewrite.js to resolve rewritten path roots.
@@ -300,17 +292,36 @@ function fns( model ) {
300
292
  navigation: assocOnNavigation,
301
293
  noDep: true,
302
294
  notFound: null, // no error, just falsy links
303
- param: paramSemantics,
304
- nestedColumn: () => ({
305
- lexical: justDollarAliases,
306
- dollar: true,
307
- dynamic: parentElements,
308
- navigation: assocOnNavigation,
309
- notFound: undefinedParentElement,
310
- rewriteProjectionToSelf: true,
311
- }),
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,
312
324
  },
313
- //
314
325
  };
315
326
 
316
327
  Object.assign( model.$functions, {
@@ -389,16 +400,13 @@ function fns( model ) {
389
400
  const exit = callback( step, exprCtx, user );
390
401
  if (exit === traverseExpr.STOP)
391
402
  return true;
392
- if (step.where && exit !== traverseExpr.SKIP &&
393
- traverseExpr( step.where,
394
- // TODO: use property in fn dictionary above
395
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
396
- ? 'calc-filter'
397
- : 'filter' ),
398
- step, callback ) === traverseExpr.STOP)
399
- 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
+ }
400
408
  if (step.args) {
401
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
409
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
402
410
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
403
411
  // TODO: there should be no array `args` on path item
404
412
  for (const arg of args) {
@@ -412,7 +420,7 @@ function fns( model ) {
412
420
  // Special expression traversal function for `resolveExpr`. Let's see
413
421
  // later whether we can use this version as the general one.
414
422
  // If we continue to have separate ones, remove the STOP stuff – it is not
415
- // needed for `resolveExpr`.
423
+ // needed for `resolveExpr`; SKIP is used, though.
416
424
 
417
425
  function traverseTypedExpr( expr, exprCtx, user, type, callback ) {
418
426
  if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
@@ -565,16 +573,13 @@ function fns( model ) {
565
573
  const exit = callback( step, exprCtx, user, null );
566
574
  if (exit === traverseExpr.STOP)
567
575
  return true;
568
- if (step.where && exit !== traverseExpr.SKIP &&
569
- traverseTypedExpr( step.where,
570
- // TODO: use property in fn dictionary above
571
- ( exprCtx === 'calc' || exprCtx === 'calc-filter'
572
- ? 'calc-filter'
573
- : 'filter' ),
574
- step, null, callback ) === traverseExpr.STOP)
575
- 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
+ }
576
581
  if (step.args) {
577
- const ctx = (exprCtx === 'from') ? 'from-args' : exprCtx;
582
+ const ctx = referenceSemantics[exprCtx].args?.() || exprCtx;
578
583
  const args = Array.isArray( step.args ) ? step.args : Object.values( step.args );
579
584
  // TODO: there should be no array `args` on path item
580
585
  for (const arg of args) {
@@ -637,7 +642,6 @@ function fns( model ) {
637
642
 
638
643
  const s = referenceSemantics[expected];
639
644
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
640
- semantics.name = expected;
641
645
 
642
646
  const r = getPathRoot( ref, semantics, origUser );
643
647
  const root = r && acceptPathRoot( r, ref, semantics, origUser );
@@ -778,19 +782,26 @@ function fns( model ) {
778
782
  ruser = ruser._outer._outer;
779
783
  }
780
784
 
781
- // Handle expand/inline, `type of`, :param, global (internally for CDL):
785
+ // Handle expand/inline before `type of`, :param, global (internally for CDL):
782
786
  if (user._columnParent && !semantics.isMainRef) { // in expand/inline
783
- const { name } = semantics;
784
- semantics = semantics.nestedColumn();
785
- 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;
786
794
  }
787
795
  if (typeof scope === 'string') { // typeOf, param, global
788
- semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
789
- if (!semantics) {
790
- if (semantics == null)
791
- 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)
792
804
  return setArtifactLink( head, null );
793
- }
794
805
  }
795
806
  const valid = [];
796
807
 
@@ -825,7 +836,8 @@ function fns( model ) {
825
836
  valid.push( dynamicDict );
826
837
  }
827
838
  else {
828
- valid.push( removeInvalidMagicVariables( model.$magicVariables.elements, semantics ),
839
+ const filterFn = semantics.variableFilter || removeRestrictedVariables;
840
+ valid.push( filterFn( model.$magicVariables.elements ),
829
841
  removeDollarNames( dynamicDict ) );
830
842
  }
831
843
  // TODO: streamline function arguments (probably: user, path, semantics )
@@ -1020,11 +1032,17 @@ function fns( model ) {
1020
1032
  return art;
1021
1033
  }
1022
1034
  case 'builtin': {
1035
+ // TODO: use properties in builtins
1023
1036
  if (art.name.id === '$at') {
1024
1037
  message( 'ref-deprecated-variable', [ head.location, user ],
1025
1038
  { code: '$at', newcode: '$valid' },
1026
1039
  '$(CODE) is deprecated; use $(NEWCODE) instead' );
1027
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
+ }
1028
1046
  return art;
1029
1047
  }
1030
1048
  default:
@@ -1059,20 +1077,12 @@ function fns( model ) {
1059
1077
  while (struct.kind === 'element')
1060
1078
  struct = struct._parent;
1061
1079
  if (struct === user._main && struct.kind !== 'annotation')
1062
- return { dynamic: typeOfParentDict, navigation: staticTarget };
1080
+ return '$typeOf';
1063
1081
  error( 'type-unexpected-typeof', [ head.location, user ],
1064
1082
  { keyword: 'type of', '#': struct.kind } );
1065
1083
  return false;
1066
1084
  }
1067
1085
 
1068
- function paramSemantics( _user, _path, _loction, semantics ) {
1069
- return {
1070
- messageMap: semantics.messageMap,
1071
- dynamic: artifactParams,
1072
- notFound: undefinedParam,
1073
- };
1074
- }
1075
-
1076
1086
  function paramUnsupported( user, _path, location ) {
1077
1087
  error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
1078
1088
  // why an extra text for calculated elements? or separate for all?
@@ -1430,8 +1440,7 @@ function fns( model ) {
1430
1440
  while ((head = head?._parent) && head.kind === 'builtin')
1431
1441
  id = `${ head.name.id }.${ id }`;
1432
1442
  const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
1433
- signalNotFound( msgId, [ item.location, user ],
1434
- removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
1443
+ signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
1435
1444
  }
1436
1445
  else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
1437
1446
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
@@ -1513,16 +1522,17 @@ function fns( model ) {
1513
1522
  : acceptElemOrVar( art, user, ref );
1514
1523
  }
1515
1524
 
1516
- 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 ) {
1517
1534
  const { path } = ref;
1518
1535
  if (art.kind === 'builtin') {
1519
- if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
1520
- error( 'ref-unexpected-var', [ ref.location, user ], {
1521
- '#': art.$onlyInExprCtx[0], name: pathName( path ),
1522
- });
1523
- return null;
1524
- }
1525
-
1526
1536
  if (user.expand || user.inline) {
1527
1537
  const location = (user.expand || user.inline)[$location];
1528
1538
  const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
@@ -2035,7 +2045,8 @@ function fns( model ) {
2035
2045
  const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
2036
2046
  if (valid) {
2037
2047
  const user = Array.isArray( location ) && location[1];
2038
- err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
2048
+ // TODO: see TODO missing param `viaCdl` in attachAndEmitValidNames:
2049
+ err.validNames = (user && definedViaCdl( user ));
2039
2050
  valid.reverse();
2040
2051
  attachAndEmitValidNames( err, ...valid );
2041
2052
  }
@@ -2076,6 +2087,8 @@ function fns( model ) {
2076
2087
  const valid = Object.assign( Object.create( null ), ...validDicts );
2077
2088
  msg.validNames = Object.create( null );
2078
2089
  for (const name of Object.keys( valid )) {
2090
+ if (!name)
2091
+ continue;
2079
2092
  const art = valid[name];
2080
2093
  // ignore internal types such as cds.Association, ignore names with dot for
2081
2094
  // CDL references to main artifacts:
@@ -2100,14 +2113,19 @@ function fns( model ) {
2100
2113
  }
2101
2114
  }
2102
2115
 
2103
- function removeInvalidMagicVariables( variables, semantics ) {
2104
- if (Array.isArray(variables))
2105
- return variables.map(variable => removeInvalidMagicVariables( variable, semantics ));
2106
-
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, …
2107
2125
  const valid = Object.create(null);
2108
2126
  for (const name in variables) {
2109
2127
  const variable = variables[name];
2110
- if (!variable.$onlyInExprCtx || variable.$onlyInExprCtx.includes( semantics.name ))
2128
+ if (!variable.$restricted)
2111
2129
  valid[name] = variable;
2112
2130
  }
2113
2131
  return valid;
@@ -457,7 +457,7 @@ function tweakAssocs( model ) {
457
457
  return;
458
458
 
459
459
  if (elem._parent?.kind === 'element') {
460
- // managed association as sub element not supported yet
460
+ // unmanaged association as sub element not supported yet
461
461
  // TODO: Only report once for multi-include chains, see
462
462
  // Associations/SubElements/UnmanagedInSubElement.err.cds
463
463
  error( 'type-unsupported-rewrite', [ elem.location, elem ], { '#': 'sub-element' } );