@sap/cds-compiler 4.8.0 → 4.9.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 +38 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +30 -17
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +38 -21
- package/lib/base/messages.js +51 -20
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +10 -3
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +38 -30
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +0 -2
- package/lib/compiler/propagator.js +23 -19
- package/lib/compiler/resolve.js +48 -29
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +72 -116
- package/lib/compiler/xpr-rewrite.js +762 -0
- package/lib/edm/annotations/edmJson.js +24 -7
- package/lib/edm/annotations/genericTranslation.js +81 -61
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +12 -12
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +23 -11
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +75 -28
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +3 -3
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +221 -124
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +47 -42
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
package/lib/base/model.js
CHANGED
|
@@ -26,9 +26,6 @@ const queryOps = {
|
|
|
26
26
|
*/
|
|
27
27
|
const availableBetaFlags = {
|
|
28
28
|
// enabled by --beta-mode
|
|
29
|
-
annotationExpressions: true,
|
|
30
|
-
odataPathsInAnnotationExpressions: true,
|
|
31
|
-
odataAnnotationExpressions: true,
|
|
32
29
|
hanaAssocRealCardinality: true,
|
|
33
30
|
mapAssocToJoinCardinality: true, // only SAP HANA HEX engine supports it
|
|
34
31
|
enableUniversalCsn: true,
|
|
@@ -39,8 +36,10 @@ const availableBetaFlags = {
|
|
|
39
36
|
tenantVariable: true,
|
|
40
37
|
calcAssoc: true,
|
|
41
38
|
v5preview: true,
|
|
39
|
+
temporalRawProjection: true,
|
|
42
40
|
// disabled by --beta-mode
|
|
43
41
|
nestedServices: false,
|
|
42
|
+
rewriteAnnotationExpressionsViaType: false,
|
|
44
43
|
};
|
|
45
44
|
|
|
46
45
|
// Used by isDeprecatedEnabled() to check if any flag ist set.
|
|
@@ -81,7 +80,7 @@ const oldDeprecatedFlags_v2 = [
|
|
|
81
80
|
*
|
|
82
81
|
* A feature always needs to be provided - otherwise false will be returned.
|
|
83
82
|
*
|
|
84
|
-
*
|
|
83
|
+
* Do not move this function to the "option processor" code.
|
|
85
84
|
*
|
|
86
85
|
* @param {object} options Options
|
|
87
86
|
* @param {string} feature Feature to check for
|
|
@@ -101,7 +100,7 @@ function isBetaEnabled( options, feature ) {
|
|
|
101
100
|
* Useful for newer functionality which might not work with some
|
|
102
101
|
* deprecated feature turned on.
|
|
103
102
|
*
|
|
104
|
-
*
|
|
103
|
+
* Do not move this function to the "option processor" code.
|
|
105
104
|
*
|
|
106
105
|
* @param {object} options Options
|
|
107
106
|
* @param {string|null} [feature] Feature to check for
|
|
@@ -53,8 +53,8 @@ function checkActionOrFunction( art, artName, prop, path ) {
|
|
|
53
53
|
Object.entries(params).forEach(([ pn, p ], i) => {
|
|
54
54
|
const type = p.items?.type || p.type;
|
|
55
55
|
if (type === '$self' && !this.csn.definitions.$self && i > 0) {
|
|
56
|
-
this.error(
|
|
57
|
-
'Binding parameter is expected to appear
|
|
56
|
+
this.error('def-invalid-param', currPath.concat(pn),
|
|
57
|
+
'Binding parameter is expected to appear in first position only');
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
}
|
|
@@ -37,6 +37,7 @@ function checkCoreMediaTypeAllowance( member ) {
|
|
|
37
37
|
function checkAnalytics( member ) {
|
|
38
38
|
if (member['@Analytics.Measure'] && !member['@Aggregation.default']) {
|
|
39
39
|
this.info(null, member.$path, {},
|
|
40
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
40
41
|
'Annotation “@Analytics.Measure” expects “@Aggregation.default” to be assigned for the same element as well');
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -63,6 +64,7 @@ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
|
|
|
63
64
|
if (!this.csnUtils.getServiceName(artifactName))
|
|
64
65
|
return;
|
|
65
66
|
if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
|
|
67
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
66
68
|
this.warning(null, artifact.$path, {}, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -93,6 +95,7 @@ function checkTemporalAnnotationsAssignment( artifact, artifactName ) {
|
|
|
93
95
|
|
|
94
96
|
// if @cds.valid.key is defined, check whether @cds.valid.from and @cds.valid.to are also there
|
|
95
97
|
if (valid.key.length && !(valid.from.length && valid.to.length))
|
|
98
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
96
99
|
this.error(null, [ 'definitions', artifactName ], 'Annotation “@cds.valid.key” was used but “@cds.valid.from” and “@cds.valid.to” are missing');
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -23,6 +23,7 @@ function validateDefaultValues( member, memberName, prop, path ) {
|
|
|
23
23
|
// TODO: This check only counts the number of leading signs, not inbetween (e.g. 1 - - 1).
|
|
24
24
|
// The message also needs to be improved.
|
|
25
25
|
if (i > 1)
|
|
26
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
26
27
|
this.error(null, path, {}, 'Illegal number of unary ‘+’/‘-’ operators');
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -37,8 +38,10 @@ function validateDefaultValues( member, memberName, prop, path ) {
|
|
|
37
38
|
* @param {CSN.Path} path Path to the member
|
|
38
39
|
*/
|
|
39
40
|
function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
|
|
40
|
-
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
|
|
41
|
-
this.error(
|
|
41
|
+
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds') {
|
|
42
|
+
this.error('def-unsupported-param', path, {},
|
|
43
|
+
'Parameter default values are not supported in SAP HANA CDS');
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
/**
|
|
@@ -167,12 +167,13 @@ function _checkRef( ref, _links, $path, inColumns ) {
|
|
|
167
167
|
const cdsPersistenceSkipped = hasAnnotationValue(targetArt, '@cds.persistence.skip');
|
|
168
168
|
this.error( null, $path, {
|
|
169
169
|
'#': cdsPersistenceSkipped ? 'std' : 'abstract',
|
|
170
|
+
anno: '@cds.persistence.skip',
|
|
170
171
|
id: nonPersistedTarget.pathStep,
|
|
171
172
|
elemref: { ref },
|
|
172
173
|
name: nonPersistedTarget.name,
|
|
173
174
|
}, {
|
|
174
|
-
std: 'Unexpected
|
|
175
|
-
abstract: 'Unexpected
|
|
175
|
+
std: 'Unexpected $(ANNO) annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
176
|
+
abstract: 'Unexpected abstract association target $(NAME) of $(ID) in path $(ELEMREF)',
|
|
176
177
|
} );
|
|
177
178
|
break; // only one error per path
|
|
178
179
|
}
|
package/lib/checks/validator.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
forEachDefinition, forEachMemberRecursively, forAllQueries,
|
|
5
5
|
forEachMember, getNormalizedQuery, hasAnnotationValue,
|
|
6
|
-
applyTransformations, functionList,
|
|
6
|
+
applyTransformations, functionList, mergeTransformers,
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const enrichCsn = require('./enricher');
|
|
9
9
|
|
|
@@ -156,7 +156,7 @@ function _validate( csn, that,
|
|
|
156
156
|
// TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
|
|
157
157
|
// const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
|
|
158
158
|
|
|
159
|
-
applyTransformations(csn,
|
|
159
|
+
applyTransformations(csn, mergeTransformers(csnValidators, that), [], { drillRef: true });
|
|
160
160
|
|
|
161
161
|
forEachDefinition(csn, (artifact, artifactName, prop, path) => {
|
|
162
162
|
artifactValidators.forEach((artifactValidator) => {
|
|
@@ -180,38 +180,6 @@ function _validate( csn, that,
|
|
|
180
180
|
return cleanup;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
/**
|
|
184
|
-
* Ensure the CSN validators adhere to the applyTransformation format - also, supply correct this value for each subfunction
|
|
185
|
-
*
|
|
186
|
-
* @param {object[]} csnValidators Validators
|
|
187
|
-
* @param {object} that Value for this
|
|
188
|
-
* @returns {object} Remapped validators.
|
|
189
|
-
*/
|
|
190
|
-
function mergeCsnValidators( csnValidators, that ) {
|
|
191
|
-
const remapped = {};
|
|
192
|
-
for (const validator of csnValidators) {
|
|
193
|
-
for (const [ n, fns ] of Object.entries(validator)) {
|
|
194
|
-
if (!remapped[n])
|
|
195
|
-
remapped[n] = [];
|
|
196
|
-
|
|
197
|
-
if (Array.isArray(fns)) {
|
|
198
|
-
remapped[n].push((parent, name, prop, path) => fns.forEach(
|
|
199
|
-
fn => fn.bind(that)(parent, name, prop, path)
|
|
200
|
-
));
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
remapped[n].push((parent, name, prop, path) => fns.bind(that)(parent, name, prop, path));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
for (const [ n, fns ] of Object.entries(remapped))
|
|
209
|
-
remapped[n] = (parent, name, prop, path) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path));
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return remapped;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
183
|
/**
|
|
216
184
|
* Depending on the dialect we need to run different validations.
|
|
217
185
|
*
|
|
@@ -129,6 +129,7 @@ function assertConsistency( model, stage ) {
|
|
|
129
129
|
],
|
|
130
130
|
instanceOf: XsnSource,
|
|
131
131
|
},
|
|
132
|
+
tokenIndex: { test: isNumber },
|
|
132
133
|
location: {
|
|
133
134
|
// every thing with a $location in CSN must have a XSN location even
|
|
134
135
|
// with syntax errors (currently even internal artifacts like $using):
|
|
@@ -136,7 +137,10 @@ function assertConsistency( model, stage ) {
|
|
|
136
137
|
kind: true,
|
|
137
138
|
instanceOf: Location,
|
|
138
139
|
requires: [ 'file' ], // line is optional in top-level location
|
|
139
|
-
optional: [
|
|
140
|
+
optional: [
|
|
141
|
+
'line', 'col', 'endLine', 'endCol', '$notFound',
|
|
142
|
+
'tokenIndex', // in parser for $lsp
|
|
143
|
+
],
|
|
140
144
|
schema: {
|
|
141
145
|
line: { test: isNumber },
|
|
142
146
|
col: { test: isNumber },
|
|
@@ -203,7 +207,7 @@ function assertConsistency( model, stage ) {
|
|
|
203
207
|
optional: [
|
|
204
208
|
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
205
209
|
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
206
|
-
'$calcDepElement', '$filtered', '_parent',
|
|
210
|
+
'$calcDepElement', '$filtered', '$enclosed', '_parent',
|
|
207
211
|
],
|
|
208
212
|
schema: {
|
|
209
213
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -232,7 +236,7 @@ function assertConsistency( model, stage ) {
|
|
|
232
236
|
test: (model.$frontend !== 'json') ? standard : TODO,
|
|
233
237
|
// TODO: the JSON parser should augment 'namespace' correctly or better: hide it
|
|
234
238
|
requires: [ 'location' ],
|
|
235
|
-
optional: [ '
|
|
239
|
+
optional: [ 'kind', 'name' ],
|
|
236
240
|
},
|
|
237
241
|
usings: {
|
|
238
242
|
test: isArray(),
|
|
@@ -257,6 +261,7 @@ function assertConsistency( model, stage ) {
|
|
|
257
261
|
foreignKeys: { kind: true, inherits: 'definitions', instanceOf: 'ignore' },
|
|
258
262
|
$keysNavigation: { kind: true, test: TODO },
|
|
259
263
|
$filtered: { kind: true, inherits: 'value' }, // for assoc+filter
|
|
264
|
+
$enclosed: { kind: true, inherits: 'value' }, // for comp+filter
|
|
260
265
|
params: { kind: true, inherits: 'definitions' },
|
|
261
266
|
_extendType: { kind: true, test: TODO },
|
|
262
267
|
mixin: { inherits: 'definitions' },
|
|
@@ -515,11 +520,13 @@ function assertConsistency( model, stage ) {
|
|
|
515
520
|
'_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
|
|
516
521
|
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
517
522
|
'args', 'op', 'func', 'suffix',
|
|
523
|
+
'$invalidPaths',
|
|
518
524
|
],
|
|
519
525
|
// TODO: name requires if not in parser?
|
|
520
526
|
},
|
|
521
527
|
$priority: { test: isOneOf( [ undefined, false, 'extend', 'annotate' ] ) },
|
|
522
528
|
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
529
|
+
$invalidPaths: { test: isBoolean },
|
|
523
530
|
name: {
|
|
524
531
|
isRequired: stageParser && (() => false), // not required in parser
|
|
525
532
|
kind: true,
|
package/lib/compiler/checks.js
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
} = require('../base/model');
|
|
20
20
|
const { CompilerAssertion } = require('../base/error');
|
|
21
21
|
const { typeParameters } = require('./builtins');
|
|
22
|
+
const { propagationRules } = require('../base/builtins');
|
|
22
23
|
|
|
23
24
|
const $location = Symbol.for( 'cds.$location' );
|
|
24
25
|
|
|
@@ -69,6 +70,13 @@ function check( model ) {
|
|
|
69
70
|
} );
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function* iterateAnnotations( art ) {
|
|
74
|
+
for (const prop in art) {
|
|
75
|
+
if (prop.charAt(0) === '@')
|
|
76
|
+
yield prop;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
function checkGenericConstruct( art ) {
|
|
73
81
|
checkName( art );
|
|
74
82
|
checkTypeArguments( art );
|
|
@@ -76,11 +84,9 @@ function check( model ) {
|
|
|
76
84
|
if (art.value && !art.$calcDepElement && art.type)
|
|
77
85
|
checkTypeCast( art.value, art );
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.forEach( a => checkAnnotationAssignment1( art, art[a] ) );
|
|
83
|
-
}
|
|
87
|
+
for (const anno of iterateAnnotations( art ))
|
|
88
|
+
checkAnnotationAssignment1( art, art[anno] );
|
|
89
|
+
|
|
84
90
|
checkTypeStructure( art );
|
|
85
91
|
checkAssociation( art ); // type def could be assoc
|
|
86
92
|
if (art.kind === 'enum')
|
|
@@ -236,6 +242,8 @@ function check( model ) {
|
|
|
236
242
|
? xpr.args[0]?._artifact
|
|
237
243
|
: xpr._artifact;
|
|
238
244
|
const type = isCast ? xpr.type : user.type;
|
|
245
|
+
if (!isCast && type.$inferred)
|
|
246
|
+
return; // e.g. $inferred:'generated'
|
|
239
247
|
if (elem && type) { // has explicit type
|
|
240
248
|
if (type._artifact?.elements)
|
|
241
249
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
|
|
@@ -264,9 +272,11 @@ function check( model ) {
|
|
|
264
272
|
// "key" keyword at localized element in SELECT list.
|
|
265
273
|
// TODO: not in inferred elements, but also inside aspects
|
|
266
274
|
if (elem.key?.val && elem._main?.query) {
|
|
275
|
+
// either the element was casted to localized (no `_origin`) or
|
|
267
276
|
// original element is localized but not key, as that would have
|
|
268
277
|
// already resulted in a warning by localized.js
|
|
269
|
-
if (elem._origin
|
|
278
|
+
if ((!elem._origin && elem.localized?.val) ||
|
|
279
|
+
(elem._origin?.localized?.val && !elem._origin.key?.val)) {
|
|
270
280
|
warning( 'def-ignoring-localized', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
271
281
|
'Keyword $(KEYWORD) is ignored for primary keys' );
|
|
272
282
|
}
|
|
@@ -860,21 +870,37 @@ function check( model ) {
|
|
|
860
870
|
return false;
|
|
861
871
|
}
|
|
862
872
|
|
|
873
|
+
/**
|
|
874
|
+
* Returns true if the given annotation accepts expressions as values.
|
|
875
|
+
*
|
|
876
|
+
* @param {object} anno
|
|
877
|
+
* @param {XSN.Artifact} art
|
|
878
|
+
* @returns {boolean}
|
|
879
|
+
*/
|
|
880
|
+
function checkAnnotationAcceptsExpressions( anno, art ) {
|
|
881
|
+
const name = anno.name?.id;
|
|
882
|
+
if (!name)
|
|
883
|
+
return true;
|
|
884
|
+
if (!propagationRules[`@${ name }`])
|
|
885
|
+
return true;
|
|
886
|
+
error( 'anno-unexpected-expr', [ anno.location, art ], { anno: name },
|
|
887
|
+
'Unexpected expression as value for $(ANNO)' );
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
863
890
|
|
|
864
|
-
// Former checkAnnotationAssignments.js ------------------------------------
|
|
865
|
-
|
|
866
|
-
// Check the annotation assignments (if any) of 'annotatable', possibly using annotation
|
|
867
|
-
// definitions from 'model'. Report errors on 'options.messages.
|
|
868
|
-
//
|
|
869
|
-
// TODO: rework completely!
|
|
870
|
-
// TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
|
|
871
|
-
|
|
872
|
-
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
873
|
-
// adapted, etc.
|
|
874
891
|
function checkAnnotationAssignment1( art, anno ) {
|
|
875
|
-
if (art.$contains?.$annotation)
|
|
876
|
-
|
|
892
|
+
if (art.$contains?.$annotation && anno.kind === '$annotation') {
|
|
893
|
+
if (checkAnnotationAcceptsExpressions( anno, art ))
|
|
894
|
+
checkAnnotationExpressions( anno, art );
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (!model.vocabularies)
|
|
898
|
+
return;
|
|
877
899
|
|
|
900
|
+
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
901
|
+
// adapted, etc.
|
|
902
|
+
// TODO: rework completely!
|
|
903
|
+
// TODO: if we have such a check, consider #variant, anno.@anno, anno@anno
|
|
878
904
|
// Sanity checks (ignore broken assignments)
|
|
879
905
|
if (!anno.name?.id)
|
|
880
906
|
return;
|
package/lib/compiler/define.js
CHANGED
|
@@ -213,6 +213,12 @@ function define( model ) {
|
|
|
213
213
|
dictForEach( model.$collectedExtensions, e => e._extensions.forEach( initExtension ) );
|
|
214
214
|
|
|
215
215
|
addI18nBlocks();
|
|
216
|
+
|
|
217
|
+
const { $self } = model.definitions;
|
|
218
|
+
if ($self) {
|
|
219
|
+
message( 'name-deprecated-$self', [ $self.location, $self ], { name: '$self' },
|
|
220
|
+
'Do not use $(NAME) as name for an artifact definition' );
|
|
221
|
+
}
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
// Phase 1: ----------------------------------------------------------------
|
|
@@ -228,14 +234,15 @@ function define( model ) {
|
|
|
228
234
|
if (!src.kind)
|
|
229
235
|
src.kind = 'source';
|
|
230
236
|
|
|
231
|
-
let
|
|
237
|
+
let namespace = src.namespace?.name;
|
|
232
238
|
let prefix = '';
|
|
233
239
|
if (namespace?.path && !namespace.path.broken) {
|
|
234
240
|
namespace.id = pathName( namespace.path );
|
|
235
241
|
prefix = `${ namespace.id }.`;
|
|
236
242
|
}
|
|
237
243
|
if (isInReservedNamespace( prefix )) {
|
|
238
|
-
error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ],
|
|
244
|
+
error( 'reserved-namespace-cds', [ src.namespace.name.location, src.namespace.name ],
|
|
245
|
+
{ name: 'cds' },
|
|
239
246
|
'The namespace $(NAME) is reserved for CDS builtins' );
|
|
240
247
|
namespace = null;
|
|
241
248
|
}
|
|
@@ -523,7 +530,7 @@ function define( model ) {
|
|
|
523
530
|
if (src.$frontend && src.$frontend !== 'cdl')
|
|
524
531
|
return;
|
|
525
532
|
if (src.namespace) {
|
|
526
|
-
const decl = src.namespace;
|
|
533
|
+
const decl = src.namespace.name;
|
|
527
534
|
if (!decl.id) // parsing may have failed
|
|
528
535
|
return;
|
|
529
536
|
if (!model.definitions[decl.id]) {
|
|
@@ -562,8 +569,8 @@ function define( model ) {
|
|
|
562
569
|
initArtifactParentLink( art, model.definitions );
|
|
563
570
|
const block = art._block;
|
|
564
571
|
checkRedefinition( art );
|
|
565
|
-
initMembers( art, art, block );
|
|
566
572
|
initDollarSelf( art ); // $self
|
|
573
|
+
initMembers( art, art, block );
|
|
567
574
|
if (art.params)
|
|
568
575
|
initDollarParameters( art ); // $parameters
|
|
569
576
|
|
|
@@ -636,6 +643,7 @@ function define( model ) {
|
|
|
636
643
|
name: { id: '$parameters' },
|
|
637
644
|
kind: '$parameters',
|
|
638
645
|
location: art.location,
|
|
646
|
+
deprecated: true, // hide in code completion
|
|
639
647
|
};
|
|
640
648
|
setLink( parameters, '_parent', art );
|
|
641
649
|
setLink( parameters, '_main', art );
|
|
@@ -679,8 +687,14 @@ function define( model ) {
|
|
|
679
687
|
setLink( self, '_origin', query );
|
|
680
688
|
setLink( self, '_parent', query );
|
|
681
689
|
setLink( self, '_main', query._main );
|
|
690
|
+
|
|
691
|
+
const projection = { ...self, deprecated: true }; // hide in code completion
|
|
692
|
+
setLink( projection, '_origin', query );
|
|
693
|
+
setLink( projection, '_parent', query );
|
|
694
|
+
setLink( projection, '_main', query._main );
|
|
695
|
+
|
|
682
696
|
query.$tableAliases.$self = self;
|
|
683
|
-
query.$tableAliases.$projection =
|
|
697
|
+
query.$tableAliases.$projection = projection;
|
|
684
698
|
}
|
|
685
699
|
initSubQuery( query ); // check for SELECT clauses after from / mixin
|
|
686
700
|
}
|
|
@@ -858,12 +872,13 @@ function define( model ) {
|
|
|
858
872
|
}
|
|
859
873
|
|
|
860
874
|
function initMixins( query, art ) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
875
|
+
forEachInOrder( query, 'mixin', initMixin );
|
|
876
|
+
|
|
877
|
+
function initMixin( mixin, name ) {
|
|
878
|
+
setLink( mixin, '_block', art._block );
|
|
879
|
+
setMemberParent( mixin, name, query );
|
|
880
|
+
checkRedefinition( mixin );
|
|
864
881
|
if (!(mixin.$duplicates)) {
|
|
865
|
-
setMemberParent( mixin, name, query );
|
|
866
|
-
setLink( mixin, '_block', art._block );
|
|
867
882
|
// TODO: do some initMembers() ? If people had annotation
|
|
868
883
|
// assignments on the mixin... (also for future mixin definitions
|
|
869
884
|
// with generated values)
|
|
@@ -924,14 +939,14 @@ function define( model ) {
|
|
|
924
939
|
setLink( col, '_block', parent._block );
|
|
925
940
|
if (col.inline) { // `@anno elem.{ * }` does not work
|
|
926
941
|
if (col.doc) {
|
|
927
|
-
|
|
942
|
+
message( 'syntax-ignoring-anno', [ col.doc.location, col ],
|
|
928
943
|
{ '#': 'doc', code: '.{ ‹inline› }' } );
|
|
929
944
|
}
|
|
930
945
|
// col.$annotations no available for CSN input, have to search.
|
|
931
|
-
//
|
|
946
|
+
// Message about first annotation should be enough to avoid spam.
|
|
932
947
|
const firstAnno = Object.keys( col ).find( key => key.startsWith( '@' ) );
|
|
933
948
|
if (firstAnno) {
|
|
934
|
-
|
|
949
|
+
message( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ],
|
|
935
950
|
{ code: '.{ ‹inline› }' } );
|
|
936
951
|
}
|
|
937
952
|
}
|
|
@@ -1009,15 +1024,11 @@ function define( model ) {
|
|
|
1009
1024
|
const { targetAspect } = obj;
|
|
1010
1025
|
if (targetAspect) {
|
|
1011
1026
|
if (obj.foreignKeys) {
|
|
1012
|
-
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ]
|
|
1013
|
-
{},
|
|
1014
|
-
'A managed aspect composition can\'t have a foreign keys specification' );
|
|
1027
|
+
error( 'type-unexpected-foreign-keys', [ obj.foreignKeys[$location], construct ] );
|
|
1015
1028
|
delete obj.foreignKeys; // continuation semantics: not specified
|
|
1016
1029
|
}
|
|
1017
1030
|
if (obj.on && !obj.target) {
|
|
1018
|
-
error( 'type-unexpected-on-condition', [ obj.on.location, construct ]
|
|
1019
|
-
{},
|
|
1020
|
-
'A managed aspect composition can\'t have a specified ON-condition' );
|
|
1031
|
+
error( 'type-unexpected-on-condition', [ obj.on.location, construct ] );
|
|
1021
1032
|
delete obj.on; // continuation semantics: not specified
|
|
1022
1033
|
}
|
|
1023
1034
|
if (targetAspect.elements)
|
|
@@ -1151,7 +1162,7 @@ function define( model ) {
|
|
|
1151
1162
|
checkRedefinition( elem );
|
|
1152
1163
|
initMembers( elem, elem, bl, initExtensions );
|
|
1153
1164
|
if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))
|
|
1154
|
-
initBoundSelfParam( elem.params );
|
|
1165
|
+
initBoundSelfParam( elem.params, elem._main );
|
|
1155
1166
|
|
|
1156
1167
|
// for a correct home path, setMemberParent needed to be called
|
|
1157
1168
|
|
|
@@ -1167,8 +1178,8 @@ function define( model ) {
|
|
|
1167
1178
|
// TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
|
|
1168
1179
|
createAndLinkCalcDepElement( elem );
|
|
1169
1180
|
|
|
1170
|
-
// Special case (hack) for calculated elements that use
|
|
1171
|
-
// See "Notes on `$
|
|
1181
|
+
// Special case (hack) for calculated elements that use composition+filter:
|
|
1182
|
+
// See "Notes on `$enclosed`" in `ExposingAssocWithFilter.md` for details.
|
|
1172
1183
|
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
|
|
1173
1184
|
delete elem.type;
|
|
1174
1185
|
delete elem.on;
|
|
@@ -1177,27 +1188,24 @@ function define( model ) {
|
|
|
1177
1188
|
}
|
|
1178
1189
|
}
|
|
1179
1190
|
|
|
1180
|
-
function initBoundSelfParam( params ) {
|
|
1191
|
+
function initBoundSelfParam( params, main ) {
|
|
1181
1192
|
if (!params)
|
|
1182
1193
|
return;
|
|
1183
1194
|
if (boundSelfParamType === true) { // first try
|
|
1184
1195
|
const def = model.definitions.$self;
|
|
1185
1196
|
if (def) {
|
|
1186
|
-
// TODO v5: bring this always, probably even as error
|
|
1187
|
-
warning( 'name-deprecated-for-artifacts', [ def.location, def ], { name: '$self' },
|
|
1188
|
-
'Do not use $(NAME) as name for an artifact definition' );
|
|
1189
1197
|
boundSelfParamType = false;
|
|
1190
1198
|
return;
|
|
1191
1199
|
}
|
|
1192
|
-
|
|
1193
|
-
boundSelfParamType = { name: { id: '$self', location }, location };
|
|
1200
|
+
boundSelfParamType = '$self';
|
|
1194
1201
|
}
|
|
1195
1202
|
const first = params[Object.keys( params )[0] || ''];
|
|
1196
1203
|
const type = first?.type || first?.items?.type; // this sequence = no derived type
|
|
1197
1204
|
const path = type?.path;
|
|
1198
1205
|
if (path?.length === 1 && path[0]?.id === '$self') { // TODO: no where: ?
|
|
1199
|
-
|
|
1200
|
-
setLink(
|
|
1206
|
+
const { $self } = main.$tableAliases;
|
|
1207
|
+
setLink( type, '_artifact', $self );
|
|
1208
|
+
setLink( path[0], '_artifact', $self );
|
|
1201
1209
|
}
|
|
1202
1210
|
}
|
|
1203
1211
|
|
package/lib/compiler/extend.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
forEachDefinition,
|
|
10
10
|
forEachMember,
|
|
11
11
|
isDeprecatedEnabled,
|
|
12
|
+
isBetaEnabled,
|
|
12
13
|
} = require('../base/model');
|
|
13
14
|
const { dictAdd, pushToDict } = require('../base/dictionaries');
|
|
14
15
|
const { kindProperties, dictKinds } = require('./base');
|
|
@@ -71,6 +72,7 @@ function extend( model ) {
|
|
|
71
72
|
} );
|
|
72
73
|
|
|
73
74
|
const includesNonShadowedFirst = isDeprecatedEnabled( model.options, 'includesNonShadowedFirst' );
|
|
75
|
+
const isV5preview = isBetaEnabled( model.options, 'v5preview' );
|
|
74
76
|
|
|
75
77
|
sortModelSources();
|
|
76
78
|
const extensionsDict = Object.create( null ); // TODO TMP
|
|
@@ -222,6 +224,7 @@ function extend( model ) {
|
|
|
222
224
|
code: 'extend … with definitions',
|
|
223
225
|
keyword: 'extend service',
|
|
224
226
|
};
|
|
227
|
+
// TODO(v5): Discuss: make this an error?
|
|
225
228
|
warning( 'ext-invalid-kind', [ loc, ext ], msgArgs, {
|
|
226
229
|
std: 'Artifact $(ART) is not of kind $(KIND), use $(CODE) instead',
|
|
227
230
|
annotate: 'There is no artifact $(ART), use $(CODE) instead',
|
|
@@ -818,7 +821,7 @@ function extend( model ) {
|
|
|
818
821
|
annotate[prop] = art[prop];
|
|
819
822
|
}
|
|
820
823
|
}
|
|
821
|
-
if (extensions.length === 1) { // i.e. no proper location if from more than one
|
|
824
|
+
if (extensions.length === 1) { // i.e. no proper location if from more than one extension
|
|
822
825
|
annotate.location = extensions[0].location;
|
|
823
826
|
annotate.name.location = extensions[0].name.location;
|
|
824
827
|
}
|
|
@@ -829,18 +832,38 @@ function extend( model ) {
|
|
|
829
832
|
}
|
|
830
833
|
|
|
831
834
|
function checkRemainingMainExtensions( art, ext, localized ) {
|
|
832
|
-
if (localized)
|
|
835
|
+
if (localized) {
|
|
836
|
+
if (isV5preview && ext.kind === 'extend') {
|
|
837
|
+
// In v5, reject any `extend` on localized.
|
|
838
|
+
error( 'ref-undefined-art', [ ext.location || ext.name.location, ext ],
|
|
839
|
+
{ '#': 'localized', keyword: 'annotate' } );
|
|
840
|
+
}
|
|
833
841
|
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
834
844
|
if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate
|
|
835
845
|
return;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
info( 'anno-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
846
|
+
|
|
847
|
+
if (art?.builtin) {
|
|
848
|
+
info( 'anno-builtin', [ ext.name.location, ext ], {} ); // TODO: better location?
|
|
849
|
+
}
|
|
850
|
+
else if (art?.kind === 'namespace') {
|
|
851
|
+
const hasAnnotations = Object.keys(ext).find(a => a.charAt(0) === '@');
|
|
852
|
+
const firstAnno = ext[hasAnnotations];
|
|
853
|
+
// In v5, extending namespaces is only allowed for `extend with definitions`.
|
|
854
|
+
// Neither annotations nor other extensions are allowed.
|
|
855
|
+
// Non-artifact extensions are reported in resolvePath() already (for v5).
|
|
856
|
+
if ((hasAnnotations || !ext.artifacts) ) {
|
|
857
|
+
if (isV5preview) {
|
|
858
|
+
error( 'ref-undefined-art', [ (firstAnno?.name || ext.name).location, ext ], {
|
|
859
|
+
'#': 'namespace', art: ext,
|
|
860
|
+
} );
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
info( 'anno-namespace', [ (firstAnno?.name || ext.name).location, ext ], {},
|
|
864
|
+
'Namespaces can\'t be annotated nor extended' );
|
|
865
|
+
}
|
|
866
|
+
}
|
|
844
867
|
}
|
|
845
868
|
}
|
|
846
869
|
|
package/lib/compiler/index.js
CHANGED
package/lib/compiler/populate.js
CHANGED
|
@@ -617,8 +617,6 @@ function populate( model ) {
|
|
|
617
617
|
|
|
618
618
|
function resolveTabRef( alias ) {
|
|
619
619
|
// effectiveType() must not be called on $self, is unnecessary for mixins:
|
|
620
|
-
// TODO: have a test for `select from E { a, $self.a as b, $self.{ b as c } }`
|
|
621
|
-
// TODO: have a negative test for `select from E { $self.*, assoc.* }`
|
|
622
620
|
// (we might have those already)
|
|
623
621
|
if (alias.kind === 'mixin' || alias.kind === '$self')
|
|
624
622
|
return;
|