@sap/cds-compiler 2.7.0 → 2.11.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 +167 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +17 -33
- package/lib/api/options.js +25 -13
- package/lib/api/validate.js +33 -9
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +26 -2
- package/lib/base/messages.js +25 -9
- package/lib/base/model.js +5 -3
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +18 -5
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +5 -2
- package/lib/compiler/definer.js +145 -120
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +207 -47
- package/lib/compiler/shared.js +47 -200
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +94 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +302 -115
- package/lib/edm/edmUtils.js +31 -12
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5308 -4308
- package/lib/json/from-csn.js +59 -30
- package/lib/json/to-csn.js +354 -105
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +81 -14
- package/lib/language/language.g4 +163 -31
- package/lib/main.d.ts +136 -17
- package/lib/main.js +7 -1
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +115 -32
- package/lib/model/csnUtils.js +71 -33
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -16
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +60 -17
- package/lib/render/toHdbcds.js +122 -74
- package/lib/render/toSql.js +57 -32
- package/lib/render/utils/common.js +6 -10
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +9 -6
- package/lib/transform/db/expansion.js +19 -7
- package/lib/transform/db/flattening.js +31 -7
- package/lib/transform/db/transformExists.js +344 -66
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +65 -436
- package/lib/transform/forOdataNew.js +21 -10
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +55 -9
- package/lib/transform/translateAssocsToJoins.js +11 -17
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
package/lib/api/validate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { makeMessageFunction
|
|
3
|
+
const { makeMessageFunction } = require('../base/messages');
|
|
4
4
|
|
|
5
5
|
/* eslint-disable arrow-body-style */
|
|
6
6
|
const booleanValidator = {
|
|
@@ -70,6 +70,14 @@ const validators = {
|
|
|
70
70
|
return val === null ? val : `type ${ typeof val }`;
|
|
71
71
|
},
|
|
72
72
|
},
|
|
73
|
+
// TODO: Maybe do a deep validation of the whole object with leafs?
|
|
74
|
+
variableReplacements: {
|
|
75
|
+
validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
|
|
76
|
+
expected: () => 'type object',
|
|
77
|
+
found: (val) => {
|
|
78
|
+
return val === null ? val : `type ${ typeof val }`;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
73
81
|
messages: {
|
|
74
82
|
validate: val => Array.isArray(val),
|
|
75
83
|
expected: () => 'type array',
|
|
@@ -89,14 +97,32 @@ const validators = {
|
|
|
89
97
|
expected: () => 'type array of string',
|
|
90
98
|
found: val => `type ${ typeof val }`,
|
|
91
99
|
},
|
|
100
|
+
defaultBinaryLength: {
|
|
101
|
+
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
|
|
102
|
+
|
|
103
|
+
expected: () => 'Integer literal',
|
|
104
|
+
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
|
|
105
|
+
},
|
|
92
106
|
defaultStringLength: {
|
|
93
|
-
validate: val => Number.isInteger(val),
|
|
107
|
+
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
|
|
108
|
+
|
|
94
109
|
expected: () => 'Integer literal',
|
|
110
|
+
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
|
|
111
|
+
},
|
|
112
|
+
csnFlavor: {
|
|
113
|
+
validate: val => typeof val === 'string',
|
|
114
|
+
expected: () => 'type string',
|
|
95
115
|
found: val => `type ${ typeof val }`,
|
|
96
116
|
},
|
|
97
117
|
dictionaryPrototype: {
|
|
98
118
|
validate: () => true,
|
|
99
119
|
},
|
|
120
|
+
assertIntegrity: {
|
|
121
|
+
validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
|
|
122
|
+
expected: () => 'a boolean or a string with value \'individual\'',
|
|
123
|
+
found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
|
|
124
|
+
},
|
|
125
|
+
assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
|
|
100
126
|
};
|
|
101
127
|
|
|
102
128
|
const allCombinationValidators = {
|
|
@@ -122,16 +148,17 @@ const allCombinationValidators = {
|
|
|
122
148
|
* Use a custom validator or "default" custom validator, fallback to Boolean validator.
|
|
123
149
|
*
|
|
124
150
|
* @param {object} options Flat options object to validate
|
|
151
|
+
* @param {string} moduleName The called module, e.g. 'for.odata', 'to.hdi'. Needed to initialize the message functions
|
|
125
152
|
* @param {object} [customValidators] Map of custom validators to use
|
|
126
153
|
* @param {string[]} [combinationValidators] Validate option combinations
|
|
127
154
|
* @returns {void}
|
|
128
155
|
* @throws {CompilationError} Throws in case of invalid option usage
|
|
129
156
|
*/
|
|
130
|
-
function validate(options, customValidators = {}, combinationValidators = []) {
|
|
157
|
+
function validate(options, moduleName, customValidators = {}, combinationValidators = []) {
|
|
131
158
|
// TODO: issuing messages in this function looks very strange...
|
|
132
159
|
{
|
|
133
160
|
const messageCollector = { messages: [] };
|
|
134
|
-
const { error, throwWithError } = makeMessageFunction(null, messageCollector);
|
|
161
|
+
const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
|
|
135
162
|
|
|
136
163
|
for (const optionName of Object.keys(options)) {
|
|
137
164
|
const optionValue = options[optionName];
|
|
@@ -143,7 +170,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
|
|
|
143
170
|
throwWithError();
|
|
144
171
|
}
|
|
145
172
|
|
|
146
|
-
const message = makeMessageFunction(null, options);
|
|
173
|
+
const message = makeMessageFunction(null, options, moduleName);
|
|
147
174
|
|
|
148
175
|
for (const combinationValidatorName of combinationValidators.concat([ 'beta-no-test' ])) {
|
|
149
176
|
const combinationValidator = allCombinationValidators[combinationValidatorName];
|
|
@@ -151,10 +178,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
|
|
|
151
178
|
message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
|
|
152
179
|
}
|
|
153
180
|
|
|
154
|
-
|
|
155
|
-
// But be aware that it only throws with non-configurable errors and that this
|
|
156
|
-
// will lead to issues in test3. See #6037
|
|
157
|
-
handleMessages(undefined, options);
|
|
181
|
+
message.throwWithError();
|
|
158
182
|
}
|
|
159
183
|
/* eslint-enable jsdoc/no-undefined-types */
|
|
160
184
|
|
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
|
|
|
@@ -405,9 +405,6 @@ function toRenameWithCsn(csn, options) {
|
|
|
405
405
|
|
|
406
406
|
function alterConstraintsWithCsn(csn, options) {
|
|
407
407
|
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
408
|
|
|
412
409
|
const {
|
|
413
410
|
drop, alter, names, src, violations
|
|
@@ -420,6 +417,10 @@ function alterConstraintsWithCsn(csn, options) {
|
|
|
420
417
|
dialect: 'hana',
|
|
421
418
|
names: names || 'plain'
|
|
422
419
|
}
|
|
420
|
+
|
|
421
|
+
// Of course we want the database constraints
|
|
422
|
+
options.assertIntegrityType = 'DB';
|
|
423
|
+
|
|
423
424
|
const transformedOptions = transformSQLOptions(csn, options);
|
|
424
425
|
const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
|
|
425
426
|
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
|
@@ -194,7 +194,7 @@ module.exports = {
|
|
|
194
194
|
'OTHERS',
|
|
195
195
|
'TIES',
|
|
196
196
|
],
|
|
197
|
-
// HANA keywords, used
|
|
197
|
+
// HANA keywords, used for smart quoting in to-hdi.plain
|
|
198
198
|
// Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html
|
|
199
199
|
// Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
|
|
200
200
|
// in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
|
|
@@ -673,5 +673,35 @@ module.exports = {
|
|
|
673
673
|
'WITHIN',
|
|
674
674
|
'XMLTABLE',
|
|
675
675
|
'YEAR'
|
|
676
|
-
]
|
|
676
|
+
],
|
|
677
|
+
// HANA CDS keywords, used for smart quoting in to-hdbcds.plain
|
|
678
|
+
hdbcds: [
|
|
679
|
+
'ALL', 'ALTER', 'AS',
|
|
680
|
+
'BEFORE', 'BEGIN', 'BOTH',
|
|
681
|
+
'CASE', 'CHAR', 'CONDITION',
|
|
682
|
+
'CONNECT', 'CROSS', 'CUBE',
|
|
683
|
+
'CURRENT_CONNECTION', 'CURRENT_DATE', 'CURRENT_SCHEMA',
|
|
684
|
+
'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER',
|
|
685
|
+
'CURRENT_UTCDATE', 'CURRENT_UTCTIME', 'CURRENT_UTCTIMESTAMP',
|
|
686
|
+
'CURRVAL', 'CURSOR', 'DECLARE',
|
|
687
|
+
'DISTINCT', 'ELSE', 'ELSEIF',
|
|
688
|
+
'ELSIF', 'END', 'EXCEPT',
|
|
689
|
+
'EXCEPTION', 'EXEC', 'FOR',
|
|
690
|
+
'FROM', 'FULL', 'GROUP',
|
|
691
|
+
'HAVING', 'IF', 'IN',
|
|
692
|
+
'INNER', 'INOUT', 'INTERSECT',
|
|
693
|
+
'INTO', 'IS', 'JOIN',
|
|
694
|
+
'LEADING', 'LEFT', 'LIMIT',
|
|
695
|
+
'LOOP', 'MINUS', 'NATURAL',
|
|
696
|
+
'NEXTVAL', 'NULL', 'ON',
|
|
697
|
+
'ORDER', 'OUT', 'OUTER',
|
|
698
|
+
'PRIOR', 'RETURN', 'RETURNS',
|
|
699
|
+
'REVERSE', 'RIGHT', 'ROLLUP',
|
|
700
|
+
'ROWID', 'SELECT', 'SET',
|
|
701
|
+
'SQL', 'START', 'SYSDATE',
|
|
702
|
+
'SYSTIME', 'SYSTIMESTAMP', 'SYSUUID',
|
|
703
|
+
'TOP', 'TRAILING', 'UNION',
|
|
704
|
+
'USING', 'VALUES', 'WHEN',
|
|
705
|
+
'WHERE', 'WHILE', 'WITH'
|
|
706
|
+
]
|
|
677
707
|
}
|
|
@@ -118,6 +118,7 @@ const centralMessages = {
|
|
|
118
118
|
'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
119
119
|
'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
120
120
|
'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
|
|
121
|
+
'syntax-csn-expected-length': { severity: 'Error' },
|
|
121
122
|
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
|
|
122
123
|
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
|
|
123
124
|
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
|
|
@@ -132,6 +133,11 @@ const centralMessages = {
|
|
|
132
133
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
133
134
|
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
134
135
|
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
136
|
+
'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
|
|
137
|
+
'odata-spec-violation-no-key': { severity: 'Warning' },
|
|
138
|
+
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
139
|
+
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
140
|
+
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
135
141
|
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
|
|
136
142
|
'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
|
|
137
143
|
};
|
|
@@ -156,6 +162,11 @@ const centralMessageTexts = {
|
|
|
156
162
|
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
|
|
157
163
|
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
|
|
158
164
|
},
|
|
165
|
+
'syntax-csn-expected-length': {
|
|
166
|
+
std: 'Expected array in $(PROP) to have at least $(N) items',
|
|
167
|
+
one: 'Expected array in $(PROP) to have at least one item',
|
|
168
|
+
suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
|
|
169
|
+
},
|
|
159
170
|
'ref-undefined-def': {
|
|
160
171
|
std: 'Artifact $(ART) has not been found',
|
|
161
172
|
// TODO: proposal 'No definition of $(NAME) found',
|
|
@@ -199,13 +210,13 @@ const centralMessageTexts = {
|
|
|
199
210
|
'duplicate-definition': {
|
|
200
211
|
std: 'Duplicate definition of $(NAME)',
|
|
201
212
|
absolute: 'Duplicate definition of artifact $(NAME)',
|
|
202
|
-
|
|
213
|
+
annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
|
|
203
214
|
element: 'Duplicate definition of element $(NAME)',
|
|
204
215
|
enum: 'Duplicate definition of enum $(NAME)',
|
|
205
216
|
key: 'Duplicate definition of key $(NAME)',
|
|
206
217
|
action: 'Duplicate definition of action or function $(NAME)',
|
|
207
218
|
param: 'Duplicate definition of parameter $(NAME)',
|
|
208
|
-
|
|
219
|
+
alias: 'Duplicate definition of table alias or mixin $(NAME)',
|
|
209
220
|
},
|
|
210
221
|
|
|
211
222
|
// TODO: rename to ref-expected-XYZ
|
|
@@ -246,6 +257,19 @@ const centralMessageTexts = {
|
|
|
246
257
|
'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
|
|
247
258
|
'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
|
|
248
259
|
// version independent messages
|
|
260
|
+
'odata-spec-violation-key-array': {
|
|
261
|
+
std: 'Unexpected array type for element $(NAME)',
|
|
262
|
+
scalar: 'Unexpected array type'
|
|
263
|
+
},
|
|
264
|
+
'odata-spec-violation-key-null': {
|
|
265
|
+
std: 'Expected key element $(NAME) to be not nullable', // structured
|
|
266
|
+
scalar: 'Expected key element to be not nullable' // flat
|
|
267
|
+
},
|
|
268
|
+
'odata-spec-violation-key-type': {
|
|
269
|
+
std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
|
|
270
|
+
scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
|
|
271
|
+
},
|
|
272
|
+
'odata-spec-violation-no-key': 'Expected entity to have a primary key',
|
|
249
273
|
'odata-spec-violation-type': 'Expected element to have a type',
|
|
250
274
|
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
|
|
251
275
|
'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
|
|
|
@@ -649,6 +651,8 @@ const paramsTransform = {
|
|
|
649
651
|
// more complex convenience:
|
|
650
652
|
names: transformManyWith( quoted ),
|
|
651
653
|
number: n => n,
|
|
654
|
+
line: l => l,
|
|
655
|
+
col: c => c,
|
|
652
656
|
literal: l => l,
|
|
653
657
|
art: transformArg,
|
|
654
658
|
service: transformArg,
|
|
@@ -851,19 +855,26 @@ function messageHash(msg) {
|
|
|
851
855
|
*
|
|
852
856
|
* Example:
|
|
853
857
|
* ```txt
|
|
854
|
-
* Error[message-id]: Can't find type `nu` in this scope
|
|
858
|
+
* Error[message-id]: Can't find type `nu` in this scope
|
|
855
859
|
* |
|
|
856
|
-
* <source>.cds:3:11, at entity:“E”
|
|
860
|
+
* <source>.cds:3:11, at entity:“E”/element:“e”
|
|
857
861
|
* ```
|
|
862
|
+
*
|
|
858
863
|
* @param {CSN.Message} err
|
|
859
864
|
* @param {object} [config = {}]
|
|
860
865
|
* @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
|
|
861
866
|
* @param {boolean} [config.noMessageId]
|
|
862
867
|
* @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
|
|
863
|
-
* @param {boolean} [config.withLineSpacer]
|
|
868
|
+
* @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
|
|
869
|
+
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
|
|
870
|
+
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
871
|
+
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
872
|
+
* unset.
|
|
864
873
|
* @returns {string}
|
|
865
874
|
*/
|
|
866
875
|
function messageStringMultiline( err, config = {} ) {
|
|
876
|
+
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
877
|
+
|
|
867
878
|
const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
|
|
868
879
|
const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
|
|
869
880
|
const home = !err.home ? '' : ('at ' + err.home);
|
|
@@ -876,7 +887,7 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
876
887
|
location += ', '
|
|
877
888
|
}
|
|
878
889
|
else if (!home)
|
|
879
|
-
return
|
|
890
|
+
return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
|
|
880
891
|
|
|
881
892
|
let lineSpacer = '';
|
|
882
893
|
if (config.withLineSpacer) {
|
|
@@ -884,8 +895,7 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
884
895
|
lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
|
|
885
896
|
}
|
|
886
897
|
|
|
887
|
-
|
|
888
|
-
return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
|
|
898
|
+
return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
|
|
889
899
|
}
|
|
890
900
|
|
|
891
901
|
/**
|
|
@@ -899,9 +909,15 @@ function messageStringMultiline( err, config = {} ) {
|
|
|
899
909
|
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
|
|
900
910
|
* from `lib/utils/file.js`
|
|
901
911
|
* @param {CSN.Message} err Error object containing all details like line, message, etc.
|
|
912
|
+
* @param {object} [config = {}]
|
|
913
|
+
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
|
|
914
|
+
* coloring will be used. If 'auto', we will decide based on certain factors such
|
|
915
|
+
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
|
|
916
|
+
* unset.
|
|
902
917
|
* @returns {string}
|
|
903
918
|
*/
|
|
904
|
-
function messageContext(sourceLines, err) {
|
|
919
|
+
function messageContext(sourceLines, err, config) {
|
|
920
|
+
colorTerm.changeColorMode(config ? config.color : 'auto');
|
|
905
921
|
const MAX_COL_LENGTH = 100;
|
|
906
922
|
|
|
907
923
|
const loc = err.$location;
|
|
@@ -953,7 +969,7 @@ function messageContext(sourceLines, err) {
|
|
|
953
969
|
// Indicate that the error is further to the right.
|
|
954
970
|
if (endColumn === MAX_COL_LENGTH)
|
|
955
971
|
highlighter = highlighter.replace(' ^', '..^');
|
|
956
|
-
msg += indent + '| ' +
|
|
972
|
+
msg += indent + '| ' + colorTerm.severity(severity, highlighter);
|
|
957
973
|
|
|
958
974
|
} else if (maxLine !== endLine) {
|
|
959
975
|
// error spans more lines which we don't print
|
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,
|
|
@@ -54,15 +53,18 @@ function isBetaEnabled( options, feature ) {
|
|
|
54
53
|
/**
|
|
55
54
|
* Test for deprecated feature, stored in option `deprecated`.
|
|
56
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.
|
|
57
57
|
*
|
|
58
58
|
* Please do not move this function to the "option processor" code.
|
|
59
59
|
*
|
|
60
60
|
* @param {object} options Options
|
|
61
|
-
* @param {string} feature Feature to check for
|
|
61
|
+
* @param {string} [feature] Feature to check for
|
|
62
62
|
* @returns {boolean}
|
|
63
63
|
*/
|
|
64
|
-
function isDeprecatedEnabled( options, feature ) {
|
|
64
|
+
function isDeprecatedEnabled( options, feature = null ) {
|
|
65
65
|
const { deprecated } = options;
|
|
66
|
+
if(!feature)
|
|
67
|
+
return !!deprecated;
|
|
66
68
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
67
69
|
}
|
|
68
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;
|
|
@@ -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 = {
|
|
@@ -59,6 +59,11 @@ function otherSideIsValidDollarSelf(on, startIndex) {
|
|
|
59
59
|
*/
|
|
60
60
|
function validateOnCondition(member, memberName, property, path) {
|
|
61
61
|
if (member && member.on) {
|
|
62
|
+
// complain about nullability constraint on managed composition
|
|
63
|
+
if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
|
|
64
|
+
this.warning(null, path.concat([ 'on' ]),
|
|
65
|
+
'Unexpected nullability constraint defined on managed composition');
|
|
66
|
+
}
|
|
62
67
|
for (let i = 0; i < member.on.length; i++) {
|
|
63
68
|
if (member.on[i].ref) {
|
|
64
69
|
const { ref } = member.on[i];
|
|
@@ -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);
|
package/lib/checks/types.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
3
|
+
const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
|
|
4
4
|
|
|
5
5
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Scale must not be 'variable' or 'floating'
|
|
9
|
+
*
|
|
10
|
+
* scale property is always propagated
|
|
11
|
+
*
|
|
12
|
+
* @param {CSN.Element} member the element to be checked
|
|
13
|
+
* @param {string} memberName the elements name
|
|
14
|
+
* @param {string} prop which kind of member are we looking at -> only prop "elements"
|
|
15
|
+
* @param {CSN.Path} path the path to the member
|
|
16
|
+
*/
|
|
17
|
+
function checkDecimalScale(member, memberName, prop, path) {
|
|
18
|
+
if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
|
|
19
|
+
// skip is already filtered in validator, here for completeness
|
|
20
|
+
hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
|
|
21
|
+
return;
|
|
22
|
+
if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
|
|
23
|
+
this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
/**
|
|
8
27
|
* View parameter for hana must be of scalar type
|
|
9
28
|
*
|
|
@@ -165,4 +184,9 @@ function hasArtifactTypeInformation(artifact) {
|
|
|
165
184
|
artifact.type; // => `type A : [type of] Integer`
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
module.exports = {
|
|
187
|
+
module.exports = {
|
|
188
|
+
checkTypeDefinitionHasType,
|
|
189
|
+
checkElementTypeDefinitionHasType,
|
|
190
|
+
checkTypeIsScalar,
|
|
191
|
+
checkDecimalScale,
|
|
192
|
+
};
|