@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
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Create a command line option processor and define valid commands, options and parameters.
|
|
5
|
+
* In order to understand a command line like this:
|
|
6
|
+
* $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
|
|
7
|
+
*
|
|
8
|
+
* The following definitions should be made:
|
|
9
|
+
*
|
|
10
|
+
* ```js
|
|
11
|
+
* const optionProcessor = createOptionProcessor();
|
|
12
|
+
* optionProcessor
|
|
13
|
+
* .help(`General help text`);
|
|
14
|
+
* .option('-x, --long-form <i>')
|
|
15
|
+
* .option(' --foo')
|
|
16
|
+
* optionProcessor.command('toXyz')
|
|
17
|
+
* .help(`Help text for command "toXyz")
|
|
18
|
+
* .option('-y --y-in-long-form')
|
|
19
|
+
* .option(' --bar-wiz <w>', ['bla', 'foo'])
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Options *must* have a long form, can have at most one <param>, and optionally
|
|
23
|
+
* an array of valid param values as strings. Commands and param values must not
|
|
24
|
+
* start with '-'. The whole processor and each command may carry a help text.
|
|
25
|
+
* To actually parse a command line, use
|
|
26
|
+
* const cli = optionProcessor.processCmdLine(process.argv);
|
|
27
|
+
* (see below)
|
|
28
|
+
*/
|
|
25
29
|
function createOptionProcessor() {
|
|
26
30
|
const optionProcessor = {
|
|
27
31
|
commands: {},
|
|
@@ -30,11 +34,16 @@ function createOptionProcessor() {
|
|
|
30
34
|
optionClashes: [],
|
|
31
35
|
option,
|
|
32
36
|
command,
|
|
33
|
-
positionalArgument
|
|
37
|
+
positionalArgument: (argumentDefinition) => {
|
|
38
|
+
// Default positional arguments; may be overwritten by commands.
|
|
39
|
+
_setPositionalArguments(argumentDefinition);
|
|
40
|
+
return optionProcessor;
|
|
41
|
+
},
|
|
34
42
|
help,
|
|
35
43
|
processCmdLine,
|
|
36
44
|
verifyOptions,
|
|
37
45
|
camelOptionsForCommand,
|
|
46
|
+
// TODO: Why exported?
|
|
38
47
|
_parseCommandString,
|
|
39
48
|
_parseOptionString,
|
|
40
49
|
}
|
|
@@ -44,9 +53,10 @@ function createOptionProcessor() {
|
|
|
44
53
|
* API: Define a general option.
|
|
45
54
|
* @param {string} optString Option string describing the command line option.
|
|
46
55
|
* @param {string[]} [validValues] Array of valid values for the options.
|
|
56
|
+
* @param {object} [options] Further options such as `ignoreCase: true`
|
|
47
57
|
*/
|
|
48
|
-
function option(optString, validValues) {
|
|
49
|
-
return _addOption(optionProcessor, optString, validValues);
|
|
58
|
+
function option(optString, validValues, options) {
|
|
59
|
+
return _addOption(optionProcessor, optString, validValues, options);
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
/**
|
|
@@ -60,13 +70,18 @@ function createOptionProcessor() {
|
|
|
60
70
|
|
|
61
71
|
/**
|
|
62
72
|
* API: Define a command
|
|
63
|
-
* @param {string} cmdString Command name, e.g. 'S, toSql'
|
|
73
|
+
* @param {string} cmdString Command name, short and long form, e.g. 'S, toSql'
|
|
64
74
|
*/
|
|
65
75
|
function command(cmdString) {
|
|
66
76
|
/** @type {object} */
|
|
67
77
|
const command = {
|
|
68
78
|
options: {},
|
|
79
|
+
positionalArguments: [],
|
|
69
80
|
option,
|
|
81
|
+
positionalArgument: (argumentDefinition) => {
|
|
82
|
+
_setPositionalArguments(argumentDefinition, command.positionalArguments);
|
|
83
|
+
return command;
|
|
84
|
+
},
|
|
70
85
|
help,
|
|
71
86
|
..._parseCommandString(cmdString)
|
|
72
87
|
};
|
|
@@ -83,12 +98,12 @@ function createOptionProcessor() {
|
|
|
83
98
|
}
|
|
84
99
|
return command;
|
|
85
100
|
|
|
86
|
-
// API: Define a command option
|
|
87
|
-
function option(optString, validValues) {
|
|
88
|
-
return _addOption(command, optString, validValues);
|
|
101
|
+
// Command API: Define a command option
|
|
102
|
+
function option(optString, validValues, options) {
|
|
103
|
+
return _addOption(command, optString, validValues, options);
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
// API: Define the command help text
|
|
106
|
+
// Command API: Define the command help text
|
|
92
107
|
function help(text) {
|
|
93
108
|
command.helpText = text;
|
|
94
109
|
return command;
|
|
@@ -96,28 +111,35 @@ function createOptionProcessor() {
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
/**
|
|
99
|
-
*
|
|
100
|
-
* to either require N positional arguments or a dynamic number (but at least one)
|
|
101
|
-
*
|
|
114
|
+
* Set the positional arguments to the command line processor. Instructs the processor
|
|
115
|
+
* to either require N positional arguments or a dynamic number (but at least one).
|
|
116
|
+
* Note that you can only call this function once. Only the last invocation sets
|
|
117
|
+
* the positional arguments.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
|
|
120
|
+
* @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
|
|
121
|
+
* @private
|
|
102
122
|
*/
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
123
|
+
function _setPositionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
|
|
124
|
+
if (argList.find((arg) => arg.isDynamic)) {
|
|
105
125
|
throw new Error(`Can't add positional arguments after a dynamic one`);
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
const registeredNames =
|
|
109
|
-
const args =
|
|
128
|
+
const registeredNames = argList.map((arg) => arg.name);
|
|
129
|
+
const args = argumentDefinition.split(' ');
|
|
110
130
|
|
|
111
131
|
for (const arg of args) {
|
|
112
|
-
|
|
132
|
+
// Remove braces, dots and camelify.
|
|
133
|
+
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
|
|
134
|
+
|
|
113
135
|
if (registeredNames.includes(argName)) {
|
|
114
|
-
throw new Error(`Duplicate positional argument ${arg}`);
|
|
136
|
+
throw new Error(`Duplicate positional argument: ${arg}`);
|
|
115
137
|
}
|
|
116
138
|
if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
|
|
117
139
|
throw new Error(`Unknown positional argument syntax: ${arg}`)
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
|
|
142
|
+
argList.push({
|
|
121
143
|
name: argName,
|
|
122
144
|
isDynamic: isDynamicPositionalArgument(arg),
|
|
123
145
|
required: true
|
|
@@ -125,7 +147,6 @@ function createOptionProcessor() {
|
|
|
125
147
|
|
|
126
148
|
registeredNames.push(argName);
|
|
127
149
|
}
|
|
128
|
-
return optionProcessor;
|
|
129
150
|
}
|
|
130
151
|
|
|
131
152
|
/**
|
|
@@ -135,8 +156,10 @@ function createOptionProcessor() {
|
|
|
135
156
|
* @private
|
|
136
157
|
* @see option()
|
|
137
158
|
*/
|
|
138
|
-
function _addOption(command, optString, validValues) {
|
|
159
|
+
function _addOption(command, optString, validValues, options) {
|
|
139
160
|
const opt = _parseOptionString(optString, validValues);
|
|
161
|
+
Object.assign(opt, options);
|
|
162
|
+
|
|
140
163
|
if (command.options[opt.longName]) {
|
|
141
164
|
throw new Error(`Duplicate assignment for long option ${opt.longName}`);
|
|
142
165
|
} else if (optionProcessor.options[opt.longName]) {
|
|
@@ -203,7 +226,6 @@ function createOptionProcessor() {
|
|
|
203
226
|
let longName;
|
|
204
227
|
let shortName;
|
|
205
228
|
let param;
|
|
206
|
-
let camelName;
|
|
207
229
|
|
|
208
230
|
// split at spaces (with optional preceding comma)
|
|
209
231
|
const tokens = optString.trim().split(/,? +/);
|
|
@@ -247,26 +269,16 @@ function createOptionProcessor() {
|
|
|
247
269
|
throw new Error(`Valid values must be of type string: ${optString}`);
|
|
248
270
|
});
|
|
249
271
|
}
|
|
250
|
-
|
|
272
|
+
|
|
251
273
|
return {
|
|
252
274
|
longName,
|
|
253
275
|
shortName,
|
|
254
|
-
camelName,
|
|
276
|
+
camelName: camelifyLongOption(longName),
|
|
255
277
|
param,
|
|
256
278
|
validValues
|
|
257
279
|
}
|
|
258
280
|
}
|
|
259
281
|
|
|
260
|
-
// Return a camelCase name "fooBar" for a long option "--foo-bar"
|
|
261
|
-
function _camelify(opt) {
|
|
262
|
-
return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Return a long option name like "--foo-bar" for a camel-case name "fooBar"
|
|
266
|
-
function _unCamelify(opt) {
|
|
267
|
-
return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
282
|
// API: Let the option processor digest a command line 'argv'
|
|
271
283
|
// The expectation is to get a commandline like this:
|
|
272
284
|
// $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
|
|
@@ -322,7 +334,12 @@ function createOptionProcessor() {
|
|
|
322
334
|
argv = [ ...argv.slice(0, i), ...arg.split('='), ...argv.slice(i + 1)];
|
|
323
335
|
arg = argv[i];
|
|
324
336
|
}
|
|
325
|
-
|
|
337
|
+
|
|
338
|
+
if (arg === '--') {
|
|
339
|
+
// No more options after '--'
|
|
340
|
+
seenDashDash = true;
|
|
341
|
+
}
|
|
342
|
+
else if (!seenDashDash && arg.startsWith('-')) {
|
|
326
343
|
if (result.command) {
|
|
327
344
|
// We already have a command
|
|
328
345
|
const opt = optionProcessor.commands[result.command].options[arg];
|
|
@@ -364,10 +381,7 @@ function createOptionProcessor() {
|
|
|
364
381
|
}
|
|
365
382
|
}
|
|
366
383
|
}
|
|
367
|
-
else
|
|
368
|
-
// No more options after '--'
|
|
369
|
-
seenDashDash = true;
|
|
370
|
-
} else {
|
|
384
|
+
else {
|
|
371
385
|
// Command or arg
|
|
372
386
|
if (result.command === undefined) {
|
|
373
387
|
if (optionProcessor.commands[arg]) {
|
|
@@ -390,21 +404,37 @@ 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
|
+
return ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
|
|
424
|
+
}
|
|
425
|
+
|
|
400
426
|
function processPositionalArgument(argumentValue) {
|
|
401
|
-
|
|
427
|
+
const argList = getCurrentPositionArguments();
|
|
428
|
+
if ( result.args.length === 0 && argList.length === 0 )
|
|
402
429
|
return;
|
|
403
|
-
const inBounds = result.args.length <
|
|
404
|
-
const lastIndex = inBounds ? result.args.length :
|
|
405
|
-
const nextUnsetArgument =
|
|
430
|
+
const inBounds = result.args.length < argList.length;
|
|
431
|
+
const lastIndex = inBounds ? result.args.length : argList.length - 1;
|
|
432
|
+
const nextUnsetArgument = argList[lastIndex];
|
|
406
433
|
if (!inBounds && !nextUnsetArgument.isDynamic) {
|
|
407
|
-
result.
|
|
434
|
+
if (result.command)
|
|
435
|
+
result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
|
|
436
|
+
else
|
|
437
|
+
result.errors.push(`Too many arguments. Expected ${argList.length}`);
|
|
408
438
|
return;
|
|
409
439
|
}
|
|
410
440
|
result.args.length += 1;
|
|
@@ -447,13 +477,13 @@ function createOptionProcessor() {
|
|
|
447
477
|
result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
|
|
448
478
|
} else {
|
|
449
479
|
result.options[command][opt.camelName] = value;
|
|
450
|
-
if (opt
|
|
480
|
+
if (!isValidOptionValue(opt, value)) {
|
|
451
481
|
result.cmdErrors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
|
|
452
482
|
}
|
|
453
483
|
}
|
|
454
484
|
} else {
|
|
455
485
|
result.options[opt.camelName] = value;
|
|
456
|
-
if (opt
|
|
486
|
+
if (!isValidOptionValue(opt, value)) {
|
|
457
487
|
result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
|
|
458
488
|
}
|
|
459
489
|
}
|
|
@@ -486,7 +516,10 @@ function createOptionProcessor() {
|
|
|
486
516
|
}
|
|
487
517
|
|
|
488
518
|
if(options) {
|
|
489
|
-
[
|
|
519
|
+
[
|
|
520
|
+
'defaultBinaryLength', 'defaultStringLength',
|
|
521
|
+
/*'length', 'precision', 'scale'*/
|
|
522
|
+
].forEach(facet => {
|
|
490
523
|
if(options[facet] && isNaN(options[facet])) {
|
|
491
524
|
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
|
|
492
525
|
} else {
|
|
@@ -511,7 +544,7 @@ function createOptionProcessor() {
|
|
|
511
544
|
}
|
|
512
545
|
// Look at each supplied option
|
|
513
546
|
for (const camelName in options) {
|
|
514
|
-
const opt = opts[
|
|
547
|
+
const opt = opts[uncamelifyLongOption(camelName)];
|
|
515
548
|
let error;
|
|
516
549
|
if (!opt) {
|
|
517
550
|
// Don't report commands in top-level options
|
|
@@ -536,7 +569,7 @@ function createOptionProcessor() {
|
|
|
536
569
|
// Parameter is required for this option
|
|
537
570
|
if (typeof param === 'boolean') {
|
|
538
571
|
return `Missing value for option "${prefix}${opt.camelName}"`;
|
|
539
|
-
} else if (opt
|
|
572
|
+
} else if (!isValidOptionValue(opt, param)) {
|
|
540
573
|
return `Invalid value "${param}" for option "${prefix}${opt.camelName}" - use one of [${opt.validValues}]`;
|
|
541
574
|
}
|
|
542
575
|
return false;
|
|
@@ -551,38 +584,67 @@ function createOptionProcessor() {
|
|
|
551
584
|
}
|
|
552
585
|
}
|
|
553
586
|
|
|
587
|
+
function isValidOptionValue(opt, value) {
|
|
588
|
+
// Explicitly convert to string, input 'value' may be boolean
|
|
589
|
+
value = String(value);
|
|
590
|
+
if (!opt.validValues || !opt.validValues.length)
|
|
591
|
+
return true;
|
|
592
|
+
if (opt.ignoreCase)
|
|
593
|
+
return opt.validValues.some( valid => valid.toLowerCase() === value.toLowerCase() );
|
|
594
|
+
return opt.validValues.includes(value);
|
|
595
|
+
}
|
|
596
|
+
|
|
554
597
|
// Return an array of unique camelNames of the options for the specified command
|
|
555
598
|
// If invalid command -> an empty array
|
|
556
599
|
function camelOptionsForCommand(command) {
|
|
557
|
-
if (command
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
} else {
|
|
563
|
-
return [];
|
|
564
|
-
}
|
|
600
|
+
if (!command || !optionProcessor.commands[command])
|
|
601
|
+
return []
|
|
602
|
+
const cmd = optionProcessor.commands[command];
|
|
603
|
+
const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
|
|
604
|
+
return [...new Set(names)];
|
|
565
605
|
}
|
|
566
606
|
}
|
|
567
607
|
|
|
568
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Return a camelCase name "fooBar" for a long option "--foo-bar"
|
|
610
|
+
*/
|
|
611
|
+
function camelifyLongOption(opt) {
|
|
612
|
+
return opt.substring(2).replace(/-./g, s => s.substring(1).toUpperCase());
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Return a long option name like "--foo-bar" for a camel-case name "fooBar"
|
|
617
|
+
*/
|
|
618
|
+
function uncamelifyLongOption(opt) {
|
|
619
|
+
return `--${opt.replace(/[A-Z]/g, s => '-' + s.toLowerCase())}`;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Check if 'opt' looks like a "-f" short option
|
|
624
|
+
*/
|
|
569
625
|
function isShortOption(opt) {
|
|
570
626
|
return /^-[a-zA-Z?]$/.test(opt);
|
|
571
627
|
}
|
|
572
628
|
|
|
573
|
-
|
|
629
|
+
/**
|
|
630
|
+
* Check if 'opt' looks like a "--foo-bar" long option
|
|
631
|
+
*/
|
|
574
632
|
function isLongOption(opt) {
|
|
575
633
|
return /^--[a-zA-Z0-9-]+$/.test(opt);
|
|
576
634
|
}
|
|
577
635
|
|
|
578
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Check if 'opt' looks like a "<foobar>" parameter
|
|
638
|
+
*/
|
|
579
639
|
function isParam(opt) {
|
|
580
|
-
return /^<[a-zA-Z]+>$/.test(opt);
|
|
640
|
+
return /^<[a-zA-Z-]+>$/.test(opt);
|
|
581
641
|
}
|
|
582
642
|
|
|
583
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Check if 'arg' looks like "<foobar...>"
|
|
645
|
+
*/
|
|
584
646
|
function isDynamicPositionalArgument(arg) {
|
|
585
|
-
return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
|
|
647
|
+
return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
|
|
586
648
|
}
|
|
587
649
|
|
|
588
650
|
module.exports = {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"jsdoc/no-undefined-types": 0,
|
|
13
13
|
// eslint-plugin-jsdoc warning
|
|
14
14
|
"jsdoc/require-property": 0,
|
|
15
|
+
// most of the main functions have the normal forEachArtifact/Member signature anyway
|
|
16
|
+
"jsdoc/require-param-description": 0,
|
|
15
17
|
// =airbnb, >eslint:
|
|
16
18
|
"max-len": [ "error", {
|
|
17
19
|
"code": 110,
|
|
@@ -110,7 +110,8 @@ function checkActionOrFunction(art, artName, prop, path) {
|
|
|
110
110
|
* @param {CSN.Path} currPath The current path
|
|
111
111
|
*/
|
|
112
112
|
function checkUserDefinedType(type, typeName, currPath) {
|
|
113
|
-
|
|
113
|
+
// TODO: isBuiltinType does not resolve any type-chains.
|
|
114
|
+
if (!isBuiltinType(type.type) && type.kind && type.kind !== 'type') {
|
|
114
115
|
const serviceOfType = this.csnUtils.getServiceName(typeName);
|
|
115
116
|
if (serviceName && serviceName !== serviceOfType) {
|
|
116
117
|
// if (!(isMultiSchema && serviceOfType)) {
|
|
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
|
12
12
|
* @param {CSN.Path} path Path to the artifact
|
|
13
13
|
*/
|
|
14
14
|
function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
|
|
15
|
-
if (artifact.kind === 'entity' &&
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
|
|
16
16
|
if (!artifact.elements || !hasRealElements(artifact.elements))
|
|
17
|
-
this.error(
|
|
17
|
+
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -18,12 +18,12 @@ function validateForeignKeys(member) {
|
|
|
18
18
|
|
|
19
19
|
// Declared as arrow-function to keep scope the same (this value)
|
|
20
20
|
const handleAssociation = (mem) => {
|
|
21
|
-
for (
|
|
22
|
-
if (
|
|
23
|
-
if (!
|
|
21
|
+
for (const key of mem.keys) {
|
|
22
|
+
if (key.ref) {
|
|
23
|
+
if (!key._art)
|
|
24
24
|
continue;
|
|
25
25
|
// eslint-disable-next-line no-use-before-define
|
|
26
|
-
checkForItems(
|
|
26
|
+
checkForItems(key._art);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
};
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
14
14
|
// Declared as arrow-function to keep scope the same (this value)
|
|
15
15
|
const handleAssociation = (mem, fn) => {
|
|
16
|
-
for (
|
|
17
|
-
if (
|
|
18
|
-
if (!
|
|
16
|
+
for (const key of mem.keys) {
|
|
17
|
+
if (key.ref) {
|
|
18
|
+
if (!key._art)
|
|
19
19
|
continue;
|
|
20
|
-
fn(
|
|
20
|
+
fn(key._art);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -124,10 +124,8 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
124
124
|
for (const prop of generalQueryProperties) {
|
|
125
125
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
126
126
|
if (Array.isArray(queryPart)) {
|
|
127
|
-
for (
|
|
128
|
-
const part = queryPart[i];
|
|
127
|
+
for (const part of queryPart)
|
|
129
128
|
checkRef(part, prop === 'columns');
|
|
130
|
-
}
|
|
131
129
|
}
|
|
132
130
|
else if (typeof queryPart === 'object') {
|
|
133
131
|
checkRef(queryPart, prop === 'columns');
|
|
@@ -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);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isBetaEnabled } = require('../base/model');
|
|
4
|
+
|
|
5
|
+
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check that @sql.prepend annotation is not used on any elements and @sql.append is not used on elements in views.
|
|
9
|
+
*
|
|
10
|
+
* @param {CSN.Element} member
|
|
11
|
+
* @param {string} memberName
|
|
12
|
+
* @param {string} prop
|
|
13
|
+
* @param {CSN.Path} path
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
function checkSqlAnnotationOnElement(member, memberName, prop, path) {
|
|
17
|
+
if (isBetaEnabled(this.options, 'sqlSnippets')) {
|
|
18
|
+
if (member['@sql.replace'])
|
|
19
|
+
this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
20
|
+
if (member['@sql.prepend'])
|
|
21
|
+
this.error('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
|
|
22
|
+
|
|
23
|
+
if (member['@sql.append']) {
|
|
24
|
+
if (this.artifact.query)
|
|
25
|
+
this.error('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
|
|
26
|
+
else if (this.csnUtils.isStructured(member))
|
|
27
|
+
this.error('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
|
|
28
|
+
else
|
|
29
|
+
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} carrier element which has the annotation
|
|
36
|
+
* @param {string} annotation
|
|
37
|
+
* @param {CSN.Path} path
|
|
38
|
+
* @param {Function} error
|
|
39
|
+
* @param {CSN.Options} options
|
|
40
|
+
*/
|
|
41
|
+
function checkValidAnnoValue(carrier, annotation, path, error, options) {
|
|
42
|
+
if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
|
|
43
|
+
if (typeof carrier[annotation] !== 'string')
|
|
44
|
+
error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, `Annotation $(ANNO) must be a string, found $(TYPE)` );
|
|
45
|
+
else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
|
|
46
|
+
guardAgainstInjection(annotation, carrier[annotation], path, error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check that @sql.prepend is not used on views - only supported for entities (tables)
|
|
52
|
+
*
|
|
53
|
+
* @param {CSN.Artifact} artifact
|
|
54
|
+
* @param {string} artifactName
|
|
55
|
+
*/
|
|
56
|
+
function checkSqlAnnotationOnArtifact(artifact, artifactName) {
|
|
57
|
+
if (isBetaEnabled(this.options, 'sqlSnippets')) {
|
|
58
|
+
if (artifact['@sql.prepend']) {
|
|
59
|
+
if (artifact.query)
|
|
60
|
+
this.error('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
|
|
61
|
+
else
|
|
62
|
+
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (artifact['@sql.replace'])
|
|
66
|
+
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
67
|
+
|
|
68
|
+
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Anything that could terminate the "old" statement and start a new one basically.
|
|
73
|
+
const invalidInSnippet = [ ';', '--', '/*', '*/' ];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check that the common characters used to terminate the current statement and start a fresh one are not used.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} annoName
|
|
79
|
+
* @param {string} annoValue
|
|
80
|
+
* @param {CSN.Path} path
|
|
81
|
+
* @param {Function} error
|
|
82
|
+
*/
|
|
83
|
+
function guardAgainstInjection(annoName, annoValue, path, error) {
|
|
84
|
+
for (const invalid of invalidInSnippet) {
|
|
85
|
+
if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
|
|
86
|
+
error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
checkSqlAnnotationOnArtifact,
|
|
92
|
+
checkSqlAnnotationOnElement,
|
|
93
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { getVariableReplacement } = require('../model/csnUtils');
|
|
4
|
+
|
|
3
5
|
// We only care about the "wild" ones - $at is validated by the compiler
|
|
4
6
|
const magicVariables = {
|
|
5
7
|
$user: [
|
|
@@ -17,7 +19,7 @@ const magicVariables = {
|
|
|
17
19
|
*
|
|
18
20
|
* Valid ways:
|
|
19
21
|
* - We know what to do -> $user.id on HANA
|
|
20
|
-
* - The user tells us what to do -> options.
|
|
22
|
+
* - The user tells us what to do -> options.variableReplacements
|
|
21
23
|
*
|
|
22
24
|
* @param {object} parent Object with the ref as a property
|
|
23
25
|
* @param {string} name Name of the ref property on parent
|
|
@@ -28,8 +30,9 @@ function unknownMagicVariable(parent, name, ref) {
|
|
|
28
30
|
const [ head, ...rest ] = ref;
|
|
29
31
|
const tail = rest.join('.');
|
|
30
32
|
const magicVariable = magicVariables[head];
|
|
31
|
-
if (magicVariable && magicVariable.indexOf(tail) === -1
|
|
32
|
-
|
|
33
|
+
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
|
+
getVariableReplacement(ref, this.options) === null)
|
|
35
|
+
this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
package/lib/checks/validator.js
CHANGED
|
@@ -35,6 +35,10 @@ const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
|
35
35
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
36
36
|
const unknownMagic = require('./unknownMagic');
|
|
37
37
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
38
|
+
const {
|
|
39
|
+
checkSqlAnnotationOnArtifact,
|
|
40
|
+
checkSqlAnnotationOnElement,
|
|
41
|
+
} = require('./sql-snippets');
|
|
38
42
|
|
|
39
43
|
const forHanaMemberValidators
|
|
40
44
|
= [
|
|
@@ -45,6 +49,8 @@ const forHanaMemberValidators
|
|
|
45
49
|
checkExplicitlyNullableKeys,
|
|
46
50
|
managedWithoutKeys,
|
|
47
51
|
warnAboutDefaultOnAssociationForHanaCds,
|
|
52
|
+
// sql.prepend/append
|
|
53
|
+
checkSqlAnnotationOnElement,
|
|
48
54
|
];
|
|
49
55
|
|
|
50
56
|
const forHanaArtifactValidators
|
|
@@ -53,6 +59,8 @@ const forHanaArtifactValidators
|
|
|
53
59
|
validateCdsPersistenceAnnotation,
|
|
54
60
|
// virtual items are not persisted on the db
|
|
55
61
|
checkForEmptyOrOnlyVirtual,
|
|
62
|
+
// sql.prepend/append
|
|
63
|
+
checkSqlAnnotationOnArtifact,
|
|
56
64
|
];
|
|
57
65
|
|
|
58
66
|
const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
|