@sap/cds-compiler 6.4.2 → 6.5.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 +87 -1159
- package/README.md +1 -10
- package/doc/IncompatibleChanges_v5.md +436 -0
- package/doc/IncompatibleChanges_v6.md +659 -0
- package/doc/Versioning.md +3 -7
- package/lib/api/main.js +1 -0
- package/lib/api/options.js +5 -0
- package/lib/api/validate.js +3 -0
- package/lib/base/message-registry.js +25 -2
- package/lib/base/messages.js +1 -1
- package/lib/base/model.js +3 -2
- package/lib/checks/actionsFunctions.js +6 -3
- package/lib/checks/existsExpressionsOnlyForeignKeys.js +16 -10
- package/lib/checks/existsInForbiddenPlaces.js +32 -0
- package/lib/checks/existsMustEndInAssoc.js +1 -1
- package/lib/checks/existsMustNotStartWithDollarSelf.js +31 -0
- package/lib/checks/validator.js +6 -2
- package/lib/compiler/assert-consistency.js +5 -7
- package/lib/compiler/builtins.js +5 -6
- package/lib/compiler/checks.js +4 -8
- package/lib/compiler/define.js +244 -459
- package/lib/compiler/extend.js +297 -11
- package/lib/compiler/finalize-parse-cdl.js +2 -10
- package/lib/compiler/generate.js +29 -1
- package/lib/compiler/populate.js +21 -63
- package/lib/compiler/propagator.js +1 -2
- package/lib/compiler/resolve.js +2 -12
- package/lib/compiler/shared.js +145 -114
- package/lib/compiler/tweak-assocs.js +14 -10
- package/lib/compiler/utils.js +97 -0
- package/lib/compiler/xpr-rewrite.js +113 -140
- package/lib/edm/annotations/edmJson.js +9 -6
- package/lib/edm/annotations/genericTranslation.js +8 -4
- package/lib/edm/csn2edm.js +3 -4
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +3 -3
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +4 -3
- package/lib/gen/Dictionary.json +16 -1
- package/lib/json/from-csn.js +4 -6
- package/lib/json/to-csn.js +3 -3
- package/lib/model/csnRefs.js +13 -4
- package/lib/model/enrichCsn.js +4 -2
- package/lib/optionProcessor.js +8 -4
- package/lib/parsers/AstBuildingParser.js +1 -1
- package/lib/render/utils/sql.js +3 -2
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +3 -3
- package/lib/transform/db/assocsToQueries/normalizeFrom.js +33 -0
- package/lib/transform/db/assocsToQueries/transformExists.js +17 -13
- package/lib/transform/db/assocsToQueries/utils.js +1 -6
- package/lib/transform/db/backlinks.js +4 -4
- package/lib/transform/db/cdsPersistence.js +4 -4
- package/lib/transform/db/constraints.js +4 -4
- package/lib/transform/db/expansion.js +5 -5
- package/lib/transform/db/flattening.js +4 -5
- package/lib/transform/db/rewriteCalculatedElements.js +3 -3
- package/lib/transform/db/temporal.js +11 -11
- package/lib/transform/draft/db.js +2 -0
- package/lib/transform/draft/odata.js +5 -7
- package/lib/transform/effective/flattening.js +1 -2
- package/lib/transform/forOdata.js +3 -3
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/localized.js +13 -20
- package/lib/transform/odata/createForeignKeys.js +1 -2
- package/lib/transform/odata/flattening.js +1 -2
- package/lib/transform/odata/toFinalBaseType.js +52 -55
- package/lib/transform/transformUtils.js +3 -4
- package/package.json +3 -3
- package/doc/CHANGELOG_BETA.md +0 -464
- package/doc/CHANGELOG_DEPRECATED.md +0 -235
package/lib/api/options.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// The options are specified in ../optionProcessor.js (and some other files).
|
|
4
|
+
// Some backends feel the need to "translate" option, which require to list
|
|
5
|
+
// all options also here (as “public" or "private" option).
|
|
6
|
+
|
|
3
7
|
const { validate, generateStringValidator } = require('./validate');
|
|
4
8
|
const { makeMessageFunction } = require('../base/messages');
|
|
5
9
|
|
|
@@ -20,6 +24,7 @@ const publicOptionsNewAPI = [
|
|
|
20
24
|
'defaultBinaryLength',
|
|
21
25
|
'defaultStringLength',
|
|
22
26
|
'csnFlavor',
|
|
27
|
+
'noDollarCalc',
|
|
23
28
|
// DB
|
|
24
29
|
'sqlDialect',
|
|
25
30
|
'sqlMapping',
|
package/lib/api/validate.js
CHANGED
|
@@ -92,9 +92,16 @@ const centralMessages = {
|
|
|
92
92
|
'ext-undefined-action': { severity: 'Warning' },
|
|
93
93
|
'ext-undefined-art': { severity: 'Warning' }, // for annotate statement (for CDL path root)
|
|
94
94
|
'ext-undefined-def': { severity: 'Warning' }, // for annotate statement (for CSN or CDL path cont)
|
|
95
|
+
'ext-undefined-art-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
96
|
+
'ext-undefined-def-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
95
97
|
'ext-undefined-element': { severity: 'Warning' },
|
|
96
98
|
'ext-undefined-key': { severity: 'Warning' },
|
|
97
99
|
'ext-undefined-param': { severity: 'Warning' },
|
|
100
|
+
'ext-undefined-element-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
101
|
+
'ext-undefined-action-sec': { severity: 'Error', configurableFor: true }, // for security-relevant…
|
|
102
|
+
'ext-undefined-param-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
103
|
+
'ext-unexpected-returns': { severity: 'Warning' },
|
|
104
|
+
'ext-unexpected-returns-sec': { severity: 'Error', configurableFor: true }, // … annotate statement
|
|
98
105
|
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
|
|
99
106
|
'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
|
|
100
107
|
|
|
@@ -707,8 +714,8 @@ const centralMessageTexts = {
|
|
|
707
714
|
on: 'Unexpected $(ID) reference; is valid only if compared to be equal to an association of the target side',
|
|
708
715
|
subQuery: 'Unexpected $(ID) reference in a sub query',
|
|
709
716
|
setQuery: 'Unexpected $(ID) reference in a query on the right side of $(OP)',
|
|
710
|
-
exists: '
|
|
711
|
-
'exists-filter': 'Unexpected $(ID) reference in filter of
|
|
717
|
+
exists: 'Paths following $(NAME) must not start with $(ID)',
|
|
718
|
+
'exists-filter': 'Unexpected $(ID) reference in filter of assoc $(ELEMREF) following “EXISTS” predicate',
|
|
712
719
|
},
|
|
713
720
|
'ref-unexpected-map': {
|
|
714
721
|
std: 'Unexpected reference to an element of type $(TYPE)', // unused
|
|
@@ -948,6 +955,8 @@ const centralMessageTexts = {
|
|
|
948
955
|
'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
|
|
949
956
|
'ext-undefined-def': 'Artifact $(ART) has not been found',
|
|
950
957
|
'ext-undefined-art': 'No artifact has been found with name $(ART)',
|
|
958
|
+
'ext-undefined-def-sec': 'Artifact $(ART) has not been found',
|
|
959
|
+
'ext-undefined-art-sec': 'No artifact has been found with name $(ART)',
|
|
951
960
|
'ext-undefined-element': {
|
|
952
961
|
std: 'Element $(NAME) has not been found',
|
|
953
962
|
element: 'Artifact $(ART) has no element $(NAME)',
|
|
@@ -955,15 +964,24 @@ const centralMessageTexts = {
|
|
|
955
964
|
returns: 'Return value of $(ART) has no element $(NAME)',
|
|
956
965
|
'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
|
|
957
966
|
},
|
|
967
|
+
'ext-undefined-element-sec': 'Element $(NAME) has not been found',
|
|
958
968
|
'ext-undefined-key': 'Foreign key $(NAME) has not been found',
|
|
959
969
|
'ext-undefined-action': {
|
|
960
970
|
std: 'Action $(ART) has not been found',
|
|
961
971
|
action: 'Artifact $(ART) has no action $(NAME)',
|
|
962
972
|
},
|
|
973
|
+
'ext-undefined-action-sec': {
|
|
974
|
+
std: 'Action $(ART) has not been found',
|
|
975
|
+
action: 'Artifact $(ART) has no action $(NAME)',
|
|
976
|
+
},
|
|
963
977
|
'ext-undefined-param': {
|
|
964
978
|
std: 'Parameter $(ART) has not been found',
|
|
965
979
|
param: 'Artifact $(ART) has no parameter $(NAME)',
|
|
966
980
|
},
|
|
981
|
+
'ext-undefined-param-sec': {
|
|
982
|
+
std: 'Parameter $(ART) has not been found',
|
|
983
|
+
param: 'Artifact $(ART) has no parameter $(NAME)',
|
|
984
|
+
},
|
|
967
985
|
|
|
968
986
|
// annotation checks against their definition
|
|
969
987
|
'anno-expecting-value': {
|
|
@@ -1157,6 +1175,11 @@ const centralMessageTexts = {
|
|
|
1157
1175
|
redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
|
|
1158
1176
|
},
|
|
1159
1177
|
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
|
|
1178
|
+
'query-unexpected-exists': {
|
|
1179
|
+
std: 'Unexpected $(PROP) predicate',
|
|
1180
|
+
groupBy: 'Unexpected $(PROP) predicate in GROUP BY clause',
|
|
1181
|
+
orderBy: 'Unexpected $(PROP) predicate in ORDER BY clause',
|
|
1182
|
+
},
|
|
1160
1183
|
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode', // eslint-disable-line cds-compiler/message-no-quotes
|
|
1161
1184
|
'query-ignoring-param-nullability': {
|
|
1162
1185
|
std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
|
package/lib/base/messages.js
CHANGED
|
@@ -1724,7 +1724,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1724
1724
|
else if (step === 'targetAspect') {
|
|
1725
1725
|
// skip
|
|
1726
1726
|
}
|
|
1727
|
-
else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value') {
|
|
1727
|
+
else if (step === 'xpr' || step === 'list' || step === 'default' || step === 'ref' || step === 'as' || step === 'value' || step === '$calc') {
|
|
1728
1728
|
break; // don't go into xprs, refs, aliases, values, etc.
|
|
1729
1729
|
}
|
|
1730
1730
|
else if (step === 'returns') {
|
package/lib/base/model.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
const { forEach } = require('../utils/objectUtils');
|
|
9
9
|
|
|
10
|
+
// Normal options are in ../optionProcessor.js (and some other files),
|
|
11
|
+
// unfortunately partly non-grep-able (option `fooBar` is defined via `--foo-bar`)
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Object of all available beta flags that will be enabled/disabled by `--beta-mode`
|
|
12
15
|
* through cdsc. Only intended for INTERNAL USE.
|
|
@@ -29,8 +32,6 @@ const availableBetaFlags = {
|
|
|
29
32
|
rewriteAnnotationExpressionsViaType: true,
|
|
30
33
|
sqlServiceDummies: true,
|
|
31
34
|
projectionViews: true,
|
|
32
|
-
draftAdminDataHiddenFilter: true,
|
|
33
|
-
$calcForDraft: true,
|
|
34
35
|
// disabled by --beta-mode
|
|
35
36
|
nestedServices: false,
|
|
36
37
|
};
|
|
@@ -25,8 +25,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
|
|
|
25
25
|
for (const [ actName, act ] of Object.entries(art.actions)) {
|
|
26
26
|
if (act.params) {
|
|
27
27
|
checkExplicitBindingParameter.call(this, act.params, path.concat([ 'actions', actName, 'params' ]));
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const params = Object.entries(act.params);
|
|
29
|
+
for (const [ paramName, param ] of params)
|
|
30
|
+
checkActionOrFunctionParameter.call(this, param, path.concat([ 'actions', actName, 'params', paramName ]), act.kind, params);
|
|
30
31
|
}
|
|
31
32
|
if (act.returns)
|
|
32
33
|
checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind);
|
|
@@ -63,7 +64,9 @@ function checkActionOrFunction( art, artName, prop, path ) {
|
|
|
63
64
|
* @param {CSN.Path} currPath path to the parameter
|
|
64
65
|
* @param {string} actKind 'action' or 'function'
|
|
65
66
|
*/
|
|
66
|
-
function checkActionOrFunctionParameter( param, currPath, actKind ) {
|
|
67
|
+
function checkActionOrFunctionParameter( param, currPath, actKind, params ) {
|
|
68
|
+
if ((param.items || param)?.type === '$self' && param === params?.[0][1])
|
|
69
|
+
return; // binding parameter
|
|
67
70
|
const paramType = param.type ? this.csnUtils.getFinalTypeInfo(param.type) : param;
|
|
68
71
|
if (!paramType)
|
|
69
72
|
return; // no type could be resolved
|
|
@@ -3,39 +3,45 @@
|
|
|
3
3
|
const { requireForeignKeyAccess } = require('../checks/onConditions');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Filter expressions in an exists path must:
|
|
7
|
+
* - only contain fk-accesses for assocs. Unmanaged traversal / non-fk access is forbidden.
|
|
8
|
+
* - not contain a ref starting with $self
|
|
7
9
|
*
|
|
8
10
|
* @param {CSN.Artifact} parent
|
|
9
11
|
* @param {string} name
|
|
10
12
|
* @param {Array} expr
|
|
11
13
|
*/
|
|
12
|
-
function
|
|
14
|
+
function assertFilterOfExists( parent, name, expr ) {
|
|
13
15
|
for (let i = 0; i < expr.length - 1; i++) {
|
|
14
16
|
if (expr[i] === 'exists' && expr[i + 1].ref) {
|
|
15
17
|
i++;
|
|
16
18
|
const current = expr[i];
|
|
17
19
|
|
|
18
|
-
const { _links } =
|
|
20
|
+
const { _links } = current;
|
|
19
21
|
|
|
20
22
|
const assocs = _links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
ensureValidFilters.call(this, assocs);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
30
|
+
* Reject:
|
|
31
|
+
* - Unmanaged traversal / non-fk access.
|
|
32
|
+
* - ref's starting with $self
|
|
29
33
|
*
|
|
30
34
|
* @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
|
|
31
35
|
*/
|
|
32
|
-
function
|
|
36
|
+
function ensureValidFilters( assocs ) {
|
|
33
37
|
for (const assoc of assocs) {
|
|
34
38
|
if (assoc.where) {
|
|
35
39
|
for (let i = 0; i < assoc.where.length; i++) {
|
|
36
40
|
const part = assoc.where[i];
|
|
37
41
|
|
|
38
42
|
if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
|
|
43
|
+
if (part.$scope === '$self')
|
|
44
|
+
this.error('ref-unexpected-self', part.$path, { '#': 'exists-filter', elemref: assoc.id, id: part.ref[0] });
|
|
39
45
|
for (const link of part._links) {
|
|
40
46
|
if (link.art && link.art.target) {
|
|
41
47
|
if (link.art.keys) { // managed - allow FK access
|
|
@@ -55,7 +61,7 @@ function checkForInvalidAssoc( assocs ) {
|
|
|
55
61
|
}
|
|
56
62
|
// Recursively drill down if the assoc-step has a filter
|
|
57
63
|
if (part.ref[link.idx].where)
|
|
58
|
-
|
|
64
|
+
ensureValidFilters.call(this, [ part.ref[link.idx] ]);
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
}
|
|
@@ -65,7 +71,7 @@ function checkForInvalidAssoc( assocs ) {
|
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
module.exports = {
|
|
68
|
-
having:
|
|
69
|
-
where:
|
|
70
|
-
xpr:
|
|
74
|
+
having: assertFilterOfExists,
|
|
75
|
+
where: assertFilterOfExists,
|
|
76
|
+
xpr: assertFilterOfExists,
|
|
71
77
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { applyTransformationsOnNonDictionary } = require('../model/csnUtils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks a query for forbidden usage of the `exists` predicate within `groupBy` and `orderBy`.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} query
|
|
9
|
+
* @param {string} prop - either groupBy or orderBy.
|
|
10
|
+
* @param {object[]} _tokenStream the value of query[prop]
|
|
11
|
+
* @param {object|Array} pathToClause - Path to respective groupBy/orderBy clause in the CSN.
|
|
12
|
+
* @this {object} The calling context must provide an `error` function.
|
|
13
|
+
*/
|
|
14
|
+
function existsInForbiddenPlaces( query, prop, _tokenStream, pathToClause ) {
|
|
15
|
+
applyTransformationsOnNonDictionary(query, prop, {
|
|
16
|
+
ref: (_parent, _prop, _ref, pathToError, tokenStream, index) => {
|
|
17
|
+
if (tokenStream[index - 1] === 'exists') {
|
|
18
|
+
// the path should point to the exists
|
|
19
|
+
const pathToExists = pathToError.with(-1, index - 1);
|
|
20
|
+
this.error('query-unexpected-exists', pathToExists, {
|
|
21
|
+
'#': prop,
|
|
22
|
+
prop: 'exists',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
}, null, pathToClause);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
groupBy: existsInForbiddenPlaces,
|
|
31
|
+
orderBy: existsInForbiddenPlaces,
|
|
32
|
+
};
|
|
@@ -13,8 +13,8 @@ function existsMustEndInAssoc( parent, prop, expression, path ) {
|
|
|
13
13
|
if (expression[i] === 'exists') {
|
|
14
14
|
const next = expression[i + 1];
|
|
15
15
|
const { _art } = next;
|
|
16
|
-
const errorPath = path.concat([ prop, i ]);
|
|
17
16
|
if (!next.SELECT && !_art?.target) {
|
|
17
|
+
const errorPath = path.concat([ prop, i ]);
|
|
18
18
|
this.error('ref-expecting-assoc', errorPath, {
|
|
19
19
|
'#': _art.type ? 'with-type' : 'std',
|
|
20
20
|
elemref: next,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A path following an “exists” predicate must always end in an association.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} parent
|
|
7
|
+
* @param {string} prop
|
|
8
|
+
* @param {Array} expression
|
|
9
|
+
* @param {CSN.Path} path
|
|
10
|
+
*/
|
|
11
|
+
function existsMustNotStartWithDollarSelf( parent, prop, expression, path ) {
|
|
12
|
+
for (let i = 0; i < expression?.length - 1; i++) {
|
|
13
|
+
if (expression[i] === 'exists') {
|
|
14
|
+
const next = expression[i + 1];
|
|
15
|
+
if (next.$scope === '$self') {
|
|
16
|
+
const errorPath = path.concat([ prop, i ]);
|
|
17
|
+
this.error('ref-unexpected-self', errorPath, {
|
|
18
|
+
'#': 'exists',
|
|
19
|
+
id: next.ref[0],
|
|
20
|
+
name: 'exists',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
having: existsMustNotStartWithDollarSelf,
|
|
29
|
+
where: existsMustNotStartWithDollarSelf,
|
|
30
|
+
xpr: existsMustNotStartWithDollarSelf,
|
|
31
|
+
};
|
package/lib/checks/validator.js
CHANGED
|
@@ -43,7 +43,9 @@ const { validateAssociationsInItems } = require('./arrayOfs');
|
|
|
43
43
|
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
|
|
44
44
|
const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
45
45
|
const existsMustEndInAssoc = require('./existsMustEndInAssoc');
|
|
46
|
-
const
|
|
46
|
+
const existsMustNotStartWithDollarSelf = require('./existsMustNotStartWithDollarSelf');
|
|
47
|
+
const existsInForbiddenPlaces = require('./existsInForbiddenPlaces');
|
|
48
|
+
const assertFilterOfExists = require('./existsExpressionsOnlyForeignKeys');
|
|
47
49
|
const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
|
|
48
50
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
49
51
|
const {
|
|
@@ -84,8 +86,10 @@ const forRelationalDBArtifactValidators = [
|
|
|
84
86
|
|
|
85
87
|
const forRelationalDBCsnValidators = [
|
|
86
88
|
checkCdsMap,
|
|
89
|
+
existsInForbiddenPlaces,
|
|
87
90
|
existsMustEndInAssoc,
|
|
88
|
-
|
|
91
|
+
existsMustNotStartWithDollarSelf,
|
|
92
|
+
assertFilterOfExists,
|
|
89
93
|
navigationIntoMany,
|
|
90
94
|
checkPathsInStoredCalcElement,
|
|
91
95
|
featureFlags,
|
|
@@ -207,7 +207,7 @@ function assertConsistency( model, stage ) {
|
|
|
207
207
|
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
208
208
|
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
209
209
|
'$calcDepElement', '$filtered', '$enclosed', '_parent',
|
|
210
|
-
'deprecated', '$
|
|
210
|
+
'deprecated', '$restricted',
|
|
211
211
|
],
|
|
212
212
|
schema: {
|
|
213
213
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -216,7 +216,7 @@ function assertConsistency( model, stage ) {
|
|
|
216
216
|
$uncheckedElements: { test: isBoolean },
|
|
217
217
|
$requireElementAccess: { test: isBoolean },
|
|
218
218
|
deprecated: { test: isBoolean },
|
|
219
|
-
$
|
|
219
|
+
$restricted: { test: TODO },
|
|
220
220
|
// missing location for normal "elements"
|
|
221
221
|
elements: { test: TODO },
|
|
222
222
|
},
|
|
@@ -316,7 +316,7 @@ function assertConsistency( model, stage ) {
|
|
|
316
316
|
requires: [ 'location', 'path' ],
|
|
317
317
|
optional: [
|
|
318
318
|
'kind', 'name', '$syntax', '_block', '_parent', '_main',
|
|
319
|
-
'elements', '_origin', '_joinParent', '$joinArgsIndex',
|
|
319
|
+
'elements', '_origin', '_joinParent', '$joinArgsIndex',
|
|
320
320
|
'$parens', '_status', // TODO: only in from
|
|
321
321
|
'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
|
|
322
322
|
'_effectiveType', '$effectiveSeqNo', // TODO:check this
|
|
@@ -449,7 +449,7 @@ function assertConsistency( model, stage ) {
|
|
|
449
449
|
requires: [ 'location', 'path' ],
|
|
450
450
|
optional: [
|
|
451
451
|
'scope', 'variant', '_artifact', '_originalArtifact',
|
|
452
|
-
'$inferred', '$parens', 'sort', 'nulls',
|
|
452
|
+
'$inferred', '$parens', 'sort', 'nulls', '$syntax',
|
|
453
453
|
],
|
|
454
454
|
},
|
|
455
455
|
none: { optional: () => true }, // parse error
|
|
@@ -507,7 +507,7 @@ function assertConsistency( model, stage ) {
|
|
|
507
507
|
args: {
|
|
508
508
|
inherits: 'value',
|
|
509
509
|
optional: [
|
|
510
|
-
'name', '$duplicate', '
|
|
510
|
+
'name', '$duplicate', 'args', 'suffix', '$parens',
|
|
511
511
|
'param', 'scope', // for dynamic parameter '?'
|
|
512
512
|
],
|
|
513
513
|
test: args,
|
|
@@ -724,7 +724,6 @@ function assertConsistency( model, stage ) {
|
|
|
724
724
|
'localized-entity', // `.texts` entity
|
|
725
725
|
'localized-origin', // `.texts` entity
|
|
726
726
|
'nav', // only used for MASKED, TODO(v6): Remove
|
|
727
|
-
'none', // only used in ensureColumnName(): Used in object representing empty alias
|
|
728
727
|
'query', // inferred query properties, e.g. `key`
|
|
729
728
|
'rewrite', // on-conditions or FKeys are rewritten
|
|
730
729
|
'parent-origin', // annotation/property copied from parent that does not come through
|
|
@@ -747,7 +746,6 @@ function assertConsistency( model, stage ) {
|
|
|
747
746
|
$withLocalized: { test: isBoolean },
|
|
748
747
|
$sources: { parser: true, test: isArray( isString ) },
|
|
749
748
|
tokenStream: { parser: true, test: TODO },
|
|
750
|
-
$expected: { parser: true, test: isOneOf( [ 'approved-exists', 'exists' ] ) },
|
|
751
749
|
$messageFunctions: { test: TODO },
|
|
752
750
|
$functions: { test: TODO },
|
|
753
751
|
$assert: { test: TODO }, // currently just for missing Error[ref-cycle]
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -211,8 +211,7 @@ const magicVariables = {
|
|
|
211
211
|
},
|
|
212
212
|
// Require that elements are accessed, i.e. no $draft, only $draft.<element>.
|
|
213
213
|
$requireElementAccess: true,
|
|
214
|
-
//
|
|
215
|
-
$onlyInExprCtx: [ 'annotation', 'annoRewrite' ],
|
|
214
|
+
$restricted: true, // only in annotation expression, see shared.js
|
|
216
215
|
},
|
|
217
216
|
};
|
|
218
217
|
|
|
@@ -454,8 +453,8 @@ function initBuiltins( model ) {
|
|
|
454
453
|
art.$requireElementAccess = magic.$requireElementAccess;
|
|
455
454
|
if (magic.deprecated)
|
|
456
455
|
art.deprecated = magic.deprecated;
|
|
457
|
-
if (magic.$
|
|
458
|
-
art.$
|
|
456
|
+
if (magic.$restricted)
|
|
457
|
+
art.$restricted = magic.$restricted;
|
|
459
458
|
|
|
460
459
|
createMagicElements( art, magic.elements );
|
|
461
460
|
if (options.variableReplacements?.[id])
|
|
@@ -480,8 +479,8 @@ function initBuiltins( model ) {
|
|
|
480
479
|
// Propagate this property so that it is available for sub-elements.
|
|
481
480
|
if (art.$uncheckedElements)
|
|
482
481
|
magic.$uncheckedElements = art.$uncheckedElements;
|
|
483
|
-
if (art.$
|
|
484
|
-
magic.$
|
|
482
|
+
if (art.$restricted)
|
|
483
|
+
magic.$restricted = art.$restricted;
|
|
485
484
|
setProp( magic, '_parent', art );
|
|
486
485
|
// setProp( magic, '_effectiveType', magic );
|
|
487
486
|
if (elements[id] && typeof elements[id] === 'object')
|
package/lib/compiler/checks.js
CHANGED
|
@@ -941,14 +941,8 @@ function check( model ) {
|
|
|
941
941
|
*/
|
|
942
942
|
function checkExpressionIsNotAssocOrSelf( arg, user, rejectAssocTail ) {
|
|
943
943
|
// Arg must not be an association and not $self
|
|
944
|
-
// Only if path is not
|
|
945
|
-
if (arg
|
|
946
|
-
if (arg.$expected === 'exists') {
|
|
947
|
-
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
948
|
-
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': variant } );
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
else if (rejectAssocTail && isAssociationOperand( arg )) {
|
|
944
|
+
// Only if path is not after an `exists`
|
|
945
|
+
if (arg.$syntax !== 'after-exists' && rejectAssocTail && isAssociationOperand( arg )) {
|
|
952
946
|
if (rejectAssocTail.rejectManaged && rejectAssocTail.rejectUnmanaged ||
|
|
953
947
|
rejectAssocTail.rejectManaged && arg._artifact.keys ||
|
|
954
948
|
rejectAssocTail.rejectUnmanaged && arg._artifact.on) {
|
|
@@ -956,6 +950,8 @@ function check( model ) {
|
|
|
956
950
|
const context = rejectAssocTail.context === 'query' && 'query-' ||
|
|
957
951
|
rejectAssocTail.context === 'anno' && 'anno-' ||
|
|
958
952
|
'';
|
|
953
|
+
// Hm, in other message context about associations or compositions, we do
|
|
954
|
+
// not have separate text variants for association or composition…
|
|
959
955
|
const variant = isComposition( model, arg._artifact ) ? 'expr-comp' : 'expr';
|
|
960
956
|
error( 'ref-unexpected-assoc', [ arg.location, user ], { '#': `${ context }${ variant }` } );
|
|
961
957
|
}
|