@sap/cds-compiler 5.7.4 → 5.8.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 (71) hide show
  1. package/CHANGELOG.md +60 -2
  2. package/bin/cdsse.js +13 -1
  3. package/doc/CHANGELOG_BETA.md +7 -0
  4. package/lib/api/options.js +2 -1
  5. package/lib/api/validate.js +9 -0
  6. package/lib/base/message-registry.js +55 -20
  7. package/lib/base/messages.js +5 -2
  8. package/lib/base/model.js +4 -1
  9. package/lib/checks/assocOutsideService.js +40 -0
  10. package/lib/checks/featureFlags.js +4 -1
  11. package/lib/checks/types.js +7 -4
  12. package/lib/checks/validator.js +3 -0
  13. package/lib/compiler/assert-consistency.js +11 -5
  14. package/lib/compiler/checks.js +79 -17
  15. package/lib/compiler/define.js +57 -3
  16. package/lib/compiler/extend.js +1 -2
  17. package/lib/compiler/generate.js +1 -1
  18. package/lib/compiler/populate.js +17 -6
  19. package/lib/compiler/propagator.js +1 -1
  20. package/lib/compiler/resolve.js +181 -150
  21. package/lib/compiler/shared.js +276 -22
  22. package/lib/compiler/tweak-assocs.js +15 -4
  23. package/lib/compiler/xpr-rewrite.js +76 -50
  24. package/lib/edm/annotations/edmJson.js +1 -1
  25. package/lib/edm/annotations/genericTranslation.js +2 -2
  26. package/lib/edm/csn2edm.js +2 -2
  27. package/lib/edm/edmPreprocessor.js +15 -9
  28. package/lib/edm/edmUtils.js +12 -5
  29. package/lib/gen/CdlGrammar.checksum +1 -0
  30. package/lib/gen/CdlParser.js +2234 -2233
  31. package/lib/gen/Dictionary.json +55 -8
  32. package/lib/json/from-csn.js +37 -17
  33. package/lib/json/to-csn.js +4 -0
  34. package/lib/language/genericAntlrParser.js +7 -0
  35. package/lib/main.d.ts +5 -0
  36. package/lib/model/cloneCsn.js +1 -0
  37. package/lib/model/csnRefs.js +1 -0
  38. package/lib/model/csnUtils.js +0 -5
  39. package/lib/modelCompare/utils/filter.js +2 -2
  40. package/lib/optionProcessor.js +2 -0
  41. package/lib/parsers/AstBuildingParser.js +47 -17
  42. package/lib/parsers/CdlGrammar.g4 +10 -12
  43. package/lib/parsers/XprTree.js +206 -0
  44. package/lib/render/toCdl.js +61 -89
  45. package/lib/render/toSql.js +59 -29
  46. package/lib/render/utils/standardDatabaseFunctions.js +252 -15
  47. package/lib/transform/addTenantFields.js +9 -3
  48. package/lib/transform/db/assocsToQueries/transformExists.js +3 -0
  49. package/lib/transform/db/assocsToQueries/utils.js +10 -3
  50. package/lib/transform/db/expansion.js +3 -1
  51. package/lib/transform/db/flattening.js +7 -3
  52. package/lib/transform/db/killAnnotations.js +1 -0
  53. package/lib/transform/db/processSqlServices.js +70 -17
  54. package/lib/transform/draft/db.js +8 -3
  55. package/lib/transform/draft/odata.js +27 -4
  56. package/lib/transform/effective/main.js +37 -10
  57. package/lib/transform/effective/misc.js +4 -9
  58. package/lib/transform/effective/service.js +34 -0
  59. package/lib/transform/effective/types.js +28 -17
  60. package/lib/transform/forOdata.js +36 -10
  61. package/lib/transform/forRelationalDB.js +30 -18
  62. package/lib/transform/odata/adaptAnnotationRefs.js +37 -21
  63. package/lib/transform/odata/createForeignKeys.js +121 -117
  64. package/lib/transform/odata/flattening.js +12 -9
  65. package/lib/transform/transformUtils.js +58 -25
  66. package/lib/transform/translateAssocsToJoins.js +10 -6
  67. package/lib/transform/universalCsn/coreComputed.js +5 -1
  68. package/package.json +1 -1
  69. package/share/messages/message-explanations.json +1 -0
  70. package/share/messages/rewrite-not-supported.md +5 -0
  71. package/share/messages/rewrite-undefined-key.md +94 -0
@@ -132,6 +132,13 @@
132
132
  "Collection"
133
133
  ]
134
134
  },
135
+ "Analytics.LevelInformation": {
136
+ "Type": "Hierarchy.HierarchyType",
137
+ "AppliesTo": [
138
+ "EntityType"
139
+ ],
140
+ "$experimental": true
141
+ },
135
142
  "Authorization.SecuritySchemes": {
136
143
  "Type": "Collection(Authorization.SecurityScheme)",
137
144
  "AppliesTo": [
@@ -1121,6 +1128,13 @@
1121
1128
  ],
1122
1129
  "$experimental": true
1123
1130
  },
1131
+ "Common.AddressViaNavigationPath": {
1132
+ "Type": "Core.Tag",
1133
+ "AppliesTo": [
1134
+ "EntityContainer"
1135
+ ],
1136
+ "$experimental": true
1137
+ },
1124
1138
  "Communication.Contact": {
1125
1139
  "Type": "Communication.ContactType",
1126
1140
  "AppliesTo": [
@@ -1519,7 +1533,7 @@
1519
1533
  "$experimental": true
1520
1534
  },
1521
1535
  "EntityRelationship.referencesWithConstantIds": {
1522
- "Type": "Collection(EntityRelationship.referencesWithConstantId)",
1536
+ "Type": "Collection(EntityRelationship.referenceWithConstantId)",
1523
1537
  "AppliesTo": [
1524
1538
  "EntityType"
1525
1539
  ],
@@ -1575,8 +1589,7 @@
1575
1589
  ]
1576
1590
  },
1577
1591
  "HTML5.LinkTarget": {
1578
- "Type": "HTML5.LinkTargetType",
1579
- "$experimental": true
1592
+ "Type": "HTML5.LinkTargetType"
1580
1593
  },
1581
1594
  "HTML5.RowSpanForDuplicateValues": {
1582
1595
  "Type": "Core.Tag",
@@ -1894,8 +1907,7 @@
1894
1907
  "Function",
1895
1908
  "ActionImport",
1896
1909
  "FunctionImport"
1897
- ],
1898
- "$experimental": true
1910
+ ]
1899
1911
  },
1900
1912
  "UI.SelectionPresentationVariant": {
1901
1913
  "Type": "UI.SelectionPresentationVariantType",
@@ -2525,6 +2537,30 @@
2525
2537
  "AccumulativeMeasure": "Core.Tag"
2526
2538
  }
2527
2539
  },
2540
+ "Analytics.MultiLevelExpandLevel": {
2541
+ "$kind": "ComplexType",
2542
+ "Properties": {
2543
+ "DimensionProperties": "Collection(Edm.String)",
2544
+ "AdditionalProperties": "Collection(Edm.String)"
2545
+ },
2546
+ "$experimental": true
2547
+ },
2548
+ "Analytics.MultiLevelExpandSiblingOrder": {
2549
+ "$kind": "ComplexType",
2550
+ "Properties": {
2551
+ "Property": "Edm.String",
2552
+ "Descending": "Edm.Boolean"
2553
+ },
2554
+ "$experimental": true
2555
+ },
2556
+ "Analytics.MultiLevelExpandEntry": {
2557
+ "$kind": "ComplexType",
2558
+ "Properties": {
2559
+ "Entry": "Collection(Edm.String)",
2560
+ "Levels": "Edm.Int64"
2561
+ },
2562
+ "$experimental": true
2563
+ },
2528
2564
  "Authorization.SecurityScheme": {
2529
2565
  "$kind": "ComplexType",
2530
2566
  "Properties": {
@@ -3964,16 +4000,26 @@
3964
4000
  },
3965
4001
  "$experimental": true
3966
4002
  },
4003
+ "Hierarchy.HierarchyType": {
4004
+ "$kind": "ComplexType",
4005
+ "Properties": {
4006
+ "LimitedDescendantCount": "Edm.Int64",
4007
+ "DrillState": "Edm.String",
4008
+ "DistanceFromRoot": "Edm.Int64"
4009
+ },
4010
+ "$experimental": true
4011
+ },
3967
4012
  "Hierarchy.RecursiveHierarchyType": {
3968
4013
  "$kind": "ComplexType",
4014
+ "BaseType": "Hierarchy.HierarchyType",
3969
4015
  "Properties": {
3970
4016
  "ExternalKey": "Edm.String",
3971
4017
  "NodeType": "Edm.String",
3972
- "ChildCount": "Edm.Int64",
3973
- "DescendantCount": "Edm.Int64",
3974
4018
  "LimitedDescendantCount": "Edm.Int64",
3975
4019
  "DrillState": "Edm.String",
3976
4020
  "DistanceFromRoot": "Edm.Int64",
4021
+ "ChildCount": "Edm.Int64",
4022
+ "DescendantCount": "Edm.Int64",
3977
4023
  "LimitedRank": "Edm.Int64",
3978
4024
  "SiblingRank": "Edm.Int64",
3979
4025
  "Matched": "Edm.Boolean",
@@ -3987,7 +4033,8 @@
3987
4033
  "ChangeNextSiblingAction": "Common.QualifiedName",
3988
4034
  "ChangeSiblingForRootsSupported": "Edm.Boolean",
3989
4035
  "CopyAction": "Common.QualifiedName"
3990
- }
4036
+ },
4037
+ "$experimental": true
3991
4038
  },
3992
4039
  "Hierarchy.TopLevelsExpandType": {
3993
4040
  "$kind": "ComplexType",
@@ -124,6 +124,7 @@ const { isAnnotationExpression } = require('../base/builtins');
124
124
  const { CompilerAssertion } = require('../base/error');
125
125
  const { Location } = require('../base/location');
126
126
  const { XsnSource } = require('../compiler/xsn-model');
127
+ const { xprAsTree } = require('../parsers/XprTree');
127
128
 
128
129
  const $location = Symbol.for('cds.$location');
129
130
 
@@ -529,7 +530,7 @@ const schema = compileSchema( {
529
530
  'from', 'all', 'distinct', 'columns', 'excluding', // no 'mixin'
530
531
  'where', 'groupBy', 'having', 'orderBy', 'limit',
531
532
  ],
532
- inKind: [ 'entity', 'event' ],
533
+ inKind: [ 'entity', 'event', 'type' ],
533
534
  },
534
535
  SELECT: {
535
536
  type: queryTerm,
@@ -664,7 +665,7 @@ const schema = compileSchema( {
664
665
  ],
665
666
  schema: {
666
667
  '=': {
667
- type: renameTo( '$tokenTexts', string ),
668
+ type: renameTo( '$tokenTexts', stringOrBool ),
668
669
  xorGroups: null, // reset xorGroup; allow '=' for all :expr
669
670
  },
670
671
  },
@@ -698,7 +699,8 @@ const schema = compileSchema( {
698
699
  // 2. Inside "xpr" => inside expressions
699
700
  // Because of (1) we have to set this property to false.
700
701
  inValue: false,
701
- optional: typeProperties, // TODO: only in CDL-style cast, otherwise just length,…
702
+ optional: typeProperties,
703
+ // TODO v6: only in CDL-style cast, otherwise just length,…
702
704
  inKind: [ '$column' ],
703
705
  },
704
706
  default: {
@@ -762,6 +764,9 @@ const schema = compileSchema( {
762
764
  options: { // deprecated top-level property
763
765
  type: ignore,
764
766
  },
767
+ csnInteropEffective: {
768
+ type: ignore, // by https://github.com/SAP/csn-interop-specification
769
+ },
765
770
  indexNo: { // CSN v0.1.0, but ignored without message
766
771
  ignore: true, type: ignore,
767
772
  },
@@ -787,6 +792,7 @@ const topLevelSpec = {
787
792
  optional: [
788
793
  'requires', 'definitions', 'vocabularies', 'extensions', 'i18n',
789
794
  'namespace', 'version', 'messages', 'meta', 'options', '@', '$location',
795
+ 'csnInteropEffective',
790
796
  ],
791
797
  requires: false, // empty object OK
792
798
  schema,
@@ -1316,6 +1322,14 @@ function string( val, spec ) {
1316
1322
  return ignore( val );
1317
1323
  }
1318
1324
 
1325
+ function stringOrBool( val, spec ) {
1326
+ if (typeof val === 'string' && val || typeof val === 'boolean')
1327
+ return val;
1328
+ error( 'syntax-expecting-string', location(true),
1329
+ { '#': spec.msgVariant || 'or-bool', prop: spec.msgProp } );
1330
+ return ignore( val );
1331
+ }
1332
+
1319
1333
  function stringVal( val, spec ) {
1320
1334
  if (typeof val === 'string' && val)
1321
1335
  // XSN TODO: do not require literal
@@ -1440,12 +1454,12 @@ function annoValue( val, spec ) {
1440
1454
  }
1441
1455
  return retval;
1442
1456
  }
1443
- else if (typeof val['='] === 'string') {
1457
+ else if (typeof val['='] === 'string' || val['='] === true) {
1444
1458
  // An object with `=` is an expression if and only if:
1445
1459
  // - there is exactly one property ('=')
1446
1460
  // - there is at least one other expression property (e.g. "xpr")
1447
1461
  const valKeys = Object.keys( val );
1448
- if (valKeys.length === 1) {
1462
+ if (valKeys.length === 1 && typeof val['='] === 'string') {
1449
1463
  ++virtualLine;
1450
1464
  const r = refSplit( val['='], '=' ); // i.e. no extra `variant` stuff
1451
1465
  ++virtualLine;
@@ -1566,12 +1580,16 @@ function xpr( exprs, spec, xsn, csn ) {
1566
1580
  { prop: 'xpr', siblingprop: 'func', '#': 'suffix' });
1567
1581
  }
1568
1582
  xsn.suffix = exprArgs( exprs, spec );
1583
+ if (exprs.length > 2)
1584
+ xsn.suffix = xprAsTree( exprs, xsn.suffix, xsn.op.location ).args;
1569
1585
  }
1570
1586
  else {
1571
1587
  // setting $parens here would not always be correct; thus, keep distinction
1572
1588
  // between 'xpr' and 'ixpr' (”internal” `xpr` = without implicit parens)
1573
1589
  xsn.op = { val: 'xpr', location: location() };
1574
1590
  xsn.args = exprArgs( exprs, spec );
1591
+ if (exprs.length > 2)
1592
+ xsn.args = xprAsTree( exprs, xsn.args, xsn.op.location ).args;
1575
1593
  }
1576
1594
  }
1577
1595
 
@@ -1620,11 +1638,10 @@ function expr( e, spec ) {
1620
1638
  if (Array.isArray( e )) {
1621
1639
  if (e.length > 1) { // struct-xpr
1622
1640
  const loc = location();
1623
- return {
1624
- op: { val: 'ixpr', location: loc },
1625
- args: exprArgs( e, spec ),
1626
- location: loc,
1627
- };
1641
+ const xsn = exprArgs( e, spec );
1642
+ return (e.length < 3) // optimization
1643
+ ? { op: { val: 'ixpr', location: loc }, args: xsn, location: loc }
1644
+ : xprAsTree( e, xsn, loc );
1628
1645
  }
1629
1646
  else if (e.length === 1) { // CSN v.0.1.0 way for parentheses
1630
1647
  const loc = location();
@@ -1657,14 +1674,14 @@ function exprOrString( val, spec ) {
1657
1674
  : expr( val, spec );
1658
1675
  }
1659
1676
 
1660
- // mark path argument of 'exits' predicate with $expected:'exists'
1677
+ // mark path argument of 'exists' predicate with $expected:'exists'
1661
1678
  function exprArgs( cond, spec ) {
1662
1679
  const rxsn = arrayOf( exprOrString )( cond, spec );
1663
1680
  // TODO: do that in definer.js, neither here nor in CDL parser
1664
1681
  if (Array.isArray( rxsn )) {
1665
1682
  for (let i = 0; i < rxsn.length - 1; i++) {
1666
1683
  // TODO: disallow param ref - write test
1667
- if (rxsn[i]?.val === 'exists' && rxsn[i].literal === 'token' && rxsn[i + 1].path)
1684
+ if (cond[i] === 'exists' && rxsn[i + 1].path)
1668
1685
  rxsn[++i].$expected = 'exists';
1669
1686
  }
1670
1687
  }
@@ -1673,11 +1690,14 @@ function exprArgs( cond, spec ) {
1673
1690
 
1674
1691
  function condition( cond, spec ) {
1675
1692
  const loc = location();
1676
- return {
1677
- op: { val: 'xpr', location: loc },
1678
- args: exprArgs( cond, spec ),
1679
- location: loc,
1680
- };
1693
+ const xsn = exprArgs( cond, spec );
1694
+ // TODO sql-like backends: with the commented `return`, test3 generated sql
1695
+ // files will not have the unnecessary `(…)` around `on` anymore → extra PR
1696
+ const tree = (cond.length < 3) ? xsn : xprAsTree( cond, xsn, loc ).args;
1697
+ return { op: { val: 'xpr', location: loc }, args: tree, location: loc };
1698
+ // return (cond.length < 3) // optimization
1699
+ // ? { op: { val: 'ixpr', location: loc }, args: xsn, location: loc }
1700
+ // : xprAsTree( cond, xsn, loc );
1681
1701
  }
1682
1702
 
1683
1703
  // Queries (std signature) ---------------------------------------------------
@@ -1147,6 +1147,8 @@ function value( node ) {
1147
1147
  }
1148
1148
 
1149
1149
  function enumValue( node ) {
1150
+ if (node.val !== undefined) // with `val` via CSN input (e.g. recompilation)
1151
+ return extra( { '#': node.sym.id, val: node.val }, node );
1150
1152
  const r = extra( { '#': node.sym.id }, node );
1151
1153
  const sym = node.sym._artifact;
1152
1154
  // add calculated `val`, but not for chained symbols:
@@ -1276,6 +1278,8 @@ function exprInternal( node, xprParens ) {
1276
1278
  function flattenInternalXpr( array, op ) {
1277
1279
  if (!structXpr)
1278
1280
  return array.flat( Infinity );
1281
+ // TODO: do not rely on 'nary' - this dosn't work with CSN input have an
1282
+ // `xpr: [{val:1},'+',{val:2},'+',{val:3}]`.
1279
1283
  if (array.length < 5 || op !== 'nary')
1280
1284
  return array;
1281
1285
  // nary: [ ‹a›, '+', ‹b›, '+', ‹c› ] → [ [ ‹a›, '+', ‹b› ], '+', ‹c› ]
@@ -853,6 +853,10 @@ function valuePathAst( ref ) {
853
853
  const implicit = this.previousTokenAtLocation( location );
854
854
  if (implicit && implicit.isIdentifier)
855
855
  implicit.isIdentifier = 'func';
856
+
857
+ const filter = path[0].cardinality || path[0].where;
858
+ if (filter)
859
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
856
860
  const op = { location, val: 'call' };
857
861
  return (args)
858
862
  ? {
@@ -881,6 +885,9 @@ function valuePathAst( ref ) {
881
885
  }, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
882
886
  break;
883
887
  }
888
+ const filter = path[i].cardinality || path[i].where;
889
+ if (filter)
890
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
884
891
  }
885
892
 
886
893
  const args = [];
package/lib/main.d.ts CHANGED
@@ -933,6 +933,11 @@ declare namespace compiler {
933
933
  /**
934
934
  * Renders the given CSN into a CDL source representation.
935
935
  *
936
+ * The CDL string representation may change between minor @sap/cds-compiler
937
+ * versions, but when compiled, will return the same CSN again.
938
+ * Hence, stylistic changes such as additional whitespace is not considered
939
+ * a breaking change.
940
+ *
936
941
  * @returns Object containing the rendered model.
937
942
  */
938
943
  function cdl(csn: CSN, options?: CdlOptions): CdlResult;
@@ -38,6 +38,7 @@ const internalCsnProps = {
38
38
  $default: shallowCopy, // used for HANA CSN migrations
39
39
  $notNull: shallowCopy, // used for HANA CSN migrations
40
40
  $sqlService: shallowCopy,
41
+ $dummyService: shallowCopy,
41
42
  };
42
43
  const internalEnumerableCsnProps = {
43
44
  __proto__: null,
@@ -170,6 +170,7 @@
170
170
  // any reference in the entity is inspected: with data for the query
171
171
  // hierarchy, query number, table aliases and links from a column to its
172
172
  // respective inferred element.
173
+ // - TODO: some `name` property would also be useful (set with `initDefinition`)
173
174
 
174
175
  // Properties in cache:
175
176
  //
@@ -1179,10 +1179,6 @@ function applyAnnotationsFromExtensions( csn, config ) {
1179
1179
  }
1180
1180
  }
1181
1181
 
1182
- function isAspect( node ) {
1183
- return node && node.kind === 'aspect';
1184
- }
1185
-
1186
1182
  /**
1187
1183
  * Return true if the artifact has a valid, truthy persistence.exists/skip annotation
1188
1184
  *
@@ -1446,7 +1442,6 @@ module.exports = {
1446
1442
  normalizeTypeRef,
1447
1443
  copyAnnotations,
1448
1444
  copyAnnotationsAndDoc,
1449
- isAspect,
1450
1445
  hasValidSkipOrExists,
1451
1446
  getNamespace,
1452
1447
  getServiceNames,
@@ -91,10 +91,10 @@ function getFilterObject( options, dialect, extensionCallback, migrationCallback
91
91
  if (migration.new.type === migration.old.type && migration.new.length < migration.old.length) {
92
92
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-length-change', id => message(id, loc, { '#': messageVariant, id: name }));
93
93
  }
94
- else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale) {
94
+ else if (migration.new.type === migration.old.type && migration.new.scale < migration.old.scale || migration.new.precision - migration.old.precision < migration.new.scale - migration.old.scale) {
95
95
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-scale-change', id => message(id, loc, { '#': messageVariant, id: name }));
96
96
  }
97
- else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale) {
97
+ else if (migration.new.type === migration.old.type && migration.new.precision < migration.old.precision) {
98
98
  raiseErrorOrMarkAsLossy(name, migration, 'migration-unsupported-precision-change', id => message(id, loc, { '#': messageVariant, id: name }));
99
99
  }
100
100
  else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
@@ -580,6 +580,7 @@ optionProcessor.command('forEffective')
580
580
  .option('--resolve-projections <val>', { valid: ['true', 'false'] } )
581
581
  .option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
582
582
  .option('--keep-localized <val>', { valid: ['true', 'false'] } )
583
+ .option('--effective-service-name <name>')
583
584
  .positionalArgument('<files...>')
584
585
  .help(`
585
586
  Usage: cdsc forEffective [options] <files...>
@@ -602,6 +603,7 @@ optionProcessor.command('forEffective')
602
603
  --keep-localized <val> Keep '.localized' property in the CSN:
603
604
  true: property is kept
604
605
  false:(default) property is deleted
606
+ --effective-service-name <name> Filter the output CSN to only contain the given service
605
607
  `);
606
608
 
607
609
  optionProcessor.command('forSeal')
@@ -48,7 +48,7 @@ const PRECEDENCE_OF_IN_PREDICATE = 10;
48
48
  const PRECEDENCE_OF_EQUAL = 10;
49
49
 
50
50
  class AstBuildingParser extends BaseParser {
51
- leanConditions = { afterBrace: true, fail: true };
51
+ leanConditions = { afterBrace: true, atRightParen: true, fail: true };
52
52
 
53
53
  constructor( lexer, keywords, table, options, messageFunctions ) {
54
54
  super( lexer, keywords, table ); // lexer has file
@@ -341,7 +341,7 @@ class AstBuildingParser extends BaseParser {
341
341
  }
342
342
 
343
343
  /**
344
- * Prepare element restrictions and check validility of final anno assignments.
344
+ * Prepare element restrictions and check validity of final anno assignments.
345
345
  *
346
346
  * Called as <prepare=…>:
347
347
  *
@@ -439,27 +439,49 @@ class AstBuildingParser extends BaseParser {
439
439
  * `;` between statements is optional only after a `}` (ex braces of structure
440
440
  * values for annotations and foreign key specifications).
441
441
  *
442
+ * Unfortunate exception: always optional after `entity … as projection on`.
443
+ *
442
444
  * Beware: mentioned in leanConditions, i.e. executed in predictions!
443
445
  */
444
446
  afterBrace( test, arg ) {
445
447
  if (!test) {
446
- this.afterBrace$
447
- = (!arg || this.afterBrace$ > 0 && this.tokens[this.afterBrace$ - 1].keyword === arg)
448
- ? this.tokenIdx
449
- : -1;
448
+ if (arg === 'normal' && this.lb().type !== '}') {
449
+ const { type, keyword } = this.la();
450
+ if (type !== ';' && type !== '}' && type !== 'EOF' && keyword !== 'actions') {
451
+ const prev = this.lb().location;
452
+ const loc = new Location( prev.file, prev.endLine, prev.endCol );
453
+ this.warning( 'syntax-missing-proj-semicolon', loc,
454
+ { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
455
+ 'Missing $(EXPECTING) before $(OFFENDING)');
456
+ }
457
+ }
458
+ // with arg 'init' (used in rule `start`/`artifactsBlock`), and arg 'sloppy'
459
+ // or 'normal' (used in rule `entityDef`), set marker in dynamic context:
460
+ if (!arg)
461
+ this.afterBrace$ = this.tokenIdx;
462
+ else if (arg === 'init')
463
+ this.dynamic_.sloppySemicolon$ = [ false ];
464
+ else if (arg === 'sloppy' || this.la().keyword === 'actions')
465
+ this.dynamic_.sloppySemicolon$[0] = (arg === 'sloppy');
466
+ return null;
450
467
  }
451
468
  // TODO TOOL: the following test belongs to the BaseParser.js:
452
469
  if (this.conditionTokenIdx === this.tokenIdx && // tested on same
453
470
  this.conditionStackLength == null && // after error recover
454
471
  test !== 'M')
455
472
  return false;
456
- // Strange optional `;` after PROJECTION ON source: the rule exit prediction
457
- // for fromRefWithOptAlias etc now checks C(afterBrace):
458
- if (test === 'E' && this.afterBrace$ > 0 &&
459
- this.tokens[this.afterBrace$ - 1]?.keyword === 'projection' &&
460
- this.tokens[this.afterBrace$].keyword === 'on')
461
- return false;
462
- return this.afterBrace$ !== this.tokenIdx;
473
+ const { sloppySemicolon$ } = this.dynamic_;
474
+ if (!sloppySemicolon$?.[0])
475
+ return this.afterBrace$ !== this.tokenIdx;
476
+ if (test === true && sloppySemicolon$) // TODO: single-let mode for running parser
477
+ sloppySemicolon$[0] = false;
478
+ return this.afterBrace$ !== this.tokenIdx && test === 'M';
479
+ // TODO: should we always fail for expected set (test === 'M'), at least if
480
+ // token is not on a new line?
481
+ }
482
+
483
+ atRightParen() {
484
+ return this.l() !== ')';
463
485
  }
464
486
 
465
487
  /**
@@ -1104,7 +1126,6 @@ class AstBuildingParser extends BaseParser {
1104
1126
  }
1105
1127
 
1106
1128
  // make sure that the parens of `IN (…)` do not disappear:
1107
- // TODO: make this a to-csn thing
1108
1129
  secureParens( expr ) {
1109
1130
  const op = expr?.op?.val;
1110
1131
  const $parens = expr?.$parens;
@@ -1163,6 +1184,10 @@ class AstBuildingParser extends BaseParser {
1163
1184
  // TODO: we could have an opt(?) parameter funcToken for speed-up (passing this.lr())
1164
1185
  if (funcToken)
1165
1186
  funcToken.parsedAs = 'func';
1187
+
1188
+ const filter = path[0].cardinality || path[0].where; // XSN TODO: filter$location
1189
+ if (filter) // TODO v7: make this be reported via guard, as error
1190
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
1166
1191
  // TODO: XSN representation of functions is a bit strange - rework
1167
1192
  const op = { location, val: 'call' };
1168
1193
  return this.attachLocation( { op, func: ref, args } );
@@ -1179,13 +1204,18 @@ class AstBuildingParser extends BaseParser {
1179
1204
  // Everything after the first function is also a function, and not a reference.
1180
1205
 
1181
1206
  for (let i = firstFunc; i < path.length; ++i) {
1182
- if (path[i].args && path[i].$syntax === ':') {
1207
+ const item = path[i];
1208
+ if (item.args && item.$syntax === ':') {
1183
1209
  // Error for `a(P => 1).b.c(P: 1)`: no ref after function.
1184
- this.error( 'syntax-invalid-ref', path[i].args[$location], {
1210
+ // TODO v6: make this be reported via guard
1211
+ this.error( 'syntax-invalid-ref', item.args[$location], {
1212
+ // TODO: msg text - huh? → syntax-invalid-named-arg ?
1185
1213
  code: '=>',
1186
1214
  }, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
1187
- break;
1188
1215
  }
1216
+ const filter = item.cardinality || item.where; // XSN TODO: filter$location
1217
+ if (filter) // TODO v7: make this be reported via guard, as error
1218
+ this.message( 'syntax-unexpected-filter', filter.location, {} );
1189
1219
  }
1190
1220
 
1191
1221
  const args = [];
@@ -45,6 +45,7 @@ tokens{ // reserved words
45
45
 
46
46
  start returns[ source = new XsnSource( 'cdl' ) ]
47
47
  :
48
+ { this.afterBrace( null, 'init' ); }<always> // init sloppy semicolon handling
48
49
  (
49
50
  ( <guard=namespaceRestriction> namespaceDeclaration[ $source ]
50
51
  | usingDeclaration[ $source ]
@@ -57,7 +58,7 @@ start returns[ source = new XsnSource( 'cdl' ) ]
57
58
 
58
59
  artifactsBlock[ art, start = undefined ]
59
60
  :
60
- '{'
61
+ '{' <prepare=afterBrace, arg=init>
61
62
  { $art.artifacts = this.createDict( $start ); $art.extensions = []; }
62
63
  (
63
64
  artifactDefOrExtend[ $art ]
@@ -235,7 +236,7 @@ aspectDef[ art, outer ]
235
236
  entityDef[ art, outer ]
236
237
  @finally{ this.attachLocation( $art ); }
237
238
  :
238
- ENTITY<prepare=afterBrace> // enable special
239
+ ENTITY
239
240
  name=namePath[ 'Entity' ]
240
241
  { this.addDef( $art, $outer, 'artifacts', 'entity', $name ); }
241
242
  { this.docComment( $art ); } annoAssignMid[ $art ]*
@@ -254,17 +255,12 @@ entityDef[ art, outer ]
254
255
  query=queryExpression
255
256
  { $art.query = $query; $art.$syntax = 'entity'; }
256
257
  |
258
+ <prepare=afterBrace, arg=sloppy> // enable special loop-exit, allow no `;`
257
259
  query=projectionSpec
258
260
  { $art.query = $query; $art.$syntax = 'projection'; }
259
261
  whereGroupByHaving[ $query ]?
260
262
  orderByLimitOffset[ $query ]?
261
- { if (this.lb().type !== '}' && ![ ';', '}', 'EOF' ].includes( this.l() ) && this.la().keyword !== 'actions') {
262
- // not worth to nicer location here - is standard error in v6
263
- this.warning( 'syntax-missing-proj-semicolon', this.la(),
264
- { expecting: [ "';'" ], offending: this.antlrName( this.la() ) },
265
- 'Missing $(EXPECTING) before $(OFFENDING)');
266
- } }
267
- <prepare=afterBrace> // disable special loop-exit, allow no `;`
263
+ {;}<prepare=afterBrace, arg=normal> // disable special loop-exit, allow no `;`
268
264
  // TODO v6: these <prepare=afterBrace>s are extremely strange
269
265
  )
270
266
  )
@@ -811,6 +807,9 @@ typeOrIncludesSpec[ art ]
811
807
  |
812
808
  ':'
813
809
  (
810
+ // Since cds-compiler v5.8; new parser only
811
+ query=projectionSpec { $art.query = $query; $art.$syntax = 'projection'; }
812
+ |
814
813
  typeExpression[ $art ]
815
814
  |
816
815
  <prefer>
@@ -1073,7 +1072,6 @@ projectionSpec returns[ default query = {} ]
1073
1072
  @finally{ this.attachLocation($query); }
1074
1073
  :
1075
1074
  PROJECTION
1076
- <prepare=afterBrace, arg=entity> // enable special loop-exit, TODO v6 delete
1077
1075
  { $query = { op: this.valueWithLocation( 'SELECT' ) }; }
1078
1076
  ON
1079
1077
  tab=fromRefWithOptAlias
@@ -1716,7 +1714,8 @@ options{ minTokensMatched = 1 }
1716
1714
  ','<prepare=nextFunctionArgument>
1717
1715
  ( expr=funcExpression { $pathStep.args.push( $expr ); }
1718
1716
  | DeleteStarFromSet // Workaround for missing feature, see #13485, TODO
1719
- | <exitLoop> // TODO <cond>: only before `)`
1717
+ | <exitLoop, guard=atRightParen>
1718
+ // TODO: later allow ')' <exitRule>, or ')'<mock, exitLoop>, or <exitBlock=MainAlt> with ( options{ block=MainAlt }: …)
1720
1719
  )
1721
1720
  )*
1722
1721
  ( // ORDER BY in generic functions, e.g. `first_value(id order by name)`
@@ -2014,7 +2013,6 @@ annoValue returns[ default value = {} ]
2014
2013
  else { $value.struct = Object.create(null); $value.literal = 'struct'; }
2015
2014
  }
2016
2015
  (
2017
- // TODO: complain about empty loop if top-level as before
2018
2016
  // TOOL TODO → allow `<guard=…> '}'` below where the condition rejects `}`
2019
2017
  // after `{` if top-level
2020
2018
  sub=annoStructValue