@sap/cds-compiler 4.9.2 → 5.0.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 (88) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/bin/cds_remove_invalid_whitespace.js +2 -1
  3. package/bin/cdsc.js +15 -11
  4. package/bin/cdshi.js +1 -0
  5. package/doc/CHANGELOG_BETA.md +7 -0
  6. package/lib/api/main.js +7 -19
  7. package/lib/api/options.js +5 -11
  8. package/lib/api/trace.js +0 -1
  9. package/lib/base/builtins.js +1 -0
  10. package/lib/base/location.js +4 -1
  11. package/lib/base/message-registry.js +29 -29
  12. package/lib/base/messages.js +22 -26
  13. package/lib/base/model.js +0 -2
  14. package/lib/base/node-helpers.js +0 -1
  15. package/lib/checks/enricher.js +1 -5
  16. package/lib/checks/structuredAnnoExpressions.js +30 -0
  17. package/lib/checks/validator.js +8 -0
  18. package/lib/compiler/assert-consistency.js +4 -1
  19. package/lib/compiler/base.js +1 -1
  20. package/lib/compiler/builtins.js +18 -2
  21. package/lib/compiler/checks.js +2 -5
  22. package/lib/compiler/define.js +7 -7
  23. package/lib/compiler/extend.js +68 -33
  24. package/lib/compiler/generate.js +1 -1
  25. package/lib/compiler/index.js +23 -6
  26. package/lib/compiler/lsp-api.js +501 -2
  27. package/lib/compiler/populate.js +2 -2
  28. package/lib/compiler/propagator.js +1 -4
  29. package/lib/compiler/resolve.js +2 -15
  30. package/lib/compiler/shared.js +112 -31
  31. package/lib/compiler/tweak-assocs.js +2 -16
  32. package/lib/compiler/utils.js +2 -1
  33. package/lib/compiler/xsn-model.js +4 -0
  34. package/lib/edm/annotations/genericTranslation.js +95 -42
  35. package/lib/edm/csn2edm.js +16 -4
  36. package/lib/edm/edm.js +2 -3
  37. package/lib/edm/edmAnnoPreprocessor.js +1 -2
  38. package/lib/edm/edmPreprocessor.js +1 -7
  39. package/lib/gen/Dictionary.json +29 -2
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +2 -1
  42. package/lib/gen/languageParser.js +4995 -4817
  43. package/lib/json/csnVersion.js +1 -1
  44. package/lib/json/from-csn.js +4 -7
  45. package/lib/json/to-csn.js +23 -12
  46. package/lib/language/antlrParser.js +2 -2
  47. package/lib/language/errorStrategy.js +0 -1
  48. package/lib/language/genericAntlrParser.js +35 -12
  49. package/lib/language/multiLineStringParser.js +3 -2
  50. package/lib/language/textUtils.js +1 -0
  51. package/lib/main.d.ts +28 -9
  52. package/lib/main.js +7 -4
  53. package/lib/model/csnRefs.js +20 -4
  54. package/lib/model/csnUtils.js +0 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +1 -1
  57. package/lib/optionProcessor.js +28 -9
  58. package/lib/render/manageConstraints.js +1 -1
  59. package/lib/render/toCdl.js +36 -7
  60. package/lib/render/toSql.js +1 -0
  61. package/lib/render/utils/common.js +12 -9
  62. package/lib/render/utils/stringEscapes.js +1 -0
  63. package/lib/transform/db/applyTransformations.js +13 -8
  64. package/lib/transform/db/associations.js +62 -54
  65. package/lib/transform/db/constraints.js +23 -25
  66. package/lib/transform/db/expansion.js +1 -6
  67. package/lib/transform/db/flattening.js +89 -111
  68. package/lib/transform/db/temporal.js +3 -4
  69. package/lib/transform/db/views.js +0 -1
  70. package/lib/transform/draft/odata.js +51 -3
  71. package/lib/transform/effective/annotations.js +3 -2
  72. package/lib/transform/effective/flattening.js +135 -0
  73. package/lib/transform/effective/main.js +6 -6
  74. package/lib/transform/effective/types.js +13 -9
  75. package/lib/transform/forOdata.js +0 -2
  76. package/lib/transform/forRelationalDB.js +0 -19
  77. package/lib/transform/localized.js +7 -8
  78. package/lib/transform/odata/flattening.js +39 -31
  79. package/lib/transform/odata/typesExposure.js +5 -17
  80. package/lib/transform/transformUtils.js +1 -1
  81. package/lib/transform/translateAssocsToJoins.js +21 -3
  82. package/lib/utils/file.js +13 -7
  83. package/lib/utils/moduleResolve.js +59 -8
  84. package/lib/utils/term.js +3 -2
  85. package/package.json +7 -3
  86. package/share/messages/message-explanations.json +2 -0
  87. package/share/messages/type-unexpected-foreign-keys.md +52 -0
  88. package/share/messages/type-unexpected-on-condition.md +52 -0
@@ -19,7 +19,6 @@ const {
19
19
  isAssocToPrimaryKeys,
20
20
  artifactRefLocation,
21
21
  } = require('./utils');
22
- const { isBetaEnabled } = require('../base/model');
23
22
 
24
23
  const $inferred = Symbol.for( 'cds.$inferred' );
25
24
  const $location = Symbol.for( 'cds.$location' );
@@ -56,13 +55,14 @@ function fns( model ) {
56
55
  lexical: userBlock,
57
56
  dynamic: modelDefinitions,
58
57
  notFound: undefinedForAnnotate,
58
+ accept: extendableArtifact,
59
59
  },
60
60
  extend: {
61
61
  isMainRef: 'no-generated',
62
62
  lexical: userBlock,
63
63
  dynamic: modelDefinitions,
64
64
  notFound: undefinedDefinition,
65
- accept: acceptRealArtifact,
65
+ accept: extendableArtifact,
66
66
  },
67
67
  _extensions: {
68
68
  isMainRef: 'all',
@@ -163,7 +163,7 @@ function fns( model ) {
163
163
  param: paramSemantics,
164
164
  },
165
165
  'limit-offset': 'limit-rows',
166
- // general element references -----------------------------------------------
166
+ // general element / variable references --------------------------------------
167
167
  where: {
168
168
  lexical: tableAliasesAndSelf,
169
169
  dollar: true,
@@ -279,7 +279,7 @@ function fns( model ) {
279
279
  annotation: { // annotation assignments
280
280
  lexical: justDollarAliases,
281
281
  dollar: true,
282
- dynamic: parentElements,
282
+ dynamic: parentElementsOrKeys,
283
283
  navigation: assocOnNavigation,
284
284
  noDep: true,
285
285
  notFound: undefinedParentElement,
@@ -298,6 +298,7 @@ function fns( model ) {
298
298
  }),
299
299
  },
300
300
  // TODO: introduce some kind of inheritance
301
+ // used by xpr-rewrite.js to resolve rewritten path roots.
301
302
  annoRewrite: { // annotation assignments
302
303
  lexical: justDollarAliases,
303
304
  dollar: true,
@@ -323,6 +324,7 @@ function fns( model ) {
323
324
  resolveTypeArgumentsUnchecked, // TODO: move to some other file
324
325
  resolvePathRoot,
325
326
  resolvePath,
327
+ resolveDefinitionName,
326
328
  checkExpr,
327
329
  checkOnCondition,
328
330
  navigationEnv,
@@ -405,6 +407,7 @@ function fns( model ) {
405
407
 
406
408
  const s = referenceSemantics[expected];
407
409
  const semantics = (typeof s === 'string') ? referenceSemantics[s] : s;
410
+ semantics.name = expected;
408
411
 
409
412
  const r = getPathRoot( ref, semantics, origUser );
410
413
  const root = r && acceptPathRoot( r, ref, semantics, origUser );
@@ -512,6 +515,25 @@ function fns( model ) {
512
515
  artifact.$typeArgs = undefined;
513
516
  }
514
517
 
518
+ // Resolve the n-1 path steps before the definition name for LSP.
519
+ function resolveDefinitionName( art ) {
520
+ const path = art?.name?.path;
521
+ if (!art || art._main || !path || path.length <= 1)
522
+ return;
523
+
524
+ // Don't resolve paths in an annotation as a definition!
525
+ const definitions = art.kind === 'annotation' ? model.vocabularies : model.definitions;
526
+
527
+ let name = art.name.id;
528
+ if (art.kind === 'namespace') // namespace-statements are ref-only.
529
+ setArtifactLink( path[path.length - 1], definitions[name] || false );
530
+
531
+ for (let i = path.length - 1; i > 0; --i) {
532
+ name = name.substring(0, name.length - path[i].id.length - 1);
533
+ setArtifactLink( path[i - 1], definitions[name] || false );
534
+ }
535
+ }
536
+
515
537
  function getPathRoot( { path, scope, location }, semantics, user ) {
516
538
  // TODO: use string value of isMainRef?
517
539
  const head = path[0];
@@ -524,8 +546,11 @@ function fns( model ) {
524
546
  ruser = ruser._outer;
525
547
 
526
548
  // Handle expand/inline, `type of`, :param, global (internally for CDL):
527
- if (user._pathHead && !semantics.isMainRef) // in expand/inline
549
+ if (user._pathHead && !semantics.isMainRef) { // in expand/inline
550
+ const { name } = semantics;
528
551
  semantics = semantics.nestedColumn();
552
+ semantics.name = name;
553
+ }
529
554
  if (typeof scope === 'string') { // typeOf, param, global
530
555
  semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
531
556
  if (!semantics) {
@@ -563,10 +588,13 @@ function fns( model ) {
563
588
  if (r)
564
589
  return setArtifactLink( head, r );
565
590
 
566
- if (!semantics.dollar)
591
+ if (!semantics.dollar) {
567
592
  valid.push( dynamicDict );
568
- else
569
- valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
593
+ }
594
+ else {
595
+ valid.push( removeInvalidMagicVariables( model.$magicVariables.elements, semantics ),
596
+ removeDollarNames( dynamicDict ) );
597
+ }
570
598
  // TODO: streamline function arguments (probably: user, path, semantics )
571
599
  const undef = semantics.notFound?.( user._user || user, head, valid, dynamicDict,
572
600
  !isMainRef && user._user && user._artifact,
@@ -629,7 +657,6 @@ function fns( model ) {
629
657
  // simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
630
658
  // could "change" to this message at the end of compile():
631
659
  error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
632
- // eslint-disable-next-line max-len
633
660
  'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
634
661
  return null; // continuation semantics: like “not found”
635
662
  }
@@ -663,9 +690,13 @@ function fns( model ) {
663
690
  // Do not accept a lonely table alias and `$projection`
664
691
  // TODO: test table alias and mixin named `$projection`
665
692
  if (path.length !== 1 || user.expand || user.inline) {
666
- // Rewrite $projection to $self
667
- if (semantics.rewriteProjectionToSelf && art.kind === '$self' && path[0].id === '$projection')
693
+ if (semantics.rewriteProjectionToSelf &&
694
+ art.kind === '$self' && path[0].id === '$projection') {
695
+ // Rewrite $projection to $self
668
696
  path[0].id = '$self';
697
+ warning( 'ref-expecting-$self', [ path[0].location, user ],
698
+ { code: '$projection', newcode: '$self' });
699
+ }
669
700
  return art.name?.$inferred !== '$internal'; // not a compiler-generated internal alias
670
701
  }
671
702
 
@@ -725,7 +756,14 @@ function fns( model ) {
725
756
  message( 'ref-obsolete-parameters', [ head.location, user ],
726
757
  { code: `$parameters.${ id }`, newcode: `:${ id }` },
727
758
  'Obsolete $(CODE) - replace by $(NEWCODE)' );
728
- // TODO: replace it in to-csn correspondingly, probably v5 or later in v4 ?
759
+ return art;
760
+ }
761
+ case 'builtin': {
762
+ if (art.name.id === '$at') {
763
+ warning( 'ref-deprecated-variable', [ head.location, user ],
764
+ { code: '$at', newcode: '$valid' },
765
+ '$(CODE) is deprecated; use $(NEWCODE) instead' );
766
+ }
729
767
  return art;
730
768
  }
731
769
  default:
@@ -868,6 +906,13 @@ function fns( model ) {
868
906
  return environment( useParent ? user._parent : user );
869
907
  }
870
908
 
909
+ function parentElementsOrKeys( user ) {
910
+ // annotations on foreign keys only ever have access to their keys (except of course via $self)
911
+ if (user.kind === 'key')
912
+ return user._parent?.foreignKeys || Object.create( null );
913
+ return parentElements( user );
914
+ }
915
+
871
916
  function queryElements( user ) {
872
917
  return environment( user );
873
918
  }
@@ -1113,11 +1158,11 @@ function fns( model ) {
1113
1158
  // TODO: if it becomes non-configurable, we can omit this warning
1114
1159
  let id = pathName( path );
1115
1160
  let head = path[0]._artifact || { _parent: art };
1116
- // eslint-disable-next-line no-cond-assign
1117
1161
  while ((head = head?._parent) && head.kind === 'builtin')
1118
1162
  id = `${ head.name.id }.${ id }`;
1119
1163
  const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
1120
- signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
1164
+ signalNotFound( msgId, [ item.location, user ],
1165
+ removeInvalidMagicVariables( valid, semantics ), { id }, semantics );
1121
1166
  }
1122
1167
  else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
1123
1168
  signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
@@ -1199,9 +1244,16 @@ function fns( model ) {
1199
1244
  : acceptElemOrVar( art, user, ref );
1200
1245
  }
1201
1246
 
1202
- function acceptElemOrVar( art, user, ref ) {
1247
+ function acceptElemOrVar( art, user, ref, semantics ) {
1203
1248
  const { path } = ref;
1204
1249
  if (art.kind === 'builtin') {
1250
+ if (art.$onlyInExprCtx && !art.$onlyInExprCtx.includes(semantics.name)) {
1251
+ error( 'ref-unexpected-var', [ ref.location, user ], {
1252
+ '#': art.$onlyInExprCtx[0], name: pathName( path ),
1253
+ });
1254
+ return null;
1255
+ }
1256
+
1205
1257
  if (user.expand || user.inline) {
1206
1258
  const location = (user.expand || user.inline)[$location];
1207
1259
  const code = (user.expand) ? '{ ‹expand› }' : '.{ ‹inline› }';
@@ -1234,16 +1286,19 @@ function fns( model ) {
1234
1286
  return art;
1235
1287
  }
1236
1288
 
1237
- function acceptRealArtifact( art, user, ref ) {
1289
+ /**
1290
+ * Returns true, if the artifact is a _real_ artifact that can be used for `extend`/`annotate`.
1291
+ */
1292
+ function extendableArtifact( art, user, ref ) {
1238
1293
  if (art.kind !== 'namespace')
1239
1294
  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))
1244
- return art;
1245
1295
  const { location } = ref.path[ref.path.length - 1];
1246
- signalNotFound( 'ref-undefined-def', [ location, user ], null, { art } );
1296
+ if (user.kind === 'extend' && !(user.elements || user.actions || user.includes))
1297
+ return art; // allow `extend with definitions` and empty extends
1298
+
1299
+ // for `annotate`, handle "namespaces" just like unknown artifacts: only emit a warning
1300
+ signalNotFound( user.kind === 'annotate' ? 'ext-undefined-def' : 'ref-undefined-def',
1301
+ [ location, user ], null, { art } );
1247
1302
  return false;
1248
1303
  }
1249
1304
 
@@ -1459,7 +1514,7 @@ function fns( model ) {
1459
1514
  // console.log('NAV:',expr.path.map(r=>r.id),self)
1460
1515
  if (self || self == null && columnRefStartsWithSelf( user )) {
1461
1516
  checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1462
- checkNoUnmanaged( expr, user, 'self-unmanaged' );
1517
+ checkNoUnmanaged( expr, user, true, 'self-unmanaged' );
1463
1518
  }
1464
1519
  // TODO: set navigation dependencies later to avoid both ref-cyclic and
1465
1520
  // ref-invalid-navigation/ref-unexpected-assoc
@@ -1469,18 +1524,20 @@ function fns( model ) {
1469
1524
  const { path } = expr;
1470
1525
  if (!path)
1471
1526
  return;
1472
- if (path?.[0]?._navigation?.kind !== '$tableAlias')
1527
+ const self = path?.[0]?._navigation?.kind !== '$tableAlias';
1528
+ if (self)
1473
1529
  checkOnlyForeignKeyNavigation( user, expr.path );
1474
- checkNoUnmanaged( expr, user );
1530
+ checkNoUnmanaged( expr, user, self );
1475
1531
  }
1476
1532
 
1477
1533
  function checkRefInQuery( expr, exprCtx, user ) {
1478
1534
  const { path } = expr;
1479
1535
  if (!path)
1480
1536
  return;
1481
- if (pathStartsWithSelf( expr ))
1537
+ const self = pathStartsWithSelf( expr );
1538
+ if (self)
1482
1539
  checkOnlyForeignKeyNavigation( user, expr.path, 0, 'self-' );
1483
- checkNoUnmanaged( expr, user );
1540
+ checkNoUnmanaged( expr, user, self );
1484
1541
  }
1485
1542
 
1486
1543
  function checkExpandInlineRef( art, user, ref ) {
@@ -1523,10 +1580,10 @@ function fns( model ) {
1523
1580
  }
1524
1581
  const index = userTargetElementPathIndex( user, path );
1525
1582
  checkOnlyForeignKeyNavigation( user, path, index );
1526
- if (ref._artifact?.on) {
1583
+ const last = path[path.length - 1];
1584
+ if (!last.where && ref._artifact?.on) { // filter already complained about
1527
1585
  const target = index > 0 && index < path.length && ref._artifact?.target;
1528
1586
  const msg = (target?._artifact === user._main) ? 'self' : 'unmanaged';
1529
- const last = path[path.length - 1];
1530
1587
  error( 'ref-unexpected-assoc', [ last.location, user ],
1531
1588
  { '#': msg, code: '= $self' } );
1532
1589
  }
@@ -1614,6 +1671,16 @@ function fns( model ) {
1614
1671
  index = checkCoveredByForeignKey( assoc, path, index, user, msgPrefix );
1615
1672
  }
1616
1673
  assoc = path[index]?._artifact;
1674
+ if (assoc?.target) {
1675
+ // testing this above is not enough: we would not complain about $self
1676
+ // assoc filter at end of ref with expand/inline. We might also move the
1677
+ // unmanaged test above to here.
1678
+ if (path[index]?.where) {
1679
+ error( 'ref-unexpected-assoc', [ path[index].location, user ],
1680
+ { '#': `${ msgPrefix }with-filter`, alias: '$self' } );
1681
+ return;
1682
+ }
1683
+ }
1617
1684
  }
1618
1685
  }
1619
1686
 
@@ -1635,7 +1702,6 @@ function fns( model ) {
1635
1702
  const txt = index >= path.length
1636
1703
  ? 'complete'
1637
1704
  : (isAssocToPrimaryKeys( assoc ) ? 'keys' : 'std');
1638
- // eslint-disable-next-line max-len
1639
1705
  error( 'ref-invalid-navigation', [ last.location, user ], {
1640
1706
  '#': msgPrefix + txt, art: assoc, name: last.id, alias: '$self',
1641
1707
  }, {
@@ -1653,10 +1719,12 @@ function fns( model ) {
1653
1719
  return path.length;
1654
1720
  }
1655
1721
 
1656
- function checkNoUnmanaged( ref, user, messageVariant = 'unmanaged' ) {
1722
+ function checkNoUnmanaged( ref, user, self, messageVariant = 'unmanaged' ) {
1657
1723
  if (ref._artifact?.on && !ref.$expected) {
1658
1724
  const { path } = ref;
1659
1725
  const last = path[path.length - 1];
1726
+ if (self && last.where) // already complained about filter
1727
+ return;
1660
1728
  error( 'ref-unexpected-assoc', [ last.location, user ],
1661
1729
  { '#': messageVariant, alias: '$self' } );
1662
1730
  }
@@ -1745,6 +1813,19 @@ function fns( model ) {
1745
1813
  }
1746
1814
  }
1747
1815
 
1816
+ function removeInvalidMagicVariables( variables, semantics ) {
1817
+ if (Array.isArray(variables))
1818
+ return variables.map(variable => removeInvalidMagicVariables( variable, semantics ));
1819
+
1820
+ const valid = Object.create(null);
1821
+ for (const name in variables) {
1822
+ const variable = variables[name];
1823
+ if (!variable.$onlyInExprCtx || variable.$onlyInExprCtx.includes( semantics.name ))
1824
+ valid[name] = variable;
1825
+ }
1826
+ return valid;
1827
+ }
1828
+
1748
1829
  function removeDollarNames( dict ) {
1749
1830
  const r = Object.create( null );
1750
1831
  for (const name in dict) {
@@ -5,7 +5,6 @@
5
5
  const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
- isBetaEnabled,
9
8
  } = require('../base/model');
10
9
  const { dictLocation, weakLocation, weakRefLocation } = require('../base/location');
11
10
 
@@ -45,8 +44,6 @@ function tweakAssocs( model ) {
45
44
  firstProjectionForPath,
46
45
  });
47
46
 
48
- const isV5preview = isBetaEnabled( model.options, 'v5preview' );
49
-
50
47
  // Phase 5: rewrite associations
51
48
  model._entities.forEach( rewriteArtifact );
52
49
  // Think hard whether an on condition rewrite can lead to a new cyclic
@@ -111,9 +108,7 @@ function tweakAssocs( model ) {
111
108
  info( 'assoc-target-not-in-service', loc,
112
109
  { target, '#': (elem._main.query ? 'select' : 'define') }, {
113
110
  std: 'Target $(TARGET) of association is outside any service', // not used
114
- // eslint-disable-next-line max-len
115
111
  define: 'Target $(TARGET) of explicitly defined association is outside any service',
116
- // eslint-disable-next-line max-len
117
112
  select: 'Target $(TARGET) of explicitly selected association is outside any service',
118
113
  } );
119
114
  }
@@ -129,7 +124,7 @@ function tweakAssocs( model ) {
129
124
  }
130
125
 
131
126
  function rewriteAssociationCheck( element ) {
132
- const elem = element.items || element; // TODO v5: nested items
127
+ const elem = element.items || element; // TODO v6: nested items
133
128
  if (elem.elements)
134
129
  forEachGeneric( elem, 'elements', rewriteAssociationCheck );
135
130
  if (!elem.target)
@@ -236,7 +231,7 @@ function tweakAssocs( model ) {
236
231
  }
237
232
 
238
233
  function rewriteAssociation( element ) {
239
- let elem = element.items || element; // TODO v5: nested items
234
+ let elem = element.items || element; // TODO v6: nested items
240
235
  if (elem.elements)
241
236
  forEachGeneric( elem, 'elements', rewriteAssociation );
242
237
  if (elem.targetAspect?.elements)
@@ -456,15 +451,6 @@ function tweakAssocs( model ) {
456
451
  };
457
452
  setArtifactLink( elem.type, assocType._artifact );
458
453
 
459
- if (!isV5preview) { // TODO(v5): Remove, only use $enclosed
460
- elem.$filtered = {
461
- val: true,
462
- literal: 'boolean',
463
- location,
464
- $inferred: '$generated',
465
- };
466
- }
467
-
468
454
  const isComp = (getUnderlyingBuiltinType( assoc )?.name?.id === 'cds.Composition');
469
455
  if (isComp) {
470
456
  elem.$enclosed = {
@@ -125,7 +125,8 @@ function proxyCopyMembers( art, dictProp, originDict, location, kind, tmpDepreca
125
125
  // TODO throughout the compiler: do not set art.‹prop›.$inferred if art.$inferred
126
126
  if (kind)
127
127
  member.kind = kind;
128
- else if (origin.key && !tmpDeprecated) // TODO(v5/v6): remove tmpDeprecated
128
+ else if (origin.key && !tmpDeprecated)
129
+ // TODO(v6): remove tmpDeprecated once `noKeyPropagationWithExpansions` is removed
129
130
  member.key = Object.assign( { $inferred: 'expanded' }, origin.key );
130
131
  if (kind && origin.masked) // TODO: remove!
131
132
  member.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
@@ -27,6 +27,10 @@ class XsnSource {
27
27
  artifacts = Object.create( null );
28
28
  vocabularies = Object.create( null );
29
29
  extensions = [];
30
+ $frontend;
31
+ constructor( frontend ) {
32
+ this.$frontend = frontend;
33
+ }
30
34
  }
31
35
 
32
36
  class XsnArtifact {