@sap/cds-compiler 3.3.2 → 3.4.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 +33 -0
- package/bin/cdsc.js +3 -1
- package/doc/CHANGELOG_BETA.md +17 -0
- package/lib/api/main.js +147 -18
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/keywords.js +104 -0
- package/lib/base/message-registry.js +137 -68
- package/lib/base/messages.js +59 -48
- package/lib/base/model.js +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +13 -8
- package/lib/checks/defaultValues.js +3 -1
- package/lib/checks/elements.js +1 -1
- package/lib/checks/parameters.js +4 -2
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/validator.js +14 -4
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/checks.js +30 -20
- package/lib/compiler/define.js +89 -25
- package/lib/compiler/extend.js +33 -28
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/populate.js +30 -8
- package/lib/compiler/propagator.js +23 -28
- package/lib/compiler/resolve.js +11 -5
- package/lib/compiler/shared.js +66 -48
- package/lib/compiler/tweak-assocs.js +2 -3
- package/lib/compiler/utils.js +11 -0
- package/lib/edm/annotations/genericTranslation.js +7 -4
- package/lib/edm/csn2edm.js +1 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3565 -3544
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +140 -158
- package/lib/json/to-csn.js +23 -5
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +7 -10
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +115 -84
- package/lib/language/language.g4 +29 -25
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.js +1 -0
- package/lib/model/csnRefs.js +4 -3
- package/lib/model/csnUtils.js +39 -7
- package/lib/model/sortViews.js +7 -3
- package/lib/modelCompare/compare.js +49 -15
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +5 -1
- package/lib/render/manageConstraints.js +9 -5
- package/lib/render/toCdl.js +120 -62
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +6 -2
- package/lib/render/utils/common.js +7 -0
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +11 -4
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +7 -1
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +7 -3
- package/lib/transform/forRelationalDB.js +12 -6
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +23 -14
- package/lib/transform/translateAssocsToJoins.js +12 -12
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
package/lib/compiler/extend.js
CHANGED
|
@@ -35,7 +35,8 @@ function extend( model ) {
|
|
|
35
35
|
resolvePath,
|
|
36
36
|
resolveUncheckedPath,
|
|
37
37
|
checkAnnotate,
|
|
38
|
-
|
|
38
|
+
initAnnotations,
|
|
39
|
+
copyAnnotationsForExtensions,
|
|
39
40
|
attachAndEmitValidNames,
|
|
40
41
|
checkDefinitions,
|
|
41
42
|
initArtifact,
|
|
@@ -163,6 +164,7 @@ function extend( model ) {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
function extendContext( name, art ) {
|
|
167
|
+
// TODO: remove this function - add remains to define.js
|
|
166
168
|
// (ext.expectedKind == art.kind) already checked by parser except for context/service
|
|
167
169
|
if (!kindProperties[art.kind].artifacts) {
|
|
168
170
|
// no context or service => warn about context extensions
|
|
@@ -189,9 +191,8 @@ function extend( model ) {
|
|
|
189
191
|
checkDefinitions( ext, art, 'columns');
|
|
190
192
|
if (ext.includes)
|
|
191
193
|
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
194
|
+
checkAnnotate( ext, art );
|
|
195
|
+
copyAnnotationsForExtensions( ext, art );
|
|
195
196
|
}
|
|
196
197
|
return true;
|
|
197
198
|
}
|
|
@@ -207,7 +208,8 @@ function extend( model ) {
|
|
|
207
208
|
* @param {boolean|'gen'} [noIncludes=false]
|
|
208
209
|
*/
|
|
209
210
|
function extendArtifact( extensions, art, noIncludes = false) {
|
|
210
|
-
if (!noIncludes && !(canApplyIncludes( art ) &&
|
|
211
|
+
if (!noIncludes && !(canApplyIncludes( art, art ) &&
|
|
212
|
+
extensions.every( ext => canApplyIncludes(ext, art) )))
|
|
211
213
|
return false;
|
|
212
214
|
if (!art.query) {
|
|
213
215
|
model._entities.push( art ); // add structure with includes in dep order
|
|
@@ -251,9 +253,9 @@ function extend( model ) {
|
|
|
251
253
|
art.includes = [ ...ext.includes ];
|
|
252
254
|
applyIncludes( ext, art );
|
|
253
255
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
checkAnnotate( ext, art );
|
|
257
|
+
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
258
|
+
copyAnnotationsForExtensions( ext, art );
|
|
257
259
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
258
260
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
259
261
|
storeTypeExtension( ext, art );
|
|
@@ -302,9 +304,6 @@ function extend( model ) {
|
|
|
302
304
|
function extendColumns( ext, art ) {
|
|
303
305
|
// TODO: consider reportUnstableExtensions
|
|
304
306
|
|
|
305
|
-
for (const col of ext.columns)
|
|
306
|
-
defineAnnotations( col, col, ext._block, ext.kind );
|
|
307
|
-
|
|
308
307
|
const { location } = ext.name;
|
|
309
308
|
const { query } = art;
|
|
310
309
|
if (!query) {
|
|
@@ -392,7 +391,7 @@ function extend( model ) {
|
|
|
392
391
|
// TODO: Future: Sub-message that points to previous extension?
|
|
393
392
|
error( 'ext-invalid-type-property', [ ext[prop].location, ext ], {
|
|
394
393
|
prop,
|
|
395
|
-
|
|
394
|
+
value: ext[prop].val,
|
|
396
395
|
number: art[prop].val,
|
|
397
396
|
'#': previousSuccess[prop] ? 'ext-smaller' : 'smaller',
|
|
398
397
|
} );
|
|
@@ -580,16 +579,20 @@ function extend( model ) {
|
|
|
580
579
|
// includes ----------------------------------------------------------------
|
|
581
580
|
|
|
582
581
|
/**
|
|
583
|
-
* Returns true, if `includes` can be applied
|
|
584
|
-
* any of the artifacts referenced in
|
|
582
|
+
* Returns true, if `art.includes` can be applied on `target`.
|
|
583
|
+
* They can't be applied if any of the artifacts referenced in
|
|
584
|
+
* `art.includes` are yet to be extended.
|
|
585
|
+
* `art !== target` if `art` is an extension.
|
|
585
586
|
*
|
|
586
587
|
* @param {XSN.Definition} art
|
|
588
|
+
* @param {XSN.Artifact} target
|
|
587
589
|
* @returns {boolean}
|
|
588
590
|
*/
|
|
589
|
-
function canApplyIncludes( art ) {
|
|
591
|
+
function canApplyIncludes( art, target ) {
|
|
590
592
|
if (art.includes) {
|
|
593
|
+
const isView = !!target.query;
|
|
591
594
|
for (const ref of art.includes) {
|
|
592
|
-
const template = resolvePath( ref, 'include', art );
|
|
595
|
+
const template = resolvePath( ref, isView ? 'viewInclude' : 'include', art );
|
|
593
596
|
if (template && template.name.absolute in extensionsDict)
|
|
594
597
|
return false;
|
|
595
598
|
}
|
|
@@ -612,20 +615,22 @@ function extend( model ) {
|
|
|
612
615
|
*/
|
|
613
616
|
function applyIncludes( ext, art ) {
|
|
614
617
|
if (kindProperties[art.kind].include !== true) {
|
|
615
|
-
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
616
|
-
|
|
618
|
+
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ],
|
|
619
|
+
{ kind: art.kind });
|
|
617
620
|
return;
|
|
618
621
|
}
|
|
619
622
|
|
|
620
|
-
if (!art.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (template
|
|
627
|
-
|
|
628
|
-
|
|
623
|
+
if (!art.query) {
|
|
624
|
+
if (!art._ancestors)
|
|
625
|
+
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
626
|
+
for (const ref of ext.includes) {
|
|
627
|
+
const template = ref._artifact;
|
|
628
|
+
// !template -> non-includable, e.g. scalar type, or cyclic
|
|
629
|
+
if (template) {
|
|
630
|
+
if (template._ancestors)
|
|
631
|
+
art._ancestors.push( ...template._ancestors );
|
|
632
|
+
art._ancestors.push( template );
|
|
633
|
+
}
|
|
629
634
|
}
|
|
630
635
|
}
|
|
631
636
|
includeMembers( ext, art, 'elements' );
|
|
@@ -1272,7 +1277,7 @@ function copyPersistenceAnnotations(target, source, options) {
|
|
|
1272
1277
|
copy( '@cds.persistence.exists' );
|
|
1273
1278
|
copy( '@cds.persistence.skip' );
|
|
1274
1279
|
|
|
1275
|
-
function copy(anno) {
|
|
1280
|
+
function copy( anno ) {
|
|
1276
1281
|
if ( source[anno] && !target[anno] )
|
|
1277
1282
|
target[anno] = { ...source[anno], $inferred: 'parent-origin' };
|
|
1278
1283
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
// Things which needs to done for parse.cdl after define()
|
|
2
2
|
|
|
3
|
+
// For transforming a single CDL file into CSN, parse.cdl resolves block-relative
|
|
4
|
+
// _artifact_ references (in `type`, `includes`, `target`, `targetAspect`, `from`,
|
|
5
|
+
// `extend`, `annotate`) to absolute names. This is the task of the export
|
|
6
|
+
// function of this file, which the compiler calls after the export function of
|
|
7
|
+
// './define.js'.
|
|
8
|
+
|
|
9
|
+
// There should be no need to do other things here – if you think there is,
|
|
10
|
+
// consider adding it to './define.js'. For “semantic locations” in error
|
|
11
|
+
// messages, we set also “structural” links and names inside `extensions` (by
|
|
12
|
+
// calling functions from './define.js').
|
|
13
|
+
|
|
3
14
|
'use strict';
|
|
4
15
|
|
|
5
16
|
const { dictAddArray } = require('../base/dictionaries');
|
|
@@ -18,7 +29,6 @@ function finalizeParseCdl( model ) {
|
|
|
18
29
|
const {
|
|
19
30
|
resolveUncheckedPath,
|
|
20
31
|
resolveTypeArgumentsUnchecked,
|
|
21
|
-
defineAnnotations,
|
|
22
32
|
initMembers,
|
|
23
33
|
extensionsDict,
|
|
24
34
|
} = model.$functions;
|
|
@@ -29,20 +39,13 @@ function finalizeParseCdl( model ) {
|
|
|
29
39
|
function resolveTypesAndExtensionsForParseCdl() {
|
|
30
40
|
const extensions = [];
|
|
31
41
|
|
|
32
|
-
// TODO: probably better to loop over extensions of all sources (there is just one)
|
|
33
42
|
for (const name in extensionsDict) {
|
|
34
43
|
for (const ext of extensionsDict[name]) {
|
|
35
44
|
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );
|
|
36
|
-
//
|
|
37
|
-
defineAnnotations( ext, ext, ext._block, 'extend' );
|
|
38
|
-
mergeAnnotatesForSameArtifact( ext );
|
|
45
|
+
mergeAnnotatesForSameArtifact( ext ); // TODO: should not be necessary anymore
|
|
39
46
|
// Initialize members and define annotations in sub-elements.
|
|
40
47
|
initMembers( ext, ext, ext._block, true );
|
|
41
48
|
extensions.push( ext );
|
|
42
|
-
for (const col of ext.columns || []) {
|
|
43
|
-
// Note, no `priority` argument, since we don't apply the extension in the end.
|
|
44
|
-
defineAnnotations( col, col, ext._block );
|
|
45
|
-
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -50,6 +53,8 @@ function finalizeParseCdl( model ) {
|
|
|
50
53
|
forEachGeneric(model, 'vocabularies', art => resolveTypesForParseCdl(art, art));
|
|
51
54
|
|
|
52
55
|
if (extensions.length > 0) {
|
|
56
|
+
// TODO: do a sort based on the location in case there were two extensions
|
|
57
|
+
// on the same definition?
|
|
53
58
|
model.extensions = extensions;
|
|
54
59
|
model.extensions.forEach(ext => resolveTypesForParseCdl(ext, ext));
|
|
55
60
|
}
|
package/lib/compiler/populate.js
CHANGED
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
} = require('../base/dictionaries');
|
|
29
29
|
const { dictLocation } = require('../base/location');
|
|
30
30
|
const { weakLocation } = require('../base/messages');
|
|
31
|
+
const { CompilerAssertion } = require('../base/error');
|
|
31
32
|
|
|
32
33
|
const { kindProperties } = require('./base');
|
|
33
34
|
const {
|
|
@@ -35,6 +36,8 @@ const {
|
|
|
35
36
|
setLink,
|
|
36
37
|
setArtifactLink,
|
|
37
38
|
annotationVal,
|
|
39
|
+
annotationIsFalse,
|
|
40
|
+
annotationLocation,
|
|
38
41
|
augmentPath,
|
|
39
42
|
splitIntoPath,
|
|
40
43
|
linkToOrigin,
|
|
@@ -623,10 +626,10 @@ function populate( model ) {
|
|
|
623
626
|
return '';
|
|
624
627
|
const path = col.value &&
|
|
625
628
|
(col.value.path || !col.value.args && col.value.func && col.value.func.path);
|
|
626
|
-
if (path) {
|
|
629
|
+
if (path && path.length) {
|
|
627
630
|
const last = !path.broken && path.length && path[path.length - 1];
|
|
628
631
|
if (last) {
|
|
629
|
-
col.name = { id: last.id, location: last.location, $inferred: 'as' };
|
|
632
|
+
col.name = { id: last.id || '', location: last.location, $inferred: 'as' };
|
|
630
633
|
return col.name.id;
|
|
631
634
|
}
|
|
632
635
|
}
|
|
@@ -678,8 +681,8 @@ function populate( model ) {
|
|
|
678
681
|
else if (elem._pathHead?.kind === '$inline' && elem.value.path.length === 1) {
|
|
679
682
|
const hpath = elem._pathHead.value?.path;
|
|
680
683
|
const head = hpath?.length === 1 && hpath[0]._navigation;
|
|
681
|
-
// Alias .{ elem }
|
|
682
|
-
if (head?.kind === '$tableAlias')
|
|
684
|
+
// Alias .{ elem } - but not with Alias.{ $magic }: consider for ON-rewrite
|
|
685
|
+
if (head?.kind === '$tableAlias' && item.id.charAt(0) !== '$')
|
|
683
686
|
pushLink( head.elements[item.id], '_projections', elem );
|
|
684
687
|
}
|
|
685
688
|
}
|
|
@@ -1022,10 +1025,12 @@ function populate( model ) {
|
|
|
1022
1025
|
const targetScope = definitionScope( target );
|
|
1023
1026
|
if (targetScope === assocScope) { // intra-scope in model
|
|
1024
1027
|
const elemScope = definitionScope( elem._main || elem );
|
|
1025
|
-
|
|
1028
|
+
// without the if, compile.recompile.json versus expected csn.json in
|
|
1029
|
+
// test3/Redirections/AutoExposeDeepScoped would fail
|
|
1030
|
+
if (targetScope === target || // model target is scope root
|
|
1026
1031
|
assocScope === assocMain || // unscoped assoc source in model
|
|
1027
1032
|
elemScope !== (elem._main || elem)) // scoped assoc source in service
|
|
1028
|
-
return elemScope;
|
|
1033
|
+
return elemScope; // own scope, then global
|
|
1029
1034
|
}
|
|
1030
1035
|
if (targetScope === target) // unscoped target in model / other service
|
|
1031
1036
|
return false; // all (there could be no scoped autoexposed)
|
|
@@ -1158,8 +1163,25 @@ function populate( model ) {
|
|
|
1158
1163
|
const autoexposed = model.definitions[absolute];
|
|
1159
1164
|
if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
|
|
1160
1165
|
if (isDirectProjection( autoexposed, target )) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1166
|
+
const anno = autoexposed['@cds.redirection.target'];
|
|
1167
|
+
if (annotationIsFalse( anno )) {
|
|
1168
|
+
// It would probably be cleaner to ignore a dubious
|
|
1169
|
+
// `@cds.redirection.target: false` earlier, but that is not easy to detect
|
|
1170
|
+
// due to the name of the autoexposed entity with scoped redirections
|
|
1171
|
+
if (!anno.$errorReported) {
|
|
1172
|
+
info( 'anno-redirecting-anyway',
|
|
1173
|
+
[ annotationLocation( anno ), autoexposed ],
|
|
1174
|
+
{ target, art: absolute, code: '@cds.redirection.target: false' },
|
|
1175
|
+
'$(TARGET) is auto-redirected to $(ART) even with $(CODE)' );
|
|
1176
|
+
anno.$errorReported = 'anno-redirecting-anyway';
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
else if (autoexposed._parent === service ||
|
|
1180
|
+
!annotationVal( autoexposed['@cds.autoexposed'] )) {
|
|
1181
|
+
// existing def not auto-exposed, or un-scoped auto-exposed: should not happen
|
|
1182
|
+
if (options.testMode)
|
|
1183
|
+
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.absolute } twice`);
|
|
1184
|
+
}
|
|
1163
1185
|
return autoexposed;
|
|
1164
1186
|
}
|
|
1165
1187
|
error( 'duplicate-autoexposed', [ service.name.location, service ],
|
|
@@ -33,6 +33,7 @@ function propagate( model ) {
|
|
|
33
33
|
'@Analytics.visible': never,
|
|
34
34
|
'@cds.autoexpose': onlyViaArtifact,
|
|
35
35
|
'@cds.autoexposed': never, // in case people set it themselves
|
|
36
|
+
'@cds.external': never,
|
|
36
37
|
'@cds.redirection.target': never,
|
|
37
38
|
'@fiori.draft.enabled': onlyViaArtifact,
|
|
38
39
|
'@': annotation, // always except in 'returns' and 'items'
|
|
@@ -84,40 +85,34 @@ function propagate( model ) {
|
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
// console.log('RUN:', art.name, art.elements ? Object.keys(art.elements) : 0)
|
|
88
|
+
|
|
87
89
|
const chain = [];
|
|
88
|
-
let
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for (const ref of t.includes || []) {
|
|
107
|
-
const s = ref._artifact;
|
|
108
|
-
if (!s) // ref error
|
|
109
|
-
continue;
|
|
110
|
-
chain.push( { target: t, source: s } );
|
|
111
|
-
if (checkAndSetStatus( s ))
|
|
112
|
-
news.push( s );
|
|
113
|
-
}
|
|
90
|
+
let targets = [ art ];
|
|
91
|
+
while (targets.length) {
|
|
92
|
+
const news = [];
|
|
93
|
+
for (const target of targets) {
|
|
94
|
+
const origin = getOrigin( target );
|
|
95
|
+
if (origin) {
|
|
96
|
+
chain.push( { target, source: origin } );
|
|
97
|
+
if (checkAndSetStatus( origin ))
|
|
98
|
+
news.push( origin );
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const ref of target.includes || []) {
|
|
102
|
+
const include = ref._artifact;
|
|
103
|
+
if (!include)
|
|
104
|
+
continue;
|
|
105
|
+
chain.push( { target, source: include } );
|
|
106
|
+
if (checkAndSetStatus( include ))
|
|
107
|
+
news.push( include );
|
|
114
108
|
}
|
|
115
|
-
targets = news;
|
|
116
109
|
}
|
|
110
|
+
targets = news;
|
|
117
111
|
}
|
|
112
|
+
|
|
118
113
|
chain.reverse().forEach( step );
|
|
119
114
|
runMembers( art );
|
|
120
|
-
// console.log('DONE:', art.name, art.elements ? Object.keys(art.elements) : 0)
|
|
115
|
+
// console.log('DONE:', art.name, art.elements ? Object.keys(art.elements) : 0);
|
|
121
116
|
}
|
|
122
117
|
|
|
123
118
|
function runMembers( art ) {
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -87,7 +87,8 @@ function resolve( model ) {
|
|
|
87
87
|
const {
|
|
88
88
|
resolvePath,
|
|
89
89
|
checkAnnotate,
|
|
90
|
-
|
|
90
|
+
initAnnotations,
|
|
91
|
+
copyAnnotationsForExtensions,
|
|
91
92
|
attachAndEmitValidNames,
|
|
92
93
|
lateExtensions,
|
|
93
94
|
applyTypeExtensions,
|
|
@@ -371,7 +372,7 @@ function resolve( model ) {
|
|
|
371
372
|
}
|
|
372
373
|
}
|
|
373
374
|
if (obj.target) {
|
|
374
|
-
// console.log(obj.name,obj._origin
|
|
375
|
+
// console.log(obj.name,obj._origin?.name,obj)
|
|
375
376
|
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
376
377
|
resolveTarget( art, obj._origin );
|
|
377
378
|
// console.log(error( 'test-target', [ obj.location, obj ],
|
|
@@ -521,9 +522,9 @@ function resolve( model ) {
|
|
|
521
522
|
setArtifactLink( ext.name, art );
|
|
522
523
|
|
|
523
524
|
if (art) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
525
|
+
checkAnnotate( ext, art );
|
|
526
|
+
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
|
|
527
|
+
copyAnnotationsForExtensions( ext, art );
|
|
527
528
|
// eslint-disable-next-line no-shadow
|
|
528
529
|
forEachMember( ext, ( elem, name, prop ) => {
|
|
529
530
|
storeExtension( elem, name, prop, art, ext._block );
|
|
@@ -1079,6 +1080,11 @@ function resolve( model ) {
|
|
|
1079
1080
|
'Only an association can be redirected' );
|
|
1080
1081
|
return;
|
|
1081
1082
|
}
|
|
1083
|
+
else if ((elem.value || elem.expand) && elem.type && !elem.type.$inferred) {
|
|
1084
|
+
error( 'ref-unexpected-assoc', [ elem.type.location, elem ], {},
|
|
1085
|
+
'Casting to an association is not supported' );
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1082
1088
|
// console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
|
|
1083
1089
|
// .toString(), elem.value)
|
|
1084
1090
|
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
package/lib/compiler/shared.js
CHANGED
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
setArtifactLink,
|
|
13
13
|
dependsOn,
|
|
14
14
|
pathName,
|
|
15
|
-
annotationHasEllipsis,
|
|
16
15
|
} = require('./utils');
|
|
17
16
|
|
|
18
17
|
function artifactsEnv( art ) {
|
|
@@ -87,6 +86,11 @@ function fns( model ) {
|
|
|
87
86
|
expectedMsgId: 'expected-struct',
|
|
88
87
|
envFn: artifactsEnv,
|
|
89
88
|
},
|
|
89
|
+
viewInclude: {
|
|
90
|
+
check: checkViewIncludesRef,
|
|
91
|
+
expectedMsgId: 'ref-expecting-bare-aspect',
|
|
92
|
+
envFn: artifactsEnv,
|
|
93
|
+
},
|
|
90
94
|
target: {
|
|
91
95
|
check: checkEntityRef,
|
|
92
96
|
expectedMsgId: 'expected-entity',
|
|
@@ -135,15 +139,22 @@ function fns( model ) {
|
|
|
135
139
|
dollar: true,
|
|
136
140
|
rootEnv: 'elements', // the final environment for the path root
|
|
137
141
|
noDep: true, // do not set dependency for circular-check
|
|
142
|
+
allowSelf: true,
|
|
138
143
|
}, // TODO: special assoc for only on user
|
|
139
144
|
'mixin-on': {
|
|
140
145
|
escape: 'param', // TODO: extra check that assocs containing param in ON is not published
|
|
141
146
|
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment
|
|
142
147
|
dollar: true,
|
|
143
148
|
noDep: true, // do not set dependency for circular-check
|
|
149
|
+
allowSelf: true,
|
|
144
150
|
}, // TODO: special assoc for only on user
|
|
145
151
|
rewrite: {
|
|
146
|
-
next: '_$next',
|
|
152
|
+
next: '_$next',
|
|
153
|
+
dollar: true,
|
|
154
|
+
escape: 'param',
|
|
155
|
+
noDep: true,
|
|
156
|
+
allowSelf: true,
|
|
157
|
+
rewrite: true,
|
|
147
158
|
}, // TODO: assertion that there is no next/escape used
|
|
148
159
|
'order-by': {
|
|
149
160
|
next: '_$next',
|
|
@@ -153,7 +164,11 @@ function fns( model ) {
|
|
|
153
164
|
deprecatedSourceRefs: true,
|
|
154
165
|
},
|
|
155
166
|
'order-by-union': {
|
|
156
|
-
next: '_$next',
|
|
167
|
+
next: '_$next',
|
|
168
|
+
dollar: true,
|
|
169
|
+
escape: 'param',
|
|
170
|
+
noDep: true,
|
|
171
|
+
noExt: true,
|
|
157
172
|
},
|
|
158
173
|
// expr TODO: better - on condition for assoc, other on
|
|
159
174
|
// expr TODO: write dependency, but care for $self
|
|
@@ -169,7 +184,7 @@ function fns( model ) {
|
|
|
169
184
|
resolveTypeArgumentsUnchecked,
|
|
170
185
|
resolvePath,
|
|
171
186
|
checkAnnotate,
|
|
172
|
-
|
|
187
|
+
copyAnnotationsForExtensions,
|
|
173
188
|
attachAndEmitValidNames,
|
|
174
189
|
} );
|
|
175
190
|
return;
|
|
@@ -183,7 +198,22 @@ function fns( model ) {
|
|
|
183
198
|
// - derived structure types: would have to follow type in extend/include;
|
|
184
199
|
// - entities with params: clarify inheritance, use of param in ON/DEFAULT;
|
|
185
200
|
// - query entities/events: difficult sequence of resolve steps
|
|
186
|
-
|
|
201
|
+
// - aspect without elements (useful for actions/annotations)
|
|
202
|
+
return !(art.elements && !art.query && !art.type && !art.params) && art.kind !== 'aspect';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Returns true, if the given artifact can be included by a query entity / view.
|
|
207
|
+
*
|
|
208
|
+
* We currently allow:
|
|
209
|
+
* - aspects without elements (the aspect may have actions):
|
|
210
|
+
* either no `elements` property or empty dictionary
|
|
211
|
+
*
|
|
212
|
+
* @param {XSN.Artifact} art
|
|
213
|
+
* @return {boolean}
|
|
214
|
+
*/
|
|
215
|
+
function checkViewIncludesRef( art ) {
|
|
216
|
+
return !(art.kind === 'aspect' && (!art.elements || Object.keys(art.elements).length === 0));
|
|
187
217
|
}
|
|
188
218
|
|
|
189
219
|
/**
|
|
@@ -331,7 +361,8 @@ function fns( model ) {
|
|
|
331
361
|
return setArtifactLink( ref, art );
|
|
332
362
|
}
|
|
333
363
|
else if (!spec.envFn && user._pathHead) {
|
|
334
|
-
|
|
364
|
+
if (art.kind === '$self')
|
|
365
|
+
rejectBareSelf( spec, path, user, extDict );
|
|
335
366
|
}
|
|
336
367
|
else if (art.kind === 'using') {
|
|
337
368
|
const def = model.definitions[art.name.absolute];
|
|
@@ -374,7 +405,10 @@ function fns( model ) {
|
|
|
374
405
|
// TODO: set art?
|
|
375
406
|
}
|
|
376
407
|
else if (art.kind === '$tableAlias' || art.kind === '$self') {
|
|
377
|
-
if (
|
|
408
|
+
if (art.kind === '$self') {
|
|
409
|
+
rejectBareSelf( spec, path, user, extDict );
|
|
410
|
+
}
|
|
411
|
+
else if (spec.noAliasOrMixin) {
|
|
378
412
|
// TODO: good enough for now - change later to not search for table aliases at all
|
|
379
413
|
signalNotFound( 'ref-rejected-on', [ head.location, user ], extDict && [ extDict ],
|
|
380
414
|
{ '#': 'alias', id: head.id } );
|
|
@@ -447,6 +481,15 @@ function fns( model ) {
|
|
|
447
481
|
return setArtifactLink( ref, art );
|
|
448
482
|
}
|
|
449
483
|
|
|
484
|
+
function rejectBareSelf( spec, path, user, extDict ) {
|
|
485
|
+
if (path.length === 1 && !spec.allowSelf && !user.expand && !user.inline) {
|
|
486
|
+
const head = path[0];
|
|
487
|
+
// TODO: extra text variant for JOIN-ON (if we have an extra `expected`)
|
|
488
|
+
signalNotFound( 'ref-unexpected-self', [ head.location, user ], extDict && [ extDict ],
|
|
489
|
+
{ id: head.id } );
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
450
493
|
// Issue errors for "smart" element-in-artifact references
|
|
451
494
|
// without a colon; and errors for misplaced colons in references.
|
|
452
495
|
// This function likely disappears again in cds-compiler v2.x.
|
|
@@ -670,8 +713,7 @@ function fns( model ) {
|
|
|
670
713
|
{ art: searchName( msgArt, head.id, 'element' ) } );
|
|
671
714
|
}
|
|
672
715
|
else {
|
|
673
|
-
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id }
|
|
674
|
-
'Element or variable $(ID) has not been found' );
|
|
716
|
+
signalNotFound( 'ref-undefined-var', [ head.location, user ], valid, { id: head.id } );
|
|
675
717
|
}
|
|
676
718
|
}
|
|
677
719
|
else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
|
|
@@ -804,13 +846,11 @@ function fns( model ) {
|
|
|
804
846
|
// TODO: views elements are proxies to query-0 elements, not the same
|
|
805
847
|
// TODO: better message text
|
|
806
848
|
signalNotFound( 'query-undefined-element', [ item.location, user ],
|
|
807
|
-
[ env ], { id: item.id }
|
|
808
|
-
'Element $(ID) has not been found in the elements of the query' );
|
|
849
|
+
[ env ], { id: item.id } );
|
|
809
850
|
}
|
|
810
851
|
else if (art.kind === '$parameters') {
|
|
811
852
|
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
812
|
-
[ env ], { art:
|
|
813
|
-
{ param: 'Entity $(ART) has no parameter $(MEMBER)' } );
|
|
853
|
+
[ env ], { art: art._main, id: item.id } );
|
|
814
854
|
}
|
|
815
855
|
else {
|
|
816
856
|
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
@@ -832,14 +872,13 @@ function fns( model ) {
|
|
|
832
872
|
* @param {any} location
|
|
833
873
|
* @param {object[]} valid
|
|
834
874
|
* @param {object} [textParams]
|
|
835
|
-
* @param {any} [texts]
|
|
836
875
|
*/
|
|
837
|
-
function signalNotFound(msgId, location, valid, textParams
|
|
876
|
+
function signalNotFound(msgId, location, valid, textParams ) {
|
|
838
877
|
if (location.$notFound)
|
|
839
878
|
return;
|
|
840
879
|
location.$notFound = true;
|
|
841
880
|
/** @type {object} */
|
|
842
|
-
const err = message( msgId, location, textParams
|
|
881
|
+
const err = message( msgId, location, textParams );
|
|
843
882
|
if (valid)
|
|
844
883
|
attachAndEmitValidNames(err, ...valid.reverse());
|
|
845
884
|
}
|
|
@@ -924,44 +963,23 @@ function fns( model ) {
|
|
|
924
963
|
}
|
|
925
964
|
}
|
|
926
965
|
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
function
|
|
930
|
-
if (
|
|
931
|
-
art.doc =
|
|
932
|
-
|
|
933
|
-
// set _block (for layering) and $priority, shallow-copy from extension
|
|
934
|
-
// TODO: think of removing $priority, then
|
|
935
|
-
// no _block: define, _block: annotate/extend/edmx
|
|
936
|
-
// would fit with extending defs with props like length
|
|
937
|
-
for (const annoProp in construct) {
|
|
966
|
+
// Copy annotations from `ext` to `art`, overwriting inferred ones.
|
|
967
|
+
// TODO: move to extend.js if not used anymore in define.js
|
|
968
|
+
function copyAnnotationsForExtensions( ext, art ) {
|
|
969
|
+
if (ext.doc)
|
|
970
|
+
art.doc = ext.doc; // e.g. through `extensions` array in CSN
|
|
971
|
+
for (const annoProp in ext) {
|
|
938
972
|
if (annoProp.charAt(0) === '@') {
|
|
939
|
-
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
a.$priority = priority; // is now: undefined (auto-set) | false | 'annotate' | 'extend'
|
|
945
|
-
if (construct !== art)
|
|
946
|
-
addAnnotation( art, annoProp, a );
|
|
947
|
-
if (!priority && annotationHasEllipsis( a )) {
|
|
948
|
-
error( 'anno-unexpected-ellipsis',
|
|
949
|
-
[ a.name.location, art ], { code: '...' } );
|
|
950
|
-
}
|
|
951
|
-
}
|
|
973
|
+
const extAnno = ext[annoProp];
|
|
974
|
+
if (art[annoProp]?.$inferred)
|
|
975
|
+
art[annoProp] = extAnno; // overwrite $inferred annos
|
|
976
|
+
else
|
|
977
|
+
dictAddArray( art, annoProp, extAnno );
|
|
952
978
|
}
|
|
953
979
|
}
|
|
954
980
|
}
|
|
955
981
|
}
|
|
956
982
|
|
|
957
|
-
// Add annotation to definition - overwriting $inferred annos
|
|
958
|
-
function addAnnotation( art, annoProp, anno ) {
|
|
959
|
-
const old = art[annoProp];
|
|
960
|
-
if (old && old.$inferred)
|
|
961
|
-
delete art[annoProp];
|
|
962
|
-
dictAddArray( art, annoProp, anno );
|
|
963
|
-
}
|
|
964
|
-
|
|
965
983
|
module.exports = {
|
|
966
984
|
fns,
|
|
967
985
|
};
|
|
@@ -484,9 +484,8 @@ function tweakAssocs( model ) {
|
|
|
484
484
|
if (elem && !Array.isArray(elem))
|
|
485
485
|
return elem;
|
|
486
486
|
// TODO: better (extra message), TODO: do it
|
|
487
|
-
error( 'query-undefined-element', [ item.location, assoc ],
|
|
488
|
-
|
|
489
|
-
'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON-condition' );
|
|
487
|
+
error( 'query-undefined-element', [ item.location, assoc ],
|
|
488
|
+
{ id: name || item.id, '#': 'redirected' } );
|
|
490
489
|
return (elem) ? false : null;
|
|
491
490
|
}
|
|
492
491
|
}
|
package/lib/compiler/utils.js
CHANGED
|
@@ -35,6 +35,16 @@ function annotationHasEllipsis( anno ) {
|
|
|
35
35
|
const { val } = anno || {};
|
|
36
36
|
return Array.isArray( val ) && val.find( v => v.literal === 'token' && v.val === '...' );
|
|
37
37
|
}
|
|
38
|
+
function annotationLocation( anno ) {
|
|
39
|
+
const { name, location } = anno;
|
|
40
|
+
return {
|
|
41
|
+
file: name.location.file,
|
|
42
|
+
line: name.location.line,
|
|
43
|
+
col: name.location.col,
|
|
44
|
+
endLine: location.endLine,
|
|
45
|
+
endCol: location.endCol,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
40
50
|
* Set compiler-calculated annotation value.
|
|
@@ -395,6 +405,7 @@ module.exports = {
|
|
|
395
405
|
annotationVal,
|
|
396
406
|
annotationIsFalse,
|
|
397
407
|
annotationHasEllipsis,
|
|
408
|
+
annotationLocation,
|
|
398
409
|
annotateWith,
|
|
399
410
|
setLink,
|
|
400
411
|
setArtifactLink,
|