@sap/cds-compiler 4.8.0 → 4.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 +29 -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 +14 -1
- 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 +32 -19
- package/lib/base/messages.js +50 -19
- 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 +8 -2
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +34 -22
- 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/propagator.js +21 -18
- package/lib/compiler/resolve.js +44 -28
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +13 -88
- package/lib/compiler/xpr-rewrite.js +689 -0
- package/lib/edm/annotations/genericTranslation.js +80 -60
- 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 +10 -11
- 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 +12 -7
- 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 +1 -1
- package/lib/transform/db/expansion.js +8 -3
- 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 +1 -1
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +189 -106
- 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 +40 -37
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -16
- 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/api/options.js
CHANGED
|
@@ -52,6 +52,8 @@ const publicOptionsNewAPI = [
|
|
|
52
52
|
// for.effective
|
|
53
53
|
'resolveSimpleTypes',
|
|
54
54
|
'resolveProjections',
|
|
55
|
+
'remapOdataAnnotations',
|
|
56
|
+
'keepLocalized',
|
|
55
57
|
];
|
|
56
58
|
|
|
57
59
|
// Internal options used for testing/debugging etc.
|
|
@@ -214,12 +216,21 @@ module.exports = {
|
|
|
214
216
|
effective: (options) => {
|
|
215
217
|
const hardOptions = {};
|
|
216
218
|
const defaultOptions = {
|
|
217
|
-
sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true,
|
|
219
|
+
sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, remapOdataAnnotations: false, keepLocalized: false,
|
|
218
220
|
};
|
|
219
221
|
const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
|
|
220
222
|
|
|
221
223
|
return Object.assign({}, processed);
|
|
222
224
|
},
|
|
225
|
+
seal: (options) => {
|
|
226
|
+
const hardOptions = {
|
|
227
|
+
sqlMapping: 'plain', resolveSimpleTypes: true, resolveProjections: true, keepLocalized: false,
|
|
228
|
+
};
|
|
229
|
+
const defaultOptions = { remapOdataAnnotations: true };
|
|
230
|
+
const processed = translateOptions(options, defaultOptions, hardOptions, null, [ 'sql-dialect-and-naming' ], 'for.effective');
|
|
231
|
+
|
|
232
|
+
return Object.assign({}, processed);
|
|
233
|
+
},
|
|
223
234
|
java: options => translateOptions(options, { sqlMapping: 'plain' }, {}, undefined, undefined, 'for.java'),
|
|
224
235
|
},
|
|
225
236
|
overallOptions, // exported for testing
|
package/lib/api/validate.js
CHANGED
|
@@ -140,17 +140,13 @@ const allCombinationValidators = {
|
|
|
140
140
|
if (options.sqlDialect && options.sqlMapping && options.sqlDialect !== 'hana' && [ 'quoted', 'hdbcds' ].includes(options.sqlMapping))
|
|
141
141
|
message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-naming', name: options.sqlDialect, prop: options.sqlMapping });
|
|
142
142
|
},
|
|
143
|
-
'sql-dialect-and-localized': (options, message) => {
|
|
144
|
-
if (options.fewerLocalizedViews && options.sqlDialect === 'hana' && (options.withHanaAssociations || options.withHanaAssociations === undefined))
|
|
145
|
-
message.error('api-invalid-combination', null, { '#': 'sql-dialect-and-localized', option: 'fewerLocalizedViews', value: 'hana' });
|
|
146
|
-
},
|
|
147
143
|
'beta-no-test': (options, message) => {
|
|
148
144
|
if (options.beta && !options.testMode)
|
|
149
145
|
message.warning('api-unexpected-combination', null, { '#': 'beta-no-test', option: 'beta' });
|
|
150
146
|
},
|
|
151
147
|
};
|
|
152
148
|
|
|
153
|
-
const alwaysRunValidators = [ 'beta-no-test'
|
|
149
|
+
const alwaysRunValidators = [ 'beta-no-test' ];
|
|
154
150
|
|
|
155
151
|
/**
|
|
156
152
|
* Run the validations for each option.
|
package/lib/base/builtins.js
CHANGED
|
@@ -4,6 +4,32 @@
|
|
|
4
4
|
// It should not contain any specific to XSN, i.e. neither XSN structures
|
|
5
5
|
// nor any other XSN properties.
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Map of propagation rules for annotations.
|
|
9
|
+
* Does not include rules for standard propagation of annotations or other properties.
|
|
10
|
+
*
|
|
11
|
+
* @type {Record<string, string>}
|
|
12
|
+
*/
|
|
13
|
+
const propagationRules = {
|
|
14
|
+
__proto__: null,
|
|
15
|
+
'@Analytics.hidden': 'never',
|
|
16
|
+
'@Analytics.visible': 'never',
|
|
17
|
+
'@cds.autoexpose': 'onlyViaArtifact',
|
|
18
|
+
'@cds.autoexposed': 'never',
|
|
19
|
+
'@cds.external': 'never',
|
|
20
|
+
'@cds.persistence.calcview': 'never',
|
|
21
|
+
'@cds.persistence.exists': 'never',
|
|
22
|
+
'@cds.persistence.skip': 'notWithPersistenceTable',
|
|
23
|
+
'@cds.persistence.table': 'never',
|
|
24
|
+
'@cds.persistence.udf': 'never',
|
|
25
|
+
'@cds.redirection.target': 'never',
|
|
26
|
+
'@com.sap.gtt.core.CoreModel.Indexable': 'never',
|
|
27
|
+
'@fiori.draft.enabled': 'onlyViaArtifact',
|
|
28
|
+
'@sql.append': 'never',
|
|
29
|
+
'@sql.prepend': 'never',
|
|
30
|
+
'@sql.replace': 'never',
|
|
31
|
+
}
|
|
32
|
+
|
|
7
33
|
/**
|
|
8
34
|
* Checks whether the given absolute path is inside a reserved namespace.
|
|
9
35
|
*
|
|
@@ -80,6 +106,7 @@ function isAnnotationExpression( val ) {
|
|
|
80
106
|
}
|
|
81
107
|
|
|
82
108
|
module.exports = {
|
|
109
|
+
propagationRules,
|
|
83
110
|
xprInAnnoProperties,
|
|
84
111
|
functionsWithoutParens,
|
|
85
112
|
isInReservedNamespace,
|
|
@@ -64,8 +64,10 @@ const centralMessages = {
|
|
|
64
64
|
'ext-undefined-param': { severity: 'Warning' },
|
|
65
65
|
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'deprecated' },
|
|
66
66
|
'anno-unexpected-localized-skip': { severity: 'Error', configurableFor: true },
|
|
67
|
+
'anno-unexpected-mixin': { severity: 'Warning', errorFor: [ 'v5' ] },
|
|
67
68
|
|
|
68
69
|
'name-invalid-dollar-alias': { severity: 'Error', configurableFor: true },
|
|
70
|
+
'name-deprecated-$self': { severity: 'Warning', errorFor: [ 'v5' ], configurableFor: true },
|
|
69
71
|
|
|
70
72
|
'type-invalid-items': { severity: 'Error' }, // not supported yet
|
|
71
73
|
'assoc-as-type': { severity: 'Error' }, // TODO: allow more, but not all
|
|
@@ -84,6 +86,7 @@ const centralMessages = {
|
|
|
84
86
|
'empty-type': { severity: 'Info' }, // only still an error in old transformers
|
|
85
87
|
|
|
86
88
|
'ref-deprecated-orderby': { severity: 'Error', configurableFor: true },
|
|
89
|
+
'ref-deprecated-self-element': { severity: 'Warning', configurableFor: true, errorFor: [ 'v5' ] },
|
|
87
90
|
'ref-invalid-type': { severity: 'Error' },
|
|
88
91
|
'ref-unexpected-self': { severity: 'Error' },
|
|
89
92
|
'ref-invalid-include': { severity: 'Error' },
|
|
@@ -159,6 +162,7 @@ const centralMessages = {
|
|
|
159
162
|
'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
|
|
160
163
|
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'deprecated' },
|
|
161
164
|
'syntax-unexpected-sql-clause': { severity: 'Error' }, // TODO: configurableFor:'tests'?
|
|
165
|
+
'syntax-ignoring-anno': { severity: 'Warning', errorFor: [ 'v5' ] },
|
|
162
166
|
|
|
163
167
|
'type-unsupported-precision-change': { severity: 'Error' },
|
|
164
168
|
'type-unsupported-key-change': { severity: 'Error', configurableFor: true },
|
|
@@ -230,6 +234,9 @@ for (const oldName in oldMessageIds) {
|
|
|
230
234
|
// If you change it, keep in sync with scripts/eslint/rules/message-text.js
|
|
231
235
|
|
|
232
236
|
const centralMessageTexts = {
|
|
237
|
+
'api-deprecated-v5': {
|
|
238
|
+
std: 'Support for generating hdbcds output is deprecated with @sap/cds-compiler v5 and later'
|
|
239
|
+
},
|
|
233
240
|
'api-invalid-option': {
|
|
234
241
|
std: 'Invalid option $(NAME)!',
|
|
235
242
|
deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
|
|
@@ -251,7 +258,6 @@ const centralMessageTexts = {
|
|
|
251
258
|
std: 'Invalid option combination found: $(OPTION) and $(PROP)', // unused
|
|
252
259
|
'valid-structured': 'Structured OData is only supported with OData version v4',
|
|
253
260
|
'sql-dialect-and-naming': 'sqlDialect $(NAME) can\'t be combined with sqlMapping $(PROP)',
|
|
254
|
-
'sql-dialect-and-localized': 'Option $(OPTION) can\'t be combined with SQL dialect $(VALUE) or the to.hdi()/to.hdbcds() backend',
|
|
255
261
|
'tenant-and-naming': 'Option $(OPTION) can\'t be combined with sqlMapping $(PROP) - expected sqlMapping $(VALUE)'
|
|
256
262
|
},
|
|
257
263
|
'api-unexpected-combination': {
|
|
@@ -284,6 +290,7 @@ const centralMessageTexts = {
|
|
|
284
290
|
'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO)',
|
|
285
291
|
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
|
|
286
292
|
'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
|
|
293
|
+
'anno-unexpected-mixin': 'Unexpected annotation on mixin definition',
|
|
287
294
|
|
|
288
295
|
'anno-unexpected-localized-skip': {
|
|
289
296
|
std: 'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped',
|
|
@@ -291,8 +298,8 @@ const centralMessageTexts = {
|
|
|
291
298
|
},
|
|
292
299
|
|
|
293
300
|
'anno-missing-rewrite': {
|
|
294
|
-
std: 'Assign a value for $(ANNO)
|
|
295
|
-
|
|
301
|
+
std: 'Assign a value for $(ANNO); the value inherited from $(ART) would contain invalid or unrelated references like $(ELEMREF)',
|
|
302
|
+
unsupported: 'Assign a value for $(ANNO); the value inherited from $(ART) can\'t be rewritten due to unsupported $(ELEMREF)',
|
|
296
303
|
},
|
|
297
304
|
|
|
298
305
|
'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
|
|
@@ -568,13 +575,17 @@ const centralMessageTexts = {
|
|
|
568
575
|
entity: 'Entity $(ART) has no parameter $(ID)',
|
|
569
576
|
action: 'Action $(ART) has no parameter $(ID)',
|
|
570
577
|
},
|
|
571
|
-
'ref-undefined-art':
|
|
578
|
+
'ref-undefined-art': {
|
|
579
|
+
std: 'No artifact has been found with name $(ART)',
|
|
580
|
+
namespace: 'No artifact has been found with name $(ART) which can be extended with annotations',
|
|
581
|
+
localized: 'Can\'t extend localized definitions, only annotate them using an $(KEYWORD) statement',
|
|
582
|
+
},
|
|
572
583
|
// TODO: proposal 'No definition found for $(NAME)',
|
|
573
584
|
'ref-undefined-element': {
|
|
574
585
|
std: 'Element $(ART) has not been found',
|
|
575
586
|
element: 'Artifact $(ART) has no element $(MEMBER)',
|
|
576
587
|
target: 'Target entity $(ART) has no element $(ID)',
|
|
577
|
-
aspect: 'Element $(ID) has not been found in the anonymous target aspect',
|
|
588
|
+
aspect: 'Element $(ID) has not been found in the anonymous target aspect',
|
|
578
589
|
query: 'The current query has no element $(ART)',
|
|
579
590
|
alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
|
|
580
591
|
virtual: 'Element $(ART) has not been found. Use $(CODE) to add virtual elements in queries'
|
|
@@ -585,6 +596,7 @@ const centralMessageTexts = {
|
|
|
585
596
|
target: 'Target entity $(ART) has no element $(ID)',
|
|
586
597
|
query: 'The current query has no element $(ART)',
|
|
587
598
|
alias: 'Element $(ART) has not been found in the sub query for alias $(ALIAS)',
|
|
599
|
+
aspect: 'Element $(ID) has not been found in the anonymous target aspect',
|
|
588
600
|
},
|
|
589
601
|
'ref-undefined-var': {
|
|
590
602
|
std: 'Variable $(ID) has not been found',
|
|
@@ -721,7 +733,7 @@ const centralMessageTexts = {
|
|
|
721
733
|
'targetAspect': 'Unexpected $(KEYWORD) on composition of aspect'
|
|
722
734
|
},
|
|
723
735
|
|
|
724
|
-
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
|
|
736
|
+
'anno-builtin': 'Builtin types should not be annotated nor extended. Use custom type instead',
|
|
725
737
|
'ext-undefined-def': 'Artifact $(ART) has not been found',
|
|
726
738
|
'ext-undefined-art': 'No artifact has been found with name $(ART)',
|
|
727
739
|
'ext-undefined-element': {
|
|
@@ -907,8 +919,8 @@ const centralMessageTexts = {
|
|
|
907
919
|
},
|
|
908
920
|
|
|
909
921
|
'query-undefined-element': {
|
|
910
|
-
std: 'Target $(TARGET) of $(NAME) is missing element $(ID);
|
|
911
|
-
redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID);
|
|
922
|
+
std: 'Target $(TARGET) of $(NAME) is missing element $(ID); use $(KEYWORD) with an explicit ON-condition',
|
|
923
|
+
redirected: 'Target $(TARGET) of $(NAME) is missing element $(ID); add an ON-condition to $(KEYWORD)',
|
|
912
924
|
},
|
|
913
925
|
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
|
|
914
926
|
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
|
|
@@ -1032,10 +1044,10 @@ const centralMessageTexts = {
|
|
|
1032
1044
|
'odata-spec-violation-assoc': 'Unexpected association in structured type for OData $(VERSION)',
|
|
1033
1045
|
'odata-spec-violation-constraints': 'Partial referential constraints produced for OData $(VERSION)',
|
|
1034
1046
|
'odata-spec-violation-id': {
|
|
1035
|
-
std: 'Expected EDM name $(ID) to start with
|
|
1047
|
+
std: 'Expected EDM name $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
|
|
1036
1048
|
'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
|
|
1037
|
-
'qualifier': 'Expected annotation qualifier $(ID) to start with
|
|
1038
|
-
'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with
|
|
1049
|
+
'qualifier': 'Expected annotation qualifier $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
|
|
1050
|
+
'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with an alphabetic character or underscore, followed by a maximum of 127 alphabetic characters, digits, or underscores',
|
|
1039
1051
|
},
|
|
1040
1052
|
// version independent messages
|
|
1041
1053
|
'odata-spec-violation-key-array': {
|
|
@@ -1051,7 +1063,7 @@ const centralMessageTexts = {
|
|
|
1051
1063
|
scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
|
|
1052
1064
|
},
|
|
1053
1065
|
'odata-spec-violation-no-key': 'Expected entity to have a primary key',
|
|
1054
|
-
'odata-spec-violation-type-unknown': 'Unknown
|
|
1066
|
+
'odata-spec-violation-type-unknown': 'Unknown EDM Type $(TYPE)',
|
|
1055
1067
|
'odata-spec-violation-type': {
|
|
1056
1068
|
std: 'Expected element to have a type',
|
|
1057
1069
|
incompatible: 'Unexpected EDM Type $(TYPE) for OData $(VERSION)',
|
|
@@ -1148,7 +1160,7 @@ const centralMessageTexts = {
|
|
|
1148
1160
|
'odata-anno-xpr': {
|
|
1149
1161
|
'std': 'unused',
|
|
1150
1162
|
'notadynexpr': '$(OP) is not a renderable dynamic expression in $(ANNO)',
|
|
1151
|
-
'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO),
|
|
1163
|
+
'use': 'Function $(OP) is not a renderable dynamic expression in $(ANNO), use $(CODE) instead',
|
|
1152
1164
|
'canonfuncalias': 'Expected function name $(CODE) to be of the form $(META).$(OTHERMETA) for $(OP) in $(ANNO)',
|
|
1153
1165
|
'unexpected': 'Unexpected expression in $(ANNO)',
|
|
1154
1166
|
}
|
|
@@ -1169,14 +1181,15 @@ const centralMessageTexts = {
|
|
|
1169
1181
|
'odata-anno-xpr-ref': {
|
|
1170
1182
|
'std': '$(ANNO) can\'t be propagated to $(NAME) because path $(ELEMREF) is not resolvable via type reference $(CODE)',
|
|
1171
1183
|
'args': 'Unexpected arguments or filters in $(ELEMREF) in $(ANNO)',
|
|
1172
|
-
'flatten_builtin': 'Expected path $(ELEMREF) in $(ANNO) to end
|
|
1173
|
-
'flatten_builtin_type': 'Expected path $(ELEMREF) in $(ANNO) to end
|
|
1184
|
+
'flatten_builtin': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening $(NAME)',
|
|
1185
|
+
'flatten_builtin_type': 'Expected path $(ELEMREF) in $(ANNO) to end in a scalar typed leaf element while flattening',
|
|
1174
1186
|
'invalid': 'Invalid path $(ELEMREF) in $(ANNO)',
|
|
1175
1187
|
// genericTranslation
|
|
1176
|
-
'notaparam': 'Element path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a parameter entity',
|
|
1177
|
-
'notaneelement': 'Parameter path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a type entity',
|
|
1178
|
-
'notrendered': 'Path step $(COUNT) of $(ELEMREF) in $(ANNO) refers to an unrendered
|
|
1179
|
-
'
|
|
1188
|
+
'notaparam': 'EDM Element path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a parameter entity',
|
|
1189
|
+
'notaneelement': 'EDM Parameter path $(ELEMREF) can\'t be used in $(ANNO) which is applied to a type entity',
|
|
1190
|
+
'notrendered': 'EDM Path step $(COUNT) of $(ELEMREF) in $(ANNO) refers to an unrendered property in the OData API',
|
|
1191
|
+
'magic': 'Unexpected magic variable $(ELEMREF) in $(ANNO)',
|
|
1192
|
+
'bparam_v2': 'Unexpected explicit binding parameter path $(ELEMREF) for OData $(VERSION) in $(ANNO)',
|
|
1180
1193
|
},
|
|
1181
1194
|
// -----------------------------------------------------------------------------------
|
|
1182
1195
|
// OData Message section ends here, no messages below this line
|
package/lib/base/messages.js
CHANGED
|
@@ -82,6 +82,23 @@ function isDowngradable( messageId, moduleName, options ) {
|
|
|
82
82
|
: configurableFor && (configurableFor !== 'deprecated' || isDeprecatedEnabled( options, 'downgradableErrors' ));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Returns a marker for messages strings indicating whether the message can be downgraded
|
|
87
|
+
* or whether it will be an error in the next cds-compiler release.
|
|
88
|
+
*
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function severityChangeMarker(msg, config) {
|
|
92
|
+
const severity = msg.severity || 'Error';
|
|
93
|
+
if (config.moduleForMarker) {
|
|
94
|
+
if (severity === 'Error' && isDowngradable(msg.messageId, config.moduleForMarker, {}))
|
|
95
|
+
return '‹↓›';
|
|
96
|
+
if (msg.messageId && centralMessages[msg.messageId]?.errorFor?.includes('v5'))
|
|
97
|
+
return '‹↑›';
|
|
98
|
+
}
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
/**
|
|
86
103
|
* Class for combined compiler errors. Additional members:
|
|
87
104
|
* - `messages`: array of compiler messages (CompileMessage)
|
|
@@ -100,8 +117,6 @@ class CompilationError extends Error {
|
|
|
100
117
|
this.code = 'ERR_CDS_COMPILATION_FAILURE';
|
|
101
118
|
this.messages = [ ...messages ].sort(compareMessageSeverityAware);
|
|
102
119
|
|
|
103
|
-
// TODO: remove this bin/cdsc.js specifics
|
|
104
|
-
Object.defineProperty( this, 'hasBeenReported', { value: false, configurable: true, writable: true, enumerable: false } );
|
|
105
120
|
// property `model` is only set with options.attachValidNames:
|
|
106
121
|
Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
|
|
107
122
|
}
|
|
@@ -176,8 +191,14 @@ class CompileMessage {
|
|
|
176
191
|
// this.error = null;
|
|
177
192
|
}
|
|
178
193
|
|
|
179
|
-
toString() {
|
|
180
|
-
|
|
194
|
+
toString() {
|
|
195
|
+
// Used by cds-dk in their own `toString` wrapper.
|
|
196
|
+
return messageString( this, {
|
|
197
|
+
normalizeFilename: false,
|
|
198
|
+
noMessageId: true, // no message-id before finalization!
|
|
199
|
+
noHome: false,
|
|
200
|
+
module: null,
|
|
201
|
+
});
|
|
181
202
|
}
|
|
182
203
|
}
|
|
183
204
|
|
|
@@ -997,12 +1018,17 @@ function replaceInString( text, params ) {
|
|
|
997
1018
|
* @param {boolean} [config.noMessageId]
|
|
998
1019
|
* If true, will _not_ show the message ID (+ explanation hint) in the output.
|
|
999
1020
|
*
|
|
1021
|
+
* @param {boolean} [config.idInBrackets]
|
|
1022
|
+
* If true, the message ID (if there is one and noMessageId is falsey) will be put in brackets.
|
|
1023
|
+
* This will be the default in cds-compiler v5.
|
|
1024
|
+
*
|
|
1000
1025
|
* @param {boolean} [config.noHome]
|
|
1001
1026
|
* If true, will _not_ show message's semantic location.
|
|
1002
1027
|
*
|
|
1003
|
-
* @param {string} [config.
|
|
1028
|
+
* @param {string} [config.moduleForMarker]
|
|
1004
1029
|
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
1005
|
-
* the message can be downgraded for the given module.
|
|
1030
|
+
* the message can be downgraded for the given module. A `‹↑›` is used if the message
|
|
1031
|
+
* will be an error in the next major cds-compiler release.
|
|
1006
1032
|
*
|
|
1007
1033
|
* @returns {string}
|
|
1008
1034
|
*/
|
|
@@ -1013,19 +1039,22 @@ function messageString( err, config ) {
|
|
|
1013
1039
|
normalizeFilename: arguments[1],
|
|
1014
1040
|
noMessageId: arguments[2],
|
|
1015
1041
|
noHome: arguments[3],
|
|
1016
|
-
|
|
1042
|
+
moduleForMarker: arguments[4],
|
|
1017
1043
|
};
|
|
1018
1044
|
}
|
|
1045
|
+
config.moduleForMarker ??= config.module; // v4.8.0 or earlier compatibility
|
|
1019
1046
|
|
|
1020
1047
|
const location = (err.$location?.file ? `${ locationString( err.$location, config.normalizeFilename ) }: ` : '');
|
|
1021
1048
|
const severity = err.severity || 'Error';
|
|
1022
|
-
const downgradable =
|
|
1023
|
-
isDowngradable(err.messageId, config.module, {}) ? '‹↓›' : '';
|
|
1049
|
+
const downgradable = severityChangeMarker(err, config);
|
|
1024
1050
|
// even with noHome, print err.home if the location is weak
|
|
1025
1051
|
const home = !err.home || config.noHome && err.$location?.endLine ? '' : ` (in ${ err.home })`;
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1052
|
+
|
|
1053
|
+
let msgId = ''; // TODO(v5): Use brackets only
|
|
1054
|
+
if (err.messageId && !config.noMessageId)
|
|
1055
|
+
msgId = (config.idInBrackets) ? `[${err.messageId}]` : ` ${err.messageId}`;
|
|
1056
|
+
|
|
1057
|
+
return `${ location }${ severity }${ downgradable }${ msgId }: ${ err.message }${ home }`;
|
|
1029
1058
|
}
|
|
1030
1059
|
|
|
1031
1060
|
/**
|
|
@@ -1065,9 +1094,10 @@ function messageHash( msg ) {
|
|
|
1065
1094
|
* @param {boolean} [config.hintExplanation]
|
|
1066
1095
|
* If true, messages with explanations will get a "…" marker.
|
|
1067
1096
|
*
|
|
1068
|
-
* @param {string} [config.
|
|
1097
|
+
* @param {string} [config.moduleForMarker]
|
|
1069
1098
|
* If set, downgradable error messages will get a '‹↓›' marker, depending on whether
|
|
1070
|
-
* the message can be downgraded for the given module.
|
|
1099
|
+
* the message can be downgraded for the given module. A `‹↑›` is used if the message
|
|
1100
|
+
* will be an error in the next major cds-compiler release.
|
|
1071
1101
|
*
|
|
1072
1102
|
* @param {Record<string, string>} [config.sourceMap]
|
|
1073
1103
|
* A dictionary of filename<->source-code entries. You can pass the `fileCache` that is used
|
|
@@ -1095,12 +1125,13 @@ function messageHash( msg ) {
|
|
|
1095
1125
|
function messageStringMultiline( err, config = {} ) {
|
|
1096
1126
|
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
1097
1127
|
|
|
1128
|
+
config.moduleForMarker ??= config.module; // v4.8.0 or earlier compatibility
|
|
1129
|
+
|
|
1098
1130
|
const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
|
|
1099
1131
|
const home = !err.home ? '' : (`at ${ err.home }`);
|
|
1100
1132
|
const severity = err.severity || 'Error';
|
|
1101
|
-
const downgradable = config
|
|
1102
|
-
|
|
1103
|
-
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${downgradable}${ explainHelp }]` : '';
|
|
1133
|
+
const downgradable = severityChangeMarker(err, config);
|
|
1134
|
+
const msgId = (err.messageId && !config.noMessageId) ? `${downgradable}[${ err.messageId }${ explainHelp }]` : '';
|
|
1104
1135
|
|
|
1105
1136
|
let location = '';
|
|
1106
1137
|
let context = '';
|
|
@@ -1590,7 +1621,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1590
1621
|
if (!currentThing)
|
|
1591
1622
|
return result;
|
|
1592
1623
|
|
|
1593
|
-
|
|
1624
|
+
const selectDepth = (csnPath[0] !== 'extensions') ? queryDepthForMessage(csnPath, model, currentThing) : null;
|
|
1594
1625
|
|
|
1595
1626
|
// Artifact ref -------------------------------------
|
|
1596
1627
|
|
|
@@ -1759,7 +1790,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1759
1790
|
if (index >= csnPath.length)
|
|
1760
1791
|
continue; // no column name
|
|
1761
1792
|
|
|
1762
|
-
|
|
1793
|
+
const elementHierarchy = [];
|
|
1763
1794
|
|
|
1764
1795
|
// Concat column+expand/inline to get a name similar to elements.
|
|
1765
1796
|
do {
|
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 },
|
|
@@ -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(),
|
|
@@ -515,11 +519,13 @@ function assertConsistency( model, stage ) {
|
|
|
515
519
|
'_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
|
|
516
520
|
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
517
521
|
'args', 'op', 'func', 'suffix',
|
|
522
|
+
'$invalidPaths',
|
|
518
523
|
],
|
|
519
524
|
// TODO: name requires if not in parser?
|
|
520
525
|
},
|
|
521
526
|
$priority: { test: isOneOf( [ undefined, false, 'extend', 'annotate' ] ) },
|
|
522
527
|
$annotations: { parser: true, kind: true, test: TODO }, // deprecated, still there for cds-lsp
|
|
528
|
+
$invalidPaths: { test: isBoolean },
|
|
523
529
|
name: {
|
|
524
530
|
isRequired: stageParser && (() => false), // not required in parser
|
|
525
531
|
kind: true,
|