@sap/cds-compiler 6.3.6 → 6.4.6
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 +101 -3
- package/LICENSE +32 -0
- package/README.md +14 -2
- package/bin/cdsse.js +0 -3
- package/doc/CHANGELOG_BETA.md +1 -1
- package/doc/CHANGELOG_DEPRECATED.md +1 -1
- package/lib/base/message-registry.js +9 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +2 -0
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +4 -2
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +37 -26
- package/lib/compiler/define.js +1 -1
- package/lib/compiler/extend.js +39 -50
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/populate.js +2 -2
- package/lib/compiler/propagator.js +29 -6
- package/lib/compiler/resolve.js +13 -3
- package/lib/compiler/shared.js +157 -133
- package/lib/compiler/tweak-assocs.js +87 -29
- package/lib/compiler/xpr-rewrite.js +164 -160
- package/lib/edm/annotations/edmJson.js +206 -37
- package/lib/edm/csn2edm.js +13 -0
- package/lib/edm/edmUtils.js +2 -2
- package/lib/gen/BaseParser.js +106 -72
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1501 -1509
- package/lib/json/to-csn.js +8 -5
- package/lib/language/genericAntlrParser.js +0 -0
- package/lib/main.js +19 -16
- package/lib/model/csnRefs.js +589 -521
- package/lib/model/csnUtils.js +8 -5
- package/lib/model/enrichCsn.js +1 -0
- package/lib/parsers/AstBuildingParser.js +73 -28
- package/lib/render/toCdl.js +2 -1
- package/lib/render/toHdbcds.js +6 -3
- package/lib/render/toSql.js +5 -0
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +4 -1
- package/lib/transform/db/assocsToQueries/transformExists.js +3 -10
- package/lib/transform/db/assocsToQueries/utils.js +0 -5
- package/lib/transform/db/cdsPersistence.js +17 -18
- package/lib/transform/db/expansion.js +179 -3
- package/lib/transform/db/flattening.js +16 -5
- package/lib/transform/db/rewriteCalculatedElements.js +79 -283
- package/lib/transform/effective/main.js +8 -1
- package/lib/transform/forOdata.js +1 -1
- package/lib/transform/forRelationalDB.js +21 -80
- package/lib/transform/localized.js +75 -127
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +89 -63
- package/lib/transform/transformUtils.js +23 -21
- package/lib/transform/translateAssocsToJoins.js +7 -5
- package/lib/transform/tupleExpansion.js +16 -3
- package/package.json +3 -3
- package/doc/DeprecatedOptions_v2.md +0 -150
- package/doc/NameResolution.md +0 -837
- package/lib/transform/parseExpr.js +0 -415
package/lib/compiler/extend.js
CHANGED
|
@@ -261,8 +261,7 @@ function extend( model ) {
|
|
|
261
261
|
const { name } = ext;
|
|
262
262
|
const { path } = name;
|
|
263
263
|
if (name._artifact === undefined) {
|
|
264
|
-
|
|
265
|
-
resolvePath( name, refCtx, ext ); // for LSP
|
|
264
|
+
resolvePath( name, ext.kind, ext ); // induce error & for LSP
|
|
266
265
|
}
|
|
267
266
|
else if (model.options.lspMode && path?.[0]._artifact === undefined) {
|
|
268
267
|
// we don't use resolvePath(…,'extend'), as that would add a dependency
|
|
@@ -487,13 +486,17 @@ function extend( model ) {
|
|
|
487
486
|
return anno;
|
|
488
487
|
const hasBase = previousAnno?.literal === 'array';
|
|
489
488
|
if (!previousAnno) {
|
|
490
|
-
const location =
|
|
491
|
-
|
|
489
|
+
const { location } = anno.name;
|
|
490
|
+
if (annoName !== '@extension.code') {
|
|
491
|
+
// Remark: we could allow that for all annotations which are not propagated
|
|
492
|
+
message( 'anno-unexpected-ellipsis', [ firstEllipsis.location || location, art ],
|
|
493
|
+
{ code: '...' } );
|
|
494
|
+
}
|
|
492
495
|
previousAnno = {
|
|
493
496
|
kind: '$annotation',
|
|
494
497
|
val: [],
|
|
495
498
|
literal: 'array',
|
|
496
|
-
name:
|
|
499
|
+
name: anno.name,
|
|
497
500
|
location,
|
|
498
501
|
};
|
|
499
502
|
}
|
|
@@ -899,11 +902,9 @@ function extend( model ) {
|
|
|
899
902
|
function createSuperAnnotate( annotate ) {
|
|
900
903
|
const extensions = annotate._extensions;
|
|
901
904
|
if (extensions && !annotate._main) {
|
|
902
|
-
const
|
|
903
|
-
const isLocalized = id.startsWith( 'localized.' ); // TODO: && anno
|
|
904
|
-
const art = model.definitions[id];
|
|
905
|
+
const art = model.definitions[annotate.name.id];
|
|
905
906
|
for (const ext of extensions)
|
|
906
|
-
checkRemainingMainExtensions( art, ext
|
|
907
|
+
checkRemainingMainExtensions( art, ext );
|
|
907
908
|
if (art?.builtin && art.kind !== 'namespace') { // TODO: do not set `builtin` on cds, cds.hana
|
|
908
909
|
setLink( annotate, '_extensions', art._extensions ); // for messages and member extensions
|
|
909
910
|
// direct annotations on builtins or on the builtins for propagation, and
|
|
@@ -923,24 +924,14 @@ function extend( model ) {
|
|
|
923
924
|
forEachMember( annotate, createSuperAnnotate );
|
|
924
925
|
}
|
|
925
926
|
|
|
926
|
-
function checkRemainingMainExtensions( art, ext
|
|
927
|
-
const isExtend = ext.kind === 'extend';
|
|
928
|
-
if (localized) {
|
|
929
|
-
if (isExtend) {
|
|
930
|
-
// In v5, reject any `extend` on localized.
|
|
931
|
-
error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
|
|
932
|
-
{ '#': 'localized', keyword: 'annotate' } );
|
|
933
|
-
}
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
|
|
927
|
+
function checkRemainingMainExtensions( art, ext ) {
|
|
937
928
|
if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
|
|
938
929
|
return;
|
|
939
930
|
|
|
940
931
|
if (art?.builtin) {
|
|
941
932
|
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
942
933
|
}
|
|
943
|
-
else if (
|
|
934
|
+
else if (ext.kind === 'extend' && art?.kind === 'namespace') {
|
|
944
935
|
// `annotate` on namespaces already handled before
|
|
945
936
|
const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
|
|
946
937
|
const firstAnno = ext[hasAnnotations];
|
|
@@ -992,7 +983,7 @@ function extend( model ) {
|
|
|
992
983
|
* includes, do so without includes.
|
|
993
984
|
*/
|
|
994
985
|
function applyExtensions() {
|
|
995
|
-
let
|
|
986
|
+
let cyclicIncludeNames = false;
|
|
996
987
|
let extNames = Object.keys( extensionsDict ).sort();
|
|
997
988
|
|
|
998
989
|
while (extNames.length) {
|
|
@@ -1000,35 +991,37 @@ function extend( model ) {
|
|
|
1000
991
|
for (const name of extNames) {
|
|
1001
992
|
const art = model.definitions[name];
|
|
1002
993
|
if (art && art.kind !== 'namespace' &&
|
|
1003
|
-
extendArtifact( extensionsDict[name], art,
|
|
994
|
+
extendArtifact( extensionsDict[name], art, cyclicIncludeNames ))
|
|
1004
995
|
delete extensionsDict[name];
|
|
1005
996
|
}
|
|
1006
997
|
extNames = Object.keys( extensionsDict ); // no sort() required anymore
|
|
1007
998
|
if (extNames.length >= length)
|
|
1008
|
-
|
|
999
|
+
cyclicIncludeNames = Object.keys( extensionsDict ); // = no includes
|
|
1009
1000
|
}
|
|
1010
1001
|
}
|
|
1011
1002
|
|
|
1012
1003
|
/**
|
|
1013
|
-
* Extend artifact `art` by `extensions`. `
|
|
1014
|
-
* -
|
|
1015
|
-
* -
|
|
1016
|
-
*
|
|
1004
|
+
* Extend artifact `art` by `extensions`. `cyclicIncludeNames` can have values:
|
|
1005
|
+
* - falsy: try to apply include, then perform extend and annotate
|
|
1006
|
+
* - an array of include names with cyclic dependencies: includes are not applied,
|
|
1007
|
+
* extend and annotate is performed
|
|
1008
|
+
* remark: we could have applied includes without cycle
|
|
1009
|
+
*
|
|
1010
|
+
* Returns true if extend and annotate are performed.
|
|
1017
1011
|
*
|
|
1018
1012
|
* @param {XSN.Extension[]} extensions
|
|
1019
1013
|
* @param {XSN.Definition} art
|
|
1020
|
-
* @param {
|
|
1014
|
+
* @param {String[]|false} [cyclicIncludeNames=false]
|
|
1021
1015
|
*/
|
|
1022
|
-
function extendArtifact( extensions, art,
|
|
1023
|
-
if (!
|
|
1016
|
+
function extendArtifact( extensions, art, cyclicIncludeNames = null ) {
|
|
1017
|
+
if (!cyclicIncludeNames && !(canApplyIncludes( art, art ) &&
|
|
1024
1018
|
extensions.every( ext => canApplyIncludes( ext, art ) )))
|
|
1025
1019
|
return false;
|
|
1026
|
-
if (
|
|
1027
|
-
canApplyIncludes( art, art,
|
|
1028
|
-
extensions.forEach( ext => canApplyIncludes( ext, art,
|
|
1020
|
+
if (cyclicIncludeNames) {
|
|
1021
|
+
canApplyIncludes( art, art, cyclicIncludeNames );
|
|
1022
|
+
extensions.forEach( ext => canApplyIncludes( ext, art, cyclicIncludeNames ) );
|
|
1029
1023
|
}
|
|
1030
|
-
else if (!
|
|
1031
|
-
!(canApplyIncludes( art, art ) &&
|
|
1024
|
+
else if (!(canApplyIncludes( art, art ) &&
|
|
1032
1025
|
extensions.every( ext => canApplyIncludes( ext, art ) ))) {
|
|
1033
1026
|
// console.log( 'FALSE:',art.name, extensions.map( e => e.name ) )
|
|
1034
1027
|
return false;
|
|
@@ -1038,17 +1031,18 @@ function extend( model ) {
|
|
|
1038
1031
|
art.$entity = ++model.$entity;
|
|
1039
1032
|
}
|
|
1040
1033
|
if (art.includes) {
|
|
1041
|
-
if (!
|
|
1034
|
+
if (!cyclicIncludeNames) {
|
|
1042
1035
|
applyIncludes( art, art );
|
|
1043
1036
|
}
|
|
1044
1037
|
else {
|
|
1038
|
+
// resolve artifacts to induce errors: either ref-invalid-include or ref-cyclic
|
|
1045
1039
|
for (const ref of art.includes)
|
|
1046
1040
|
resolvePath( ref, 'include', art );
|
|
1047
1041
|
}
|
|
1048
1042
|
}
|
|
1049
1043
|
// checkExtensionsKind( extensions, art );
|
|
1050
|
-
extendMembers( extensions, art
|
|
1051
|
-
if (!
|
|
1044
|
+
extendMembers( extensions, art );
|
|
1045
|
+
if (!cyclicIncludeNames && art.includes) {
|
|
1052
1046
|
// early propagation of specific annotation assignments
|
|
1053
1047
|
propagateEarly( art, '@cds.autoexpose' );
|
|
1054
1048
|
propagateEarly( art, '@fiori.draft.enabled' );
|
|
@@ -1057,7 +1051,7 @@ function extend( model ) {
|
|
|
1057
1051
|
return true;
|
|
1058
1052
|
}
|
|
1059
1053
|
|
|
1060
|
-
function extendMembers( extensions, art
|
|
1054
|
+
function extendMembers( extensions, art ) {
|
|
1061
1055
|
// TODO: do the whole extension stuff lazily if the elements are requested
|
|
1062
1056
|
const elemExtensions = [];
|
|
1063
1057
|
if (art._main) // extensions already sorted for main artifacts
|
|
@@ -1069,11 +1063,6 @@ function extend( model ) {
|
|
|
1069
1063
|
// 'Info', 'EXT').toString())
|
|
1070
1064
|
if (ext.name._artifact === undefined) { // not already applied
|
|
1071
1065
|
setArtifactLink( ext.name, art );
|
|
1072
|
-
if (noExtend && ext.kind === 'extend') {
|
|
1073
|
-
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
|
|
1074
|
-
'You can\'t use $(KEYWORD) on the generated $(ART)' );
|
|
1075
|
-
continue;
|
|
1076
|
-
}
|
|
1077
1066
|
if (ext.includes) {
|
|
1078
1067
|
// TODO: currently, re-compiling from gensrc does not give the exact
|
|
1079
1068
|
// element sequence - we need something like
|
|
@@ -1207,17 +1196,17 @@ function extend( model ) {
|
|
|
1207
1196
|
*
|
|
1208
1197
|
* @param {XSN.Definition} art
|
|
1209
1198
|
* @param {XSN.Artifact} target
|
|
1210
|
-
* @param {string[]} [
|
|
1199
|
+
* @param {string[]} [cyclicIncludeNames]
|
|
1211
1200
|
* @returns {boolean}
|
|
1212
1201
|
*/
|
|
1213
|
-
function canApplyIncludes( art, target,
|
|
1202
|
+
function canApplyIncludes( art, target, cyclicIncludeNames ) {
|
|
1214
1203
|
if (!art.includes)
|
|
1215
1204
|
return true;
|
|
1216
1205
|
for (const ref of art.includes) {
|
|
1217
1206
|
const name = resolveUncheckedPath( ref, 'include', art );
|
|
1218
|
-
// console.log('CAI:',
|
|
1219
|
-
if (
|
|
1220
|
-
if (!
|
|
1207
|
+
// console.log('CAI:',cyclicIncludeNames, name, ref.path, Object.keys(extensionsDict))
|
|
1208
|
+
if (cyclicIncludeNames) {
|
|
1209
|
+
if (!cyclicIncludeNames.includes( name ))
|
|
1221
1210
|
continue;
|
|
1222
1211
|
delete ref._artifact;
|
|
1223
1212
|
}
|
|
@@ -40,7 +40,7 @@ function finalizeParseCdl( model ) {
|
|
|
40
40
|
// TODO: why not just use the extensions as they are from the first source?
|
|
41
41
|
for (const name in late) {
|
|
42
42
|
for (const ext of late[name]._extensions) {
|
|
43
|
-
ext.name.id = resolveUncheckedPath( ext.name, '
|
|
43
|
+
ext.name.id = resolveUncheckedPath( ext.name, '_uncheckedExtension', ext );
|
|
44
44
|
// Initialize members and define annotations in sub-elements.
|
|
45
45
|
initMembers( ext, ext, ext._block, true );
|
|
46
46
|
extensions.push( ext );
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// This files includes an iterator over "semantic tokens" in an XSN model.
|
|
9
9
|
// "Semantic tokens" are identifiers, but also the "return" parameter.
|
|
10
|
-
// See
|
|
10
|
+
// See <../../internalDoc/lsp/IdentifierCrawling.md> for details.
|
|
11
11
|
|
|
12
12
|
const { CompilerAssertion } = require('../base/error');
|
|
13
13
|
const $inferred = Symbol.for( 'cds.$inferred' );
|
package/lib/compiler/populate.js
CHANGED
|
@@ -882,7 +882,7 @@ function populate( model ) {
|
|
|
882
882
|
for (const name in env) {
|
|
883
883
|
const navElem = env[name];
|
|
884
884
|
// TODO: remove all access to masked (use 'grep')
|
|
885
|
-
if (excludingDict[name] || navElem.masked
|
|
885
|
+
if (excludingDict[name] || navElem.masked?.val)
|
|
886
886
|
continue;
|
|
887
887
|
const sibling = siblingElements[name];
|
|
888
888
|
if (sibling) { // is explicitly provided (without duplicate)
|
|
@@ -917,7 +917,7 @@ function populate( model ) {
|
|
|
917
917
|
const elemLocation = !query._main.$inferred && location;
|
|
918
918
|
const origin = envParent ? navElem : navElem._origin;
|
|
919
919
|
const elem = linkToOrigin( origin, name, query, null, elemLocation );
|
|
920
|
-
if (origin.$calcDepElement)
|
|
920
|
+
if (origin.$calcDepElement)
|
|
921
921
|
dependsOn( elem, origin.$calcDepElement, location );
|
|
922
922
|
|
|
923
923
|
// TODO: check assocToMany { * }
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
forEachDefinition,
|
|
13
13
|
forEachMember,
|
|
14
14
|
forEachGeneric,
|
|
15
|
+
isBetaEnabled,
|
|
15
16
|
} = require( '../base/model');
|
|
16
17
|
const {
|
|
17
18
|
setLink,
|
|
@@ -34,7 +35,11 @@ function propagate( model ) {
|
|
|
34
35
|
virtual,
|
|
35
36
|
notNull,
|
|
36
37
|
targetElement: onlyViaParent, // in foreign keys
|
|
37
|
-
value:
|
|
38
|
+
value: enumOrCalcValue, // enum symbol value, calculated element
|
|
39
|
+
// `value` is also used for column expression
|
|
40
|
+
// TODO(!): think of having an extra XSN property for calculated elements,
|
|
41
|
+
// replacing `value:…`+`$syntax:'calc'` and `$calc:…
|
|
42
|
+
$calc: enumOrCalcValue,
|
|
38
43
|
// masked: special = done in definer
|
|
39
44
|
// key: special = done in resolver
|
|
40
45
|
// actions: struct includes & primary source = in definer/resolver
|
|
@@ -59,7 +64,7 @@ function propagate( model ) {
|
|
|
59
64
|
// enum: expensive,
|
|
60
65
|
// params: expensive, // actually only with parent action
|
|
61
66
|
// returns,
|
|
62
|
-
$enclosed: annotation,
|
|
67
|
+
$enclosed: annotation, // TODO: hm
|
|
63
68
|
};
|
|
64
69
|
const ruleToFunction = {
|
|
65
70
|
__proto__: null,
|
|
@@ -71,7 +76,7 @@ function propagate( model ) {
|
|
|
71
76
|
for (const rule in propagationRules)
|
|
72
77
|
props[rule] = ruleToFunction[propagationRules[rule]];
|
|
73
78
|
|
|
74
|
-
const { rewriteAnnotationsRefs } = xprRewriteFns( model );
|
|
79
|
+
const { rewriteAnnotationsRefs, rewriteRefsInExpression } = xprRewriteFns( model );
|
|
75
80
|
|
|
76
81
|
const { message, throwWithError } = model.$messageFunctions;
|
|
77
82
|
|
|
@@ -165,7 +170,9 @@ function propagate( model ) {
|
|
|
165
170
|
// console.log('PROPS:',ref(source),'->',ref(target),keys.join('+'))
|
|
166
171
|
for (const prop of keys) {
|
|
167
172
|
// TODO: warning with competing props from multi-includes, but not in propagator.js
|
|
168
|
-
if (target[prop] !== undefined
|
|
173
|
+
if (target[prop] !== undefined &&
|
|
174
|
+
(prop !== 'value' || !source.$calcDepElement || !target._main?.query) ||
|
|
175
|
+
source[prop] === undefined)
|
|
169
176
|
continue;
|
|
170
177
|
const transformer = props[prop] || props[prop.charAt(0)];
|
|
171
178
|
if (transformer)
|
|
@@ -282,6 +289,22 @@ function propagate( model ) {
|
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
function enumOrCalcValue( prop, destination, origin ) {
|
|
293
|
+
// Remark: with include, the calc expression has been copied early
|
|
294
|
+
if (prop === 'value' && !origin.$calcDepElement) {
|
|
295
|
+
onlyViaParent( prop, destination, origin ); // enum value
|
|
296
|
+
}
|
|
297
|
+
else if (destination.kind === 'element' &&
|
|
298
|
+
destination._main?.query && // query element
|
|
299
|
+
!destination.$calc && origin.$calc !== true &&
|
|
300
|
+
isBetaEnabled( model.options, '$calcForDraft' )) {
|
|
301
|
+
destination.$calc
|
|
302
|
+
= Object.assign( copyExpr( origin[prop] ), { $inferred: 'prop' } );
|
|
303
|
+
if (rewriteRefsInExpression( destination, origin, '$calc' ))
|
|
304
|
+
destination.$calc = true; // TODO: or { val: true }?
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
285
308
|
function notWithExpand( prop, target, source ) {
|
|
286
309
|
if (!target.expand || prop === 'type' && source.elements)
|
|
287
310
|
always( prop, target, source );
|
|
@@ -296,13 +319,13 @@ function propagate( model ) {
|
|
|
296
319
|
function annotation( prop, target, source ) {
|
|
297
320
|
const anno = source[prop];
|
|
298
321
|
if (anno.val !== null)
|
|
299
|
-
withKind( prop, target, source );
|
|
322
|
+
withKind( prop, target, source ); // TODO: unfold
|
|
300
323
|
}
|
|
301
324
|
|
|
302
325
|
function docComment( prop, target, source ) {
|
|
303
326
|
if (model.options.propagateDocComments)
|
|
304
327
|
annotation( prop, target, source );
|
|
305
|
-
else // TODO:
|
|
328
|
+
else // TODO: or just "never"
|
|
306
329
|
onlyViaParent( prop, target, source );
|
|
307
330
|
}
|
|
308
331
|
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -1583,12 +1583,19 @@ function resolve( model ) {
|
|
|
1583
1583
|
// (for code completion)
|
|
1584
1584
|
const last = expr.path[expr.path.length - 1];
|
|
1585
1585
|
if (!last || !(last.args || last.where || last.cardinality) ||
|
|
1586
|
-
expr.$expected === 'approved-exists' ||
|
|
1587
1586
|
user.expand || user.inline ||
|
|
1588
1587
|
expWithFilter.includes( expected ) || // `from`, …
|
|
1589
1588
|
last._navigation?.kind === '$tableAlias') // error already reported
|
|
1590
1589
|
return ref;
|
|
1591
1590
|
|
|
1591
|
+
if (expr.$expected === 'approved-exists') {
|
|
1592
|
+
if (last.where?.args?.length === 0) {
|
|
1593
|
+
// at the moment, empty filter is not allowed on last path step
|
|
1594
|
+
reportUnexpectedArgsAndFilter( last, expected, user, null, 'last-empty-filter' );
|
|
1595
|
+
}
|
|
1596
|
+
return ref;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1592
1599
|
const type = effectiveType( last._artifact );
|
|
1593
1600
|
const art = type && (type.kind === 'entity' ? type : type.target?._artifact);
|
|
1594
1601
|
if (!art)
|
|
@@ -1596,7 +1603,8 @@ function resolve( model ) {
|
|
|
1596
1603
|
if (last.args || last.where || last.cardinality) {
|
|
1597
1604
|
const unexpectedFilter = (expected !== 'annotation' && expected !== 'column' &&
|
|
1598
1605
|
expected !== 'calc' && 'std') ||
|
|
1599
|
-
isQuasiVirtualAssociation( type ) && 'model-only'
|
|
1606
|
+
isQuasiVirtualAssociation( type ) && 'model-only' ||
|
|
1607
|
+
last.where?.args?.length === 0 && 'last-empty-filter';
|
|
1600
1608
|
reportUnexpectedArgsAndFilter( last, expected, user, art, unexpectedFilter );
|
|
1601
1609
|
}
|
|
1602
1610
|
// TODO: we should have different message-ids for the "last" stuff: adding
|
|
@@ -1696,7 +1704,9 @@ function resolve( model ) {
|
|
|
1696
1704
|
error( 'expr-unexpected-argument', loc, { '#': variant } );
|
|
1697
1705
|
}
|
|
1698
1706
|
if ((step.where || step.cardinality) && variant) {
|
|
1699
|
-
const location =
|
|
1707
|
+
const location = step.where?.location || step.cardinality?.location
|
|
1708
|
+
? combinedLocation( step.where, step.cardinality )
|
|
1709
|
+
: step.location;
|
|
1700
1710
|
// XSN TODO: filter$location including […]
|
|
1701
1711
|
error( 'expr-unexpected-filter', [ location, user ], { '#': variant } );
|
|
1702
1712
|
}
|