@sap/cds-compiler 2.15.2 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -1590
- package/bin/cdsc.js +42 -46
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +312 -143
- package/lib/api/options.js +15 -85
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +80 -24
- package/lib/base/messages.js +103 -52
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +2 -1
- package/lib/compiler/assert-consistency.js +15 -10
- package/lib/compiler/builtins.js +127 -10
- package/lib/compiler/define.js +6 -4
- package/lib/compiler/extend.js +63 -12
- package/lib/compiler/finalize-parse-cdl.js +20 -9
- package/lib/compiler/index.js +25 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +16 -14
- package/lib/compiler/propagator.js +3 -3
- package/lib/compiler/resolve.js +194 -222
- package/lib/compiler/shared.js +56 -76
- package/lib/compiler/tweak-assocs.js +9 -10
- package/lib/compiler/utils.js +7 -2
- package/lib/edm/annotations/genericTranslation.js +60 -6
- package/lib/edm/annotations/preprocessAnnotations.js +10 -11
- package/lib/edm/csn2edm.js +39 -41
- package/lib/edm/edm.js +22 -15
- package/lib/edm/edmPreprocessor.js +66 -69
- package/lib/edm/edmUtils.js +12 -62
- package/lib/gen/Dictionary.json +8 -6
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20717 -22376
- package/lib/json/from-csn.js +73 -68
- package/lib/json/to-csn.js +13 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +333 -259
- package/lib/language/language.g4 +600 -645
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +27 -42
- package/lib/main.js +104 -81
- package/lib/model/csnRefs.js +2 -1
- package/lib/model/csnUtils.js +183 -285
- package/lib/model/revealInternalProperties.js +32 -9
- package/lib/model/sortViews.js +32 -31
- package/lib/optionProcessor.js +64 -57
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +334 -339
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +60 -54
- package/lib/render/utils/common.js +15 -1
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +18 -19
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +19 -22
- package/lib/transform/forOdataNew.js +13 -15
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +11 -9
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +6 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
|
@@ -45,6 +45,7 @@ const centralMessages = {
|
|
|
45
45
|
'anno-definition': { severity: 'Warning' },
|
|
46
46
|
'anno-duplicate': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
47
47
|
'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
48
|
+
'anno-unstable-array': { severity: 'Warning' },
|
|
48
49
|
'anno-invalid-sql-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
|
|
49
50
|
'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
|
|
50
51
|
'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
|
|
@@ -67,7 +68,7 @@ const centralMessages = {
|
|
|
67
68
|
'def-unexpected-calcview-assoc': { severity: 'Error' },
|
|
68
69
|
'chained-array-of': { severity: 'Error' },
|
|
69
70
|
'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
|
|
70
|
-
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },
|
|
71
|
+
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
|
|
71
72
|
|
|
72
73
|
'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
|
|
73
74
|
|
|
@@ -80,6 +81,7 @@ const centralMessages = {
|
|
|
80
81
|
'expected-type': { severity: 'Error' },
|
|
81
82
|
'ref-sloppy-type': { severity: 'Error' },
|
|
82
83
|
'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
|
|
84
|
+
'type-ignoring-argument': { severity: 'Error', configurableFor: true },
|
|
83
85
|
'type-expected-builtin': { severity: 'Error', configurableFor: true },
|
|
84
86
|
'expected-actionparam-type': { severity: 'Error' },
|
|
85
87
|
'ref-sloppy-actionparam-type': { severity: 'Error' },
|
|
@@ -116,6 +118,7 @@ const centralMessages = {
|
|
|
116
118
|
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
117
119
|
'ref-undefined-param': { severity: 'Error' },
|
|
118
120
|
'ref-rejected-on': { severity: 'Error' },
|
|
121
|
+
'ref-expected-element': { severity: 'Error' },
|
|
119
122
|
|
|
120
123
|
'rewrite-key-not-covered-explicit': { severity: 'Error', configurableFor: 'deprecated' },
|
|
121
124
|
'rewrite-key-not-covered-implicit': { severity: 'Error', configurableFor: 'deprecated' },
|
|
@@ -128,14 +131,11 @@ const centralMessages = {
|
|
|
128
131
|
'service-nested-context': { severity: 'Error', configurableFor: true }, // does not hurt compile, TODO
|
|
129
132
|
'service-nested-service': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
|
|
130
133
|
|
|
131
|
-
'syntax-
|
|
132
|
-
'syntax-
|
|
133
|
-
'syntax-
|
|
134
|
-
'syntax-
|
|
135
|
-
'syntax-
|
|
136
|
-
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
|
|
137
|
-
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
|
|
138
|
-
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
|
|
134
|
+
'syntax-expected-cardinality': { severity: 'Error' },
|
|
135
|
+
'syntax-expected-length': { severity: 'Error' },
|
|
136
|
+
'syntax-expected-translation': { severity: 'Error' },
|
|
137
|
+
'syntax-required-subproperty': { severity: 'Error' },
|
|
138
|
+
'syntax-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
|
|
139
139
|
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },
|
|
140
140
|
'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
|
|
141
141
|
'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
|
|
@@ -148,6 +148,8 @@ const centralMessages = {
|
|
|
148
148
|
'syntax-missing-escape': { severity: 'Error' },
|
|
149
149
|
|
|
150
150
|
'syntax-expected-integer': { severity: 'Error' },
|
|
151
|
+
'syntax-invalid-masked': { severity: 'Error', configurableFor: true },
|
|
152
|
+
'syntax-unexpected-null': { severity: 'Error', configurableFor: true },
|
|
151
153
|
|
|
152
154
|
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
|
|
153
155
|
|
|
@@ -158,12 +160,12 @@ const centralMessages = {
|
|
|
158
160
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
159
161
|
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
160
162
|
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
161
|
-
'odata-spec-violation-id': { severity: 'Error' },
|
|
162
|
-
'odata-spec-violation-type': { severity: 'Error', configurableFor:
|
|
163
|
+
'odata-spec-violation-id': { severity: 'Error', configurableFor: true },
|
|
164
|
+
'odata-spec-violation-type': { severity: 'Error', configurableFor: true },
|
|
163
165
|
'odata-spec-violation-type-unknown': { severity: 'Warning' },
|
|
164
166
|
'odata-spec-violation-no-key': { severity: 'Warning' },
|
|
165
|
-
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
166
|
-
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
167
|
+
'odata-spec-violation-key-array': { severity: 'Error', configurableFor: true }, // more than 30 chars
|
|
168
|
+
'odata-spec-violation-key-null': { severity: 'Error', configurableFor: true }, // more than 30 chars
|
|
167
169
|
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
168
170
|
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
|
|
169
171
|
};
|
|
@@ -174,6 +176,15 @@ const centralMessages = {
|
|
|
174
176
|
// The keys will be added to `oldNames` of the new message, which is used for reclassification.
|
|
175
177
|
const oldMessageIds = createDict({
|
|
176
178
|
'old-anno-duplicate': 'anno-duplicate', // Example
|
|
179
|
+
|
|
180
|
+
// These IDs are used by large stakeholders. If we change them, we should
|
|
181
|
+
// be backward-compatible.
|
|
182
|
+
// 'redirected-to-complex': 'TODO',
|
|
183
|
+
// 'wildcard-excluding-one': 'TODO',
|
|
184
|
+
// 'wildcard-excluding-many': 'TODO',
|
|
185
|
+
// 'assoc-outside-service': 'TODO',
|
|
186
|
+
// 'redirected-to-same': 'TODO',
|
|
187
|
+
// 'query-navigate-many': 'TODO',
|
|
177
188
|
});
|
|
178
189
|
|
|
179
190
|
// Set up the old-to-new message ID mapping in the message registry.
|
|
@@ -193,27 +204,47 @@ for (const oldName in oldMessageIds) {
|
|
|
193
204
|
|
|
194
205
|
// For messageIds, where no text has been provided via code (central def)
|
|
195
206
|
const centralMessageTexts = {
|
|
207
|
+
'api-invalid-option': {
|
|
208
|
+
std: 'Option $(NAME) is deprecated! Use SNAPI options instead',
|
|
209
|
+
magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
210
|
+
user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
211
|
+
locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
|
|
212
|
+
'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
|
|
213
|
+
},
|
|
214
|
+
|
|
196
215
|
'anno-duplicate': 'Duplicate assignment with $(ANNO)',
|
|
216
|
+
'anno-duplicate-unrelated-layer': 'Duplicate assignment with $(ANNO)',
|
|
217
|
+
'anno-unstable-array': 'Unstable order of array items due to repeated assignments for $(ANNO) in unrelated layers',
|
|
197
218
|
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
|
|
198
219
|
'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
|
|
199
220
|
'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
|
|
200
221
|
'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',
|
|
201
|
-
|
|
202
|
-
'
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
222
|
+
|
|
223
|
+
'name-duplicate-element': {
|
|
224
|
+
'std': 'Generated element $(NAME) conflicts with another element',
|
|
225
|
+
'flatten-element-gen': 'Generated element $(NAME) conflicts with other generated element',
|
|
226
|
+
'flatten-element-exist': 'Flattened name of structured element conflicts with existing element $(NAME)',
|
|
227
|
+
'flatten-fkey-gen': 'Duplicate definition of foreign key element $(NAME) for association $(ART)',
|
|
228
|
+
'flatten-fkey-exists': 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element',
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
'syntax-unexpected-ellipsis': {
|
|
232
|
+
std: 'Expected no more than one $(CODE)',
|
|
233
|
+
'nested-array': 'Unexpected $(CODE) in nested array'
|
|
234
|
+
},
|
|
235
|
+
'syntax-expected-object': 'Expected object for property $(PROP)',
|
|
236
|
+
'syntax-expected-column': 'Expected object or string \'*\' for property $(PROP)',
|
|
237
|
+
'syntax-expected-natnum': 'Expected non-negative number for property $(PROP)',
|
|
238
|
+
'syntax-expected-cardinality': 'Expected non-negative number or string \'*\' for property $(PROP)',
|
|
239
|
+
'syntax-expected-reference': 'Expected non-empty string or object for property $(PROP)',
|
|
240
|
+
'syntax-expected-term': 'Expected non-empty string or object for property $(PROP)',
|
|
210
241
|
'syntax-dollar-ident': {
|
|
211
242
|
std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',
|
|
212
243
|
$tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
|
|
213
244
|
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
|
|
214
245
|
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
|
|
215
246
|
},
|
|
216
|
-
'syntax-
|
|
247
|
+
'syntax-expected-length': {
|
|
217
248
|
std: 'Expected array in $(PROP) to have at least $(N) items',
|
|
218
249
|
one: 'Expected array in $(PROP) to have at least one item',
|
|
219
250
|
suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
|
|
@@ -245,6 +276,26 @@ const centralMessageTexts = {
|
|
|
245
276
|
unknown: 'Unknown argument $(CODE)',
|
|
246
277
|
duplicate: 'Duplicate argument $(CODE)',
|
|
247
278
|
},
|
|
279
|
+
'syntax-duplicate-annotate': 'You shouldn\'t refer to $(NAME) repeatedly in the same annotate statement',
|
|
280
|
+
'syntax-duplicate-extend': {
|
|
281
|
+
std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement',
|
|
282
|
+
define: 'You can\'t refer to $(NAME) in the same extend statement where it was defined',
|
|
283
|
+
extend: 'You can\'t refer to $(NAME) repeatedly in the same extend statement',
|
|
284
|
+
},
|
|
285
|
+
'syntax-invalid-literal': {
|
|
286
|
+
'std': 'Invalid literal',
|
|
287
|
+
'uneven-hex': 'A binary literal must have an even number of characters',
|
|
288
|
+
'invalid-hex': 'A binary literal must only contain characters 0-9, a-f and A-F',
|
|
289
|
+
'time': 'Expected time\'hh:mm:ss\' where hh, mm and the optional ss are numbers',
|
|
290
|
+
'date': 'Expected date\'YYYY-MM-DD\' where YYYY, MM and DD are numbers',
|
|
291
|
+
'timestamp': 'Expected timestamp\'YYYY-MM-DD hh:mm:ss.u…u\' where YYYY, MM, DD, hh, mm, ss and u are numbers (optional 1-7×u)',
|
|
292
|
+
},
|
|
293
|
+
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before',
|
|
294
|
+
'syntax-unexpected-vocabulary': {
|
|
295
|
+
std: 'Annotations can\'t be defined inside contexts or services',
|
|
296
|
+
service: 'Annotations can\'t be defined inside services',
|
|
297
|
+
context: 'Annotations can\'t be defined inside contexts',
|
|
298
|
+
},
|
|
248
299
|
'ref-undefined-def': {
|
|
249
300
|
std: 'Artifact $(ART) has not been found',
|
|
250
301
|
// TODO: proposal 'No definition of $(NAME) found',
|
|
@@ -266,12 +317,17 @@ const centralMessageTexts = {
|
|
|
266
317
|
mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
|
|
267
318
|
alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
|
|
268
319
|
},
|
|
320
|
+
'ref-expected-element': {
|
|
321
|
+
std: 'Expected element reference',
|
|
322
|
+
magicVar: 'Only elements of magic variable $(ID) can be selected',
|
|
323
|
+
},
|
|
269
324
|
'type-unexpected-typeof': {
|
|
270
325
|
std: 'Unexpected $(KEYWORD) for the type reference here',
|
|
271
326
|
type: 'Unexpected $(KEYWORD) in type of a type definition',
|
|
272
327
|
event: 'Unexpected $(KEYWORD) for the type of an event',
|
|
273
328
|
param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
|
|
274
329
|
select: 'Unexpected $(KEYWORD) for type references in queries',
|
|
330
|
+
annotation: '$(KEYWORD) can\'t be used in annotation definitions',
|
|
275
331
|
},
|
|
276
332
|
|
|
277
333
|
'type-missing-argument': 'Missing value for argument $(NAME) in reference to type $(ID)',
|
|
@@ -423,7 +479,7 @@ const centralMessageTexts = {
|
|
|
423
479
|
* Whether the error can be reclassified to a warning or lower.
|
|
424
480
|
* If not `true` then an array is expected with specified modules in which the error is downgradable.
|
|
425
481
|
* Only has an effect if default severity is 'Error'.
|
|
426
|
-
* 'deprecated': severity can only be changed with deprecated.
|
|
482
|
+
* 'deprecated': severity can only be changed with deprecated._downgradableErrors.
|
|
427
483
|
* TODO: Value `true` is temporary. Use an array instead.
|
|
428
484
|
* @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.
|
|
429
485
|
* @property {boolean} [throughMessageCall]
|
package/lib/base/messages.js
CHANGED
|
@@ -27,7 +27,7 @@ let test$texts = null;
|
|
|
27
27
|
/**
|
|
28
28
|
* Returns true if at least one of the given messages is of severity "Error".
|
|
29
29
|
*
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {CompileMessage[]} messages
|
|
31
31
|
* @returns {boolean}
|
|
32
32
|
*/
|
|
33
33
|
function hasErrors( messages ) {
|
|
@@ -39,7 +39,7 @@ function hasErrors( messages ) {
|
|
|
39
39
|
* and *cannot* be reclassified to a warning for the given module.
|
|
40
40
|
* Won't detect already downgraded messages.
|
|
41
41
|
*
|
|
42
|
-
* @param {
|
|
42
|
+
* @param {CompileMessage[]} messages
|
|
43
43
|
* @param {string} moduleName
|
|
44
44
|
* @returns {boolean}
|
|
45
45
|
*/
|
|
@@ -319,20 +319,23 @@ function createMessageFunctions( options, moduleName, model = null ) {
|
|
|
319
319
|
* ```
|
|
320
320
|
* @param {object} model
|
|
321
321
|
* @param {CSN.Options} [options]
|
|
322
|
-
* @param {string} [moduleName]
|
|
322
|
+
* @param {string|null} [moduleName]
|
|
323
323
|
*/
|
|
324
324
|
function makeMessageFunction( model, options, moduleName = null ) {
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
if (options.testMode) {
|
|
326
|
+
// ensure message consistency during runtime with --test-mode
|
|
327
327
|
_check$Init( options );
|
|
328
|
+
if (!options.messages)
|
|
329
|
+
throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
|
|
330
|
+
}
|
|
328
331
|
|
|
329
332
|
const hasMessageArray = !!options.messages;
|
|
330
|
-
const deprecatedDowngradable = isDeprecatedEnabled( options, '
|
|
333
|
+
const deprecatedDowngradable = isDeprecatedEnabled( options, '_downgradableErrors' );
|
|
331
334
|
/**
|
|
332
335
|
* Array of collected compiler messages. Only use it for debugging. Will not
|
|
333
336
|
* contain the messages created during a `callTransparently` call.
|
|
334
337
|
*
|
|
335
|
-
* @type {
|
|
338
|
+
* @type {CompileMessage[]}
|
|
336
339
|
*/
|
|
337
340
|
let messages = options.messages || [];
|
|
338
341
|
/**
|
|
@@ -361,7 +364,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
361
364
|
const [ fileLocation, semanticLocation, definition ] = _normalizeMessageLocation(location);
|
|
362
365
|
const text = messageText( texts || centralMessageTexts[id], textOrArguments );
|
|
363
366
|
|
|
364
|
-
/** @type {
|
|
367
|
+
/** @type {CompileMessage} */
|
|
365
368
|
const msg = new CompileMessage( fileLocation, text, severity, id, semanticLocation, moduleName );
|
|
366
369
|
if (options.internalMsg)
|
|
367
370
|
msg.error = new Error( 'stack' );
|
|
@@ -432,7 +435,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
432
435
|
// CSN.Location (with line/endLine, col/endCol)
|
|
433
436
|
return [ location, location.home || null, null ]
|
|
434
437
|
|
|
435
|
-
const isCsnPath = (typeof location[0] === 'string');
|
|
438
|
+
const isCsnPath = (typeof location[0] === 'string'); // could be `definitions`, `extensions`, ....
|
|
436
439
|
if (isCsnPath) {
|
|
437
440
|
return [
|
|
438
441
|
searchForLocation( model, location ),
|
|
@@ -553,7 +556,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
553
556
|
*
|
|
554
557
|
* @param {Function} callback
|
|
555
558
|
* @param {...any} args
|
|
556
|
-
* @returns {
|
|
559
|
+
* @returns {CompileMessage[]}
|
|
557
560
|
*/
|
|
558
561
|
function callTransparently(callback, ...args) {
|
|
559
562
|
const backup = messages;
|
|
@@ -579,6 +582,22 @@ function _check$Init( options ) {
|
|
|
579
582
|
}
|
|
580
583
|
}
|
|
581
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Check the consistency of the given message and run some basic lint checks. These include:
|
|
587
|
+
*
|
|
588
|
+
* - Long message IDs must be listed centrally.
|
|
589
|
+
* - Messages with the same ID must have the same severity (in a module).
|
|
590
|
+
* - Messages with the same ID must have the same message texts.
|
|
591
|
+
* This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
|
|
592
|
+
* use the same ID for different meanings, i.e. texts.
|
|
593
|
+
*
|
|
594
|
+
* @param {string} id
|
|
595
|
+
* @param {string} moduleName
|
|
596
|
+
* @param {string} severity
|
|
597
|
+
* @param {string|object} texts
|
|
598
|
+
* @param {CSN.Options} options
|
|
599
|
+
* @private
|
|
600
|
+
*/
|
|
582
601
|
function _check$Consistency( id, moduleName, severity, texts, options ) {
|
|
583
602
|
if (id.length > 30 && !centralMessages[id])
|
|
584
603
|
throw new CompilerAssertion( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
|
|
@@ -589,6 +608,16 @@ function _check$Consistency( id, moduleName, severity, texts, options ) {
|
|
|
589
608
|
_check$Texts( id, variant, text );
|
|
590
609
|
}
|
|
591
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Check the consistency of the message severity for the given message ID.
|
|
613
|
+
* Messages with the same ID must have the same severity (in a module).
|
|
614
|
+
* Non-downgradable errors must never be called with a lower severity.
|
|
615
|
+
*
|
|
616
|
+
* @param {string} id
|
|
617
|
+
* @param {string} moduleName
|
|
618
|
+
* @param {string} severity
|
|
619
|
+
* @private
|
|
620
|
+
*/
|
|
592
621
|
function _check$Severities( id, moduleName, severity ) {
|
|
593
622
|
if (!severity) // if just used message(), we are automatically consistent
|
|
594
623
|
return;
|
|
@@ -598,22 +627,34 @@ function _check$Severities( id, moduleName, severity ) {
|
|
|
598
627
|
if (!expected)
|
|
599
628
|
test$severities[id] = severity;
|
|
600
629
|
else if (expected !== severity)
|
|
601
|
-
throw new CompilerAssertion( `
|
|
630
|
+
throw new CompilerAssertion( `Inconsistent severity: Expecting "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
|
|
602
631
|
return;
|
|
603
632
|
}
|
|
604
633
|
// now try whether the message could be something less than an Error in the module due to user wishes
|
|
605
634
|
if (!isDowngradable( id, moduleName )) { // always an error in module
|
|
606
635
|
if (severity !== 'Error')
|
|
607
|
-
throw new CompilerAssertion( `
|
|
636
|
+
throw new CompilerAssertion( `Inconsistent severity: Expecting "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
|
|
608
637
|
}
|
|
609
638
|
else if (spec.severity === 'Error') {
|
|
610
|
-
throw new CompilerAssertion( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
|
|
639
|
+
throw new CompilerAssertion( `Inconsistent severity: Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
|
|
611
640
|
}
|
|
612
641
|
else if (spec.severity !== severity) {
|
|
613
|
-
throw new CompilerAssertion( `
|
|
642
|
+
throw new CompilerAssertion( `Inconsistent severity: Expecting "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
|
|
614
643
|
}
|
|
615
644
|
}
|
|
616
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Check the consistency of the message text for the given message ID.
|
|
648
|
+
*
|
|
649
|
+
* Messages with the same ID must have the same message texts.
|
|
650
|
+
* This ensures that $(PLACEHOLDERS) are used and that we don't accidentally
|
|
651
|
+
* use the same ID for different meanings, i.e. texts.
|
|
652
|
+
*
|
|
653
|
+
* @param {string} id
|
|
654
|
+
* @param {string} prop
|
|
655
|
+
* @param {string} value
|
|
656
|
+
* @private
|
|
657
|
+
*/
|
|
617
658
|
function _check$Texts( id, prop, value ) {
|
|
618
659
|
if (!test$texts[id])
|
|
619
660
|
test$texts[id] = Object.create(null);
|
|
@@ -621,7 +662,7 @@ function _check$Texts( id, prop, value ) {
|
|
|
621
662
|
if (!expected)
|
|
622
663
|
test$texts[id][prop] = value;
|
|
623
664
|
else if (expected !== value)
|
|
624
|
-
throw new CompilerAssertion( `Expecting
|
|
665
|
+
throw new CompilerAssertion( `Different texts for the same message ID. Expecting "${expected}", not "${value}" for ID "${id}" and text variant "${prop}"`);
|
|
625
666
|
}
|
|
626
667
|
|
|
627
668
|
const quote = { // could be an option in the future
|
|
@@ -819,7 +860,7 @@ function weakLocation( loc ) {
|
|
|
819
860
|
* Example:
|
|
820
861
|
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
|
|
821
862
|
*
|
|
822
|
-
* @param {
|
|
863
|
+
* @param {CompileMessage} err
|
|
823
864
|
* @param {boolean} [normalizeFilename]
|
|
824
865
|
* @param {boolean} [noMessageId]
|
|
825
866
|
* @param {boolean} [noHome]
|
|
@@ -841,7 +882,7 @@ function messageString( err, normalizeFilename, noMessageId, noHome ) {
|
|
|
841
882
|
* Return message hash which is either the message string without the file location,
|
|
842
883
|
* or the full message string if no semantic location is provided.
|
|
843
884
|
*
|
|
844
|
-
* @param {
|
|
885
|
+
* @param {CompileMessage} msg
|
|
845
886
|
* @returns {string} can be used to uniquely identify a message
|
|
846
887
|
*/
|
|
847
888
|
function messageHash(msg) {
|
|
@@ -867,7 +908,7 @@ function messageHash(msg) {
|
|
|
867
908
|
* <source>.cds:3:11, at entity:“E”/element:“e”
|
|
868
909
|
* ```
|
|
869
910
|
*
|
|
870
|
-
* @param {
|
|
911
|
+
* @param {CompileMessage} err
|
|
871
912
|
* @param {object} [config = {}]
|
|
872
913
|
* @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
|
|
873
914
|
* @param {boolean} [config.noMessageId]
|
|
@@ -915,7 +956,7 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
915
956
|
*
|
|
916
957
|
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
|
|
917
958
|
* from `lib/utils/file.js`
|
|
918
|
-
* @param {
|
|
959
|
+
* @param {CompileMessage} err Error object containing all details like line, message, etc.
|
|
919
960
|
* @param {object} [config = {}]
|
|
920
961
|
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
|
|
921
962
|
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
@@ -994,8 +1035,8 @@ function messageContext(sourceLines, err, config) {
|
|
|
994
1035
|
* larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location
|
|
995
1036
|
* are considered larger than messages with a location.
|
|
996
1037
|
*
|
|
997
|
-
* @param {
|
|
998
|
-
* @param {
|
|
1038
|
+
* @param {CompileMessage} a
|
|
1039
|
+
* @param {CompileMessage} b
|
|
999
1040
|
*/
|
|
1000
1041
|
function compareMessage( a, b ) {
|
|
1001
1042
|
const aFile = a.$location && a.$location.file;
|
|
@@ -1029,8 +1070,8 @@ function compareMessage( a, b ) {
|
|
|
1029
1070
|
* location and severity, >0 if `a` is larger than `b`, and <0 if `a` is smaller
|
|
1030
1071
|
* than `b`. See `compareSeverities()` for how severities are compared.
|
|
1031
1072
|
*
|
|
1032
|
-
* @param {
|
|
1033
|
-
* @param {
|
|
1073
|
+
* @param {CompileMessage} a
|
|
1074
|
+
* @param {CompileMessage} b
|
|
1034
1075
|
*/
|
|
1035
1076
|
function compareMessageSeverityAware( a, b ) {
|
|
1036
1077
|
const c = compareSeverities(a.severity, b.severity);
|
|
@@ -1042,7 +1083,7 @@ function compareMessageSeverityAware( a, b ) {
|
|
|
1042
1083
|
* Messages without semantic locations are considered smaller (for syntax errors)
|
|
1043
1084
|
* and (currently - should not happen in v2) larger for other messages.
|
|
1044
1085
|
*
|
|
1045
|
-
* @param {
|
|
1086
|
+
* @param {CompileMessage} msg
|
|
1046
1087
|
*/
|
|
1047
1088
|
function homeSortName( { home, messageId } ) {
|
|
1048
1089
|
return (!home)
|
|
@@ -1061,7 +1102,7 @@ function homeSortName( { home, messageId } ) {
|
|
|
1061
1102
|
* A message is more precise if it is contained in the other or if
|
|
1062
1103
|
* the first does not have an endLine/endCol.
|
|
1063
1104
|
*
|
|
1064
|
-
* @param {
|
|
1105
|
+
* @param {CompileMessage[]} messages
|
|
1065
1106
|
*/
|
|
1066
1107
|
function deduplicateMessages( messages ) {
|
|
1067
1108
|
const seen = new Map();
|
|
@@ -1133,7 +1174,7 @@ function homeName( art, absoluteOnly ) {
|
|
|
1133
1174
|
return null;
|
|
1134
1175
|
else if (art.kind === 'using')
|
|
1135
1176
|
return 'using:' + quoted( art.name.id );
|
|
1136
|
-
else if (art.kind === 'extend')
|
|
1177
|
+
else if (art.kind === 'extend' || art.kind === 'annotate')
|
|
1137
1178
|
return !absoluteOnly && homeNameForExtend ( art );
|
|
1138
1179
|
else if (art.name._artifact) // block, extend, annotate
|
|
1139
1180
|
return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
|
|
@@ -1148,35 +1189,41 @@ function homeName( art, absoluteOnly ) {
|
|
|
1148
1189
|
// The "home" for extensions is handled differently because `_artifact` is not
|
|
1149
1190
|
// set for unknown extensions and we could have nested extensions.
|
|
1150
1191
|
function homeNameForExtend( art ) {
|
|
1192
|
+
const kind = art.kind || 'extend';
|
|
1151
1193
|
// TODO: fix the following - do like in collectArtifactExtensions() or
|
|
1152
|
-
//
|
|
1153
|
-
const absoluteName =
|
|
1154
|
-
|
|
1194
|
+
// basically resolveUncheckedPath()
|
|
1195
|
+
const absoluteName = art.name.id ? art.name.id :
|
|
1196
|
+
(!art.name.element && art.name.absolute || art.name.path.map(s => s && s.id).join('.'));
|
|
1155
1197
|
|
|
1156
1198
|
// Surrounding parent may be another extension.
|
|
1157
1199
|
const parent = art._parent;
|
|
1158
1200
|
if (!parent)
|
|
1159
|
-
return '
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
return artName(parent) + '/' + quoted(absoluteName);
|
|
1165
|
-
|
|
1166
|
-
let extensionName;
|
|
1167
|
-
if (parentArt.enum || parentArt.elements) {
|
|
1168
|
-
const fakeArt = {
|
|
1169
|
-
kind: parentArt.enum ? 'enum' : 'element',
|
|
1170
|
-
name: { element: absoluteName }
|
|
1171
|
-
};
|
|
1172
|
-
extensionName = artName(fakeArt);
|
|
1201
|
+
return kind + ':' + quoted(absoluteName);
|
|
1202
|
+
|
|
1203
|
+
if (art.name.param && parent.params) {
|
|
1204
|
+
const fakeArt = { kind: 'param', name: { param: absoluteName } };
|
|
1205
|
+
return homeNameForExtend(parent) + '/' + artName(fakeArt);
|
|
1173
1206
|
}
|
|
1174
|
-
else {
|
|
1175
|
-
|
|
1207
|
+
else if (art.name.action && parent.actions) {
|
|
1208
|
+
const type = art.name._artifact?.kind || 'action';
|
|
1209
|
+
const fakeArt = { kind: type, name: { action: absoluteName }, _main: art.name._artifact?._main };
|
|
1210
|
+
return homeNameForExtend(parent) + '/' + artName(fakeArt);
|
|
1211
|
+
}
|
|
1212
|
+
else if (parent.enum || parent.elements || parent.returns?.elements) {
|
|
1213
|
+
// For enum, extensions may store them in `elements`, i.e. don't differ between enum/elements,
|
|
1214
|
+
// so we need to look at the parent artifact.
|
|
1215
|
+
// For `extend <art> with enum`, there is `enum`.
|
|
1216
|
+
const parentArt = parent.name?._artifact;
|
|
1217
|
+
const fakeKind = (parent.enum || parentArt?.enum) ? 'enum' : 'element';
|
|
1218
|
+
const fakeArt = { kind: fakeKind, name: { element: art.name.element } };
|
|
1219
|
+
let parentOfElementChain = parent;
|
|
1220
|
+
while (parentOfElementChain.name?.element && parentOfElementChain._parent)
|
|
1221
|
+
parentOfElementChain = parentOfElementChain._parent;
|
|
1222
|
+
|
|
1223
|
+
return homeNameForExtend(parentOfElementChain) + '/' + artName(fakeArt);
|
|
1176
1224
|
}
|
|
1177
|
-
//
|
|
1178
|
-
|
|
1179
|
-
return 'extend:' + artName(parentArt) + '/' + extensionName;
|
|
1225
|
+
// This case should not happen, but just in case
|
|
1226
|
+
return kind + ':' + artName(parent);
|
|
1180
1227
|
}
|
|
1181
1228
|
|
|
1182
1229
|
function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
@@ -1189,10 +1236,14 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1189
1236
|
];
|
|
1190
1237
|
const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
|
|
1191
1238
|
|
|
1192
|
-
|
|
1193
|
-
csnPath
|
|
1194
|
-
|
|
1195
|
-
|
|
1239
|
+
if (csnPath[0] === 'extensions') {
|
|
1240
|
+
const ext = model.extensions && model.extensions[csnPath[1]] || {};
|
|
1241
|
+
if (ext.annotate)
|
|
1242
|
+
return 'annotate:' + quoted(ext.annotate);
|
|
1243
|
+
return 'extend:' + quoted(ext.extend);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
let { query } = analyseCsnPath(csnPath, model, false);
|
|
1196
1247
|
|
|
1197
1248
|
// remove definitions
|
|
1198
1249
|
csnPath.shift();
|
package/lib/base/model.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { forEach } = require("../utils/objectUtils");
|
|
4
|
+
|
|
3
5
|
const queryOps = {
|
|
4
6
|
query: 'select', // TODO: rename to SELECT
|
|
5
7
|
union: 'union',
|
|
@@ -20,7 +22,6 @@ const queryOps = {
|
|
|
20
22
|
const availableBetaFlags = {
|
|
21
23
|
// enabled by --beta-mode
|
|
22
24
|
toRename: true,
|
|
23
|
-
addTextsLanguageAssoc: true,
|
|
24
25
|
assocsWithParams: true,
|
|
25
26
|
hanaAssocRealCardinality: true,
|
|
26
27
|
mapAssocToJoinCardinality: true,
|
|
@@ -58,16 +59,56 @@ function isBetaEnabled( options, feature ) {
|
|
|
58
59
|
* Please do not move this function to the "option processor" code.
|
|
59
60
|
*
|
|
60
61
|
* @param {object} options Options
|
|
61
|
-
* @param {string} [feature] Feature to check for
|
|
62
|
+
* @param {string|null} [feature] Feature to check for
|
|
62
63
|
* @returns {boolean}
|
|
63
64
|
*/
|
|
64
65
|
function isDeprecatedEnabled( options, feature = null ) {
|
|
65
66
|
const { deprecated } = options;
|
|
66
67
|
if (!feature)
|
|
67
68
|
return !!deprecated;
|
|
69
|
+
|
|
68
70
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
const oldDeprecatedFlags_v2 = [
|
|
74
|
+
'createLocalizedViews',
|
|
75
|
+
'downgradableErrors',
|
|
76
|
+
'generatedEntityNameWithUnderscore',
|
|
77
|
+
'longAutoexposed',
|
|
78
|
+
'noElementsExpansion',
|
|
79
|
+
'noInheritedAutoexposeViaComposition',
|
|
80
|
+
'noScopedRedirections',
|
|
81
|
+
'oldVirtualNotNullPropagation',
|
|
82
|
+
'parensAsStrings',
|
|
83
|
+
'projectionAsQuery',
|
|
84
|
+
'redirectInSubQueries',
|
|
85
|
+
'renderVirtualElements',
|
|
86
|
+
'shortAutoexposed',
|
|
87
|
+
'unmanagedUpInComponent',
|
|
88
|
+
'v1KeysForTemporal',
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* In cds-compiler v3, we removed old v2 deprecated flags. That can lead to silent
|
|
93
|
+
* errors such as entity/view names changing. To ensure that the user is forced
|
|
94
|
+
* to change their code, emit an error if one of such removed flags was used.
|
|
95
|
+
*
|
|
96
|
+
* @param {CSN.Options} options
|
|
97
|
+
* @param error Error message function returned by makeMessageFunctions().
|
|
98
|
+
*/
|
|
99
|
+
function checkRemovedDeprecatedFlags( options, { error } ) {
|
|
100
|
+
// Assume that we emitted these errors once if a message with this ID was found.
|
|
101
|
+
if (!options.deprecated || options.messages?.some(m => m.messageId === 'api-invalid-deprecated'))
|
|
102
|
+
return;
|
|
103
|
+
|
|
104
|
+
forEach(options.deprecated, (key, val) => {
|
|
105
|
+
if (val && oldDeprecatedFlags_v2.includes(key)) {
|
|
106
|
+
error('api-invalid-deprecated', null, { name: key },
|
|
107
|
+
'Deprecated flag $(NAME) has been removed in CDS compiler v3');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
71
112
|
// Apply function `callback` to all artifacts in dictionary
|
|
72
113
|
// `model.definitions`. See function `forEachGeneric` for details.
|
|
73
114
|
function forEachDefinition( model, callback ) {
|
|
@@ -129,6 +170,7 @@ module.exports = {
|
|
|
129
170
|
isBetaEnabled,
|
|
130
171
|
availableBetaFlags,
|
|
131
172
|
isDeprecatedEnabled,
|
|
173
|
+
checkRemovedDeprecatedFlags,
|
|
132
174
|
queryOps,
|
|
133
175
|
forEachDefinition,
|
|
134
176
|
forEachMember,
|