@sap/cds-compiler 4.8.0 → 4.9.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 (95) hide show
  1. package/CHANGELOG.md +38 -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 +30 -17
  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 +38 -21
  12. package/lib/base/messages.js +51 -20
  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 +10 -3
  20. package/lib/compiler/checks.js +44 -18
  21. package/lib/compiler/define.js +38 -30
  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/populate.js +0 -2
  26. package/lib/compiler/propagator.js +23 -19
  27. package/lib/compiler/resolve.js +48 -29
  28. package/lib/compiler/shared.js +60 -20
  29. package/lib/compiler/tweak-assocs.js +72 -116
  30. package/lib/compiler/xpr-rewrite.js +762 -0
  31. package/lib/edm/annotations/edmJson.js +24 -7
  32. package/lib/edm/annotations/genericTranslation.js +81 -61
  33. package/lib/edm/edm.js +4 -4
  34. package/lib/edm/edmInboundChecks.js +33 -0
  35. package/lib/edm/edmPreprocessor.js +9 -6
  36. package/lib/gen/Dictionary.json +129 -14
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +1 -1
  39. package/lib/gen/languageParser.js +1523 -1518
  40. package/lib/json/from-csn.js +13 -4
  41. package/lib/json/to-csn.js +12 -12
  42. package/lib/language/genericAntlrParser.js +14 -6
  43. package/lib/main.d.ts +67 -14
  44. package/lib/main.js +1 -0
  45. package/lib/model/cloneCsn.js +6 -3
  46. package/lib/model/csnRefs.js +23 -11
  47. package/lib/model/csnUtils.js +13 -7
  48. package/lib/model/enrichCsn.js +3 -1
  49. package/lib/model/revealInternalProperties.js +2 -1
  50. package/lib/model/sortViews.js +14 -6
  51. package/lib/modelCompare/compare.js +33 -34
  52. package/lib/optionProcessor.js +27 -2
  53. package/lib/render/DuplicateChecker.js +6 -6
  54. package/lib/render/manageConstraints.js +1 -0
  55. package/lib/render/toCdl.js +3 -1
  56. package/lib/transform/db/applyTransformations.js +33 -0
  57. package/lib/transform/db/constraints.js +75 -28
  58. package/lib/transform/db/expansion.js +8 -3
  59. package/lib/transform/db/flattening.js +2 -2
  60. package/lib/transform/db/groupByOrderBy.js +2 -2
  61. package/lib/transform/db/temporal.js +6 -3
  62. package/lib/transform/db/transformExists.js +2 -2
  63. package/lib/transform/effective/annotations.js +194 -0
  64. package/lib/transform/effective/main.js +6 -8
  65. package/lib/transform/effective/misc.js +31 -10
  66. package/lib/transform/forOdata.js +23 -7
  67. package/lib/transform/forRelationalDB.js +3 -3
  68. package/lib/transform/localized.js +7 -6
  69. package/lib/transform/odata/flattening.js +221 -124
  70. package/lib/transform/odata/toFinalBaseType.js +1 -1
  71. package/lib/transform/odata/typesExposure.js +15 -12
  72. package/lib/transform/parseExpr.js +4 -4
  73. package/lib/transform/transformUtils.js +47 -42
  74. package/lib/transform/translateAssocsToJoins.js +47 -47
  75. package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
  76. package/package.json +1 -1
  77. package/share/messages/anno-missing-rewrite.md +45 -0
  78. package/share/messages/message-explanations.json +1 -0
  79. package/bin/.eslintrc.json +0 -17
  80. package/lib/api/.eslintrc.json +0 -37
  81. package/lib/checks/.eslintrc.json +0 -31
  82. package/lib/compiler/.eslintrc.json +0 -8
  83. package/lib/edm/.eslintrc.json +0 -46
  84. package/lib/inspect/.eslintrc.json +0 -4
  85. package/lib/json/.eslintrc.json +0 -4
  86. package/lib/language/.eslintrc.json +0 -4
  87. package/lib/model/.eslintrc.json +0 -13
  88. package/lib/modelCompare/utils/.eslintrc.json +0 -22
  89. package/lib/render/.eslintrc.json +0 -22
  90. package/lib/transform/.eslintrc.json +0 -13
  91. package/lib/transform/db/.eslintrc.json +0 -41
  92. package/lib/transform/draft/.eslintrc.json +0 -4
  93. package/lib/transform/effective/.eslintrc.json +0 -4
  94. package/lib/transform/universalCsn/.eslintrc.json +0 -37
  95. package/lib/utils/.eslintrc.json +0 -7
@@ -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'
@@ -70,9 +56,20 @@ function propagate( model ) {
70
56
  enum: expensive,
71
57
  params: expensive, // actually only with parent action
72
58
  returns,
73
- $filtered: annotation,
59
+ $filtered: annotation, // TODO(v5): Remove
60
+ $enclosed: annotation,
74
61
  };
62
+ const ruleToFunction = {
63
+ __proto__: null,
64
+ never,
65
+ onlyViaArtifact,
66
+ notWithPersistenceTable,
67
+ };
68
+ for (const rule in propagationRules)
69
+ props[rule] = ruleToFunction[propagationRules[rule]];
70
+
75
71
  const { options } = model;
72
+ const { rewriteAnnotationsRefs } = xprRewriteFns( model );
76
73
  // eslint-disable-next-line max-len
77
74
  const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
78
75
  const { warning, throwWithError } = model.$messageFunctions;
@@ -103,7 +100,7 @@ function propagate( model ) {
103
100
  const news = [];
104
101
  for (const target of targets) {
105
102
  const origin = getOrigin( target );
106
- if (origin) {
103
+ if (origin && origin.kind !== '$self') {
107
104
  // Calculated elements that are simple references: `calc = field;`.
108
105
  // Respect sibling properties in inheritance.
109
106
  if (target._calcOrigin?._origin && target.value?._artifact) {
@@ -202,6 +199,10 @@ function propagate( model ) {
202
199
  target[prop] = [ ...val ];
203
200
  target[prop].$inferred = 'prop';
204
201
  }
202
+ else if (prop.charAt(0) === '@' && val?.kind === '$annotation') {
203
+ target[prop] = Object.assign( copyExpr( val ), { $inferred: 'prop' } );
204
+ rewriteAnnotationsRefs( target, source, prop );
205
+ }
205
206
  else {
206
207
  target[prop] = Object.assign( {}, val, { $inferred: 'prop' } );
207
208
  if (val._artifact !== undefined)
@@ -270,6 +271,7 @@ function propagate( model ) {
270
271
  target[prop][$inferred] = 'prop';
271
272
  }
272
273
 
274
+ // Only propagate if parent object (which is not necessarily `_parent`) was propagated.
273
275
  function onlyViaParent( prop, target, source ) {
274
276
  if (target.$inferred === 'proxy' || target.$inferred === 'expanded')
275
277
  // assocs and enums do not have 'include'
@@ -279,6 +281,8 @@ function propagate( model ) {
279
281
  function targetAspect( prop, target, source ) {
280
282
  if (target.targetAspect)
281
283
  return;
284
+ if (target.type?._artifact === model.definitions['cds.Association'])
285
+ return; // don't propagate targetAspect to associations (e.g. via $enclosed)
282
286
  const ta = source.targetAspect;
283
287
  if (!ta.elements && !ta._origin) { // _origin set for elements in source
284
288
  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:
@@ -178,9 +182,12 @@ function resolve( model ) {
178
182
  // Path could start with table alias; get start index
179
183
  let index = path.indexOf(nav.item);
180
184
  if (index === -1)
181
- return;
185
+ return; // should not happen
182
186
 
183
187
  let navItem = nav.navigation;
188
+ if (!nav.item._navigation) // first non-table-alias
189
+ setLink( nav.item, '_navigation', navItem );
190
+
184
191
  if (path[index].where || path[index].args)
185
192
  return;
186
193
  ++index;
@@ -363,6 +370,9 @@ function resolve( model ) {
363
370
  const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
364
371
  const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
365
372
 
373
+ if (model.options.lspMode && art.name && !art._main)
374
+ resolveDefinitionName( art );
375
+
366
376
  // Check KEY (TODO: make this an extra function)
367
377
  const { key } = art;
368
378
  if (key?.val && !key.$inferred) {
@@ -557,10 +567,12 @@ function resolve( model ) {
557
567
  const sType = specifiedElement.type?._artifact;
558
568
  const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
559
569
  const iType = iTypeArt || inferredElement;
560
-
561
- // xor: could be missing a type;
562
570
  // FIXME: The coding above returns incorrect iType for expand on associations
563
571
 
572
+ // $enclosed: maybe composition was changed to association; we allow that change here.
573
+ const compToAssoc = sType === model.definitions['cds.Association'] && inferredElement.target;
574
+
575
+ // xor: could be missing a type;
564
576
  if (!specifiedElement.type && inferredElement.type) {
565
577
  error( 'query-mismatched-element', [ specifiedElement.location, user ], {
566
578
  '#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
@@ -568,7 +580,7 @@ function resolve( model ) {
568
580
  return;
569
581
  }
570
582
  // If specified type is `null`, type could not be resolved.
571
- else if (sType && sType !== iType &&
583
+ else if (!compToAssoc && sType && sType !== iType &&
572
584
  // Special case for $recompilation: allow one level of type indirection. See #12113.
573
585
  (!model.options.$recompile || sType !== iType.type?._artifact)) {
574
586
  const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
@@ -1348,6 +1360,19 @@ function resolve( model ) {
1348
1360
  // General resolver functions
1349
1361
  //--------------------------------------------------------------------------
1350
1362
 
1363
+ // Resolve the n-1 path steps before the definition name for LSP.
1364
+ function resolveDefinitionName( art ) {
1365
+ const path = art?.name?.path;
1366
+ if (!art || art._main || !path || path.length <= 1)
1367
+ return;
1368
+
1369
+ let name = art.name.id;
1370
+ for (let i = path.length - 1; i > 0; --i) {
1371
+ name = name.substring(0, name.length - path[i].id.length - 1);
1372
+ setArtifactLink( path[i - 1], model.definitions[name] || false );
1373
+ }
1374
+ }
1375
+
1351
1376
  // Resolve the type and its arguments if applicable.
1352
1377
  function resolveTypeExpr( art, user ) {
1353
1378
  const typeArt = resolvePath( art.type, 'type', user );
@@ -1367,7 +1392,6 @@ function resolve( model ) {
1367
1392
  if (!anno.kind)
1368
1393
  initAnnotationForExpression( anno, art );
1369
1394
  resolveExpr( expr, 'annotation', anno );
1370
- reportUnsupportedAnnoExpr( expr );
1371
1395
  }
1372
1396
  else if (expr.literal === 'array') {
1373
1397
  expr.val.forEach( val => resolveAnnoExpr( val, art, anno ) );
@@ -1377,32 +1401,22 @@ function resolve( model ) {
1377
1401
  }
1378
1402
  }
1379
1403
 
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
1404
+ /**
1405
+ * For faster processing, mark artifacts and annotations which contain anno expressions
1406
+ *
1407
+ * @param {object} anno
1408
+ * @param {XSN.Artifact} art
1409
+ */
1396
1410
  function initAnnotationForExpression( anno, art ) {
1397
1411
  anno.kind = '$annotation';
1398
1412
  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
- }
1413
+ art.$contains ??= {};
1414
+ art.$contains.$annotation = { // set in resolveExprItem
1415
+ $path: false,
1416
+ $self: false,
1417
+ };
1418
+ // Think about tagging parents too (like before #12636).
1419
+ // Might be useful for future recursive types.
1406
1420
  }
1407
1421
 
1408
1422
  function resolveExpr( expr, exprCtx, user ) {
@@ -1434,6 +1448,11 @@ function resolve( model ) {
1434
1448
  resolveParamsAndWhere( step, expected, user, step === last );
1435
1449
  // TODO: delete 4th arg
1436
1450
  }
1451
+
1452
+ if (expected === 'annotation') {
1453
+ user._outer.$contains.$annotation.$path = true;
1454
+ user._outer.$contains.$annotation.$self ||= expr.path[0]?._navigation?.kind === '$self';
1455
+ }
1437
1456
  }
1438
1457
  else if (expr.query) {
1439
1458
  const { query } = expr;
@@ -19,6 +19,7 @@ const {
19
19
  isAssocToPrimaryKeys,
20
20
  artifactRefLocation,
21
21
  } = require('./utils');
22
+ const { isBetaEnabled } = require('../base/model');
22
23
 
23
24
  const $inferred = Symbol.for( 'cds.$inferred' );
24
25
  const $location = Symbol.for( 'cds.$location' );
@@ -133,14 +134,14 @@ function fns( model ) {
133
134
  param: paramSemantics,
134
135
  },
135
136
  filter: {
136
- lexical: justDollarSelf,
137
+ lexical: justDollarAliases,
137
138
  dollar: true,
138
139
  dynamic: targetElements,
139
140
  notFound: undefinedTargetElement,
140
141
  param: paramSemantics,
141
142
  },
142
143
  'calc-filter': {
143
- lexical: justDollarSelf,
144
+ lexical: justDollarAliases,
144
145
  dollar: true,
145
146
  dynamic: targetElements,
146
147
  navigation: calcElemNavigation,
@@ -181,7 +182,7 @@ function fns( model ) {
181
182
  check: checkColumnRef,
182
183
  param: paramSemantics,
183
184
  nestedColumn: () => ({ // in expand and inline
184
- lexical: justDollarSelf,
185
+ lexical: justDollarAliases,
185
186
  dollar: true,
186
187
  dynamic: nestedElements,
187
188
  notFound: undefinedNestedElement,
@@ -197,7 +198,7 @@ function fns( model ) {
197
198
  param: paramSemantics,
198
199
  },
199
200
  calc: {
200
- lexical: justDollarSelf,
201
+ lexical: justDollarAliases,
201
202
  dollar: true,
202
203
  dynamic: parentElements,
203
204
  navigation: calcElemNavigation,
@@ -213,7 +214,7 @@ function fns( model ) {
213
214
  param: paramSemantics,
214
215
  },
215
216
  on: { // unmanaged assoc: outside query, redirected or new assoc in column
216
- lexical: justDollarSelf,
217
+ lexical: justDollarAliases,
217
218
  dollar: true,
218
219
  dynamic: parentElements,
219
220
  navigation: assocOnNavigation,
@@ -222,7 +223,7 @@ function fns( model ) {
222
223
  check: checkAssocOn,
223
224
  param: paramUnsupported,
224
225
  nestedColumn: () => ({ // in expand and inline
225
- lexical: justDollarSelf,
226
+ lexical: justDollarAliases,
226
227
  dollar: true,
227
228
  dynamic: parentElements,
228
229
  navigation: assocOnNavigation,
@@ -276,7 +277,7 @@ function fns( model ) {
276
277
  param: paramSemantics,
277
278
  },
278
279
  annotation: { // annotation assignments
279
- lexical: justDollarSelf, // TODO: forbid $projection
280
+ lexical: justDollarAliases,
280
281
  dollar: true,
281
282
  dynamic: parentElements,
282
283
  navigation: assocOnNavigation,
@@ -287,8 +288,8 @@ function fns( model ) {
287
288
  'ref-undefined-param': 'anno-undefined-param',
288
289
  },
289
290
  param: paramSemantics,
290
- nestedColumn: () => ({ // in expand and inline - TODO
291
- lexical: justDollarSelf,
291
+ nestedColumn: () => ({
292
+ lexical: justDollarAliases,
292
293
  dollar: true,
293
294
  dynamic: parentElements,
294
295
  navigation: assocOnNavigation,
@@ -298,14 +299,21 @@ function fns( model ) {
298
299
  },
299
300
  // TODO: introduce some kind of inheritance
300
301
  annoRewrite: { // annotation assignments
301
- lexical: justDollarSelf, // TODO: forbid $projection
302
+ lexical: justDollarAliases,
302
303
  dollar: true,
303
304
  dynamic: parentElements,
304
305
  navigation: assocOnNavigation,
305
306
  noDep: true,
306
307
  notFound: null, // no error, just falsy links
307
308
  param: paramSemantics,
308
- // nestedColumn: // in expand and inline - TODO
309
+ nestedColumn: () => ({
310
+ lexical: justDollarAliases,
311
+ dollar: true,
312
+ dynamic: parentElements,
313
+ navigation: assocOnNavigation,
314
+ notFound: undefinedParentElement,
315
+ rewriteProjectionToSelf: true,
316
+ }),
309
317
  },
310
318
  };
311
319
 
@@ -313,9 +321,11 @@ function fns( model ) {
313
321
  traverseExpr,
314
322
  resolveUncheckedPath,
315
323
  resolveTypeArgumentsUnchecked, // TODO: move to some other file
324
+ resolvePathRoot,
316
325
  resolvePath,
317
326
  checkExpr,
318
327
  checkOnCondition,
328
+ navigationEnv,
319
329
  nestedElements,
320
330
  attachAndEmitValidNames,
321
331
  } );
@@ -354,7 +364,6 @@ function fns( model ) {
354
364
  const { path } = ref;
355
365
  if (!path || path.broken) // incomplete type AST
356
366
  return undefined;
357
-
358
367
  const semantics = referenceSemantics[refCtx];
359
368
  if (!semantics.isMainRef)
360
369
  throw new CompilerAssertion( `resolveUncheckedPath() called for reference ctx '${ refCtx }'` );
@@ -628,6 +637,22 @@ function fns( model ) {
628
637
  return art;
629
638
  }
630
639
 
640
+ /**
641
+ * Resolve the _path-root_ only. Used for rewriting annotation paths.
642
+ *
643
+ * @param ref
644
+ * @param {string} expected
645
+ * @param user
646
+ */
647
+ function resolvePathRoot( ref, expected, user ) {
648
+ if (ref == null || !ref.path) // no references -> nothing to do
649
+ return undefined;
650
+ const s = referenceSemantics[expected];
651
+ const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
652
+ const r = getPathRoot( ref, semantics, user );
653
+ return r && acceptPathRoot( r, ref, semantics, user );
654
+ }
655
+
631
656
  // Helper functions for resolve[Unchecked]Path, getPath{Root,Item}: -----------
632
657
 
633
658
  function acceptLexical( art, path, semantics, user ) {
@@ -687,9 +712,14 @@ function fns( model ) {
687
712
  // TODO: remove again, should be easy enough in to-csn without.
688
713
  if (path.length === 1 && art.kind === '$tableAlias')
689
714
  (user._user || user).$noOrigin = true;
715
+ if (head.id === '$projection' && user.kind === '$annotation') {
716
+ error( 'ref-unsupported-projection', [ head.location, user ],
717
+ { code: '$projection', newcode: '$self' },
718
+ '$(CODE) is not supported in annotations; replace by $(NEWCODE)' );
719
+ }
690
720
  return art;
691
721
  }
692
- case '$parameters': { // TODO: remove from CC
722
+ case '$parameters': {
693
723
  // TODO: if ref.scope='param' is handled, test that here, too ?
694
724
  const { id } = path[1];
695
725
  message( 'ref-obsolete-parameters', [ head.location, user ],
@@ -756,10 +786,10 @@ function fns( model ) {
756
786
  function userBlock( user ) {
757
787
  return definedViaCdl( user ) && user._block;
758
788
  }
759
- function justDollarSelf( user ) {
789
+ function justDollarAliases( user ) {
760
790
  const query = userQuery( user );
761
791
  if (!query)
762
- return user._main || user;
792
+ return user._main || user; // TODO: also contains `up_` for aspects; remove
763
793
  // query.$tableAliases contains both aliases and $self/$projection
764
794
  const aliases = query.$tableAliases;
765
795
  const r = Object.create( null );
@@ -783,7 +813,7 @@ function fns( model ) {
783
813
  const aliases = userQuery( user ).$tableAliases;
784
814
  user.$extended = Object.keys( aliases )[0];
785
815
  }
786
- return justDollarSelf( user );
816
+ return justDollarAliases( user );
787
817
  }
788
818
 
789
819
  // Functions called via semantics.dynamic: ------------------------------------
@@ -829,7 +859,13 @@ function fns( model ) {
829
859
  return query._combined; // TODO: do we need query._parent._combined ?
830
860
  }
831
861
  function parentElements( user ) {
832
- return environment( user._main && user.kind !== 'select' ? user._parent : user );
862
+ // Note: We could have `$self` in bound actions refer to its entity, but reject it now.
863
+ // If users request it, we can either allow it later or point them to binding parameters.
864
+ const useParent = user._main &&
865
+ user.kind !== 'select' &&
866
+ user.kind !== 'action' &&
867
+ user.kind !== 'function';
868
+ return environment( useParent ? user._parent : user );
833
869
  }
834
870
 
835
871
  function queryElements( user ) {
@@ -1144,7 +1180,7 @@ function fns( model ) {
1144
1180
  // corresponding column expression; this might have references to elements
1145
1181
  // of invisible table aliases; at least one stakeholder uses this,
1146
1182
  // so it can't be an error (yet).
1147
- warning( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1183
+ message( 'ref-deprecated-self-element', [ ref.path[0].location, user._user ], {},
1148
1184
  // eslint-disable-next-line max-len
1149
1185
  'Referring to the query\'s own elements here might lead to invalid SQL references; use source elements only' );
1150
1186
  return false;
@@ -1199,8 +1235,12 @@ function fns( model ) {
1199
1235
  }
1200
1236
 
1201
1237
  function acceptRealArtifact( art, user, ref ) {
1202
- // For compatibility, we accept `extend Unknown` without elements/actions/includes
1203
- if (art.kind !== 'namespace' || !(user.elements || user.actions || user.includes))
1238
+ if (art.kind !== 'namespace')
1239
+ return art;
1240
+ // For compatibility (≤v4), we accept `extend Unknown` without elements/actions/includes
1241
+ // In v5, only allow `extend with definitions`.
1242
+ if (!isBetaEnabled( model.options, 'v5preview' ) &&
1243
+ !(user.elements || user.actions || user.includes))
1204
1244
  return art;
1205
1245
  const { location } = ref.path[ref.path.length - 1];
1206
1246
  signalNotFound( 'ref-undefined-def', [ location, user ], null, { art } );