@sap/cds-compiler 3.7.2 → 3.8.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 +63 -4
- package/bin/cdsc.js +3 -0
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +15 -0
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +61 -22
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +5 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +64 -22
- package/lib/base/messages.js +12 -7
- package/lib/base/model.js +3 -2
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/hasPersistedElements.js +1 -1
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/onConditions.js +9 -6
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +1 -2
- package/lib/compiler/assert-consistency.js +24 -5
- package/lib/compiler/base.js +49 -2
- package/lib/compiler/builtins.js +15 -6
- package/lib/compiler/checks.js +4 -4
- package/lib/compiler/define.js +59 -80
- package/lib/compiler/extend.js +701 -498
- package/lib/compiler/finalize-parse-cdl.js +4 -3
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +2 -2
- package/lib/compiler/populate.js +17 -9
- package/lib/compiler/propagator.js +12 -5
- package/lib/compiler/resolve.js +26 -173
- package/lib/compiler/shared.js +12 -53
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +124 -46
- package/lib/edm/csn2edm.js +22 -1
- package/lib/edm/edmPreprocessor.js +41 -21
- package/lib/gen/Dictionary.json +4 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4810 -4482
- package/lib/inspect/inspectPropagation.js +20 -36
- package/lib/json/from-csn.js +55 -5
- package/lib/json/to-csn.js +71 -110
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +47 -8
- package/lib/language/language.g4 +88 -62
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +14 -2
- package/lib/model/csnUtils.js +11 -74
- package/lib/model/revealInternalProperties.js +3 -0
- package/lib/optionProcessor.js +3 -0
- package/lib/render/toCdl.js +203 -104
- package/lib/render/toHdbcds.js +0 -1
- package/lib/render/toRename.js +14 -51
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/rewriteCalculatedElements.js +55 -14
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +21 -14
- package/lib/transform/parseExpr.js +2 -0
- package/lib/transform/transformUtilsNew.js +36 -9
- package/lib/transform/translateAssocsToJoins.js +11 -4
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
|
@@ -4,7 +4,13 @@ const { createMessageFunctions } = require('../base/messages');
|
|
|
4
4
|
const { locationString } = require('../base/location');
|
|
5
5
|
const { findArtifact, stringRefToPath } = require('./inspectUtils');
|
|
6
6
|
const { term } = require('../utils/term');
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
const inferredNiceOutput = {
|
|
9
|
+
'*': 'wildcard',
|
|
10
|
+
'aspect-composition': 'composition',
|
|
11
|
+
prop: 'propagation',
|
|
12
|
+
$generated: 'generated',
|
|
13
|
+
};
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* @param {XSN.Model} xsn
|
|
@@ -77,29 +83,16 @@ function _inspectAnnotations( artifactXsn ) {
|
|
|
77
83
|
for (const anno of annos) {
|
|
78
84
|
const annoXsn = artifactXsn[anno];
|
|
79
85
|
const loc = locationString(annoXsn.name.location);
|
|
86
|
+
|
|
80
87
|
let origin;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
case 'extend':
|
|
90
|
-
case 'annotate':
|
|
91
|
-
origin = annoXsn.$priority;
|
|
92
|
-
break;
|
|
93
|
-
|
|
94
|
-
case undefined:
|
|
95
|
-
if (annoXsn.$inferred === '$generated') {
|
|
96
|
-
origin = 'generated';
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
// fallthrough
|
|
100
|
-
default:
|
|
101
|
-
throw new CompilerAssertion(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
|
|
102
|
-
}
|
|
88
|
+
if (annoXsn.$inferred === '$generated')
|
|
89
|
+
origin = 'generated';
|
|
90
|
+
else if (annoXsn.$inferred)
|
|
91
|
+
origin = inferredNiceOutput[annoXsn.$inferred] || annoXsn.$inferred;
|
|
92
|
+
else if (isContainedInParentLocation(annoXsn.name, artifactXsn))
|
|
93
|
+
origin = 'direct';
|
|
94
|
+
else
|
|
95
|
+
origin = 'annotate'; // ...or `extend`
|
|
103
96
|
|
|
104
97
|
maxAnnoLength = Math.max(maxAnnoLength, anno.length);
|
|
105
98
|
|
|
@@ -122,11 +115,6 @@ function _inspectElements( artifactXsn ) {
|
|
|
122
115
|
const result = [];
|
|
123
116
|
const elements = Object.keys(artifactXsn.elements);
|
|
124
117
|
|
|
125
|
-
const inferredNiceOutput = {
|
|
126
|
-
'*': 'wildcard',
|
|
127
|
-
'aspect-composition': 'composition',
|
|
128
|
-
};
|
|
129
|
-
|
|
130
118
|
let maxElemLength = 12;
|
|
131
119
|
let maxOriginLength = 6;
|
|
132
120
|
|
|
@@ -153,7 +141,7 @@ function _inspectElements( artifactXsn ) {
|
|
|
153
141
|
origin = elementXsn.$inferred;
|
|
154
142
|
}
|
|
155
143
|
else if (!isContainedInParentLocation(elementXsn, artifactXsn)) {
|
|
156
|
-
// just a heuristic
|
|
144
|
+
// just a heuristic - a good enough one
|
|
157
145
|
origin = 'extend';
|
|
158
146
|
}
|
|
159
147
|
else {
|
|
@@ -192,13 +180,9 @@ function isContainedInParentLocation( art, parent ) {
|
|
|
192
180
|
const parentLoc = parent.location;
|
|
193
181
|
if (artLoc.file !== parentLoc.file)
|
|
194
182
|
return false;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return false;
|
|
199
|
-
// Good enough for now
|
|
200
|
-
// TODO: Check columns
|
|
201
|
-
return true;
|
|
183
|
+
const startDiff = artLoc.line - parentLoc.line || artLoc.col - parentLoc.col;
|
|
184
|
+
const endDiff = artLoc.endLine - parentLoc.endLine || artLoc.endCol - parentLoc.endCol;
|
|
185
|
+
return startDiff >= 0 && endDiff <= 0;
|
|
202
186
|
}
|
|
203
187
|
|
|
204
188
|
module.exports = {
|
package/lib/json/from-csn.js
CHANGED
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
* it.
|
|
76
76
|
* @property {string} [xorException] A property name that is allowed besides another property
|
|
77
77
|
* of an xorGroup (as an exception to the rule).
|
|
78
|
+
* @property {boolean} [ignoreExtra] Whether extra properties are ignored and not put
|
|
79
|
+
* into $extra.
|
|
78
80
|
*/
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -303,10 +305,20 @@ const schema = compileSchema( {
|
|
|
303
305
|
},
|
|
304
306
|
// type properties (except: elements, enum, keys, on): ---------------------
|
|
305
307
|
type: {
|
|
306
|
-
type:
|
|
308
|
+
type: typeArtifactRef,
|
|
307
309
|
msgVariant: 'or-object', // for 'syntax-expecting-string',
|
|
308
310
|
optional: [ 'ref', 'global' ],
|
|
309
311
|
inKind: [ 'element', 'type', 'param', 'mixin', 'event', 'annotation', 'extend' ],
|
|
312
|
+
schema: {
|
|
313
|
+
ref: {
|
|
314
|
+
arrayOf: typeRefItem,
|
|
315
|
+
type: renameTo( 'path', typeRef ),
|
|
316
|
+
minLength: 1,
|
|
317
|
+
requires: 'id',
|
|
318
|
+
optional: [ 'id' ],
|
|
319
|
+
ignoreExtra: true, // custom properties inside `ref` ignored.
|
|
320
|
+
},
|
|
321
|
+
},
|
|
310
322
|
},
|
|
311
323
|
targetAspect: {
|
|
312
324
|
type: artifactRef,
|
|
@@ -429,6 +441,7 @@ const schema = compileSchema( {
|
|
|
429
441
|
list: {
|
|
430
442
|
class: 'condition',
|
|
431
443
|
type: list,
|
|
444
|
+
inKind: [ '$column' ],
|
|
432
445
|
},
|
|
433
446
|
val: {
|
|
434
447
|
type: value,
|
|
@@ -604,7 +617,7 @@ const schema = compileSchema( {
|
|
|
604
617
|
optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args' ],
|
|
605
618
|
schema: {
|
|
606
619
|
'=': {
|
|
607
|
-
type:
|
|
620
|
+
type: renameTo( '$tokenTexts', string ),
|
|
608
621
|
xorGroups: null, // reset xorGroup; allow '=' for all :expr
|
|
609
622
|
},
|
|
610
623
|
},
|
|
@@ -625,7 +638,7 @@ const schema = compileSchema( {
|
|
|
625
638
|
},
|
|
626
639
|
notNull: {
|
|
627
640
|
type: boolOrNull,
|
|
628
|
-
inKind: [ 'element', 'param' ], // TODO: $column - or if so: in 'cast'?
|
|
641
|
+
inKind: [ 'element', 'param', 'type' ], // TODO: $column - or if so: in 'cast'?
|
|
629
642
|
},
|
|
630
643
|
virtual: {
|
|
631
644
|
type: boolOrNull,
|
|
@@ -1133,6 +1146,14 @@ function validKind( val, spec, xsn ) {
|
|
|
1133
1146
|
return ignore( val );
|
|
1134
1147
|
}
|
|
1135
1148
|
|
|
1149
|
+
function typeArtifactRef( ref, spec ) {
|
|
1150
|
+
if (ref && typeof ref === 'object' && !Array.isArray( ref )) {
|
|
1151
|
+
if (ref.ref?.length === 1)
|
|
1152
|
+
return artifactRef( ref, { ...spec, ignoreExtra: true } );
|
|
1153
|
+
}
|
|
1154
|
+
return artifactRef( ref, spec );
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1136
1157
|
// Use with spec.msgVariant: 'or-object'
|
|
1137
1158
|
function artifactRef( ref, spec ) {
|
|
1138
1159
|
if (!ref || typeof ref !== 'string') {
|
|
@@ -1253,6 +1274,32 @@ function symbol( id, spec, xsn ) { // for CSN property '#'
|
|
|
1253
1274
|
xsn.sym = { id, location: location() };
|
|
1254
1275
|
}
|
|
1255
1276
|
|
|
1277
|
+
/**
|
|
1278
|
+
* Wrapper around the default `ref` spec: Don't allow references of length 1 for types.
|
|
1279
|
+
*/
|
|
1280
|
+
function typeRef( val, spec, xsn, csn ) {
|
|
1281
|
+
// e.g. { ref: [ 'T' ] }
|
|
1282
|
+
if (Array.isArray(val) && val.length <= 1)
|
|
1283
|
+
warning( 'syntax-deprecated-type-ref', location(true), { '#': 'std', prop: 'type' });
|
|
1284
|
+
|
|
1285
|
+
return arrayOf(spec.arrayOf)(val, spec, xsn, csn);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Similar to refItem(), but warns that the item should be a string if `id` is the only CSN
|
|
1290
|
+
* property inside the ref-item.
|
|
1291
|
+
*/
|
|
1292
|
+
function typeRefItem( val, spec, xsn, csn ) {
|
|
1293
|
+
// e.g. [ 'T', { id: 'elem', other_prop: true } ]
|
|
1294
|
+
// avoid duplicate messages for single-item reference, see typeRef()
|
|
1295
|
+
if (val && csn.ref?.length > 1 && typeof val === 'object' && val.id) {
|
|
1296
|
+
const ownKeysCount = Object.keys(val).filter(key => ourpropsRegex.test(key)).length;
|
|
1297
|
+
if (ownKeysCount === 1)
|
|
1298
|
+
warning('syntax-deprecated-type-ref', location(true), { '#': 'ref-item', prop: 'ref[]' });
|
|
1299
|
+
}
|
|
1300
|
+
return refItem(val, spec);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1256
1303
|
/**
|
|
1257
1304
|
* returns:
|
|
1258
1305
|
* - false = no "...",
|
|
@@ -1311,7 +1358,7 @@ function annoValue( val, spec ) {
|
|
|
1311
1358
|
if (valKeys.length > 1 && isBetaEnabled(userOptions, 'annotationExpressions')) {
|
|
1312
1359
|
const s = schema['@'].schema['-expr'];
|
|
1313
1360
|
const r = { location: location() };
|
|
1314
|
-
r
|
|
1361
|
+
Object.assign(r, object(val, s));
|
|
1315
1362
|
return r;
|
|
1316
1363
|
}
|
|
1317
1364
|
else if (valKeys.length === 1) {
|
|
@@ -1643,8 +1690,11 @@ function getSpec( parentSpec, csn, prop, xor, expected, kind ) {
|
|
|
1643
1690
|
const p0 = schema[prop] ? prop : prop.charAt(0);
|
|
1644
1691
|
const s = (parentSpec.schema || schema)[p0];
|
|
1645
1692
|
if (!s || s.noPrefix === (prop !== p0) ) {
|
|
1646
|
-
if (prop && !ourpropsRegex.test( prop ))
|
|
1693
|
+
if (prop && !ourpropsRegex.test( prop )) {
|
|
1694
|
+
if (parentSpec.ignoreExtra)
|
|
1695
|
+
return { prop, type: ignore };
|
|
1647
1696
|
return { prop, type: extra };
|
|
1697
|
+
}
|
|
1648
1698
|
// TODO v4: No warning with --sloppy
|
|
1649
1699
|
warning( 'syntax-unknown-property', location(true), { prop },
|
|
1650
1700
|
'Unknown CSN property $(PROP)' );
|
package/lib/json/to-csn.js
CHANGED
|
@@ -400,109 +400,19 @@ function extensions( node, csn, model ) {
|
|
|
400
400
|
if (model.kind && model.kind !== 'source')
|
|
401
401
|
return undefined;
|
|
402
402
|
const exts = node.map( definition );
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const art = model.definitions[name];
|
|
407
|
-
|
|
408
|
-
// For namespaces and builtins: Extract annotations since they cannot be represented
|
|
409
|
-
// in CSN. For all other artifacts, check whether they may be auto-exposed,
|
|
410
|
-
// $inferred, etc. and extract their annotations.
|
|
411
|
-
// In parseCdl mode extensions were already put into "extensions".
|
|
412
|
-
if (!model.options.parseCdl && (art.kind === 'namespace' || art.builtin)) {
|
|
413
|
-
extractAnnotationsToExtension( art );
|
|
414
|
-
}
|
|
415
|
-
else if (gensrcFlavor) {
|
|
403
|
+
if (gensrcFlavor) {
|
|
404
|
+
for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
|
|
405
|
+
const art = model.definitions[name];
|
|
416
406
|
// From definitions (without redefinitions) with potential inferred elements:
|
|
417
407
|
const result = { annotate: Object.create(null) };
|
|
418
|
-
attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
|
|
408
|
+
attachAnnotations( result, 'annotate', { [name]: art }, art.$inferred );
|
|
419
409
|
if (result.annotate[name])
|
|
420
|
-
exts.push({ annotate: name, ...result.annotate[name] } );
|
|
410
|
+
exts.push( { annotate: name, ...result.annotate[name] } );
|
|
421
411
|
}
|
|
422
412
|
}
|
|
423
|
-
|
|
424
|
-
return exts.sort(
|
|
413
|
+
return exts.sort( // TODO: really sort with parse.cdl?
|
|
425
414
|
(a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
|
|
426
415
|
);
|
|
427
|
-
|
|
428
|
-
/*
|
|
429
|
-
function attachElementAnnos( annotate, art ) {
|
|
430
|
-
while (art.items)
|
|
431
|
-
art = art.items;
|
|
432
|
-
if (art.elements) {
|
|
433
|
-
const elems = inferred( art.elements, art.$inferred );
|
|
434
|
-
if (Object.keys( elems ).length)
|
|
435
|
-
annotate.elements = elems;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function attachParamAnnos( annotate, art ) {
|
|
440
|
-
const inferredParent = art.$inferred;
|
|
441
|
-
if (art.params) {
|
|
442
|
-
const ext = Object.create( dictionaryPrototype );
|
|
443
|
-
for (const name in art.params) {
|
|
444
|
-
const par = art.params[name];
|
|
445
|
-
if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
|
|
446
|
-
continue;
|
|
447
|
-
const render = annotationsAndDocComment( par );
|
|
448
|
-
const subElems = par.$expand !== 'origin' && (par.items || par).elements;
|
|
449
|
-
if (subElems) {
|
|
450
|
-
const sub = inferred( subElems, par.$inferred );
|
|
451
|
-
if (Object.keys( sub ).length)
|
|
452
|
-
render.elements = sub;
|
|
453
|
-
}
|
|
454
|
-
if (Object.keys(render).length)
|
|
455
|
-
ext[name] = render;
|
|
456
|
-
}
|
|
457
|
-
if (obj.keys( ext ))
|
|
458
|
-
annotate.params = ext;
|
|
459
|
-
}
|
|
460
|
-
if (art.returns) {
|
|
461
|
-
const par = art.returns;
|
|
462
|
-
if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
|
|
463
|
-
return;
|
|
464
|
-
const render = annotationsAndDocComment( par );
|
|
465
|
-
const subElems = par.$expand !== 'origin' && (par.items || par).elements;
|
|
466
|
-
if (subElems) {
|
|
467
|
-
const sub = inferred( subElems, par.$inferred );
|
|
468
|
-
if (Object.keys( sub ).length)
|
|
469
|
-
render.elements = sub;
|
|
470
|
-
}
|
|
471
|
-
if (Object.keys(render).length)
|
|
472
|
-
const sub = inferred( subElems, par.$inferred );
|
|
473
|
-
if (Object.keys( sub ).length)
|
|
474
|
-
render.elements = sub;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return ext;
|
|
478
|
-
*/
|
|
479
|
-
|
|
480
|
-
// extract namespace/builtin annotations
|
|
481
|
-
function extractAnnotationsToExtension( art ) {
|
|
482
|
-
const name = art.name.absolute;
|
|
483
|
-
// 'true' because annotations on namespaces and builtins can only
|
|
484
|
-
// happen through extensions.
|
|
485
|
-
const annos = annotationsAndDocComment( art );
|
|
486
|
-
const annotate = Object.assign( { annotate: name }, annos );
|
|
487
|
-
if (Object.keys( annotate ).length > 1) {
|
|
488
|
-
const loc = locationForAnnotationExtension();
|
|
489
|
-
if (loc)
|
|
490
|
-
location( loc, annotate, art );
|
|
491
|
-
exts.push( annotate );
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Either the artifact's name's location or (for builtin types) the location
|
|
495
|
-
// of its first annotation.
|
|
496
|
-
function locationForAnnotationExtension() {
|
|
497
|
-
if (art.location)
|
|
498
|
-
return art.location;
|
|
499
|
-
for (const key in art) {
|
|
500
|
-
if (key.charAt(0) === '@' && art[key].name)
|
|
501
|
-
return art[key].name.location;
|
|
502
|
-
}
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
416
|
}
|
|
507
417
|
|
|
508
418
|
/**
|
|
@@ -540,7 +450,10 @@ function sources( srcDict, csn ) {
|
|
|
540
450
|
|
|
541
451
|
function attachAnnotations( annotate, prop, dict, inferred, insideReturns = false ) {
|
|
542
452
|
const annoDict = Object.create( dictionaryPrototype );
|
|
543
|
-
|
|
453
|
+
const names = Object.keys( dict );
|
|
454
|
+
if (strictMode)
|
|
455
|
+
names.sort();
|
|
456
|
+
for (const name of names) {
|
|
544
457
|
const entry = dict[name];
|
|
545
458
|
const inf = inferred || entry.$inferred; // is probably always inferred if parent was
|
|
546
459
|
const sub = (inf) ? annotationsAndDocComment( entry ) : {};
|
|
@@ -549,7 +462,7 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
|
|
|
549
462
|
attachAnnotations( sub, 'actions', entry.actions, inf );
|
|
550
463
|
else if (entry.params)
|
|
551
464
|
attachAnnotations( sub, 'params', entry.params, inf );
|
|
552
|
-
const obj = entry.returns || entry;
|
|
465
|
+
const obj = entry.returns || entry; // TODO: create returns !
|
|
553
466
|
const many = obj.items || obj;
|
|
554
467
|
const elems = (many.targetAspect || many).elements;
|
|
555
468
|
if (elems)
|
|
@@ -649,9 +562,11 @@ function elements( dict, csn, node ) {
|
|
|
649
562
|
// no 'elements' with SELECT or inferred elements with gensrc;
|
|
650
563
|
// hidden or visible 'elements' will be set in query()
|
|
651
564
|
return undefined;
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
565
|
+
// TODO(!): inside `annotate`, use sorted with --test-mode
|
|
566
|
+
if (dict === 0)
|
|
567
|
+
return undefined;
|
|
568
|
+
// In "super annotate" statements, use sorted dictionary
|
|
569
|
+
return (node.$inferred === '') ? sortedDict( dict ) : insertOrderDict( dict );
|
|
655
570
|
}
|
|
656
571
|
|
|
657
572
|
function enumDict( dict, csn, node ) {
|
|
@@ -766,7 +681,7 @@ function location( loc, csn, xsn ) {
|
|
|
766
681
|
* @param {object} csn
|
|
767
682
|
*/
|
|
768
683
|
function addLocation( loc, csn ) {
|
|
769
|
-
if (loc) {
|
|
684
|
+
if (loc?.file) {
|
|
770
685
|
// Remove endLine/endCol:
|
|
771
686
|
// Reasoning: $location is mostly attached to definitions/members but the name
|
|
772
687
|
// is often not the reason for an error or warning. So we gain little benefit for
|
|
@@ -791,8 +706,10 @@ function sortedDict( dict ) {
|
|
|
791
706
|
return dictionary( dict, keys );
|
|
792
707
|
}
|
|
793
708
|
|
|
794
|
-
function actions( dict ) {
|
|
709
|
+
function actions( dict, _csn, node ) {
|
|
795
710
|
const keys = Object.keys( dict );
|
|
711
|
+
if (strictMode && node.kind === 'annotate')
|
|
712
|
+
keys.sort(); // TODO: always sort with --test-mode ?
|
|
796
713
|
return (keys.length)
|
|
797
714
|
? dictionary( dict, keys, 'actions' )
|
|
798
715
|
: undefined;
|
|
@@ -900,6 +817,25 @@ function includesOrigin( includes, art ) {
|
|
|
900
817
|
return (Object.keys( result ).length === 1) ? $origin : result;
|
|
901
818
|
}
|
|
902
819
|
|
|
820
|
+
/**
|
|
821
|
+
* Calculated elements via `includes` can inherit annotations from sibling elements.
|
|
822
|
+
* These annotations need to be put into `$origin`, because `$origin` points to
|
|
823
|
+
* the calculated element, not the simple ref's artifact.
|
|
824
|
+
*/
|
|
825
|
+
function calculatedElementOrigin( csn, xsn, origin ) {
|
|
826
|
+
const $origin = originRef( origin );
|
|
827
|
+
const result = { $origin };
|
|
828
|
+
for (const prop in xsn) {
|
|
829
|
+
if ((prop.charAt(0) === '@' || prop === 'doc') && !origin[prop] && xsn[prop].$inferred) {
|
|
830
|
+
const annoVal = xsn[prop];
|
|
831
|
+
if (annoVal.val !== null)
|
|
832
|
+
// materialize non-null annos (whether direct or inherited)
|
|
833
|
+
result[prop] = value( Object.create( annoVal, { $inferred: { value: null } } ) );
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return (Object.keys( result ).length === 1) ? undefined : result;
|
|
837
|
+
}
|
|
838
|
+
|
|
903
839
|
function addOrigin( csn, xsn, node ) {
|
|
904
840
|
if (!universalCsn)
|
|
905
841
|
return;
|
|
@@ -945,7 +881,7 @@ function addOrigin( csn, xsn, node ) {
|
|
|
945
881
|
}
|
|
946
882
|
// from here on: member:
|
|
947
883
|
// TODO: write a xsnNode._csnOrigin, which is useful to decide whether to write
|
|
948
|
-
//
|
|
884
|
+
// $origins for its members
|
|
949
885
|
const parent = getParent( xsn );
|
|
950
886
|
const parentOrigin = getOrigin( parent );
|
|
951
887
|
// console.log( 'X:',xsn, origin, parent, parentOrigin, getParent( origin ) );
|
|
@@ -960,8 +896,16 @@ function addOrigin( csn, xsn, node ) {
|
|
|
960
896
|
parentOrigin === getParent( origin )) {
|
|
961
897
|
// implicit prototype or shortened reference
|
|
962
898
|
const { id } = origin.name || {};
|
|
963
|
-
|
|
899
|
+
|
|
900
|
+
if (id && xsn.name && id !== xsn.name.id) {
|
|
964
901
|
csn.$origin = id;
|
|
902
|
+
}
|
|
903
|
+
else if (xsn._calcOrigin) {
|
|
904
|
+
const calcOrigin = calculatedElementOrigin( csn, xsn, origin );
|
|
905
|
+
if (calcOrigin)
|
|
906
|
+
csn.$origin = calcOrigin;
|
|
907
|
+
}
|
|
908
|
+
|
|
965
909
|
return;
|
|
966
910
|
}
|
|
967
911
|
if (origin.kind === 'mixin') {
|
|
@@ -1076,7 +1020,7 @@ function hasExplicitProp( ref, alsoLikeExplicit ) {
|
|
|
1076
1020
|
|
|
1077
1021
|
/**
|
|
1078
1022
|
* @param art
|
|
1079
|
-
* @param user
|
|
1023
|
+
* @param [user]
|
|
1080
1024
|
* @return {boolean|string[]}
|
|
1081
1025
|
*/
|
|
1082
1026
|
function originRef( art, user ) {
|
|
@@ -1255,9 +1199,8 @@ function args( node ) {
|
|
|
1255
1199
|
}
|
|
1256
1200
|
|
|
1257
1201
|
function anno( node ) {
|
|
1258
|
-
if (node
|
|
1259
|
-
|
|
1260
|
-
return Object.assign({ '=': '42' }, expression( node.value ));
|
|
1202
|
+
if (node.$tokenTexts) // expressions in annotation values
|
|
1203
|
+
return Object.assign({ '=': node.$tokenTexts }, expression( node ));
|
|
1261
1204
|
return value(node);
|
|
1262
1205
|
}
|
|
1263
1206
|
|
|
@@ -1274,6 +1217,8 @@ function value( node ) {
|
|
|
1274
1217
|
}
|
|
1275
1218
|
if (node.$inferred && gensrcFlavor)
|
|
1276
1219
|
return undefined;
|
|
1220
|
+
if (node.$tokenTexts)
|
|
1221
|
+
return Object.assign({ '=': node.$tokenTexts }, expression( node ));
|
|
1277
1222
|
if (node.path) {
|
|
1278
1223
|
const ref = pathName( node.path );
|
|
1279
1224
|
return extra( { '=': node.variant ? `${ ref }#${ pathName(node.variant.path) }` : ref }, node );
|
|
@@ -1374,6 +1319,8 @@ function exprInternal( node, xprParens ) {
|
|
|
1374
1319
|
case 'ixpr':
|
|
1375
1320
|
case 'xpr':
|
|
1376
1321
|
break;
|
|
1322
|
+
case '?:':
|
|
1323
|
+
return ternaryOperator( node );
|
|
1377
1324
|
case 'cast':
|
|
1378
1325
|
return cast( expression( node.args[0] ), node );
|
|
1379
1326
|
case 'list':
|
|
@@ -1410,6 +1357,20 @@ function flattenInternalXpr( array, op ) {
|
|
|
1410
1357
|
return left;
|
|
1411
1358
|
}
|
|
1412
1359
|
|
|
1360
|
+
function ternaryOperator( node ) {
|
|
1361
|
+
const rargs = [
|
|
1362
|
+
'case',
|
|
1363
|
+
'when', exprInternal(node.args[0]),
|
|
1364
|
+
'then', exprInternal(node.args[2]),
|
|
1365
|
+
'else', exprInternal(node.args[4]),
|
|
1366
|
+
'end',
|
|
1367
|
+
];
|
|
1368
|
+
|
|
1369
|
+
if (node.$parens?.length)
|
|
1370
|
+
return { xpr: flattenInternalXpr( rargs, 'xpr' ) };
|
|
1371
|
+
return flattenInternalXpr( rargs, 'xpr' );
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1413
1374
|
function query( node, csn, xsn, _prop, expectedParens = 0 ) {
|
|
1414
1375
|
if (node.op.val === 'SELECT') {
|
|
1415
1376
|
if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&
|
|
@@ -1579,7 +1540,7 @@ function setHidden( obj, prop, val ) {
|
|
|
1579
1540
|
}
|
|
1580
1541
|
|
|
1581
1542
|
function addExplicitAs( node, name, implicit ) {
|
|
1582
|
-
if (name && name
|
|
1543
|
+
if (name?.id && name.$inferred !== '$internal' &&
|
|
1583
1544
|
(!name.$inferred || !node.ref && !node.func || implicit && implicit(name.id) ))
|
|
1584
1545
|
node.as = name.id;
|
|
1585
1546
|
return node;
|
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
const { pathName } = require('../compiler/utils');
|
|
21
21
|
const { isBetaEnabled } = require('../base/model');
|
|
22
22
|
const { weakLocation } = require('../base/messages');
|
|
23
|
+
const { normalizeNewLine } = require('./textUtils');
|
|
23
24
|
|
|
24
25
|
const $location = Symbol.for('cds.$location');
|
|
25
26
|
|
|
@@ -78,6 +79,7 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
78
79
|
attachLocation,
|
|
79
80
|
assignAnnotation,
|
|
80
81
|
addAnnotation,
|
|
82
|
+
expressionAsAnnotationValue,
|
|
81
83
|
checkExtensionDict,
|
|
82
84
|
handleDuplicateExtension,
|
|
83
85
|
startLocation,
|
|
@@ -88,6 +90,7 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
88
90
|
previousTokenAtLocation,
|
|
89
91
|
combinedLocation,
|
|
90
92
|
surroundByParens,
|
|
93
|
+
tokensToStringRepresentation,
|
|
91
94
|
secureParens,
|
|
92
95
|
unaryOpForParens,
|
|
93
96
|
leftAssocBinaryOp,
|
|
@@ -526,18 +529,24 @@ function fixMultiLineTokenEndLocation( token, location ) {
|
|
|
526
529
|
}
|
|
527
530
|
|
|
528
531
|
/**
|
|
529
|
-
* Return `val` with
|
|
530
|
-
*
|
|
532
|
+
* Return `val` with a location; if `val` and `endToken` are not provided, use the
|
|
533
|
+
* lower-cased token string of `startToken` as `val`. As location, use the
|
|
534
|
+
* location covered by `startToken` and `endToken`, or only `startToken` if no
|
|
535
|
+
* `endToken` is provided. The `startToken` defaults to the previous token.
|
|
531
536
|
*
|
|
532
537
|
* @param {object} startToken
|
|
533
538
|
* @param {object} endToken
|
|
534
539
|
* @param {any} val
|
|
535
540
|
*/
|
|
536
|
-
function valueWithTokenLocation( val, startToken
|
|
537
|
-
|
|
538
|
-
|
|
541
|
+
function valueWithTokenLocation( val = undefined, startToken = this._input.LT(-1),
|
|
542
|
+
endToken = undefined ) {
|
|
543
|
+
// if (!startToken)
|
|
544
|
+
// startToken = this._input.LT(-1);
|
|
539
545
|
const loc = this.tokenLocation( startToken, endToken );
|
|
540
|
-
return {
|
|
546
|
+
return {
|
|
547
|
+
location: loc,
|
|
548
|
+
val: (endToken || val !== undefined) ? val : startToken.text.toLowerCase(),
|
|
549
|
+
};
|
|
541
550
|
}
|
|
542
551
|
|
|
543
552
|
function previousTokenAtLocation( location ) {
|
|
@@ -585,6 +594,23 @@ function surroundByParens( expr, open, close, asQuery = false ) {
|
|
|
585
594
|
return (asQuery) ? { query: expr, location } : expr;
|
|
586
595
|
}
|
|
587
596
|
|
|
597
|
+
|
|
598
|
+
function tokensToStringRepresentation( matchedRule ) {
|
|
599
|
+
const tokens = this._input.getTokens(
|
|
600
|
+
matchedRule.start.tokenIndex,
|
|
601
|
+
matchedRule.stop.tokenIndex + 1, null
|
|
602
|
+
).filter(tok => tok.channel === antlr4.Token.DEFAULT_CHANNEL);
|
|
603
|
+
if (tokens.length === 0)
|
|
604
|
+
return '';
|
|
605
|
+
|
|
606
|
+
let result = tokens[0].text;
|
|
607
|
+
for (let i = 1; i < tokens.length; ++i) {
|
|
608
|
+
const str = normalizeNewLine(tokens[i].text);
|
|
609
|
+
result += (tokens[i].start > tokens[i - 1].stop + 1) ? ` ${ str }` : str;
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
|
|
588
614
|
function unaryOpForParens( query, val ) {
|
|
589
615
|
const parens = query?.$parens;
|
|
590
616
|
if (!parens)
|
|
@@ -697,8 +723,11 @@ function argsExpression( args, nary, location ) {
|
|
|
697
723
|
location: undefined,
|
|
698
724
|
} );
|
|
699
725
|
}
|
|
726
|
+
// eslint-disable-next-line no-nested-ternary
|
|
727
|
+
const val = nary === '?:' ? nary
|
|
728
|
+
: (nary && nary !== '=' ? 'nary' : 'ixpr');
|
|
700
729
|
const op = {
|
|
701
|
-
val
|
|
730
|
+
val, // there is no n-ary in rule conditionTerm
|
|
702
731
|
location: this.startLocation(),
|
|
703
732
|
};
|
|
704
733
|
return this.attachLocation( { op, args, location: location && { ...location } } );
|
|
@@ -814,6 +843,16 @@ function fixNewKeywordPlacement( args ) {
|
|
|
814
843
|
args.push(ixpr);
|
|
815
844
|
}
|
|
816
845
|
|
|
846
|
+
function expressionAsAnnotationValue( assignment, cond ) {
|
|
847
|
+
if (!cond.cond) // parse error
|
|
848
|
+
return;
|
|
849
|
+
Object.assign(assignment, cond.cond);
|
|
850
|
+
assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
|
|
851
|
+
if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
|
|
852
|
+
this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
|
|
853
|
+
'Expressions in annotation values are not supported' );
|
|
854
|
+
}
|
|
855
|
+
}
|
|
817
856
|
|
|
818
857
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
819
858
|
// otherwise (including for '+'), represent it as extra unary prefix operator.
|
|
@@ -1063,7 +1102,7 @@ function aspectWithoutElements( art ) {
|
|
|
1063
1102
|
}
|
|
1064
1103
|
}
|
|
1065
1104
|
|
|
1066
|
-
// must be in action directly after having parsed '{' or
|
|
1105
|
+
// must be in action directly after having parsed '{', '(`, or a keyword before
|
|
1067
1106
|
function createDict() {
|
|
1068
1107
|
const dict = Object.create(null);
|
|
1069
1108
|
dict[$location] = this.startLocation( this._input.LT(-1) );
|