@sap/cds-compiler 2.10.4 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- 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 +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- 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 +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/api/options.js
CHANGED
|
@@ -15,6 +15,7 @@ const publicOptionsNewAPI = [
|
|
|
15
15
|
'severities',
|
|
16
16
|
'messages',
|
|
17
17
|
'withLocations',
|
|
18
|
+
'defaultBinaryLength',
|
|
18
19
|
'defaultStringLength',
|
|
19
20
|
'csnFlavor',
|
|
20
21
|
// DB
|
|
@@ -23,7 +24,8 @@ const publicOptionsNewAPI = [
|
|
|
23
24
|
'sqlChangeMode',
|
|
24
25
|
'allowCsnDowngrade',
|
|
25
26
|
'joinfk',
|
|
26
|
-
'magicVars',
|
|
27
|
+
'magicVars', // deprecated
|
|
28
|
+
'variableReplacements',
|
|
27
29
|
// ODATA
|
|
28
30
|
'odataVersion',
|
|
29
31
|
'odataFormat',
|
|
@@ -47,9 +49,11 @@ const privateOptions = [
|
|
|
47
49
|
'traceParserAmb',
|
|
48
50
|
'testMode',
|
|
49
51
|
'testSortCsn',
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
52
|
+
'constraintsAsAlter',
|
|
53
|
+
'integrityNotEnforced',
|
|
54
|
+
'integrityNotValidated',
|
|
55
|
+
'assertIntegrity',
|
|
56
|
+
'assertIntegrityType',
|
|
53
57
|
'noRecompile',
|
|
54
58
|
'internalMsg',
|
|
55
59
|
'disableHanaComments', // in case of issues with hana comment rendering
|
|
@@ -80,9 +84,9 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
|
|
|
80
84
|
for (const name of overallOptions) {
|
|
81
85
|
// Ensure that arrays are not passed as a reference!
|
|
82
86
|
// This caused issues with the way messages are handled in processMessages
|
|
83
|
-
if (Array.isArray(input[name]) && inputOptionNames.
|
|
87
|
+
if (Array.isArray(input[name]) && inputOptionNames.includes(name))
|
|
84
88
|
options[name] = [ ...input[name] ];
|
|
85
|
-
else if (inputOptionNames.
|
|
89
|
+
else if (inputOptionNames.includes(name))
|
|
86
90
|
options[name] = input[name];
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -109,6 +113,10 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
|
|
|
109
113
|
mapToOldNames(optionName, optionValue);
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
// Convenience for $user -> $user.id replacement
|
|
117
|
+
if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')
|
|
118
|
+
options.variableReplacements.$user = { id: options.variableReplacements.$user };
|
|
119
|
+
|
|
112
120
|
/**
|
|
113
121
|
* Map a new-style option to it's old format
|
|
114
122
|
*
|
|
@@ -130,6 +138,7 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
|
|
|
130
138
|
case 'sqlMapping':
|
|
131
139
|
options.names = optionValue;
|
|
132
140
|
break;
|
|
141
|
+
// No need to remap variableReplacements here - we use the new mechanism with that directly
|
|
133
142
|
case 'magicVars':
|
|
134
143
|
if (optionValue.user)
|
|
135
144
|
options.user = optionValue.user;
|
|
@@ -149,7 +158,7 @@ module.exports = {
|
|
|
149
158
|
sql: (options) => {
|
|
150
159
|
const hardOptions = { src: 'sql' };
|
|
151
160
|
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
|
|
152
|
-
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
|
|
161
|
+
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming', 'constraints-as-alter-sqlite' ], 'to.sql');
|
|
153
162
|
|
|
154
163
|
const result = Object.assign({}, processed);
|
|
155
164
|
result.toSql = Object.assign({}, processed);
|
|
@@ -157,7 +166,7 @@ module.exports = {
|
|
|
157
166
|
return result;
|
|
158
167
|
},
|
|
159
168
|
hdi: (options) => {
|
|
160
|
-
const hardOptions = { src: 'hdi' };
|
|
169
|
+
const hardOptions = { src: 'hdi', constraintsAsAlter: false };
|
|
161
170
|
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
|
|
162
171
|
const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
|
|
163
172
|
|
package/lib/api/validate.js
CHANGED
|
@@ -25,13 +25,14 @@ const booleanValidator = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Generate a Validator that validates that the
|
|
27
27
|
* input is a string and one of the available options.
|
|
28
|
+
* The validation of the option values is case-insensitive.
|
|
28
29
|
*
|
|
29
30
|
* @param {any} availableValues Available values
|
|
30
31
|
* @returns {Validator} Return a validator for a string in an expected range
|
|
31
32
|
*/
|
|
32
33
|
function generateStringValidator(availableValues) {
|
|
33
34
|
return {
|
|
34
|
-
validate: val => typeof val === 'string' && availableValues.
|
|
35
|
+
validate: val => typeof val === 'string' && availableValues.some( av => av.toLowerCase() === val.toLowerCase() ),
|
|
35
36
|
expected: (val) => {
|
|
36
37
|
return typeof val !== 'string' ? 'type string' : availableValues.join(', ');
|
|
37
38
|
},
|
|
@@ -70,6 +71,14 @@ const validators = {
|
|
|
70
71
|
return val === null ? val : `type ${ typeof val }`;
|
|
71
72
|
},
|
|
72
73
|
},
|
|
74
|
+
// TODO: Maybe do a deep validation of the whole object with leafs?
|
|
75
|
+
variableReplacements: {
|
|
76
|
+
validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
|
|
77
|
+
expected: () => 'type object',
|
|
78
|
+
found: (val) => {
|
|
79
|
+
return val === null ? val : `type ${ typeof val }`;
|
|
80
|
+
},
|
|
81
|
+
},
|
|
73
82
|
messages: {
|
|
74
83
|
validate: val => Array.isArray(val),
|
|
75
84
|
expected: () => 'type array',
|
|
@@ -89,10 +98,17 @@ const validators = {
|
|
|
89
98
|
expected: () => 'type array of string',
|
|
90
99
|
found: val => `type ${ typeof val }`,
|
|
91
100
|
},
|
|
101
|
+
defaultBinaryLength: {
|
|
102
|
+
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
|
|
103
|
+
|
|
104
|
+
expected: () => 'Integer literal',
|
|
105
|
+
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
|
|
106
|
+
},
|
|
92
107
|
defaultStringLength: {
|
|
93
|
-
validate: val => Number.isInteger(val),
|
|
108
|
+
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
|
|
109
|
+
|
|
94
110
|
expected: () => 'Integer literal',
|
|
95
|
-
found: val =>
|
|
111
|
+
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
|
|
96
112
|
},
|
|
97
113
|
csnFlavor: {
|
|
98
114
|
validate: val => typeof val === 'string',
|
|
@@ -102,6 +118,12 @@ const validators = {
|
|
|
102
118
|
dictionaryPrototype: {
|
|
103
119
|
validate: () => true,
|
|
104
120
|
},
|
|
121
|
+
assertIntegrity: {
|
|
122
|
+
validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
|
|
123
|
+
expected: () => 'a boolean or a string with value \'individual\'',
|
|
124
|
+
found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
|
|
125
|
+
},
|
|
126
|
+
assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
|
|
105
127
|
};
|
|
106
128
|
|
|
107
129
|
const allCombinationValidators = {
|
|
@@ -120,6 +142,11 @@ const allCombinationValidators = {
|
|
|
120
142
|
severity: 'warning',
|
|
121
143
|
getMessage: () => 'Option "beta" was used. This option should not be used in productive scenarios!',
|
|
122
144
|
},
|
|
145
|
+
'constraints-as-alter-sqlite': {
|
|
146
|
+
validate: options => options.constraintsAsAlter && options.sqlDialect && options.sqlDialect === 'sqlite',
|
|
147
|
+
severity: 'warning',
|
|
148
|
+
getMessage: options => `Option 'constraintsAsAlter' is ignored for sqlDialect '${ options.sqlDialect }'`,
|
|
149
|
+
},
|
|
123
150
|
};
|
|
124
151
|
/* eslint-disable jsdoc/no-undefined-types */
|
|
125
152
|
/**
|
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
|
],
|
|
@@ -42,6 +42,10 @@ const centralMessages = {
|
|
|
42
42
|
'anno-definition': { severity: 'Warning' },
|
|
43
43
|
'anno-duplicate': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
44
44
|
'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
45
|
+
'anno-invalid-sql-element': { severity: 'Error'}, // @sql.prepend/append
|
|
46
|
+
'anno-invalid-sql-struct': { severity: 'Error'}, // @sql.prepend/append
|
|
47
|
+
'anno-invalid-sql-view': { severity: 'Error' }, // @sql.prepend/append
|
|
48
|
+
'anno-invalid-sql-view-element': { severity: 'Error'}, // @sql.prepend/append
|
|
45
49
|
'anno-undefined-action': { severity: 'Info' },
|
|
46
50
|
'anno-undefined-art': { severity: 'Info' }, // for annotate statement (for CDL path root)
|
|
47
51
|
'anno-undefined-def': { severity: 'Info' }, // for annotate statement (for CSN or CDL path cont)
|
|
@@ -60,7 +64,6 @@ const centralMessages = {
|
|
|
60
64
|
|
|
61
65
|
'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
|
|
62
66
|
|
|
63
|
-
'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
|
|
64
67
|
'empty-type': { severity: 'Info' }, // only still an error in old transformers
|
|
65
68
|
|
|
66
69
|
// Structured types were warned about but made CSN un-recompilable.
|
|
@@ -69,7 +72,7 @@ const centralMessages = {
|
|
|
69
72
|
// TODO: rename to ref-expected-XYZ
|
|
70
73
|
'expected-type': { severity: 'Error' },
|
|
71
74
|
'ref-sloppy-type': { severity: 'Error' },
|
|
72
|
-
'
|
|
75
|
+
'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
|
|
73
76
|
'expected-actionparam-type': { severity: 'Error' },
|
|
74
77
|
'ref-sloppy-actionparam-type': { severity: 'Error' },
|
|
75
78
|
'expected-event-type': { severity: 'Error' },
|
|
@@ -89,6 +92,7 @@ const centralMessages = {
|
|
|
89
92
|
'query-undefined-element': { severity: 'Error' },
|
|
90
93
|
'query-unexpected-assoc-hdbcds': { severity: 'Error' },
|
|
91
94
|
'query-unexpected-structure-hdbcds': { severity: 'Error' },
|
|
95
|
+
'query-ignoring-param-nullability': { severity: 'Info' },
|
|
92
96
|
|
|
93
97
|
'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
|
|
94
98
|
'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
|
|
@@ -99,6 +103,7 @@ const centralMessages = {
|
|
|
99
103
|
'ref-undefined-def': { severity: 'Error' },
|
|
100
104
|
'ref-undefined-var': { severity: 'Error' },
|
|
101
105
|
'ref-undefined-element': { severity: 'Error' },
|
|
106
|
+
'ref-unknown-var': { severity: 'Info' },
|
|
102
107
|
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
|
|
103
108
|
'ref-undefined-param': { severity: 'Error' },
|
|
104
109
|
'ref-rejected-on': { severity: 'Error' },
|
|
@@ -118,6 +123,7 @@ const centralMessages = {
|
|
|
118
123
|
'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
119
124
|
'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
|
|
120
125
|
'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
|
|
126
|
+
'syntax-csn-expected-length': { severity: 'Error' },
|
|
121
127
|
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
|
|
122
128
|
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
|
|
123
129
|
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
|
|
@@ -125,14 +131,22 @@ const centralMessages = {
|
|
|
125
131
|
'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
|
|
126
132
|
'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
|
|
127
133
|
|
|
134
|
+
'syntax-invalid-text-block' : { severity: 'Error' },
|
|
135
|
+
'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
|
|
136
|
+
'syntax-invalid-escape': { severity: 'Error' },
|
|
137
|
+
'syntax-missing-escape': { severity: 'Error' },
|
|
138
|
+
|
|
128
139
|
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
|
|
129
140
|
|
|
141
|
+
'def-missing-element': { severity: 'Error' },
|
|
142
|
+
|
|
130
143
|
'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
|
|
131
144
|
'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
|
|
132
145
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
133
146
|
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
134
147
|
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
135
148
|
'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
|
|
149
|
+
'odata-spec-violation-no-key': { severity: 'Warning' },
|
|
136
150
|
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
137
151
|
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
138
152
|
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
@@ -143,7 +157,7 @@ const centralMessages = {
|
|
|
143
157
|
// For messageIds, where no text has been provided via code (central def)
|
|
144
158
|
const centralMessageTexts = {
|
|
145
159
|
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
|
|
146
|
-
'anno-unexpected-ellipsis': '
|
|
160
|
+
'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
|
|
147
161
|
'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
|
|
148
162
|
'syntax-csn-expected-object': 'Expected object for property $(PROP)',
|
|
149
163
|
'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
|
|
@@ -160,6 +174,27 @@ const centralMessageTexts = {
|
|
|
160
174
|
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
|
|
161
175
|
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
|
|
162
176
|
},
|
|
177
|
+
'syntax-csn-expected-length': {
|
|
178
|
+
std: 'Expected array in $(PROP) to have at least $(N) items',
|
|
179
|
+
one: 'Expected array in $(PROP) to have at least one item',
|
|
180
|
+
suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
|
|
181
|
+
},
|
|
182
|
+
'syntax-invalid-text-block': 'Missing newline in text block',
|
|
183
|
+
'syntax-unknown-escape': 'Unknown escape sequence $(CODE)',
|
|
184
|
+
'syntax-invalid-escape': {
|
|
185
|
+
std: 'Invalid escape sequence $(CODE)',
|
|
186
|
+
octal: 'Octal escape sequences are not supported. Use unicode escapes instead',
|
|
187
|
+
whitespace: 'Unknown escape sequence: Can\'t escape whitespace',
|
|
188
|
+
codepoint: 'Undefined code-point for $(CODE)',
|
|
189
|
+
'unicode-hex': 'Expected hexadecimal numbers for unicode escape but found $(CODE)',
|
|
190
|
+
'hex-count': 'Expected $(NUMBER) hexadecimal numbers for escape sequence but found $(CODE)',
|
|
191
|
+
'unicode-brace': 'Missing closing brace for unicode escape sequence',
|
|
192
|
+
'language-identifier': 'Escape sequences in text-block\'s language identifier are not allowed',
|
|
193
|
+
},
|
|
194
|
+
'syntax-missing-escape': {
|
|
195
|
+
std: 'Missing escape. Replace $(CODE) with $(NEWCODE)',
|
|
196
|
+
placeholder: 'Placeholders are not supported. Replace $(CODE) with $(NEWCODE)',
|
|
197
|
+
},
|
|
163
198
|
'ref-undefined-def': {
|
|
164
199
|
std: 'Artifact $(ART) has not been found',
|
|
165
200
|
// TODO: proposal 'No definition of $(NAME) found',
|
|
@@ -171,17 +206,20 @@ const centralMessageTexts = {
|
|
|
171
206
|
std: 'Element $(ART) has not been found',
|
|
172
207
|
element: 'Artifact $(ART) has no element $(MEMBER)'
|
|
173
208
|
},
|
|
209
|
+
'ref-unknown-var': {
|
|
210
|
+
std: 'Replacement $(ID) not found'
|
|
211
|
+
},
|
|
174
212
|
'ref-rejected-on': {
|
|
175
213
|
std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
|
|
176
214
|
mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
|
|
177
215
|
alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
|
|
178
216
|
},
|
|
179
|
-
'
|
|
180
|
-
std: '
|
|
181
|
-
type: '
|
|
182
|
-
event: '
|
|
183
|
-
param: '
|
|
184
|
-
select: '
|
|
217
|
+
'type-unexpected-typeof': {
|
|
218
|
+
std: 'Unexpected $(KEYWORD) for the type reference here',
|
|
219
|
+
type: 'Unexpected $(KEYWORD) in type of a type definition',
|
|
220
|
+
event: 'Unexpected $(KEYWORD) for the type of an event',
|
|
221
|
+
param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
|
|
222
|
+
select: 'Unexpected $(KEYWORD) for type references in queries',
|
|
185
223
|
},
|
|
186
224
|
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
|
|
187
225
|
'anno-undefined-def': 'Artifact $(ART) has not been found',
|
|
@@ -200,16 +238,21 @@ const centralMessageTexts = {
|
|
|
200
238
|
param: 'Artifact $(ART) has no parameter $(MEMBER)'
|
|
201
239
|
},
|
|
202
240
|
|
|
241
|
+
'def-missing-element': {
|
|
242
|
+
std: 'Expecting entity to have at least one non-virtual element',
|
|
243
|
+
view: 'Expecting view to have at least one non-virtual element'
|
|
244
|
+
},
|
|
245
|
+
|
|
203
246
|
'duplicate-definition': {
|
|
204
247
|
std: 'Duplicate definition of $(NAME)',
|
|
205
248
|
absolute: 'Duplicate definition of artifact $(NAME)',
|
|
206
|
-
|
|
249
|
+
annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
|
|
207
250
|
element: 'Duplicate definition of element $(NAME)',
|
|
208
251
|
enum: 'Duplicate definition of enum $(NAME)',
|
|
209
252
|
key: 'Duplicate definition of key $(NAME)',
|
|
210
253
|
action: 'Duplicate definition of action or function $(NAME)',
|
|
211
254
|
param: 'Duplicate definition of parameter $(NAME)',
|
|
212
|
-
|
|
255
|
+
alias: 'Duplicate definition of table alias or mixin $(NAME)',
|
|
213
256
|
},
|
|
214
257
|
|
|
215
258
|
// TODO: rename to ref-expected-XYZ
|
|
@@ -228,6 +271,10 @@ const centralMessageTexts = {
|
|
|
228
271
|
|
|
229
272
|
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
|
|
230
273
|
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
|
|
274
|
+
'query-ignoring-param-nullability': {
|
|
275
|
+
std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
|
|
276
|
+
sql: 'Ignoring nullability constraint on parameter when generating SQL view'
|
|
277
|
+
},
|
|
231
278
|
|
|
232
279
|
'ref-sloppy-type': 'A type or an element is expected here',
|
|
233
280
|
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
|
|
@@ -262,9 +309,15 @@ const centralMessageTexts = {
|
|
|
262
309
|
std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
|
|
263
310
|
scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
|
|
264
311
|
},
|
|
312
|
+
'odata-spec-violation-no-key': 'Expected entity to have a primary key',
|
|
265
313
|
'odata-spec-violation-type': 'Expected element to have a type',
|
|
266
314
|
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
|
|
267
315
|
'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
|
|
316
|
+
// Other odata/edm errors
|
|
317
|
+
'odata-definition-exists': {
|
|
318
|
+
std: 'Entity can\'t be created due to name collision with existing definition $(NAME)',
|
|
319
|
+
proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
|
|
320
|
+
}
|
|
268
321
|
}
|
|
269
322
|
|
|
270
323
|
/**
|
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,7 @@ const availableBetaFlags = {
|
|
|
28
27
|
ignoreAssocPublishingInUnion: true,
|
|
29
28
|
nestedProjections: true,
|
|
30
29
|
enableUniversalCsn: true,
|
|
31
|
-
|
|
30
|
+
sqlSnippets: true,
|
|
32
31
|
// disabled by --beta-mode
|
|
33
32
|
nestedServices: false,
|
|
34
33
|
};
|
|
@@ -55,15 +54,18 @@ function isBetaEnabled( options, feature ) {
|
|
|
55
54
|
/**
|
|
56
55
|
* Test for deprecated feature, stored in option `deprecated`.
|
|
57
56
|
* With that, the value of `deprecated` is a dictionary of feature=>Boolean.
|
|
57
|
+
* If no `feature` is provided, checks if any deprecated option is set.
|
|
58
58
|
*
|
|
59
59
|
* Please do not move this function to the "option processor" code.
|
|
60
60
|
*
|
|
61
61
|
* @param {object} options Options
|
|
62
|
-
* @param {string} feature Feature to check for
|
|
62
|
+
* @param {string} [feature] Feature to check for
|
|
63
63
|
* @returns {boolean}
|
|
64
64
|
*/
|
|
65
|
-
function isDeprecatedEnabled( options, feature ) {
|
|
65
|
+
function isDeprecatedEnabled( options, feature = null ) {
|
|
66
66
|
const { deprecated } = options;
|
|
67
|
+
if(!feature)
|
|
68
|
+
return !!deprecated;
|
|
67
69
|
return deprecated && typeof deprecated === 'object' && deprecated[feature];
|
|
68
70
|
}
|
|
69
71
|
|