@sap/cds-compiler 5.3.2 → 5.4.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 +23 -2
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +2 -2
- package/lib/api/options.js +4 -2
- package/lib/base/builtins.js +0 -10
- package/lib/base/keywords.js +3 -31
- package/lib/base/message-registry.js +23 -5
- package/lib/base/messages.js +1 -1
- package/lib/checks/existsMustEndInAssoc.js +7 -2
- package/lib/checks/foreignKeys.js +12 -7
- package/lib/compiler/assert-consistency.js +11 -3
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +88 -38
- package/lib/compiler/define.js +2 -2
- package/lib/compiler/shared.js +9 -10
- package/lib/compiler/xpr-rewrite.js +11 -0
- package/lib/compiler/xsn-model.js +1 -1
- package/lib/edm/csn2edm.js +2 -0
- package/lib/edm/edm.js +2 -1
- package/lib/edm/edmPreprocessor.js +14 -1
- package/lib/edm/edmUtils.js +17 -2
- package/lib/gen/BaseParser.js +291 -197
- package/lib/gen/CdlParser.js +1631 -1605
- package/lib/gen/Dictionary.json +74 -6
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1808 -1804
- package/lib/language/antlrParser.js +8 -4
- package/lib/language/genericAntlrParser.js +3 -3
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +4 -0
- package/lib/parsers/AstBuildingParser.js +172 -108
- package/lib/parsers/CdlGrammar.g4 +154 -134
- package/lib/parsers/Lexer.js +3 -3
- package/lib/parsers/identifiers.js +59 -0
- package/lib/render/toCdl.js +5 -5
- package/lib/render/utils/common.js +5 -0
- package/lib/render/utils/delta.js +23 -5
- package/lib/transform/db/expansion.js +2 -1
- package/lib/transform/db/transformExists.js +10 -9
- package/lib/transform/effective/annotations.js +147 -0
- package/lib/transform/effective/main.js +16 -2
- package/lib/transform/forOdata.js +53 -10
- package/lib/transform/forRelationalDB.js +7 -0
- package/lib/transform/odata/createForeignKeys.js +180 -0
- package/lib/transform/odata/flattening.js +135 -19
- package/lib/transform/odata/typesExposure.js +4 -3
- package/lib/transform/transformUtils.js +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@
|
|
|
7
7
|
Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
|
|
8
8
|
The compiler behavior concerning `beta` features can change at any time without notice.
|
|
9
9
|
|
|
10
|
+
## Version 5.4.0 - 2024-10-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- to.edm(x): `cds.Map` as empty open complex type with name `cds_Map` or if the definition
|
|
15
|
+
has been assigned `@open: false` as empty open complex type `cds_Map_closed` in OData V4.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Update OData vocabularies: 'Capabilities', 'Common', 'Core', 'PersonalData', 'PDF', 'UI'.
|
|
20
|
+
- to.cdl: Identifiers using non-ASCII unicode characters, as introduced in v4.4.0, are no longer quoted.
|
|
21
|
+
- For propagated expressions as annotation values, the `=` is changed as well, if it is a simple identifier.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- compiler: Some invalid CDL snippets could crash the parser and compiler.
|
|
26
|
+
- to.edm(x): OData V2: `Core.Links` watermark annotation has a `xmlns` attribute now.
|
|
27
|
+
- for.seal: Remove unapplied extensions from CSN.
|
|
28
|
+
- to.sql.migration: Handle `ALTER COLUMN` for columns with `NOT NULL` and a default value.
|
|
29
|
+
|
|
30
|
+
|
|
10
31
|
## Version 5.3.2 - 2024-10-08
|
|
11
32
|
|
|
12
33
|
### Fixed
|
|
@@ -19,8 +40,8 @@ The compiler behavior concerning `beta` features can change at any time without
|
|
|
19
40
|
### Added
|
|
20
41
|
|
|
21
42
|
- compiler:
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
+ A warning is emitted if a string enum's values are longer than the specified length.
|
|
44
|
+
+ ON-condition rewriting has been improved and now supports secondary associations.
|
|
24
45
|
- to.edm(x): Support optional action and function parameters in OData V4. The following rules apply:
|
|
25
46
|
+ A parameter declared `not null` without default value is mandatory.
|
|
26
47
|
+ A **function** parameter declared `null` without default value is mandatory.
|
package/bin/cdsc.js
CHANGED
|
@@ -370,7 +370,7 @@ async function executeCommandLine( command, options, args ) {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
function forSeal( model ) {
|
|
373
|
-
const features = [ 'remapOdataAnnotations' ];
|
|
373
|
+
const features = [ 'remapOdataAnnotations', 'deriveAnalyticalAnnotations' ];
|
|
374
374
|
for (const feature of features) {
|
|
375
375
|
if (options[feature]) // map to boolean equivalent
|
|
376
376
|
options[feature] = options[feature] === 'true';
|
package/doc/CHANGELOG_BETA.md
CHANGED
|
@@ -305,9 +305,9 @@ This is now the default - see CHANGELOG entry for 2.6.0
|
|
|
305
305
|
- `toSql`/`toHdbcds`: omit constraint generation if the option `skipDbConstraints` is set
|
|
306
306
|
- If the database constraints are switched off by the global option,
|
|
307
307
|
render constraints nevertheless if an association / composition
|
|
308
|
-
is annotated with `@cds.
|
|
308
|
+
is annotated with `@cds.persistence.assert.integrity: true`
|
|
309
309
|
- omit constraint generation if an association / composition
|
|
310
|
-
is annotated with `@cds.
|
|
310
|
+
is annotated with `@cds.persistence.assert.integrity: false`
|
|
311
311
|
-> for managed compositions, the `up_` link in the compositions target entity
|
|
312
312
|
will not result in a constraint if the composition is annotated as described
|
|
313
313
|
|
package/lib/api/options.js
CHANGED
|
@@ -54,6 +54,8 @@ const publicOptionsNewAPI = [
|
|
|
54
54
|
'resolveProjections',
|
|
55
55
|
'remapOdataAnnotations',
|
|
56
56
|
'keepLocalized',
|
|
57
|
+
// for.seal
|
|
58
|
+
'deriveAnalyticalAnnotations',
|
|
57
59
|
// to.sql.migration
|
|
58
60
|
'script',
|
|
59
61
|
];
|
|
@@ -210,7 +212,7 @@ module.exports = {
|
|
|
210
212
|
return translateOptions(options, defaultOptions, hardOptions, undefined, undefined, 'for.hana');
|
|
211
213
|
},
|
|
212
214
|
effective: (options) => {
|
|
213
|
-
const hardOptions = { addCdsPersistenceName: false };
|
|
215
|
+
const hardOptions = { addCdsPersistenceName: false, deriveAnalyticalAnnotations: false };
|
|
214
216
|
const defaultOptions = {
|
|
215
217
|
sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, remapOdataAnnotations: false, keepLocalized: false,
|
|
216
218
|
};
|
|
@@ -222,7 +224,7 @@ module.exports = {
|
|
|
222
224
|
const hardOptions = {
|
|
223
225
|
sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, keepLocalized: false, addCdsPersistenceName: true,
|
|
224
226
|
};
|
|
225
|
-
const defaultOptions = { remapOdataAnnotations: true };
|
|
227
|
+
const defaultOptions = { remapOdataAnnotations: true, deriveAnalyticalAnnotations: false };
|
|
226
228
|
const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
|
|
227
229
|
|
|
228
230
|
return Object.assign({}, processed);
|
package/lib/base/builtins.js
CHANGED
|
@@ -90,15 +90,6 @@ const xprInAnnoProperties = [
|
|
|
90
90
|
'cast',
|
|
91
91
|
];
|
|
92
92
|
|
|
93
|
-
/**
|
|
94
|
-
* Functions without parentheses in CDL (common standard SQL-92 functions)
|
|
95
|
-
* (do not add more - make it part of the SQL renderer to remove parentheses for
|
|
96
|
-
* other funny SQL functions like CURRENT_UTCTIMESTAMP).
|
|
97
|
-
*/
|
|
98
|
-
const functionsWithoutParens = [
|
|
99
|
-
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
|
100
|
-
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
101
|
-
];
|
|
102
93
|
|
|
103
94
|
/**
|
|
104
95
|
* Return whether JSON object `val` is a representation for an annotation expression
|
|
@@ -110,7 +101,6 @@ function isAnnotationExpression( val ) {
|
|
|
110
101
|
module.exports = {
|
|
111
102
|
propagationRules,
|
|
112
103
|
xprInAnnoProperties,
|
|
113
|
-
functionsWithoutParens,
|
|
114
104
|
isInReservedNamespace,
|
|
115
105
|
isBuiltinType,
|
|
116
106
|
isMagicVariable,
|
package/lib/base/keywords.js
CHANGED
|
@@ -1,40 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { functionsWithoutParentheses, cdlKeywords } = require('../parsers/identifiers');
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
|
|
7
|
-
// Keep in sync with reserved keywords in language.g4
|
|
8
|
-
cdl: [
|
|
9
|
-
'ALL',
|
|
10
|
-
'ANY',
|
|
11
|
-
'AS',
|
|
12
|
-
'BY',
|
|
13
|
-
'CASE',
|
|
14
|
-
'CAST',
|
|
15
|
-
'DISTINCT',
|
|
16
|
-
'EXISTS',
|
|
17
|
-
'EXTRACT',
|
|
18
|
-
'FALSE', // boolean
|
|
19
|
-
'FROM',
|
|
20
|
-
'IN',
|
|
21
|
-
'KEY',
|
|
22
|
-
'NEW',
|
|
23
|
-
'NOT',
|
|
24
|
-
'NULL',
|
|
25
|
-
'OF',
|
|
26
|
-
'ON',
|
|
27
|
-
'SELECT',
|
|
28
|
-
'SOME',
|
|
29
|
-
'TRIM',
|
|
30
|
-
'TRUE', // boolean
|
|
31
|
-
'WHEN',
|
|
32
|
-
'WHERE',
|
|
33
|
-
'WITH',
|
|
34
|
-
],
|
|
6
|
+
cdl: cdlKeywords,
|
|
35
7
|
// CDL functions, used for automatic quoting in 'toCdl' renderer,
|
|
36
8
|
// only relevant for element references of path length 1.
|
|
37
|
-
cdl_functions:
|
|
9
|
+
cdl_functions: functionsWithoutParentheses,
|
|
38
10
|
// SQLite keywords, used to warn in 'toSql' renderer with dialect 'sqlite'
|
|
39
11
|
// Taken from http://www.sqlite.org/draft/lang_keywords.html
|
|
40
12
|
// Better use keywords in tool/mkkeywordhash.c of a sqlite distribution.
|
|
@@ -533,6 +533,13 @@ const centralMessageTexts = {
|
|
|
533
533
|
annotation: 'Annotation definitions can\'t have calculated elements',
|
|
534
534
|
param: 'Parameters can\'t have calculated elements',
|
|
535
535
|
},
|
|
536
|
+
'def-invalid-name': {
|
|
537
|
+
std: 'The character \'.\' is not allowed in identifiers',
|
|
538
|
+
element: 'The character \'.\' is not allowed in element names',
|
|
539
|
+
param: 'The character \'.\' is not allowed in parameter names',
|
|
540
|
+
action: 'The character \'.\' is not allowed in bound action names',
|
|
541
|
+
function: 'The character \'.\' is not allowed in bound function names',
|
|
542
|
+
},
|
|
536
543
|
'ref-invalid-calc-elem': {
|
|
537
544
|
std: 'Can\'t include artifact with calculated element',
|
|
538
545
|
event: 'An event can\'t include an artifact with calculated elements',
|
|
@@ -579,6 +586,11 @@ const centralMessageTexts = {
|
|
|
579
586
|
exists: 'With $(NAME), path steps must not start with $(ID)',
|
|
580
587
|
'exists-filter': 'Unexpected $(ID) reference in filter of path $(ELEMREF) following “EXISTS” predicate',
|
|
581
588
|
},
|
|
589
|
+
'ref-unexpected-map': {
|
|
590
|
+
std: 'Unexpected reference to an element of type $(TYPE)', // unused
|
|
591
|
+
keys: 'Unexpected reference to an element of type $(TYPE) in foreign keys',
|
|
592
|
+
onCond: 'Unexpected reference to an element of type $(TYPE) in an ON-condition',
|
|
593
|
+
},
|
|
582
594
|
'ref-undefined-def': {
|
|
583
595
|
std: 'Artifact $(ART) has not been found',
|
|
584
596
|
// TODO: proposal 'No definition of $(NAME) found',
|
|
@@ -695,9 +707,10 @@ const centralMessageTexts = {
|
|
|
695
707
|
},
|
|
696
708
|
'ref-unsupported-type': {
|
|
697
709
|
std: 'Type $(TYPE) is not supported',
|
|
710
|
+
dialect: 'Type $(TYPE) is not supported for SQL dialect $(VALUE)',
|
|
698
711
|
hana: 'Type $(TYPE) is only supported for SQL dialect $(VALUE), not $(OTHERVALUE)',
|
|
699
712
|
hdbcds:'Type $(TYPE) is not supported in HDBCDS',
|
|
700
|
-
odata: 'Type $(TYPE) is not supported for OData'
|
|
713
|
+
odata: 'Type $(TYPE) is not supported for OData $(VERSION)'
|
|
701
714
|
},
|
|
702
715
|
'ref-unexpected-var': {
|
|
703
716
|
std: 'Variable $(NAME) can\'t be used here',
|
|
@@ -759,9 +772,10 @@ const centralMessageTexts = {
|
|
|
759
772
|
'type-unexpected-default': {
|
|
760
773
|
std: 'Unexpected $(KEYWORD) on an association/composition', // unused
|
|
761
774
|
multi: 'Unexpected $(KEYWORD); expected exactly one foreign key in combination with default value, but found $(COUNT)',
|
|
762
|
-
|
|
775
|
+
structuredKey: 'Unexpected $(KEYWORD) in combination with structured foreign key $(NAME); $(KEYWORD) requires a non-structured foreign key',
|
|
763
776
|
'onCond': 'Unexpected $(KEYWORD) on an association/composition with ON-condition; $(KEYWORD) requires exactly one foreign key',
|
|
764
|
-
'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
|
|
777
|
+
'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect',
|
|
778
|
+
map: 'Unexpected $(KEYWORD) for type $(TYPE)',
|
|
765
779
|
},
|
|
766
780
|
'type-expecting-service-target': {
|
|
767
781
|
std: 'Expecting service entity $(TARGET)',
|
|
@@ -823,6 +837,7 @@ const centralMessageTexts = {
|
|
|
823
837
|
virtual: 'Unexpected $(PROP) for virtual element',
|
|
824
838
|
// TODO: Better message?
|
|
825
839
|
include: '$(ART) can\'t have additional keys (through include)',
|
|
840
|
+
invalidType: 'Unexpected $(PROP) for element of type $(TYPE)',
|
|
826
841
|
},
|
|
827
842
|
'def-unexpected-localized': {
|
|
828
843
|
std: '$(ART) can\'t have localized elements',
|
|
@@ -890,7 +905,10 @@ const centralMessageTexts = {
|
|
|
890
905
|
},
|
|
891
906
|
|
|
892
907
|
'ref-expecting-$self': 'Use $(NEWCODE) instead of $(CODE) here or remove $(CODE) altogether if possible; the compiler has rewritten it to $(NEWCODE) in CSN',
|
|
893
|
-
'ref-expecting-assoc':
|
|
908
|
+
'ref-expecting-assoc': {
|
|
909
|
+
std: 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition',
|
|
910
|
+
'with-type': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
|
|
911
|
+
},
|
|
894
912
|
'ref-expecting-const': 'A constant expression or variable is expected here',
|
|
895
913
|
'ref-expecting-foreign-key': 'Expecting foreign key access after managed association $(NAME) in filter expression of $(ID), but found $(ALIAS)',
|
|
896
914
|
'ref-invalid-target': {
|
|
@@ -1038,7 +1056,7 @@ const centralMessageTexts = {
|
|
|
1038
1056
|
},
|
|
1039
1057
|
|
|
1040
1058
|
'type-invalid-cast': {
|
|
1041
|
-
std: '
|
|
1059
|
+
std: 'Can\'t cast to $(TYPE)',
|
|
1042
1060
|
'to-structure': 'Can\'t cast to a structured type',
|
|
1043
1061
|
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
1044
1062
|
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
package/lib/base/messages.js
CHANGED
|
@@ -1486,7 +1486,7 @@ function homeName( art, absoluteOnly ) {
|
|
|
1486
1486
|
return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
|
|
1487
1487
|
let main = art._main || art;
|
|
1488
1488
|
while (main._outer) // anonymous aspect
|
|
1489
|
-
main = main._outer._main;
|
|
1489
|
+
main = main._outer._main || main._outer; // w/o `_main` if wrongly in `type`
|
|
1490
1490
|
return (absoluteOnly) ? main.name.id : `${ main.kind }:${ artName( art ) }`;
|
|
1491
1491
|
}
|
|
1492
1492
|
|
|
@@ -14,8 +14,13 @@ function existsMustEndInAssoc( parent, prop, expression, path ) {
|
|
|
14
14
|
const next = expression[i + 1];
|
|
15
15
|
const { _art } = next;
|
|
16
16
|
const errorPath = path.concat([ prop, i ]);
|
|
17
|
-
if (!next.SELECT && !_art?.target)
|
|
18
|
-
this.error('ref-expecting-assoc', errorPath, {
|
|
17
|
+
if (!next.SELECT && !_art?.target) {
|
|
18
|
+
this.error('ref-expecting-assoc', errorPath, {
|
|
19
|
+
'#': _art.type ? 'with-type' : 'std',
|
|
20
|
+
elemref: next,
|
|
21
|
+
type: _art.type,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
}
|
|
@@ -51,13 +51,18 @@ function validateForeignKeys( member, memberName ) {
|
|
|
51
51
|
handleStructured(mem);
|
|
52
52
|
}
|
|
53
53
|
else if (mem.type) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
if (mem.type === 'cds.Map') {
|
|
55
|
+
this.error(null, member.$path, { type: mem.type }, 'Unexpected type $(TYPE) in foreign key');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const type = mem.type.ref
|
|
59
|
+
? this.artifactRef(mem.type)
|
|
60
|
+
: this.csn.definitions[mem.type];
|
|
61
|
+
if (type && !type.$visited) {
|
|
62
|
+
setProp(type, '$visited', true);
|
|
63
|
+
checkForItemsOrMissingType(type, memName);
|
|
64
|
+
delete type.$visited;
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
else if (mem && !mem.type && this.options.transformation !== 'odata') {
|
|
@@ -530,6 +530,8 @@ function assertConsistency( model, stage ) {
|
|
|
530
530
|
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
531
531
|
'args', 'op', 'func', 'suffix',
|
|
532
532
|
'$invalidPaths', '$parens',
|
|
533
|
+
// for invalid CDL (parser issues)
|
|
534
|
+
'orderBy',
|
|
533
535
|
],
|
|
534
536
|
// TODO: name requires if not in parser?
|
|
535
537
|
},
|
|
@@ -857,9 +859,11 @@ function assertConsistency( model, stage ) {
|
|
|
857
859
|
isObject( node, parent, prop, spec, idx );
|
|
858
860
|
|
|
859
861
|
// eslint-disable-next-line no-nested-ternary
|
|
860
|
-
const choice = (
|
|
861
|
-
? '
|
|
862
|
-
: (node.
|
|
862
|
+
const choice = (!noSyntaxErrors())
|
|
863
|
+
? 'none'
|
|
864
|
+
: (node.from !== undefined)
|
|
865
|
+
? 'select'
|
|
866
|
+
: 'union';
|
|
863
867
|
if (spec[choice])
|
|
864
868
|
assertProp( node, parent, prop, spec[choice], choice );
|
|
865
869
|
else
|
|
@@ -921,6 +925,10 @@ function assertConsistency( model, stage ) {
|
|
|
921
925
|
}
|
|
922
926
|
|
|
923
927
|
function expressionSpec( node ) {
|
|
928
|
+
// When a condition failure is ignored (TODO: we might test specifically
|
|
929
|
+
// against this), an expression could have properties for query clauses:
|
|
930
|
+
if (!noSyntaxErrors())
|
|
931
|
+
return 'none';
|
|
924
932
|
if (node.path)
|
|
925
933
|
return 'ref';
|
|
926
934
|
else if (node.literal || node.val)
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -33,6 +33,7 @@ const core = {
|
|
|
33
33
|
Boolean: { category: 'boolean' },
|
|
34
34
|
UUID: { category: 'string' },
|
|
35
35
|
Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
|
|
36
|
+
Map: { category: 'map' },
|
|
36
37
|
Association: { internal: true, category: 'relation' },
|
|
37
38
|
Composition: { internal: true, category: 'relation' },
|
|
38
39
|
};
|
|
@@ -336,6 +337,7 @@ const typeCategories = {
|
|
|
336
337
|
relation: [],
|
|
337
338
|
geo: [],
|
|
338
339
|
vector: [],
|
|
340
|
+
map: [],
|
|
339
341
|
};
|
|
340
342
|
// Fill type categories with `cds.*` types
|
|
341
343
|
Object.keys( core ).forEach( (type) => {
|
package/lib/compiler/checks.js
CHANGED
|
@@ -17,7 +17,6 @@ const {
|
|
|
17
17
|
forEachMemberRecursively,
|
|
18
18
|
isDeprecatedEnabled,
|
|
19
19
|
} = require('../base/model');
|
|
20
|
-
const { CompilerAssertion } = require('../base/error');
|
|
21
20
|
const { typeParameters } = require('./builtins');
|
|
22
21
|
const { propagationRules } = require('../base/builtins');
|
|
23
22
|
|
|
@@ -88,6 +87,7 @@ function check( model ) {
|
|
|
88
87
|
|
|
89
88
|
checkTypeStructure( art );
|
|
90
89
|
checkAssociation( art ); // type def could be assoc
|
|
90
|
+
checkDefaultValue( art );
|
|
91
91
|
if (art.kind === 'enum')
|
|
92
92
|
checkEnum( art );
|
|
93
93
|
checkEnumType( art );
|
|
@@ -108,22 +108,29 @@ function check( model ) {
|
|
|
108
108
|
forEachMember( member, m => checkMember( m, parentProps ) );
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function
|
|
112
|
-
const
|
|
111
|
+
function checkKey( elem, parentProps ) {
|
|
112
|
+
const key = parentProps.key || elem.key;
|
|
113
|
+
if (!key?.val || key?.$inferred)
|
|
114
|
+
return;
|
|
115
|
+
|
|
113
116
|
const isVirtual = parentProps.virtual?.val || elem.virtual?.val;
|
|
114
|
-
if (
|
|
117
|
+
if (isVirtual) {
|
|
115
118
|
error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
|
|
116
119
|
{ '#': 'virtual', prop: 'key' } );
|
|
117
120
|
}
|
|
121
|
+
else if (elem._effectiveType?.name?.id === 'cds.Map') {
|
|
122
|
+
error( 'def-unexpected-key', [ elem.type?.location || elem.location, elem ],
|
|
123
|
+
{ '#': 'invalidType', prop: 'key', type: 'cds.Map' } );
|
|
124
|
+
}
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
function checkElement( elem, parentProps ) {
|
|
121
128
|
checkLocalizedSubElement( elem );
|
|
122
|
-
|
|
129
|
+
checkKey( elem, parentProps );
|
|
123
130
|
checkLocalizedElement( elem );
|
|
124
131
|
|
|
125
132
|
if (elem.value) {
|
|
126
|
-
if (elem._main
|
|
133
|
+
if (elem._main?.query)
|
|
127
134
|
checkSelectItemValue( elem );
|
|
128
135
|
else if (elem.$syntax === 'calc')
|
|
129
136
|
checkCalculatedElementValue( elem );
|
|
@@ -141,8 +148,8 @@ function check( model ) {
|
|
|
141
148
|
// Maybe remove the check? But consider runtimes that rely on '.' as element separator.
|
|
142
149
|
if (construct.kind === 'element' || construct.kind === 'action' || construct.kind === 'param') {
|
|
143
150
|
if (construct.name.id?.includes( '.' )) {
|
|
144
|
-
error(
|
|
145
|
-
|
|
151
|
+
error( 'def-invalid-name', [ construct.name.location, construct ],
|
|
152
|
+
{ '#': construct.kind || 'std' } );
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
}
|
|
@@ -238,13 +245,15 @@ function check( model ) {
|
|
|
238
245
|
function checkTypeCast( xpr, user ) {
|
|
239
246
|
const isCast = (xpr.op?.val === 'cast');
|
|
240
247
|
const elem = isCast
|
|
241
|
-
? xpr.args[0]?._artifact
|
|
248
|
+
? xpr.args?.[0]?._artifact
|
|
242
249
|
: xpr._artifact;
|
|
243
250
|
const type = isCast ? xpr.type : user.type;
|
|
244
251
|
if (!isCast && type.$inferred)
|
|
245
252
|
return; // e.g. $inferred:'generated'
|
|
246
253
|
if (elem && type) { // has explicit type
|
|
247
|
-
if (type._artifact?.
|
|
254
|
+
if (type._artifact?._effectiveType?.name.id === 'cds.Map')
|
|
255
|
+
error( 'type-invalid-cast', [ type.location, user ], { '#': 'std', type: 'cds.Map' } );
|
|
256
|
+
else if (type._artifact?.elements)
|
|
248
257
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'to-structure' } );
|
|
249
258
|
else if (elem.elements) // TODO: calc elements
|
|
250
259
|
error( 'type-invalid-cast', [ type.location, user ], { '#': 'from-structure' } );
|
|
@@ -259,8 +268,14 @@ function check( model ) {
|
|
|
259
268
|
function checkLocalizedElement( elem ) {
|
|
260
269
|
if (elem.localized?.val) {
|
|
261
270
|
const type = elem._effectiveType;
|
|
262
|
-
//
|
|
263
|
-
if (
|
|
271
|
+
// TODO(v6): Also for ` || type?.elements`; (#13154)
|
|
272
|
+
if (type?.category === 'map') {
|
|
273
|
+
error( 'ref-unexpected-localized-map', [ elem.type?.location, elem ],
|
|
274
|
+
{ keyword: 'localized' },
|
|
275
|
+
'Map types can\'t be used with $(KEYWORD)' );
|
|
276
|
+
}
|
|
277
|
+
else if (!type || !type.builtin || type.category !== 'string') {
|
|
278
|
+
// See discussion issue #6520: should we allow all scalar types?
|
|
264
279
|
info( 'ref-expecting-localized-string', [ elem.type?.location, elem ],
|
|
265
280
|
{ keyword: 'localized' },
|
|
266
281
|
'Expecting a string type in combination with keyword $(KEYWORD)' );
|
|
@@ -344,12 +359,22 @@ function check( model ) {
|
|
|
344
359
|
if (!type || type.enum)
|
|
345
360
|
return;
|
|
346
361
|
|
|
347
|
-
// All builtin types are allowed except binary and relational types.
|
|
362
|
+
// All builtin types are allowed except binary, structured (Map), and relational types.
|
|
348
363
|
// The latter are "internal" types.
|
|
349
|
-
|
|
364
|
+
// Structures/Arrays are not allowed.
|
|
365
|
+
// TODO(v6): Reverse coding: use allow-list approach; don't forget about geo, etc.
|
|
366
|
+
const invalidEnumBuiltins = {
|
|
367
|
+
__proto__: null,
|
|
368
|
+
structure: 'struct',
|
|
369
|
+
binary: 'binary',
|
|
370
|
+
relation: 'relation',
|
|
371
|
+
vector: 'vector',
|
|
372
|
+
map: 'map',
|
|
373
|
+
};
|
|
374
|
+
if (!type.builtin || type.internal || type.category in invalidEnumBuiltins) {
|
|
350
375
|
let typeClass = 'std';
|
|
351
|
-
if (type.category
|
|
352
|
-
typeClass = type.category;
|
|
376
|
+
if (type.category in invalidEnumBuiltins)
|
|
377
|
+
typeClass = invalidEnumBuiltins[type.category];
|
|
353
378
|
else if (type.elements)
|
|
354
379
|
typeClass = 'struct';
|
|
355
380
|
else if (type.items)
|
|
@@ -360,7 +385,9 @@ function check( model ) {
|
|
|
360
385
|
binary: 'Binary types are not allowed as enums',
|
|
361
386
|
relation: 'Relational types are not allowed as enums',
|
|
362
387
|
struct: 'Structured types are not allowed as enums',
|
|
388
|
+
vector: 'Vector types are not allowed as enums',
|
|
363
389
|
items: 'Arrayed types are not allowed as enums',
|
|
390
|
+
map: 'Map types are not allowed as enums',
|
|
364
391
|
} );
|
|
365
392
|
return;
|
|
366
393
|
}
|
|
@@ -470,12 +497,14 @@ function check( model ) {
|
|
|
470
497
|
const isLocalizedSubElement = element.localized?.val;
|
|
471
498
|
if (isLocalizedSubElement || element.type?._artifact?.localized?.val) {
|
|
472
499
|
const loc = isLocalizedSubElement ? element.localized.location : element.type.location;
|
|
473
|
-
warning( 'localized-sub-element', [ loc, element ],
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
500
|
+
warning( 'localized-sub-element', [ loc, element ], {
|
|
501
|
+
type: element.type?._artifact || '',
|
|
502
|
+
'#': isLocalizedSubElement ? 'std' : 'type',
|
|
503
|
+
keyword: 'localized',
|
|
504
|
+
}, {
|
|
505
|
+
std: 'Keyword $(KEYWORD) is ignored for sub elements',
|
|
506
|
+
type: 'Keyword $(KEYWORD) in type $(TYPE) is ignored for sub elements',
|
|
507
|
+
} );
|
|
479
508
|
}
|
|
480
509
|
}
|
|
481
510
|
|
|
@@ -524,11 +553,14 @@ function check( model ) {
|
|
|
524
553
|
if (elem.foreignKeys) {
|
|
525
554
|
for (const k in elem.foreignKeys) {
|
|
526
555
|
++fkCount;
|
|
556
|
+
// Note: If the foreign key is structured, we don't check its elements!
|
|
527
557
|
const key = elem.foreignKeys[k].targetElement;
|
|
528
558
|
if (key && isVirtualElement( key._artifact ))
|
|
529
559
|
error( 'ref-unexpected-virtual', [ key.location, elem ], { '#': 'fkey' } );
|
|
530
560
|
else if (key._artifact?.$syntax === 'calc' && !key._artifact.value.stored?.val)
|
|
531
561
|
error( 'ref-unexpected-calculated', [ key.location, elem ], { '#': 'fkey' } );
|
|
562
|
+
else if (key._artifact?._effectiveType?.name.id === 'cds.Map')
|
|
563
|
+
error( 'ref-unexpected-map', [ key.location, elem ], { '#': 'keys', type: 'cds.Map' } );
|
|
532
564
|
}
|
|
533
565
|
}
|
|
534
566
|
if (elem.default?.val !== undefined) {
|
|
@@ -542,7 +574,7 @@ function check( model ) {
|
|
|
542
574
|
const fkName = Object.keys( elem.foreignKeys )[0];
|
|
543
575
|
if (elem.foreignKeys[fkName].targetElement._artifact?._effectiveType?.elements) {
|
|
544
576
|
error( 'type-unexpected-default', [ elem.default.location, elem ], {
|
|
545
|
-
'#': '
|
|
577
|
+
'#': 'structuredKey', keyword: 'default', name: fkName,
|
|
546
578
|
} );
|
|
547
579
|
}
|
|
548
580
|
}
|
|
@@ -551,6 +583,20 @@ function check( model ) {
|
|
|
551
583
|
checkOnCondition( elem );
|
|
552
584
|
}
|
|
553
585
|
|
|
586
|
+
function checkDefaultValue( art ) {
|
|
587
|
+
if (!art.default || !art._effectiveType)
|
|
588
|
+
return;
|
|
589
|
+
// TODO(v6): Also reject default for structures (#13154)
|
|
590
|
+
const isStructured = art._effectiveType?.name.id === 'cds.Map';
|
|
591
|
+
if (!isStructured)
|
|
592
|
+
return;
|
|
593
|
+
if (art.default?.val !== undefined) {
|
|
594
|
+
error( 'type-unexpected-default', [ art.default.location, art ], {
|
|
595
|
+
'#': 'map', keyword: 'default', type: 'cds.Map',
|
|
596
|
+
} );
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
554
600
|
function getBinaryOp( cond ) {
|
|
555
601
|
const { op, args } = cond;
|
|
556
602
|
return op?.val === 'ixpr' && args?.length === 3 && args[1].literal === 'token' &&
|
|
@@ -579,6 +625,7 @@ function check( model ) {
|
|
|
579
625
|
'Used type $(TYPE) is not arrayed and conflicts with $(PROP) property' );
|
|
580
626
|
}
|
|
581
627
|
else if (artifact.elements && !finalType.elements) {
|
|
628
|
+
// TODO: Handle cds.Map!
|
|
582
629
|
warning( 'type-elements-mismatch', [ artifact.type.location, artifact ],
|
|
583
630
|
{ type: artifact.type, prop: 'elements' },
|
|
584
631
|
'Used type $(TYPE) is not structured and conflicts with $(PROP) property' );
|
|
@@ -686,18 +733,22 @@ function check( model ) {
|
|
|
686
733
|
function checkOnCondition( elem ) {
|
|
687
734
|
if (elem.$inferred === 'localized')
|
|
688
735
|
return; // ignore
|
|
736
|
+
if (!elem.on || elem.on.$inferred)
|
|
737
|
+
return;
|
|
689
738
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
739
|
+
visitExpression( elem.on, elem, (xpr, user) => {
|
|
740
|
+
checkExpressionNotVirtual( xpr, user );
|
|
741
|
+
checkExpressionAssociationUsage( xpr, user, true );
|
|
742
|
+
|
|
743
|
+
if (xpr._artifact?._effectiveType?.name.id === 'cds.Map') {
|
|
744
|
+
error( 'ref-unexpected-map', [ xpr.location, user ], { '#': 'onCond', type: 'cds.Map' } );
|
|
745
|
+
}
|
|
746
|
+
else if (xpr._artifact?.$syntax === 'calc' && !xpr._artifact.value.stored?.val) {
|
|
695
747
|
// Essential check. Dependency handling for `on` conditions must change if
|
|
696
748
|
// this is allowed. See test3/Associations/Dependencies/.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
749
|
+
error('ref-unexpected-calculated', [ xpr.location, user ], { '#': 'on' });
|
|
750
|
+
}
|
|
751
|
+
} );
|
|
701
752
|
}
|
|
702
753
|
|
|
703
754
|
function checkSelectItemValue( elem ) {
|
|
@@ -969,13 +1020,11 @@ function check( model ) {
|
|
|
969
1020
|
return;
|
|
970
1021
|
}
|
|
971
1022
|
|
|
972
|
-
// Sanity checks
|
|
973
1023
|
if (!elementDecl._effectiveType)
|
|
974
|
-
|
|
975
|
-
|
|
1024
|
+
return; // type resolution error
|
|
976
1025
|
|
|
977
1026
|
// Must have literal or path unless it is a boolean
|
|
978
|
-
if (!anno.literal && !anno.path && elementDecl._effectiveType
|
|
1027
|
+
if (!anno.literal && !anno.path && elementDecl._effectiveType.category !== 'boolean') {
|
|
979
1028
|
if (elementDecl.type?._artifact) {
|
|
980
1029
|
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
|
|
981
1030
|
{ '#': 'type', type: elementDecl.type._artifact } );
|
|
@@ -1089,7 +1138,8 @@ function check( model ) {
|
|
|
1089
1138
|
// TODO: complain at definition instead
|
|
1090
1139
|
}
|
|
1091
1140
|
else if (!type.enum) {
|
|
1092
|
-
|
|
1141
|
+
// type error somewhere; ignore
|
|
1142
|
+
return;
|
|
1093
1143
|
}
|
|
1094
1144
|
|
|
1095
1145
|
// Check enums
|
|
@@ -1167,7 +1217,7 @@ function checkSapCommonTextsAspects( model ) {
|
|
|
1167
1217
|
if (locale._effectiveType !== model.definitions['cds.String']) {
|
|
1168
1218
|
const hasCommonLocale = !!model.definitions['sap.common.Locale'];
|
|
1169
1219
|
const { error } = model.$messageFunctions;
|
|
1170
|
-
error( 'def-invalid-element-type', [ locale.type.location, locale ], {
|
|
1220
|
+
error( 'def-invalid-element-type', [ (locale.type || locale.name).location, locale ], {
|
|
1171
1221
|
'#': hasCommonLocale ? 'texts-aspect-locale' : 'std',
|
|
1172
1222
|
art: name,
|
|
1173
1223
|
elemref: 'locale',
|
package/lib/compiler/define.js
CHANGED
|
@@ -343,8 +343,8 @@ function define( model ) {
|
|
|
343
343
|
decl.usings.forEach( u => addUsing( u, src ) );
|
|
344
344
|
return;
|
|
345
345
|
}
|
|
346
|
-
const
|
|
347
|
-
if (path.broken || !path[0]) // syntax error
|
|
346
|
+
const path = decl.extern?.path;
|
|
347
|
+
if (!path || path.broken || !path[0]) // syntax error
|
|
348
348
|
return;
|
|
349
349
|
decl.extern.id = pathName( path );
|
|
350
350
|
if (!decl.name)
|