@sap/cds-compiler 6.9.3 → 7.0.1
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 +76 -2
- package/bin/cdsc.js +4 -33
- package/doc/IncompatibleChanges_v7.md +639 -0
- package/lib/api/main.js +4 -56
- package/lib/api/options.js +5 -15
- package/lib/api/validate.js +1 -0
- package/lib/base/builtins.js +1 -2
- package/lib/base/csnRefs.js +2 -6
- package/lib/base/message-registry.js +82 -76
- package/lib/base/messages.js +8 -5
- package/lib/base/optionProcessor.js +2 -72
- package/lib/base/specialOptions.js +20 -17
- package/lib/checks/defaultValues.js +1 -39
- package/lib/checks/hasPersistedElements.js +19 -3
- package/lib/checks/parameters.js +0 -34
- package/lib/checks/selectItems.js +2 -38
- package/lib/checks/typeParameters.js +162 -0
- package/lib/checks/validator.js +5 -8
- package/lib/compiler/assert-consistency.js +19 -5
- package/lib/compiler/checks.js +47 -43
- package/lib/compiler/define.js +6 -6
- package/lib/compiler/extend.js +102 -111
- package/lib/compiler/generate.js +4 -8
- package/lib/compiler/populate.js +4 -7
- package/lib/compiler/propagator.js +9 -9
- package/lib/compiler/resolve.js +205 -7
- package/lib/compiler/shared.js +76 -82
- package/lib/compiler/tweak-assocs.js +102 -22
- package/lib/compiler/utils.js +57 -12
- package/lib/compiler/xpr-rewrite.js +2 -15
- package/lib/edm/annotations/edmJson.js +14 -10
- package/lib/edm/annotations/genericTranslation.js +3 -1
- package/lib/edm/annotations/preprocessAnnotations.js +9 -26
- package/lib/edm/csn2edm.js +27 -20
- package/lib/edm/edmUtils.js +25 -0
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +2237 -2241
- package/lib/gen/Dictionary.json +17 -2
- package/lib/json/from-csn.js +67 -52
- package/lib/json/to-csn.js +28 -25
- package/lib/language/textUtils.js +0 -13
- package/lib/main.d.ts +22 -59
- package/lib/main.js +1 -1
- package/lib/model/csnUtils.js +9 -8
- package/lib/parsers/AstBuildingParser.js +45 -55
- package/lib/parsers/Lexer.js +2 -0
- package/lib/parsers/identifiers.js +0 -9
- package/lib/render/toCdl.js +41 -40
- package/lib/render/toSql.js +8 -1
- package/lib/render/utils/common.js +1 -1
- package/lib/render/utils/sql.js +2 -3
- package/lib/tool-lib/enrichCsn.js +1 -2
- package/lib/transform/db/applyTransformations.js +7 -5
- package/lib/transform/db/assertUnique.js +8 -51
- package/lib/transform/db/associations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -15
- package/lib/transform/db/expansion.js +9 -12
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/groupByOrderBy.js +0 -16
- package/lib/transform/db/views.js +57 -161
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdata.js +25 -14
- package/lib/transform/forRelationalDB.js +93 -301
- package/lib/transform/localized.js +33 -102
- package/lib/transform/odata/flattening.js +11 -2
- package/lib/transform/transformUtils.js +25 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -2
- package/package.json +2 -2
- package/lib/render/toHdbcds.js +0 -1810
package/lib/compiler/resolve.js
CHANGED
|
@@ -59,6 +59,7 @@ const {
|
|
|
59
59
|
compositionTextVariant,
|
|
60
60
|
targetCantBeAspect,
|
|
61
61
|
userParam,
|
|
62
|
+
userQuery,
|
|
62
63
|
forEachDefinition,
|
|
63
64
|
forEachMember,
|
|
64
65
|
forEachGeneric,
|
|
@@ -96,11 +97,15 @@ function resolve( model ) {
|
|
|
96
97
|
getInheritedProp,
|
|
97
98
|
hasTruthyProp, // limited inheritance
|
|
98
99
|
resolveTypeArgumentsUnchecked,
|
|
100
|
+
userTargetElementPathIndex,
|
|
101
|
+
getQueryOperatorName,
|
|
102
|
+
traverseDollarSelfPairs,
|
|
99
103
|
} = model.$functions;
|
|
100
104
|
Object.assign( model.$functions, {
|
|
101
105
|
addForeignKeyNavigations,
|
|
102
106
|
redirectionChain,
|
|
103
107
|
resolveExprInAnnotations,
|
|
108
|
+
checkBacklinkPartner,
|
|
104
109
|
} );
|
|
105
110
|
|
|
106
111
|
const ignoreSpecifiedElements
|
|
@@ -154,6 +159,8 @@ function resolve( model ) {
|
|
|
154
159
|
// Phase 4: resolve all artifacts:
|
|
155
160
|
forEachDefinition( model, resolveRefs );
|
|
156
161
|
forEachGeneric( model, 'vocabularies', resolveRefs );
|
|
162
|
+
forEachDefinition( model, checkOpenBacklinkTargets ); // after checkBacklinkPartner()
|
|
163
|
+
|
|
157
164
|
if (options.lspMode) {
|
|
158
165
|
for (const name in model.sources)
|
|
159
166
|
resolveDefinitionName( model.sources[name].namespace );
|
|
@@ -287,7 +294,7 @@ function resolve( model ) {
|
|
|
287
294
|
}
|
|
288
295
|
// Second argument controls whether `key` is only propagated along simple
|
|
289
296
|
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
290
|
-
traverseQueryPost( view.query,
|
|
297
|
+
traverseQueryPost( view.query, !!options.v6KeyPropagation, ( query ) => {
|
|
291
298
|
if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
|
|
292
299
|
withKeyPropagation( query )) // now the part with messages
|
|
293
300
|
inheritKeyProp( query, true );
|
|
@@ -357,10 +364,10 @@ function resolve( model ) {
|
|
|
357
364
|
one: 'Key $(NAMES) has not been projected - key properties are not propagated',
|
|
358
365
|
} );
|
|
359
366
|
}
|
|
360
|
-
if (options.
|
|
367
|
+
if (!options.v6KeyPropagation)
|
|
361
368
|
return propagateKeys;
|
|
362
369
|
|
|
363
|
-
//
|
|
370
|
+
// v6: check that there is no to-many assoc in the FROM reference or in the select
|
|
364
371
|
// item reference (references in expressions are not checked,
|
|
365
372
|
// withAssociation() will see no _artifact links anyway)
|
|
366
373
|
const toMany = withAssociation( from, targetMaxNotOne, true );
|
|
@@ -548,6 +555,7 @@ function resolve( model ) {
|
|
|
548
555
|
}
|
|
549
556
|
}
|
|
550
557
|
if (obj.target) {
|
|
558
|
+
// TODO: introduce some resolveAssociation()
|
|
551
559
|
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
552
560
|
resolveTarget( art, obj );
|
|
553
561
|
else
|
|
@@ -1079,6 +1087,9 @@ function resolve( model ) {
|
|
|
1079
1087
|
}
|
|
1080
1088
|
}
|
|
1081
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* Resolve user-provided target.
|
|
1092
|
+
*/
|
|
1082
1093
|
function resolveTarget( art, obj ) {
|
|
1083
1094
|
if (art !== obj && obj.on) {
|
|
1084
1095
|
// Unmanaged assoc inside items. Unmanaged assoc in param handled in resolveRefs()
|
|
@@ -1110,8 +1121,8 @@ function resolve( model ) {
|
|
|
1110
1121
|
// TODO: also warning if inside structure
|
|
1111
1122
|
}
|
|
1112
1123
|
else { // if (obj.target._artifact)
|
|
1113
|
-
// TODO: extra with $inferred (to avoid messages)?
|
|
1114
1124
|
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
|
|
1125
|
+
checkBacklinkPartner( obj );
|
|
1115
1126
|
}
|
|
1116
1127
|
}
|
|
1117
1128
|
else if (art.kind === 'mixin') {
|
|
@@ -1138,6 +1149,187 @@ function resolve( model ) {
|
|
|
1138
1149
|
}
|
|
1139
1150
|
}
|
|
1140
1151
|
|
|
1152
|
+
function checkBareDollarSelf( assoc ) {
|
|
1153
|
+
traverseDollarSelfPairs( assoc.on, assoc, ( item, right, itemIsPath ) => {
|
|
1154
|
+
registerBacklink( item, right, itemIsPath );
|
|
1155
|
+
checkAssocOnSelf( item );
|
|
1156
|
+
checkAssocOnSelf( right );
|
|
1157
|
+
} );
|
|
1158
|
+
return;
|
|
1159
|
+
|
|
1160
|
+
function registerBacklink( item, right, itemIsPath ) {
|
|
1161
|
+
const backlinkPath = itemIsPath ? item : right;
|
|
1162
|
+
const last = backlinkPath.path[backlinkPath.path.length - 1];
|
|
1163
|
+
if (assoc.$backlink) {
|
|
1164
|
+
const location = combinedLocation( item.path[0], right.path[right.path.length - 1] );
|
|
1165
|
+
message( 'ref-duplicate-self-comparison', [ location, assoc ], { id: '$self' },
|
|
1166
|
+
'Duplicate comparison with a bare $(ID) reference in an ON-condition' );
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const backlinkRef = last?._artifact;
|
|
1170
|
+
if (backlinkRef?.target) {
|
|
1171
|
+
const backlink = { location: last.location, target: {} };
|
|
1172
|
+
setLink( backlink, '_origin', backlinkRef );
|
|
1173
|
+
setLink( backlink, '_outer', assoc );
|
|
1174
|
+
assoc.$backlink = backlink;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function checkAssocOnSelf( ref ) {
|
|
1179
|
+
// TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
|
|
1180
|
+
// target of ‹backlink› is not the current entity.
|
|
1181
|
+
// - what does it mean in an aspect
|
|
1182
|
+
// - how about auto-redirections/rewrite
|
|
1183
|
+
// TODO: also disallow if not in entity (
|
|
1184
|
+
const { path } = ref;
|
|
1185
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
1186
|
+
if (assoc.on.$inferred)
|
|
1187
|
+
return;
|
|
1188
|
+
const query = userQuery( assoc );
|
|
1189
|
+
const main = query?._main;
|
|
1190
|
+
if (query && query !== main._leadingQuery) {
|
|
1191
|
+
const { txt, op } = getQueryOperatorName( query );
|
|
1192
|
+
const { location, id } = path[0];
|
|
1193
|
+
error( 'ref-unexpected-self', [ location, assoc ], { '#': txt, id, op } );
|
|
1194
|
+
}
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
const index = userTargetElementPathIndex( assoc, path );
|
|
1198
|
+
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
1199
|
+
if (!target && ref._artifact) {
|
|
1200
|
+
const last = path[path.length - 1];
|
|
1201
|
+
error( 'ref-expecting-target-assoc', [ last.location, assoc ], { id: '$self' },
|
|
1202
|
+
'Only an association of the target side can be compared to $(ID)' );
|
|
1203
|
+
// TODO: ref-expecting-backlink 'Only a backlink association can ...'
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Check that the target T of the backlink association is related to the entity
|
|
1210
|
+
* (or other structure) E where `assoc` is embedded in: E is a valid redirection
|
|
1211
|
+
* target (without E having to be an entity) for T (or the "simple projection
|
|
1212
|
+
* ancestor" of T if `assoc` is managed, see below).
|
|
1213
|
+
*
|
|
1214
|
+
* Note: we do not forbid backlink comparisons in non-entities, because:
|
|
1215
|
+
*
|
|
1216
|
+
* - they might appear during recompilation case when including an entity,
|
|
1217
|
+
* - they are ok in an aspect used as target of a composition.
|
|
1218
|
+
*
|
|
1219
|
+
* Aspects can have _one_ "open backlink target", i.e. a target T which does not
|
|
1220
|
+
* fulfil the above condition. The check is then performed for all definitions
|
|
1221
|
+
* which include the aspect, see function checkOpenBacklinkTargets().
|
|
1222
|
+
*
|
|
1223
|
+
* A "simple projection ancestor" is the (recursive) query source of a simple
|
|
1224
|
+
* projection (one source, no assoc navigation, no query in FROM).
|
|
1225
|
+
*
|
|
1226
|
+
* If the condition is fulfilled, we can properly expand a backlink comparison,
|
|
1227
|
+
* taking potential renamings of source elements into account.
|
|
1228
|
+
*/
|
|
1229
|
+
function checkBacklinkPartner( assoc ) {
|
|
1230
|
+
checkBareDollarSelf( assoc );
|
|
1231
|
+
// console.log('CBP:',assoc.kind,assoc._main?.name?.id,assoc.name.id,!!assoc.$backlink)
|
|
1232
|
+
const target = assoc.$backlink?._origin?.target?._artifact;
|
|
1233
|
+
if (!target)
|
|
1234
|
+
return;
|
|
1235
|
+
const main = assoc._main;
|
|
1236
|
+
const chain = main.name && redirectionChain( null, main, target, true );
|
|
1237
|
+
// Remark: an anonymous target in `targetAspect` does not have a name
|
|
1238
|
+
setLink( assoc.$backlink, '_redirected', chain );
|
|
1239
|
+
if (chain) // standard correct case
|
|
1240
|
+
return;
|
|
1241
|
+
|
|
1242
|
+
if (target._ancestors) { // && assocHasForeignKeys( assoc.$backlink?._origin )) {
|
|
1243
|
+
// with non-cyclic projection ancestors, not only for managed assoc, but also
|
|
1244
|
+
// unmanaged, see test3/Associations/Backlinks/CrossEyedBacklink.sql-err.cds
|
|
1245
|
+
// Current entity is no valid redirection target for the backlink association
|
|
1246
|
+
// → try whether it would be if its target is replaced with its “simple ancestor”
|
|
1247
|
+
// (simple projection, only managed assoc - TODO for tweak-assoc.js).
|
|
1248
|
+
// TODO: include this "go to ancestor" logic into redirectionChain()?
|
|
1249
|
+
// ...probably just if implicit key calculation is part of populate.js
|
|
1250
|
+
let ancestor = target;
|
|
1251
|
+
// TODO: warning!!!
|
|
1252
|
+
while (ancestor?.query?.from?._artifact?.kind === 'entity')
|
|
1253
|
+
ancestor = ancestor.query.from._artifact;
|
|
1254
|
+
// TODO: we should do the "simple projection ancestor" probably only if no
|
|
1255
|
+
// include is involved in the redirection chain
|
|
1256
|
+
if (redirectionChain( null, main, ancestor, true )) {
|
|
1257
|
+
warning( 'ref-dubious-backlink', [ assoc.$backlink.location, assoc ],
|
|
1258
|
+
{ target },
|
|
1259
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
1260
|
+
'The target $(TARGET) of the backlink association has just a distant relationship to the current entity' );
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if (main.kind !== 'aspect') {
|
|
1265
|
+
message( 'ref-invalid-backlink', [ assoc.$backlink.location, assoc ], { target } );
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (!main.$backlink) {
|
|
1269
|
+
main.$backlink = { target: {} };
|
|
1270
|
+
setLink( main.$backlink.target, '_artifact', target );
|
|
1271
|
+
setLink( main.$backlink, '_associations', [] );
|
|
1272
|
+
}
|
|
1273
|
+
else if (target !== main.$backlink.target?._artifact) {
|
|
1274
|
+
message( 'ref-invalid-backlink', [ assoc.$backlink.location, assoc ],
|
|
1275
|
+
{ '#': 'aspect', target } );
|
|
1276
|
+
if (main.$backlink.target) { // had been ok so far → complain about all
|
|
1277
|
+
for (const a of main.$backlink._associations) {
|
|
1278
|
+
message( 'ref-invalid-backlink', [ a.$backlink.location, a ],
|
|
1279
|
+
{ '#': 'aspect', target: a.$backlink._origin.target._artifact } );
|
|
1280
|
+
}
|
|
1281
|
+
main.$backlink.target = undefined;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
main.$backlink._associations.push( assoc );
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Check potential "open backlink target" of an aspect which is included into
|
|
1289
|
+
* `art`, see function checkBacklinkPartner(). Entities generated for managed
|
|
1290
|
+
* composition of anonymous aspects also check open backlink targets.
|
|
1291
|
+
*/
|
|
1292
|
+
function checkOpenBacklinkTargets( art ) {
|
|
1293
|
+
const includes = art.includes ||
|
|
1294
|
+
art.$inferred === 'composition-entity' && [ anonymousAspectAsInclude( art._origin ) ];
|
|
1295
|
+
// Remark: how to detect wrong open backlink target inside an anonymous target aspect:
|
|
1296
|
+
// - direct: the pseudo include of the anonymous aspect is checked via this function
|
|
1297
|
+
// - recompilation: is not checked directly, but condition is copied to generated entity
|
|
1298
|
+
if (!includes)
|
|
1299
|
+
return;
|
|
1300
|
+
for (const incl of includes) {
|
|
1301
|
+
const backlink = incl._artifact?.$backlink;
|
|
1302
|
+
if (backlink?.target && !redirectionChain( null, art, backlink.target._artifact, true )) {
|
|
1303
|
+
message( 'ref-invalid-backlink', [ incl.location, art ],
|
|
1304
|
+
{ '#': 'include', target: backlink.target } );
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function anonymousAspectAsInclude( aspect ) {
|
|
1310
|
+
return { _artifact: aspect, location: aspect.location };
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// /**
|
|
1314
|
+
// * Return true if the association `assoc` is managed. Note: the foreign keys
|
|
1315
|
+
// * have not been calculated and propagated/rewritten yet.
|
|
1316
|
+
// */
|
|
1317
|
+
// function assocHasForeignKeys( assoc ) {
|
|
1318
|
+
// if (!assoc._effectiveType)
|
|
1319
|
+
// return null;
|
|
1320
|
+
// do {
|
|
1321
|
+
// if (assoc.foreignKeys)
|
|
1322
|
+
// return true;
|
|
1323
|
+
// if (assoc.on)
|
|
1324
|
+
// return false;
|
|
1325
|
+
// const max = assoc.cardinality?.targetMax;
|
|
1326
|
+
// if (max && (typeof max.val !== 'number' || max.val > 1))
|
|
1327
|
+
// return false;
|
|
1328
|
+
// // TODO: check newAssoc = managed[filter]
|
|
1329
|
+
// assoc = getOrigin( assoc );
|
|
1330
|
+
// } while (assoc);
|
|
1331
|
+
// return true;
|
|
1332
|
+
// }
|
|
1141
1333
|
|
|
1142
1334
|
function checkRedirectedUserTarget( art ) {
|
|
1143
1335
|
const issue = { target: art.target._artifact };
|
|
@@ -1326,6 +1518,11 @@ function resolve( model ) {
|
|
|
1326
1518
|
}
|
|
1327
1519
|
}
|
|
1328
1520
|
|
|
1521
|
+
/**
|
|
1522
|
+
* Check whether it is correct to redirect assoc `elem` to `target` (will always
|
|
1523
|
+
* be the case for auto-redirected assocs). Finally, set the `elem._redirected`
|
|
1524
|
+
* to the "redirection chain", i.e. an array of all entities involved.
|
|
1525
|
+
*/
|
|
1329
1526
|
// TODO: add this somehow to tweak-assocs.js ?
|
|
1330
1527
|
function resolveRedirected( elem, target ) {
|
|
1331
1528
|
setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
|
|
@@ -1508,7 +1705,7 @@ function resolve( model ) {
|
|
|
1508
1705
|
}
|
|
1509
1706
|
|
|
1510
1707
|
function resolveAnnoExpr( expr, art, anno = expr, topItem = false ) {
|
|
1511
|
-
if (expr.$tokenTexts) {
|
|
1708
|
+
if (expr.$tokenTexts || expr.sym) {
|
|
1512
1709
|
if (anno === expr) {
|
|
1513
1710
|
initAnnotationForExpression( anno, art );
|
|
1514
1711
|
}
|
|
@@ -1526,7 +1723,7 @@ function resolve( model ) {
|
|
|
1526
1723
|
const withExpr = anno === expr &&
|
|
1527
1724
|
// the following is needed for checkAnnotationAcceptsExpressions(),
|
|
1528
1725
|
// otherwise @cds.persistence.skip: [hdi, hdbcds, sql.postgres] fails
|
|
1529
|
-
expr.val.some( item => item.$tokenTexts || item.literal === 'struct');
|
|
1726
|
+
expr.val.some( item => item.$tokenTexts || item.sym || item.literal === 'struct');
|
|
1530
1727
|
if (withExpr)
|
|
1531
1728
|
initAnnotationForExpression( anno, art );
|
|
1532
1729
|
expr.val.forEach( val => resolveAnnoExpr( val, art, anno, withExpr ) );
|
|
@@ -1725,7 +1922,8 @@ function resolve( model ) {
|
|
|
1725
1922
|
setLink( sym, '_artifact', symbols[sym.id] );
|
|
1726
1923
|
}
|
|
1727
1924
|
else {
|
|
1728
|
-
|
|
1925
|
+
setLink( sym, '_artifact', null );
|
|
1926
|
+
// inferred enums can't be extended (yet): show underlying enum - TODO: now possible
|
|
1729
1927
|
while (type.enum[$inferred])
|
|
1730
1928
|
type = getOrigin( type );
|
|
1731
1929
|
const err = message( 'ref-undefined-enum', [ sym.location, user ],
|
package/lib/compiler/shared.js
CHANGED
|
@@ -48,10 +48,7 @@ function fns( model ) {
|
|
|
48
48
|
lexical: null,
|
|
49
49
|
dynamic: modelDefinitions,
|
|
50
50
|
notFound: undefinedDefinition,
|
|
51
|
-
messageMap: {
|
|
52
|
-
'ref-undefined-art': 'ref-undefined-using',
|
|
53
|
-
'ref-undefined-def': 'ref-undefined-using',
|
|
54
|
-
},
|
|
51
|
+
messageMap: { 'ref-undefined-def': 'ref-undefined-using' },
|
|
55
52
|
},
|
|
56
53
|
// scope:'global': for cds.Association and auto-redirected targets
|
|
57
54
|
$global: {
|
|
@@ -73,10 +70,7 @@ function fns( model ) {
|
|
|
73
70
|
lexical: userBlock,
|
|
74
71
|
dynamic: modelDefinitions,
|
|
75
72
|
notFound: undefinedDefinition,
|
|
76
|
-
messageMap: {
|
|
77
|
-
'ref-undefined-art': 'ext-undefined-art-sec',
|
|
78
|
-
'ref-undefined-def': 'ext-undefined-def-sec',
|
|
79
|
-
},
|
|
73
|
+
messageMap: { 'ref-undefined-def': 'ext-undefined-def-sec' },
|
|
80
74
|
accept: extendableArtifact,
|
|
81
75
|
},
|
|
82
76
|
extend: {
|
|
@@ -353,6 +347,9 @@ function fns( model ) {
|
|
|
353
347
|
navigationEnv,
|
|
354
348
|
nestedElements,
|
|
355
349
|
attachAndEmitValidNames,
|
|
350
|
+
userTargetElementPathIndex,
|
|
351
|
+
getQueryOperatorName,
|
|
352
|
+
traverseDollarSelfPairs,
|
|
356
353
|
} );
|
|
357
354
|
traverseExpr.STOP = Symbol( 'STOP' );
|
|
358
355
|
traverseExpr.SKIP = Symbol( 'SKIP' );
|
|
@@ -942,7 +939,7 @@ function fns( model ) {
|
|
|
942
939
|
else if (art.$inferred === 'autoexposed' && !user.$inferred &&
|
|
943
940
|
isMainRef !== 'no-leaf-gap') {
|
|
944
941
|
// Depending on the processing sequence, the following could be a
|
|
945
|
-
// simple 'ref-undefined-
|
|
942
|
+
// simple 'ref-undefined-def' - TODO: which we
|
|
946
943
|
// could "change" to this message at the end of compile():
|
|
947
944
|
// HINT: this does not appear anymore
|
|
948
945
|
error( 'ref-unexpected-autoexposed', [ item.location, user ], { art },
|
|
@@ -1028,7 +1025,8 @@ function fns( model ) {
|
|
|
1028
1025
|
for (let env = lexical; env; env = env._block)
|
|
1029
1026
|
valid.push( removeGapArtifact( env.artifacts || Object.create( null ) ) );
|
|
1030
1027
|
}
|
|
1031
|
-
|
|
1028
|
+
const dynamicDict = semantics.dynamic( user );
|
|
1029
|
+
valid.push( removeGapArtifact( dynamicDict, !definedViaCdl( user ) ) );
|
|
1032
1030
|
semantics.notFound?.( user._user || user, head, valid, model.definitions,
|
|
1033
1031
|
null, path, semantics );
|
|
1034
1032
|
|
|
@@ -1373,10 +1371,14 @@ function fns( model ) {
|
|
|
1373
1371
|
// Functions called via semantics.notFound: -----------------------------------
|
|
1374
1372
|
|
|
1375
1373
|
function undefinedDefinition( user, item, valid, _dict, prev, _path, semantics ) {
|
|
1376
|
-
// in a CSN source
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1374
|
+
// in a CSN source, for `using`, or if not the first path item, only one env
|
|
1375
|
+
// was tested (valid.length 1) → don't mention `using` declaration
|
|
1376
|
+
const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
|
|
1377
|
+
const msg = (valid.length > 1 && proposeUsingDecl( name, valid.at( -2 ) ))
|
|
1378
|
+
? 'using'
|
|
1379
|
+
: 'std';
|
|
1380
|
+
signalNotFound( 'ref-undefined-def', [ item.location, user ], valid,
|
|
1381
|
+
{ '#': msg, art: name, keyword: 'using' }, semantics );
|
|
1380
1382
|
// TODO: improve text, use text variant for: "or builtin" or "definitions" or none
|
|
1381
1383
|
}
|
|
1382
1384
|
|
|
@@ -1405,21 +1407,17 @@ function fns( model ) {
|
|
|
1405
1407
|
name === 'DRAFT' && path?.length === 2 && path[1].id === 'DraftAdministrativeData' ||
|
|
1406
1408
|
name.startsWith( 'localized.' )) // TODO: only if suffix is defined
|
|
1407
1409
|
return;
|
|
1408
|
-
signalNotFound(
|
|
1409
|
-
// TODO: ext-undefined-xyz
|
|
1410
|
+
signalNotFound( 'ext-undefined-def', // TODO: ext-undefined-xyz
|
|
1410
1411
|
[ item.location, user ], valid, { art: name } );
|
|
1411
1412
|
}
|
|
1412
1413
|
|
|
1413
1414
|
function undefinedForExtend( user, item, valid, _dict, prev ) {
|
|
1414
1415
|
// in a CSN source, only one env was tested (valid.length 1):
|
|
1415
1416
|
const name = (prev) ? `${ prev.name.id }.${ item.id }` : item.id;
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
else {
|
|
1421
|
-
undefinedDefinition( user, item, valid, _dict, prev );
|
|
1422
|
-
}
|
|
1417
|
+
// TODO: do not “accept” `localized` as first path item for `extend`
|
|
1418
|
+
const msg = (name.startsWith( 'localized.' )) ? 'localized' : 'std';
|
|
1419
|
+
signalNotFound( 'ref-undefined-def', [ item.location, user ],
|
|
1420
|
+
valid, { '#': msg, art: name } );
|
|
1423
1421
|
}
|
|
1424
1422
|
|
|
1425
1423
|
function signalElementHint( user, item, valid, prev, semantics, path ) {
|
|
@@ -1879,35 +1877,26 @@ function fns( model ) {
|
|
|
1879
1877
|
}
|
|
1880
1878
|
|
|
1881
1879
|
// TODO: Don't allow path args and filter!
|
|
1882
|
-
function checkOnCondition( expr, exprCtx, user ) {
|
|
1883
|
-
|
|
1884
|
-
return;
|
|
1885
|
-
const { op } = expr;
|
|
1886
|
-
let { args } = expr;
|
|
1887
|
-
if (!op || !args) {
|
|
1888
|
-
checkExpr( expr, exprCtx, user );
|
|
1889
|
-
return;
|
|
1890
|
-
}
|
|
1891
|
-
if (op?.val === '=') // TMP
|
|
1892
|
-
args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
|
|
1880
|
+
function checkOnCondition( expr, exprCtx, user ) { // TODO: move to tweak-assoc.js (or resolve.js)
|
|
1881
|
+
traverseDollarSelfPairs( expr, user, onPair, onLeaf );
|
|
1893
1882
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
(isDollarSelfPair( item, right, user ) || isDollarSelfPair( right, item, user ))) {
|
|
1901
|
-
checkAssocOnSelf( item, exprCtx, user );
|
|
1902
|
-
checkAssocOnSelf( right, exprCtx, user );
|
|
1903
|
-
index += 2;
|
|
1904
|
-
continue;
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
checkOnCondition( item, exprCtx, user );
|
|
1883
|
+
function onPair( item, right ) {
|
|
1884
|
+
checkDollarSelfPairNavigation( item, user );
|
|
1885
|
+
checkDollarSelfPairNavigation( right, user );
|
|
1886
|
+
}
|
|
1887
|
+
function onLeaf( leaf ) {
|
|
1888
|
+
checkExpr( leaf, exprCtx, user );
|
|
1908
1889
|
}
|
|
1909
1890
|
}
|
|
1910
1891
|
|
|
1892
|
+
function checkDollarSelfPairNavigation( ref, user ) {
|
|
1893
|
+
const { path } = ref;
|
|
1894
|
+
if (path.length === 1 && path[0]._navigation?.kind === '$self')
|
|
1895
|
+
return;
|
|
1896
|
+
const index = userTargetElementPathIndex( user, path );
|
|
1897
|
+
checkOnlyForeignKeyNavigation( user, path, index );
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1911
1900
|
// // standard element reference check;
|
|
1912
1901
|
// function checkElementStd( art, _user, ref, semantics ) {
|
|
1913
1902
|
// // No further checks on navigation: nothing to do
|
|
@@ -1982,6 +1971,36 @@ function fns( model ) {
|
|
|
1982
1971
|
return userTargetElementPathIndex( user, path ) > 0;
|
|
1983
1972
|
}
|
|
1984
1973
|
|
|
1974
|
+
function traverseDollarSelfPairs( expr, user, onPair, onLeaf ) {
|
|
1975
|
+
const { op } = expr;
|
|
1976
|
+
let { args } = expr;
|
|
1977
|
+
if (!op || !args) {
|
|
1978
|
+
if (onLeaf)
|
|
1979
|
+
onLeaf( expr );
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (op.val === '=') // TMP
|
|
1983
|
+
args = [ args[0], { val: '=', literal: 'token' }, args[1] ];
|
|
1984
|
+
|
|
1985
|
+
for (let index = 0; index < args.length; ++index) {
|
|
1986
|
+
const item = args[index];
|
|
1987
|
+
const eq = args[index + 1];
|
|
1988
|
+
if (eq?.val === '=' && eq.literal === 'token' && item.path && !item.scope) {
|
|
1989
|
+
const right = args[index + 2];
|
|
1990
|
+
if (right?.path && !right.scope) {
|
|
1991
|
+
const itemIsPath = isDollarSelfPair( item, right, user );
|
|
1992
|
+
const rightIsPath = !itemIsPath && isDollarSelfPair( right, item, user );
|
|
1993
|
+
if (itemIsPath || rightIsPath) {
|
|
1994
|
+
onPair( item, right, itemIsPath );
|
|
1995
|
+
index += 2;
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
traverseDollarSelfPairs( item, user, onPair, onLeaf );
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1985
2004
|
function checkAssocOn( ref, exprCtx, user ) {
|
|
1986
2005
|
const { path } = ref;
|
|
1987
2006
|
if (!path)
|
|
@@ -2004,40 +2023,6 @@ function fns( model ) {
|
|
|
2004
2023
|
}
|
|
2005
2024
|
}
|
|
2006
2025
|
|
|
2007
|
-
function checkAssocOnSelf( ref, exprCtx, user ) {
|
|
2008
|
-
// TODO: fully specify what `‹current_assoc›.‹backlink› = $self` means if the
|
|
2009
|
-
// target of ‹backlink› is not the current entity.
|
|
2010
|
-
// - what does it mean in an aspect
|
|
2011
|
-
// - how about auto-redirections/rewrite
|
|
2012
|
-
const { path } = ref;
|
|
2013
|
-
if (path.length === 1 && path[0]._navigation?.kind === '$self') {
|
|
2014
|
-
const query = userQuery( user );
|
|
2015
|
-
const main = query?._main;
|
|
2016
|
-
if (query && query !== main._leadingQuery) {
|
|
2017
|
-
const { txt, op } = getQueryOperatorName( query );
|
|
2018
|
-
const { location, id } = path[0];
|
|
2019
|
-
error( 'ref-unexpected-self', [ location, user ], { '#': txt, id, op } );
|
|
2020
|
-
}
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
const index = userTargetElementPathIndex( user, path );
|
|
2024
|
-
checkOnlyForeignKeyNavigation( user, path, index );
|
|
2025
|
-
const target = index > 0 && index < path.length && ref._artifact?.target;
|
|
2026
|
-
if (!target) {
|
|
2027
|
-
const last = path[path.length - 1];
|
|
2028
|
-
error( 'ref-expecting-target-assoc', [ last.location, user ], { id: '$self' },
|
|
2029
|
-
'Only an association of the target side can be compared to $(ID)' );
|
|
2030
|
-
}
|
|
2031
|
-
// in entity: target must match
|
|
2032
|
-
// in aspect: must end with assoc, TODO: target must include aspect (+ add/ check)
|
|
2033
|
-
else if (target._artifact && target._artifact !== user._main && user._main.kind === 'entity') {
|
|
2034
|
-
const last = path[path.length - 1];
|
|
2035
|
-
warning( 'ref-invalid-backlink', [ last.location, user ], { art: target, id: '$self' },
|
|
2036
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
2037
|
-
'The target $(ART) of the association is not the current entity represented by $(ID)' );
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
2026
|
|
|
2042
2027
|
// right of union: parent = main → get correct operator
|
|
2043
2028
|
// FROM subquery: parent = tab alias of outer query
|
|
@@ -2147,6 +2132,15 @@ function fns( model ) {
|
|
|
2147
2132
|
|
|
2148
2133
|
// Low-level functions --------------------------------------------------------
|
|
2149
2134
|
|
|
2135
|
+
function proposeUsingDecl( name, sourceArtifacts ) {
|
|
2136
|
+
const art = model.definitions[name];
|
|
2137
|
+
// TODO: if there are sub artifacts, test whether at least one of them is real
|
|
2138
|
+
return art &&
|
|
2139
|
+
(art.kind !== 'namespace' || art._subArtifacts) &&
|
|
2140
|
+
!sourceArtifacts?.[name];
|
|
2141
|
+
;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2150
2144
|
/**
|
|
2151
2145
|
* Make a "not found" error and optionally attach valid names.
|
|
2152
2146
|
*
|