@sap/cds-compiler 5.8.0 → 5.9.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 +37 -0
- package/bin/cds_remove_invalid_whitespace.js +5 -3
- package/bin/cds_update_identifiers.js +9 -6
- package/bin/cdsc.js +79 -59
- package/bin/cdsse.js +14 -10
- package/bin/cdsv2m.js +3 -1
- package/lib/api/options.js +28 -6
- package/lib/base/message-registry.js +15 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/checks.js +70 -50
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/generate.js +8 -2
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +78 -31
- package/lib/compiler/shared.js +3 -3
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +10 -0
- package/lib/compiler/xpr-rewrite.js +1 -1
- package/lib/edm/annotations/edmJson.js +42 -39
- package/lib/edm/annotations/genericTranslation.js +55 -55
- package/lib/edm/annotations/preprocessAnnotations.js +5 -5
- package/lib/edm/csn2edm.js +21 -16
- package/lib/edm/edm.js +62 -62
- package/lib/edm/edmAnnoPreprocessor.js +2 -2
- package/lib/edm/edmInboundChecks.js +1 -1
- package/lib/edm/edmPreprocessor.js +32 -32
- package/lib/edm/edmUtils.js +8 -8
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +77 -81
- package/lib/gen/Dictionary.json +3062 -3072
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1238 -1236
- package/lib/json/from-csn.js +1 -1
- package/lib/json/to-csn.js +30 -3
- package/lib/language/genericAntlrParser.js +16 -0
- package/lib/main.d.ts +79 -1
- package/lib/model/csnRefs.js +12 -5
- package/lib/model/xprAsTree.js +71 -0
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +46 -32
- package/lib/parsers/CdlGrammar.g4 +33 -28
- package/lib/parsers/Lexer.js +1 -1
- package/lib/parsers/XprTree.js +25 -16
- package/lib/render/toCdl.js +902 -414
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +8 -0
- package/lib/render/utils/common.js +2 -2
- package/lib/render/utils/operators.js +160 -0
- package/lib/render/utils/pretty.js +337 -0
- package/lib/sql-identifier.js +7 -9
- package/lib/transform/addTenantFields.js +39 -41
- package/lib/transform/db/applyTransformations.js +4 -4
- package/lib/transform/db/assertUnique.js +6 -5
- package/lib/transform/db/associations.js +3 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
- package/lib/transform/db/assocsToQueries/utils.js +8 -0
- package/lib/transform/db/backlinks.js +19 -14
- package/lib/transform/db/constraints.js +6 -6
- package/lib/transform/db/expansion.js +1 -1
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +1 -1
- package/lib/transform/db/processSqlServices.js +3 -3
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/temporal.js +7 -9
- package/lib/transform/db/views.js +6 -6
- package/lib/transform/draft/odata.js +2 -0
- package/lib/transform/effective/annotations.js +1 -1
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/main.js +1 -0
- package/lib/transform/effective/service.js +2 -2
- package/lib/transform/forRelationalDB.js +11 -5
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
- package/lib/transform/odata/createForeignKeys.js +1 -1
- package/lib/transform/odata/flattening.js +2 -1
- package/lib/transform/parseExpr.js +2 -2
- package/lib/transform/transformUtils.js +9 -7
- package/lib/transform/translateAssocsToJoins.js +0 -2
- package/lib/transform/universalCsn/coreComputed.js +2 -2
- package/lib/utils/moduleResolve.js +7 -5
- package/package.json +1 -1
- package/share/messages/def-upcoming-virtual-change.md +55 -0
- package/share/messages/file-unexpected-case-mismatch.md +61 -0
- package/share/messages/message-explanations.json +2 -0
- package/lib/transform/braceExpression.js +0 -77
package/lib/api/options.js
CHANGED
|
@@ -20,6 +20,7 @@ const publicOptionsNewAPI = [
|
|
|
20
20
|
'defaultBinaryLength',
|
|
21
21
|
'defaultStringLength',
|
|
22
22
|
'csnFlavor',
|
|
23
|
+
'compositionIncludes',
|
|
23
24
|
// DB
|
|
24
25
|
'sqlDialect',
|
|
25
26
|
'sqlMapping',
|
|
@@ -33,6 +34,7 @@ const publicOptionsNewAPI = [
|
|
|
33
34
|
'fewerLocalizedViews',
|
|
34
35
|
'withHanaAssociations',
|
|
35
36
|
'standardDatabaseFunctions',
|
|
37
|
+
'booleanEquality',
|
|
36
38
|
// ODATA
|
|
37
39
|
'odataOpenapiHints',
|
|
38
40
|
'edm4OpenAPI',
|
|
@@ -48,6 +50,9 @@ const publicOptionsNewAPI = [
|
|
|
48
50
|
'odataNoCreator',
|
|
49
51
|
'service',
|
|
50
52
|
'serviceNames',
|
|
53
|
+
// to.cdl
|
|
54
|
+
'renderCdlDefinitionNesting',
|
|
55
|
+
'renderCdlCommonNamespace',
|
|
51
56
|
//
|
|
52
57
|
'dictionaryPrototype',
|
|
53
58
|
// for.effective
|
|
@@ -66,7 +71,7 @@ const publicOptionsNewAPI = [
|
|
|
66
71
|
const privateOptions = [
|
|
67
72
|
// Not callable via cdsc, keep private for now until we are sure that we want this
|
|
68
73
|
'filterCsn',
|
|
69
|
-
'lintMode',
|
|
74
|
+
'lintMode', // for cdsse only
|
|
70
75
|
'traceFs',
|
|
71
76
|
'traceParser',
|
|
72
77
|
'traceParserAmb',
|
|
@@ -134,7 +139,7 @@ function translateOptions( input = {}, defaults = {}, hardRequire = {},
|
|
|
134
139
|
reclassifyErrorsForOpenApi( options );
|
|
135
140
|
|
|
136
141
|
// Convenience for $user -> $user.id replacement
|
|
137
|
-
if (options.variableReplacements
|
|
142
|
+
if (options.variableReplacements?.$user && typeof options.variableReplacements?.$user === 'string')
|
|
138
143
|
options.variableReplacements.$user = { id: options.variableReplacements.$user };
|
|
139
144
|
|
|
140
145
|
return options;
|
|
@@ -158,11 +163,21 @@ function reclassifyErrorsForOpenApi( options ) {
|
|
|
158
163
|
|
|
159
164
|
module.exports = {
|
|
160
165
|
to: {
|
|
161
|
-
cdl: options =>
|
|
166
|
+
cdl: (options) => {
|
|
167
|
+
// TODO(v6): Change defaults to 'true'
|
|
168
|
+
const defaultOptions = {
|
|
169
|
+
renderCdlDefinitionNesting: false,
|
|
170
|
+
renderCdlCommonNamespace: false,
|
|
171
|
+
};
|
|
172
|
+
return translateOptions(options, defaultOptions, undefined, undefined, undefined, 'to.cdl');
|
|
173
|
+
},
|
|
162
174
|
sql: (options) => {
|
|
163
175
|
const hardOptions = { src: 'sql', toSql: true, forHana: true };
|
|
164
176
|
const defaultOptions = {
|
|
165
|
-
sqlMapping: 'plain',
|
|
177
|
+
sqlMapping: 'plain',
|
|
178
|
+
sqlDialect: 'plain',
|
|
179
|
+
withHanaAssociations: true,
|
|
180
|
+
booleanEquality: false,
|
|
166
181
|
};
|
|
167
182
|
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
|
|
168
183
|
|
|
@@ -172,14 +187,21 @@ module.exports = {
|
|
|
172
187
|
const hardOptions = { src: 'hdi', toSql: true, forHana: true };
|
|
173
188
|
// TODO: sqlDialect should be a hard option!
|
|
174
189
|
const defaultOptions = {
|
|
175
|
-
sqlMapping: 'plain',
|
|
190
|
+
sqlMapping: 'plain',
|
|
191
|
+
sqlDialect: 'hana',
|
|
192
|
+
withHanaAssociations: true,
|
|
193
|
+
booleanEquality: false,
|
|
176
194
|
};
|
|
177
195
|
return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
|
|
178
196
|
},
|
|
179
197
|
hdbcds: (options) => {
|
|
180
198
|
const hardOptions = { forHana: true };
|
|
181
199
|
// TODO: sqlDialect should be a hard option!
|
|
182
|
-
const defaultOptions = {
|
|
200
|
+
const defaultOptions = {
|
|
201
|
+
sqlMapping: 'plain',
|
|
202
|
+
sqlDialect: 'hana',
|
|
203
|
+
booleanEquality: false,
|
|
204
|
+
};
|
|
183
205
|
return translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdbcds');
|
|
184
206
|
},
|
|
185
207
|
edm: (options) => {
|
|
@@ -76,6 +76,8 @@ const centralMessages = {
|
|
|
76
76
|
|
|
77
77
|
'type-invalid-items': { severity: 'Error' }, // not supported yet
|
|
78
78
|
'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
|
|
79
|
+
// the following is not really an error in v6, but gets a different semantics:
|
|
80
|
+
'assoc-incomplete-to-many': { severity: 'Warning', errorFor: [ 'v6' ] },
|
|
79
81
|
'def-unexpected-nested-proj': { severity: 'Error', configurableFor: 'v4' },
|
|
80
82
|
'def-unexpected-paramview-assoc': { severity: 'Error' },
|
|
81
83
|
'def-unexpected-calcview-assoc': { severity: 'Error' },
|
|
@@ -93,7 +95,7 @@ const centralMessages = {
|
|
|
93
95
|
|
|
94
96
|
'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
|
|
95
97
|
'ref-deprecated-self-element': { severity: 'Error', configurableFor: true },
|
|
96
|
-
'ref-deprecated-variable': { severity: 'Warning'
|
|
98
|
+
'ref-deprecated-variable': { severity: 'Warning' },
|
|
97
99
|
'ref-invalid-type': { severity: 'Error' },
|
|
98
100
|
'ref-unexpected-self': { severity: 'Error' },
|
|
99
101
|
'ref-invalid-include': { severity: 'Error' },
|
|
@@ -163,15 +165,16 @@ const centralMessages = {
|
|
|
163
165
|
// fallback, but then parse.cdl is not supposed to work correctly (it can
|
|
164
166
|
// then either issue an error or produce a CSN missing some annotations):
|
|
165
167
|
'syntax-duplicate-annotate': { severity: 'Error' },
|
|
166
|
-
'syntax-duplicate-clause': { severity: 'Error', configurableFor: 'v6' },
|
|
168
|
+
'syntax-duplicate-clause': { severity: 'Error', configurableFor: [ 'v6' ] },
|
|
167
169
|
// remark: a hard syntax error in new parser for `null` together with `not null`
|
|
168
170
|
'syntax-duplicate-equal-clause': { severity: 'Warning', errorFor: [ 'v6' ] },
|
|
169
171
|
'syntax-invalid-name': { severity: 'Error', configurableFor: 'deprecated' },
|
|
170
172
|
'syntax-missing-as': { severity: 'Error', configurableFor: true },
|
|
171
|
-
'syntax-missing-proj-semicolon': { severity: 'Warning'
|
|
173
|
+
'syntax-missing-proj-semicolon': { severity: 'Warning' },
|
|
172
174
|
'syntax-unexpected-after': { severity: 'Warning', errorFor: [ 'v6' ] },
|
|
173
175
|
'syntax-unexpected-filter': { severity: 'Warning', errorFor: [ 'v6' ] },
|
|
174
176
|
'syntax-unexpected-many-one': { severity: 'Error', configurableFor: true }, // TODO: remove `configurableFor` soon, latest v6
|
|
177
|
+
'syntax-deprecated-ref-virtual': { severity: 'Warning', errorFor: [ 'v6' ] },
|
|
175
178
|
'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true },
|
|
176
179
|
'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
|
|
177
180
|
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
|
|
@@ -268,6 +271,9 @@ const centralMessageTexts = {
|
|
|
268
271
|
type: 'Expected option $(OPTION) to be of type $(VALUE). Found: $(OTHERVALUE)',
|
|
269
272
|
forbidden: 'Option $(OPTION) can\'t be used with API function $(MODULE)',
|
|
270
273
|
},
|
|
274
|
+
'def-upcoming-virtual-change': {
|
|
275
|
+
std: 'This select item will be a new element in cds-compiler v6, without referencing $(NAME); prepend a table alias if you want to keep the virtual element as a reference; see https://cap.cloud.sap/docs/cds/compiler/messages#def-upcoming-virtual-change for details',
|
|
276
|
+
},
|
|
271
277
|
|
|
272
278
|
'api-invalid-variable-replacement': {
|
|
273
279
|
std: 'Option $(OPTION) does not support $(NAME)',
|
|
@@ -385,6 +391,11 @@ const centralMessageTexts = {
|
|
|
385
391
|
},
|
|
386
392
|
'syntax-unexpected-filter': 'Unexpected filter on the result of a function call',
|
|
387
393
|
'syntax-unexpected-many-one': 'Replace $(CODE) with $(DELIMITED) to avoid an ambiguity with managed compositions of anonymous aspects',
|
|
394
|
+
'syntax-deprecated-ref-virtual': {
|
|
395
|
+
std: 'Use $(DELIMITED) at the beginning of the column expression',
|
|
396
|
+
ref: 'Use $(DELIMITED) to refer to the element $(NAME) at the beginning of the column expression',
|
|
397
|
+
func: 'Use $(DELIMITED) when calling the function $(NAME) at the beginning of the column expression',
|
|
398
|
+
},
|
|
388
399
|
'syntax-invalid-name': {
|
|
389
400
|
std: 'Identifier must consist of at least one character', // only via delimited id
|
|
390
401
|
// as: 'String in property $(PROP) must not be empty', // expecting non-empty string is ok
|
|
@@ -900,12 +911,12 @@ const centralMessageTexts = {
|
|
|
900
911
|
},
|
|
901
912
|
'def-unexpected-localized': {
|
|
902
913
|
std: 'Unexpected $(KEYWORD)',
|
|
903
|
-
struct: 'Unexpected $(KEYWORD) for structured type',
|
|
904
914
|
map: 'Unexpected $(KEYWORD) for map type',
|
|
905
915
|
elements: '$(ART) can\'t have localized elements',
|
|
906
916
|
// TODO: Better message?
|
|
907
917
|
include: '$(ART) can\'t have localized elements (through include)',
|
|
908
918
|
},
|
|
919
|
+
'def-unexpected-localized-struct': '$(KEYWORD) is not fully supported for structures',
|
|
909
920
|
'def-unexpected-localized-anno': 'Annotations can\'t have localized elements',
|
|
910
921
|
'type-unexpected-structure': {
|
|
911
922
|
std: 'Unexpected structured type', // unused variant
|
package/lib/checks/validator.js
CHANGED
|
@@ -52,6 +52,7 @@ const {
|
|
|
52
52
|
} = require('./sql-snippets');
|
|
53
53
|
const assertNoAssocUsageOutsideOfService = require('./assocOutsideService');
|
|
54
54
|
const featureFlags = require('./featureFlags');
|
|
55
|
+
const { timetrace } = require('../utils/timetrace');
|
|
55
56
|
|
|
56
57
|
const forRelationalDBMemberValidators
|
|
57
58
|
= [
|
|
@@ -156,7 +157,9 @@ function _validate( csn, that,
|
|
|
156
157
|
artifactValidators = [],
|
|
157
158
|
queryValidators = [],
|
|
158
159
|
iterateOptions = {} ) {
|
|
160
|
+
timetrace.start('Enrich CSN');
|
|
159
161
|
const { cleanup } = enrichCsn(csn, that.options);
|
|
162
|
+
timetrace.stop('Enrich CSN');
|
|
160
163
|
// TODO: Don't know if that's feasible? Do we really need to enrich annotations always?
|
|
161
164
|
// const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
|
|
162
165
|
|
package/lib/compiler/base.js
CHANGED
|
@@ -75,7 +75,7 @@ function propExists( prop, parent ) {
|
|
|
75
75
|
* `element`. The following code makes use of the fact that only member extensions
|
|
76
76
|
* have a "sparse" name structure.
|
|
77
77
|
*
|
|
78
|
-
* @param {XSN.Artifact} art
|
|
78
|
+
* @param {XSN.Artifact & XSN.Using} art
|
|
79
79
|
* @returns {XSN.Name}
|
|
80
80
|
*/
|
|
81
81
|
function getArtifactName( art ) {
|
package/lib/compiler/checks.js
CHANGED
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
const { typeParameters } = require('./builtins');
|
|
22
22
|
const { propagationRules } = require('../base/builtins');
|
|
23
23
|
const { annotationVal } = require('./utils');
|
|
24
|
+
const { functionsWithoutParentheses } = require('../parsers/identifiers');
|
|
24
25
|
|
|
25
26
|
const $location = Symbol.for( 'cds.$location' );
|
|
26
27
|
|
|
@@ -131,7 +132,6 @@ function check( model ) {
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
function checkElement( elem, parentProps ) {
|
|
134
|
-
checkLocalizedSubElement( elem );
|
|
135
135
|
checkKey( elem, parentProps );
|
|
136
136
|
checkLocalizedElement( elem );
|
|
137
137
|
|
|
@@ -274,10 +274,13 @@ function check( model ) {
|
|
|
274
274
|
function checkLocalizedElement( elem ) {
|
|
275
275
|
if (elem.localized?.val) {
|
|
276
276
|
const type = elem._effectiveType;
|
|
277
|
-
if (type?.category === 'map'
|
|
278
|
-
(type?.elements && isBetaEnabled( model.options, 'v6preview' ))) {
|
|
277
|
+
if (type?.category === 'map') {
|
|
279
278
|
error( 'def-unexpected-localized', [ elem.localized.location, elem ],
|
|
280
|
-
{ keyword: 'localized', '#':
|
|
279
|
+
{ keyword: 'localized', '#': 'map' } );
|
|
280
|
+
}
|
|
281
|
+
else if (type?.elements) { // warning only, as we want to support it in the future
|
|
282
|
+
warning( 'def-unexpected-localized-struct', [ elem.localized.location, elem ],
|
|
283
|
+
{ keyword: 'localized' } );
|
|
281
284
|
}
|
|
282
285
|
else if (!type || !type.builtin || type.category !== 'string') {
|
|
283
286
|
// See discussion issue #6520: should we allow all scalar types?
|
|
@@ -287,9 +290,10 @@ function check( model ) {
|
|
|
287
290
|
}
|
|
288
291
|
}
|
|
289
292
|
|
|
290
|
-
// TODO: This check should be moved to localized.js
|
|
293
|
+
// TODO: This check should be moved to localized.js - WHY?
|
|
291
294
|
// "key" keyword at localized element in SELECT list.
|
|
292
295
|
// TODO: not in inferred elements, but also inside aspects
|
|
296
|
+
// TODO: `localized` is not necessarily at _origin, but the _origin chain
|
|
293
297
|
if (elem.key?.val && elem._main?.query) {
|
|
294
298
|
// either the element was casted to localized (no `_origin`) or
|
|
295
299
|
// original element is localized but not key, as that would have
|
|
@@ -472,47 +476,6 @@ function check( model ) {
|
|
|
472
476
|
}
|
|
473
477
|
}
|
|
474
478
|
|
|
475
|
-
/**
|
|
476
|
-
* TODO: check inside compiler as it is a compiler restriction - improve
|
|
477
|
-
* TODO: Recursive check; use "parentProps" as other member checks
|
|
478
|
-
*
|
|
479
|
-
* Non-recursive check if sub-elements have a "localized" keyword since this is
|
|
480
|
-
* not yet supported.
|
|
481
|
-
*
|
|
482
|
-
* This check is not recursive to avoid a runtime overhead. Because of this it fails
|
|
483
|
-
* to detect scenarios with indirections, e.g.
|
|
484
|
-
* ```cds
|
|
485
|
-
* type L : localized String;
|
|
486
|
-
* type L1 : L;
|
|
487
|
-
* type L2 : L1;
|
|
488
|
-
*
|
|
489
|
-
* entity E {
|
|
490
|
-
* struct : {
|
|
491
|
-
* subElement : L2;
|
|
492
|
-
* }
|
|
493
|
-
* }
|
|
494
|
-
* ```
|
|
495
|
-
*
|
|
496
|
-
* @param {XSN.Artifact} element
|
|
497
|
-
*/
|
|
498
|
-
function checkLocalizedSubElement( element ) {
|
|
499
|
-
if (element._parent?.kind !== 'element')
|
|
500
|
-
return;
|
|
501
|
-
|
|
502
|
-
const isLocalizedSubElement = element.localized?.val;
|
|
503
|
-
if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
|
|
504
|
-
const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
|
|
505
|
-
warning( 'localized-sub-element', [ loc, element ], {
|
|
506
|
-
type: element.type?._artifact || '',
|
|
507
|
-
'#': isLocalizedSubElement ? 'std' : 'type',
|
|
508
|
-
keyword: 'localized',
|
|
509
|
-
}, {
|
|
510
|
-
std: 'Keyword $(KEYWORD) is ignored for sub elements',
|
|
511
|
-
type: 'Keyword $(KEYWORD) in type $(TYPE) is ignored for sub elements',
|
|
512
|
-
} );
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
479
|
/**
|
|
517
480
|
* Check that min and max cardinalities of 'art' have legal values
|
|
518
481
|
*
|
|
@@ -615,13 +578,22 @@ function check( model ) {
|
|
|
615
578
|
}
|
|
616
579
|
}
|
|
617
580
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (isStructured) {
|
|
581
|
+
const isMap = art._effectiveType?.name.id === 'cds.Map';
|
|
582
|
+
if (isMap) {
|
|
621
583
|
error( 'type-unexpected-default', [ defaultValue.location, art ], {
|
|
622
584
|
'#': 'map', keyword: 'default', type: 'cds.Map',
|
|
623
585
|
} );
|
|
624
586
|
}
|
|
587
|
+
else if (art._effectiveType?.elements) {
|
|
588
|
+
warning( 'type-unexpected-default-struct', [ defaultValue.location, art ], {
|
|
589
|
+
'#': art.kind, keyword: 'default',
|
|
590
|
+
}, {
|
|
591
|
+
std: 'Unexpected $(KEYWORD) for a structure',
|
|
592
|
+
param: 'Unexpected $(KEYWORD) for a structured parameter',
|
|
593
|
+
type: 'Unexpected $(KEYWORD) for a structured type definition',
|
|
594
|
+
element: 'Unexpected $(KEYWORD) for a structured element',
|
|
595
|
+
} );
|
|
596
|
+
}
|
|
625
597
|
}
|
|
626
598
|
|
|
627
599
|
function getBinaryOp( cond ) {
|
|
@@ -673,7 +645,7 @@ function check( model ) {
|
|
|
673
645
|
const element = def.elements[name];
|
|
674
646
|
// Element is new in `art`, not expanded; we can't check for !element._origin, due
|
|
675
647
|
// to calculated elements such as `a = b`.
|
|
676
|
-
if (element.$inferred !== 'include') {
|
|
648
|
+
if (element.$inferred !== 'include' && element.$inferred !== 'aspect-composition') {
|
|
677
649
|
for (const include of def.includes) {
|
|
678
650
|
if (include._artifact?.elements?.[name] !== undefined)
|
|
679
651
|
checkElementOverride( element, include._artifact.elements[name] );
|
|
@@ -780,6 +752,7 @@ function check( model ) {
|
|
|
780
752
|
|
|
781
753
|
function checkSelectItemValue( elem ) {
|
|
782
754
|
checkExpressionAssociationUsage( elem.value, elem, false );
|
|
755
|
+
checkVirtualSelectItemChangeForV6( elem );
|
|
783
756
|
// To avoid duplicate messages, only run this check if the type wasn't inferred from
|
|
784
757
|
// the cast, as otherwise we will check it twice (once here, once via element).
|
|
785
758
|
if (elem.value?.op?.val === 'cast' && elem.type?.$inferred !== 'cast') {
|
|
@@ -792,6 +765,40 @@ function check( model ) {
|
|
|
792
765
|
} );
|
|
793
766
|
}
|
|
794
767
|
|
|
768
|
+
/**
|
|
769
|
+
* In v6, there will be a change in semantics for following example:
|
|
770
|
+
*
|
|
771
|
+
* ```cds
|
|
772
|
+
* view V as select from E {
|
|
773
|
+
* virtual b, // -> warning: will be new element, not reference in v6
|
|
774
|
+
* }
|
|
775
|
+
* ```
|
|
776
|
+
*
|
|
777
|
+
* We allow users to define _new_ elements using `virtual`. But all such references
|
|
778
|
+
* in v5 are valid, resolvable references. Hence, the semantics will change in v6.
|
|
779
|
+
* Let users know about it.
|
|
780
|
+
*
|
|
781
|
+
* @param elem
|
|
782
|
+
*/
|
|
783
|
+
function checkVirtualSelectItemChangeForV6( elem ) {
|
|
784
|
+
if (
|
|
785
|
+
!elem.virtual?.val || elem.virtual.$inferred || // not explicitly marked virtual
|
|
786
|
+
elem.type && !elem.type.$inferred || // has explicit type
|
|
787
|
+
elem.value.path?.length > 1 || // multi-path step, i.e. no new definition
|
|
788
|
+
!elem.name.$inferred // has explicit alias
|
|
789
|
+
)
|
|
790
|
+
return;
|
|
791
|
+
|
|
792
|
+
if ((!elem.value.path || !elem.value._artifact) &&
|
|
793
|
+
!isFunctionWithoutParentheses( elem ))
|
|
794
|
+
return; // not a reference nor function without parentheses
|
|
795
|
+
|
|
796
|
+
if (elem.value.path?.some(ps => ps.args || ps.where))
|
|
797
|
+
return; // not a simple reference
|
|
798
|
+
|
|
799
|
+
warning('def-upcoming-virtual-change', [ elem.virtual.location, elem ], { name: elem.name.id });
|
|
800
|
+
}
|
|
801
|
+
|
|
795
802
|
function checkCalculatedElementValue( elem ) {
|
|
796
803
|
const isStored = elem.value.stored?.val;
|
|
797
804
|
visitExpression( elem.value, elem, (xpr, user) => {
|
|
@@ -1378,4 +1385,17 @@ function isComplexView( art ) {
|
|
|
1378
1385
|
return (!art.query.from?._artifact || art.query.from._artifact.kind === 'element');
|
|
1379
1386
|
}
|
|
1380
1387
|
|
|
1388
|
+
/**
|
|
1389
|
+
* @param {XSN.Element} elem
|
|
1390
|
+
* @returns {boolean}
|
|
1391
|
+
*/
|
|
1392
|
+
function isFunctionWithoutParentheses( elem ) {
|
|
1393
|
+
const func = elem.value?.func;
|
|
1394
|
+
if (!func?.path || elem.value.args)
|
|
1395
|
+
return false;
|
|
1396
|
+
if (func.path.length !== 1)
|
|
1397
|
+
return false;
|
|
1398
|
+
return functionsWithoutParentheses.includes(func.path[0].id.toUpperCase());
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1381
1401
|
module.exports = check;
|
package/lib/compiler/extend.js
CHANGED
|
@@ -1400,7 +1400,7 @@ function layeredExtensions( extensions ) {
|
|
|
1400
1400
|
* at least two files are involved
|
|
1401
1401
|
*
|
|
1402
1402
|
* @param {Record<string, object>} layered Structure as returned by layeredExtensions()
|
|
1403
|
-
* @returns {{
|
|
1403
|
+
* @returns {{highest, issue: boolean|string}}
|
|
1404
1404
|
*/
|
|
1405
1405
|
function extensionsOfHighestLayers( layered ) {
|
|
1406
1406
|
const layerNames = Object.keys( layered );
|
package/lib/compiler/generate.js
CHANGED
|
@@ -35,6 +35,7 @@ function generate( model ) {
|
|
|
35
35
|
extendArtifactBefore,
|
|
36
36
|
applyIncludes,
|
|
37
37
|
} = model.$functions;
|
|
38
|
+
model.$functions.hasTruthyProp = hasTruthyProp;
|
|
38
39
|
|
|
39
40
|
const addTextsLanguageAssoc = checkTextsLanguageAssocOption( model, options );
|
|
40
41
|
const useTextsAspect = checkTextsAspect();
|
|
@@ -530,7 +531,7 @@ function generate( model ) {
|
|
|
530
531
|
return false;
|
|
531
532
|
name = (art._main || art)?.name?.id;
|
|
532
533
|
}
|
|
533
|
-
else if (art.type
|
|
534
|
+
else if (art.type) {
|
|
534
535
|
// TODO: also do something special for TYPE OF inside `art`s own elements
|
|
535
536
|
// TODO: check for own - add test case with Type:elem (not TYPE OF elem)
|
|
536
537
|
name = resolveUncheckedPath( art.type, 'type', art );
|
|
@@ -563,7 +564,7 @@ function generate( model ) {
|
|
|
563
564
|
const elem = base.elements[name];
|
|
564
565
|
if (elem.$duplicates)
|
|
565
566
|
return false; // no composition-of-type unfold with redefined elems
|
|
566
|
-
if (elem.key
|
|
567
|
+
if (elem.key?.val)
|
|
567
568
|
k[name] = elem;
|
|
568
569
|
}
|
|
569
570
|
return k;
|
|
@@ -695,6 +696,8 @@ function generate( model ) {
|
|
|
695
696
|
$inferred: 'composition-entity',
|
|
696
697
|
};
|
|
697
698
|
if (target.name) { // named target aspect
|
|
699
|
+
if (options.compositionIncludes !== false) // default is 'true' since v5.9
|
|
700
|
+
art.includes = [ createInclude( target.name.id, location ) ];
|
|
698
701
|
setLink( art, '_origin', target );
|
|
699
702
|
setLink( art, '_upperAspects', [ target, ...(elem._main._upperAspects || []) ] );
|
|
700
703
|
}
|
|
@@ -749,6 +752,9 @@ function generate( model ) {
|
|
|
749
752
|
extendArtifactBefore( art );
|
|
750
753
|
// Copy persistence annotations from aspect.
|
|
751
754
|
copyPersistenceAnnotations( art, target ); // after extendArtifactBefore()
|
|
755
|
+
|
|
756
|
+
if (options.compositionIncludes !== false && art.includes)
|
|
757
|
+
applyIncludes( art, art ); // for actions
|
|
752
758
|
return art;
|
|
753
759
|
}
|
|
754
760
|
|
package/lib/compiler/index.js
CHANGED
|
@@ -124,7 +124,7 @@ function parserForFile( source, ext, options ) {
|
|
|
124
124
|
// file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
|
|
125
125
|
// Options can have the following properties:
|
|
126
126
|
// - Truthy `parseOnly`: stop compilation after parsing.
|
|
127
|
-
// - Truthy `lintMode`: do not do
|
|
127
|
+
// - Truthy `lintMode`: do not do propagation
|
|
128
128
|
// - many others - TODO
|
|
129
129
|
|
|
130
130
|
// This function returns a Promise and can be used with `await`. For an example
|
package/lib/compiler/lsp-api.js
CHANGED
|
@@ -168,7 +168,7 @@ function* artifactTokens( art ) {
|
|
|
168
168
|
if (artifactActions[prop])
|
|
169
169
|
yield* artifactActions[prop](art[prop], art);
|
|
170
170
|
else if (prop.charAt(0) === '@')
|
|
171
|
-
yield* artifactActions['@'](art[prop]
|
|
171
|
+
yield* artifactActions['@'](art[prop]);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
return null;
|
|
@@ -366,10 +366,10 @@ function propagate( model ) {
|
|
|
366
366
|
return;
|
|
367
367
|
for (const item of path) {
|
|
368
368
|
const art = item && item._artifact;
|
|
369
|
-
if (art
|
|
369
|
+
if (art?.virtual?.val) {
|
|
370
370
|
warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
|
|
371
371
|
// eslint-disable-next-line @stylistic/js/max-len
|
|
372
|
-
'Prepend $(KEYWORD) to current select item -
|
|
372
|
+
'Prepend $(KEYWORD) to current select item - containing element $(ART) is virtual' );
|
|
373
373
|
return;
|
|
374
374
|
}
|
|
375
375
|
}
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -64,6 +64,7 @@ const {
|
|
|
64
64
|
linkToOrigin,
|
|
65
65
|
compositionTextVariant,
|
|
66
66
|
targetCantBeAspect,
|
|
67
|
+
userParam,
|
|
67
68
|
} = require('./utils');
|
|
68
69
|
|
|
69
70
|
const detectCycles = require('./cycle-detector');
|
|
@@ -96,6 +97,7 @@ function resolve( model ) {
|
|
|
96
97
|
effectiveType,
|
|
97
98
|
getOrigin,
|
|
98
99
|
getInheritedProp,
|
|
100
|
+
hasTruthyProp, // limited inheritance
|
|
99
101
|
resolveTypeArgumentsUnchecked,
|
|
100
102
|
} = model.$functions;
|
|
101
103
|
Object.assign( model.$functions, {
|
|
@@ -509,7 +511,7 @@ function resolve( model ) {
|
|
|
509
511
|
|
|
510
512
|
// Check if relational type is missing its target or if it's used directly.
|
|
511
513
|
if (elemtype.category === 'relation' &&
|
|
512
|
-
|
|
514
|
+
!obj.target && !obj.targetAspect) {
|
|
513
515
|
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
514
516
|
error( 'type-missing-target', [ obj.type.location, obj ],
|
|
515
517
|
{ '#': isCsn ? 'csn' : 'std', type: elemtype }, {
|
|
@@ -582,6 +584,35 @@ function resolve( model ) {
|
|
|
582
584
|
}
|
|
583
585
|
}
|
|
584
586
|
|
|
587
|
+
// Set '@Core.Computed' in the Core Compiler to have it propagated...
|
|
588
|
+
if (art.kind !== 'element' || art['@Core.Computed'])
|
|
589
|
+
return;
|
|
590
|
+
|
|
591
|
+
// For events and types, elements can't be @Core.Computed, as values are only used
|
|
592
|
+
// to infer the element signature. For virtual, we keep @Core.Computed, as it's
|
|
593
|
+
// always been that way, even before type projections.
|
|
594
|
+
const elementsCanBeComputed = art._main?.kind !== 'type' && art._main?.kind !== 'event';
|
|
595
|
+
|
|
596
|
+
if (art.virtual?.val ||
|
|
597
|
+
elementsCanBeComputed && art.value &&
|
|
598
|
+
(!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
|
|
599
|
+
art.value.stored?.val || // calculated elements on-write are always computed
|
|
600
|
+
art.value._artifact.kind === 'builtin' ||
|
|
601
|
+
art.value._artifact.kind === 'param' ||
|
|
602
|
+
art.value.scope === 'param' )) {
|
|
603
|
+
art['@Core.Computed'] = {
|
|
604
|
+
name: {
|
|
605
|
+
path: [ { id: 'Core.Computed', location: art.location } ],
|
|
606
|
+
location: art.location,
|
|
607
|
+
},
|
|
608
|
+
$inferred: '$generated',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (art.kind === 'element' && art._effectiveType)
|
|
613
|
+
checkLocalizedElement( art );
|
|
614
|
+
return;
|
|
615
|
+
|
|
585
616
|
/**
|
|
586
617
|
* Check whether the signature of the specified element matches that of the inferred one.
|
|
587
618
|
*
|
|
@@ -619,11 +650,11 @@ function resolve( model ) {
|
|
|
619
650
|
}
|
|
620
651
|
// If specified type is `null`, type could not be resolved.
|
|
621
652
|
else if (!compToAssoc && sType && sType !== iType &&
|
|
622
|
-
|
|
623
|
-
|
|
653
|
+
// Special case for $recompilation: allow one level of type indirection. See #12113.
|
|
654
|
+
(!options.$recompile || sType !== iType.type?._artifact)) {
|
|
624
655
|
const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
|
|
625
|
-
|
|
626
|
-
|
|
656
|
+
iType?.name && sType?.name && 'typeName' || // both types are named
|
|
657
|
+
'type'; // unknown type names
|
|
627
658
|
const othertype = typeName !== 'type' && iType || '';
|
|
628
659
|
error( 'query-mismatched-element', [
|
|
629
660
|
specifiedElement.type.location || specifiedElement.location, user,
|
|
@@ -639,7 +670,7 @@ function resolve( model ) {
|
|
|
639
670
|
// This relies on (element) expansion! Check that both sides have the following properties.
|
|
640
671
|
// On the inferred side, they are likely expanded.
|
|
641
672
|
if (!hasXorPropMismatch( 'elements' ) && !hasXorPropMismatch( 'items' ) &&
|
|
642
|
-
|
|
673
|
+
!hasXorPropMismatch( 'target' ) && !hasXorPropMismatch( 'enum' )) {
|
|
643
674
|
// Element are already traversed via elements$ merging.
|
|
644
675
|
|
|
645
676
|
// only check items, if the specified one is not expanded/inferred
|
|
@@ -789,7 +820,7 @@ function resolve( model ) {
|
|
|
789
820
|
// This property is directly in the `value` property, but not part of `inferredElement`
|
|
790
821
|
// which has a `type` property, but no `enum`.
|
|
791
822
|
if (!inferredElement[prop] !== !specifiedElement[prop] &&
|
|
792
|
-
|
|
823
|
+
!inferredElement.value?.[prop] !== !specifiedElement[prop]) {
|
|
793
824
|
error( 'query-mismatched-element', [ specifiedElement.location, specifiedElement ], {
|
|
794
825
|
'#': specifiedElement[prop] ? 'extra' : 'missing', name: user.name.id, prop,
|
|
795
826
|
} );
|
|
@@ -812,31 +843,41 @@ function resolve( model ) {
|
|
|
812
843
|
return element.cardinality || ref?.[ref.length - 1]?.cardinality;
|
|
813
844
|
}
|
|
814
845
|
}
|
|
846
|
+
}
|
|
815
847
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
848
|
+
/**
|
|
849
|
+
* Issue warnings for restrictions concerning `localized`, i.e. for situations
|
|
850
|
+
* where a (later) inherited `localized` does not lead to the texts entity
|
|
851
|
+
* (element) being created, because the inherited info was not available then,
|
|
852
|
+
* or would involve more work (localized sub elements).
|
|
853
|
+
*/
|
|
854
|
+
function checkLocalizedElement( art ) {
|
|
855
|
+
const parent = art._parent;
|
|
856
|
+
if (!parent)
|
|
857
|
+
return; // with duplicate defs
|
|
858
|
+
const isSubElem = (parent.kind === 'element' || art._outer);
|
|
859
|
+
if (isSubElem) { // sub element or in MANY
|
|
860
|
+
// Localized sub elements in types, aspects, parameters and non-query
|
|
861
|
+
// entities are not problematic. They are just not really useful there →
|
|
862
|
+
// just report direct (not inherited) `localized` usage in non-inferred
|
|
863
|
+
// elements then. For non-query entities, always report.
|
|
864
|
+
if (art._main?.kind !== 'entity' || art._main?.query || userParam( parent )
|
|
865
|
+
? !art.$inferred && art.localized?.val && art._main?.kind !== 'annotation'
|
|
866
|
+
: getInheritedProp( art, 'localized' )) {
|
|
867
|
+
const loc = (art.localized || art.type || art.value)?.location || art.location;
|
|
868
|
+
warning( 'type-unsupported-localized', [ loc, art ], {},
|
|
869
|
+
'Localized sub elements are not supported' );
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
else if (parent.kind === 'entity' && !art._main?.query &&
|
|
873
|
+
art.$syntax !== 'calc' &&
|
|
874
|
+
getInheritedProp( art, 'localized' )?.val &&
|
|
875
|
+
// no inherited `localized` which wasn't known in generate.js
|
|
876
|
+
// TODO: should we set `localized` to null otherwise?
|
|
877
|
+
!hasTruthyProp( art, 'localized' )) {
|
|
878
|
+
const loc = (art.localized || art.type)?.location || art.location;
|
|
879
|
+
warning( 'type-missing-localized', [ loc, art ], { keyword: 'localized' },
|
|
880
|
+
'Add keyword $(KEYWORD), can\'t derive early enough that the element is localized' );
|
|
840
881
|
}
|
|
841
882
|
}
|
|
842
883
|
|
|
@@ -1132,6 +1173,12 @@ function resolve( model ) {
|
|
|
1132
1173
|
}
|
|
1133
1174
|
|
|
1134
1175
|
function addImplicitForeignKeys( art, obj, target ) {
|
|
1176
|
+
const max = art.cardinality?.targetMax;
|
|
1177
|
+
if (max && (typeof max.val !== 'number' || max.val > 1) &&
|
|
1178
|
+
!art.$inferred && !art.virtual?.val) {
|
|
1179
|
+
warning( 'assoc-incomplete-to-many', [ max.location, art ], null,
|
|
1180
|
+
'Provide an ON-condition or foreign keys to this to-many association' );
|
|
1181
|
+
}
|
|
1135
1182
|
obj.foreignKeys = Object.create( null );
|
|
1136
1183
|
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
1137
1184
|
if (elem.key?.val) {
|