@sap/cds-compiler 3.6.2 → 3.7.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 (68) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +9 -5
  4. package/doc/CHANGELOG_BETA.md +20 -2
  5. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  6. package/lib/api/main.js +2 -1
  7. package/lib/base/dictionaries.js +10 -0
  8. package/lib/base/message-registry.js +56 -12
  9. package/lib/base/messages.js +39 -20
  10. package/lib/base/model.js +1 -0
  11. package/lib/base/shuffle.js +2 -1
  12. package/lib/checks/elements.js +29 -1
  13. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
  14. package/lib/checks/nonexpandableStructured.js +1 -1
  15. package/lib/checks/onConditions.js +8 -5
  16. package/lib/checks/types.js +6 -1
  17. package/lib/checks/validator.js +7 -3
  18. package/lib/compiler/assert-consistency.js +20 -23
  19. package/lib/compiler/base.js +1 -2
  20. package/lib/compiler/builtins.js +2 -2
  21. package/lib/compiler/checks.js +237 -242
  22. package/lib/compiler/define.js +63 -75
  23. package/lib/compiler/extend.js +325 -22
  24. package/lib/compiler/finalize-parse-cdl.js +1 -55
  25. package/lib/compiler/kick-start.js +6 -7
  26. package/lib/compiler/populate.js +284 -288
  27. package/lib/compiler/propagator.js +15 -13
  28. package/lib/compiler/resolve.js +136 -306
  29. package/lib/compiler/shared.js +42 -44
  30. package/lib/compiler/tweak-assocs.js +29 -27
  31. package/lib/compiler/utils.js +29 -3
  32. package/lib/edm/annotations/genericTranslation.js +7 -13
  33. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  34. package/lib/edm/csn2edm.js +0 -4
  35. package/lib/edm/edm.js +6 -4
  36. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  37. package/lib/edm/edmPreprocessor.js +1 -5
  38. package/lib/gen/Dictionary.json +34 -2
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -1
  41. package/lib/gen/languageParser.js +2429 -2401
  42. package/lib/inspect/inspectPropagation.js +2 -0
  43. package/lib/json/from-csn.js +87 -41
  44. package/lib/json/to-csn.js +47 -16
  45. package/lib/language/errorStrategy.js +1 -0
  46. package/lib/language/genericAntlrParser.js +109 -28
  47. package/lib/language/language.g4 +20 -4
  48. package/lib/model/csnRefs.js +1 -1
  49. package/lib/model/csnUtils.js +1 -0
  50. package/lib/model/revealInternalProperties.js +1 -2
  51. package/lib/modelCompare/compare.js +2 -1
  52. package/lib/render/manageConstraints.js +5 -2
  53. package/lib/render/toCdl.js +20 -7
  54. package/lib/render/toHdbcds.js +2 -8
  55. package/lib/render/toSql.js +4 -3
  56. package/lib/render/utils/common.js +9 -5
  57. package/lib/transform/db/assertUnique.js +2 -1
  58. package/lib/transform/db/expansion.js +2 -0
  59. package/lib/transform/db/flattening.js +37 -36
  60. package/lib/transform/db/rewriteCalculatedElements.js +559 -0
  61. package/lib/transform/db/transformExists.js +4 -0
  62. package/lib/transform/db/views.js +40 -37
  63. package/lib/transform/forRelationalDB.js +38 -28
  64. package/lib/transform/odata/typesExposure.js +50 -15
  65. package/lib/transform/parseExpr.js +14 -8
  66. package/lib/transform/transformUtilsNew.js +6 -5
  67. package/lib/transform/translateAssocsToJoins.js +49 -33
  68. package/package.json +1 -1
@@ -192,6 +192,8 @@ function isContainedInParentLocation( art, parent ) {
192
192
  const parentLoc = parent.location;
193
193
  if (artLoc.file !== parentLoc.file)
194
194
  return false;
195
+ // Warning is correct, but we have a "TODO" below.
196
+ // eslint-disable-next-line sonarjs/prefer-single-boolean-return
195
197
  if (artLoc.line < parentLoc.line || artLoc.line > parentLoc.endLine)
196
198
  return false;
197
199
  // Good enough for now
@@ -62,8 +62,8 @@
62
62
  * @property {number} [minLength] Minimum number of elements that an array must have.
63
63
  * @property {boolean} [inValue] Puts the value into an XSN property "value",
64
64
  * e.g. { value: ... }
65
- * @property {string} [xorGroup] Corresponding xor group. It references a value of
66
- * xorGroups. If set then only one property of of the
65
+ * @property {string[]} [xorGroups] Corresponding xor groups. It references a value of
66
+ * xorGroups. If set then only one property of the
67
67
  * xorGroup may be set, e.g. if target is set, elements
68
68
  * may not.
69
69
  * @property {string} [xsnOp] Defines the operator to be used for XSN. Used for SET
@@ -90,6 +90,7 @@
90
90
  const { dictAdd } = require('../base/dictionaries');
91
91
  const { quotedLiteralPatterns } = require('../compiler/builtins');
92
92
  const { CompilerAssertion } = require('../base/error');
93
+ const { isBetaEnabled } = require('../base/model');
93
94
 
94
95
  const $location = Symbol.for('cds.$location');
95
96
 
@@ -121,7 +122,8 @@ const xorGroups = {
121
122
  ':expr': [ // see also xorException property in schema
122
123
  'ref', 'xpr', 'list', 'val', '#', 'func', 'SELECT', 'SET', 'expand',
123
124
  '=', 'path', 'value', 'op', // '='/'path' is CSN v0.1.0 here
124
- ], // also set xorGroupTwo ':col' for 'expand' and 'inline'
125
+ ],
126
+ ':col': [ 'expand', 'inline' ],
125
127
  ':ext': [ 'annotate', 'extend' ], // TODO: better msg for test/negative/UnexpectedProperties.csn
126
128
  ':assoc': [ 'on', 'keys', 'foreignKeys', 'onCond' ], // 'foreignKeys'/'onCond' is CSN v0.1.0
127
129
  // TODO - improve consequential errors: assume no name given with `join` or `inline`?
@@ -162,10 +164,15 @@ const schemaClasses = {
162
164
  xpr: {
163
165
  class: 'condition',
164
166
  type: xprInValue,
165
- xorException: 'func', // see xorGroup :expr
167
+ xorException: 'func', // see xorGroup :expr; for window functions
166
168
  inKind: [ '$column' ],
167
169
  inValue: true,
168
170
  },
171
+ '=': {
172
+ // by not setting `vZeroFor`, we disallow `=` in `columns`.
173
+ // CSN v0.1 didn't have columns, so this isn't breaking v0.1 compatibility.
174
+ type: ignore,
175
+ },
169
176
  },
170
177
  },
171
178
  };
@@ -252,12 +259,10 @@ const schema = compileSchema( {
252
259
  expand: {
253
260
  class: 'columns',
254
261
  xorException: 'ref', // see xorGroup :expr
255
- xorGroupTwo: ':col',
256
262
  inKind: [ '$column' ], // only valid in $column
257
263
  },
258
264
  inline: {
259
265
  class: 'columns',
260
- xorGroupTwo: ':col',
261
266
  onlyWith: 'ref',
262
267
  inKind: [ '$column' ], // only valid in $column
263
268
  },
@@ -592,6 +597,19 @@ const schema = compileSchema( {
592
597
  prop: '@‹anno›', // which property name do messages use for annotation assignments?
593
598
  type: annotation,
594
599
  inKind: () => true, // allowed in all definitions (including columns and extensions)
600
+ schema: {
601
+ '-expr': { // '-expr' and '-' must not exist top-level
602
+ prop: '@‹anno›',
603
+ type: object,
604
+ optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args' ],
605
+ schema: {
606
+ '=': {
607
+ type: ignore,
608
+ xorGroups: null, // reset xorGroup; allow '=' for all :expr
609
+ },
610
+ },
611
+ },
612
+ },
595
613
  },
596
614
  abstract: { // v1: with 'abstract', an entity becomes an aspect
597
615
  type: abstract,
@@ -620,7 +638,7 @@ const schema = compileSchema( {
620
638
  // 2. Inside "xpr" => inside expressions
621
639
  // Because of (1) we have to set this property to false.
622
640
  inValue: false,
623
- optional: typeProperties,
641
+ optional: typeProperties, // TODO: only in CDL-style cast, otherwise just length,…
624
642
  inKind: [ '$column' ],
625
643
  },
626
644
  default: {
@@ -731,6 +749,8 @@ let virtualLine = 1;
731
749
  /** @type {CSN.Location[]} */
732
750
  let dollarLocations = [];
733
751
  let arrayLevelCount = 0;
752
+ /** @type {CSN.Options} */
753
+ let userOptions = null; // must be reset!
734
754
 
735
755
  /**
736
756
  * @param {Object.<string, SchemaSpec>} specs
@@ -763,12 +783,15 @@ function compileSchema( specs, proto = null ) {
763
783
  else
764
784
  throw new CompilerAssertion( `Missing type specification for property "${ p }"` );
765
785
  }
766
- }
767
- // Set property 'xorGroup' in main and sub schema:
768
- for (const group in xorGroups) {
769
- for (const prop of xorGroups[group]) {
770
- if (r[prop].xorGroup === undefined)
771
- r[prop].xorGroup = group;
786
+
787
+ if (s.xorGroups === undefined) {
788
+ // Only set xorGroup once. Could already be set through shared sub-schema
789
+ // of schemaClasses or be explicitly set.
790
+ s.xorGroups = [];
791
+ for (const group in xorGroups) {
792
+ if (xorGroups[group].includes(p))
793
+ s.xorGroups.push(group);
794
+ }
772
795
  }
773
796
  }
774
797
  if (proto)
@@ -1283,21 +1306,28 @@ function annoValue( val, spec ) {
1283
1306
  }
1284
1307
  return retval;
1285
1308
  }
1286
- if (typeof val['#'] === 'string') {
1287
- if (Object.keys( val ).length === 1) {
1309
+ else if (typeof val['='] === 'string') {
1310
+ const valKeys = Object.keys(val);
1311
+ if (valKeys.length > 1 && isBetaEnabled(userOptions, 'annotationExpressions')) {
1312
+ const s = schema['@'].schema['-expr'];
1313
+ const r = { location: location() };
1314
+ r.value = object(val, s);
1315
+ return r;
1316
+ }
1317
+ else if (valKeys.length === 1) {
1288
1318
  ++virtualLine;
1289
- const xsn = { location: location() };
1290
- symbol( val['#'], schema['#'], xsn );
1319
+ const r = refSplit( val['='], '=' );
1291
1320
  ++virtualLine;
1292
- return xsn;
1321
+ return r;
1293
1322
  }
1294
1323
  }
1295
- else if (typeof val['='] === 'string') {
1324
+ if (typeof val['#'] === 'string') {
1296
1325
  if (Object.keys( val ).length === 1) {
1297
1326
  ++virtualLine;
1298
- const r = refSplit( val['='], '=' );
1327
+ const xsn = { location: location() };
1328
+ symbol( val['#'], schema['#'], xsn );
1299
1329
  ++virtualLine;
1300
- return r;
1330
+ return xsn;
1301
1331
  }
1302
1332
  }
1303
1333
  else if (val['...'] && Object.keys( val ).length === 1) {
@@ -1314,13 +1344,13 @@ function annoValue( val, spec ) {
1314
1344
  ++virtualLine;
1315
1345
  return r;
1316
1346
  }
1317
- const struct = Object.create(null);
1347
+ const r = { struct: Object.create(null), literal: 'struct', location: location() };
1318
1348
  ++virtualLine;
1319
1349
  for (const name of Object.keys( val )) {
1320
- struct[name] = annotation( val[name], schema['@'], null, val, name );
1350
+ r.struct[name] = annotation( val[name], schema['@'], null, val, name );
1321
1351
  ++virtualLine;
1322
1352
  }
1323
- return { struct, literal: 'struct', location: location() };
1353
+ return r;
1324
1354
  }
1325
1355
 
1326
1356
  function annotation( val, spec, xsn, csn, name ) {
@@ -1333,6 +1363,7 @@ function annotation( val, spec, xsn, csn, name ) {
1333
1363
  n.absolute = absolute;
1334
1364
  if (variantIndex < absolute.length)
1335
1365
  n.variant = { id: name.substring( variantIndex ), location: location() };
1366
+
1336
1367
  const r = annoValue( val, spec );
1337
1368
  r.name = n;
1338
1369
  return r;
@@ -1628,10 +1659,10 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1628
1659
  }
1629
1660
  const zero = s.vZeroFor;
1630
1661
  if (zero) { // (potential) CSN v0.1.0 property
1631
- const group = s.xorGroup;
1632
- if (expected( zero, schema[zero] ) && !(group && xor[group])) {
1662
+ const groups = s.xorGroups;
1663
+ if (expected( zero, schema[zero] ) && !(groups.length && groups.every(group => xor[group]))) {
1633
1664
  replaceZeroProp( prop, zero );
1634
- if (group)
1665
+ for (const group of groups)
1635
1666
  xor[group] = prop;
1636
1667
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
1637
1668
  return s;
@@ -1650,8 +1681,8 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
1650
1681
  kind,
1651
1682
  } );
1652
1683
  }
1653
- else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor ) &&
1654
- checkAndSetXorGroup( s.xorGroupTwo, s.xorException, prop, xor ) ) {
1684
+ else if (checkAndSetXorGroup( s.xorGroups, s.xorException, prop, xor )) {
1685
+ // TODO: If all targets of onlyWith are xor-excluded/ignore, also exclude/ignore this one.
1655
1686
  onlyWith( s, s.onlyWith, csn, prop, xor, expected );
1656
1687
  return s;
1657
1688
  }
@@ -1693,7 +1724,7 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1693
1724
  const allowed = need.filter( p => expected( p, spec ));
1694
1725
  // There should be at least one elem, otherwise the spec is wrong;
1695
1726
  // first try to find element which is not excluded
1696
- need = allowed.find( p => !xor[schema[p].xorGroup] ) || allowed[0];
1727
+ need = allowed.find( p => !schema[p].xorGroups?.some(g => xor[g]) ) || allowed[0];
1697
1728
  }
1698
1729
  if (prop) {
1699
1730
  error( 'syntax-missing-property', location(true), // location at $(PROP)
@@ -1713,18 +1744,31 @@ function onlyWith( spec, need, csn, prop, xor, expected ) {
1713
1744
  return spec;
1714
1745
  }
1715
1746
 
1716
- function checkAndSetXorGroup( group, exception, prop, xor ) {
1717
- if (!group)
1718
- return true;
1719
- const siblingprop = xor[group];
1720
- if (!siblingprop) {
1721
- xor[group] = prop;
1722
- return true;
1723
- }
1724
- if (siblingprop === exception)
1747
+ /**
1748
+ * @param {string[]} groups
1749
+ * @param {string} exception
1750
+ * @param {string} prop
1751
+ * @param {object} xor
1752
+ * @return {boolean}
1753
+ */
1754
+ function checkAndSetXorGroup( groups, exception, prop, xor ) {
1755
+ if (!groups || groups.length === 0)
1725
1756
  return true;
1726
- error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1727
- return false;
1757
+ let silent = false;
1758
+ return groups.every((group) => {
1759
+ const siblingprop = xor[group];
1760
+ if (!siblingprop) {
1761
+ xor[group] = prop;
1762
+ return true;
1763
+ }
1764
+ if (siblingprop === exception)
1765
+ return true;
1766
+ if (!silent) {
1767
+ error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } );
1768
+ silent = true;
1769
+ }
1770
+ return false;
1771
+ });
1728
1772
  }
1729
1773
 
1730
1774
  function implicitName( ref ) {
@@ -1824,6 +1868,7 @@ function popLocation( obj ) {
1824
1868
  function resetHeapModuleVars() {
1825
1869
  vocabInDefinitions = null;
1826
1870
  dollarLocations = [];
1871
+ userOptions = null;
1827
1872
  message = () => undefined;
1828
1873
  error = () => undefined;
1829
1874
  warning = () => undefined;
@@ -1848,6 +1893,7 @@ function toXsn( csn, filename, options, messageFunctions ) {
1848
1893
  arrayLevelCount = 0;
1849
1894
  inExtensions = null;
1850
1895
  vocabInDefinitions = null;
1896
+ userOptions = options;
1851
1897
 
1852
1898
  const xsn = { $frontend: 'json' };
1853
1899
 
@@ -55,7 +55,7 @@ const transformers = {
55
55
  kind,
56
56
  id: n => n, // in path item
57
57
  doc: value,
58
- '@': value,
58
+ '@': anno,
59
59
  virtual: value,
60
60
  key: value,
61
61
  unique: value,
@@ -73,6 +73,17 @@ const transformers = {
73
73
  // type properties (without 'elements') ------------------------------------
74
74
  localized: value,
75
75
  type,
76
+ $typeArgs: (node, csn, xsn) => {
77
+ const typeArgs = xsn.$typeArgs;
78
+ // One or two arguments are interpreted as either length or precision/scale.
79
+ if (typeArgs?.length === 1) {
80
+ csn.length = value(typeArgs[0]);
81
+ }
82
+ else if (typeArgs?.length === 2) {
83
+ csn.precision = value(typeArgs[0]);
84
+ csn.scale = value(typeArgs[1]);
85
+ }
86
+ },
76
87
  length: value,
77
88
  precision: value,
78
89
  scale: value,
@@ -190,6 +201,7 @@ const typeProperties = [
190
201
  'cardinality', // for association publishing in views
191
202
  'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
192
203
  'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED
204
+ '$typeArgs', // for unresolved type arguments, e.g. through parseCql
193
205
  ];
194
206
 
195
207
  const csnDictionaries = [
@@ -243,6 +255,12 @@ function sortCsn( csn, cloneOptions = false ) {
243
255
  setHidden( r, 'elements', csnDictionary( csn.elements, false, cloneOptions ) );
244
256
  if (hasNonEnumerable( csn, '$tableConstraints' ) && !r.$tableConstraints)
245
257
  setHidden( r, '$tableConstraints', csn.$tableConstraints );
258
+ if (cloneOptions.hiddenPropertiesToClone) {
259
+ cloneOptions.hiddenPropertiesToClone.forEach((property) => {
260
+ if ({}.hasOwnProperty.call( csn, property )) // used in generic reference flattener
261
+ setHidden( r, property, csn[property] );
262
+ });
263
+ }
246
264
  }
247
265
  return r;
248
266
  }
@@ -812,12 +830,12 @@ function foreignKeys( dict, csn, node ) {
812
830
  csn.keys.push( definition( dict[n] ) );
813
831
  }
814
832
 
815
- function returns( art, csn, _node, prop ) {
833
+ function returns( art, csn, node, prop ) {
816
834
  // TODO: currently, the `returns` structure might just have been created by the propagator
817
835
  // if that is the case, there should be no reason to store it in universal CSN
818
- if (universalCsn && art.$inferred === 'proxy')
836
+ if (universalCsn && (art.$inferred === 'proxy' || node.$expand === 'origin'))
819
837
  return undefined;
820
- return definition( art, csn, _node, prop );
838
+ return definition( art, csn, node, prop );
821
839
  }
822
840
 
823
841
  function definition( art, _csn, _node, prop ) {
@@ -872,10 +890,10 @@ function includesOrigin( includes, art ) {
872
890
  for (const prop in aspect) {
873
891
  if ((prop.charAt(0) === '@' || prop === 'doc') &&
874
892
  (!art[prop] || art[prop].$inferred)) {
875
- const anno = aspect[prop];
876
- if (anno.val !== null)
877
- // matererialize non-null annos (whether direct or inherited)
878
- result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
893
+ const annoVal = aspect[prop];
894
+ if (annoVal.val !== null)
895
+ // materialize non-null annos (whether direct or inherited)
896
+ result[prop] = value( Object.create( annoVal, { $inferred: { value: null } } ) );
879
897
  }
880
898
  }
881
899
  }
@@ -926,14 +944,20 @@ function addOrigin( csn, xsn, node ) {
926
944
  return;
927
945
  }
928
946
  // from here on: member:
947
+ // TODO: write a xsnNode._csnOrigin, which is useful to decide whether to write
948
+ // $origins for its members
929
949
  const parent = getParent( xsn );
930
950
  const parentOrigin = getOrigin( parent );
951
+ // console.log( 'X:',xsn, origin, parent, parentOrigin, getParent( origin ) );
931
952
  if (!origin) {
932
- if (parentOrigin && !(parent.enum && !parent.$origin && parent.type))
953
+ if (parent && parentOrigin &&
954
+ (parent.kind !== 'select' || parent === parent._main._leadingQuery) &&
955
+ !(parent.enum && !parent.$origin && parent.type))
933
956
  csn.$origin = null;
934
957
  return;
935
958
  }
936
- if (parentOrigin === getParent( origin )) {
959
+ if (parent?.kind !== 'select' && parentOrigin?.kind !== 'select' &&
960
+ parentOrigin === getParent( origin )) {
937
961
  // implicit prototype or shortened reference
938
962
  const { id } = origin.name || {};
939
963
  if (id && xsn.name && id !== xsn.name.id)
@@ -1034,13 +1058,13 @@ function getOrigin( art ) {
1034
1058
  if (art.$noOrigin)
1035
1059
  return undefined;
1036
1060
  const { _origin } = art;
1037
- if (_origin)
1038
- return (_origin.kind === 'builtin') ? undefined : _origin; // not $dollarVariable
1061
+ if (_origin) // also for query entities
1062
+ return (_origin.kind === 'builtin') ? undefined : _origin;
1063
+
1039
1064
  if (hasExplicitProp( art.type, 'cast' ))
1040
1065
  return art.type._artifact;
1041
- if (art._from)
1042
- return art._from[0]._origin;
1043
- // must come after _from, since queries can have includes as well.
1066
+ // must come after checking _origin, since entities can have both queries and
1067
+ // includes as well -> the query wins
1044
1068
  if (art.includes)
1045
1069
  return art.includes[0]._artifact;
1046
1070
  return undefined;
@@ -1230,6 +1254,13 @@ function args( node ) {
1230
1254
  return dict;
1231
1255
  }
1232
1256
 
1257
+ function anno( node ) {
1258
+ if (node.value) // expressions in annotation values
1259
+ // TODO: actual string representation and not placeholder "42"
1260
+ return Object.assign({ '=': '42' }, expression( node.value ));
1261
+ return value(node);
1262
+ }
1263
+
1233
1264
  function value( node ) {
1234
1265
  // "Short" value form, e.g. for annotation assignments
1235
1266
  if (!node)
@@ -1300,7 +1331,7 @@ function expression( node ) {
1300
1331
  function exprInternal( node, xprParens ) {
1301
1332
  if (typeof node === 'string')
1302
1333
  return node;
1303
- if (!node) // make to-csn robst
1334
+ if (!node) // make to-csn robust
1304
1335
  return {};
1305
1336
  if (node.scope === 'param') {
1306
1337
  if (node.path)
@@ -101,6 +101,7 @@ Object.assign( KeywordErrorStrategy.prototype, {
101
101
 
102
102
  // Attempt to recover from problems in subrules, except if rule has defined a
103
103
  // local variable `_sync` with value 'nop'
104
+ // TODO: consider performance - see #8800
104
105
  function sync( recognizer ) {
105
106
  // If already recovering, don't try to sync
106
107
  if (this.inErrorRecoveryMode(recognizer))
@@ -19,6 +19,7 @@ const {
19
19
  } = require('../compiler/builtins');
20
20
  const { pathName } = require('../compiler/utils');
21
21
  const { isBetaEnabled } = require('../base/model');
22
+ const { weakLocation } = require('../base/messages');
22
23
 
23
24
  const $location = Symbol.for('cds.$location');
24
25
 
@@ -73,6 +74,7 @@ Object.assign(GenericAntlrParser.prototype, {
73
74
  info(...args) {
74
75
  return _message( this, 'info', ...args );
75
76
  },
77
+ isBetaEnabled,
76
78
  attachLocation,
77
79
  assignAnnotation,
78
80
  addAnnotation,
@@ -96,6 +98,7 @@ Object.assign(GenericAntlrParser.prototype, {
96
98
  pushXprToken,
97
99
  argsExpression,
98
100
  valuePathAst,
101
+ fixNewKeywordPlacement,
99
102
  signedExpression,
100
103
  numberLiteral,
101
104
  quotedLiteral,
@@ -120,6 +123,7 @@ Object.assign(GenericAntlrParser.prototype, {
120
123
  associationInSelectItem,
121
124
  reportExpandInline,
122
125
  checkTypeFacet,
126
+ checkTypeArgs,
123
127
  csnParseOnly,
124
128
  noAssignmentInSameLine,
125
129
  noSemicolonHere,
@@ -361,15 +365,12 @@ function assignAnnotation( art, anno, prefix = '' ) {
361
365
  }
362
366
 
363
367
  function addAnnotation( art, prop, anno ) {
364
- // TODO: just overwrite after having reported the error
365
- dictAddArray( art, prop, anno, (n, location, a) => {
366
- // if we would make it a warning, we would still need to keep it an error
367
- // with '...'; otherwise parse.cdl would have to split annotate statements
368
- this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
369
- 'Duplicate assignment with $(ANNO)' );
370
- a.$errorReported = 'syntax-duplicate-anno';
371
- // do not report again later as anno-duplicate-xyz
372
- } );
368
+ const old = art[prop];
369
+ if (old) {
370
+ this.error( 'syntax-duplicate-anno', old.name.location, { anno: prop },
371
+ 'Assignment for $(ANNO) is overwritten by another one below' );
372
+ }
373
+ art[prop] = anno;
373
374
  }
374
375
 
375
376
  const extensionDicts = {
@@ -652,7 +653,7 @@ function fragileAlias( ast, safe = false ) {
652
653
  return ast;
653
654
  }
654
655
 
655
- // Return AST for identifier token `token`. Also check that identifer is not empty.
656
+ // Return AST for identifier token `token`. Also check that identifier is not empty.
656
657
  function identAst( token, category, noTokenTypeCheck = false ) {
657
658
  token.isIdentifier = category;
658
659
  let id = token.text;
@@ -713,37 +714,107 @@ function pushXprToken( args ) {
713
714
  }
714
715
 
715
716
  function valuePathAst( ref ) {
716
- // TODO: XSN representation of functions is a bit strange - rework if methods
717
- // are introduced
717
+ // TODO: XSN representation of functions is a bit strange - rework
718
718
  const { path } = ref;
719
719
  if (!path || path.broken)
720
720
  return ref;
721
721
  if (path.length !== 1) {
722
722
  const item = path.find( i => i.args && i.$syntax !== ':' );
723
- if (!item)
723
+ if (!item) // also covers empty paths
724
724
  return ref;
725
- this.error( 'syntax-unsupported-method', item.location, {},
726
- 'Methods in expressions are not supported yet' );
727
- path.broken = true;
728
- path.length = 1;
729
725
  }
730
- const { args, id, location } = path[0];
731
- if (args
726
+ else if (path.length === 1) {
727
+ const { args, id, location } = path[0];
728
+ if (args
732
729
  ? path[0].$syntax === ':'
733
730
  : path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
734
- return ref;
731
+ return ref;
732
+
733
+ const implicit = this.previousTokenAtLocation( location );
734
+ if (implicit && implicit.isIdentifier)
735
+ implicit.isIdentifier = 'func';
736
+ const op = { location, val: 'call' };
737
+ return (args)
738
+ ? {
739
+ op, func: ref, location: ref.location, args,
740
+ }
741
+ : { op, func: ref, location: ref.location };
742
+ }
735
743
 
736
- const implicit = this.previousTokenAtLocation( location );
737
- if (implicit && implicit.isIdentifier)
738
- implicit.isIdentifier = 'func';
739
- const op = { location, val: 'call' };
740
- return (args)
741
- ? {
742
- op, func: ref, location: ref.location, args,
744
+ // method call ---------------------------
745
+
746
+ const args = [];
747
+ const pathRest = [ ...path ];
748
+ let pathHead = pathRest.shift();
749
+
750
+ if (pathHead.args) {
751
+ args.push({
752
+ op: { location: pathHead.location, val: 'call' },
753
+ func: { path: [ pathHead ] },
754
+ location: pathHead.location,
755
+ args: pathHead.args || [],
756
+ });
757
+ pathHead = pathRest.shift();
758
+ }
759
+ else {
760
+ const refPath = [];
761
+ while (pathHead && !pathHead.args) {
762
+ refPath.push(pathHead);
763
+ pathHead = pathRest.shift();
743
764
  }
744
- : { op, func: ref, location: ref.location };
765
+ args.push({ path: refPath, location: refPath[0].location });
766
+ }
767
+
768
+ if (pathHead?.args)
769
+ pathRest.unshift(pathHead);
770
+
771
+ for (const method of pathRest) {
772
+ args.push({
773
+ // TODO: Update parser to have proper location for `.`?
774
+ location: weakLocation(method.location),
775
+ val: '.',
776
+ literal: 'token',
777
+ });
778
+ const func = {
779
+ op: { location: method.location, val: 'call' },
780
+ func: { path: [ method ] },
781
+ location: method.location,
782
+ };
783
+ if (method.args)
784
+ func.args = method.args;
785
+ args.push(func);
786
+ }
787
+
788
+ return {
789
+ op: {
790
+ val: 'ixpr',
791
+ location: this.startLocation(),
792
+ },
793
+ args,
794
+ location: ref.location,
795
+ };
745
796
  }
746
797
 
798
+
799
+ /**
800
+ * Adds the first argument of `args` ('new' keyword) to the second argument, if it's a method-ixpr.
801
+ *
802
+ * @todo Cleanup, remove.
803
+ * @param args
804
+ */
805
+ function fixNewKeywordPlacement( args ) {
806
+ // TODO: Currently, the parser creates an args-array with `new` and an `ixpr` for
807
+ // `new P().abc()`. That is, "new" is separate from the methods.
808
+ // This function tries to work around it, but its more of a hack.
809
+ if (args.length !== 2 || !args[1].args || args[1].op?.val !== 'ixpr')
810
+ return;
811
+ const ixpr = args[1];
812
+ ixpr.args.unshift(args[0]);
813
+ args.length = 0;
814
+ args.push(ixpr);
815
+ }
816
+
817
+
747
818
  // If a '-' is directly before an unsigned number, consider it part of the number;
748
819
  // otherwise (including for '+'), represent it as extra unary prefix operator.
749
820
  function signedExpression( args, expr ) {
@@ -1174,4 +1245,14 @@ function checkTypeFacet( art, argIdent ) {
1174
1245
  return false;
1175
1246
  }
1176
1247
 
1248
+ function checkTypeArgs( art ) {
1249
+ const args = art.$typeArgs;
1250
+ // One or two arguments are interpreted as either length or precision/scale.
1251
+ if (args.length > 2) {
1252
+ const loc = args[2].location;
1253
+ this.error( 'syntax-unexpected-argument', loc, {}, 'Too many type arguments' );
1254
+ art.$typeArgs = undefined;
1255
+ }
1256
+ }
1257
+
1177
1258
  module.exports = GenericAntlrParser;