@sap/cds-compiler 4.3.2 → 4.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 +39 -0
- package/lib/api/main.js +14 -24
- package/lib/api/options.js +1 -0
- package/lib/api/trace.js +38 -0
- package/lib/base/location.js +46 -1
- package/lib/base/message-registry.js +68 -16
- package/lib/base/messages.js +8 -3
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/selectItems.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +25 -1
- package/lib/compiler/checks.js +6 -5
- package/lib/compiler/define.js +12 -10
- package/lib/compiler/extend.js +44 -23
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +7 -5
- package/lib/compiler/populate.js +31 -22
- package/lib/compiler/propagator.js +6 -2
- package/lib/compiler/resolve.js +52 -17
- package/lib/compiler/shared.js +85 -39
- package/lib/compiler/tweak-assocs.js +64 -23
- package/lib/compiler/utils.js +40 -23
- package/lib/edm/.eslintrc.json +2 -0
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +260 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +82 -423
- package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
- package/lib/edm/csn2edm.js +12 -5
- package/lib/edm/edm.js +14 -73
- package/lib/edm/edmPreprocessor.js +6 -0
- package/lib/gen/Dictionary.json +187 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageLexer.interp +1 -1
- package/lib/gen/languageLexer.js +1129 -671
- package/lib/gen/languageParser.js +4285 -4283
- package/lib/json/from-csn.js +13 -18
- package/lib/json/to-csn.js +11 -6
- package/lib/language/antlrParser.js +0 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +95 -30
- package/lib/language/genericAntlrParser.js +21 -1
- package/lib/main.js +13 -3
- package/lib/model/csnRefs.js +42 -8
- package/lib/model/csnUtils.js +14 -2
- package/lib/model/enrichCsn.js +33 -5
- package/lib/model/revealInternalProperties.js +5 -0
- package/lib/modelCompare/compare.js +76 -14
- package/lib/modelCompare/utils/filter.js +19 -12
- package/lib/optionProcessor.js +2 -0
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toHdbcds.js +3 -0
- package/lib/render/toRename.js +3 -1
- package/lib/render/toSql.js +46 -92
- package/lib/render/utils/common.js +76 -0
- package/lib/render/utils/delta.js +17 -3
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/db/.eslintrc.json +1 -0
- package/lib/transform/db/applyTransformations.js +30 -4
- package/lib/transform/db/associations.js +22 -10
- package/lib/transform/db/backlinks.js +6 -2
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/db/transformExists.js +13 -39
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/draft/odata.js +5 -18
- package/lib/transform/effective/associations.js +46 -15
- package/lib/transform/effective/main.js +7 -2
- package/lib/transform/effective/misc.js +43 -24
- package/lib/transform/effective/queries.js +20 -22
- package/lib/transform/effective/types.js +6 -2
- package/lib/transform/forOdata.js +10 -3
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +22 -15
- package/lib/utils/term.js +2 -2
- package/package.json +2 -1
package/lib/compiler/populate.js
CHANGED
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
const {
|
|
27
27
|
dictAdd, dictAddArray, dictFirst, dictForEach,
|
|
28
28
|
} = require('../base/dictionaries');
|
|
29
|
-
const { weakLocation } = require('../base/location');
|
|
29
|
+
const { weakLocation, weakRefLocation } = require('../base/location');
|
|
30
30
|
const { CompilerAssertion } = require('../base/error');
|
|
31
31
|
|
|
32
32
|
const { kindProperties } = require('./base');
|
|
@@ -36,7 +36,6 @@ const {
|
|
|
36
36
|
annotationVal,
|
|
37
37
|
annotationIsFalse,
|
|
38
38
|
annotationLocation,
|
|
39
|
-
augmentPath,
|
|
40
39
|
linkToOrigin,
|
|
41
40
|
setMemberParent,
|
|
42
41
|
proxyCopyMembers,
|
|
@@ -113,6 +112,7 @@ function populate( model ) {
|
|
|
113
112
|
|
|
114
113
|
/** Make sure that effectiveType() is called on all members and items */
|
|
115
114
|
function traverseElementEnvironments( art ) {
|
|
115
|
+
// TODO: we could leave out foreign keys (but they are traversed via forEachMember)
|
|
116
116
|
let type = effectiveType( art );
|
|
117
117
|
while (type?.items)
|
|
118
118
|
type = effectiveType( type.items );
|
|
@@ -376,8 +376,7 @@ function populate( model ) {
|
|
|
376
376
|
art.items = 0; // circular
|
|
377
377
|
return true;
|
|
378
378
|
}
|
|
379
|
-
const
|
|
380
|
-
const location = ref && ref.location || art.location;
|
|
379
|
+
const location = weakRefLocation( art.type || art.value ) || weakLocation( art.location );
|
|
381
380
|
art.items = { $inferred: 'expanded', location };
|
|
382
381
|
setLink( art.items, '_outer', art );
|
|
383
382
|
setLink( art.items, '_parent', art._parent );
|
|
@@ -403,10 +402,10 @@ function populate( model ) {
|
|
|
403
402
|
return true;
|
|
404
403
|
}
|
|
405
404
|
const ref = art.type || art.value || art.name;
|
|
406
|
-
const location = ref
|
|
405
|
+
const location = weakRefLocation( ref ) || weakLocation( art.location );
|
|
407
406
|
// console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
|
|
408
407
|
// .toString(), Object.keys(struct.elements))
|
|
409
|
-
proxyCopyMembers( art, 'elements', struct.elements,
|
|
408
|
+
proxyCopyMembers( art, 'elements', struct.elements, location,
|
|
410
409
|
null, isDeprecatedEnabled( options, 'noKeyPropagationWithExpansions' ) );
|
|
411
410
|
// Set elements expansion status (the if condition is always true, as no
|
|
412
411
|
// elements expansion will take place on artifact with existing other
|
|
@@ -421,8 +420,8 @@ function populate( model ) {
|
|
|
421
420
|
if (art.enum)
|
|
422
421
|
return false;
|
|
423
422
|
const ref = art.type || art.value || art.name;
|
|
424
|
-
const location =
|
|
425
|
-
proxyCopyMembers( art, 'enum', origin.enum,
|
|
423
|
+
const location = weakRefLocation( ref ) || weakLocation( art.location );
|
|
424
|
+
proxyCopyMembers( art, 'enum', origin.enum, location );
|
|
426
425
|
// Set elements expansion status (the if condition is always true, as no
|
|
427
426
|
// elements expansion will take place on artifact with existing other
|
|
428
427
|
// member property):
|
|
@@ -521,6 +520,9 @@ function populate( model ) {
|
|
|
521
520
|
for (const id in (art.elements || art.enum)) {
|
|
522
521
|
const ielem = art.elements ? art.elements[id] : art.enum[id]; // inferred element
|
|
523
522
|
const selem = art.elements$ ? art.elements$[id] : art.enum$[id]; // specified element
|
|
523
|
+
// TODO: the positions are very strange, at least for enums
|
|
524
|
+
// see e.g. for test3/Queries/SpecifiedElements/SpecifiedElements.err.csn
|
|
525
|
+
// better to complain at the end position of the enum dict
|
|
524
526
|
if (!selem) {
|
|
525
527
|
info( 'query-missing-element', [ ielem.name.location, art ], {
|
|
526
528
|
'#': ielem.kind === 'enum' ? 'enum' : 'std', id,
|
|
@@ -799,7 +801,9 @@ function populate( model ) {
|
|
|
799
801
|
// TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
|
|
800
802
|
function expandWildcard( wildcard, siblingElements, colParent, query ) {
|
|
801
803
|
const { elements } = query.items || query;
|
|
802
|
-
let location = wildcard.location ||
|
|
804
|
+
let location = wildcard.location ||
|
|
805
|
+
weakRefLocation( query.from ) ||
|
|
806
|
+
weakLocation( query.location );
|
|
803
807
|
const inferred = query._main.$inferred;
|
|
804
808
|
const excludingDict = (colParent || query).excludingDict || Object.create( null );
|
|
805
809
|
|
|
@@ -845,19 +849,24 @@ function populate( model ) {
|
|
|
845
849
|
}
|
|
846
850
|
else {
|
|
847
851
|
location = weakLocation( location );
|
|
852
|
+
// Usually, the location of a `*`-inferred element is the location of the `*`.
|
|
853
|
+
// For inferred entities, it is the location of the corresponding source elem
|
|
854
|
+
// (from all generated entities, only auto-exposed are “wildcard projections”):
|
|
855
|
+
const elemLocation = !query._main.$inferred && location;
|
|
848
856
|
const origin = envParent ? navElem : navElem._origin;
|
|
849
|
-
const elem = linkToOrigin( origin, name, query, null,
|
|
857
|
+
const elem = linkToOrigin( origin, name, query, null, elemLocation );
|
|
850
858
|
// TODO: check assocToMany { * }
|
|
851
859
|
dictAdd( elements, name, elem, ( _name, loc ) => {
|
|
852
860
|
// there can be a definition from a previous inline with the same name:
|
|
853
861
|
error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
|
|
854
862
|
} );
|
|
855
|
-
|
|
856
|
-
|
|
863
|
+
if (!query._main.$inferred || origin.$inferred)
|
|
864
|
+
elem.$inferred = '*';
|
|
865
|
+
elem.name.$inferred = '*'; // matters for A2J
|
|
857
866
|
if (envParent)
|
|
858
867
|
setWildcardExpandInline( elem, envParent, origin, name, location );
|
|
859
868
|
else
|
|
860
|
-
setElementOrigin( elem, navElem, name, location );
|
|
869
|
+
setElementOrigin( elem, navElem, name, elem.location );
|
|
861
870
|
}
|
|
862
871
|
}
|
|
863
872
|
if (envParent || query.kind !== 'select') {
|
|
@@ -1293,12 +1302,12 @@ function populate( model ) {
|
|
|
1293
1302
|
return target;
|
|
1294
1303
|
}
|
|
1295
1304
|
// console.log(absolute)
|
|
1296
|
-
const
|
|
1297
|
-
const from =
|
|
1305
|
+
const location = weakRefLocation( target.name );
|
|
1306
|
+
const from = { path: [ { id: target.name.id, location } ], location };
|
|
1298
1307
|
let art = {
|
|
1299
1308
|
kind: 'entity',
|
|
1300
1309
|
name: { location, id: absolute },
|
|
1301
|
-
location
|
|
1310
|
+
location,
|
|
1302
1311
|
query: { location, op: { val: 'SELECT', location }, from },
|
|
1303
1312
|
$syntax: 'projection',
|
|
1304
1313
|
$inferred: 'autoexposed',
|
|
@@ -1311,14 +1320,14 @@ function populate( model ) {
|
|
|
1311
1320
|
if (target.params) {
|
|
1312
1321
|
art.params = Object.create( null );
|
|
1313
1322
|
// is art.query.from.path[0].$syntax: ':' required?
|
|
1314
|
-
|
|
1323
|
+
from.path[0].args = Object.create( null );
|
|
1315
1324
|
forEachGeneric( target, 'params', (p, pn) => {
|
|
1316
|
-
art.params[pn] = linkToOrigin( p, pn, art, 'params'
|
|
1317
|
-
|
|
1318
|
-
name: { id: p.name.id, location
|
|
1319
|
-
location
|
|
1325
|
+
art.params[pn] = linkToOrigin( p, pn, art, 'params' );
|
|
1326
|
+
from.path[0].args[pn] = {
|
|
1327
|
+
name: { id: p.name.id, location },
|
|
1328
|
+
location,
|
|
1320
1329
|
scope: 'param',
|
|
1321
|
-
path: [ { id: pn, location
|
|
1330
|
+
path: [ { id: pn, location } ],
|
|
1322
1331
|
};
|
|
1323
1332
|
} );
|
|
1324
1333
|
}
|
|
@@ -48,7 +48,7 @@ function propagate( model ) {
|
|
|
48
48
|
virtual,
|
|
49
49
|
notNull,
|
|
50
50
|
targetElement: onlyViaParent, // in foreign keys
|
|
51
|
-
value: onlyViaParent, // enum symbol value
|
|
51
|
+
value: onlyViaParent, // enum symbol value, calculated element
|
|
52
52
|
// masked: special = done in definer
|
|
53
53
|
// key: special = done in resolver
|
|
54
54
|
// actions: struct includes & primary source = in definer/resolver
|
|
@@ -248,7 +248,10 @@ function propagate( model ) {
|
|
|
248
248
|
for (const name in dict) {
|
|
249
249
|
const member = linkToOrigin( dict[name], name, target, prop, location );
|
|
250
250
|
member.$inferred = 'proxy';
|
|
251
|
-
|
|
251
|
+
if (prop === 'foreignKeys')
|
|
252
|
+
setLink( member, '_effectiveType', member );
|
|
253
|
+
else
|
|
254
|
+
setEffectiveType( member, dict[name] );
|
|
252
255
|
}
|
|
253
256
|
target[prop][$inferred] = 'prop';
|
|
254
257
|
}
|
|
@@ -380,6 +383,7 @@ function checkAndSetStatus( art ) {
|
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
function setEffectiveType( target, source ) {
|
|
386
|
+
// TODO: when is this already set?
|
|
383
387
|
if (source._effectiveType !== undefined)
|
|
384
388
|
setLink( target, '_effectiveType', source._effectiveType );
|
|
385
389
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -62,6 +62,8 @@ const {
|
|
|
62
62
|
targetMaxNotOne,
|
|
63
63
|
traverseQueryPost,
|
|
64
64
|
linkToOrigin,
|
|
65
|
+
compositionTextVariant,
|
|
66
|
+
targetCantBeAspect,
|
|
65
67
|
} = require('./utils');
|
|
66
68
|
|
|
67
69
|
const detectCycles = require('./cycle-detector');
|
|
@@ -383,17 +385,31 @@ function resolve( model ) {
|
|
|
383
385
|
}
|
|
384
386
|
}
|
|
385
387
|
|
|
386
|
-
if (art.targetAspect &&
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
if (art.targetAspect && targetCantBeAspect( art, true )) {
|
|
389
|
+
// If not for anonymous aspect, this message can only occur for CSN input →
|
|
390
|
+
// we are more CSN specific (we could add more text variants, but this is
|
|
391
|
+
// CSN input with an undocumented CSN property…) For an anonymous target
|
|
392
|
+
// aspect, we could have more text variants, though…
|
|
393
|
+
const msg = art.targetAspect.elements
|
|
394
|
+
? 'anonymous'
|
|
395
|
+
: (art.target || !art._parent?.query && art._parent?.kind !== 'event') && 'std';
|
|
396
|
+
error( 'type-unexpected-target-aspect', [ art.targetAspect.location, art ],
|
|
397
|
+
{ '#': msg || 'target', prop: 'targetAspect', otherprop: 'target' },
|
|
398
|
+
{
|
|
399
|
+
std: 'Unexpected property $(PROP)',
|
|
400
|
+
anonymous: 'Unexpected anonymous target aspect',
|
|
401
|
+
target: 'Unexpected property $(PROP), adding property $(OTHERPROP) might help',
|
|
402
|
+
} );
|
|
403
|
+
} // TODO: else resolvePath() + test for cds.Composition?
|
|
404
|
+
|
|
390
405
|
if (art.includes && !allowedInMain) {
|
|
406
|
+
// TODO: make this a check function for shared.js / or make it part of extend.js
|
|
391
407
|
for (const include of art.includes) {
|
|
392
408
|
const struct = include._artifact;
|
|
393
409
|
if (struct && struct.kind !== 'type' && struct.elements &&
|
|
394
410
|
Object.values( struct.elements ).some( e => e.targetAspect )) {
|
|
395
|
-
|
|
396
|
-
|
|
411
|
+
error( 'type-managed-composition', [ include.location, art ],
|
|
412
|
+
{ '#': struct.kind, art: struct } );
|
|
397
413
|
}
|
|
398
414
|
}
|
|
399
415
|
}
|
|
@@ -425,16 +441,15 @@ function resolve( model ) {
|
|
|
425
441
|
const elemtype = obj.type._artifact;
|
|
426
442
|
if (elemtype && effectiveType( elemtype )) {
|
|
427
443
|
const assocType = getAssocSpec( elemtype ) || {};
|
|
428
|
-
if (assocType.on && !obj.on)
|
|
444
|
+
if ((assocType.on || assocType.$assocFilter) && !obj.on)
|
|
429
445
|
obj.on = { $inferred: 'rewrite' }; // TODO: no extra rewrite here
|
|
430
446
|
if (assocType.targetAspect) {
|
|
431
447
|
error( 'composition-as-type-of', [ obj.type.location, art ], {},
|
|
432
448
|
'A managed aspect composition element can\'t be used as type' );
|
|
433
449
|
return;
|
|
434
450
|
}
|
|
435
|
-
else if (assocType.on) {
|
|
436
|
-
error( '
|
|
437
|
-
'An unmanaged association can\'t be used as type' );
|
|
451
|
+
else if (assocType.on || assocType.$assocFilter) {
|
|
452
|
+
error( 'type-unexpected-assoc', [ obj.type.location, art ] );
|
|
438
453
|
return;
|
|
439
454
|
}
|
|
440
455
|
|
|
@@ -505,6 +520,7 @@ function resolve( model ) {
|
|
|
505
520
|
checkStructureCast( art );
|
|
506
521
|
}
|
|
507
522
|
|
|
523
|
+
resolveExprInAnnotations( art );
|
|
508
524
|
forEachMember( art, resolveRefs, art.targetAspect );
|
|
509
525
|
// After the resolving of foreign keys (and adding implicit ones):
|
|
510
526
|
if (obj.target?.$inferred === '')
|
|
@@ -831,10 +847,12 @@ function resolve( model ) {
|
|
|
831
847
|
function getAssocSpec( type ) {
|
|
832
848
|
let unmanaged = null;
|
|
833
849
|
while (type) {
|
|
834
|
-
if (type.on)
|
|
850
|
+
if (type.on) // if unmanaged, continue trying to find targetAspect
|
|
835
851
|
unmanaged = type;
|
|
836
852
|
else if (type.foreignKeys || type.targetAspect)
|
|
837
853
|
return type;
|
|
854
|
+
else if (type.value?.path?.[type.value.path.length - 1]?.where)
|
|
855
|
+
return { $assocFilter: true }; // filter -> always unmanaged
|
|
838
856
|
type = getOrigin( type );
|
|
839
857
|
}
|
|
840
858
|
return unmanaged;
|
|
@@ -953,10 +971,8 @@ function resolve( model ) {
|
|
|
953
971
|
// TODO: test of .items a bit unclear - we should somehow restrict the
|
|
954
972
|
// use of unmanaged assocs in MANY, at least with $self
|
|
955
973
|
// TODO: $self usage in anonymous aspects to be corrected in Core Compiler
|
|
956
|
-
const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
|
|
957
|
-
obj.type.path[0].id === 'cds.Composition';
|
|
958
974
|
message( 'assoc-as-type', [ obj.on.location, art ],
|
|
959
|
-
{ '#':
|
|
975
|
+
{ '#': compositionTextVariant( obj, 'comp' ) }, {
|
|
960
976
|
std: 'An unmanaged association can\'t be defined as type',
|
|
961
977
|
comp: 'An unmanaged composition can\'t be defined as type',
|
|
962
978
|
} );
|
|
@@ -1073,9 +1089,9 @@ function resolve( model ) {
|
|
|
1073
1089
|
obj.foreignKeys = Object.create( null );
|
|
1074
1090
|
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
1075
1091
|
if (elem.key && elem.key.val) {
|
|
1076
|
-
const
|
|
1092
|
+
const location = weakLocation( obj.target.location );
|
|
1077
1093
|
const key = {
|
|
1078
|
-
name: { location, id: elem.name.id, $inferred: 'keys' },
|
|
1094
|
+
name: { location, id: elem.name.id, $inferred: 'keys' },
|
|
1079
1095
|
kind: 'key',
|
|
1080
1096
|
targetElement: { path: [ { id: elem.name.id, location } ], location },
|
|
1081
1097
|
location,
|
|
@@ -1086,7 +1102,9 @@ function resolve( model ) {
|
|
|
1086
1102
|
// the following should be done automatically, since we run resolveRefs after that
|
|
1087
1103
|
setArtifactLink( key.targetElement, elem );
|
|
1088
1104
|
setArtifactLink( key.targetElement.path[0], elem );
|
|
1089
|
-
|
|
1105
|
+
// _origin/_effectiveType like we do in effectiveType() → … getOriginRaw():
|
|
1106
|
+
setLink( key, '_origin', '' );
|
|
1107
|
+
setLink( key, '_effectiveType', key );
|
|
1090
1108
|
dependsOn( key, elem, location );
|
|
1091
1109
|
// TODO TMP: instead, make managed composition of aspects and unmanaged
|
|
1092
1110
|
// assocs not depend on their `on` condition (empty `_deps` after resolve)
|
|
@@ -1328,7 +1346,24 @@ function resolve( model ) {
|
|
|
1328
1346
|
resolveTypeArgumentsUnchecked( art, typeArt, user );
|
|
1329
1347
|
}
|
|
1330
1348
|
|
|
1349
|
+
function resolveExprInAnnotations( art ) {
|
|
1350
|
+
for (const anno in art) {
|
|
1351
|
+
if (anno.charAt(0) === '@')
|
|
1352
|
+
resolveAnnoExpr( art[anno], art );
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function resolveAnnoExpr( expr, art ) {
|
|
1357
|
+
if (expr.$tokenTexts)
|
|
1358
|
+
resolveExpr( expr, 'annotation', art );
|
|
1359
|
+
else if (expr.literal === 'array')
|
|
1360
|
+
expr.val.forEach( val => resolveAnnoExpr( val, art ) );
|
|
1361
|
+
else if (expr.literal === 'struct')
|
|
1362
|
+
Object.values( expr.struct ).forEach( val => resolveAnnoExpr( val, art ) );
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1331
1365
|
function resolveExpr( expr, exprCtx, user ) {
|
|
1366
|
+
// console.log(expr?.location.line,exprCtx)
|
|
1332
1367
|
traverseExpr( expr, exprCtx, user, (e, c, u) => resolveExprItem( e, c, u ) );
|
|
1333
1368
|
}
|
|
1334
1369
|
|
package/lib/compiler/shared.js
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
pathName,
|
|
14
14
|
userQuery,
|
|
15
15
|
definedViaCdl,
|
|
16
|
-
|
|
16
|
+
targetCantBeAspect,
|
|
17
17
|
pathStartsWithSelf,
|
|
18
18
|
columnRefStartsWithSelf,
|
|
19
19
|
isAssocToPrimaryKeys,
|
|
@@ -275,6 +275,28 @@ function fns( model ) {
|
|
|
275
275
|
check: checkRefInQuery,
|
|
276
276
|
param: paramSemantics,
|
|
277
277
|
},
|
|
278
|
+
annotation: { // annotation assignments
|
|
279
|
+
lexical: justDollarSelf, // TODO: forbid $projection
|
|
280
|
+
dollar: true,
|
|
281
|
+
dynamic: parentElements,
|
|
282
|
+
navigation: assocOnNavigation,
|
|
283
|
+
noDep: true,
|
|
284
|
+
notFound: undefinedParentElement,
|
|
285
|
+
messageMap: {
|
|
286
|
+
'ref-undefined-element': 'anno-undefined-element',
|
|
287
|
+
'ref-undefined-param': 'anno-undefined-param',
|
|
288
|
+
},
|
|
289
|
+
// check: checkAssocOn,
|
|
290
|
+
param: paramSemantics,
|
|
291
|
+
nestedColumn: () => ({ // in expand and inline - TODO
|
|
292
|
+
lexical: justDollarSelf,
|
|
293
|
+
dollar: true,
|
|
294
|
+
dynamic: parentElements,
|
|
295
|
+
navigation: assocOnNavigation,
|
|
296
|
+
notFound: undefinedParentElement,
|
|
297
|
+
rewriteProjectionToSelf: true,
|
|
298
|
+
}),
|
|
299
|
+
},
|
|
278
300
|
};
|
|
279
301
|
|
|
280
302
|
Object.assign( model.$functions, {
|
|
@@ -482,7 +504,7 @@ function fns( model ) {
|
|
|
482
504
|
if (user._pathHead && !semantics.isMainRef) // in expand/inline
|
|
483
505
|
semantics = semantics.nestedColumn();
|
|
484
506
|
if (typeof scope === 'string') { // typeOf, param, global
|
|
485
|
-
semantics = semantics?.[scope] && semantics[scope]( ruser, path, location );
|
|
507
|
+
semantics = semantics?.[scope] && semantics[scope]( ruser, path, location, semantics );
|
|
486
508
|
if (!semantics) {
|
|
487
509
|
if (semantics == null)
|
|
488
510
|
throw new CompilerAssertion( `Scope ${ scope } is not expected here` );
|
|
@@ -524,7 +546,7 @@ function fns( model ) {
|
|
|
524
546
|
valid.push( model.$magicVariables.elements, removeDollarNames( dynamicDict ) );
|
|
525
547
|
// TODO: streamline function arguments (probably: user, path, semantics )
|
|
526
548
|
const undef = semantics.notFound( ruser, head, valid, dynamicDict,
|
|
527
|
-
!isMainRef && user._user && user._artifact, path );
|
|
549
|
+
!isMainRef && user._user && user._artifact, path, semantics );
|
|
528
550
|
return setArtifactLink( head, undef || null );
|
|
529
551
|
}
|
|
530
552
|
|
|
@@ -572,7 +594,7 @@ function fns( model ) {
|
|
|
572
594
|
// TODO: streamline function arguments (probably: user, path, semantics, prev )
|
|
573
595
|
// false returned by semantics.navigation: no further error:
|
|
574
596
|
if (env !== false)
|
|
575
|
-
notFound( user, item, [ env ], null, prev, path );
|
|
597
|
+
notFound( user, item, [ env ], null, prev, path, semantics );
|
|
576
598
|
return null;
|
|
577
599
|
}
|
|
578
600
|
// need to do that here, because we also need to disallow Service.AutoExposed:elem
|
|
@@ -699,9 +721,14 @@ function fns( model ) {
|
|
|
699
721
|
return false;
|
|
700
722
|
}
|
|
701
723
|
|
|
702
|
-
function paramSemantics() {
|
|
703
|
-
return {
|
|
724
|
+
function paramSemantics( _user, _path, _loction, semantics ) {
|
|
725
|
+
return {
|
|
726
|
+
messageMap: semantics.messageMap,
|
|
727
|
+
dynamic: artifactParams,
|
|
728
|
+
notFound: undefinedParam,
|
|
729
|
+
};
|
|
704
730
|
}
|
|
731
|
+
|
|
705
732
|
function paramUnsupported( user, _path, location ) {
|
|
706
733
|
error( 'ref-unexpected-scope', [ location, user ], // TODO: ref-unexpected-param
|
|
707
734
|
// why an extra text for calculated elements? or separate for all?
|
|
@@ -754,9 +781,17 @@ function fns( model ) {
|
|
|
754
781
|
}
|
|
755
782
|
|
|
756
783
|
function artifactParams( user ) {
|
|
757
|
-
const lexical = (user._main || user).$tableAliases;
|
|
758
784
|
// TODO: already report error here if no parameters?
|
|
759
|
-
return
|
|
785
|
+
return boundActionOrMain( user ).params || Object.create( null );
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function boundActionOrMain( art ) {
|
|
789
|
+
while (art._main) {
|
|
790
|
+
if (art.kind === 'action' || art.kind === 'function')
|
|
791
|
+
return art;
|
|
792
|
+
art = art._parent;
|
|
793
|
+
}
|
|
794
|
+
return art;
|
|
760
795
|
}
|
|
761
796
|
|
|
762
797
|
function typeOfParentDict( user ) {
|
|
@@ -899,18 +934,29 @@ function fns( model ) {
|
|
|
899
934
|
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
900
935
|
}
|
|
901
936
|
|
|
902
|
-
function undefinedForAnnotate( user, item, valid, _dict, prev ) {
|
|
937
|
+
function undefinedForAnnotate( user, item, valid, _dict, prev, path ) {
|
|
903
938
|
// in a CSN source, only one env was tested (valid.length 1):
|
|
904
939
|
const art = (!prev) ? item.id : searchName( prev, item.id, 'absolute' );
|
|
905
|
-
|
|
940
|
+
if (!user.elements && !user.actions && !user.enum && !user.params &&
|
|
941
|
+
couldBeDraftsEntity( item.id, valid, prev, path ))
|
|
942
|
+
return;
|
|
943
|
+
signalNotFound( (valid.length > 1 ? 'ext-undefined-art' : 'ext-undefined-def'),
|
|
906
944
|
// TODO: ext-undefined-xyz
|
|
907
945
|
[ item.location, user ], valid, { art } );
|
|
908
946
|
}
|
|
909
947
|
|
|
910
|
-
function
|
|
911
|
-
|
|
948
|
+
function couldBeDraftsEntity( id, valid, prev, path ) {
|
|
949
|
+
const entity = prev
|
|
950
|
+
? prev === path[path.length - 2]._artifact && prev
|
|
951
|
+
: path.length === 1 && id.endsWith( '.drafts' ) && model.definitions[id.slice( 0, -7 )];
|
|
952
|
+
return entity?.kind === 'entity' && !!entity._service;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function undefinedParam( user, head, valid, _dict, _art, _path, semantics ) {
|
|
956
|
+
// TODO: text variant if there are no parameters, or in artifactParameters()
|
|
957
|
+
// TODO: use prepared message variants
|
|
912
958
|
signalNotFound( 'ref-undefined-param', [ head.location, user ], valid,
|
|
913
|
-
{ art: user
|
|
959
|
+
{ art: boundActionOrMain( user ), id: head.id }, semantics );
|
|
914
960
|
}
|
|
915
961
|
|
|
916
962
|
function undefinedTargetElement( user, head, valid, _dict, pathItemArtifact ) {
|
|
@@ -953,7 +999,7 @@ function fns( model ) {
|
|
|
953
999
|
}
|
|
954
1000
|
}
|
|
955
1001
|
|
|
956
|
-
function undefinedParentElement( user, head, valid, dynamicDict ) {
|
|
1002
|
+
function undefinedParentElement( user, head, valid, dynamicDict, _art, _path, semantics ) {
|
|
957
1003
|
// TODO: we might mention both the "direct" and the "effective" type and
|
|
958
1004
|
// always just mentioned one identifier as not found
|
|
959
1005
|
const { id } = head;
|
|
@@ -969,7 +1015,7 @@ function fns( model ) {
|
|
|
969
1015
|
const msgVar = userQuery( user ) ? 'query' : null;
|
|
970
1016
|
// TODO: better with ON in expand if that is supported
|
|
971
1017
|
signalNotFound( 'ref-undefined-element', [ head.location, user ], valid,
|
|
972
|
-
{ '#': msgVar, art: head.id } );
|
|
1018
|
+
{ '#': msgVar, art: head.id }, semantics );
|
|
973
1019
|
}
|
|
974
1020
|
}
|
|
975
1021
|
|
|
@@ -994,18 +1040,21 @@ function fns( model ) {
|
|
|
994
1040
|
return undefinedItemElement( user, head, valid, null, art, path );
|
|
995
1041
|
}
|
|
996
1042
|
|
|
997
|
-
function undefinedItemElement( user, item, valid, _dict, art, path ) {
|
|
1043
|
+
function undefinedItemElement( user, item, valid, _dict, art, path, semantics ) {
|
|
998
1044
|
const query = userQuery( art );
|
|
999
1045
|
if (query?.name?.id > 1) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1046
|
+
const root = userQuery( user ) !== query && path[0]._navigation;
|
|
1047
|
+
const alias = (root?.kind === '$navElement')
|
|
1048
|
+
? root._parent
|
|
1049
|
+
: root?.kind === '$tableAlias' && root;
|
|
1050
|
+
// TODO: improve alias retrieval if inside expand/inline
|
|
1051
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
1052
|
+
{ '#': (alias ? 'alias' : 'query'), art: item.id, alias: alias?.name?.id },
|
|
1053
|
+
semantics );
|
|
1005
1054
|
}
|
|
1006
1055
|
else if (art.kind === '$parameters') {
|
|
1007
1056
|
signalNotFound( 'ref-undefined-param', [ item.location, user ],
|
|
1008
|
-
valid, { art: art._main, id: item.id } );
|
|
1057
|
+
valid, { art: art._main, id: item.id }, semantics );
|
|
1009
1058
|
}
|
|
1010
1059
|
else if (art.kind === 'builtin') { // magic variable / replacement variable
|
|
1011
1060
|
// $magic.{ var } is a configurable error,
|
|
@@ -1015,27 +1064,24 @@ function fns( model ) {
|
|
|
1015
1064
|
// eslint-disable-next-line no-cond-assign
|
|
1016
1065
|
while ((head = head?._parent) && head.kind === 'builtin')
|
|
1017
1066
|
id = `${ head.name.id }.${ id }`;
|
|
1018
|
-
|
|
1019
|
-
|
|
1067
|
+
const msgId = (art.$uncheckedElements) ? 'ref-unknown-var' : 'ref-undefined-var';
|
|
1068
|
+
signalNotFound( msgId, [ item.location, user ], valid, { id }, semantics );
|
|
1020
1069
|
}
|
|
1021
|
-
else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect
|
|
1022
|
-
signalNotFound( 'ref-undefined-element', [ item.location, user ],
|
|
1023
|
-
|
|
1070
|
+
else if (art.kind === 'aspect' && !art.name) { // anonymous target aspect - TODO: still?
|
|
1071
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
1072
|
+
{ '#': 'aspect', id: item.id }, semantics );
|
|
1024
1073
|
}
|
|
1025
1074
|
else {
|
|
1026
1075
|
const target = art._effectiveType?.target;
|
|
1027
1076
|
if (target?._artifact) {
|
|
1028
1077
|
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
1029
|
-
{ '#': 'target', art: target, id: item.id } );
|
|
1078
|
+
{ '#': 'target', art: target, id: item.id }, semantics );
|
|
1030
1079
|
}
|
|
1031
1080
|
else if (!target) {
|
|
1032
1081
|
const variant = art.kind === 'aspect' && !art.name && 'aspect';
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
art: (variant ? '' : searchName( art, item.id, 'element' )),
|
|
1037
|
-
id: item.id,
|
|
1038
|
-
} );
|
|
1082
|
+
const a = (variant) ? '' : searchName( art, item.id, 'element' );
|
|
1083
|
+
signalNotFound( 'ref-undefined-element', [ item.location, user ], valid,
|
|
1084
|
+
{ '#': variant, art: a, id: item.id }, semantics );
|
|
1039
1085
|
}
|
|
1040
1086
|
}
|
|
1041
1087
|
return null;
|
|
@@ -1189,15 +1235,15 @@ function fns( model ) {
|
|
|
1189
1235
|
return false;
|
|
1190
1236
|
}
|
|
1191
1237
|
|
|
1192
|
-
// Remember:
|
|
1238
|
+
// Remember: a valid aspect should have already been moved to XSN targetAspect, but
|
|
1193
1239
|
// the error messages should still talk about potential aspects
|
|
1194
1240
|
function acceptEntity( art, user, ref ) { // for target
|
|
1195
1241
|
if (art.kind === 'entity')
|
|
1196
1242
|
return art;
|
|
1197
1243
|
// Extra msg text with Composition of NeitherEntityNorAspect:
|
|
1198
1244
|
const bare = !art.elements || art.elements[$inferred];
|
|
1199
|
-
const std = (
|
|
1200
|
-
const msg = std
|
|
1245
|
+
const std = targetCantBeAspect( user );
|
|
1246
|
+
const msg = std || (bare && art.kind === 'aspect' ? 'bare' : 'composition');
|
|
1201
1247
|
signalNotFound( 'ref-invalid-target', [ ref.location, user ], null,
|
|
1202
1248
|
{ '#': msg } );
|
|
1203
1249
|
return false;
|
|
@@ -1558,12 +1604,12 @@ function fns( model ) {
|
|
|
1558
1604
|
* @param {object[]} valid
|
|
1559
1605
|
* @param {object} [textParams]
|
|
1560
1606
|
*/
|
|
1561
|
-
function signalNotFound( msgId, location, valid, textParams ) {
|
|
1607
|
+
function signalNotFound( msgId, location, valid, textParams, semantics ) {
|
|
1562
1608
|
if (location.$notFound) // TODO: still necessary?
|
|
1563
1609
|
return;
|
|
1564
1610
|
location.$notFound = true;
|
|
1565
1611
|
/** @type {object} */
|
|
1566
|
-
const err = message( msgId, location, textParams );
|
|
1612
|
+
const err = message( semantics?.messageMap?.[msgId] || msgId, location, textParams );
|
|
1567
1613
|
if (valid) {
|
|
1568
1614
|
const user = Array.isArray( location ) && location[1];
|
|
1569
1615
|
err.validNames = (user && definedViaCdl( user )); // viaCdl -> '.'?
|