@sap/cds-compiler 2.10.2 → 2.11.4
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 +90 -5
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +3 -1
- package/bin/cdsc.js +49 -25
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +8 -36
- package/lib/api/options.js +15 -6
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +34 -10
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +5 -4
- package/lib/base/optionProcessorHelper.js +57 -23
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/compiler/assert-consistency.js +9 -2
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +2 -1
- package/lib/compiler/definer.js +66 -108
- package/lib/compiler/index.js +29 -29
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +225 -58
- package/lib/compiler/shared.js +53 -229
- package/lib/compiler/utils.js +184 -0
- package/lib/edm/annotations/genericTranslation.js +1 -1
- package/lib/edm/csn2edm.js +3 -2
- package/lib/edm/edmPreprocessor.js +34 -38
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +17 -1
- package/lib/gen/language.tokens +79 -73
- package/lib/gen/languageLexer.interp +19 -1
- package/lib/gen/languageLexer.js +779 -731
- package/lib/gen/languageLexer.tokens +71 -65
- package/lib/gen/languageParser.js +4668 -4072
- package/lib/json/from-csn.js +10 -10
- package/lib/json/to-csn.js +228 -47
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +73 -14
- package/lib/language/language.g4 +79 -3
- package/lib/main.d.ts +215 -18
- package/lib/main.js +3 -1
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +117 -33
- package/lib/model/csnUtils.js +65 -133
- package/lib/model/enrichCsn.js +62 -37
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +15 -8
- package/lib/render/toHdbcds.js +26 -49
- package/lib/render/toSql.js +61 -39
- package/lib/render/utils/common.js +1 -1
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +3 -2
- package/lib/transform/db/expansion.js +6 -4
- package/lib/transform/db/flattening.js +19 -3
- package/lib/transform/db/transformExists.js +102 -9
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +93 -448
- package/lib/transform/forOdataNew.js +9 -2
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/structuralPath.js +1 -5
- package/lib/transform/transformUtilsNew.js +22 -8
- package/lib/transform/translateAssocsToJoins.js +7 -15
- package/lib/utils/file.js +11 -5
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/backends.js
CHANGED
|
@@ -14,7 +14,7 @@ const { csn2edm, csn2edmAll } = require('./edm/csn2edm');
|
|
|
14
14
|
const { mergeOptions } = require('./model/csnUtils');
|
|
15
15
|
const { isBetaEnabled } = require('./base/model');
|
|
16
16
|
const { optionProcessor } = require('./optionProcessor')
|
|
17
|
-
const timetrace = require('./utils/timetrace');
|
|
17
|
+
const { timetrace } = require('./utils/timetrace');
|
|
18
18
|
const { makeMessageFunction } = require('./base/messages');
|
|
19
19
|
const { forEachDefinition } = require('./model/csnUtils');
|
|
20
20
|
|
|
@@ -92,9 +92,9 @@ function toOdataWithCsn(csn, options) {
|
|
|
92
92
|
// Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
|
|
93
93
|
// using 'options'
|
|
94
94
|
function preparedCsnToEdmx(csn, service, options) {
|
|
95
|
-
|
|
95
|
+
const e = csn2edm(csn, service, options)
|
|
96
96
|
return {
|
|
97
|
-
edmx
|
|
97
|
+
edmx: (e ? e.toXML('all') : undefined)
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -115,9 +115,9 @@ function preparedCsnToEdmxAll(csn, options) {
|
|
|
115
115
|
function preparedCsnToEdm(csn, service, options) {
|
|
116
116
|
// Merge options; override OData version as edm json is always v4
|
|
117
117
|
options = mergeOptions(options, { toOdata : { version : 'v4' }});
|
|
118
|
-
const
|
|
118
|
+
const e = csn2edm(csn, service, options);
|
|
119
119
|
return {
|
|
120
|
-
edmj
|
|
120
|
+
edmj: (e ? e.toJSON() : undefined)
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -303,9 +303,9 @@ function transformSQLOptions(model, options) {
|
|
|
303
303
|
}
|
|
304
304
|
// move the locale option(if provided) under user.locale
|
|
305
305
|
if (options.locale) {
|
|
306
|
-
options.user
|
|
306
|
+
options.user = options.user
|
|
307
307
|
? Object.assign(options.user, { locale: options.locale })
|
|
308
|
-
:
|
|
308
|
+
: { locale: options.locale };
|
|
309
309
|
delete options.locale;
|
|
310
310
|
}
|
|
311
311
|
}
|
|
@@ -395,19 +395,14 @@ function toRenameWithCsn(csn, options) {
|
|
|
395
395
|
});
|
|
396
396
|
|
|
397
397
|
// Assemble result
|
|
398
|
-
|
|
398
|
+
return {
|
|
399
399
|
rename : toRenameDdl(forHanaCsn, options),
|
|
400
400
|
options
|
|
401
401
|
};
|
|
402
|
-
|
|
403
|
-
return result;
|
|
404
402
|
}
|
|
405
403
|
|
|
406
404
|
function alterConstraintsWithCsn(csn, options) {
|
|
407
405
|
const { error } = makeMessageFunction(csn, options, 'manageConstraints');
|
|
408
|
-
// Requires beta mode
|
|
409
|
-
if (!isBetaEnabled(options, 'foreignKeyConstraints'))
|
|
410
|
-
error(null, null, 'ALTER TABLE statements for adding/modifying referential constraints are only available in beta mode');
|
|
411
406
|
|
|
412
407
|
const {
|
|
413
408
|
drop, alter, names, src, violations
|
|
@@ -420,6 +415,10 @@ function alterConstraintsWithCsn(csn, options) {
|
|
|
420
415
|
dialect: 'hana',
|
|
421
416
|
names: names || 'plain'
|
|
422
417
|
}
|
|
418
|
+
|
|
419
|
+
// Of course we want the database constraints
|
|
420
|
+
options.assertIntegrityType = 'DB';
|
|
421
|
+
|
|
423
422
|
const transformedOptions = transformSQLOptions(csn, options);
|
|
424
423
|
const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
|
|
425
424
|
const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions, 'to.sql');
|
package/lib/base/dictionaries.js
CHANGED
|
@@ -28,6 +28,7 @@ function dictAdd( dict, name, entry, duplicateCallback ) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function dictForEach( dict, callback ) {
|
|
31
|
+
// TODO: probably define an extra dictForEachArray()
|
|
31
32
|
for (const name in dict) {
|
|
32
33
|
const entry = dict[name];
|
|
33
34
|
if (Array.isArray(entry)) {
|
|
@@ -35,7 +36,7 @@ function dictForEach( dict, callback ) {
|
|
|
35
36
|
}
|
|
36
37
|
else {
|
|
37
38
|
callback( entry );
|
|
38
|
-
if (Array.isArray(entry.$duplicates))
|
|
39
|
+
if (Array.isArray( entry.$duplicates ))
|
|
39
40
|
entry.$duplicates.forEach( callback );
|
|
40
41
|
}
|
|
41
42
|
}
|
package/lib/base/keywords.js
CHANGED
|
@@ -11,7 +11,7 @@ module.exports = {
|
|
|
11
11
|
'DISTINCT',
|
|
12
12
|
'EXISTS',
|
|
13
13
|
'EXTRACT',
|
|
14
|
-
'FALSE',
|
|
14
|
+
'FALSE', // boolean
|
|
15
15
|
'FROM',
|
|
16
16
|
'IN',
|
|
17
17
|
'KEY',
|
|
@@ -22,8 +22,9 @@ module.exports = {
|
|
|
22
22
|
'ON',
|
|
23
23
|
'SELECT',
|
|
24
24
|
'SOME',
|
|
25
|
+
'WHEN',
|
|
25
26
|
'TRIM',
|
|
26
|
-
'TRUE',
|
|
27
|
+
'TRUE', // boolean
|
|
27
28
|
'WHERE',
|
|
28
29
|
'WITH',
|
|
29
30
|
],
|
|
@@ -47,6 +47,7 @@ const centralMessages = {
|
|
|
47
47
|
'anno-undefined-def': { severity: 'Info' }, // for annotate statement (for CSN or CDL path cont)
|
|
48
48
|
'anno-undefined-element': { severity: 'Info' },
|
|
49
49
|
'anno-undefined-param': { severity: 'Info' },
|
|
50
|
+
'anno-unstable-hdbcds': { severity: 'Warning' },
|
|
50
51
|
|
|
51
52
|
'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
|
|
52
53
|
'args-no-params': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
|
|
@@ -60,7 +61,6 @@ const centralMessages = {
|
|
|
60
61
|
|
|
61
62
|
'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
|
|
62
63
|
|
|
63
|
-
'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
|
|
64
64
|
'empty-type': { severity: 'Info' }, // only still an error in old transformers
|
|
65
65
|
|
|
66
66
|
// Structured types were warned about but made CSN un-recompilable.
|
|
@@ -69,7 +69,7 @@ const centralMessages = {
|
|
|
69
69
|
// TODO: rename to ref-expected-XYZ
|
|
70
70
|
'expected-type': { severity: 'Error' },
|
|
71
71
|
'ref-sloppy-type': { severity: 'Error' },
|
|
72
|
-
'
|
|
72
|
+
'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
|
|
73
73
|
'expected-actionparam-type': { severity: 'Error' },
|
|
74
74
|
'ref-sloppy-actionparam-type': { severity: 'Error' },
|
|
75
75
|
'expected-event-type': { severity: 'Error' },
|
|
@@ -89,6 +89,7 @@ const centralMessages = {
|
|
|
89
89
|
'query-undefined-element': { severity: 'Error' },
|
|
90
90
|
'query-unexpected-assoc-hdbcds': { severity: 'Error' },
|
|
91
91
|
'query-unexpected-structure-hdbcds': { severity: 'Error' },
|
|
92
|
+
'query-ignoring-param-nullability': { severity: 'Info' },
|
|
92
93
|
|
|
93
94
|
'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
|
|
94
95
|
'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
|
|
@@ -99,6 +100,7 @@ const centralMessages = {
|
|
|
99
100
|
'ref-undefined-def': { severity: 'Error' },
|
|
100
101
|
'ref-undefined-var': { severity: 'Error' },
|
|
101
102
|
'ref-undefined-element': { severity: 'Error' },
|
|
103
|
+
'ref-unknown-var': { severity: 'Info' },
|
|
102
104
|
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
103
105
|
'ref-undefined-param': { severity: 'Error' },
|
|
104
106
|
'ref-rejected-on': { severity: 'Error' },
|
|
@@ -118,6 +120,7 @@ const centralMessages = {
|
|
|
118
120
|
'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
119
121
|
'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
120
122
|
'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
|
|
123
|
+
'syntax-csn-expected-length': { severity: 'Error' },
|
|
121
124
|
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
|
|
122
125
|
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
|
|
123
126
|
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
|
|
@@ -127,12 +130,15 @@ const centralMessages = {
|
|
|
127
130
|
|
|
128
131
|
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
|
|
129
132
|
|
|
133
|
+
'def-missing-element': { severity: 'Error' },
|
|
134
|
+
|
|
130
135
|
'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
|
|
131
136
|
'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
|
|
132
137
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
133
138
|
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
134
139
|
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
135
140
|
'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
|
|
141
|
+
'odata-spec-violation-no-key': { severity: 'Warning' },
|
|
136
142
|
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
137
143
|
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
138
144
|
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
@@ -160,6 +166,11 @@ const centralMessageTexts = {
|
|
|
160
166
|
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
|
|
161
167
|
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
|
|
162
168
|
},
|
|
169
|
+
'syntax-csn-expected-length': {
|
|
170
|
+
std: 'Expected array in $(PROP) to have at least $(N) items',
|
|
171
|
+
one: 'Expected array in $(PROP) to have at least one item',
|
|
172
|
+
suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
|
|
173
|
+
},
|
|
163
174
|
'ref-undefined-def': {
|
|
164
175
|
std: 'Artifact $(ART) has not been found',
|
|
165
176
|
// TODO: proposal 'No definition of $(NAME) found',
|
|
@@ -171,17 +182,20 @@ const centralMessageTexts = {
|
|
|
171
182
|
std: 'Element $(ART) has not been found',
|
|
172
183
|
element: 'Artifact $(ART) has no element $(MEMBER)'
|
|
173
184
|
},
|
|
185
|
+
'ref-unknown-var': {
|
|
186
|
+
std: 'Replacement $(ID) not found'
|
|
187
|
+
},
|
|
174
188
|
'ref-rejected-on': {
|
|
175
189
|
std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
|
|
176
190
|
mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
|
|
177
191
|
alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
|
|
178
192
|
},
|
|
179
|
-
'
|
|
180
|
-
std: '
|
|
181
|
-
type: '
|
|
182
|
-
event: '
|
|
183
|
-
param: '
|
|
184
|
-
select: '
|
|
193
|
+
'type-unexpected-typeof': {
|
|
194
|
+
std: 'Unexpected $(KEYWORD) for the type reference here',
|
|
195
|
+
type: 'Unexpected $(KEYWORD) in type of a type definition',
|
|
196
|
+
event: 'Unexpected $(KEYWORD) for the type of an event',
|
|
197
|
+
param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
|
|
198
|
+
select: 'Unexpected $(KEYWORD) for type references in queries',
|
|
185
199
|
},
|
|
186
200
|
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
|
|
187
201
|
'anno-undefined-def': 'Artifact $(ART) has not been found',
|
|
@@ -200,16 +214,21 @@ const centralMessageTexts = {
|
|
|
200
214
|
param: 'Artifact $(ART) has no parameter $(MEMBER)'
|
|
201
215
|
},
|
|
202
216
|
|
|
217
|
+
'def-missing-element': {
|
|
218
|
+
std: 'Expecting entity to have at least one non-virtual element',
|
|
219
|
+
view: 'Expecting view to have at least one non-virtual element'
|
|
220
|
+
},
|
|
221
|
+
|
|
203
222
|
'duplicate-definition': {
|
|
204
223
|
std: 'Duplicate definition of $(NAME)',
|
|
205
224
|
absolute: 'Duplicate definition of artifact $(NAME)',
|
|
206
|
-
|
|
225
|
+
annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
|
|
207
226
|
element: 'Duplicate definition of element $(NAME)',
|
|
208
227
|
enum: 'Duplicate definition of enum $(NAME)',
|
|
209
228
|
key: 'Duplicate definition of key $(NAME)',
|
|
210
229
|
action: 'Duplicate definition of action or function $(NAME)',
|
|
211
230
|
param: 'Duplicate definition of parameter $(NAME)',
|
|
212
|
-
|
|
231
|
+
alias: 'Duplicate definition of table alias or mixin $(NAME)',
|
|
213
232
|
},
|
|
214
233
|
|
|
215
234
|
// TODO: rename to ref-expected-XYZ
|
|
@@ -228,6 +247,10 @@ const centralMessageTexts = {
|
|
|
228
247
|
|
|
229
248
|
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
|
|
230
249
|
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
|
|
250
|
+
'query-ignoring-param-nullability': {
|
|
251
|
+
std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
|
|
252
|
+
sql: 'Ignoring nullability constraint on parameter when generating SQL view'
|
|
253
|
+
},
|
|
231
254
|
|
|
232
255
|
'ref-sloppy-type': 'A type or an element is expected here',
|
|
233
256
|
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
|
|
@@ -262,6 +285,7 @@ const centralMessageTexts = {
|
|
|
262
285
|
std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
|
|
263
286
|
scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
|
|
264
287
|
},
|
|
288
|
+
'odata-spec-violation-no-key': 'Expected entity to have a primary key',
|
|
265
289
|
'odata-spec-violation-type': 'Expected element to have a type',
|
|
266
290
|
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
|
|
267
291
|
'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
|
package/lib/base/messages.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
-
const term = require('../utils/term');
|
|
7
|
+
const { term } = require('../utils/term');
|
|
8
8
|
const { locationString } = require('./location');
|
|
9
9
|
const { isDeprecatedEnabled } = require('./model');
|
|
10
10
|
const { centralMessages, centralMessageTexts } = require('./message-registry');
|
|
@@ -15,6 +15,8 @@ const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
|
|
18
|
+
// term instance for messages
|
|
19
|
+
const colorTerm = term();
|
|
18
20
|
|
|
19
21
|
// Functions ensuring message consistency during runtime with --test-mode
|
|
20
22
|
|
|
@@ -670,13 +672,16 @@ function ignoreTextTransform() {
|
|
|
670
672
|
|
|
671
673
|
function transformManyWith( t, sorted ) {
|
|
672
674
|
return function transformMany( many, r, args, texts ) {
|
|
673
|
-
const prop = ['none','one'][ many.length ];
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
675
|
+
const prop = ['none','one','two'][ many.length ];
|
|
676
|
+
const names = many.map(t);
|
|
677
|
+
if (sorted)
|
|
678
|
+
names.sort();
|
|
679
|
+
if (!prop || !texts[prop] || args['#'] )
|
|
680
|
+
return names.join(', ');
|
|
678
681
|
r['#'] = prop; // text variant
|
|
679
|
-
|
|
682
|
+
if (many.length === 2)
|
|
683
|
+
r.second = names[1];
|
|
684
|
+
return many.length && names[0];
|
|
680
685
|
};
|
|
681
686
|
}
|
|
682
687
|
|
|
@@ -791,7 +796,7 @@ function replaceInString( text, params ) {
|
|
|
791
796
|
start = pattern.lastIndex;
|
|
792
797
|
}
|
|
793
798
|
parts.push( text.substring( start ) );
|
|
794
|
-
let remain = ('#'
|
|
799
|
+
let remain = (params['#']) ? [] : Object.keys( params ).filter( n => params[n] );
|
|
795
800
|
return (remain.length)
|
|
796
801
|
? parts.join('') + '; ' +
|
|
797
802
|
remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
|
|
@@ -843,7 +848,10 @@ function messageHash(msg) {
|
|
|
843
848
|
if(!msg.home)
|
|
844
849
|
return messageString(msg);
|
|
845
850
|
const copy = {...msg};
|
|
846
|
-
|
|
851
|
+
// Note: This is a hack. deduplicateMessages() would otherwise remove
|
|
852
|
+
// all but one message about duplicated artifacts.
|
|
853
|
+
if (!msg.messageId || !msg.messageId.includes('duplicate'))
|
|
854
|
+
copy.$location = undefined;
|
|
847
855
|
return messageString(copy);
|
|
848
856
|
}
|
|
849
857
|
|
|
@@ -853,19 +861,26 @@ function messageHash(msg) {
|
|
|
853
861
|
*
|
|
854
862
|
* Example:
|
|
855
863
|
* ```txt
|
|
856
|
-
* Error[message-id]: Can't find type `nu` in this scope
|
|
864
|
+
* Error[message-id]: Can't find type `nu` in this scope
|
|
857
865
|
* |
|
|
858
|
-
* <source>.cds:3:11, at entity:“E”
|
|
866
|
+
* <source>.cds:3:11, at entity:“E”/element:“e”
|
|
859
867
|
* ```
|
|
868
|
+
*
|
|
860
869
|
* @param {CSN.Message} err
|
|
861
870
|
* @param {object} [config = {}]
|
|
862
871
|
* @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
|
|
863
872
|
* @param {boolean} [config.noMessageId]
|
|
864
873
|
* @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
|
|
865
|
-
* @param {boolean} [config.withLineSpacer]
|
|
874
|
+
* @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
|
|
875
|
+
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
|
|
876
|
+
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
877
|
+
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
878
|
+
* unset.
|
|
866
879
|
* @returns {string}
|
|
867
880
|
*/
|
|
868
881
|
function messageStringMultiline( err, config = {} ) {
|
|
882
|
+
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
883
|
+
|
|
869
884
|
const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
|
|
870
885
|
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
|
|
871
886
|
const home = !err.home ? '' : ('at ' + err.home);
|
|
@@ -878,7 +893,7 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
878
893
|
location += ', '
|
|
879
894
|
}
|
|
880
895
|
else if (!home)
|
|
881
|
-
return
|
|
896
|
+
return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
|
|
882
897
|
|
|
883
898
|
let lineSpacer = '';
|
|
884
899
|
if (config.withLineSpacer) {
|
|
@@ -886,8 +901,7 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
886
901
|
lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
|
|
887
902
|
}
|
|
888
903
|
|
|
889
|
-
|
|
890
|
-
return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
|
|
904
|
+
return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
|
|
891
905
|
}
|
|
892
906
|
|
|
893
907
|
/**
|
|
@@ -901,9 +915,15 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
901
915
|
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
|
|
902
916
|
* from `lib/utils/file.js`
|
|
903
917
|
* @param {CSN.Message} err Error object containing all details like line, message, etc.
|
|
918
|
+
* @param {object} [config = {}]
|
|
919
|
+
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
|
|
920
|
+
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
921
|
+
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
922
|
+
* unset.
|
|
904
923
|
* @returns {string}
|
|
905
924
|
*/
|
|
906
|
-
function messageContext(sourceLines, err) {
|
|
925
|
+
function messageContext(sourceLines, err, config) {
|
|
926
|
+
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
907
927
|
const MAX_COL_LENGTH = 100;
|
|
908
928
|
|
|
909
929
|
const loc = err.$location;
|
|
@@ -955,7 +975,7 @@ function messageContext(sourceLines, err) {
|
|
|
955
975
|
// Indicate that the error is further to the right.
|
|
956
976
|
if (endColumn === MAX_COL_LENGTH)
|
|
957
977
|
highlighter = highlighter.replace(' ^', '..^');
|
|
958
|
-
msg += indent + '| ' +
|
|
978
|
+
msg += indent + '| ' + colorTerm.severity(severity, highlighter);
|
|
959
979
|
|
|
960
980
|
} else if (maxLine !== endLine) {
|
|
961
981
|
// error spans more lines which we don't print
|
|
@@ -1068,7 +1088,7 @@ function deduplicateMessages( messages ) {
|
|
|
1068
1088
|
|
|
1069
1089
|
function shortArtName( art ) {
|
|
1070
1090
|
const { name } = art;
|
|
1071
|
-
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null ) &&
|
|
1091
|
+
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
|
|
1072
1092
|
!name.absolute.includes(':'))
|
|
1073
1093
|
return quote.name( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
|
|
1074
1094
|
return artName( art );
|
package/lib/base/model.js
CHANGED
|
@@ -19,7 +19,6 @@ const queryOps = {
|
|
|
19
19
|
*/
|
|
20
20
|
const availableBetaFlags = {
|
|
21
21
|
// enabled by --beta-mode
|
|
22
|
-
foreignKeyConstraints: true,
|
|
23
22
|
toRename: true,
|
|
24
23
|
addTextsLanguageAssoc: true,
|
|
25
24
|
assocsWithParams: true,
|
|
@@ -28,7 +27,6 @@ const availableBetaFlags = {
|
|
|
28
27
|
ignoreAssocPublishingInUnion: true,
|
|
29
28
|
nestedProjections: true,
|
|
30
29
|
enableUniversalCsn: true,
|
|
31
|
-
windowFunctions: true,
|
|
32
30
|
// disabled by --beta-mode
|
|
33
31
|
nestedServices: false,
|
|
34
32
|
};
|
|
@@ -55,15 +53,18 @@ function isBetaEnabled( options, feature ) {
|
|
|
55
53
|
/**
|
|
56
54
|
* Test for deprecated feature, stored in option `deprecated`.
|
|
57
55
|
* With that, the value of `deprecated` is a dictionary of feature=>Boolean.
|
|
56
|
+
* If no `feature` is provided, checks if any deprecated option is set.
|
|
58
57
|
*
|
|
59
58
|
* Please do not move this function to the "option processor" code.
|
|
60
59
|
*
|
|
61
60
|
* @param {object} options Options
|
|
62
|
-
* @param {string} feature Feature to check for
|
|
61
|
+
* @param {string} [feature] Feature to check for
|
|
63
62
|
* @returns {boolean}
|
|
64
63
|
*/
|
|
65
|
-
function isDeprecatedEnabled( options, feature ) {
|
|
64
|
+
function isDeprecatedEnabled( options, feature = null ) {
|
|
66
65
|
const { deprecated } = options;
|
|
66
|
+
if(!feature)
|
|
67
|
+
return !!deprecated;
|
|
67
68
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -30,7 +30,11 @@ function createOptionProcessor() {
|
|
|
30
30
|
optionClashes: [],
|
|
31
31
|
option,
|
|
32
32
|
command,
|
|
33
|
-
positionalArgument
|
|
33
|
+
positionalArgument: (argumentDefinition) => {
|
|
34
|
+
// Default positional arguments; may be overwritten by commands.
|
|
35
|
+
_positionalArguments(argumentDefinition);
|
|
36
|
+
return optionProcessor;
|
|
37
|
+
},
|
|
34
38
|
help,
|
|
35
39
|
processCmdLine,
|
|
36
40
|
verifyOptions,
|
|
@@ -66,7 +70,12 @@ function createOptionProcessor() {
|
|
|
66
70
|
/** @type {object} */
|
|
67
71
|
const command = {
|
|
68
72
|
options: {},
|
|
73
|
+
positionalArguments: [],
|
|
69
74
|
option,
|
|
75
|
+
positionalArgument: (argumentDefinition) => {
|
|
76
|
+
_positionalArguments(argumentDefinition, command.positionalArguments);
|
|
77
|
+
return command;
|
|
78
|
+
},
|
|
70
79
|
help,
|
|
71
80
|
..._parseCommandString(cmdString)
|
|
72
81
|
};
|
|
@@ -96,28 +105,34 @@ function createOptionProcessor() {
|
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
|
-
*
|
|
100
|
-
* to either require N positional arguments or a dynamic number (but at least one)
|
|
101
|
-
*
|
|
108
|
+
* Set the positional arguments to the command line processor. Instructs the processor
|
|
109
|
+
* to either require N positional arguments or a dynamic number (but at least one).
|
|
110
|
+
* Note that you can only call this function once. Only the last invocation sets
|
|
111
|
+
* the positional arguments.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
|
|
114
|
+
* @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
|
|
102
115
|
*/
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
116
|
+
function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
|
|
117
|
+
if (argList.find((arg) => arg.isDynamic)) {
|
|
105
118
|
throw new Error(`Can't add positional arguments after a dynamic one`);
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
const registeredNames =
|
|
109
|
-
const args =
|
|
121
|
+
const registeredNames = argList.map((arg) => arg.name);
|
|
122
|
+
const args = argumentDefinition.split(' ');
|
|
110
123
|
|
|
111
124
|
for (const arg of args) {
|
|
112
|
-
|
|
125
|
+
// Remove braces, dots and camelify.
|
|
126
|
+
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
|
|
127
|
+
|
|
113
128
|
if (registeredNames.includes(argName)) {
|
|
114
|
-
throw new Error(`Duplicate positional argument ${arg}`);
|
|
129
|
+
throw new Error(`Duplicate positional argument: ${arg}`);
|
|
115
130
|
}
|
|
116
131
|
if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
|
|
117
132
|
throw new Error(`Unknown positional argument syntax: ${arg}`)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
argList.push({
|
|
121
136
|
name: argName,
|
|
122
137
|
isDynamic: isDynamicPositionalArgument(arg),
|
|
123
138
|
required: true
|
|
@@ -125,7 +140,6 @@ function createOptionProcessor() {
|
|
|
125
140
|
|
|
126
141
|
registeredNames.push(argName);
|
|
127
142
|
}
|
|
128
|
-
return optionProcessor;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
/**
|
|
@@ -390,21 +404,38 @@ function createOptionProcessor() {
|
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
// Complain about first missing positional arguments
|
|
393
|
-
const missingArg =
|
|
407
|
+
const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
|
|
394
408
|
if (missingArg) {
|
|
395
|
-
result.
|
|
409
|
+
const forCommand = result.command ? ` for '${ result.command }'` : '';
|
|
410
|
+
result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
return result;
|
|
399
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Specific commands may have custom positional arguments.
|
|
417
|
+
* If the current one does, use it instead of the defaults.
|
|
418
|
+
*
|
|
419
|
+
* @returns {object[]} Array of positional argument configurations.
|
|
420
|
+
*/
|
|
421
|
+
function getCurrentPositionArguments() {
|
|
422
|
+
const cmd = optionProcessor.commands[result.command];
|
|
423
|
+
const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
|
|
424
|
+
return args;
|
|
425
|
+
}
|
|
426
|
+
|
|
400
427
|
function processPositionalArgument(argumentValue) {
|
|
401
|
-
|
|
428
|
+
const argList = getCurrentPositionArguments();
|
|
429
|
+
if ( result.args.length === 0 && argList.length === 0 )
|
|
402
430
|
return;
|
|
403
|
-
const inBounds = result.args.length <
|
|
404
|
-
const lastIndex = inBounds ? result.args.length :
|
|
405
|
-
const nextUnsetArgument =
|
|
431
|
+
const inBounds = result.args.length < argList.length;
|
|
432
|
+
const lastIndex = inBounds ? result.args.length : argList.length - 1;
|
|
433
|
+
const nextUnsetArgument = argList[lastIndex];
|
|
406
434
|
if (!inBounds && !nextUnsetArgument.isDynamic) {
|
|
407
|
-
result.
|
|
435
|
+
if (result.command)
|
|
436
|
+
result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
|
|
437
|
+
else
|
|
438
|
+
result.errors.push(`Too many arguments. Expected ${argList.length}`);
|
|
408
439
|
return;
|
|
409
440
|
}
|
|
410
441
|
result.args.length += 1;
|
|
@@ -453,7 +484,7 @@ function createOptionProcessor() {
|
|
|
453
484
|
}
|
|
454
485
|
} else {
|
|
455
486
|
result.options[opt.camelName] = value;
|
|
456
|
-
if (opt.validValues && !opt.validValues.
|
|
487
|
+
if (opt.validValues && !opt.validValues.some( validValue => validValue.toLowerCase() === value.toLowerCase() ) ) {
|
|
457
488
|
result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
|
|
458
489
|
}
|
|
459
490
|
}
|
|
@@ -486,7 +517,10 @@ function createOptionProcessor() {
|
|
|
486
517
|
}
|
|
487
518
|
|
|
488
519
|
if(options) {
|
|
489
|
-
[
|
|
520
|
+
[
|
|
521
|
+
'defaultBinaryLength', 'defaultStringLength',
|
|
522
|
+
/*'length', 'precision', 'scale'*/
|
|
523
|
+
].forEach(facet => {
|
|
490
524
|
if(options[facet] && isNaN(options[facet])) {
|
|
491
525
|
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
|
|
492
526
|
} else {
|
|
@@ -577,12 +611,12 @@ function isLongOption(opt) {
|
|
|
577
611
|
|
|
578
612
|
// Check if 'opt' looks like a "<foobar>" parameter
|
|
579
613
|
function isParam(opt) {
|
|
580
|
-
return /^<[a-zA-Z]+>$/.test(opt);
|
|
614
|
+
return /^<[a-zA-Z-]+>$/.test(opt);
|
|
581
615
|
}
|
|
582
616
|
|
|
583
617
|
// Check if 'arg' looks like "<foobar...>"
|
|
584
618
|
function isDynamicPositionalArgument(arg) {
|
|
585
|
-
return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
|
|
619
|
+
return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
|
|
586
620
|
}
|
|
587
621
|
|
|
588
622
|
module.exports = {
|
|
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
|
12
12
|
* @param {CSN.Path} path Path to the artifact
|
|
13
13
|
*/
|
|
14
14
|
function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
|
|
15
|
-
if (artifact.kind === 'entity' &&
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
|
|
16
16
|
if (!artifact.elements || !hasRealElements(artifact.elements))
|
|
17
|
-
this.error(
|
|
17
|
+
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -23,6 +23,10 @@ function validateSelectItems(query) {
|
|
|
23
23
|
'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
|
|
27
|
+
this.error(null, selectItem.$path,
|
|
28
|
+
'Window functions are not supported by SAP HANA CDS');
|
|
29
|
+
}
|
|
26
30
|
});
|
|
27
31
|
// .call() with 'this' to ensure we have access to the options
|
|
28
32
|
rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { getVariableReplacement } = require('../model/csnUtils');
|
|
4
|
+
|
|
3
5
|
// We only care about the "wild" ones - $at is validated by the compiler
|
|
4
6
|
const magicVariables = {
|
|
5
7
|
$user: [
|
|
@@ -17,7 +19,7 @@ const magicVariables = {
|
|
|
17
19
|
*
|
|
18
20
|
* Valid ways:
|
|
19
21
|
* - We know what to do -> $user.id on HANA
|
|
20
|
-
* - The user tells us what to do -> options.
|
|
22
|
+
* - The user tells us what to do -> options.variableReplacements
|
|
21
23
|
*
|
|
22
24
|
* @param {object} parent Object with the ref as a property
|
|
23
25
|
* @param {string} name Name of the ref property on parent
|
|
@@ -28,8 +30,9 @@ function unknownMagicVariable(parent, name, ref) {
|
|
|
28
30
|
const [ head, ...rest ] = ref;
|
|
29
31
|
const tail = rest.join('.');
|
|
30
32
|
const magicVariable = magicVariables[head];
|
|
31
|
-
if (magicVariable && magicVariable.indexOf(tail) === -1
|
|
32
|
-
|
|
33
|
+
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
|
+
getVariableReplacement(ref, this.options) === null)
|
|
35
|
+
this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|