@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.
- package/CHANGELOG.md +49 -0
- package/README.md +3 -0
- package/bin/cdsc.js +9 -5
- package/doc/CHANGELOG_BETA.md +20 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +2 -1
- package/lib/base/dictionaries.js +10 -0
- package/lib/base/message-registry.js +56 -12
- package/lib/base/messages.js +39 -20
- package/lib/base/model.js +1 -0
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +9 -5
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +8 -5
- package/lib/checks/types.js +6 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +20 -23
- package/lib/compiler/base.js +1 -2
- package/lib/compiler/builtins.js +2 -2
- package/lib/compiler/checks.js +237 -242
- package/lib/compiler/define.js +63 -75
- package/lib/compiler/extend.js +325 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -55
- package/lib/compiler/kick-start.js +6 -7
- package/lib/compiler/populate.js +284 -288
- package/lib/compiler/propagator.js +15 -13
- package/lib/compiler/resolve.js +136 -306
- package/lib/compiler/shared.js +42 -44
- package/lib/compiler/tweak-assocs.js +29 -27
- package/lib/compiler/utils.js +29 -3
- package/lib/edm/annotations/genericTranslation.js +7 -13
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +0 -4
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +1 -5
- package/lib/gen/Dictionary.json +34 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2429 -2401
- package/lib/inspect/inspectPropagation.js +2 -0
- package/lib/json/from-csn.js +87 -41
- package/lib/json/to-csn.js +47 -16
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +109 -28
- package/lib/language/language.g4 +20 -4
- package/lib/model/csnRefs.js +1 -1
- package/lib/model/csnUtils.js +1 -0
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +20 -7
- package/lib/render/toHdbcds.js +2 -8
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +559 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forRelationalDB.js +38 -28
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +14 -8
- package/lib/transform/transformUtilsNew.js +6 -5
- package/lib/transform/translateAssocsToJoins.js +49 -33
- 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
|
package/lib/json/from-csn.js
CHANGED
|
@@ -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}
|
|
66
|
-
* xorGroups. If set then only one property of
|
|
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
|
-
],
|
|
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',
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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['
|
|
1287
|
-
|
|
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
|
|
1290
|
-
symbol( val['#'], schema['#'], xsn );
|
|
1319
|
+
const r = refSplit( val['='], '=' );
|
|
1291
1320
|
++virtualLine;
|
|
1292
|
-
return
|
|
1321
|
+
return r;
|
|
1293
1322
|
}
|
|
1294
1323
|
}
|
|
1295
|
-
|
|
1324
|
+
if (typeof val['#'] === 'string') {
|
|
1296
1325
|
if (Object.keys( val ).length === 1) {
|
|
1297
1326
|
++virtualLine;
|
|
1298
|
-
const
|
|
1327
|
+
const xsn = { location: location() };
|
|
1328
|
+
symbol( val['#'], schema['#'], xsn );
|
|
1299
1329
|
++virtualLine;
|
|
1300
|
-
return
|
|
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
|
|
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
|
|
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
|
|
1632
|
-
if (expected( zero, schema[zero] ) && !(
|
|
1662
|
+
const groups = s.xorGroups;
|
|
1663
|
+
if (expected( zero, schema[zero] ) && !(groups.length && groups.every(group => xor[group]))) {
|
|
1633
1664
|
replaceZeroProp( prop, zero );
|
|
1634
|
-
|
|
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.
|
|
1654
|
-
|
|
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 => !
|
|
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
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (
|
|
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
|
-
|
|
1727
|
-
return
|
|
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
|
|
package/lib/json/to-csn.js
CHANGED
|
@@ -55,7 +55,7 @@ const transformers = {
|
|
|
55
55
|
kind,
|
|
56
56
|
id: n => n, // in path item
|
|
57
57
|
doc: value,
|
|
58
|
-
'@':
|
|
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,
|
|
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,
|
|
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
|
|
876
|
-
if (
|
|
877
|
-
//
|
|
878
|
-
result[prop] = value( Object.create(
|
|
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 (
|
|
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 (
|
|
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;
|
|
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
|
-
|
|
1042
|
-
|
|
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
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
731
|
-
|
|
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
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
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;
|