@sap/cds-compiler 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/bin/cdsc.js +14 -6
- package/doc/CHANGELOG_ARCHIVE.md +10 -10
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +32 -55
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +104 -32
- package/lib/base/messages.js +277 -212
- package/lib/base/model.js +33 -22
- package/lib/base/optionProcessorHelper.js +9 -2
- package/lib/base/shuffle.js +50 -0
- package/lib/checks/actionsFunctions.js +37 -20
- package/lib/checks/foreignKeys.js +13 -6
- package/lib/checks/nonexpandableStructured.js +1 -2
- package/lib/checks/onConditions.js +21 -19
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -0
- package/lib/checks/types.js +16 -22
- package/lib/compiler/assert-consistency.js +31 -28
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +72 -63
- package/lib/compiler/define.js +396 -314
- package/lib/compiler/extend.js +55 -49
- package/lib/compiler/index.js +5 -0
- package/lib/compiler/populate.js +28 -11
- package/lib/compiler/propagator.js +2 -1
- package/lib/compiler/resolve.js +29 -20
- package/lib/compiler/shared.js +15 -10
- package/lib/compiler/utils.js +7 -7
- package/lib/edm/annotations/genericTranslation.js +51 -46
- package/lib/edm/annotations/preprocessAnnotations.js +39 -42
- package/lib/edm/csn2edm.js +69 -21
- package/lib/edm/edm.js +2 -2
- package/lib/edm/edmInboundChecks.js +6 -8
- package/lib/edm/edmPreprocessor.js +88 -80
- package/lib/edm/edmUtils.js +6 -15
- package/lib/gen/Dictionary.json +81 -13
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4680 -4484
- package/lib/inspect/inspectModelStatistics.js +2 -1
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +131 -78
- package/lib/json/to-csn.js +39 -23
- package/lib/language/antlrParser.js +0 -3
- package/lib/language/docCommentParser.js +7 -3
- package/lib/language/errorStrategy.js +3 -2
- package/lib/language/genericAntlrParser.js +96 -41
- package/lib/language/language.g4 +112 -128
- package/lib/language/multiLineStringParser.js +2 -1
- package/lib/main.d.ts +115 -2
- package/lib/main.js +16 -3
- package/lib/model/csnRefs.js +3 -3
- package/lib/model/csnUtils.js +109 -179
- package/lib/model/enrichCsn.js +13 -8
- package/lib/model/revealInternalProperties.js +4 -3
- package/lib/optionProcessor.js +23 -3
- package/lib/render/manageConstraints.js +11 -15
- package/lib/render/toCdl.js +144 -47
- package/lib/render/toHdbcds.js +22 -22
- package/lib/render/toRename.js +3 -4
- package/lib/render/toSql.js +29 -20
- package/lib/render/utils/delta.js +3 -1
- package/lib/render/utils/sql.js +3 -16
- package/lib/transform/db/associations.js +6 -6
- package/lib/transform/db/cdsPersistence.js +3 -3
- package/lib/transform/db/constraints.js +8 -8
- package/lib/transform/db/expansion.js +4 -4
- package/lib/transform/db/flattening.js +12 -15
- package/lib/transform/db/temporal.js +4 -3
- package/lib/transform/db/transformExists.js +2 -1
- package/lib/transform/draft/db.js +7 -7
- package/lib/transform/forOdataNew.js +15 -4
- package/lib/transform/forRelationalDB.js +53 -39
- package/lib/transform/odata/toFinalBaseType.js +106 -82
- package/lib/transform/odata/typesExposure.js +26 -17
- package/lib/transform/odata/utils.js +1 -1
- package/lib/transform/parseExpr.js +1 -1
- package/lib/transform/transformUtilsNew.js +33 -10
- package/lib/transform/translateAssocsToJoins.js +8 -7
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
- package/lib/utils/timetrace.js +2 -2
- package/package.json +1 -2
package/lib/compiler/extend.js
CHANGED
|
@@ -54,7 +54,7 @@ function extend( model ) {
|
|
|
54
54
|
applyExtensions();
|
|
55
55
|
|
|
56
56
|
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options);
|
|
57
|
-
const
|
|
57
|
+
const useTextsAspect = checkTextsAspect();
|
|
58
58
|
|
|
59
59
|
Object.keys( model.definitions ).forEach( processArtifact );
|
|
60
60
|
|
|
@@ -301,38 +301,24 @@ function extend( model ) {
|
|
|
301
301
|
*
|
|
302
302
|
* @return {boolean}
|
|
303
303
|
*/
|
|
304
|
-
function
|
|
304
|
+
function checkTextsAspect() {
|
|
305
305
|
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
let hasError = false;
|
|
309
|
-
|
|
310
|
-
if (textsAspect) {
|
|
311
|
-
const specialElements = { locale: { key: true } };
|
|
312
|
-
if (!checkTextsAspect(textsAspect, specialElements))
|
|
313
|
-
hasError = true;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (fioriTextsAspect) {
|
|
317
|
-
const specialElements = { ID_texts: { key: true }, locale: { key: false } };
|
|
318
|
-
if (!checkTextsAspect(fioriTextsAspect, specialElements))
|
|
319
|
-
hasError = true;
|
|
320
|
-
}
|
|
306
|
+
if (!textsAspect)
|
|
307
|
+
return false;
|
|
321
308
|
|
|
322
|
-
|
|
323
|
-
}
|
|
309
|
+
const specialElements = { locale: { key: true } };
|
|
324
310
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
311
|
+
if (textsAspect.kind !== 'aspect' || !textsAspect.elements) {
|
|
312
|
+
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
313
|
+
{ '#': 'no-aspect', art: textsAspect });
|
|
328
314
|
return false;
|
|
329
315
|
}
|
|
330
316
|
|
|
331
317
|
let hasError = false;
|
|
332
|
-
if (addTextsLanguageAssoc &&
|
|
333
|
-
const lang =
|
|
318
|
+
if (addTextsLanguageAssoc && textsAspect.elements.language) {
|
|
319
|
+
const lang = textsAspect.elements.language;
|
|
334
320
|
error('def-unexpected-element', [ lang.name.location, lang ],
|
|
335
|
-
{ option: 'addTextsLanguageAssoc', art, name: 'language' },
|
|
321
|
+
{ option: 'addTextsLanguageAssoc', art: textsAspect, name: 'language' },
|
|
336
322
|
// eslint-disable-next-line max-len
|
|
337
323
|
'$(ART) is not used because option $(OPTION) conflicts with existing element $(NAME); remove either option or element' );
|
|
338
324
|
hasError = true;
|
|
@@ -340,14 +326,14 @@ function extend( model ) {
|
|
|
340
326
|
|
|
341
327
|
for (const name in specialElements) {
|
|
342
328
|
const expected = specialElements[name];
|
|
343
|
-
const elem =
|
|
329
|
+
const elem = textsAspect.elements[name];
|
|
344
330
|
if (!elem) {
|
|
345
|
-
error('def-invalid-texts-aspect', [
|
|
346
|
-
{ '#': 'missing', art, name });
|
|
331
|
+
error('def-invalid-texts-aspect', [ textsAspect.name.location, textsAspect ],
|
|
332
|
+
{ '#': 'missing', art: textsAspect, name });
|
|
347
333
|
hasError = true;
|
|
348
334
|
}
|
|
349
335
|
else if (expected.key !== undefined && !!elem.key?.val !== expected.key) {
|
|
350
|
-
const loc = elem.key?.location || elem.name?.location ||
|
|
336
|
+
const loc = elem.key?.location || elem.name?.location || textsAspect.name.location;
|
|
351
337
|
error('def-invalid-texts-aspect', [ loc, elem ],
|
|
352
338
|
{ '#': expected.key ? 'key' : 'no-key', art: elem });
|
|
353
339
|
hasError = true;
|
|
@@ -357,13 +343,13 @@ function extend( model ) {
|
|
|
357
343
|
if (hasError) // avoid subsequent errors, if the special elements are already wrong
|
|
358
344
|
return false;
|
|
359
345
|
|
|
360
|
-
for (const name in
|
|
361
|
-
const elem =
|
|
346
|
+
for (const name in textsAspect.elements) {
|
|
347
|
+
const elem = textsAspect.elements[name];
|
|
362
348
|
const include = elem.$inferred === 'include';
|
|
363
349
|
if (!specialElements[name] && elem.key) {
|
|
364
350
|
const loc = include ? elem.location : elem.key.location;
|
|
365
351
|
error( 'def-unexpected-key', [ loc, elem ],
|
|
366
|
-
{ '#': !include ? 'std' : 'include', art } );
|
|
352
|
+
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
367
353
|
hasError = true;
|
|
368
354
|
}
|
|
369
355
|
else if (hasTruthyProp( elem, 'localized' )) {
|
|
@@ -371,11 +357,12 @@ function extend( model ) {
|
|
|
371
357
|
// Not supported anyway, but important for recompilation (which fails correctly).
|
|
372
358
|
const loc = elem.localized?.location || elem.location;
|
|
373
359
|
error( 'def-unexpected-localized', [ loc, elem ],
|
|
374
|
-
{ '#': !include ? 'std' : 'include', art } );
|
|
360
|
+
{ '#': !include ? 'std' : 'include', art: textsAspect } );
|
|
375
361
|
hasError = true;
|
|
376
362
|
}
|
|
377
363
|
else if (elem.targetAspect) {
|
|
378
|
-
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
|
|
364
|
+
error( 'def-unexpected-composition', [ elem.targetAspect.location, elem ],
|
|
365
|
+
{ art: textsAspect },
|
|
379
366
|
'$(ART) can\'t have composition of aspects' );
|
|
380
367
|
hasError = true;
|
|
381
368
|
}
|
|
@@ -607,7 +594,7 @@ function extend( model ) {
|
|
|
607
594
|
for (const ext of exts)
|
|
608
595
|
info( 'anno-builtin', [ ext.name.location, ext ] );
|
|
609
596
|
}
|
|
610
|
-
// created texts entity,
|
|
597
|
+
// created texts entity, auto-exposed entity
|
|
611
598
|
if (exts) {
|
|
612
599
|
extendArtifact( exts, art, 'gen' );
|
|
613
600
|
if (veryLate)
|
|
@@ -728,13 +715,12 @@ function extend( model ) {
|
|
|
728
715
|
}
|
|
729
716
|
|
|
730
717
|
/**
|
|
731
|
-
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]
|
|
732
|
-
*
|
|
733
|
-
* Included members are prepended to existing ones.
|
|
718
|
+
* Add all members (e.g. elements or actions) of `ext.includes` to `ext[prop]`.
|
|
719
|
+
* If `art` is `ext`, set the parent link accordingly.
|
|
734
720
|
*
|
|
735
721
|
* @param {XSN.Extension} ext
|
|
736
722
|
* @param {XSN.Artifact} art
|
|
737
|
-
* @param {string} prop
|
|
723
|
+
* @param {string} prop: 'elements' or 'actions'
|
|
738
724
|
*/
|
|
739
725
|
function includeMembers( ext, art, prop ) {
|
|
740
726
|
// TODO two kind of messages:
|
|
@@ -892,10 +878,7 @@ function extend( model ) {
|
|
|
892
878
|
* @param {boolean} fioriEnabled
|
|
893
879
|
*/
|
|
894
880
|
function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
|
|
895
|
-
const
|
|
896
|
-
const withTextsAspect = useTextsAspects && model.definitions[name];
|
|
897
|
-
|
|
898
|
-
const art = withTextsAspect
|
|
881
|
+
const art = useTextsAspect
|
|
899
882
|
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
|
|
900
883
|
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
|
|
901
884
|
|
|
@@ -955,7 +938,16 @@ function extend( model ) {
|
|
|
955
938
|
applyIncludes(art, art);
|
|
956
939
|
}
|
|
957
940
|
|
|
958
|
-
if (fioriEnabled
|
|
941
|
+
if (fioriEnabled) {
|
|
942
|
+
// The includes mechanism puts TextsAspect's elements before .texts' elements.
|
|
943
|
+
// Because ID_texts is not copied from TextsAspect, the order is messed
|
|
944
|
+
// up. Fix it.
|
|
945
|
+
const { elements } = art;
|
|
946
|
+
art.elements = Object.create(null);
|
|
947
|
+
const names = [ 'ID_texts', 'locale', ...Object.keys(elements) ];
|
|
948
|
+
for (const name of names)
|
|
949
|
+
art.elements[name] = elements[name];
|
|
950
|
+
|
|
959
951
|
const { locale } = art.elements;
|
|
960
952
|
assertUniqueValue.unshift({
|
|
961
953
|
path: [ { id: locale.name.id, location: locale.location } ],
|
|
@@ -975,21 +967,19 @@ function extend( model ) {
|
|
|
975
967
|
*
|
|
976
968
|
* Does NOT apply the include!
|
|
977
969
|
*
|
|
978
|
-
* TODO: When beta flag textsAspect is removed, update caller-site and remove old coding.
|
|
979
|
-
*
|
|
980
970
|
* @param {XSN.Artifact} base
|
|
981
971
|
* @param {string} absolute
|
|
982
972
|
* @param {boolean} fioriEnabled
|
|
983
973
|
*/
|
|
984
974
|
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
|
|
975
|
+
const textsAspectName = 'sap.common.TextsAspect';
|
|
976
|
+
const textsAspect = model.definitions['sap.common.TextsAspect'];
|
|
985
977
|
const elements = Object.create(null);
|
|
986
978
|
const { location } = base.name;
|
|
987
|
-
|
|
988
|
-
const include = fioriEnabled ? 'sap.common.FioriTextsAspect' : 'sap.common.TextsAspect';
|
|
989
979
|
const art = {
|
|
990
980
|
kind: 'entity',
|
|
991
981
|
name: { path: splitIntoPath( location, absolute ), absolute, location },
|
|
992
|
-
includes: [ createInclude(
|
|
982
|
+
includes: [ createInclude( textsAspectName, base.location ) ],
|
|
993
983
|
location: base.location,
|
|
994
984
|
elements,
|
|
995
985
|
$inferred: 'localized-entity',
|
|
@@ -1000,6 +990,22 @@ function extend( model ) {
|
|
|
1000
990
|
// TODO (next major version): remove?
|
|
1001
991
|
annotateWith( art, '@odata.draft.enabled', art.location, false );
|
|
1002
992
|
}
|
|
993
|
+
else {
|
|
994
|
+
// @fiori.draft.enabled artifacts need default elements ID_texts and locale.
|
|
995
|
+
// `locale` is copied from `sap.common.TextsAspect`, but without "key".
|
|
996
|
+
const textId = {
|
|
997
|
+
name: { location, id: 'ID_texts' },
|
|
998
|
+
kind: 'element',
|
|
999
|
+
key: { val: true, location },
|
|
1000
|
+
type: augmentPath( location, 'cds.UUID' ),
|
|
1001
|
+
location,
|
|
1002
|
+
};
|
|
1003
|
+
dictAdd( art.elements, 'ID_texts', textId );
|
|
1004
|
+
|
|
1005
|
+
// "Early" include; only for element `locale`, which has its `key` property
|
|
1006
|
+
// removed (or rather: it is not copied).
|
|
1007
|
+
linkToOrigin( textsAspect.elements.locale, 'locale', art, 'elements', location );
|
|
1008
|
+
}
|
|
1003
1009
|
|
|
1004
1010
|
if (addTextsLanguageAssoc && art.elements.language)
|
|
1005
1011
|
art.elements.language = undefined; // TODO: Message? Ignore?
|
package/lib/compiler/index.js
CHANGED
|
@@ -465,6 +465,11 @@ function compileDoX( model ) {
|
|
|
465
465
|
extend( model );
|
|
466
466
|
kickStart( model );
|
|
467
467
|
populate( model );
|
|
468
|
+
|
|
469
|
+
model.definitions = model.$functions.shuffleDict( model.definitions );
|
|
470
|
+
// Shuffling extensions is more difficult due to intra-file extensions of same artifact
|
|
471
|
+
// TODO: think about making this work
|
|
472
|
+
|
|
468
473
|
resolve( model );
|
|
469
474
|
tweakAssocs( model );
|
|
470
475
|
assertConsistency( model );
|
package/lib/compiler/populate.js
CHANGED
|
@@ -219,7 +219,7 @@ function populate( model ) {
|
|
|
219
219
|
// console.log( 'EXPR-IN', art.kind, refString(art.name) )
|
|
220
220
|
if (!art._main || !art.value || !art.value.path)
|
|
221
221
|
return undefined;
|
|
222
|
-
if (art.
|
|
222
|
+
if (art.value.path) {
|
|
223
223
|
setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
|
|
224
224
|
return art._origin;
|
|
225
225
|
}
|
|
@@ -432,7 +432,8 @@ function populate( model ) {
|
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
// console.log( resolveChain.map( v => msgName(v)+v._status ) );
|
|
435
|
-
|
|
435
|
+
resolveChain.reverse();
|
|
436
|
+
for (const view of resolveChain) {
|
|
436
437
|
if (view._status !== '_query' ) { // not already resolved
|
|
437
438
|
setLink( view, '_status', '_query' );
|
|
438
439
|
// must be run in order “sub query in FROM first”:
|
|
@@ -454,11 +455,11 @@ function populate( model ) {
|
|
|
454
455
|
*
|
|
455
456
|
* This is important to ensure re-compilability.
|
|
456
457
|
*
|
|
458
|
+
* TODO: make this part of a revamped on-demand 'extend' functionality.
|
|
459
|
+
*
|
|
457
460
|
* @param art
|
|
458
461
|
*/
|
|
459
462
|
function mergeSpecifiedElementsOrEnum( art ) {
|
|
460
|
-
// Later we use specified elements as proxies to inferred of leading query
|
|
461
|
-
// (No, we probably do not.)
|
|
462
463
|
for (const id in (art.elements || art.enum)) {
|
|
463
464
|
const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
|
|
464
465
|
const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
|
|
@@ -807,15 +808,16 @@ function populate( model ) {
|
|
|
807
808
|
const { id } = sibling.name;
|
|
808
809
|
if (Array.isArray(navElem)) {
|
|
809
810
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
810
|
-
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
811
|
-
|
|
811
|
+
info( 'wildcard-excluding-many', [ sibling.name.location, query ],
|
|
812
|
+
{ id, keyword: 'excluding' },
|
|
813
|
+
// eslint-disable-next-line max-len
|
|
812
814
|
'This select item replaces $(ID) from two or more sources. Add $(ID) to $(KEYWORD) to silence this message' );
|
|
813
815
|
}
|
|
814
816
|
else {
|
|
815
817
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
816
818
|
info( 'wildcard-excluding-one', [ sibling.name.location, query ],
|
|
817
819
|
{ id, alias: navElem._parent.name.id, keyword: 'excluding' },
|
|
818
|
-
|
|
820
|
+
// eslint-disable-next-line max-len
|
|
819
821
|
'This select item replaces $(ID) from table alias $(ALIAS). Add $(ID) to $(KEYWORD) to silence this message' );
|
|
820
822
|
}
|
|
821
823
|
}
|
|
@@ -1001,7 +1003,7 @@ function populate( model ) {
|
|
|
1001
1003
|
// - exclude all indirect projections, i.e. those which are projection on others in list
|
|
1002
1004
|
//
|
|
1003
1005
|
// To avoid repeated messages: if already tried to do autoexposure, return
|
|
1004
|
-
//
|
|
1006
|
+
// auto-exposed entity when successful, or `target` otherwise (no/failed autoexposure)
|
|
1005
1007
|
function minimalExposure( target, service, elemScope ) {
|
|
1006
1008
|
const descendants = scopedExposure( target._descendants &&
|
|
1007
1009
|
target._descendants[service.name.absolute] ||
|
|
@@ -1175,7 +1177,7 @@ function populate( model ) {
|
|
|
1175
1177
|
if (annotationIsFalse( anno )) {
|
|
1176
1178
|
// It would probably be cleaner to ignore a dubious
|
|
1177
1179
|
// `@cds.redirection.target: false` earlier, but that is not easy to detect
|
|
1178
|
-
// due to the name of the
|
|
1180
|
+
// due to the name of the auto-exposed entity with scoped redirections
|
|
1179
1181
|
if (!anno.$errorReported) {
|
|
1180
1182
|
info( 'anno-redirecting-anyway',
|
|
1181
1183
|
[ annotationLocation( anno ), autoexposed ],
|
|
@@ -1194,7 +1196,7 @@ function populate( model ) {
|
|
|
1194
1196
|
}
|
|
1195
1197
|
error( 'duplicate-autoexposed', [ service.name.location, service ],
|
|
1196
1198
|
{ target, art: absolute },
|
|
1197
|
-
'Name $(ART) of
|
|
1199
|
+
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
|
|
1198
1200
|
info( null, [ target.name.location, target ],
|
|
1199
1201
|
{ art: service },
|
|
1200
1202
|
'Expose this (or the competing) entity explicitly in service $(ART)' );
|
|
@@ -1203,7 +1205,7 @@ function populate( model ) {
|
|
|
1203
1205
|
const firstTarget = autoexposed.query.from._artifact;
|
|
1204
1206
|
error( 'duplicate-autoexposed', [ service.name.location, service ],
|
|
1205
1207
|
{ target: firstTarget, art: absolute },
|
|
1206
|
-
'Name $(ART) of
|
|
1208
|
+
'Name $(ART) of auto-exposed entity for $(TARGET) collides with other definition' );
|
|
1207
1209
|
info( null, [ firstTarget.name.location, firstTarget ],
|
|
1208
1210
|
{ art: service },
|
|
1209
1211
|
'Expose this (or the competing) entity explicitly in service $(ART)' );
|
|
@@ -1225,6 +1227,21 @@ function populate( model ) {
|
|
|
1225
1227
|
$inferred: '$generated',
|
|
1226
1228
|
},
|
|
1227
1229
|
};
|
|
1230
|
+
// forward target parameters to projection
|
|
1231
|
+
if (target.params) {
|
|
1232
|
+
art.params = Object.create(null);
|
|
1233
|
+
// is art.query.from.path[0].$syntax: ':' required?
|
|
1234
|
+
art.query.from.path[0].args = Object.create(null);
|
|
1235
|
+
forEachGeneric(target, 'params', (p, pn) => {
|
|
1236
|
+
art.params[pn] = linkToOrigin(p, pn, art, 'params', p.location);
|
|
1237
|
+
art.query.from.path[0].args[pn] = {
|
|
1238
|
+
name: { id: p.name.id, location: p.location },
|
|
1239
|
+
location: p.location,
|
|
1240
|
+
scope: 'param',
|
|
1241
|
+
path: [ { id: pn, location: p.location } ],
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1228
1245
|
// TODO: do we need to tag the generated entity with elemScope = 'auto'?
|
|
1229
1246
|
if (autoexposed) {
|
|
1230
1247
|
Object.assign( autoexposed, art );
|
|
@@ -111,7 +111,8 @@ function propagate( model ) {
|
|
|
111
111
|
targets = news;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
chain.reverse()
|
|
114
|
+
chain.reverse();
|
|
115
|
+
chain.forEach( step );
|
|
115
116
|
runMembers( art );
|
|
116
117
|
// console.log('DONE:', art.name, art.elements ? Object.keys(art.elements) : 0);
|
|
117
118
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
'use strict';
|
|
40
40
|
|
|
41
41
|
const {
|
|
42
|
-
isDeprecatedEnabled,
|
|
43
42
|
forEachDefinition,
|
|
44
43
|
forEachMember,
|
|
45
44
|
forEachGeneric,
|
|
@@ -70,6 +69,7 @@ const {
|
|
|
70
69
|
} = require('./utils');
|
|
71
70
|
|
|
72
71
|
const detectCycles = require('./cycle-detector');
|
|
72
|
+
const { CompilerAssertion } = require('../base/error');
|
|
73
73
|
|
|
74
74
|
const $location = Symbol.for('cds.$location');
|
|
75
75
|
|
|
@@ -80,7 +80,7 @@ const $inferred = Symbol.for('cds.$inferred');
|
|
|
80
80
|
// exception in case of an error, but push the corresponding error object to
|
|
81
81
|
// that property (should be a vector).
|
|
82
82
|
function resolve( model ) {
|
|
83
|
-
const { options } = model;
|
|
83
|
+
// const { options } = model;
|
|
84
84
|
// Get shared functionality and the message function:
|
|
85
85
|
const {
|
|
86
86
|
info, warning, error, message,
|
|
@@ -129,7 +129,7 @@ function resolve( model ) {
|
|
|
129
129
|
// for builtin types
|
|
130
130
|
forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
|
|
131
131
|
forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
|
|
132
|
-
// Phase 6: apply ANNOTATE on
|
|
132
|
+
// Phase 6: apply ANNOTATE on auto-exposed entities and unknown artifacts:
|
|
133
133
|
lateExtensions( annotateMembers );
|
|
134
134
|
if (model.extensions)
|
|
135
135
|
model.extensions.map( annotateUnknown );
|
|
@@ -242,16 +242,16 @@ function resolve( model ) {
|
|
|
242
242
|
for (const name in query.elements) {
|
|
243
243
|
const elem = query.elements[name];
|
|
244
244
|
if (!elem.$inferred && elem.value &&
|
|
245
|
-
testExpr( elem.value, selectTest, () => false ))
|
|
245
|
+
testExpr( elem.value, selectTest, () => false, elem ))
|
|
246
246
|
propagateKeys = false;
|
|
247
247
|
}
|
|
248
248
|
return propagateKeys;
|
|
249
249
|
|
|
250
|
-
function selectTest( expr ) {
|
|
250
|
+
function selectTest( expr, user ) {
|
|
251
251
|
const art = withAssociation( expr, targetMaxNotOne );
|
|
252
252
|
if (art) {
|
|
253
253
|
// ID published! Used in stakeholder project; if renamed, add to oldMessageIds
|
|
254
|
-
info( 'query-navigate-many', [ art.location, query ], { art },
|
|
254
|
+
info( 'query-navigate-many', [ art.location, user || query ], { art },
|
|
255
255
|
{
|
|
256
256
|
// eslint-disable-next-line max-len
|
|
257
257
|
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
@@ -291,10 +291,12 @@ function resolve( model ) {
|
|
|
291
291
|
// all sub elements.
|
|
292
292
|
// TODO: make this function smaller
|
|
293
293
|
function resolveRefs( art ) {
|
|
294
|
+
if (art.builtin)
|
|
295
|
+
return;
|
|
294
296
|
// console.log(message( null, art.location, art, {}, 'Info','REFS').toString())
|
|
295
297
|
// console.log(message( null, art.location, art, {target:art.target}, 'Info','RR').toString())
|
|
296
298
|
const parent = art._parent;
|
|
297
|
-
const allowedInMain = [ 'entity', 'aspect' ].includes( adHocOrMainKind( art ) );
|
|
299
|
+
const allowedInMain = [ 'entity', 'aspect', 'event' ].includes( adHocOrMainKind( art ) );
|
|
298
300
|
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
299
301
|
if (art.key && art.key.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
|
|
300
302
|
warning( 'unexpected-key', [ art.key.location, art ],
|
|
@@ -385,6 +387,7 @@ function resolve( model ) {
|
|
|
385
387
|
resolveRedirected( obj, obj.target._artifact );
|
|
386
388
|
}
|
|
387
389
|
else if (obj.kind === 'mixin') {
|
|
390
|
+
// TODO: also check that the type is cds.Association or cds.Composition
|
|
388
391
|
error( 'non-assoc-in-mixin', [ (obj.type || obj.name).location, art ], {},
|
|
389
392
|
'Only unmanaged associations are allowed in mixin clauses' );
|
|
390
393
|
}
|
|
@@ -540,8 +543,14 @@ function resolve( model ) {
|
|
|
540
543
|
'Elements only exist in entities, types or typed constructs' );
|
|
541
544
|
}
|
|
542
545
|
else {
|
|
546
|
+
const isEntity = (parent.returns?.type || parent.type)?._artifact?.kind === 'entity';
|
|
547
|
+
let variant = parent.enum ? 'enum' : 'element';
|
|
548
|
+
if (isEntity)
|
|
549
|
+
variant = 'entity-element';
|
|
550
|
+
else if (parent.returns)
|
|
551
|
+
variant = 'returns';
|
|
543
552
|
notFound( 'anno-undefined-element', ext.name.location, art,
|
|
544
|
-
{ art:
|
|
553
|
+
{ '#': variant, art: parent, name },
|
|
545
554
|
parent.elements || parent.enum );
|
|
546
555
|
}
|
|
547
556
|
}
|
|
@@ -689,7 +698,7 @@ function resolve( model ) {
|
|
|
689
698
|
// enums would be first in elements
|
|
690
699
|
if ( parent[kindProperties[kind].dict] &&
|
|
691
700
|
parent[kindProperties[kind].dict][art.name.id] )
|
|
692
|
-
throw new
|
|
701
|
+
throw new CompilerAssertion(art.name.id);
|
|
693
702
|
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
694
703
|
}
|
|
695
704
|
ext.kind = 'annotate'; // after setMemberParent()!
|
|
@@ -711,6 +720,8 @@ function resolve( model ) {
|
|
|
711
720
|
if (prop.charAt(0) === '@')
|
|
712
721
|
chooseAssignment( prop, art );
|
|
713
722
|
}
|
|
723
|
+
if (art.doc)
|
|
724
|
+
chooseAssignment( 'doc', art );
|
|
714
725
|
}
|
|
715
726
|
|
|
716
727
|
function chooseAssignment( annoName, art ) {
|
|
@@ -742,9 +753,12 @@ function resolve( model ) {
|
|
|
742
753
|
const msg = (issue === true)
|
|
743
754
|
? 'anno-duplicate'
|
|
744
755
|
: (index >= 0) ? 'anno-duplicate-unrelated-layer' : 'anno-unstable-array';
|
|
756
|
+
const variant = annoName === 'doc' ? 'doc' : 'std';
|
|
745
757
|
for (const a of assignments) {
|
|
746
|
-
if (!a.$errorReported)
|
|
747
|
-
message( msg, [ a.name.location, art ],
|
|
758
|
+
if (!a.$errorReported) {
|
|
759
|
+
message( msg, [ a.name?.location || a.location, art ],
|
|
760
|
+
{ '#': variant, anno: annoName } );
|
|
761
|
+
}
|
|
748
762
|
}
|
|
749
763
|
}
|
|
750
764
|
// else if (index > 0) -- if we allow multiple assignments in one file - the last wins
|
|
@@ -878,7 +892,7 @@ function resolve( model ) {
|
|
|
878
892
|
for (const col of query.$inlines)
|
|
879
893
|
resolveExpr( col.value, 'expr', col, undefined, true );
|
|
880
894
|
// for (const col of query.$inlines)
|
|
881
|
-
// if (!col.value.path) throw
|
|
895
|
+
// if (!col.value.path) throw new CompilerAssertion(col.name.element)
|
|
882
896
|
if (query !== query._main._leadingQuery) // will be done later
|
|
883
897
|
forEachGeneric( query, 'elements', resolveRefs );
|
|
884
898
|
if (query.from)
|
|
@@ -1068,8 +1082,7 @@ function resolve( model ) {
|
|
|
1068
1082
|
return;
|
|
1069
1083
|
}
|
|
1070
1084
|
else if ((elem.value || elem.expand) && elem.type && !elem.type.$inferred) {
|
|
1071
|
-
error( 'ref-unexpected-assoc', [ elem.type.location, elem ], {}
|
|
1072
|
-
'Casting to an association is not supported' );
|
|
1085
|
+
error( 'ref-unexpected-assoc', [ elem.type.location, elem ], { '#': 'cast' } );
|
|
1073
1086
|
return;
|
|
1074
1087
|
}
|
|
1075
1088
|
// console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
|
|
@@ -1131,7 +1144,8 @@ function resolve( model ) {
|
|
|
1131
1144
|
}
|
|
1132
1145
|
}
|
|
1133
1146
|
let redirected = null;
|
|
1134
|
-
|
|
1147
|
+
chain.reverse();
|
|
1148
|
+
let news = [ { chain, sources: [ target ] } ];
|
|
1135
1149
|
const dict = Object.create(null);
|
|
1136
1150
|
while (news.length) {
|
|
1137
1151
|
const outer = news;
|
|
@@ -1328,11 +1342,6 @@ function resolve( model ) {
|
|
|
1328
1342
|
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
1329
1343
|
args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
|
|
1330
1344
|
}
|
|
1331
|
-
if (expr.suffix && isDeprecatedEnabled( options )) {
|
|
1332
|
-
const { location } = expr.suffix[0] || expr;
|
|
1333
|
-
error( null, [ location, user ], { prop: 'deprecated' },
|
|
1334
|
-
'Window functions are not supported if $(PROP) options are set' );
|
|
1335
|
-
}
|
|
1336
1345
|
if (expr.suffix)
|
|
1337
1346
|
expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
|
|
1338
1347
|
}
|
package/lib/compiler/shared.js
CHANGED
|
@@ -274,6 +274,8 @@ function fns( model ) {
|
|
|
274
274
|
function resolveUncheckedPath( ref, expected, user ) {
|
|
275
275
|
if (!ref.path || ref.path.broken) // incomplete type AST
|
|
276
276
|
return undefined;
|
|
277
|
+
if (ref._artifact)
|
|
278
|
+
return ref._artifact.name.absolute;
|
|
277
279
|
const spec = specExpected[expected];
|
|
278
280
|
let art = (ref.scope === 'global' || spec.global)
|
|
279
281
|
? getPathRoot( ref.path, spec, user, {}, model[spec.global || 'definitions'] )
|
|
@@ -594,7 +596,8 @@ function fns( model ) {
|
|
|
594
596
|
// const { _origin } = user._pathHead;
|
|
595
597
|
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
|
|
596
598
|
}
|
|
597
|
-
// if (head.id === 'k') {console.log(Object.keys(user));
|
|
599
|
+
// if (head.id === 'k') {console.log(Object.keys(user));
|
|
600
|
+
// throw new CompilerAssertion(JSON.stringify(user.name))}
|
|
598
601
|
// if head._artifact is set or is null then it was already computed once
|
|
599
602
|
if ('_artifact' in head)
|
|
600
603
|
return Array.isArray(head._artifact) ? false : head._artifact;
|
|
@@ -723,8 +726,10 @@ function fns( model ) {
|
|
|
723
726
|
} );
|
|
724
727
|
}
|
|
725
728
|
else {
|
|
729
|
+
const isVirtual = (user.name?.id === head.id && user.virtual?.val);
|
|
730
|
+
const code = isVirtual ? 'virtual null as ‹name›' : '';
|
|
726
731
|
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
727
|
-
{ art: head.id, '#': 'std' } );
|
|
732
|
+
{ art: head.id, '#': isVirtual ? 'virtual' : 'std', code } );
|
|
728
733
|
}
|
|
729
734
|
}
|
|
730
735
|
else if (env.$frontend && env.$frontend !== 'cdl' || spec.global) {
|
|
@@ -817,7 +822,7 @@ function fns( model ) {
|
|
|
817
822
|
// could "change" to this message at the end of compile():
|
|
818
823
|
message( 'ref-autoexposed', [ item.location, user ], { art },
|
|
819
824
|
// eslint-disable-next-line max-len
|
|
820
|
-
'An
|
|
825
|
+
'An auto-exposed entity can\'t be referred to - expose entity $(ART) explicitly' );
|
|
821
826
|
}
|
|
822
827
|
}
|
|
823
828
|
return art;
|
|
@@ -844,8 +849,8 @@ function fns( model ) {
|
|
|
844
849
|
}
|
|
845
850
|
|
|
846
851
|
function errorNotFound( item, env ) {
|
|
847
|
-
if (!spec.next) { // artifact ref
|
|
848
|
-
// TODO: better for
|
|
852
|
+
if (!spec.next && artItemsCount >= 0) { // artifact ref
|
|
853
|
+
// TODO: better for FROM e.Assoc (even disallow for other refs)
|
|
849
854
|
const a = searchName( art, item.id, (spec.envFn || art._subArtifacts) && 'absolute' );
|
|
850
855
|
signalNotFound( spec.undefinedDef || 'ref-undefined-def', [ item.location, user ],
|
|
851
856
|
[ env ], { art: a } );
|
|
@@ -890,8 +895,10 @@ function fns( model ) {
|
|
|
890
895
|
location.$notFound = true;
|
|
891
896
|
/** @type {object} */
|
|
892
897
|
const err = message( msgId, location, textParams );
|
|
893
|
-
if (valid)
|
|
894
|
-
|
|
898
|
+
if (valid) {
|
|
899
|
+
valid.reverse();
|
|
900
|
+
attachAndEmitValidNames(err, ...valid);
|
|
901
|
+
}
|
|
895
902
|
}
|
|
896
903
|
|
|
897
904
|
/**
|
|
@@ -977,10 +984,8 @@ function fns( model ) {
|
|
|
977
984
|
// Copy annotations from `ext` to `art`, overwriting inferred ones.
|
|
978
985
|
// TODO: move to extend.js if not used anymore in define.js
|
|
979
986
|
function copyAnnotationsForExtensions( ext, art ) {
|
|
980
|
-
if (ext.doc)
|
|
981
|
-
art.doc = ext.doc; // e.g. through `extensions` array in CSN
|
|
982
987
|
for (const annoProp in ext) {
|
|
983
|
-
if (annoProp.charAt(0) === '@') {
|
|
988
|
+
if (annoProp.charAt(0) === '@' || annoProp === 'doc') {
|
|
984
989
|
const extAnno = ext[annoProp];
|
|
985
990
|
if (art[annoProp]?.$inferred)
|
|
986
991
|
art[annoProp] = extAnno; // overwrite $inferred annos
|
package/lib/compiler/utils.js
CHANGED
|
@@ -141,7 +141,7 @@ function setMemberParent( elem, name, parent, prop ) {
|
|
|
141
141
|
else
|
|
142
142
|
delete elem.name[kind];
|
|
143
143
|
});
|
|
144
|
-
// try { throw new
|
|
144
|
+
// try { throw new CompilerAssertion('Foo') } catch (e) { elem.name.stack = e; };
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
@@ -264,27 +264,27 @@ function copyExpr( expr, location, skipUnderscored, rewritePath ) {
|
|
|
264
264
|
return r;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
function testExpr( expr, pathTest, queryTest ) {
|
|
267
|
+
function testExpr( expr, pathTest, queryTest, user ) {
|
|
268
268
|
// TODO: also check path arguments/filters
|
|
269
269
|
if (!expr || typeof expr === 'string') { // parse error or keywords in {xpr:...}
|
|
270
270
|
return false;
|
|
271
271
|
}
|
|
272
272
|
else if (Array.isArray(expr)) {
|
|
273
|
-
return expr.some( e => testExpr( e, pathTest, queryTest ) );
|
|
273
|
+
return expr.some( e => testExpr( e, pathTest, queryTest, user ) );
|
|
274
274
|
}
|
|
275
275
|
else if (expr.path) {
|
|
276
|
-
return pathTest( expr );
|
|
276
|
+
return pathTest( expr, user );
|
|
277
277
|
}
|
|
278
278
|
else if (expr.query) {
|
|
279
|
-
return queryTest( expr.query );
|
|
279
|
+
return queryTest( expr.query, user );
|
|
280
280
|
}
|
|
281
281
|
else if (expr.op && expr.args) {
|
|
282
282
|
// unnamed args => array
|
|
283
283
|
if (Array.isArray(expr.args))
|
|
284
|
-
return expr.args.some( e => testExpr( e, pathTest, queryTest ) );
|
|
284
|
+
return expr.args.some( e => testExpr( e, pathTest, queryTest, user ) );
|
|
285
285
|
// named args => dictionary
|
|
286
286
|
for (const namedArg of Object.keys(expr.args)) {
|
|
287
|
-
if (testExpr(expr.args[namedArg], pathTest, queryTest))
|
|
287
|
+
if (testExpr(expr.args[namedArg], pathTest, queryTest, user))
|
|
288
288
|
return true;
|
|
289
289
|
}
|
|
290
290
|
}
|