@sap/cds-compiler 3.0.0 → 3.1.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 +104 -9
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +28 -16
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +92 -40
- package/lib/api/options.js +2 -3
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +33 -5
- package/lib/base/messages.js +54 -65
- package/lib/base/model.js +2 -0
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +65 -13
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +93 -4
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +20 -11
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +37 -32
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +15 -19
- package/lib/compiler/shared.js +54 -18
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +15 -6
- package/lib/edm/annotations/genericTranslation.js +12 -2
- package/lib/edm/annotations/preprocessAnnotations.js +18 -15
- package/lib/edm/csn2edm.js +18 -17
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +336 -665
- package/lib/edm/edmUtils.js +86 -45
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4332 -4496
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +19 -20
- package/lib/json/to-csn.js +11 -8
- package/lib/language/genericAntlrParser.js +150 -92
- package/lib/language/language.g4 +47 -74
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +56 -29
- package/lib/model/csnUtils.js +29 -14
- package/lib/model/revealInternalProperties.js +6 -4
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +81 -38
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +31 -11
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +4 -4
- package/lib/transform/localized.js +15 -11
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/file.js +28 -18
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +3 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/lib/checks/unknownMagic.js +0 -41
package/lib/compiler/define.js
CHANGED
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
|
|
110
110
|
'use strict';
|
|
111
111
|
|
|
112
|
-
const {
|
|
112
|
+
const { forEachGeneric, forEachInOrder } = require('../base/model');
|
|
113
113
|
const {
|
|
114
114
|
dictAdd, dictAddArray, dictForEach, pushToDict,
|
|
115
115
|
} = require('../base/dictionaries');
|
|
@@ -147,6 +147,7 @@ function define( model ) {
|
|
|
147
147
|
} = model.$messageFunctions;
|
|
148
148
|
const {
|
|
149
149
|
resolveUncheckedPath,
|
|
150
|
+
checkAnnotate,
|
|
150
151
|
defineAnnotations,
|
|
151
152
|
} = model.$functions;
|
|
152
153
|
|
|
@@ -197,11 +198,12 @@ function define( model ) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// Phase 1: ----------------------------------------------------------------
|
|
201
|
+
// Functions called from top-level: addSource()
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
204
|
* Add definitions of the given source AST, both CDL and CSN
|
|
203
205
|
*
|
|
204
|
-
* @param {XSN.
|
|
206
|
+
* @param {XSN.SourceAst} src
|
|
205
207
|
*/
|
|
206
208
|
function addSource( src ) {
|
|
207
209
|
// handle sub model from parser
|
|
@@ -266,7 +268,7 @@ function define( model ) {
|
|
|
266
268
|
return;
|
|
267
269
|
}
|
|
268
270
|
setLink( art, '_block', block );
|
|
269
|
-
// dictAdd might set $duplicates
|
|
271
|
+
// dictAdd might set $duplicates
|
|
270
272
|
dictAdd( model.definitions, absolute, art );
|
|
271
273
|
}
|
|
272
274
|
|
|
@@ -306,7 +308,7 @@ function define( model ) {
|
|
|
306
308
|
* declaration.
|
|
307
309
|
*
|
|
308
310
|
* @param {XSN.Using} decl Node to be expanded and added to `src`
|
|
309
|
-
* @param {XSN.
|
|
311
|
+
* @param {XSN.SourceAst} src
|
|
310
312
|
*/
|
|
311
313
|
function addUsing( decl, src ) {
|
|
312
314
|
if (decl.usings) {
|
|
@@ -390,14 +392,17 @@ function define( model ) {
|
|
|
390
392
|
setLink( vocab, '_block', block );
|
|
391
393
|
const { name } = vocab;
|
|
392
394
|
if (!name.absolute)
|
|
393
|
-
name.absolute = prefix + name.path
|
|
395
|
+
name.absolute = prefix + pathName( name.path );
|
|
394
396
|
dictAdd( model.vocabularies, name.absolute, vocab );
|
|
395
397
|
}
|
|
396
398
|
|
|
397
399
|
// Phase 2 ("init") --------------------------------------------------------
|
|
400
|
+
// Functions called from top-level: initNamespaceAndUsing(), initArtifact(),
|
|
401
|
+
// initVocabulary()
|
|
398
402
|
|
|
399
403
|
function checkRedefinition( art ) {
|
|
400
|
-
if (!art.$duplicates
|
|
404
|
+
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
|
|
405
|
+
art.$errorReported === 'syntax-duplicate-annotate')
|
|
401
406
|
return;
|
|
402
407
|
if (art._main) {
|
|
403
408
|
error( 'duplicate-definition', [ art.name.location, art ], {
|
|
@@ -504,18 +509,6 @@ function define( model ) {
|
|
|
504
509
|
definitions[prefix] = parent;
|
|
505
510
|
initParentLink( parent, definitions );
|
|
506
511
|
}
|
|
507
|
-
if (art.kind !== 'namespace' &&
|
|
508
|
-
isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' )) {
|
|
509
|
-
let p = parent;
|
|
510
|
-
while (p && kindProperties[p.kind].artifacts)
|
|
511
|
-
p = p._parent;
|
|
512
|
-
if (p) {
|
|
513
|
-
error( 'subartifacts-not-supported', [ art.name.location, art ],
|
|
514
|
-
{ art: p, prop: 'deprecated._generatedEntityNameWithUnderscore' },
|
|
515
|
-
// eslint-disable-next-line max-len
|
|
516
|
-
'With the option $(PROP), no sub artifact can be defined for a non-context/service $(ART)' );
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
512
|
setLink( art, '_parent', parent );
|
|
520
513
|
if (!parent._subArtifacts)
|
|
521
514
|
setLink( parent, '_subArtifacts', Object.create(null) );
|
|
@@ -611,7 +604,17 @@ function define( model ) {
|
|
|
611
604
|
// Either expression (value), expand or new association (target && type)
|
|
612
605
|
else if (col.value || col.expand || (col.target && col.type)) {
|
|
613
606
|
setLink( col, '_block', parent._block );
|
|
614
|
-
defineAnnotations( col, col, parent._block );
|
|
607
|
+
defineAnnotations( col, col, parent._block );
|
|
608
|
+
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
609
|
+
if (col.doc)
|
|
610
|
+
warning( 'syntax-anno-ignored', [ col.doc.location, col ], { '#': 'doc' } );
|
|
611
|
+
|
|
612
|
+
// col.$annotations no available for CSN input, have to search.
|
|
613
|
+
// Warning about first annotation should be enough to avoid spam.
|
|
614
|
+
const firstAnno = Object.keys(col).find(key => key.startsWith('@'));
|
|
615
|
+
if (firstAnno)
|
|
616
|
+
warning( 'syntax-anno-ignored', [ col[firstAnno].name.location, col ] );
|
|
617
|
+
}
|
|
615
618
|
// TODO: allow sub queries? at least in top-level expand without parallel ref
|
|
616
619
|
if (columns)
|
|
617
620
|
initExprForQuery( col.value, parent );
|
|
@@ -670,6 +673,8 @@ function define( model ) {
|
|
|
670
673
|
* @param {object} exprOrPathElement starts w/ an expr but then subelem from .path or .where.args
|
|
671
674
|
*/
|
|
672
675
|
function approveExistsInChildren(exprOrPathElement) {
|
|
676
|
+
if (!exprOrPathElement) // may be null in case of parse error
|
|
677
|
+
return;
|
|
673
678
|
if (exprOrPathElement.$expected === 'exists')
|
|
674
679
|
exprOrPathElement.$expected = 'approved-exists';
|
|
675
680
|
// Drill down
|
|
@@ -880,9 +885,7 @@ function define( model ) {
|
|
|
880
885
|
|
|
881
886
|
/**
|
|
882
887
|
* Set property `_parent` for all elements in `parent` to `parent` and do so
|
|
883
|
-
* recursively for all sub elements.
|
|
884
|
-
* `name.component` of the element with the help of argument `prefix`
|
|
885
|
-
* (which is basically the component name of the `parent` element plus a dot).
|
|
888
|
+
* recursively for all sub elements.
|
|
886
889
|
*/
|
|
887
890
|
// If not for extensions: construct === parent
|
|
888
891
|
function initMembers( construct, parent, block, initExtensions = false ) {
|
|
@@ -1014,6 +1017,8 @@ function define( model ) {
|
|
|
1014
1017
|
setMemberParent( elem, name, parent, construct !== parent && prop );
|
|
1015
1018
|
// console.log(message( null, elem.location, elem, {}, 'Info', 'INIT').toString())
|
|
1016
1019
|
checkRedefinition( elem );
|
|
1020
|
+
if (elem.kind === 'annotate')
|
|
1021
|
+
checkAnnotate( elem, elem );
|
|
1017
1022
|
defineAnnotations( elem, elem, bl );
|
|
1018
1023
|
initMembers( elem, elem, bl, initExtensions );
|
|
1019
1024
|
|
|
@@ -1125,7 +1130,7 @@ function mergeI18nBlocks( model ) {
|
|
|
1125
1130
|
* Add the source's translations to the model. Warns if the sources translations
|
|
1126
1131
|
* do not match the ones from previous sources.
|
|
1127
1132
|
*
|
|
1128
|
-
* @param {XSN.
|
|
1133
|
+
* @param {XSN.SourceAst} src
|
|
1129
1134
|
*/
|
|
1130
1135
|
function initI18nFromSource( src ) {
|
|
1131
1136
|
for (const langKey of Object.keys( src.i18n )) {
|
package/lib/compiler/extend.js
CHANGED
|
@@ -33,6 +33,7 @@ function extend( model ) {
|
|
|
33
33
|
const {
|
|
34
34
|
resolvePath,
|
|
35
35
|
resolveUncheckedPath,
|
|
36
|
+
checkAnnotate,
|
|
36
37
|
defineAnnotations,
|
|
37
38
|
attachAndEmitValidNames,
|
|
38
39
|
checkDefinitions,
|
|
@@ -179,6 +180,10 @@ function extend( model ) {
|
|
|
179
180
|
checkDefinitions( ext, art, 'actions');
|
|
180
181
|
checkDefinitions( ext, art, 'params');
|
|
181
182
|
checkDefinitions( ext, art, 'columns');
|
|
183
|
+
if (ext.includes)
|
|
184
|
+
applyIncludes( ext, art ); // emits error if `includes` is set
|
|
185
|
+
if (ext.kind === 'annotate')
|
|
186
|
+
checkAnnotate( ext, art );
|
|
182
187
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
183
188
|
}
|
|
184
189
|
return true;
|
|
@@ -238,6 +243,8 @@ function extend( model ) {
|
|
|
238
243
|
art.includes = [ ...ext.includes ];
|
|
239
244
|
applyIncludes( ext, art );
|
|
240
245
|
}
|
|
246
|
+
if (ext.kind === 'annotate')
|
|
247
|
+
checkAnnotate( ext, art );
|
|
241
248
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
242
249
|
// TODO: do we allow to add elements with array of {...}? If yes, adapt
|
|
243
250
|
initMembers( ext, art, ext._block ); // might set _extend, _annotate
|
|
@@ -389,9 +396,13 @@ function extend( model ) {
|
|
|
389
396
|
for (const ext of exts) {
|
|
390
397
|
delete ext.name.path[0]._artifact; // get message for root
|
|
391
398
|
// TODO: make resolvePath('extend'/'annotate') ignore namespaces
|
|
392
|
-
|
|
399
|
+
// Don't try to apply annotations in the `localized.` namespace.
|
|
400
|
+
// That's done in `localized.js`.
|
|
401
|
+
if (!name.startsWith('localized.') &&
|
|
402
|
+
resolvePath( ext.name, ext.kind, ext )) { // should issue error/info
|
|
393
403
|
// should issue error for cds extensions (annotate ok)
|
|
394
404
|
if (art.kind === 'namespace') {
|
|
405
|
+
// TODO: Emit error if namespace is extended by non-definitions.
|
|
395
406
|
info( 'anno-namespace', [ ext.name.location, ext ], {},
|
|
396
407
|
'Namespaces can\'t be annotated' );
|
|
397
408
|
}
|
|
@@ -451,6 +462,12 @@ function extend( model ) {
|
|
|
451
462
|
* @param {XSN.Artifact} art
|
|
452
463
|
*/
|
|
453
464
|
function applyIncludes( ext, art ) {
|
|
465
|
+
if (kindProperties[art.kind].include !== true) {
|
|
466
|
+
error('extend-unexpected-include', [ ext.includes[0]?.location, ext ], { kind: art.kind },
|
|
467
|
+
'Can\'t extend $(KIND) with includes');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
454
471
|
if (!art._ancestors)
|
|
455
472
|
setLink( art, '_ancestors', [] ); // recursive array of includes
|
|
456
473
|
for (const ref of ext.includes) {
|
|
@@ -512,9 +529,7 @@ function extend( model ) {
|
|
|
512
529
|
const fioriAnno = art['@fiori.draft.enabled'];
|
|
513
530
|
const fioriEnabled = fioriAnno && (fioriAnno.val === undefined || fioriAnno.val);
|
|
514
531
|
|
|
515
|
-
const textsName =
|
|
516
|
-
? `${ art.name.absolute }_texts`
|
|
517
|
-
: `${ art.name.absolute }.texts`;
|
|
532
|
+
const textsName = `${ art.name.absolute }.texts`;
|
|
518
533
|
const textsEntity = model.definitions[textsName];
|
|
519
534
|
const localized = localizedData( art, textsEntity, fioriEnabled );
|
|
520
535
|
if (!localized)
|
|
@@ -657,8 +672,6 @@ function extend( model ) {
|
|
|
657
672
|
};
|
|
658
673
|
dictAdd( art.elements, 'ID_texts', textId );
|
|
659
674
|
}
|
|
660
|
-
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
661
|
-
setLink( art, '_base', base );
|
|
662
675
|
|
|
663
676
|
dictAdd( art.elements, 'locale', locale );
|
|
664
677
|
if (addTextsLanguageAssoc) {
|
|
@@ -831,9 +844,7 @@ function extend( model ) {
|
|
|
831
844
|
target = resolvePath( origin.targetAspect, 'compositionTarget', origin );
|
|
832
845
|
if (!target || !target.elements)
|
|
833
846
|
return;
|
|
834
|
-
const entityName =
|
|
835
|
-
? `${ base.name.absolute }_${ elem.name.id }`
|
|
836
|
-
: `${ base.name.absolute }.${ elem.name.id }`;
|
|
847
|
+
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
|
|
837
848
|
const entity = allowAspectComposition( target, elem, keys, entityName ) &&
|
|
838
849
|
createTargetEntity( target, elem, keys, entityName, base );
|
|
839
850
|
elem.target = {
|
|
@@ -965,8 +976,6 @@ function extend( model ) {
|
|
|
965
976
|
// even if target cardinality is 1..1
|
|
966
977
|
up.notNull = { location, val: true };
|
|
967
978
|
}
|
|
968
|
-
if (isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ))
|
|
969
|
-
setLink( art, '_base', base._base || base );
|
|
970
979
|
|
|
971
980
|
dictAdd( art.elements, 'up_', up);
|
|
972
981
|
addProxyElements( art, target.elements, 'aspect-composition', target.name && location );
|
|
@@ -82,8 +82,12 @@ function finalizeParseCdl( model ) {
|
|
|
82
82
|
for (const include of artifact.includes || [])
|
|
83
83
|
resolveUncheckedPath(include, 'include', main);
|
|
84
84
|
|
|
85
|
+
// define.js takes care that `target` is a ref and
|
|
86
|
+
// `targetAspect` is a structure.
|
|
85
87
|
if (artifact.target)
|
|
86
88
|
resolveUncheckedPath(artifact.target, 'target', main);
|
|
89
|
+
if (artifact.targetAspect)
|
|
90
|
+
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
87
91
|
|
|
88
92
|
if (artifact.from) {
|
|
89
93
|
const { from } = artifact;
|
|
@@ -93,12 +97,6 @@ function finalizeParseCdl( model ) {
|
|
|
93
97
|
resolveTypesForParseCdl(from, main);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
if (artifact.targetAspect) {
|
|
97
|
-
if (artifact.targetAspect.path)
|
|
98
|
-
resolveUncheckedPath(artifact.targetAspect, 'target', main);
|
|
99
|
-
resolveTypesForParseCdl(artifact.targetAspect, main);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
100
|
// Recursively go through all XSN properties. There are a few that need to be
|
|
103
101
|
// handled specifically. Refer to the code below this loop for details.
|
|
104
102
|
for (const prop in artifact) {
|
|
@@ -158,8 +156,6 @@ function finalizeParseCdl( model ) {
|
|
|
158
156
|
* @param {XSN.Artifact} user
|
|
159
157
|
*/
|
|
160
158
|
function resolveTypeUnchecked(artWithType, user) {
|
|
161
|
-
if (!artWithType.type)
|
|
162
|
-
return;
|
|
163
159
|
const root = artWithType.type.path && artWithType.type.path[0];
|
|
164
160
|
if (!root) // parse error
|
|
165
161
|
return;
|
|
@@ -187,7 +183,7 @@ function finalizeParseCdl( model ) {
|
|
|
187
183
|
let struct = artWithType;
|
|
188
184
|
while (struct.kind === 'element')
|
|
189
185
|
struct = struct._parent;
|
|
190
|
-
if (struct.kind === 'select' || struct !== user._main) {
|
|
186
|
+
if (struct.kind === 'select' || struct.kind === 'annotation' || struct !== user._main) {
|
|
191
187
|
message( 'type-unexpected-typeof', [ artWithType.type.location, user ],
|
|
192
188
|
{ keyword: 'type of', '#': struct.kind } );
|
|
193
189
|
return;
|
package/lib/compiler/index.js
CHANGED
|
@@ -84,6 +84,8 @@ function parseX( source, filename, options = {}, messageFunctions = null ) {
|
|
|
84
84
|
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
85
85
|
if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
|
|
86
86
|
return parseLanguage( source, filename, options, messageFunctions );
|
|
87
|
+
if (source.startsWith('{')) // Source may be JSON.
|
|
88
|
+
return parseCsn.parse( source, filename, options, messageFunctions );
|
|
87
89
|
|
|
88
90
|
const model = { location: { file: filename } };
|
|
89
91
|
messageFunctions.error( 'file-unknown-ext', emptyWeakLocation(filename),
|
package/lib/compiler/populate.js
CHANGED
|
@@ -73,14 +73,8 @@ function populate( model ) {
|
|
|
73
73
|
/** @type {any} may also be a boolean */
|
|
74
74
|
let newAutoExposed = [];
|
|
75
75
|
|
|
76
|
-
// behavior depending on option `deprecated`:
|
|
77
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
78
|
-
// TODO: we should get rid of noElementsExpansion soon; both
|
|
79
|
-
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
80
76
|
const scopedRedirections
|
|
81
|
-
=
|
|
82
|
-
!isDeprecatedEnabled( options, '_generatedEntityNameWithUnderscore' ) &&
|
|
83
|
-
!isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
|
|
77
|
+
= !isDeprecatedEnabled( options, '_shortAutoexposed' ) &&
|
|
84
78
|
!isDeprecatedEnabled( options, '_longAutoexposed' ) &&
|
|
85
79
|
!isDeprecatedEnabled( options, '_noInheritedAutoexposeViaComposition' ) &&
|
|
86
80
|
!isDeprecatedEnabled( options, '_noScopedRedirections' );
|
|
@@ -109,6 +103,8 @@ function populate( model ) {
|
|
|
109
103
|
function traverseElementEnvironments( art ) {
|
|
110
104
|
populateView( art );
|
|
111
105
|
environment( art );
|
|
106
|
+
if (art.elements$)
|
|
107
|
+
mergeSpecifiedElements(art);
|
|
112
108
|
forEachMember( art, traverseElementEnvironments );
|
|
113
109
|
}
|
|
114
110
|
|
|
@@ -165,7 +161,7 @@ function populate( model ) {
|
|
|
165
161
|
// console.log(message( null, art.location, art, {}, 'Info','FT').toString())
|
|
166
162
|
const chain = [];
|
|
167
163
|
while (art && !('_effectiveType' in art) &&
|
|
168
|
-
(art.type || art._origin || art.value
|
|
164
|
+
(art.type || art._origin || art.value?.path || art.value?.type) &&
|
|
169
165
|
// TODO: really stop at art.enum? See #8942
|
|
170
166
|
!art.target && !art.enum && !art.elements && !art.items) {
|
|
171
167
|
chain.push( art );
|
|
@@ -195,7 +191,7 @@ function populate( model ) {
|
|
|
195
191
|
let eType = art;
|
|
196
192
|
if (eType._outer)
|
|
197
193
|
eType = effectiveType( eType._outer );
|
|
198
|
-
// collect the "latest" cardinality (calculate
|
|
194
|
+
// collect the "latest" cardinality (calculate lazily if necessary)
|
|
199
195
|
let cardinality = art.cardinality ||
|
|
200
196
|
art._effectiveType && (() => getCardinality( art._effectiveType ));
|
|
201
197
|
let prev = art;
|
|
@@ -222,10 +218,12 @@ function populate( model ) {
|
|
|
222
218
|
return art._origin;
|
|
223
219
|
if (art.type)
|
|
224
220
|
return resolveType( art.type, art );
|
|
221
|
+
if (art.value?.type)
|
|
222
|
+
return resolveType( art.value.type, art );
|
|
225
223
|
// console.log( 'EXPR-IN', art.kind, refString(art.name) )
|
|
226
224
|
if (!art._main || !art.value || !art.value.path)
|
|
227
225
|
return undefined;
|
|
228
|
-
if (art._pathHead && art.value) {
|
|
226
|
+
if (art._pathHead && art.value.path) {
|
|
229
227
|
setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
|
|
230
228
|
return art._origin;
|
|
231
229
|
}
|
|
@@ -246,7 +244,9 @@ function populate( model ) {
|
|
|
246
244
|
let struct = user;
|
|
247
245
|
while (struct.kind === 'element')
|
|
248
246
|
struct = struct._parent;
|
|
249
|
-
if (struct.kind === 'select') {
|
|
247
|
+
if (struct.kind === 'select' || struct.kind === 'annotation') {
|
|
248
|
+
// `type of` in annotation definitions can't work, because csn type refs
|
|
249
|
+
// always refer to definitions.
|
|
250
250
|
message( 'type-unexpected-typeof', [ ref.location, user ],
|
|
251
251
|
{ keyword: 'type of', '#': struct.kind } );
|
|
252
252
|
// we actually refer to an element in _combined; TODO: return null if
|
|
@@ -293,7 +293,7 @@ function populate( model ) {
|
|
|
293
293
|
|
|
294
294
|
|
|
295
295
|
function expandItems( art, origin, eType ) {
|
|
296
|
-
if (
|
|
296
|
+
if (art.items)
|
|
297
297
|
return false;
|
|
298
298
|
if (isInParents( art, eType )) {
|
|
299
299
|
art.items = 0; // circular
|
|
@@ -310,8 +310,6 @@ function populate( model ) {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
function expandElements( art, struct, eType ) {
|
|
313
|
-
if (!enableExpandElements)
|
|
314
|
-
return false;
|
|
315
313
|
if (art.elements || art.kind === '$tableAlias' ||
|
|
316
314
|
// no element expansions for "non-proper" types like
|
|
317
315
|
// entities (as parameter types) etc:
|
|
@@ -345,7 +343,7 @@ function populate( model ) {
|
|
|
345
343
|
}
|
|
346
344
|
|
|
347
345
|
function expandEnum( art, origin ) {
|
|
348
|
-
if (
|
|
346
|
+
if (art.enum)
|
|
349
347
|
return false;
|
|
350
348
|
const ref = art.type || art.value || art.name;
|
|
351
349
|
const location = weakLocation( ref && ref.location || art.location );
|
|
@@ -425,8 +423,6 @@ function populate( model ) {
|
|
|
425
423
|
setLink( view, '_status', '_query' );
|
|
426
424
|
// must be run in order “sub query in FROM first”:
|
|
427
425
|
traverseQueryPost( view.query, null, populateQuery );
|
|
428
|
-
if (view.elements$) // specified elements
|
|
429
|
-
mergeSpecifiedElements( view );
|
|
430
426
|
if (!view.$entity) {
|
|
431
427
|
model._entities.push( view );
|
|
432
428
|
view.$entity = ++model.$entity;
|
|
@@ -435,14 +431,25 @@ function populate( model ) {
|
|
|
435
431
|
}
|
|
436
432
|
}
|
|
437
433
|
|
|
438
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Merge _specified_ elements with _inferred_ elements in the given view/element,
|
|
436
|
+
* where specified elements can appear through CSN.
|
|
437
|
+
*
|
|
438
|
+
* We only copy annotations, since they are not part of `columns`,
|
|
439
|
+
* but only appear in `elements` in CSN.
|
|
440
|
+
*
|
|
441
|
+
* This is important to ensure re-compilability.
|
|
442
|
+
*
|
|
443
|
+
* @param art
|
|
444
|
+
*/
|
|
445
|
+
function mergeSpecifiedElements( art ) {
|
|
439
446
|
// Later we use specified elements as proxies to inferred of leading query
|
|
440
447
|
// (No, we probably do not.)
|
|
441
|
-
for (const id in
|
|
442
|
-
const ielem =
|
|
443
|
-
const selem =
|
|
448
|
+
for (const id in art.elements) {
|
|
449
|
+
const ielem = art.elements[id]; // inferred element
|
|
450
|
+
const selem = art.elements$[id]; // specified element
|
|
444
451
|
if (!selem) {
|
|
445
|
-
info( 'query-missing-element', [ ielem.name.location,
|
|
452
|
+
info( 'query-missing-element', [ ielem.name.location, art ], { id },
|
|
446
453
|
'Element $(ID) is missing in specified elements' );
|
|
447
454
|
}
|
|
448
455
|
else {
|
|
@@ -452,10 +459,14 @@ function populate( model ) {
|
|
|
452
459
|
ielem[prop] = selem[prop];
|
|
453
460
|
}
|
|
454
461
|
selem.$replacement = true;
|
|
462
|
+
if (selem.elements) {
|
|
463
|
+
setLink(ielem, 'elements$', selem.elements);
|
|
464
|
+
delete selem.elements;
|
|
465
|
+
}
|
|
455
466
|
}
|
|
456
467
|
}
|
|
457
|
-
for (const id in
|
|
458
|
-
const selem =
|
|
468
|
+
for (const id in art.elements$) {
|
|
469
|
+
const selem = art.elements$[id]; // specified element
|
|
459
470
|
if (!selem.$replacement) {
|
|
460
471
|
error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
|
|
461
472
|
'Element $(ID) does not result from the query' );
|
|
@@ -472,8 +483,6 @@ function populate( model ) {
|
|
|
472
483
|
forEachGeneric( query, '$tableAliases', resolveTabRef );
|
|
473
484
|
|
|
474
485
|
initFromColumns( query, query.columns );
|
|
475
|
-
// TODO: already in definer: complain about EXCLUDING with no wildcard
|
|
476
|
-
// (would have been automatically with a good CDL syntax: `* without (...)`)
|
|
477
486
|
if (query.excludingDict) {
|
|
478
487
|
for (const name in query.excludingDict)
|
|
479
488
|
resolveExcluding( name, query._combined, query.excludingDict, query );
|
|
@@ -1037,8 +1046,6 @@ function populate( model ) {
|
|
|
1037
1046
|
// which is not a context/service/namespace, or the definition itself.
|
|
1038
1047
|
// If inside service, it is the direct child of the (most inner) service.
|
|
1039
1048
|
function definitionScope( art ) {
|
|
1040
|
-
if (art._base) // with deprecated.generatedEntityNameWithUnderscore
|
|
1041
|
-
return art._base;
|
|
1042
1049
|
let base = art;
|
|
1043
1050
|
while (art._parent) {
|
|
1044
1051
|
if (art._parent.kind === 'service')
|
|
@@ -1110,10 +1117,8 @@ function populate( model ) {
|
|
|
1110
1117
|
// the name for dependent entities have already been created using `_` then
|
|
1111
1118
|
return `${ service.name.absolute }.${ name }`;
|
|
1112
1119
|
}
|
|
1113
|
-
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
|
|
1114
|
-
|
|
1115
|
-
return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
|
|
1116
|
-
}
|
|
1120
|
+
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
|
|
1121
|
+
return `${ service.name.absolute }.${ absolute }`;
|
|
1117
1122
|
const base = definitionScope( target );
|
|
1118
1123
|
if (base === target)
|
|
1119
1124
|
return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Propagate properties in XSN
|
|
2
|
+
|
|
3
|
+
// See also internalDoc/PropagatedCsn.md.
|
|
4
|
+
// As opposed to that document, the propagator here works on XSN, not CSN.
|
|
5
|
+
// We also do not deep-copy member dictionaries here, but create proxy members
|
|
6
|
+
// which get their properties via propagation: we use function `onlyViaParent`
|
|
7
|
+
// if that property would not be propagated otherwise.
|
|
2
8
|
|
|
3
9
|
'use strict';
|
|
4
10
|
|
|
@@ -11,6 +17,7 @@ const {
|
|
|
11
17
|
const { setLink, linkToOrigin, withAssociation } = require('./utils');
|
|
12
18
|
// const { refString } = require( '../base/messages')
|
|
13
19
|
|
|
20
|
+
// Note that propagation here is also used for deep-copying (function `onlyViaParent`)
|
|
14
21
|
function propagate( model ) {
|
|
15
22
|
const props = {
|
|
16
23
|
'@com.sap.gtt.core.CoreModel.Indexable': never,
|
|
@@ -56,7 +63,6 @@ function propagate( model ) {
|
|
|
56
63
|
returns,
|
|
57
64
|
};
|
|
58
65
|
const { options } = model;
|
|
59
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
60
66
|
// eslint-disable-next-line max-len
|
|
61
67
|
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, '_oldVirtualNotNullPropagation' );
|
|
62
68
|
|
|
@@ -189,7 +195,7 @@ function propagate( model ) {
|
|
|
189
195
|
if (!type || type._main)
|
|
190
196
|
return false;
|
|
191
197
|
// We do not consider the $expand status, as elements are already expanded
|
|
192
|
-
// by the resolve()
|
|
198
|
+
// by the resolve()
|
|
193
199
|
run( type );
|
|
194
200
|
return type[prop];
|
|
195
201
|
}
|
|
@@ -313,11 +319,10 @@ function propagate( model ) {
|
|
|
313
319
|
|
|
314
320
|
function items( prop, target, source ) {
|
|
315
321
|
// usually considered expensive, except:
|
|
316
|
-
// - array of Entity
|
|
322
|
+
// - array of Entity
|
|
317
323
|
const line = availableAtType( prop, target, source );
|
|
318
324
|
if (!line ||
|
|
319
|
-
line.type && line.type._artifact && line.type._artifact.kind === 'entity'
|
|
320
|
-
!line.elements && !line.enum && !line.items && !enableExpandElements)
|
|
325
|
+
line.type && line.type._artifact && line.type._artifact.kind === 'entity')
|
|
321
326
|
returns( prop, target, source, true );
|
|
322
327
|
}
|
|
323
328
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -87,6 +87,7 @@ function resolve( model ) {
|
|
|
87
87
|
} = model.$messageFunctions;
|
|
88
88
|
const {
|
|
89
89
|
resolvePath,
|
|
90
|
+
checkAnnotate,
|
|
90
91
|
defineAnnotations,
|
|
91
92
|
attachAndEmitValidNames,
|
|
92
93
|
lateExtensions,
|
|
@@ -103,11 +104,6 @@ function resolve( model ) {
|
|
|
103
104
|
|
|
104
105
|
/** @type {any} may also be a boolean */
|
|
105
106
|
|
|
106
|
-
// behavior depending on option `deprecated`:
|
|
107
|
-
const enableExpandElements = !isDeprecatedEnabled( options, '_noElementsExpansion' );
|
|
108
|
-
// TODO: we should get rid of noElementsExpansion soon; both
|
|
109
|
-
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
110
|
-
|
|
111
107
|
return doResolve();
|
|
112
108
|
|
|
113
109
|
function doResolve() {
|
|
@@ -314,7 +310,7 @@ function resolve( model ) {
|
|
|
314
310
|
if (obj.type) // TODO: && !obj.type.$inferred ?
|
|
315
311
|
resolveTypeExpr( obj, art );
|
|
316
312
|
const type = effectiveType( obj ); // make sure implicitly redirected target exists
|
|
317
|
-
if (!obj.items && type && type.items
|
|
313
|
+
if (!obj.items && type && type.items) {
|
|
318
314
|
// TODO: shouldn't be this part of populate.js ?
|
|
319
315
|
const items = {
|
|
320
316
|
location: weakLocation( (obj.type || obj).location ),
|
|
@@ -327,8 +323,7 @@ function resolve( model ) {
|
|
|
327
323
|
}
|
|
328
324
|
if (obj.items) { // TODO: make this a while in v2 (also items proxy)
|
|
329
325
|
obj = obj.items || obj; // the object which has type properties
|
|
330
|
-
|
|
331
|
-
effectiveType(obj);
|
|
326
|
+
effectiveType(obj);
|
|
332
327
|
}
|
|
333
328
|
if (obj.type) { // TODO: && !obj.type.$inferred ?
|
|
334
329
|
if (obj !== (art.returns || art)) // not already checked
|
|
@@ -481,10 +476,7 @@ function resolve( model ) {
|
|
|
481
476
|
if (ext.$extension) // extension for known artifact -> already applied
|
|
482
477
|
return;
|
|
483
478
|
annotateMembers( ext );
|
|
484
|
-
|
|
485
|
-
if (prop.charAt(0) === '@')
|
|
486
|
-
chooseAssignment( prop, ext );
|
|
487
|
-
}
|
|
479
|
+
chooseAnnotationsInArtifact( ext );
|
|
488
480
|
}
|
|
489
481
|
|
|
490
482
|
/**
|
|
@@ -519,6 +511,8 @@ function resolve( model ) {
|
|
|
519
511
|
setArtifactLink( ext.name, art );
|
|
520
512
|
|
|
521
513
|
if (art) {
|
|
514
|
+
if (art.kind === 'annotate')
|
|
515
|
+
checkAnnotate( ext, art );
|
|
522
516
|
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
523
517
|
// eslint-disable-next-line no-shadow
|
|
524
518
|
forEachMember( ext, ( elem, name, prop ) => {
|
|
@@ -541,7 +535,7 @@ function resolve( model ) {
|
|
|
541
535
|
}
|
|
542
536
|
else if (prop === 'actions') {
|
|
543
537
|
if (!feature) {
|
|
544
|
-
warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
|
|
538
|
+
warning( 'anno-unexpected-actions', [ ext.name.location, art._parent || art ], {},
|
|
545
539
|
'Actions and functions only exist top-level and for entities' );
|
|
546
540
|
}
|
|
547
541
|
else {
|
|
@@ -572,7 +566,7 @@ function resolve( model ) {
|
|
|
572
566
|
// Currently(?), effectiveType() does not calculate the effective type of
|
|
573
567
|
// its line item:
|
|
574
568
|
effectiveType( obj );
|
|
575
|
-
if (art._annotate.elements)
|
|
569
|
+
if (art._annotate.elements) // explicit $expand on aor needed
|
|
576
570
|
setExpandStatusAnnotate( aor, 'annotate' );
|
|
577
571
|
annotate( obj, 'element', 'elements', 'enum', art );
|
|
578
572
|
annotate( art, 'action', 'actions' );
|
|
@@ -596,6 +590,8 @@ function resolve( model ) {
|
|
|
596
590
|
// eslint-disable-next-line no-shadow
|
|
597
591
|
function annotate( obj, kind, prop, altProp, parent = obj ) {
|
|
598
592
|
const dict = art._annotate[prop];
|
|
593
|
+
if (dict && art._annotate[prop])
|
|
594
|
+
setExpandStatusAnnotate( art, 'annotate' );
|
|
599
595
|
const env = obj[prop] || altProp && obj[altProp] || null;
|
|
600
596
|
for (const n in dict)
|
|
601
597
|
annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
|
|
@@ -618,7 +614,7 @@ function resolve( model ) {
|
|
|
618
614
|
|
|
619
615
|
function expandParameters( action ) {
|
|
620
616
|
// see also expandElements()
|
|
621
|
-
if (!
|
|
617
|
+
if (!effectiveType( action ))
|
|
622
618
|
return;
|
|
623
619
|
const chain = [];
|
|
624
620
|
// Should we be able to consider params and returns separately?
|
|
@@ -959,7 +955,7 @@ function resolve( model ) {
|
|
|
959
955
|
resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
|
|
960
956
|
if (query.orderBy) { // ORDER BY
|
|
961
957
|
// search in `query.elements` after having checked table aliases of the current query
|
|
962
|
-
resolveBy( query.orderBy, '
|
|
958
|
+
resolveBy( query.orderBy, 'order-by', query.elements );
|
|
963
959
|
// TODO: disallow resulting element ref if in expression!
|
|
964
960
|
// Necessary to check it in the compiler as it might work with other semantics on DB!
|
|
965
961
|
// (we could downgrade it to a warning if name is equal to unique source element name)
|
|
@@ -1267,7 +1263,7 @@ function resolve( model ) {
|
|
|
1267
1263
|
const typeArt = resolveType( art.type, user );
|
|
1268
1264
|
if (typeArt) {
|
|
1269
1265
|
resolveTypeArgumentsUnchecked( art, typeArt, user );
|
|
1270
|
-
checkTypeArguments( art );
|
|
1266
|
+
checkTypeArguments( art, typeArt );
|
|
1271
1267
|
}
|
|
1272
1268
|
}
|
|
1273
1269
|
|
|
@@ -1275,13 +1271,13 @@ function resolve( model ) {
|
|
|
1275
1271
|
* Check the type arguments on `artWithType`.
|
|
1276
1272
|
* If the effective type is an array or structured type, an error is emitted.
|
|
1277
1273
|
*/
|
|
1278
|
-
function checkTypeArguments( artWithType ) {
|
|
1274
|
+
function checkTypeArguments( artWithType, typeArt ) {
|
|
1279
1275
|
// Note: `_effectiveType` may point to `artWithType` itself, if the type is structured.
|
|
1280
1276
|
// Also: For enums, it points to the enum type, which is why this trick is needed.
|
|
1281
1277
|
// TODO(#8942): May not be necessary if effectiveType() is adapted. Furthermore, the enum
|
|
1282
1278
|
// trick may be removed if effectiveType() does not stop at enums.
|
|
1283
1279
|
const cyclic = new Set();
|
|
1284
|
-
let effectiveTypeArt = effectiveType(
|
|
1280
|
+
let effectiveTypeArt = effectiveType( typeArt );
|
|
1285
1281
|
while (effectiveTypeArt && effectiveTypeArt.enum && !cyclic.has(effectiveTypeArt)) {
|
|
1286
1282
|
cyclic.add(effectiveTypeArt);
|
|
1287
1283
|
const underlyingEnumType = directType(effectiveTypeArt);
|