@sap/cds-compiler 4.5.0 → 4.6.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 +50 -7
- package/bin/cdsc.js +13 -11
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +256 -115
- package/lib/api/options.js +8 -0
- package/lib/base/message-registry.js +17 -4
- package/lib/base/messages.js +15 -3
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +2 -3
- package/lib/compiler/assert-consistency.js +2 -1
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +30 -6
- package/lib/compiler/define.js +31 -9
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/resolve.js +26 -21
- package/lib/compiler/shared.js +19 -9
- package/lib/compiler/tweak-assocs.js +82 -107
- package/lib/compiler/utils.js +2 -1
- package/lib/edm/annotations/edmJson.js +23 -22
- package/lib/edm/annotations/genericTranslation.js +14 -4
- package/lib/edm/csn2edm.js +24 -10
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +11 -9
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +3 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -1
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +5253 -5214
- package/lib/json/to-csn.js +7 -1
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +21 -4
- package/lib/language/genericAntlrParser.js +9 -11
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +12 -7
- package/lib/optionProcessor.js +21 -19
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +18 -15
- package/lib/render/toHdbcds.js +59 -28
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +57 -82
- package/lib/render/utils/common.js +17 -0
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +27 -31
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/flattening.js +68 -69
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +13 -9
- package/lib/transform/forRelationalDB.js +36 -17
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +33 -8
- package/package.json +2 -2
package/lib/api/options.js
CHANGED
|
@@ -67,6 +67,7 @@ const privateOptions = [
|
|
|
67
67
|
'noRecompile',
|
|
68
68
|
'internalMsg',
|
|
69
69
|
'disableHanaComments', // in case of issues with hana comment rendering
|
|
70
|
+
'tenantAsColumn', // not published yet
|
|
70
71
|
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(v5): Remove option
|
|
71
72
|
];
|
|
72
73
|
|
|
@@ -158,6 +159,13 @@ module.exports = {
|
|
|
158
159
|
};
|
|
159
160
|
return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.edmx');
|
|
160
161
|
},
|
|
162
|
+
odata: (options) => {
|
|
163
|
+
const hardOptions = { combined: true, toOdata: true };
|
|
164
|
+
const defaultOptions = {
|
|
165
|
+
odataVersion: 'v4', odataFormat: 'flat',
|
|
166
|
+
};
|
|
167
|
+
return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.odata');
|
|
168
|
+
},
|
|
161
169
|
},
|
|
162
170
|
for: { // TODO: Rename version to oDataVersion
|
|
163
171
|
odata: (options) => {
|
|
@@ -122,7 +122,7 @@ const centralMessages = {
|
|
|
122
122
|
'ref-undefined-var': { severity: 'Error' },
|
|
123
123
|
'ref-undefined-element': { severity: 'Error' },
|
|
124
124
|
'anno-undefined-element': { severity: 'Error' },
|
|
125
|
-
'ref-unknown-var': { severity: 'Info'
|
|
125
|
+
'ref-unknown-var': { severity: 'Info' },
|
|
126
126
|
'ref-obsolete-parameters': { severity: 'Error', configurableFor: 'v4' },
|
|
127
127
|
// does not hurt us, but makes it tedious to detect parameter refs
|
|
128
128
|
'ref-undefined-param': { severity: 'Error' },
|
|
@@ -254,6 +254,7 @@ const centralMessageTexts = {
|
|
|
254
254
|
std: 'Unexpected option combination: $(OPTION) and $(PROP)', // unused
|
|
255
255
|
'beta-no-test':'Option $(OPTION) was used. This option should not be used in productive scenarios!',
|
|
256
256
|
},
|
|
257
|
+
'api-unexpected-option': 'Option $(OPTION) can\'t be used in backend $(MODULE)',
|
|
257
258
|
'api-invalid-lookup-dir': {
|
|
258
259
|
std: '',
|
|
259
260
|
slash: 'Expected directory $(VALUE) in option $(OPTION) to end with $(OTHERVALUE)',
|
|
@@ -585,6 +586,7 @@ const centralMessageTexts = {
|
|
|
585
586
|
std: 'Variable $(ID) has not been found',
|
|
586
587
|
alias: 'Variable $(ID) has not been found. Use table alias $(ALIAS) to refer an element with the same name',
|
|
587
588
|
self: 'Variable $(ID) has not been found. Use $(ALIAS) to refer an element with the same name',
|
|
589
|
+
value: 'No value found for variable $(ID). Use option $(OPTION) to specify a value for $(ID)',
|
|
588
590
|
},
|
|
589
591
|
'ref-unknown-var': {
|
|
590
592
|
std: 'No replacement found for special variable $(ID)'
|
|
@@ -659,6 +661,12 @@ const centralMessageTexts = {
|
|
|
659
661
|
'on-condition': 'ON-conditions must not contain parameters, step $(ID) of path $(ELEMREF)',
|
|
660
662
|
calc: 'Unexpected arguments in path $(ELEMREF) of stored calculated element; only simple paths can be used here'
|
|
661
663
|
},
|
|
664
|
+
'ref-unsupported-type': {
|
|
665
|
+
std: 'Type $(TYPE) is not supported',
|
|
666
|
+
hana: 'Type $(TYPE) is only supported for SQL dialect $(VALUE), not $(OTHERVALUE)',
|
|
667
|
+
hdbcds:'Type $(TYPE) is not supported in HDBCDS',
|
|
668
|
+
odata: 'Type $(TYPE) is not supported for OData'
|
|
669
|
+
},
|
|
662
670
|
|
|
663
671
|
// TODO: Better text ?
|
|
664
672
|
'rewrite-not-supported': 'The ON-condition is not rewritten here - provide an explicit ON-condition',
|
|
@@ -852,8 +860,10 @@ const centralMessageTexts = {
|
|
|
852
860
|
param: 'A type, an element, or a service entity is expected here',
|
|
853
861
|
event: 'A type, an element, an event, or a service entity is expected here',
|
|
854
862
|
},
|
|
855
|
-
|
|
856
|
-
|
|
863
|
+
'ref-invalid-source': {
|
|
864
|
+
std: 'A query source must be an entity or an association',
|
|
865
|
+
event: 'An event\'s projection source must be an entity, structured type, event or an association',
|
|
866
|
+
},
|
|
857
867
|
'extend-columns': {
|
|
858
868
|
std: 'Artifact $(ART) can\'t be extended with columns, only simple views/projections without JOINs and UNIONs can',
|
|
859
869
|
join: 'Artifact $(ART) can\'t be extended with columns, because it contains a JOIN',
|
|
@@ -910,6 +920,7 @@ const centralMessageTexts = {
|
|
|
910
920
|
std: 'Specified element $(NAME) differs from inferred element in property $(PROP)',
|
|
911
921
|
type: 'Expected type of specified element $(NAME) to be the same as the inferred element\'s type',
|
|
912
922
|
typeName: 'Expected type $(TYPE) of specified element $(NAME) to be the same as the inferred element\'s type $(OTHERTYPE)',
|
|
923
|
+
typeExtra: 'Element $(NAME) does not have an inferred type property, but an unexpected type $(TYPE) was specified',
|
|
913
924
|
missing: 'Specified element $(NAME) differs from inferred element: it is missing property $(PROP)',
|
|
914
925
|
extra: 'Specified element $(NAME) differs from inferred element: it has an additional property $(PROP)',
|
|
915
926
|
target: 'Expected target $(TARGET) of specified element $(NAME) to be the same as the inferred element\'s target $(ART)',
|
|
@@ -958,7 +969,8 @@ const centralMessageTexts = {
|
|
|
958
969
|
'to-structure': 'Can\'t cast to a structured type',
|
|
959
970
|
'from-structure': 'Structured elements can\'t be cast to a different type',
|
|
960
971
|
'expr-to-structure': 'Can\'t cast an expression to a structured type',
|
|
961
|
-
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type'
|
|
972
|
+
'val-to-structure': 'Can\'t cast $(VALUE) to a structured type',
|
|
973
|
+
'from-assoc': 'Invalid type cast on an association'
|
|
962
974
|
},
|
|
963
975
|
|
|
964
976
|
// -----------------------------------------------------------------------------------
|
|
@@ -1096,6 +1108,7 @@ const centralMessageTexts = {
|
|
|
1096
1108
|
'unknown': '$(TYPE) is not a known vocabulary type for $(ANNO)',
|
|
1097
1109
|
'abstract': 'Unexpected abstract type $(TYPE) for $(ANNO), use $(CODE) to specify a concrete type',
|
|
1098
1110
|
'derived': 'Expected specified $(TYPE) to be derived from $(NAME) for $(ANNO)',
|
|
1111
|
+
'literal': 'Expected value $(RAWVALUE) of specified $(CODE) to be a string literal for $(ANNO)'
|
|
1099
1112
|
},
|
|
1100
1113
|
'odata-anno-def': {
|
|
1101
1114
|
// All $(ANNO) w/o sub elements, term qualifiers and context stack
|
package/lib/base/messages.js
CHANGED
|
@@ -305,14 +305,14 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
305
305
|
throw new CompilerAssertion('makeMessageFunction() expects options.messages to exist in testMode!');
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
const hasMessageArray =
|
|
308
|
+
const hasMessageArray = Array.isArray(options.messages);
|
|
309
309
|
/**
|
|
310
310
|
* Array of collected compiler messages. Only use it for debugging. Will not
|
|
311
311
|
* contain the messages created during a `callTransparently` call.
|
|
312
312
|
*
|
|
313
313
|
* @type {CompileMessage[]}
|
|
314
314
|
*/
|
|
315
|
-
let messages = options.messages
|
|
315
|
+
let messages = hasMessageArray ? options.messages : [];
|
|
316
316
|
/**
|
|
317
317
|
* Whether an error was emitted in the module. Also includes reclassified errors.
|
|
318
318
|
* @type {boolean}
|
|
@@ -332,6 +332,7 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
332
332
|
throwWithAnyError,
|
|
333
333
|
callTransparently,
|
|
334
334
|
moduleName,
|
|
335
|
+
setModel,
|
|
335
336
|
};
|
|
336
337
|
|
|
337
338
|
function _message( id, location, textOrArguments, severity, texts = null ) {
|
|
@@ -550,6 +551,16 @@ function makeMessageFunction( model, options, moduleName = null ) {
|
|
|
550
551
|
messages = backup;
|
|
551
552
|
return collected;
|
|
552
553
|
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Change the model used to calculate CSN locations.
|
|
557
|
+
* This is necessary if you change the model heavily and rely on $paths relative to the new model.
|
|
558
|
+
*
|
|
559
|
+
* @param {CSN.Model} _model
|
|
560
|
+
*/
|
|
561
|
+
function setModel( _model ) {
|
|
562
|
+
model = _model;
|
|
563
|
+
}
|
|
553
564
|
}
|
|
554
565
|
|
|
555
566
|
/**
|
|
@@ -679,6 +690,7 @@ const paramsTransform = {
|
|
|
679
690
|
meta: quote.angle,
|
|
680
691
|
othermeta: quote.angle,
|
|
681
692
|
keyword,
|
|
693
|
+
module: quote.single,
|
|
682
694
|
// more complex convenience:
|
|
683
695
|
names: transformManyWith( quoted ),
|
|
684
696
|
number: quote.single, // number cited from source or expected in source
|
|
@@ -1583,7 +1595,7 @@ function constructSemanticLocationFromCsnPath( model, options, csnPath ) {
|
|
|
1583
1595
|
else if (step === 'targetAspect') {
|
|
1584
1596
|
// skip
|
|
1585
1597
|
}
|
|
1586
|
-
else if (step === 'xpr' || step === 'ref' || step === 'as' || step === 'value') {
|
|
1598
|
+
else if (step === 'xpr' || step === 'default' || step === 'ref' || step === 'as' || step === 'value') {
|
|
1587
1599
|
break; // don't go into xprs, refs, aliases, values, etc.
|
|
1588
1600
|
}
|
|
1589
1601
|
else if (step === 'returns') {
|
package/lib/base/model.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* optionProcessor.command('toXyz')
|
|
17
17
|
* .help(`Help text for command "toXyz")
|
|
18
18
|
* .option('-y --y-in-long-form')
|
|
19
|
-
* .option(' --bar-wiz <w>', ['bla', 'foo'])
|
|
19
|
+
* .option(' --bar-wiz <w>', { valid: ['bla', 'foo'] })
|
|
20
|
+
* .option('-z --name-in-cdsc', { optionName: 'nameInOptions' })
|
|
20
21
|
* ```
|
|
21
22
|
*
|
|
22
23
|
* Options *must* have a long form, can have at most one <param>, and optionally
|
|
@@ -34,29 +35,27 @@ function createOptionProcessor() {
|
|
|
34
35
|
optionClashes: [],
|
|
35
36
|
option,
|
|
36
37
|
command,
|
|
37
|
-
positionalArgument
|
|
38
|
+
positionalArgument(argumentDefinition) {
|
|
38
39
|
// Default positional arguments; may be overwritten by commands.
|
|
39
40
|
_setPositionalArguments(argumentDefinition);
|
|
40
41
|
return optionProcessor;
|
|
41
42
|
},
|
|
42
43
|
help,
|
|
43
44
|
processCmdLine,
|
|
44
|
-
verifyOptions,
|
|
45
|
-
camelOptionsForCommand,
|
|
46
|
-
// TODO: Why exported?
|
|
47
|
-
_parseCommandString,
|
|
48
|
-
_parseOptionString,
|
|
49
45
|
};
|
|
50
46
|
return optionProcessor;
|
|
51
47
|
|
|
52
48
|
/**
|
|
53
49
|
* API: Define a general option.
|
|
54
50
|
* @param {string} optString Option string describing the command line option.
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
51
|
+
* @param {object} [options] Further options such as `ignoreCase: true` and `valid: []`.
|
|
52
|
+
* @param {string[]} [options.valid] Valid values for the option.
|
|
53
|
+
* @param {string[]} [options.ignoreCase] Ignore the case for "options.valid".
|
|
54
|
+
* @param {string[]} [options.optionName] Name of the option after parsing CLI arguments.
|
|
55
|
+
* Defaults to the camelified name of the long name.
|
|
57
56
|
*/
|
|
58
|
-
function option( optString,
|
|
59
|
-
return _addOption(optionProcessor, optString,
|
|
57
|
+
function option( optString, options ) {
|
|
58
|
+
return _addOption(optionProcessor, optString, options);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
@@ -78,7 +77,7 @@ function createOptionProcessor() {
|
|
|
78
77
|
options: {},
|
|
79
78
|
positionalArguments: [],
|
|
80
79
|
option: commandOption,
|
|
81
|
-
positionalArgument
|
|
80
|
+
positionalArgument(argumentDefinition) {
|
|
82
81
|
_setPositionalArguments(argumentDefinition, cmd.positionalArguments);
|
|
83
82
|
return cmd;
|
|
84
83
|
},
|
|
@@ -99,8 +98,8 @@ function createOptionProcessor() {
|
|
|
99
98
|
return cmd;
|
|
100
99
|
|
|
101
100
|
// Command API: Define a command option
|
|
102
|
-
function commandOption( optString,
|
|
103
|
-
return _addOption(cmd, optString,
|
|
101
|
+
function commandOption( optString, options ) {
|
|
102
|
+
return _addOption(cmd, optString, options);
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// Command API: Define the command help text
|
|
@@ -124,13 +123,15 @@ function createOptionProcessor() {
|
|
|
124
123
|
if (argList.find(arg => arg.isDynamic))
|
|
125
124
|
throw new Error('Can\'t add positional arguments after a dynamic one');
|
|
126
125
|
|
|
127
|
-
|
|
128
126
|
const registeredNames = argList.map(arg => arg.name);
|
|
129
127
|
const args = argumentDefinition.split(' ');
|
|
130
128
|
|
|
131
129
|
for (const arg of args) {
|
|
132
130
|
// Remove braces, dots and camelify.
|
|
133
|
-
const argName = arg.replace('<', '')
|
|
131
|
+
const argName = arg.replace('<', '')
|
|
132
|
+
.replace('>', '')
|
|
133
|
+
.replace('...', '')
|
|
134
|
+
.replace(/[ -]./g, s => s.substring(1).toUpperCase());
|
|
134
135
|
|
|
135
136
|
if (registeredNames.includes(argName))
|
|
136
137
|
throw new Error(`Duplicate positional argument: ${ arg }`);
|
|
@@ -138,7 +139,6 @@ function createOptionProcessor() {
|
|
|
138
139
|
if (!isParam(arg) && !isDynamicPositionalArgument(arg))
|
|
139
140
|
throw new Error(`Unknown positional argument syntax: ${ arg }`);
|
|
140
141
|
|
|
141
|
-
|
|
142
142
|
argList.push({
|
|
143
143
|
name: argName,
|
|
144
144
|
isDynamic: isDynamicPositionalArgument(arg),
|
|
@@ -157,65 +157,42 @@ function createOptionProcessor() {
|
|
|
157
157
|
* @private
|
|
158
158
|
* @see option()
|
|
159
159
|
*/
|
|
160
|
-
function _addOption( cmd, optString,
|
|
161
|
-
const cliOpt = _parseOptionString(optString,
|
|
160
|
+
function _addOption( cmd, optString, options ) {
|
|
161
|
+
const cliOpt = _parseOptionString(optString, options);
|
|
162
162
|
Object.assign(cliOpt, options);
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
_addOptionName(cmd, cliOpt.longName, cliOpt);
|
|
164
|
+
_addOptionName(cmd, cliOpt.shortName, cliOpt);
|
|
165
165
|
|
|
166
166
|
for (const alias of cliOpt.aliases || []) {
|
|
167
167
|
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
|
|
168
|
-
|
|
168
|
+
_addOptionName(cmd, alias, aliasOpt); // use same optionName, etc. for alias
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
return cmd;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
|
-
* Internal: Add
|
|
176
|
-
* Throws if the option is already registered in the given command context.
|
|
177
|
-
* or in the given command.
|
|
178
|
-
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
179
|
-
*
|
|
180
|
-
* @private
|
|
181
|
-
* @see _addOption()
|
|
182
|
-
*/
|
|
183
|
-
function _addLongOption( cmd, longName, opt ) {
|
|
184
|
-
if (cmd.options[longName]) {
|
|
185
|
-
throw new Error(`Duplicate assignment for long option ${ longName }`);
|
|
186
|
-
}
|
|
187
|
-
else if (optionProcessor.options[longName]) {
|
|
188
|
-
// This path is only taken if optString is for commands
|
|
189
|
-
optionProcessor.optionClashes.push({
|
|
190
|
-
option: longName,
|
|
191
|
-
description: `Command '${ cmd.longName }' has option clash with general options for: ${ longName }`,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
cmd.options[longName] = opt;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Internal: Add shortName to the list of options.
|
|
175
|
+
* Internal: Add a name to the list of options.
|
|
198
176
|
* Throws if the option is already registered in the given command context.
|
|
199
177
|
* or in the given command.
|
|
200
|
-
* `longName` may differ from `opt.longName`, e.g. for aliases.
|
|
201
178
|
*
|
|
202
179
|
* @private
|
|
203
180
|
* @see _addOption()
|
|
204
181
|
*/
|
|
205
|
-
function
|
|
206
|
-
if (!
|
|
182
|
+
function _addOptionName( cmd, name, opt ) {
|
|
183
|
+
if (!name)
|
|
207
184
|
return;
|
|
208
|
-
if (cmd.options[
|
|
209
|
-
throw new Error(`Duplicate assignment for
|
|
185
|
+
if (cmd.options[name]) {
|
|
186
|
+
throw new Error(`Duplicate assignment for option ${ name }`);
|
|
210
187
|
}
|
|
211
|
-
else if (optionProcessor.options[
|
|
188
|
+
else if (optionProcessor.options[name]) {
|
|
212
189
|
// This path is only taken if optString is for commands
|
|
213
190
|
optionProcessor.optionClashes.push({
|
|
214
|
-
option:
|
|
215
|
-
description: `Command '${ cmd.longName }' has option clash with general options for: ${
|
|
191
|
+
option: name,
|
|
192
|
+
description: `Command '${ cmd.longName }' has option clash with general options for: ${ name }`,
|
|
216
193
|
});
|
|
217
194
|
}
|
|
218
|
-
cmd.options[
|
|
195
|
+
cmd.options[name] = opt;
|
|
219
196
|
}
|
|
220
197
|
|
|
221
198
|
// Internal: Parse one command string like "F, toFoo". Return an object like this
|
|
@@ -251,11 +228,11 @@ function createOptionProcessor() {
|
|
|
251
228
|
// {
|
|
252
229
|
// longName: '--foo-bar',
|
|
253
230
|
// shortName: '-f',
|
|
254
|
-
//
|
|
231
|
+
// optionName: 'fooBar', // or options.optionName if provided
|
|
255
232
|
// param: '<p>'
|
|
256
|
-
//
|
|
233
|
+
// valid
|
|
257
234
|
// }
|
|
258
|
-
function _parseOptionString( optString,
|
|
235
|
+
function _parseOptionString( optString, options ) {
|
|
259
236
|
let longName;
|
|
260
237
|
let shortName;
|
|
261
238
|
let param;
|
|
@@ -294,11 +271,11 @@ function createOptionProcessor() {
|
|
|
294
271
|
if (!longName)
|
|
295
272
|
throw new Error(`Invalid option description, missing long name: ${ optString }`);
|
|
296
273
|
|
|
297
|
-
if (!param &&
|
|
274
|
+
if (!param && options?.valid)
|
|
298
275
|
throw new Error(`Option description has valid values but no param: ${ optString }`);
|
|
299
276
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
277
|
+
if (options?.valid) {
|
|
278
|
+
options.valid.forEach((value) => {
|
|
302
279
|
if (typeof value !== 'string')
|
|
303
280
|
throw new Error(`Valid values must be of type string: ${ optString }`);
|
|
304
281
|
});
|
|
@@ -307,9 +284,9 @@ function createOptionProcessor() {
|
|
|
307
284
|
return {
|
|
308
285
|
longName,
|
|
309
286
|
shortName,
|
|
310
|
-
|
|
287
|
+
optionName: options?.option ?? camelifyLongOption(longName),
|
|
311
288
|
param,
|
|
312
|
-
|
|
289
|
+
valid: options?.valid,
|
|
313
290
|
isAlias: false, // default
|
|
314
291
|
};
|
|
315
292
|
}
|
|
@@ -520,10 +497,10 @@ function createOptionProcessor() {
|
|
|
520
497
|
if (currentCommand) {
|
|
521
498
|
if (!result.options[currentCommand])
|
|
522
499
|
result.options[currentCommand] = {};
|
|
523
|
-
result.options[currentCommand][currentOption.
|
|
500
|
+
result.options[currentCommand][currentOption.optionName] = val;
|
|
524
501
|
}
|
|
525
502
|
else {
|
|
526
|
-
result.options[currentOption.
|
|
503
|
+
result.options[currentOption.optionName] = val;
|
|
527
504
|
}
|
|
528
505
|
}
|
|
529
506
|
|
|
@@ -542,7 +519,7 @@ function createOptionProcessor() {
|
|
|
542
519
|
function reportInvalidValue( opt, value ) {
|
|
543
520
|
const shortOption = opt.shortName ? `${ opt.shortName }, ` : '';
|
|
544
521
|
const errors = currentCommand ? result.cmdErrors : result.errors;
|
|
545
|
-
errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.
|
|
522
|
+
errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.valid }]`);
|
|
546
523
|
}
|
|
547
524
|
|
|
548
525
|
/**
|
|
@@ -580,114 +557,14 @@ function createOptionProcessor() {
|
|
|
580
557
|
}
|
|
581
558
|
}
|
|
582
559
|
|
|
583
|
-
// Assuming that 'options' came from this option processor, verify options therein.
|
|
584
|
-
// If 'command' is supplied, check only 'options.command', otherwise check
|
|
585
|
-
// only top-level options
|
|
586
|
-
// Return an array of complaints (possibly empty)
|
|
587
|
-
function verifyOptions( options, commandName = undefined, silent = false ) {
|
|
588
|
-
const result = [];
|
|
589
|
-
let opts;
|
|
590
|
-
|
|
591
|
-
if ((options.betaMode || options.beta) && !options.testMode && !silent) {
|
|
592
|
-
const mode = options.beta ? 'beta' : 'beta-mode';
|
|
593
|
-
result.push(`Option --${ mode } was used. This option should not be used in productive scenarios!`);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
if (options) {
|
|
597
|
-
[
|
|
598
|
-
'defaultBinaryLength', 'defaultStringLength',
|
|
599
|
-
/* 'length', 'precision', 'scale' */
|
|
600
|
-
].forEach((facet) => {
|
|
601
|
-
if (options[facet] && isNaN(options[facet]))
|
|
602
|
-
result.push(`Invalid value "${ options[facet] }" for option "--${ facet }" - not an Integer`);
|
|
603
|
-
else
|
|
604
|
-
options[facet] = parseInt(options[facet]);
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (commandName) {
|
|
609
|
-
const cmd = optionProcessor.commands[commandName];
|
|
610
|
-
if (!cmd)
|
|
611
|
-
throw new Error(`Expected existing command: "${ cmd }"`);
|
|
612
|
-
|
|
613
|
-
opts = cmd.options;
|
|
614
|
-
options = options[cmd] || {};
|
|
615
|
-
if (typeof options === 'boolean') {
|
|
616
|
-
// Special case: command without options
|
|
617
|
-
options = {};
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
opts = optionProcessor.options;
|
|
622
|
-
}
|
|
623
|
-
// Look at each supplied option
|
|
624
|
-
for (const camelName in options) {
|
|
625
|
-
const opt = opts[uncamelifyLongOption(camelName)];
|
|
626
|
-
let error;
|
|
627
|
-
if (!opt) {
|
|
628
|
-
// Don't report commands in top-level options
|
|
629
|
-
if ((commandName || !optionProcessor.commands[camelName]) && !silent) {
|
|
630
|
-
const prefix = commandName ? `${commandName}.` : '';
|
|
631
|
-
error = `Unknown option "${prefix}${camelName}"`;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
const param = options[camelName];
|
|
636
|
-
error = verifyOptionParam(param, opt, commandName ? `${ commandName }.` : '');
|
|
637
|
-
}
|
|
638
|
-
if (error)
|
|
639
|
-
result.push(error);
|
|
640
|
-
}
|
|
641
|
-
// hard-coded option dependencies (they disappear with command)
|
|
642
|
-
return result;
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* Verify parameter value 'param' against option definition 'opt'. Return an error
|
|
646
|
-
* string or false for an accepted param. Use 'prefix' when mentioning the option name.
|
|
647
|
-
*
|
|
648
|
-
* @param param
|
|
649
|
-
* @param opt
|
|
650
|
-
* @param prefix
|
|
651
|
-
* @return {string|boolean}
|
|
652
|
-
*/
|
|
653
|
-
function verifyOptionParam( param, opt, prefix ) {
|
|
654
|
-
if (opt.param) {
|
|
655
|
-
// Parameter is required for this option
|
|
656
|
-
if (typeof param === 'boolean')
|
|
657
|
-
return `Missing value for option "${ prefix }${ opt.camelName }"`;
|
|
658
|
-
else if (!isValidOptionValue(opt, param))
|
|
659
|
-
return `Invalid value "${ param }" for option "${ prefix }${ opt.camelName }" - use one of [${ opt.validValues }]`;
|
|
660
|
-
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
// Option does not expect a parameter
|
|
664
|
-
if (typeof param !== 'boolean') {
|
|
665
|
-
// FIXME: Might be a bit too strict in case of internal sub-options like 'forHana' etc...
|
|
666
|
-
return `Expecting boolean value for option "${ prefix }${ opt.camelName }"`;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
560
|
function isValidOptionValue( opt, value ) {
|
|
674
561
|
// Explicitly convert to string, input 'value' may be boolean
|
|
675
562
|
value = String(value);
|
|
676
|
-
if (!opt.
|
|
563
|
+
if (!opt.valid?.length)
|
|
677
564
|
return true;
|
|
678
565
|
if (opt.ignoreCase)
|
|
679
|
-
return opt.
|
|
680
|
-
return opt.
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Return an array of unique camelNames of the options for the specified command
|
|
684
|
-
// If invalid command -> an empty array
|
|
685
|
-
function camelOptionsForCommand( cmd ) {
|
|
686
|
-
if (!cmd || !optionProcessor.commands[cmd])
|
|
687
|
-
return [];
|
|
688
|
-
cmd = optionProcessor.commands[cmd];
|
|
689
|
-
const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
|
|
690
|
-
return [ ...new Set(names) ];
|
|
566
|
+
return opt.valid.some( valid => valid.toLowerCase() === value.toLowerCase() );
|
|
567
|
+
return opt.valid.includes(value);
|
|
691
568
|
}
|
|
692
569
|
}
|
|
693
570
|
|
|
@@ -713,14 +590,6 @@ function camelifyLongOption( opt ) {
|
|
|
713
590
|
return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
|
|
714
591
|
}
|
|
715
592
|
|
|
716
|
-
/**
|
|
717
|
-
* Return a long option name like "--foo-bar" for a camel-case name "fooBar"
|
|
718
|
-
*/
|
|
719
|
-
function uncamelifyLongOption( opt ) {
|
|
720
|
-
const longForm = opt.replace(/[A-Z]/g, s => `-${ s.toLowerCase() }`);
|
|
721
|
-
return `--${ longForm }`;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
593
|
/**
|
|
725
594
|
* Check if 'opt' looks like a "-f" short option
|
|
726
595
|
*/
|
package/lib/checks/elements.js
CHANGED
|
@@ -112,45 +112,43 @@ function checkVirtualElement( member ) {
|
|
|
112
112
|
* Checks whether managed associations
|
|
113
113
|
* with cardinality 'to many' have an on-condition.
|
|
114
114
|
*
|
|
115
|
-
* @param {CSN.Artifact}
|
|
116
|
-
* @todo this is a member validator, is it not?
|
|
115
|
+
* @param {CSN.Artifact} member The member (e.g. element artifact)
|
|
117
116
|
*/
|
|
118
|
-
function checkManagedAssoc(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
!isManagedComposition.bind(this)(member)) {
|
|
122
|
-
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
|
|
123
|
-
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
|
|
124
|
-
// foreign key array, we won't emit a warning to avoid spamming the user.
|
|
125
|
-
const max = member.cardinality?.max ? member.cardinality.max : 1;
|
|
126
|
-
if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
|
|
127
|
-
const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
|
|
128
|
-
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
|
|
129
|
-
value: cardinality2str(member, false),
|
|
130
|
-
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
|
|
131
|
-
}, {
|
|
132
|
-
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
|
|
133
|
-
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
});
|
|
117
|
+
function checkManagedAssoc( member ) {
|
|
118
|
+
if (!member.target || isManagedComposition.bind(this)(member))
|
|
119
|
+
return;
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
121
|
+
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
|
|
122
|
+
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
|
|
123
|
+
// foreign key array, we won't emit a warning to avoid spamming the user.
|
|
124
|
+
const max = member.cardinality?.max ? member.cardinality.max : 1;
|
|
125
|
+
if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
|
|
126
|
+
const isNoDb = this.artifact['@cds.persistence.skip'] || this.artifact['@cds.persistence.exists'];
|
|
127
|
+
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
|
|
128
|
+
value: cardinality2str(member, false),
|
|
129
|
+
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
|
|
130
|
+
}, {
|
|
131
|
+
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
|
|
132
|
+
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
|
|
133
|
+
});
|
|
151
134
|
}
|
|
152
135
|
}
|
|
153
136
|
|
|
137
|
+
/**
|
|
138
|
+
*
|
|
139
|
+
* @param {CSN.Element} member The member
|
|
140
|
+
* @returns {boolean} Whether the member is managed composition
|
|
141
|
+
*/
|
|
142
|
+
function isManagedComposition( member ) {
|
|
143
|
+
if (member.targetAspect)
|
|
144
|
+
return true;
|
|
145
|
+
if (!member.target)
|
|
146
|
+
return false;
|
|
147
|
+
const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
|
|
148
|
+
return target.kind !== 'entity';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
154
152
|
/**
|
|
155
153
|
* All DB & OData flat mode must reject recursive type usages in entities
|
|
156
154
|
* 'items' break recursion as 'items' will turn into an NCLOB and the path
|