@sap/cds-compiler 2.10.4 → 2.12.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.
- package/CHANGELOG.md +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/json/from-csn.js
CHANGED
|
@@ -705,7 +705,7 @@ let csnFilename = '';
|
|
|
705
705
|
let virtualLine = 1;
|
|
706
706
|
/** @type {CSN.Location[]} */
|
|
707
707
|
let dollarLocations = [];
|
|
708
|
-
let
|
|
708
|
+
let arrayLevelCount = 0;
|
|
709
709
|
|
|
710
710
|
/**
|
|
711
711
|
* @param {Object.<string, SchemaSpec>} specs
|
|
@@ -773,12 +773,8 @@ function arrayOf( fn, filter = undefined ) {
|
|
|
773
773
|
} );
|
|
774
774
|
const minLength = spec.minLength || 0;
|
|
775
775
|
if (minLength > val.length) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
{
|
|
779
|
-
std: 'Expected array in $(PROP) to have at least $(N) items',
|
|
780
|
-
one: 'Expected array in $(PROP) to have at least one item',
|
|
781
|
-
} );
|
|
776
|
+
message( 'syntax-csn-expected-length', location(true),
|
|
777
|
+
{ prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
|
|
782
778
|
}
|
|
783
779
|
if (val.length)
|
|
784
780
|
++virtualLine; // [] in one JSON line
|
|
@@ -913,7 +909,7 @@ function definition( def, spec, xsn, csn, name ) {
|
|
|
913
909
|
++virtualLine;
|
|
914
910
|
}
|
|
915
911
|
}
|
|
916
|
-
if (!r.name && name) {
|
|
912
|
+
if (!r.name && name != null) {
|
|
917
913
|
r.name = { id: name, location: r.location };
|
|
918
914
|
if (prop === 'columns' || prop === 'keys' || prop === 'foreignKeys')
|
|
919
915
|
r.name.$inferred = 'as';
|
|
@@ -1002,13 +998,13 @@ function selectItem( def, spec, xsn, csn ) {
|
|
|
1002
998
|
return definition( def, spec, xsn, csn, null ); // definer sets name
|
|
1003
999
|
}
|
|
1004
1000
|
|
|
1005
|
-
function returnsDefinition( def, spec, xsn, csn
|
|
1001
|
+
function returnsDefinition( def, spec, xsn, csn ) {
|
|
1006
1002
|
// TODO: be stricter in what is allowed inside returns
|
|
1007
1003
|
if (!inExtensions)
|
|
1008
|
-
return definition( def, spec, xsn, csn,
|
|
1004
|
+
return definition( def, spec, xsn, csn, '' );
|
|
1009
1005
|
// for the moment, flatten elements in returns in an annotate
|
|
1010
1006
|
// TODO: bigger Core Compiler changes would have to be done otherwise
|
|
1011
|
-
xsn.elements = definition( def, spec, xsn, csn,
|
|
1007
|
+
xsn.elements = definition( def, spec, xsn, csn, '' ).elements;
|
|
1012
1008
|
xsn.$syntax = 'returns';
|
|
1013
1009
|
return undefined;
|
|
1014
1010
|
}
|
|
@@ -1168,6 +1164,12 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
|
|
|
1168
1164
|
xsn.sym = { id, location: location() };
|
|
1169
1165
|
}
|
|
1170
1166
|
|
|
1167
|
+
// returns: false = no "...", true = "..." without UP TO, 'upTo' = "..." with UP TO
|
|
1168
|
+
function isEllipsis( val ) {
|
|
1169
|
+
return val && typeof val === 'object' && '...' in val && Object.keys(val).length === 1 &&
|
|
1170
|
+
(val['...'] === true || 'upTo');
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1171
1173
|
function annoValue( val, spec ) {
|
|
1172
1174
|
if (val == null) // TODO: reject undefined
|
|
1173
1175
|
return { val, literal: 'null', location: location() };
|
|
@@ -1175,22 +1177,39 @@ function annoValue( val, spec ) {
|
|
|
1175
1177
|
if (lit !== 'object')
|
|
1176
1178
|
return { val, literal: lit, location: location() };
|
|
1177
1179
|
if (Array.isArray( val )) {
|
|
1178
|
-
|
|
1179
|
-
if (
|
|
1180
|
-
|
|
1181
|
-
|
|
1180
|
+
let seenEllipsis = false;
|
|
1181
|
+
if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)
|
|
1182
|
+
if (val.some( isEllipsis )) {
|
|
1183
|
+
error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
|
|
1184
|
+
'Unexpected $(CODE) in nested array' );
|
|
1185
|
+
}
|
|
1182
1186
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1187
|
+
else {
|
|
1188
|
+
for (const item of val) {
|
|
1189
|
+
if (seenEllipsis !== true) {
|
|
1190
|
+
seenEllipsis = isEllipsis( item ) || seenEllipsis;
|
|
1191
|
+
}
|
|
1192
|
+
else if (isEllipsis( item )) { // with or without UP TO
|
|
1193
|
+
// error position at the beginning of the array, but that is fine
|
|
1194
|
+
error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
|
|
1195
|
+
'Expected no more than one $(CODE)' );
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1186
1199
|
}
|
|
1187
|
-
|
|
1200
|
+
arrayLevelCount++;
|
|
1188
1201
|
const retval = {
|
|
1189
1202
|
location: location(),
|
|
1190
1203
|
val: arrayOf( annoValue )( val, spec ),
|
|
1191
1204
|
literal: 'array',
|
|
1192
1205
|
};
|
|
1193
|
-
|
|
1206
|
+
arrayLevelCount--;
|
|
1207
|
+
if (seenEllipsis === 'upTo') {
|
|
1208
|
+
error( 'syntax-csn-expecting-ellipsis', location(true), // at closing bracket
|
|
1209
|
+
{ code: '... up to', newcode: '...' },
|
|
1210
|
+
// TODO: should we be more CSN specific in the message?
|
|
1211
|
+
'Expecting an array item $(NEWCODE) after an item with $(CODE)' );
|
|
1212
|
+
}
|
|
1194
1213
|
return retval;
|
|
1195
1214
|
}
|
|
1196
1215
|
if (typeof val['#'] === 'string') {
|
|
@@ -1209,12 +1228,19 @@ function annoValue( val, spec ) {
|
|
|
1209
1228
|
return refSplit( val['='], '=' );
|
|
1210
1229
|
}
|
|
1211
1230
|
}
|
|
1212
|
-
else if (val['...'] && Object.keys(val).length === 1) {
|
|
1213
|
-
|
|
1231
|
+
else if (val['...'] && Object.keys( val ).length === 1) {
|
|
1232
|
+
// TODO: only if not nested - see error above
|
|
1233
|
+
++virtualLine;
|
|
1234
|
+
const ell = val['...'];
|
|
1235
|
+
const r = {
|
|
1214
1236
|
val: '...',
|
|
1215
1237
|
literal: 'token',
|
|
1216
1238
|
location: location(),
|
|
1217
1239
|
};
|
|
1240
|
+
if (ell !== true)
|
|
1241
|
+
r.upTo = annoValue( ell, schema['@'] );
|
|
1242
|
+
++virtualLine;
|
|
1243
|
+
return r;
|
|
1218
1244
|
}
|
|
1219
1245
|
const struct = Object.create(null);
|
|
1220
1246
|
++virtualLine;
|
|
@@ -1271,6 +1297,10 @@ function func( val, spec, xsn ) {
|
|
|
1271
1297
|
|
|
1272
1298
|
function xpr( exprs, spec, xsn, csn ) {
|
|
1273
1299
|
if (csn.func) {
|
|
1300
|
+
if (!exprs.length) {
|
|
1301
|
+
message( 'syntax-csn-expected-length', location(true),
|
|
1302
|
+
{ prop: 'xpr', otherprop: 'func', '#': 'suffix' });
|
|
1303
|
+
}
|
|
1274
1304
|
xsn.suffix = exprArgs( exprs, spec );
|
|
1275
1305
|
}
|
|
1276
1306
|
else {
|
|
@@ -1354,12 +1384,11 @@ function exprArgs( cond, spec, xsn, csn ) {
|
|
|
1354
1384
|
|
|
1355
1385
|
function condition( cond, spec ) {
|
|
1356
1386
|
const loc = location();
|
|
1357
|
-
|
|
1387
|
+
return {
|
|
1358
1388
|
op: { val: 'xpr', location: loc },
|
|
1359
1389
|
args: exprArgs( cond, spec ),
|
|
1360
1390
|
location: loc,
|
|
1361
1391
|
};
|
|
1362
|
-
return x;
|
|
1363
1392
|
}
|
|
1364
1393
|
|
|
1365
1394
|
function vZeroValue( obj, spec, xsn ) {
|
|
@@ -1666,8 +1695,8 @@ function pushLocation( obj ) {
|
|
|
1666
1695
|
error( 'syntax-csn-expected-object', location(true), { prop: '$location' } );
|
|
1667
1696
|
}
|
|
1668
1697
|
// hidden feature: string $location
|
|
1669
|
-
const m = /:(\d+)(?::(\d+)
|
|
1670
|
-
if (!m) {
|
|
1698
|
+
const m = /:(\d+)(?::(\d+))?$/.exec( loc ); // extra '^'s at end deliberately left out
|
|
1699
|
+
if (!m) { // without location or with '^'s: do not use
|
|
1671
1700
|
dollarLocations.push( null );
|
|
1672
1701
|
}
|
|
1673
1702
|
else {
|
|
@@ -1710,8 +1739,10 @@ function toXsn( csn, filename, options, messageFunctions ) {
|
|
|
1710
1739
|
csnFilename = filename;
|
|
1711
1740
|
virtualLine = 1;
|
|
1712
1741
|
dollarLocations = [];
|
|
1742
|
+
arrayLevelCount = 0;
|
|
1713
1743
|
inExtensions = null;
|
|
1714
1744
|
vocabInDefinitions = null;
|
|
1745
|
+
|
|
1715
1746
|
const xsn = { $frontend: 'json' };
|
|
1716
1747
|
|
|
1717
1748
|
// eslint-disable-next-line object-curly-newline
|
package/lib/json/to-csn.js
CHANGED
|
@@ -33,12 +33,23 @@ let projectionAsQuery = false;
|
|
|
33
33
|
let withLocations = false;
|
|
34
34
|
let dictionaryPrototype = null;
|
|
35
35
|
|
|
36
|
+
// Properties for dictionaries, set in compileX() and TODO: parseX(), must be
|
|
37
|
+
// stored with symbols as keys, as we do not want to disallow any key name:
|
|
38
|
+
const $inferred = Symbol.for('cds.$inferred');
|
|
39
|
+
|
|
40
|
+
// XSN $inferred values mapped to Universal CSN $generated values:
|
|
41
|
+
const inferredAsGenerated = {
|
|
42
|
+
autoexposed: 'exposed',
|
|
43
|
+
'localized-entity': 'localized',
|
|
44
|
+
localized: 'localized', // on elements (texts, localized)
|
|
45
|
+
'composition-entity': 'composed', // ('aspect-composition' on element not in CSN)
|
|
46
|
+
};
|
|
47
|
+
|
|
36
48
|
// IMPORTANT: the order of these properties determine the order of properties
|
|
37
49
|
// in the resulting CSN !!! Also check const `csnPropertyNames`.
|
|
38
50
|
const transformers = {
|
|
39
51
|
// early and modifiers (without null / not null) ---------------------------
|
|
40
52
|
kind,
|
|
41
|
-
_outer: ( _, csn, node ) => addOrigin( csn, node ),
|
|
42
53
|
id: n => n, // in path item
|
|
43
54
|
doc: value,
|
|
44
55
|
'@': value,
|
|
@@ -63,11 +74,11 @@ const transformers = {
|
|
|
63
74
|
precision: value,
|
|
64
75
|
scale: value,
|
|
65
76
|
srid: value,
|
|
66
|
-
cardinality
|
|
77
|
+
cardinality, // also in pathItem: after 'id', before 'where'
|
|
67
78
|
targetAspect,
|
|
68
79
|
target,
|
|
69
80
|
foreignKeys,
|
|
70
|
-
enum:
|
|
81
|
+
enum: enumDict,
|
|
71
82
|
items,
|
|
72
83
|
includes: arrayOf( artifactRef ), // also entities
|
|
73
84
|
// late expressions / query properties -------------------------------------
|
|
@@ -89,7 +100,7 @@ const transformers = {
|
|
|
89
100
|
offset: expression,
|
|
90
101
|
on: onCondition,
|
|
91
102
|
// definitions, extensions, members ----------------------------------------
|
|
92
|
-
returns
|
|
103
|
+
returns, // storing the return type of actions
|
|
93
104
|
notNull: value,
|
|
94
105
|
default: expression,
|
|
95
106
|
// targetElement: ignore, // special display of foreign key, renameTo: select
|
|
@@ -199,6 +210,15 @@ const operators = {
|
|
|
199
210
|
partitionBy: exprs => [
|
|
200
211
|
'partition', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
|
|
201
212
|
],
|
|
213
|
+
rows: exprs => [
|
|
214
|
+
'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
|
|
215
|
+
],
|
|
216
|
+
preceding: postfix( [ 'preceding' ] ),
|
|
217
|
+
unboundedPreceding: [ 'unbounded', 'preceding' ],
|
|
218
|
+
currentRow: [ 'current', 'row' ],
|
|
219
|
+
unboundedFollowing: [ 'unbounded', 'following' ],
|
|
220
|
+
following: postfix( [ 'following' ] ),
|
|
221
|
+
frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
|
|
202
222
|
// xpr: (exprs) => [].concat( ...exprs ), see below - handled extra
|
|
203
223
|
};
|
|
204
224
|
|
|
@@ -536,7 +556,7 @@ function sources( srcDict, csn ) {
|
|
|
536
556
|
}
|
|
537
557
|
}
|
|
538
558
|
|
|
539
|
-
function attachAnnotations( annotate, prop, dict, inferred,
|
|
559
|
+
function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
|
|
540
560
|
const annoDict = Object.create( dictionaryPrototype );
|
|
541
561
|
for (const name in dict) {
|
|
542
562
|
const elem = dict[name];
|
|
@@ -554,7 +574,7 @@ function attachAnnotations( annotate, prop, dict, inferred, returns = false ) {
|
|
|
554
574
|
annoDict[name] = sub;
|
|
555
575
|
}
|
|
556
576
|
if (Object.keys( annoDict ).length) {
|
|
557
|
-
if (
|
|
577
|
+
if (insideReturns)
|
|
558
578
|
annotate.returns = { elements: annoDict };
|
|
559
579
|
else
|
|
560
580
|
annotate[prop] = annoDict;
|
|
@@ -597,10 +617,21 @@ function set( prop, csn, node ) {
|
|
|
597
617
|
}
|
|
598
618
|
|
|
599
619
|
function targetAspect( val, csn, node ) {
|
|
620
|
+
if (universalCsn) {
|
|
621
|
+
if (val.$inferred)
|
|
622
|
+
return undefined;
|
|
623
|
+
if (node.target) {
|
|
624
|
+
csn.$origin = { type: 'cds.Composition' };
|
|
625
|
+
if (node.cardinality)
|
|
626
|
+
csn.$origin.cardinality = standard( node.cardinality );
|
|
627
|
+
csn.$origin.target = (val.elements) ? standard( val ) : artifactRef( val, true );
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
600
631
|
const ta = (val.elements)
|
|
601
632
|
? addLocation( val.location, standard( val ) )
|
|
602
633
|
: artifactRef( val, true );
|
|
603
|
-
if (!gensrcFlavor || node.target && !node.target.$inferred)
|
|
634
|
+
if (!gensrcFlavor && !universalCsn || node.target && !node.target.$inferred)
|
|
604
635
|
return ta;
|
|
605
636
|
// For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
|
|
606
637
|
csn.target = ta;
|
|
@@ -622,7 +653,7 @@ function target( val, _csn, node ) {
|
|
|
622
653
|
}
|
|
623
654
|
|
|
624
655
|
function items( obj, csn, node ) {
|
|
625
|
-
if (!keepElements( node ))
|
|
656
|
+
if (!keepElements( node, obj ))
|
|
626
657
|
return undefined;
|
|
627
658
|
return standard( obj ); // no 'elements' with inferred elements with gensrc
|
|
628
659
|
}
|
|
@@ -637,23 +668,42 @@ function elements( dict, csn, node ) {
|
|
|
637
668
|
return insertOrderDict( dict );
|
|
638
669
|
}
|
|
639
670
|
|
|
671
|
+
function enumDict( dict, csn, node ) {
|
|
672
|
+
if (gensrcFlavor && dict[$inferred] ||
|
|
673
|
+
!keepElements( node ))
|
|
674
|
+
// no 'elements' with SELECT or inferred elements with gensrc;
|
|
675
|
+
// hidden or visible 'elements' will be set in query()
|
|
676
|
+
return undefined;
|
|
677
|
+
if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate' &&
|
|
678
|
+
node.type._artifact && !node.type._artifact.builtin)
|
|
679
|
+
// derived type of enum type with individual annotations: also set $origin
|
|
680
|
+
csn.$origin = originRef( node.type._artifact );
|
|
681
|
+
return insertOrderDict( dict );
|
|
682
|
+
}
|
|
683
|
+
|
|
640
684
|
function enumerableQueryElements( select ) {
|
|
641
|
-
|
|
642
|
-
return false;
|
|
643
|
-
if (select.orderBy || select.$orderBy)
|
|
644
|
-
return true;
|
|
645
|
-
const alias = select._parent;
|
|
646
|
-
return alias.query && (alias.query._leadingQuery || alias.query) === select;
|
|
685
|
+
return (universalCsn && select !== select._main._leadingQuery);
|
|
647
686
|
}
|
|
648
687
|
|
|
649
688
|
// Should we render the elements? (and items?)
|
|
650
|
-
function keepElements( node ) {
|
|
689
|
+
function keepElements( node, line ) {
|
|
651
690
|
if (universalCsn)
|
|
652
691
|
// $expand = null/undefined: not elements not via expansion
|
|
653
692
|
// $expand = 'target'/'annotate': with redirections / individual annotations
|
|
654
693
|
return node.$expand !== 'origin';
|
|
655
694
|
if (!node.type || node.kind === 'type')
|
|
656
695
|
return true;
|
|
696
|
+
// keep many SimpleType/Entity
|
|
697
|
+
if (line) {
|
|
698
|
+
if (!node.type)
|
|
699
|
+
return true;
|
|
700
|
+
const array = node.type._artifact; // see function items() in propagator.js
|
|
701
|
+
const ltype = line.type && line.type._artifact;
|
|
702
|
+
if (!array || // reference errors
|
|
703
|
+
array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
|
|
704
|
+
(!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
657
707
|
// even if expanded elements have no new target or direct annotation,
|
|
658
708
|
// they might have got one via propagation – any new target/annos during their
|
|
659
709
|
// way from the original structure type definition to the current usage
|
|
@@ -711,7 +761,8 @@ function ignore() { /* no-op: ignore property */ }
|
|
|
711
761
|
|
|
712
762
|
function location( loc, csn, xsn ) {
|
|
713
763
|
if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
|
|
714
|
-
(!xsn.$inferred || !xsn._main)
|
|
764
|
+
(!xsn.$inferred || !xsn._main) &&
|
|
765
|
+
xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
|
|
715
766
|
// Also include $location for elements in queries (if not via '*')
|
|
716
767
|
addLocation( xsn.name && xsn.name.location || loc, csn );
|
|
717
768
|
}
|
|
@@ -767,8 +818,10 @@ function dictionary( dict, keys, prop ) {
|
|
|
767
818
|
}
|
|
768
819
|
|
|
769
820
|
function foreignKeys( dict, csn, node ) {
|
|
770
|
-
if (universalCsn
|
|
771
|
-
|
|
821
|
+
if (universalCsn) {
|
|
822
|
+
if (!target( node.target, csn, node ) || dict[$inferred] === 'keys')
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
772
825
|
if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')
|
|
773
826
|
dict = node._origin.foreignKeys;
|
|
774
827
|
const keys = [];
|
|
@@ -782,6 +835,14 @@ function foreignKeys( dict, csn, node ) {
|
|
|
782
835
|
csn.keys = keys;
|
|
783
836
|
}
|
|
784
837
|
|
|
838
|
+
function returns( art, csn, _node, prop ) {
|
|
839
|
+
// TODO: currently, the `returns` structure might just have been created by the propagator
|
|
840
|
+
// if that is the case, there should be no reason to store it in universal CSN
|
|
841
|
+
if (universalCsn && art.$inferred === 'proxy')
|
|
842
|
+
return undefined;
|
|
843
|
+
return definition( art, csn, _node, prop );
|
|
844
|
+
}
|
|
845
|
+
|
|
785
846
|
function definition( art, _csn, _node, prop ) {
|
|
786
847
|
if (!art || typeof art !== 'object')
|
|
787
848
|
return undefined; // TODO: complain with strict
|
|
@@ -801,50 +862,151 @@ function definition( art, _csn, _node, prop ) {
|
|
|
801
862
|
delete c.elements;
|
|
802
863
|
c.returns = { elements: elems };
|
|
803
864
|
}
|
|
865
|
+
// precondition already fulfilled: art.kind !== 'key'
|
|
866
|
+
addOrigin( c, art, art._origin );
|
|
804
867
|
return c;
|
|
805
868
|
}
|
|
806
869
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
870
|
+
// create $origin specification for `includes` of `art`
|
|
871
|
+
function includesOrigin( includes, art ) {
|
|
872
|
+
const $origin = originRef( includes[0]._artifact );
|
|
873
|
+
if (includes.length === 1)
|
|
874
|
+
return $origin;
|
|
875
|
+
const result = { $origin };
|
|
876
|
+
for (const incl of includes.slice(1)) {
|
|
877
|
+
const aspect = incl._artifact;
|
|
878
|
+
for (const prop in aspect) {
|
|
879
|
+
if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
|
|
880
|
+
const anno = aspect[prop];
|
|
881
|
+
if (anno.val !== null)
|
|
882
|
+
// matererialize non-null annos (whether direct or inherited)
|
|
883
|
+
result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return (Object.keys( result ).length === 1) ? $origin : result;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function addOrigin( csn, xsn, origin ) {
|
|
891
|
+
if (!universalCsn || hasExplicitProp( xsn.type ))
|
|
892
|
+
return;
|
|
810
893
|
if (xsn._from) {
|
|
811
|
-
|
|
894
|
+
const source = xsn._from[0]._origin;
|
|
895
|
+
csn.$origin = originRef( source );
|
|
896
|
+
if (source.params && !xsn.params)
|
|
897
|
+
csn.params = null; // discontinue `params` inheritance
|
|
898
|
+
if (source.actions && !xsn.actions)
|
|
899
|
+
csn.actions = null; // discontinue `actions` inheritance
|
|
900
|
+
return;
|
|
812
901
|
}
|
|
813
|
-
else if (xsn.includes
|
|
814
|
-
csn.$origin =
|
|
902
|
+
else if (xsn.includes) {
|
|
903
|
+
csn.$origin = includesOrigin( xsn.includes, xsn );
|
|
904
|
+
return;
|
|
815
905
|
}
|
|
816
|
-
else if (
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
906
|
+
else if (!xsn._main || xsn.kind === 'select') {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const parent = getParent( xsn );
|
|
910
|
+
const parentOrigin = getOrigin( parent );
|
|
911
|
+
if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
|
|
912
|
+
if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
|
|
913
|
+
csn.$origin = null;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// Skip all proxies which do not make it into the CSN, as there are no
|
|
917
|
+
// individual annotations or redirection targets on it:
|
|
918
|
+
while (origin._parent && origin._parent.$expand === 'origin')
|
|
919
|
+
origin = origin._origin || origin.type._artifact;
|
|
920
|
+
// The while loop is not only for the else case below: when setting implicit
|
|
921
|
+
// prototypes, it is important that we do not have to follow the prototypes of
|
|
922
|
+
// other object; we would need to ensure a right order to avoid issues otherwise.
|
|
923
|
+
if (parentOrigin === getParent( origin )) {
|
|
924
|
+
// implicit prototype or shortened reference
|
|
925
|
+
const { id } = origin.name || {};
|
|
926
|
+
if (id && xsn.name && id !== xsn.name.id)
|
|
927
|
+
csn.$origin = id;
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (origin.kind === 'mixin') {
|
|
931
|
+
// currently, target and on are always set - nothing to do here, just set type
|
|
932
|
+
csn.type = 'cds.Association';
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const ref = originRef( origin, xsn );
|
|
936
|
+
if (ref) {
|
|
937
|
+
csn.$origin = ref;
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
// An element of a query with a query in FROM:
|
|
941
|
+
const anon = definition( origin ); // use $origin: {...} if necessary
|
|
942
|
+
// as there are no implicit $origin prototypes on sub query elements (yet),
|
|
943
|
+
// we do not have to care about $origin not being set
|
|
944
|
+
const { $origin } = anon;
|
|
945
|
+
if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
|
|
946
|
+
// repeated anon: flatten
|
|
947
|
+
csn.$origin = Object.assign( $origin, anon );
|
|
948
|
+
}
|
|
949
|
+
else if (Object.keys( anon )
|
|
950
|
+
// (we can use the properties in `csn`, because addOrigin() is called last)
|
|
951
|
+
.every( p => p in csn || p === '$origin' || p === '$location')) {
|
|
952
|
+
// nothing new in $origin: {...}
|
|
953
|
+
addOrigin( csn, xsn, origin._origin );
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
csn.$origin = anon;
|
|
821
957
|
}
|
|
822
|
-
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function getParent( art ) {
|
|
961
|
+
const parent = art._parent;
|
|
962
|
+
const main = parent._main;
|
|
963
|
+
return (main && parent === main._leadingQuery) ? main : parent;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function getOrigin( art ) {
|
|
967
|
+
if (art._origin)
|
|
968
|
+
return art._origin;
|
|
969
|
+
if (hasExplicitProp( art.type ))
|
|
970
|
+
return art.type._artifact;
|
|
971
|
+
if (art.includes)
|
|
972
|
+
return art.includes[0]._artifact;
|
|
973
|
+
if (art._from)
|
|
974
|
+
return art._from[0]._origin;
|
|
975
|
+
return undefined;
|
|
823
976
|
}
|
|
824
977
|
|
|
825
978
|
function hasExplicitProp( ref ) {
|
|
826
979
|
return ref && !ref.$inferred;
|
|
827
980
|
}
|
|
828
981
|
|
|
829
|
-
function originRef( art ) {
|
|
982
|
+
function originRef( art, user ) {
|
|
830
983
|
const r = [];
|
|
831
984
|
// do not use name.element, as we allow `.`s in name
|
|
832
|
-
let
|
|
833
|
-
while (
|
|
834
|
-
const nkind = normalizedKind[
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
985
|
+
let parent = art;
|
|
986
|
+
while (parent._main && parent.kind !== 'select') {
|
|
987
|
+
const nkind = normalizedKind[parent.kind];
|
|
988
|
+
if (parent.name.id || !r.length)
|
|
989
|
+
// Return parameter is in XSN - kind: 'param', name.id: ''
|
|
990
|
+
// eslint-disable-next-line no-nested-ternary, max-len
|
|
991
|
+
r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
|
|
992
|
+
parent = parent._parent;
|
|
993
|
+
}
|
|
994
|
+
if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
|
|
995
|
+
// well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
|
|
996
|
+
return null; // probably use $origin: {...}
|
|
841
997
|
// for sub query in FROM in sub query in FROM, we could condense the info
|
|
998
|
+
|
|
999
|
+
// Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
|
|
1000
|
+
if (r.length === 1 && normalizedKind[art.kind] === 'action')
|
|
1001
|
+
return [ art.name.absolute, art.name.id ];
|
|
842
1002
|
r.push( art.name.absolute );
|
|
843
1003
|
r.reverse();
|
|
844
1004
|
return r;
|
|
845
1005
|
}
|
|
846
1006
|
|
|
847
1007
|
function kind( k, csn, node ) {
|
|
1008
|
+
if (node.$inferred === 'REDIRECTED')
|
|
1009
|
+
return;
|
|
848
1010
|
if (k === 'annotate' || k === 'extend') {
|
|
849
1011
|
// We just use `name.absolute` because it is very likely a "constructed"
|
|
850
1012
|
// extensions. The CSN parser must produce name.path like for other refs.
|
|
@@ -853,22 +1015,47 @@ function kind( k, csn, node ) {
|
|
|
853
1015
|
else if (k === 'extend')
|
|
854
1016
|
csn.kind = k;
|
|
855
1017
|
}
|
|
856
|
-
else {
|
|
857
|
-
|
|
858
|
-
'element', 'key', 'param', 'enum', 'select', '$join',
|
|
859
|
-
'$tableAlias', 'annotation', 'mixin',
|
|
860
|
-
].includes(k))
|
|
861
|
-
csn.kind = k;
|
|
862
|
-
addOrigin( csn, node );
|
|
1018
|
+
else if (k === 'action' && node._main && universalCsn && node.$inferred) {
|
|
1019
|
+
// Universal CSN: do not mention kind: 'action' on expanded action
|
|
863
1020
|
}
|
|
1021
|
+
else if (![
|
|
1022
|
+
'element', 'key', 'param', 'enum', 'select', '$join',
|
|
1023
|
+
'$tableAlias', 'annotation', 'mixin',
|
|
1024
|
+
].includes(k)) {
|
|
1025
|
+
csn.kind = k;
|
|
1026
|
+
}
|
|
1027
|
+
const generated = universalCsn && inferredAsGenerated[node.$inferred];
|
|
1028
|
+
if (typeof generated === 'string')
|
|
1029
|
+
csn.$generated = generated;
|
|
864
1030
|
}
|
|
865
1031
|
|
|
866
1032
|
function type( node, csn, xsn ) {
|
|
867
|
-
if (universalCsn
|
|
1033
|
+
if (!universalCsn)
|
|
1034
|
+
return artifactRef( node, !node.$extra );
|
|
1035
|
+
if (node.$inferred)
|
|
1036
|
+
return undefined;
|
|
1037
|
+
if (xsn._origin) {
|
|
1038
|
+
if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
|
|
1039
|
+
csn.$origin = definition( xsn._origin );
|
|
1040
|
+
return undefined;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else if ( xsn.targetAspect && xsn.target ) {
|
|
1044
|
+
// type moved to $origin: { type: … }
|
|
868
1045
|
return undefined;
|
|
1046
|
+
}
|
|
869
1047
|
return artifactRef( node, !node.$extra );
|
|
870
1048
|
}
|
|
871
1049
|
|
|
1050
|
+
function cardinality( node, csn, xsn ) {
|
|
1051
|
+
if (!universalCsn)
|
|
1052
|
+
return standard( node );
|
|
1053
|
+
// cardinality might be moved to $origin: { cardinality: … }
|
|
1054
|
+
if (node.$inferred || xsn.targetAspect && !xsn.targetAspect.$inferred && xsn.target)
|
|
1055
|
+
return undefined;
|
|
1056
|
+
return standard( node );
|
|
1057
|
+
}
|
|
1058
|
+
|
|
872
1059
|
function artifactRef( node, terse ) {
|
|
873
1060
|
// When called as transformer function, a CSN node is provided as argument
|
|
874
1061
|
// for `terse`, i.e. it is usually truthy, except for FROM
|
|
@@ -985,7 +1172,7 @@ function value( node ) {
|
|
|
985
1172
|
if (node.literal === 'array')
|
|
986
1173
|
return node.val.map( value );
|
|
987
1174
|
if (node.literal === 'token' && node.val === '...')
|
|
988
|
-
return extra( { '...':
|
|
1175
|
+
return extra( { '...': !node.upTo || value( node.upTo ) } );
|
|
989
1176
|
if (node.literal !== 'struct')
|
|
990
1177
|
// no val (undefined) as true only for annotation values (and struct elem values)
|
|
991
1178
|
return node.name && !('val' in node) || node.val;
|
|
@@ -997,7 +1184,10 @@ function value( node ) {
|
|
|
997
1184
|
|
|
998
1185
|
function enumValue( v, csn, node ) {
|
|
999
1186
|
// Enums can have values but if enums are extended, their kind is 'element',
|
|
1000
|
-
// so we check whether the node is inside an extension.
|
|
1187
|
+
// so we check whether the node is inside an extension. (TODO: still?)
|
|
1188
|
+
if (universalCsn && v.$inferred)
|
|
1189
|
+
return;
|
|
1190
|
+
// (with gensrc, the symbol itself would not make it into the CSN)
|
|
1001
1191
|
if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
|
|
1002
1192
|
Object.assign( csn, expression( v, true ) );
|
|
1003
1193
|
}
|
|
@@ -1185,7 +1375,7 @@ function columns( xsnColumns, csn, xsn ) {
|
|
|
1185
1375
|
addElementAsColumn( col, csnColumns );
|
|
1186
1376
|
}
|
|
1187
1377
|
}
|
|
1188
|
-
else {
|
|
1378
|
+
else { // null = use elements - TODO: still used by A2J? -> remove
|
|
1189
1379
|
for (const name in xsn.elements)
|
|
1190
1380
|
addElementAsColumn( xsn.elements[name], csnColumns );
|
|
1191
1381
|
}
|
|
@@ -1335,6 +1525,9 @@ function compactExpr( e ) { // TODO: options
|
|
|
1335
1525
|
return e && expression( e, true );
|
|
1336
1526
|
}
|
|
1337
1527
|
|
|
1528
|
+
/**
|
|
1529
|
+
* @param {CSN.Options} options
|
|
1530
|
+
*/
|
|
1338
1531
|
function initModuleVars( options = { csnFlavor: 'gensrc' } ) {
|
|
1339
1532
|
gensrcFlavor = options.parseCdl || options.csnFlavor === 'gensrc' ||
|
|
1340
1533
|
options.toCsn && options.toCsn.flavor === 'gensrc';
|
|
@@ -112,7 +112,7 @@ const rules = {
|
|
|
112
112
|
expr: { func: 'conditionEOF', returns: 'cond' }, // yes, condition
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions, rule = 'cdl' ) {
|
|
115
|
+
function parse( source, filename = '<undefined>.cds', options = {}, messageFunctions = null, rule = 'cdl' ) {
|
|
116
116
|
const lexer = new Lexer( new antlr4.InputStream(source) );
|
|
117
117
|
const tokenStream = new RewriteTypeTokenStream(lexer);
|
|
118
118
|
/** @type {object} */
|
|
@@ -162,6 +162,17 @@ function parse( source, filename = '<undefined>.cds', options = {}, messageFunct
|
|
|
162
162
|
if (rulespec.$frontend)
|
|
163
163
|
ast.$frontend = rulespec.$frontend;
|
|
164
164
|
|
|
165
|
+
// Warn about unused doc-comments.
|
|
166
|
+
// Do not warn if docComments are explicitly disabled.
|
|
167
|
+
if (options.docComment !== false) {
|
|
168
|
+
for (const token of tokenStream.tokens) {
|
|
169
|
+
if (token.type === parser.constructor.DocComment && !token.isUsed) {
|
|
170
|
+
messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
|
|
171
|
+
"Ignoring doc-comment as it does not belong to any artifact");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
// TODO: clarify with LSP colleagues: still necessary?
|
|
166
177
|
if (parser.messages) {
|
|
167
178
|
Object.defineProperty( ast, 'messages',
|