@sap/cds-compiler 4.2.4 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/cdsc.js +8 -0
  3. package/bin/cdshi.js +3 -3
  4. package/doc/CHANGELOG_BETA.md +7 -0
  5. package/lib/api/main.js +19 -0
  6. package/lib/base/location.js +16 -0
  7. package/lib/base/message-registry.js +47 -16
  8. package/lib/base/messages.js +49 -38
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
  11. package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
  12. package/lib/checks/existsMustEndInAssoc.js +27 -0
  13. package/lib/checks/onConditions.js +47 -1
  14. package/lib/checks/validator.js +10 -1
  15. package/lib/compiler/assert-consistency.js +23 -15
  16. package/lib/compiler/base.js +31 -14
  17. package/lib/compiler/builtins.js +21 -20
  18. package/lib/compiler/checks.js +36 -49
  19. package/lib/compiler/define.js +71 -91
  20. package/lib/compiler/extend.js +27 -25
  21. package/lib/compiler/finalize-parse-cdl.js +1 -1
  22. package/lib/compiler/generate.js +67 -87
  23. package/lib/compiler/kick-start.js +7 -5
  24. package/lib/compiler/populate.js +32 -30
  25. package/lib/compiler/propagator.js +2 -0
  26. package/lib/compiler/resolve.js +29 -25
  27. package/lib/compiler/shared.js +57 -31
  28. package/lib/compiler/tweak-assocs.js +203 -22
  29. package/lib/compiler/utils.js +0 -18
  30. package/lib/gen/Dictionary.json +10 -4
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/languageParser.js +3 -3
  33. package/lib/inspect/inspectPropagation.js +2 -1
  34. package/lib/json/from-csn.js +63 -28
  35. package/lib/json/to-csn.js +23 -13
  36. package/lib/language/antlrParser.js +1 -1
  37. package/lib/language/errorStrategy.js +5 -1
  38. package/lib/language/genericAntlrParser.js +67 -61
  39. package/lib/main.d.ts +26 -1
  40. package/lib/main.js +2 -1
  41. package/lib/model/csnRefs.js +1 -0
  42. package/lib/model/csnUtils.js +28 -0
  43. package/lib/model/revealInternalProperties.js +3 -9
  44. package/lib/optionProcessor.js +17 -1
  45. package/lib/render/toCdl.js +1 -1
  46. package/lib/transform/db/associations.js +3 -4
  47. package/lib/transform/db/backlinks.js +293 -0
  48. package/lib/transform/db/expansion.js +9 -7
  49. package/lib/transform/db/flattening.js +3 -2
  50. package/lib/transform/db/rewriteCalculatedElements.js +1 -67
  51. package/lib/transform/db/transformExists.js +3 -58
  52. package/lib/transform/db/views.js +8 -14
  53. package/lib/transform/effective/.eslintrc.json +4 -0
  54. package/lib/transform/effective/associations.js +101 -0
  55. package/lib/transform/effective/main.js +88 -0
  56. package/lib/transform/effective/misc.js +61 -0
  57. package/lib/transform/effective/queries.js +42 -0
  58. package/lib/transform/effective/types.js +121 -0
  59. package/lib/transform/forRelationalDB.js +12 -235
  60. package/lib/transform/localized.js +22 -3
  61. package/lib/transform/parseExpr.js +7 -3
  62. package/lib/transform/transformUtils.js +5 -22
  63. package/lib/transform/translateAssocsToJoins.js +42 -38
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
  65. package/package.json +1 -2
  66. package/lib/language/language.g4 +0 -3260
@@ -6,8 +6,7 @@ const {
6
6
  forEachGeneric,
7
7
  forEachInOrder,
8
8
  } = require('../base/model');
9
- const { dictLocation } = require('../base/location');
10
- const { weakLocation } = require('../base/messages');
9
+ const { dictLocation, weakLocation } = require('../base/location');
11
10
 
12
11
  const {
13
12
  setLink,
@@ -21,6 +20,7 @@ const {
21
20
  setExpandStatus,
22
21
  } = require('./utils');
23
22
  const { CsnLocation } = require('./classes');
23
+ const { CompilerAssertion } = require('../base/error');
24
24
 
25
25
  const $location = Symbol.for( 'cds.$location' );
26
26
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -131,20 +131,37 @@ function tweakAssocs( model ) {
131
131
  // eslint-disable-next-line max-len
132
132
  'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
133
133
  }
134
+ checkIgnoredFilter( elem );
134
135
  }
135
136
  else if (elem.foreignKeys && !inferredForeignKeys( elem.foreignKeys )) {
136
137
  const assoc = getOrigin( elem );
137
- if (assoc && assoc.on) {
138
+ if (assoc?.on) {
138
139
  error( 'rewrite-on-for-managed',
139
140
  [ elem.foreignKeys[$location] || dictLocation( elem.foreignKeys ), elem ],
140
141
  { art: assocWithExplicitSpec( assoc ) },
141
142
  'Do not specify foreign keys when redirecting the unmanaged association $(ART)' );
142
143
  }
143
- else if (assoc && assoc.foreignKeys) {
144
+ else if (assoc?.foreignKeys) {
144
145
  // same sequence is not checked
145
146
  rewriteKeysMatch( elem, assoc );
146
147
  rewriteKeysCovered( assoc, elem );
147
148
  }
149
+
150
+ checkIgnoredFilter( elem );
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Publishing an association with filters is allowed, but the filter is ignored
156
+ * if the association is redirected. That indicates modeling mistakes, so we
157
+ * emit a warning.
158
+ */
159
+ function checkIgnoredFilter( elem ) {
160
+ const lastStep = elem.value?.path?.[elem.value.path.length - 1];
161
+ if (lastStep?.where) {
162
+ const loc = lastStep.where.location;
163
+ const variant = elem.foreignKeys ? 'fKey' : 'onCond';
164
+ warning( 'query-ignoring-filter', [ loc, elem ], { '#': variant } );
148
165
  }
149
166
  }
150
167
 
@@ -208,7 +225,7 @@ function tweakAssocs( model ) {
208
225
  }
209
226
 
210
227
  function rewriteAssociation( element ) {
211
- let elem = element.items || element; // TODO v2: nested items
228
+ let elem = element.items || element; // TODO v5: nested items
212
229
  if (elem.elements)
213
230
  forEachGeneric( elem, 'elements', rewriteAssociation );
214
231
  if (!originTarget( elem ))
@@ -276,6 +293,8 @@ function tweakAssocs( model ) {
276
293
  } );
277
294
  if (elem.foreignKeys) // Possibly no fk was set
278
295
  elem.foreignKeys[$inferred] = 'rewrite';
296
+
297
+ addConditionFromAssocPublishing( elem, assoc );
279
298
  }
280
299
 
281
300
  // TODO: there is no need to rewrite the on condition of non-leading queries,
@@ -301,8 +320,11 @@ function tweakAssocs( model ) {
301
320
  const cond = copyExpr( assoc.on,
302
321
  // replace location in ON except if from mixin element
303
322
  nav.tableAlias && elem.name.location );
304
- cond.$inferred = 'copy';
305
323
  elem.on = cond;
324
+ addConditionFromAssocPublishing( elem, assoc );
325
+ // `cond` still points to the original condition; does not include possible assoc filter
326
+ elem.on.$inferred = 'copy';
327
+
306
328
  // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
307
329
  // 'Info','ON').toString(), nav)
308
330
  const { navigation } = nav;
@@ -312,11 +334,9 @@ function tweakAssocs( model ) {
312
334
  // Currently, having an unmanaged association inside a struct is not
313
335
  // supported by this function:
314
336
  if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
315
- // For "assoc1.assoc2" and "structelem1.assoc2"
316
- if (elem._redirected !== null) { // null = already reported
317
- error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
318
- 'The ON-condition is not rewritten here - provide an explicit ON-condition' );
319
- }
337
+ // For "assoc1.assoc2" and "struct.elem1.assoc2"
338
+ if (elem._redirected !== null) // null = already reported
339
+ error( 'rewrite-not-supported', [ elem.target.location, elem ] );
320
340
  return;
321
341
  }
322
342
  if (!nav.tableAlias || nav.tableAlias.path) {
@@ -328,7 +348,167 @@ function tweakAssocs( model ) {
328
348
  error( null, [ elem.value.location, elem ], {},
329
349
  'Selecting unmanaged associations from a sub query is not supported' );
330
350
  }
331
- cond.$inferred = 'rewrite';
351
+ elem.on.$inferred = 'rewrite';
352
+ }
353
+
354
+ /**
355
+ * If an unmanaged association is being published, we add a potential
356
+ * filter to the ON-condition and use its cardinality.
357
+ * If a managed association is published, we transform it into an unmanaged
358
+ * and do the same.
359
+ *
360
+ * The added condition (filter) is already rewritten relative to `elem`.
361
+ */
362
+ function addConditionFromAssocPublishing( elem, assoc ) {
363
+ const publishAssoc = (elem._main?.query && elem.value?.path?.length > 0);
364
+ if (!publishAssoc)
365
+ return;
366
+
367
+ const { location } = elem.name;
368
+ const lastStep = elem.value.path[elem.value.path.length - 1];
369
+
370
+ if (lastStep?.cardinality) {
371
+ if (!elem.cardinality)
372
+ elem.cardinality = assoc.cardinality || { location };
373
+ for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
374
+ if (lastStep.cardinality[card])
375
+ elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
376
+ }
377
+ }
378
+
379
+ if (lastStep?.where) {
380
+ // If there are foreign keys, transform them into an ON-condition first.
381
+ if (assoc.foreignKeys) {
382
+ const cond = foreignKeysToOnCondition( elem );
383
+ if (cond) {
384
+ elem.on = cond;
385
+ elem.foreignKeys = undefined;
386
+ }
387
+ }
388
+
389
+ if (elem.on) {
390
+ elem.on = {
391
+ op: { val: 'and', location },
392
+ args: [
393
+ // TODO: Get rid of $parens
394
+ { ...elem.on, $parens: [ assoc.location ] },
395
+ filterToCondition( lastStep, elem ),
396
+ ],
397
+ location,
398
+ $inferred: 'copy',
399
+ };
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Transform a filter on `assoc` into an ON-condition.
406
+ * Paths inside the filter are rewritten relative to `elem`.
407
+ */
408
+ function filterToCondition( assoc, elem ) {
409
+ const cond = copyExpr( assoc.where );
410
+ // TODO: Get rid of $parens
411
+ cond.$parens = [ assoc.location ];
412
+ traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
413
+ if (!expr.path || expr.path.length === 0)
414
+ return;
415
+
416
+ const root = expr.path[0]._navigation || expr.path[0]._artifact;
417
+ if (!root)
418
+ return; // only for compile error, e.g. missing definition
419
+ if (root.kind === '$self') {
420
+ // $projection -> $self for recompilability
421
+ expr.path[0].id = '$self';
422
+ }
423
+ else if (!root.builtin && root.kind !== 'builtin') {
424
+ expr.path.unshift({
425
+ id: elem.name.id,
426
+ location: elem.name.location,
427
+ });
428
+ setLink( expr.path[0], '_artifact', elem );
429
+ // _navigation link not necessary because this condition is not rewritten
430
+ // inside the same view (would otherwise be needed for mixins).
431
+ }
432
+ } );
433
+ return cond;
434
+ }
435
+
436
+ // Caller must ensure ON-condition correctness via rewriteExpr()!
437
+ function foreignKeysToOnCondition( elem ) {
438
+ const nav = pathNavigation( elem.value );
439
+ if (!nav.tableAlias && model.options.testMode && !elem._pathHead)
440
+ throw new CompilerAssertion('rewriting keys to ON-condition: no tableAlias but not inline');
441
+
442
+ if (!nav.tableAlias || elem._parent?.kind === 'element' ||
443
+ (nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
444
+ // - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
445
+ // - _parent is element for expand
446
+ // - nav.item is different for multi-path steps e.g. `sub.assoc`, which is not supported, yet
447
+ // TODO: Support this
448
+ error( 'rewrite-not-supported', [ elem.value.location, elem ] );
449
+ return null;
450
+ }
451
+
452
+ let cond = [];
453
+ forEachInOrder( elem, 'foreignKeys', function keyToCond( fKey ) {
454
+ // Format: lhs = rhs
455
+ // assoc.id = assoc_id
456
+ // lhs and rhs look the same, but have different ids and _artifact links.
457
+ // rhs is rewritten further down to ensure that the foreign key is projected.
458
+ const lhs = {
459
+ path: [
460
+ { id: elem.name.id, location: elem.name.location },
461
+ ...copyExpr( fKey.targetElement.path ),
462
+ ],
463
+ location: elem.name.location,
464
+ };
465
+ setLink( lhs.path[0], '_artifact', elem ); // different to rhs!
466
+ setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
467
+
468
+ const rhs = {
469
+ path: [
470
+ // use origin's name; elem could have alias
471
+ { id: elem._origin.name.id, location: elem._origin.name.location },
472
+ ...copyExpr( fKey.targetElement.path ),
473
+ ],
474
+ location: elem.name.location,
475
+ };
476
+ setLink( rhs.path[0], '_artifact', elem._origin ); // different to lhs!
477
+ setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
478
+
479
+ // Can't use rewriteExpr as that would use `assoc[…]` itself as well.
480
+ const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
481
+ rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
482
+
483
+ const fkCond = {
484
+ op: { val: 'ixpr', location: elem.name.location },
485
+ args: [
486
+ lhs,
487
+ { val: '=', literal: 'token', location: elem.name.location },
488
+ rhs,
489
+ ],
490
+ location: elem.name.location,
491
+ };
492
+ cond.push(fkCond);
493
+ } );
494
+
495
+ if (cond.length === 0) {
496
+ const lastStep = elem.value.path[elem.value.path.length - 1];
497
+ error( 'expr-missing-foreign-key', [ lastStep.location, elem ], {
498
+ '#': 'publishingFilter',
499
+ id: lastStep.id,
500
+ } );
501
+ return null;
502
+ }
503
+
504
+ cond = (cond.length === 1) ? cond[0]
505
+ : {
506
+ op: { val: 'and', location: elem.name.location },
507
+ args: cond,
508
+ location: elem.name.location,
509
+ };
510
+
511
+ return cond;
332
512
  }
333
513
 
334
514
  function rewriteExpr( expr, assoc, tableAlias ) {
@@ -347,14 +527,16 @@ function tweakAssocs( model ) {
347
527
  if (tableAlias) { // from ON cond of element in source ref/d by table alias
348
528
  const source = tableAlias._origin;
349
529
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
350
- // console.log( info(null, [ assoc.name.location, assoc ],
351
- // { names: expr.path.map(i => i.id), art: root }, 'TA').toString());
352
530
  if (!root || root._main !== source)
353
531
  return; // not $self or source element
354
532
  if (expr.scope === 'param' || root.kind === '$parameters')
355
533
  return; // are not allowed anyway - there was an error before
356
- const { item, elem } = firstProjectionForPath( expr.path, tableAlias, assoc );
357
- rewritePath( expr, item, assoc, elem, assoc.value.location );
534
+ const result = firstProjectionForPath( expr.path, tableAlias, assoc );
535
+ // For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
536
+ if (result.item && assoc._origin === result.item._artifact)
537
+ result.elem = assoc;
538
+
539
+ rewritePath( expr, result.item, assoc, result.elem, assoc.value.location );
358
540
  }
359
541
  else if (assoc._main.query) { // from ON cond of mixin element in query
360
542
  const root = expr.path[0]._navigation || expr.path[0]._artifact;
@@ -370,8 +552,8 @@ function tweakAssocs( model ) {
370
552
  }
371
553
  const nav = pathNavigation( expr );
372
554
  if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
373
- rewritePath( expr, nav.item, assoc,
374
- navProjection( nav.navigation, assoc ),
555
+ const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
556
+ rewritePath( expr, nav.item, assoc, elem,
375
557
  nav.item ? nav.item.location : expr.path[0].location );
376
558
  }
377
559
  }
@@ -384,6 +566,8 @@ function tweakAssocs( model ) {
384
566
  return; // just $self
385
567
  // corresponding elem in including structure
386
568
  const elem = (assoc._main.items || assoc._main).elements[item.id];
569
+ if (!elem)
570
+ return; // See #11755
387
571
  if (!(Array.isArray( elem ) || // no msg for redefs
388
572
  elem === item._artifact || // redirection for explicit def
389
573
  elem._origin === item._artifact)) {
@@ -410,7 +594,7 @@ function tweakAssocs( model ) {
410
594
  name: assoc.name.id, art: item._artifact, elemref: { ref: path },
411
595
  }, {
412
596
  std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
413
- element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
597
+ element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
414
598
  } );
415
599
  }
416
600
  delete root._navigation;
@@ -461,9 +645,6 @@ function tweakAssocs( model ) {
461
645
  }
462
646
 
463
647
  function rewriteItem( elem, item, name, assoc, forKeys ) {
464
- // TODO: for rewriting ON conditions of explicitly provided model targets,
465
- // we need to only rewrite the current element, not all sibling elements
466
- // TODO: this will be different in v4
467
648
  if (!elem._redirected)
468
649
  return true;
469
650
  for (const alias of elem._redirected) {
@@ -11,7 +11,6 @@
11
11
  'use strict';
12
12
 
13
13
  const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
14
- const { kindProperties } = require('./base');
15
14
  const { XsnName, XsnArtifact, CsnLocation } = require('./classes');
16
15
 
17
16
  const $inferred = Symbol.for( 'cds.$inferred' );
@@ -149,23 +148,6 @@ function setMemberParent( elem, name, parent, prop ) {
149
148
  parent = parent._outer;
150
149
  setLink( elem, '_parent', parent );
151
150
  setLink( elem, '_main', parent._main || parent );
152
- const parentName = parent.name || parent._outer?.name;
153
- if (parentName) // may not be available in e.g. cast() - TODO recheck (#9503)
154
- elem.name.absolute = parentName.absolute;
155
- if (!parentName || name == null)
156
- return;
157
- const normalized = kindProperties[elem.kind].normalized || elem.kind;
158
- [ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
159
- if (normalized === kind)
160
- elem.name[kind] = (parentName[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parentName[kind] }.${ name }` : name;
161
-
162
- else if (parentName[kind] != null)
163
- elem.name[kind] = parentName[kind];
164
-
165
- else
166
- delete elem.name[kind];
167
- } );
168
- // try { throw new CompilerAssertion('Foo') } catch (e) { elem.name.stack = e; };
169
151
  }
170
152
 
171
153
  /**
@@ -485,7 +485,8 @@
485
485
  "Common.ExternalID": {
486
486
  "Type": "Edm.String",
487
487
  "AppliesTo": [
488
- "Property"
488
+ "Property",
489
+ "Parameter"
489
490
  ],
490
491
  "$experimental": true
491
492
  },
@@ -2666,6 +2667,7 @@
2666
2667
  "$kind": "ComplexType",
2667
2668
  "BaseType": "Capabilities.ReadRestrictionsBase",
2668
2669
  "Properties": {
2670
+ "TypecastSegmentSupported": "Edm.Boolean",
2669
2671
  "ReadByKeyRestrictions": "Capabilities.ReadByKeyRestrictionsType",
2670
2672
  "Readable": "Edm.Boolean",
2671
2673
  "Permissions": "Collection(Capabilities.PermissionType)",
@@ -3513,7 +3515,9 @@
3513
3515
  "Padding": "Edm.Boolean",
3514
3516
  "HeaderFooter": "Edm.Boolean",
3515
3517
  "ResultSizeDefault": "Edm.Int32",
3516
- "ResultSizeMaximum": "Edm.Int32"
3518
+ "ResultSizeMaximum": "Edm.Int32",
3519
+ "IANATimezoneFormat": "Edm.Boolean",
3520
+ "Treeview": "Edm.Boolean"
3517
3521
  }
3518
3522
  },
3519
3523
  "PersonalData.EntitySemanticsType": {
@@ -3570,8 +3574,8 @@
3570
3574
  "Value": "UserID",
3571
3575
  "Type": "String"
3572
3576
  },
3573
- "EndOfPurposeDate": {
3574
- "Value": "EndOfPurposeDate",
3577
+ "EndOfBusinessDate": {
3578
+ "Value": "EndOfBusinessDate",
3575
3579
  "Type": "String"
3576
3580
  }
3577
3581
  },
@@ -3640,6 +3644,7 @@
3640
3644
  "$kind": "ComplexType",
3641
3645
  "Properties": {
3642
3646
  "Url": "Edm.String",
3647
+ "Stream": "Edm.Stream",
3643
3648
  "ContentType": "Edm.String",
3644
3649
  "ByteSize": "Edm.Int64",
3645
3650
  "ChangedAt": "Edm.DateTimeOffset",
@@ -3652,6 +3657,7 @@
3652
3657
  "$kind": "ComplexType",
3653
3658
  "Properties": {
3654
3659
  "Url": "Edm.String",
3660
+ "Stream": "Edm.Stream",
3655
3661
  "Width": "Edm.String",
3656
3662
  "Height": "Edm.String"
3657
3663
  }
@@ -1 +1 @@
1
- 0c76b976c907bebf1f1913d063dca4bf
1
+ 462a4274cec75ca772064210a7421aec
@@ -2674,7 +2674,7 @@ class languageParser extends genericAntlrParser_js_1.default {
2674
2674
  this.state = 290;
2675
2675
  this.match(languageParser.T__0);
2676
2676
  this._ctx.stop = this._input.LT(-1);
2677
- localctx.source.namespace = this.attachLocation(localctx.decl);
2677
+ localctx.source.namespace = localctx.decl;
2678
2678
  }
2679
2679
  catch (re) {
2680
2680
  if (re instanceof antlr4_1.default.error.RecognitionException) {
@@ -15997,7 +15997,7 @@ class languageParser extends genericAntlrParser_js_1.default {
15997
15997
  this.state = 3698;
15998
15998
  this.match(languageParser.Identifier);
15999
15999
  this._ctx.stop = this._input.LT(-1);
16000
- localctx.id = this.identAst(localctx.stop, localctx.category);
16000
+ localctx.id = this.identAst(null, localctx.category);
16001
16001
  }
16002
16002
  catch (re) {
16003
16003
  if (re instanceof antlr4_1.default.error.RecognitionException) {
@@ -16030,7 +16030,7 @@ class languageParser extends genericAntlrParser_js_1.default {
16030
16030
  this.consume();
16031
16031
  }
16032
16032
  this._ctx.stop = this._input.LT(-1);
16033
- localctx.id = this.identAst(localctx.stop, localctx.category);
16033
+ localctx.id = this.identAst(null, localctx.category);
16034
16034
  }
16035
16035
  catch (re) {
16036
16036
  if (re instanceof antlr4_1.default.error.RecognitionException) {
@@ -41,6 +41,7 @@ function inspectPropagation( xsn, options, artifactName ) {
41
41
  return null;
42
42
  }
43
43
  result.push(color.underline('analyzing propagation for artifact:'));
44
+ // TODO: back to artifactXsn.name.id (not ok now, and not before this change!)
44
45
  result.push(` name: ${ artifactXsn.name.id }`);
45
46
  result.push(` kind: ${ artifactXsn.kind }`);
46
47
 
@@ -131,7 +132,7 @@ function _inspectElements( artifactXsn ) {
131
132
  const elementXsn = artifactXsn.elements[element];
132
133
  const loc = locationString(_origin(elementXsn).name.location);
133
134
  let origin;
134
- const originName = elementXsn._origin?.name?.absolute || '';
135
+ const originName = (elementXsn._origin?._main || elementXsn._origin)?.name?.id || '';
135
136
 
136
137
  if (elementXsn.$inferred) {
137
138
  // Use nice(r) output for known $inferred
@@ -267,6 +267,7 @@ const schema = compileSchema( {
267
267
  dictionaryOf: definition,
268
268
  defaultKind: 'element',
269
269
  validKinds: [ 'element' ],
270
+ requires: requiresOnWithBothTargetProps,
270
271
  inKind: [
271
272
  'element',
272
273
  'type',
@@ -365,15 +366,15 @@ const schema = compileSchema( {
365
366
  },
366
367
  targetAspect: {
367
368
  type: artifactRef,
368
- xorException: 'target', // see xorGroup :type
369
+ xorException: inferredTargetEntityForAspect, // usually allows `target`
369
370
  msgVariant: 'or-object', // for 'syntax-expecting-string',
370
371
  requires: 'elements',
371
372
  optional: [ 'elements' ], // 'elements' for ad-hoc aspect compositions
372
- inKind: [ 'element', 'type' ],
373
+ inKind: [ 'element' ],
373
374
  },
374
375
  target: {
375
376
  type: artifactRef,
376
- xorException: 'targetAspect', // see xorGroup :type
377
+ xorException: inferredTargetEntityForAspect, // usually allows `targetAspect`
377
378
  msgVariant: 'or-object', // for 'syntax-expecting-string',
378
379
  requires: 'elements',
379
380
  optional: [ 'elements' ], // 'elements' for ad-hoc COMPOSITION OF (gensrc style CSN)
@@ -980,9 +981,7 @@ function definition( def, spec, xsn, csn, name ) {
980
981
  if (!isObject( def, spec )) {
981
982
  return {
982
983
  kind: (inExtensions ? 'annotate' : spec.defaultKind),
983
- name: {
984
- id: '', path: [], absolute: name || '', location: location(),
985
- },
984
+ name: { id: '', location: location() },
986
985
  location: location(),
987
986
  };
988
987
  }
@@ -1442,20 +1441,19 @@ function annoValue( val, spec ) {
1442
1441
  // - there is exactly one property ('=')
1443
1442
  // - there is at least one other expression property (e.g. "xpr")
1444
1443
  // TODO: Have xprInAnnoProperties centrally for other backends to use as well (toCdl)
1445
- const valKeys = Object.keys(val);
1446
- if (valKeys.length > 1 &&
1447
- xprInAnnoProperties.some(prop => val[prop] !== undefined)) {
1448
- const s = schema['@'].schema['-expr'];
1449
- const r = { location: location() };
1450
- Object.assign(r, object(val, s));
1451
- return r;
1452
- }
1453
- else if (valKeys.length === 1) {
1444
+ const valKeys = Object.keys( val );
1445
+ if (valKeys.length === 1) {
1454
1446
  ++virtualLine;
1455
- const r = refSplit( val['='], '=' );
1447
+ const r = refSplit( val['='], '=' ); // i.e. no extra `variant` stuff
1456
1448
  ++virtualLine;
1457
1449
  return r;
1458
1450
  }
1451
+ else if (xprInAnnoProperties.some( prop => val[prop] !== undefined) ) {
1452
+ const s = schema['@'].schema['-expr'];
1453
+ const r = { location: location() };
1454
+ Object.assign( r, object( val, s ) );
1455
+ return r;
1456
+ }
1459
1457
  // fallthrough -> unchecked structure
1460
1458
  }
1461
1459
  if (typeof val['#'] === 'string') {
@@ -1491,15 +1489,12 @@ function annoValue( val, spec ) {
1491
1489
  }
1492
1490
 
1493
1491
  function annotation( val, spec, xsn, csn, name ) {
1494
- const absolute = (xsn ? name.substring(1) : name);
1495
- // TODO: really care about variant (qualifier parts)?
1496
- const variantIndex = absolute.indexOf('#') + 1 || absolute.length; // including '#'
1497
- const n = refSplit( absolute.substring( 0, variantIndex ), !xsn && '{}' );
1498
- if (!n)
1499
- return undefined;
1500
- n.absolute = absolute;
1501
- if (variantIndex < absolute.length)
1502
- n.variant = refSplit( name.substring( variantIndex ), null );
1492
+ // not used for the value
1493
+ const id = (xsn ? name.substring(1) : name);
1494
+ if (!id) { // `"@": …` is already syntax-unknown-property
1495
+ message( 'syntax-invalid-name', location(true), { '#': '{}' } );
1496
+ }
1497
+ const n = { id, location: location() };
1503
1498
  const r = annoValue( val, spec );
1504
1499
  r.name = n;
1505
1500
  return r;
@@ -1785,7 +1780,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1785
1780
  return { prop, type: ignore };
1786
1781
  return { prop, type: extra };
1787
1782
  }
1788
- // TODO v4: No warning with --sloppy
1783
+ // TODO v5: No warning with --sloppy
1789
1784
  warning( 'syntax-unknown-property', location(true), { prop },
1790
1785
  'Unknown CSN property $(PROP)' );
1791
1786
  return { type: ignore };
@@ -1820,7 +1815,7 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1820
1815
  kind,
1821
1816
  } );
1822
1817
  }
1823
- else if (checkAndSetXorGroup( s.xorGroups, s.xorException, prop, xor )) {
1818
+ else if (checkAndSetXorGroup( s.xorGroups, s.xorException, prop, xor, csn )) {
1824
1819
  // TODO: If all targets of onlyWith are xor-excluded/ignore, also exclude/ignore this one.
1825
1820
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
1826
1821
  return s;
@@ -1849,6 +1844,18 @@ function calculateKind( def, spec ) {
1849
1844
  : kind;
1850
1845
  }
1851
1846
 
1847
+ function requiresOnWithBothTargetProps( csn ) {
1848
+ if (!csn.on && csn.target && csn.targetAspect &&
1849
+ inferredTargetEntityForAspect( 'target', 'targetAspect', csn )) {
1850
+ error( 'syntax-missing-property', location(true), {
1851
+ '#': 'bothTargets',
1852
+ siblingprop: 'targetAspect',
1853
+ otherprop: 'target',
1854
+ prop: 'on',
1855
+ } );
1856
+ }
1857
+ }
1858
+
1852
1859
  function onlyWith( spec, need, csn, prop, xor, expected ) {
1853
1860
  if (!need)
1854
1861
  return spec;
@@ -1861,6 +1868,10 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1861
1868
  if (need in csn) // TODO: enumerable ?
1862
1869
  return spec;
1863
1870
  }
1871
+ else if (typeof need === 'function') {
1872
+ need( csn, prop );
1873
+ return spec;
1874
+ }
1864
1875
  else if (need.some( n => n in csn )) {
1865
1876
  return spec;
1866
1877
  }
@@ -1895,7 +1906,7 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1895
1906
  * @param {object} xor
1896
1907
  * @return {boolean}
1897
1908
  */
1898
- function checkAndSetXorGroup( groups, exception, prop, xor ) {
1909
+ function checkAndSetXorGroup( groups, exception, prop, xor, csn ) {
1899
1910
  if (!groups || groups.length === 0)
1900
1911
  return true;
1901
1912
  let silent = false;
@@ -1907,6 +1918,13 @@ function checkAndSetXorGroup( groups, exception, prop, xor ) {
1907
1918
  }
1908
1919
  if (siblingprop === exception)
1909
1920
  return true;
1921
+ if (typeof exception === 'function') {
1922
+ const r = exception( prop, siblingprop, csn, silent );
1923
+ if (r === false) // error reported
1924
+ silent = true;
1925
+ if (r != null)
1926
+ return r;
1927
+ }
1910
1928
  if (!silent) {
1911
1929
  error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1912
1930
  silent = true;
@@ -1915,6 +1933,23 @@ function checkAndSetXorGroup( groups, exception, prop, xor ) {
1915
1933
  });
1916
1934
  }
1917
1935
 
1936
+ function inferredTargetEntityForAspect( prop, siblingprop, csn, silent = true ) {
1937
+ if (siblingprop !== 'target' && siblingprop !== 'targetAspect')
1938
+ return null;
1939
+ const { target } = csn;
1940
+ if (typeof target !== 'object' || !target?.elements)
1941
+ return true;
1942
+ if (!silent) {
1943
+ error( 'syntax-unexpected-property', location(true), {
1944
+ '#': prop,
1945
+ prop,
1946
+ siblingprop,
1947
+ subprop: 'elements',
1948
+ } );
1949
+ }
1950
+ return false;
1951
+ }
1952
+
1918
1953
  function implicitName( ref ) {
1919
1954
  // careful, the input CSN might be wrong!
1920
1955
  const item = ref && ref[ref.length - 1];