@sap/cds-compiler 3.9.4 → 4.0.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 +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/compiler/extend.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
const { searchName, weakLocation } = require('../base/messages');
|
|
6
6
|
const {
|
|
7
|
-
forEachInOrder,
|
|
7
|
+
forEachInOrder,
|
|
8
|
+
forEachDefinition,
|
|
8
9
|
forEachMember,
|
|
9
|
-
|
|
10
|
+
isDeprecatedEnabled,
|
|
10
11
|
} = require('../base/model');
|
|
11
12
|
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
12
13
|
const { kindProperties, dictKinds } = require('./base');
|
|
@@ -21,6 +22,7 @@ const {
|
|
|
21
22
|
annotationHasEllipsis,
|
|
22
23
|
} = require('./utils');
|
|
23
24
|
const layers = require('./moduleLayers');
|
|
25
|
+
const { CompilerAssertion } = require('../base/error');
|
|
24
26
|
|
|
25
27
|
const $location = Symbol.for('cds.$location');
|
|
26
28
|
|
|
@@ -29,7 +31,6 @@ const genLocation = { file: '' }; // attach stupid location - TODO: remove in v4
|
|
|
29
31
|
// Array.prototype.spread = 42; // prototype-polluted JS classes
|
|
30
32
|
|
|
31
33
|
function extend( model ) {
|
|
32
|
-
const { options } = model;
|
|
33
34
|
// Get simplified "resolve" functionality and the message function:
|
|
34
35
|
const {
|
|
35
36
|
message, error, warning, info,
|
|
@@ -49,6 +50,8 @@ function extend( model ) {
|
|
|
49
50
|
applyIncludes, // TODO: re-check
|
|
50
51
|
} );
|
|
51
52
|
|
|
53
|
+
const includesNonShadowedFirst = isDeprecatedEnabled(model.options, 'includesNonShadowedFirst');
|
|
54
|
+
|
|
52
55
|
sortModelSources();
|
|
53
56
|
const extensionsDict = Object.create(null); // TODO TMP
|
|
54
57
|
forEachDefinition( model, tagIncludes ); // TODO TMP
|
|
@@ -78,8 +81,8 @@ function extend( model ) {
|
|
|
78
81
|
function extendArtifactBefore( art ) {
|
|
79
82
|
// for main artifacts, move extensions from `$collectedExtensions` model dictionary:
|
|
80
83
|
if (!art._main && !art._outer && art._extensions === undefined &&
|
|
84
|
+
art.name && // TODO: probably just a workaround, check with TODO in getOriginRaw()
|
|
81
85
|
art.kind !== 'namespace') {
|
|
82
|
-
// if (!art.name) console.log(art)
|
|
83
86
|
const { absolute } = art.name;
|
|
84
87
|
setLink( art, '_extensions', model.$collectedExtensions[absolute]?._extensions || null );
|
|
85
88
|
if (art._extensions && !art.builtin) { // keep extensions for builtin in $collectedExtensions
|
|
@@ -123,16 +126,16 @@ function extend( model ) {
|
|
|
123
126
|
const scaleDiff = applyTypeExtensions( art, exts.scale, 'scale' );
|
|
124
127
|
applyTypeExtensions( art, exts.precision, 'precision', scaleDiff );
|
|
125
128
|
applyTypeExtensions( art, exts.srid, 'srid' );
|
|
129
|
+
checkPrecisionScaleExtension( art, exts );
|
|
130
|
+
|
|
126
131
|
delete art.$typeExts;
|
|
127
132
|
}
|
|
128
|
-
|
|
129
|
-
if (art.kind === 'annotate' && !art.returns &&
|
|
130
|
-
(extensionsMap.elements?.some( e => e.$syntax === 'returns' ) ||
|
|
131
|
-
extensionsMap.enum?.some( e => e.$syntax === 'returns' )))
|
|
133
|
+
|
|
134
|
+
if (art.kind === 'annotate' && !art.returns && extensionsMap.returns)
|
|
132
135
|
annotateCreate( art, '', art, 'returns' );
|
|
133
136
|
|
|
134
137
|
moveDictExtensions( art, extensionsMap, 'params' );
|
|
135
|
-
|
|
138
|
+
moveReturnsExtensions( art, extensionsMap );
|
|
136
139
|
const sub = art.returns || art.items || art.targetAspect?.elements && art.targetAspect;
|
|
137
140
|
if (sub) {
|
|
138
141
|
if (art.returns) { // after having applied params!
|
|
@@ -140,8 +143,8 @@ function extend( model ) {
|
|
|
140
143
|
extendHandleReturns( extensionsMap.enum, art );
|
|
141
144
|
}
|
|
142
145
|
// care about 'ext-unexpected-returns' in a later change if we have XSN returns
|
|
143
|
-
pushToDict( sub, '_extensions', ...extensionsMap
|
|
144
|
-
pushToDict( sub, '_extensions', ...extensionsMap
|
|
146
|
+
pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'elements'));
|
|
147
|
+
pushToDict( sub, '_extensions', ...avoidRecursiveReturns(extensionsMap, 'enum') );
|
|
145
148
|
}
|
|
146
149
|
else {
|
|
147
150
|
moveDictExtensions( art, extensionsMap,
|
|
@@ -151,6 +154,25 @@ function extend( model ) {
|
|
|
151
154
|
moveDictExtensions( art, extensionsMap, 'actions' );
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
/**
|
|
158
|
+
* FIXME: Remove this workaround. This workaround avoids endless recursion for annotate statements
|
|
159
|
+
* that have both "returns" and "elements". The endless recursion happens due to the
|
|
160
|
+
* pushDict on `sub`. The `elements` extensions will point to the same extension on which
|
|
161
|
+
* `returns` exist.
|
|
162
|
+
*
|
|
163
|
+
* @param extensionsMap
|
|
164
|
+
* @param {string} prop
|
|
165
|
+
* @return {XSN.Extension[]}
|
|
166
|
+
*/
|
|
167
|
+
function avoidRecursiveReturns( extensionsMap, prop ) {
|
|
168
|
+
if (!extensionsMap[prop])
|
|
169
|
+
return [];
|
|
170
|
+
const exts = [];
|
|
171
|
+
for (const ext of extensionsMap[prop])
|
|
172
|
+
exts.push({ ...ext, returns: undefined });
|
|
173
|
+
return exts;
|
|
174
|
+
}
|
|
175
|
+
|
|
154
176
|
/**
|
|
155
177
|
* Create super annotate statements for remaining extensions
|
|
156
178
|
*/
|
|
@@ -165,14 +187,16 @@ function extend( model ) {
|
|
|
165
187
|
Object.values( model.definitions ).forEach( setArtifactLinkForExtensions );
|
|
166
188
|
}
|
|
167
189
|
|
|
168
|
-
// TODO: delete again
|
|
190
|
+
// TODO: delete again - if not, what about extensions in contexts/services?
|
|
169
191
|
function setArtifactLinkForExtensions( source ) {
|
|
170
192
|
if (!source.extensions)
|
|
171
193
|
return;
|
|
172
194
|
for (const ext of source.extensions ) {
|
|
173
195
|
const { name } = ext;
|
|
174
|
-
if (name?.absolute && name._artifact === undefined)
|
|
175
|
-
|
|
196
|
+
if (name?.absolute && name._artifact === undefined) {
|
|
197
|
+
const refCtx = (name.absolute.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
|
|
198
|
+
resolvePath( name, refCtx, ext ); // for LSP
|
|
199
|
+
}
|
|
176
200
|
}
|
|
177
201
|
}
|
|
178
202
|
|
|
@@ -210,7 +234,7 @@ function extend( model ) {
|
|
|
210
234
|
const dict = Object.create(null);
|
|
211
235
|
for (const ext of art._extensions) {
|
|
212
236
|
for (const prop in ext) {
|
|
213
|
-
if (ext[prop] === undefined) // deleted
|
|
237
|
+
if (ext[prop] === undefined) // deleted property
|
|
214
238
|
continue;
|
|
215
239
|
// TODO: do this check nicer (after complete move to new extensions mechanism)
|
|
216
240
|
if (prop.charAt(0) === '@' || prop === 'doc' ||
|
|
@@ -357,6 +381,8 @@ function extend( model ) {
|
|
|
357
381
|
}
|
|
358
382
|
else if (prop === 'columns') {
|
|
359
383
|
const { query } = art;
|
|
384
|
+
for (const col of ext.columns)
|
|
385
|
+
col.$extended = true;
|
|
360
386
|
if (!query?.from?.path)
|
|
361
387
|
error( 'extend-columns', [ ext.columns[$location], ext ], { art } );
|
|
362
388
|
else if (!query.columns)
|
|
@@ -532,6 +558,24 @@ function extend( model ) {
|
|
|
532
558
|
return 0;
|
|
533
559
|
}
|
|
534
560
|
|
|
561
|
+
/**
|
|
562
|
+
* If the target artifact has both precision and scale set, then extensions on it must also
|
|
563
|
+
* provide both to avoid user errors for subsequent `extend` statements.
|
|
564
|
+
*
|
|
565
|
+
* @param {XSN.Artifact} art
|
|
566
|
+
* @param {object} exts
|
|
567
|
+
*/
|
|
568
|
+
function checkPrecisionScaleExtension( art, exts ) {
|
|
569
|
+
if (art.precision && art.scale) {
|
|
570
|
+
if ((exts.precision || exts.scale) && !(exts.precision && exts.scale)) {
|
|
571
|
+
const missing = exts.precision ? 'scale' : 'precision';
|
|
572
|
+
const prop = exts.precision ? 'precision' : 'scale';
|
|
573
|
+
error( 'ext-missing-type-property', [ exts[prop].location, exts[prop] ],
|
|
574
|
+
{ art, prop, otherprop: missing } );
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
535
579
|
function allowsTypeArgument( art, prop ) {
|
|
536
580
|
const { parameters } = art._effectiveType;
|
|
537
581
|
if (!parameters)
|
|
@@ -548,8 +592,8 @@ function extend( model ) {
|
|
|
548
592
|
|
|
549
593
|
for (const ext of extensions) {
|
|
550
594
|
const extDict = ext[extProp];
|
|
595
|
+
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
551
596
|
for (const name in extDict) {
|
|
552
|
-
let dictCheck = (art.kind !== 'annotate'); // no check in super annotate statement
|
|
553
597
|
const elemExt = extDict[name];
|
|
554
598
|
if (elemExt.kind !== 'annotate' && elemExt.kind !== 'extend') // TODO: specified elems
|
|
555
599
|
continue; // definitions inside extend, already handled
|
|
@@ -561,13 +605,28 @@ function extend( model ) {
|
|
|
561
605
|
}
|
|
562
606
|
}
|
|
563
607
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
608
|
+
function moveReturnsExtensions( art, extensionsMap ) {
|
|
609
|
+
if (!extensionsMap.returns)
|
|
610
|
+
return;
|
|
611
|
+
|
|
612
|
+
for (const ext of extensionsMap.returns) {
|
|
613
|
+
if (!art.returns && art.kind !== 'annotate') { // no check in super annotate statement
|
|
614
|
+
const variant = art.kind === 'action' || art.kind === 'function' ? art.kind : 'std';
|
|
615
|
+
warning( 'ext-unexpected-returns', [ ext.returns.location, ext ], {
|
|
616
|
+
'#': variant, keyword: 'returns',
|
|
617
|
+
}, {
|
|
618
|
+
std: 'Unexpected $(KEYWORD); only actions and functions have return parameters',
|
|
619
|
+
action: 'Unexpected $(KEYWORD) for action without return parameter',
|
|
620
|
+
// function without `returns` can happen via CSN input!
|
|
621
|
+
function: 'Unexpected $(KEYWORD) for function without return parameter',
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
// If `!art.returns`, then it could be CSN from SQL, where actions are replaced by dummies.
|
|
625
|
+
const elem = art.returns || annotateFor( art, 'params', '' );
|
|
626
|
+
setLink( ext.name, '_artifact', (elem.kind !== 'annotate' ? elem : null) );
|
|
627
|
+
pushToDict(elem, '_extensions', ext.returns);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
571
630
|
|
|
572
631
|
function annotateFor( art, prop, name ) {
|
|
573
632
|
const base = annotateBase( art );
|
|
@@ -580,7 +639,7 @@ function extend( model ) {
|
|
|
580
639
|
}
|
|
581
640
|
|
|
582
641
|
function annotateBase( art ) {
|
|
583
|
-
while (art._outer) //
|
|
642
|
+
while (art._outer) // TODO: think about anonymous target aspect
|
|
584
643
|
art = art._outer;
|
|
585
644
|
if (art.kind === 'annotate')
|
|
586
645
|
return art;
|
|
@@ -616,19 +675,13 @@ function extend( model ) {
|
|
|
616
675
|
|
|
617
676
|
function extendHandleReturns( extensions, art ) {
|
|
618
677
|
for (const ext of extensions || []) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}, {
|
|
627
|
-
std: 'Expected $(CODE)', // unused variant
|
|
628
|
-
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
629
|
-
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
630
|
-
} );
|
|
631
|
-
}
|
|
678
|
+
warning( 'ext-expecting-returns', [ ext.name.location, ext ], {
|
|
679
|
+
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
680
|
+
}, {
|
|
681
|
+
std: 'Expected $(CODE)', // unused variant
|
|
682
|
+
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
|
|
683
|
+
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
|
|
684
|
+
} );
|
|
632
685
|
}
|
|
633
686
|
}
|
|
634
687
|
|
|
@@ -666,12 +719,9 @@ function extend( model ) {
|
|
|
666
719
|
warning( 'anno-unexpected-params', [ location, ext._parent ], {},
|
|
667
720
|
'Parameters only exist for actions or functions' );
|
|
668
721
|
break;
|
|
669
|
-
case 'actions':
|
|
670
|
-
warning( 'anno-unexpected-actions', [ location, ext._parent ], {},
|
|
671
|
-
'Actions and functions only exist top-level and for entities' );
|
|
672
|
-
break;
|
|
673
722
|
default:
|
|
674
|
-
|
|
723
|
+
if (model.options.testMode)
|
|
724
|
+
throw new CompilerAssertion(`Missing case for prop: ${ prop }`);
|
|
675
725
|
}
|
|
676
726
|
return false;
|
|
677
727
|
}
|
|
@@ -767,17 +817,12 @@ function extend( model ) {
|
|
|
767
817
|
|
|
768
818
|
// --> without art._block, art not found
|
|
769
819
|
if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
|
|
770
|
-
if (construct
|
|
771
|
-
//
|
|
772
|
-
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
|
|
773
|
-
// `art._block` ensures that `art` is a defined def.
|
|
774
|
-
return;
|
|
775
|
-
// warning('ext-unexpected-returns', [ construct.name.location, construct ],
|
|
776
|
-
// { keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
|
|
820
|
+
if (construct.returns && art.kind !== 'action' && art.kind !== 'function' ) {
|
|
821
|
+
// See moveReturnsExtensions()
|
|
777
822
|
}
|
|
778
|
-
else if (construct
|
|
823
|
+
else if (!construct.returns &&
|
|
779
824
|
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
|
|
780
|
-
warning('ext-
|
|
825
|
+
warning('ext-expecting-returns', [ construct.name.location, construct ], {
|
|
781
826
|
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
|
|
782
827
|
}, {
|
|
783
828
|
std: 'Expected $(CODE)', // unused variant
|
|
@@ -1084,13 +1129,21 @@ function extend( model ) {
|
|
|
1084
1129
|
const parent = ext === art && art;
|
|
1085
1130
|
const members = ext[prop];
|
|
1086
1131
|
ext[prop] = Object.create(null); // TODO: do not set actions property if there are none
|
|
1132
|
+
let hasNewElement = false;
|
|
1133
|
+
|
|
1087
1134
|
for (const ref of ext.includes) {
|
|
1088
1135
|
const template = ref._artifact; // already resolved
|
|
1089
1136
|
if (template) { // be robust
|
|
1137
|
+
// eslint-disable-next-line no-loop-func
|
|
1090
1138
|
forEachInOrder( template, prop, ( origin, name ) => {
|
|
1091
|
-
if (members && name
|
|
1092
|
-
|
|
1139
|
+
if (members && members[name]) {
|
|
1140
|
+
if (!includesNonShadowedFirst && !ext[prop][name])
|
|
1141
|
+
dictAdd( ext[prop], name, members[name] ); // to keep order
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
hasNewElement = true;
|
|
1093
1145
|
const elem = linkToOrigin( origin, name, parent, prop, weakLocation( ref.location ) );
|
|
1146
|
+
setLink( elem, '_block', origin._block );
|
|
1094
1147
|
if (!parent) // not yet set for EXTEND foo WITH bar => linkToOrigin() did not add it
|
|
1095
1148
|
dictAdd( ext[prop], name, elem );
|
|
1096
1149
|
elem.$inferred = 'include';
|
|
@@ -1101,6 +1154,7 @@ function extend( model ) {
|
|
|
1101
1154
|
if (origin.value && origin.$syntax === 'calc') {
|
|
1102
1155
|
// TODO: If paths become invalid in the new artifact, should we mark
|
|
1103
1156
|
// all usages in the expressions? Possibly just the first one?
|
|
1157
|
+
// TODO: Unify with coding in extend.js
|
|
1104
1158
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
1105
1159
|
elem.$syntax = 'calc';
|
|
1106
1160
|
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
@@ -1109,11 +1163,19 @@ function extend( model ) {
|
|
|
1109
1163
|
});
|
|
1110
1164
|
}
|
|
1111
1165
|
}
|
|
1166
|
+
|
|
1112
1167
|
checkRedefinitionThroughIncludes( parent, prop );
|
|
1113
|
-
|
|
1114
|
-
if (members) {
|
|
1168
|
+
|
|
1169
|
+
if (!hasNewElement && members) {
|
|
1170
|
+
ext[prop] = members;
|
|
1171
|
+
}
|
|
1172
|
+
else if (members) {
|
|
1173
|
+
// TODO: expand elements having direct elements (if needed)
|
|
1115
1174
|
forEachInOrder( { [prop]: members }, prop, ( elem, name ) => {
|
|
1116
|
-
|
|
1175
|
+
// The element could have been added in the previous loop (includes) to keep
|
|
1176
|
+
// the element order.
|
|
1177
|
+
if (ext[prop][name] !== elem )
|
|
1178
|
+
dictAdd( ext[prop], name, elem );
|
|
1117
1179
|
});
|
|
1118
1180
|
}
|
|
1119
1181
|
}
|
|
@@ -1130,15 +1192,8 @@ function extend( model ) {
|
|
|
1130
1192
|
forEachInOrder(parent, prop, ( member, name ) => {
|
|
1131
1193
|
if (member.$inferred === 'include' && Array.isArray(member.$duplicates)) {
|
|
1132
1194
|
const includes = [ member, ...member.$duplicates ].map(dup => dup._origin._main);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
// Error accidentally removed in v2/v3, therefore only a warning.
|
|
1139
|
-
warning( 'ref-duplicate-include-member', [ parent.name.location, member ],
|
|
1140
|
-
{ '#': prop, name, sorted_arts: includes } );
|
|
1141
|
-
}
|
|
1195
|
+
error( 'duplicate-definition', [ parent.name.location, member ],
|
|
1196
|
+
{ '#': `include-${ prop }`, name, sorted_arts: includes } );
|
|
1142
1197
|
}
|
|
1143
1198
|
});
|
|
1144
1199
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
+
const { CompilerAssertion } = require('../base/error');
|
|
16
17
|
const { forEachGeneric } = require('../base/model');
|
|
17
|
-
const { setLink, setArtifactLink } = require('./utils');
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
// Used for resolving types in parseCdl mode.
|
|
@@ -24,7 +24,6 @@ const parseCdlIgnoredXsnProps = [ 'location', 'query', '$tableAliases' ];
|
|
|
24
24
|
|
|
25
25
|
function finalizeParseCdl( model ) {
|
|
26
26
|
// Get simplified "resolve" functionality and the message function:
|
|
27
|
-
const { message, error } = model.$messageFunctions;
|
|
28
27
|
const {
|
|
29
28
|
resolveUncheckedPath,
|
|
30
29
|
resolveTypeArgumentsUnchecked,
|
|
@@ -41,7 +40,7 @@ function finalizeParseCdl( model ) {
|
|
|
41
40
|
// TODO: why not just use the extensions as they are from the first source?
|
|
42
41
|
for (const name in late) {
|
|
43
42
|
for (const ext of late[name]._extensions) {
|
|
44
|
-
ext.name.absolute = resolveUncheckedPath( ext.name, '
|
|
43
|
+
ext.name.absolute = resolveUncheckedPath( ext.name, '_extensions', ext );
|
|
45
44
|
// Initialize members and define annotations in sub-elements.
|
|
46
45
|
initMembers( ext, ext, ext._block, true );
|
|
47
46
|
extensions.push( ext );
|
|
@@ -53,7 +52,7 @@ function finalizeParseCdl( model ) {
|
|
|
53
52
|
|
|
54
53
|
if (extensions.length > 0) {
|
|
55
54
|
// TODO: do a sort based on the location in case there were two extensions
|
|
56
|
-
// on the same definition?
|
|
55
|
+
// on the same definition? Yes: anno first outside, then inside service def
|
|
57
56
|
model.extensions = extensions;
|
|
58
57
|
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
|
|
59
58
|
}
|
|
@@ -149,51 +148,21 @@ function finalizeParseCdl( model ) {
|
|
|
149
148
|
* `artWithType` has the `type` property, i.e. it could be an `items` object.
|
|
150
149
|
* `user` is the actual artifact, e.g. entity or element.
|
|
151
150
|
*
|
|
151
|
+
* TODO: this should basically be covered by a function of shared.js
|
|
152
|
+
*
|
|
152
153
|
* @param {object} artWithType
|
|
153
154
|
* @param {XSN.Artifact} user
|
|
154
155
|
*/
|
|
155
156
|
function resolveTypeUnchecked( artWithType, user ) {
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
// `scope` is only `typeOf` for `type of element` and not
|
|
160
|
-
// `type of Entity:element`. For the latter we can resolve the path
|
|
161
|
-
// without special treatment.
|
|
162
|
-
if (artWithType.type.scope !== 'typeOf') {
|
|
163
|
-
// elem: Type or elem: type of Artifact:elem
|
|
164
|
-
const name = resolveUncheckedPath( artWithType.type, 'type', user );
|
|
165
|
-
const type = name && model.definitions[name] || { name: { absolute: name } };
|
|
157
|
+
const name = resolveUncheckedPath( artWithType.type, 'type', user );
|
|
158
|
+
if (name) { // correct ref to main artifact
|
|
159
|
+
const type = model.definitions[name];
|
|
166
160
|
resolveTypeArgumentsUnchecked( artWithType, type, user );
|
|
167
161
|
}
|
|
168
|
-
else if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
else if (root.id === '$self' || root.id === '$projection') {
|
|
173
|
-
setArtifactLink( root, user._main );
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
// For better error messages, check for invalid TYPE OFs similarly
|
|
177
|
-
// to how `resolveType()` does.
|
|
178
|
-
let struct = artWithType;
|
|
179
|
-
// `items` have no kind, but need to be skipped as well
|
|
180
|
-
while (struct.kind === 'element' || struct._outer?.items) {
|
|
181
|
-
if (struct._outer?.items)
|
|
182
|
-
struct = struct._outer;
|
|
183
|
-
else
|
|
184
|
-
struct = struct._parent;
|
|
185
|
-
}
|
|
186
|
-
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
|
|
187
|
-
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
188
|
-
{ keyword: 'type of', '#': struct.kind } );
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const fake = { name: { absolute: user.name.absolute } };
|
|
193
|
-
// to-csn just needs a fake element whose absolute name and _parent/_main links are correct
|
|
194
|
-
setLink( fake, '_parent', user._parent );
|
|
195
|
-
setLink( fake, '_main', user._main ); // value does not matter...
|
|
196
|
-
setArtifactLink( root, fake );
|
|
162
|
+
else if (name === '' && artWithType.$typeArgs && model.options.testMode) {
|
|
163
|
+
// Ensure: parser does not allow type args with Main:elem/`type of`,
|
|
164
|
+
// extend … with type only with named type arguments
|
|
165
|
+
throw new CompilerAssertion( 'Unexpected type arguments for TYPE OF' );
|
|
197
166
|
}
|
|
198
167
|
}
|
|
199
168
|
}
|
package/lib/compiler/generate.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
augmentPath,
|
|
17
17
|
splitIntoPath,
|
|
18
18
|
isDirectComposition,
|
|
19
|
+
copyExpr,
|
|
19
20
|
} = require('./utils');
|
|
20
21
|
|
|
21
22
|
function generate( model ) {
|
|
@@ -250,7 +251,8 @@ function generate( model ) {
|
|
|
250
251
|
}
|
|
251
252
|
else if (!art._block.$withLocalized && !options.$recompile) {
|
|
252
253
|
art._block.$withLocalized = true;
|
|
253
|
-
|
|
254
|
+
// no semantic loc: message only emitted once
|
|
255
|
+
info( 'def-unexpected-texts-entities', [ art.name.location, null ], {},
|
|
254
256
|
'Input CSN contains expansions for localized data' );
|
|
255
257
|
return textElems; // make compilation idempotent
|
|
256
258
|
}
|
|
@@ -548,6 +550,7 @@ function generate( model ) {
|
|
|
548
550
|
}
|
|
549
551
|
else if (art.type && art._block && art.type.scope !== 'typeOf') {
|
|
550
552
|
// TODO: also do something special for TYPE OF inside `art`s own elements
|
|
553
|
+
// TODO: check for own - add test case with Type:elem (not TYPE OF elem)
|
|
551
554
|
name = resolveUncheckedPath( art.type, 'type', art );
|
|
552
555
|
art = name && model.definitions[name];
|
|
553
556
|
}
|
|
@@ -593,7 +596,7 @@ function generate( model ) {
|
|
|
593
596
|
origin = origin._origin;
|
|
594
597
|
let target = origin.targetAspect;
|
|
595
598
|
if (target && target.path)
|
|
596
|
-
target = resolvePath( origin.targetAspect, '
|
|
599
|
+
target = resolvePath( origin.targetAspect, 'targetAspect', origin );
|
|
597
600
|
if (!target || !target.elements)
|
|
598
601
|
return;
|
|
599
602
|
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
@@ -692,12 +695,11 @@ function generate( model ) {
|
|
|
692
695
|
$inferred: 'aspect-composition',
|
|
693
696
|
};
|
|
694
697
|
|
|
695
|
-
const elements = Object.create(null);
|
|
696
698
|
const art = {
|
|
697
699
|
kind: 'entity',
|
|
698
700
|
name: { path: splitIntoPath( location, entityName ), absolute: entityName, location },
|
|
699
701
|
location,
|
|
700
|
-
elements,
|
|
702
|
+
elements: Object.create(null),
|
|
701
703
|
$inferred: 'composition-entity',
|
|
702
704
|
};
|
|
703
705
|
if (target.name) { // named target aspect
|
|
@@ -752,16 +754,25 @@ function generate( model ) {
|
|
|
752
754
|
}
|
|
753
755
|
|
|
754
756
|
function addProxyElements( proxyDict, elements, inferred, location, prefix = '', anno = '' ) {
|
|
755
|
-
// TODO: also use for includeMembers()?
|
|
757
|
+
// TODO: also use for includeMembers()? Both are similar. Combine?
|
|
756
758
|
for (const name in elements) {
|
|
757
759
|
const pname = `${ prefix }${ name }`;
|
|
758
760
|
const origin = elements[name];
|
|
759
761
|
const proxy = linkToOrigin( origin, pname, null, null, location || origin.location );
|
|
762
|
+
setLink( proxy, '_block', origin._block );
|
|
760
763
|
proxy.$inferred = inferred;
|
|
761
764
|
if (origin.masked)
|
|
762
765
|
proxy.masked = Object.assign( { $inferred: 'include' }, origin.masked );
|
|
763
766
|
if (origin.key)
|
|
764
767
|
proxy.key = Object.assign( { $inferred: 'include' }, origin.key );
|
|
768
|
+
if (origin.value && origin.$syntax === 'calc') {
|
|
769
|
+
// TODO: If paths become invalid in the new artifact, should we mark
|
|
770
|
+
// all usages in the expressions? Possibly just the first one?
|
|
771
|
+
// TODO: Unify with coding in extend.js
|
|
772
|
+
proxy.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
773
|
+
proxy.$syntax = 'calc';
|
|
774
|
+
setLink( proxy, '_calcOrigin', origin._calcOrigin || origin );
|
|
775
|
+
}
|
|
765
776
|
if (anno)
|
|
766
777
|
setAnnotation( proxy, anno );
|
|
767
778
|
dictAdd( proxyDict.elements, pname, proxy );
|
package/lib/compiler/index.js
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
// - The XSN is the only context which context-dependent functions can depend on.
|
|
10
10
|
// - Sharing such a function is by adding it to `‹xsn›.$functions`,
|
|
11
11
|
// e.g. `resolvePath` and similar will be attached to the XSN.
|
|
12
|
-
// - Functions which might be overwritten in a next sub module
|
|
13
|
-
// are added to `‹xsn›.$volatileFunctions`, currently just `environment`.
|
|
14
12
|
|
|
15
13
|
'use strict';
|
|
16
14
|
|
|
@@ -55,6 +53,7 @@ const extensionParsers = {
|
|
|
55
53
|
class InvocationError extends Error {
|
|
56
54
|
constructor( errs, ...args ) {
|
|
57
55
|
super(...args);
|
|
56
|
+
this.code = 'ERR_CDS_COMPILER_INVOCATION';
|
|
58
57
|
this.errors = errs;
|
|
59
58
|
this.hasBeenReported = false;
|
|
60
59
|
}
|
|
@@ -65,6 +64,7 @@ class InvocationError extends Error {
|
|
|
65
64
|
class ArgumentError extends Error {
|
|
66
65
|
constructor( arg, ...args ) {
|
|
67
66
|
super(...args);
|
|
67
|
+
this.code = 'ERR_CDS_COMPILER_ARGUMENT';
|
|
68
68
|
this.argument = arg;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -410,8 +410,12 @@ function compileSourcesX( sourcesDict, options = {} ) {
|
|
|
410
410
|
* TODO: probably issue message api-recompiled-csn there.
|
|
411
411
|
*/
|
|
412
412
|
function recompileX( csn, options ) {
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
options = {
|
|
414
|
+
...options,
|
|
415
|
+
parseCdl: false, // Explicitly set parseCdl to false because backends cannot handle it
|
|
416
|
+
docComment: null, // Input is CSN: leave doc comments alone
|
|
417
|
+
$recompile: true,
|
|
418
|
+
};
|
|
415
419
|
// Reset csnFlavor: Use client style (default)
|
|
416
420
|
delete options.csnFlavor;
|
|
417
421
|
delete options.toCsn;
|
|
@@ -453,7 +457,6 @@ function compileDoX( model ) {
|
|
|
453
457
|
return model;
|
|
454
458
|
}
|
|
455
459
|
model.$functions = {};
|
|
456
|
-
model.$volatileFunctions = {};
|
|
457
460
|
fns( model ); // attach (mostly) paths functions
|
|
458
461
|
define( model );
|
|
459
462
|
// do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.
|
|
@@ -77,7 +77,7 @@ function kickStart( model ) {
|
|
|
77
77
|
(preferredRedirectionTarget || !annotationIsFalse( art['@cds.redirection.target'] ) )) {
|
|
78
78
|
chain.push( art );
|
|
79
79
|
setLink( art, '_ancestors', 0 ); // avoid infloop with cyclic from
|
|
80
|
-
const name = resolveUncheckedPath( art.query.from, '
|
|
80
|
+
const name = resolveUncheckedPath( art.query.from, 'from', art );
|
|
81
81
|
art = name && model.definitions[name];
|
|
82
82
|
if (autoexposed)
|
|
83
83
|
break; // only direct projection for auto-exposed
|
|
@@ -146,9 +146,10 @@ function kickStart( model ) {
|
|
|
146
146
|
if (art && art.$duplicates)
|
|
147
147
|
continue;
|
|
148
148
|
const ref = def.extern;
|
|
149
|
-
const
|
|
149
|
+
const user = (topLevel ? def : src);
|
|
150
|
+
const from = user.fileDep;
|
|
150
151
|
if (art || !from || from.realname) // no error for non-existing ref with non-existing module
|
|
151
|
-
resolvePath( ref, '
|
|
152
|
+
resolvePath( ref, 'using', def ); // TODO: consider FROM for validNames
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
}
|