@sap/cds-compiler 2.5.0 → 2.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
package/lib/api/validate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { makeMessageFunction
|
|
3
|
+
const { makeMessageFunction } = require('../base/messages');
|
|
4
4
|
|
|
5
5
|
/* eslint-disable arrow-body-style */
|
|
6
6
|
const booleanValidator = {
|
|
@@ -94,6 +94,11 @@ const validators = {
|
|
|
94
94
|
expected: () => 'Integer literal',
|
|
95
95
|
found: val => `type ${ typeof val }`,
|
|
96
96
|
},
|
|
97
|
+
csnFlavor: {
|
|
98
|
+
validate: val => typeof val === 'string',
|
|
99
|
+
expected: () => 'type string',
|
|
100
|
+
found: val => `type ${ typeof val }`,
|
|
101
|
+
},
|
|
97
102
|
dictionaryPrototype: {
|
|
98
103
|
validate: () => true,
|
|
99
104
|
},
|
|
@@ -122,15 +127,17 @@ const allCombinationValidators = {
|
|
|
122
127
|
* Use a custom validator or "default" custom validator, fallback to Boolean validator.
|
|
123
128
|
*
|
|
124
129
|
* @param {object} options Flat options object to validate
|
|
130
|
+
* @param {string} moduleName The called module, e.g. 'for.odata', 'to.hdi'. Needed to initialize the message functions
|
|
125
131
|
* @param {object} [customValidators] Map of custom validators to use
|
|
126
132
|
* @param {string[]} [combinationValidators] Validate option combinations
|
|
127
133
|
* @returns {void}
|
|
128
134
|
* @throws {CompilationError} Throws in case of invalid option usage
|
|
129
135
|
*/
|
|
130
|
-
function validate(options, customValidators = {}, combinationValidators = []) {
|
|
136
|
+
function validate(options, moduleName, customValidators = {}, combinationValidators = []) {
|
|
137
|
+
// TODO: issuing messages in this function looks very strange...
|
|
131
138
|
{
|
|
132
139
|
const messageCollector = { messages: [] };
|
|
133
|
-
const { error, throwWithError } = makeMessageFunction(null, messageCollector);
|
|
140
|
+
const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
|
|
134
141
|
|
|
135
142
|
for (const optionName of Object.keys(options)) {
|
|
136
143
|
const optionValue = options[optionName];
|
|
@@ -142,7 +149,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
|
|
|
142
149
|
throwWithError();
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
const message = makeMessageFunction(null, options);
|
|
152
|
+
const message = makeMessageFunction(null, options, moduleName);
|
|
146
153
|
|
|
147
154
|
for (const combinationValidatorName of combinationValidators.concat([ 'beta-no-test' ])) {
|
|
148
155
|
const combinationValidator = allCombinationValidators[combinationValidatorName];
|
|
@@ -150,10 +157,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
|
|
|
150
157
|
message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
|
|
154
|
-
// But be aware that it only throws with non-configurable errors and that this
|
|
155
|
-
// will lead to issues in test3. See #6037
|
|
156
|
-
handleMessages(undefined, options);
|
|
160
|
+
message.throwWithError();
|
|
157
161
|
}
|
|
158
162
|
/* eslint-enable jsdoc/no-undefined-types */
|
|
159
163
|
|
package/lib/backends.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
const { transformForHanaWithCsn } = require('./transform/forHanaNew');
|
|
7
7
|
const { compactModel, sortCsn } = require('./json/to-csn')
|
|
8
8
|
const { toCdsSourceCsn } = require('./render/toCdl');
|
|
9
|
-
const { toHdbcdsSource } = require('./render/toHdbcds');
|
|
10
9
|
const { toSqlDdl } = require('./render/toSql');
|
|
11
10
|
const { toRenameDdl } = require('./render/toRename');
|
|
12
11
|
const { manageConstraints, listReferentialIntegrityViolations } = require('./render/manageConstraints');
|
|
@@ -19,85 +18,6 @@ const timetrace = require('./utils/timetrace');
|
|
|
19
18
|
const { makeMessageFunction } = require('./base/messages');
|
|
20
19
|
const { forEachDefinition } = require('./model/csnUtils');
|
|
21
20
|
|
|
22
|
-
/**
|
|
23
|
-
* Transform a CSN into HANA-compatible CDS source.
|
|
24
|
-
* The following options control what is actually generated (see help above):
|
|
25
|
-
* options : {
|
|
26
|
-
* toHana.names
|
|
27
|
-
* toHana.src
|
|
28
|
-
* toHana.csn
|
|
29
|
-
* }
|
|
30
|
-
* Options provided here are merged with (and take precedence over) options from 'model'.
|
|
31
|
-
* If 'toHana.names' is not provided, 'quoted' is used.
|
|
32
|
-
* If neither 'toHana.src' nor 'toHana.csn' are provided, the default is to generate only HANA CDS
|
|
33
|
-
* source files.
|
|
34
|
-
* If all provided options are part of 'toHana', the 'toHana' wrapper can be omitted.
|
|
35
|
-
* The result object contains the generation results as follows (as enabled in 'options'):
|
|
36
|
-
* result : {
|
|
37
|
-
* csn : the (compact) transformed CSN model
|
|
38
|
-
* hdbcds : a dictionary of top-level artifact names, containing for each name 'X':
|
|
39
|
-
* <X> : the HANA CDS source string of the artifact 'X'. Please note that the
|
|
40
|
-
* name of 'X' may contain characters that are not legal for filenames on
|
|
41
|
-
* all operating systems (e.g. ':', '\' or '/').
|
|
42
|
-
* X reflects the naming policy set by toHana.names
|
|
43
|
-
* }
|
|
44
|
-
* Throws a CompilationError on errors.
|
|
45
|
-
*
|
|
46
|
-
* @param {CSN.Model} csn
|
|
47
|
-
* @param {CSN.Options} [options]
|
|
48
|
-
*/
|
|
49
|
-
function toHanaWithCsn(csn, options) {
|
|
50
|
-
timetrace.start('toHanaWithCsn');
|
|
51
|
-
// In case of API usage the options are in the 'options' argument
|
|
52
|
-
// put the OData specific options under the 'options.toHana' wrapper
|
|
53
|
-
// and leave the rest under 'options'
|
|
54
|
-
if (options && !options.toHana) {
|
|
55
|
-
_wrapRelevantOptionsForCmd(options, 'toHana');
|
|
56
|
-
}
|
|
57
|
-
// Provide defaults and merge options with those from model
|
|
58
|
-
options = mergeOptions({ toHana : getDefaultBackendOptions().toHana }, options);
|
|
59
|
-
|
|
60
|
-
// Provide something to generate if nothing else was given (conditional default)
|
|
61
|
-
if (!options.toHana.src && !options.toHana.csn) {
|
|
62
|
-
options.toHana.src = true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { warning } = makeMessageFunction(csn, options, 'to.hdbcds');
|
|
66
|
-
|
|
67
|
-
// Verify options
|
|
68
|
-
optionProcessor.verifyOptions(options, 'toHana', true).forEach(complaint => warning(null, null, `${complaint}`));
|
|
69
|
-
|
|
70
|
-
// Special case: For naming variant 'hdbcds' in combination with 'toHana' (and only there!), 'forHana'
|
|
71
|
-
// must leave namespaces, structs and associations alone.
|
|
72
|
-
if (options.toHana.names === 'hdbcds') {
|
|
73
|
-
options = mergeOptions(options, { forHana : { keepNamespaces: true, keepStructsAssocs: true } });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
options = mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana });
|
|
77
|
-
|
|
78
|
-
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)
|
|
79
|
-
let forHanaCsn = transformForHanaWithCsn(csn, options, 'to.hdbcds');
|
|
80
|
-
|
|
81
|
-
// Assemble result
|
|
82
|
-
let result = {};
|
|
83
|
-
|
|
84
|
-
if (options.toHana.src) {
|
|
85
|
-
if(options.testMode){
|
|
86
|
-
const sorted = sortCsn(forHanaCsn, options);
|
|
87
|
-
result = toHdbcdsSource(sorted, options);
|
|
88
|
-
} else {
|
|
89
|
-
result = toHdbcdsSource(forHanaCsn, options);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (options.toHana.csn) {
|
|
94
|
-
result.csn = options.testMode ? sortCsn(forHanaCsn, options) : forHanaCsn;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
timetrace.stop();
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
21
|
/**
|
|
102
22
|
* Generate ODATA for `csn` using `options`.
|
|
103
23
|
* The twin of the toOdata function but using CSN
|
|
@@ -603,7 +523,6 @@ function _wrapRelevantOptionsForCmd(options, command) {
|
|
|
603
523
|
}
|
|
604
524
|
|
|
605
525
|
module.exports = {
|
|
606
|
-
toHanaWithCsn,
|
|
607
526
|
toOdataWithCsn,
|
|
608
527
|
preparedCsnToEdmx,
|
|
609
528
|
preparedCsnToEdmxAll,
|
package/lib/base/keywords.js
CHANGED
|
@@ -194,7 +194,7 @@ module.exports = {
|
|
|
194
194
|
'OTHERS',
|
|
195
195
|
'TIES',
|
|
196
196
|
],
|
|
197
|
-
// HANA keywords, used
|
|
197
|
+
// HANA keywords, used for smart quoting in to-hdi.plain
|
|
198
198
|
// Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html
|
|
199
199
|
// Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
|
|
200
200
|
// in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
|
|
@@ -673,5 +673,35 @@ module.exports = {
|
|
|
673
673
|
'WITHIN',
|
|
674
674
|
'XMLTABLE',
|
|
675
675
|
'YEAR'
|
|
676
|
-
]
|
|
676
|
+
],
|
|
677
|
+
// HANA CDS keywords, used for smart quoting in to-hdbcds.plain
|
|
678
|
+
hdbcds: [
|
|
679
|
+
'ALL', 'ALTER', 'AS',
|
|
680
|
+
'BEFORE', 'BEGIN', 'BOTH',
|
|
681
|
+
'CASE', 'CHAR', 'CONDITION',
|
|
682
|
+
'CONNECT', 'CROSS', 'CUBE',
|
|
683
|
+
'CURRENT_CONNECTION', 'CURRENT_DATE', 'CURRENT_SCHEMA',
|
|
684
|
+
'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER',
|
|
685
|
+
'CURRENT_UTCDATE', 'CURRENT_UTCTIME', 'CURRENT_UTCTIMESTAMP',
|
|
686
|
+
'CURRVAL', 'CURSOR', 'DECLARE',
|
|
687
|
+
'DISTINCT', 'ELSE', 'ELSEIF',
|
|
688
|
+
'ELSIF', 'END', 'EXCEPT',
|
|
689
|
+
'EXCEPTION', 'EXEC', 'FOR',
|
|
690
|
+
'FROM', 'FULL', 'GROUP',
|
|
691
|
+
'HAVING', 'IF', 'IN',
|
|
692
|
+
'INNER', 'INOUT', 'INTERSECT',
|
|
693
|
+
'INTO', 'IS', 'JOIN',
|
|
694
|
+
'LEADING', 'LEFT', 'LIMIT',
|
|
695
|
+
'LOOP', 'MINUS', 'NATURAL',
|
|
696
|
+
'NEXTVAL', 'NULL', 'ON',
|
|
697
|
+
'ORDER', 'OUT', 'OUTER',
|
|
698
|
+
'PRIOR', 'RETURN', 'RETURNS',
|
|
699
|
+
'REVERSE', 'RIGHT', 'ROLLUP',
|
|
700
|
+
'ROWID', 'SELECT', 'SET',
|
|
701
|
+
'SQL', 'START', 'SYSDATE',
|
|
702
|
+
'SYSTIME', 'SYSTIMESTAMP', 'SYSUUID',
|
|
703
|
+
'TOP', 'TRAILING', 'UNION',
|
|
704
|
+
'USING', 'VALUES', 'WHEN',
|
|
705
|
+
'WHERE', 'WHILE', 'WITH'
|
|
706
|
+
]
|
|
677
707
|
}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
// Central registry for messages.
|
|
2
2
|
|
|
3
|
+
// `centralMessages` contains all details of a message-id except its standard texts
|
|
4
|
+
// (`standardTexts` exists for that). Only `severity` is required, all other
|
|
5
|
+
// properties are optional.
|
|
6
|
+
|
|
7
|
+
// The user can specify "severity wishes" via the option `severities`. Errors
|
|
8
|
+
// that don't have a `configurableFor` property cannot be reclassified by
|
|
9
|
+
// users. If a module is used that is _not_ listed in `configurableFor` (if it
|
|
10
|
+
// is an array) property of the message then the message cannot be
|
|
11
|
+
// reclassified.
|
|
12
|
+
|
|
13
|
+
// We also allow `configurableFor` to have value `true` for errors which are
|
|
14
|
+
// always configurable; useful for issues like deprecated syntax variants which
|
|
15
|
+
// do not affect the compiler or CSN processors. Temporarily, we also allow
|
|
16
|
+
// value `deprecated` for errors which are only configurable if the option
|
|
17
|
+
// `deprecated.downgradableErrors` is set.
|
|
18
|
+
|
|
19
|
+
// Messages other than errors can always be reclassified by the user except if
|
|
20
|
+
// the module is listed in the message's `errorFor` property.
|
|
21
|
+
|
|
22
|
+
// __NEW__: If the future `poc` (proof of concept) or `sloppy` option is set,
|
|
23
|
+
// the module name `compile` is added to all configurable messages, i.e. to all
|
|
24
|
+
// `configurableFor` arrays. (module `compile` includes all parsers and the
|
|
25
|
+
// core compiler). This allows creators of _non-productive models_ to
|
|
26
|
+
// reclassify errors which usually cannot be reclassified, and continue the
|
|
27
|
+
// compilation but has the side effect that the result may be unstable, hence
|
|
28
|
+
// "sloppy": with an upcoming _minor_ version of the compiler, the compilation
|
|
29
|
+
// might lead to an error anyway or the compiled CSN might look different.
|
|
30
|
+
|
|
3
31
|
'use strict';
|
|
4
32
|
|
|
5
33
|
/**
|
|
@@ -59,13 +87,15 @@ const centralMessages = {
|
|
|
59
87
|
'param-default': { severity: 'Error', configurableFor: 'deprecated' }, // not supported yet
|
|
60
88
|
|
|
61
89
|
'query-undefined-element': { severity: 'Error' },
|
|
90
|
+
'query-unexpected-assoc-hdbcds': { severity: 'Error' },
|
|
91
|
+
'query-unexpected-structure-hdbcds': { severity: 'Error' },
|
|
62
92
|
|
|
63
93
|
'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
|
|
64
94
|
'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
|
|
65
95
|
'type-ambiguous-target': { severity: 'Warning' },
|
|
66
96
|
|
|
67
97
|
'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' },
|
|
68
|
-
'ref-undefined-art': { severity: 'Error' },
|
|
98
|
+
'ref-undefined-art': { severity: 'Error' },
|
|
69
99
|
'ref-undefined-def': { severity: 'Error' },
|
|
70
100
|
'ref-undefined-var': { severity: 'Error' },
|
|
71
101
|
'ref-undefined-element': { severity: 'Error' },
|
|
@@ -100,8 +130,12 @@ const centralMessages = {
|
|
|
100
130
|
'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
|
|
101
131
|
'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
|
|
102
132
|
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
|
|
103
|
-
'odata-spec-violation-array
|
|
104
|
-
'odata-spec-violation-constraints': { severity: '
|
|
133
|
+
'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
|
|
134
|
+
'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
|
|
135
|
+
'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
|
|
136
|
+
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
|
|
137
|
+
'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
|
|
138
|
+
'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
|
|
105
139
|
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
|
|
106
140
|
'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
|
|
107
141
|
};
|
|
@@ -191,6 +225,10 @@ const centralMessageTexts = {
|
|
|
191
225
|
'expected-target': 'An entity or an aspect is expected here',
|
|
192
226
|
'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
|
|
193
227
|
'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
|
|
228
|
+
|
|
229
|
+
'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
|
|
230
|
+
'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
|
|
231
|
+
|
|
194
232
|
'ref-sloppy-type': 'A type or an element is expected here',
|
|
195
233
|
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
|
|
196
234
|
'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
|
|
@@ -204,13 +242,29 @@ const centralMessageTexts = {
|
|
|
204
242
|
},
|
|
205
243
|
|
|
206
244
|
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
|
|
207
|
-
'odata-spec-violation-array-of-v2': 'EDM $(LITERAL) must not have an attribute $(PROP) with the value $(NAME)',
|
|
208
|
-
'odata-spec-violation-param-v2' : 'Type of EDM FunctionImport Parameter must be an EDM SimpleType or ComplexType',
|
|
209
|
-
'odata-spec-violation-returns-v2': 'EDM FunctionImport must return EDM SimpleType, ComplexType, EntityType or a collection of one of these',
|
|
210
|
-
'odata-spec-violation-assoc-v2': 'EDM ComplexType $(NAME) must not contain an EDM NavigationProperty',
|
|
211
|
-
'odata-spec-violation-attribute': 'EDM Property $(NAME) has no attribute $(PROP)',
|
|
212
|
-
'odata-spec-violation-property-name': 'EDM Property $(NAME) must not have the same name as the declaring $(TYPE)',
|
|
213
245
|
|
|
246
|
+
// OData version dependent messages
|
|
247
|
+
'odata-spec-violation-array': 'Unexpected array type for $(API)',
|
|
248
|
+
'odata-spec-violation-param' : 'Expected parameter to be typed with either scalar or structured type for $(API)',
|
|
249
|
+
'odata-spec-violation-returns': 'Expected $(KIND) to return one or many values of scalar, complex, entity or view type for $(API)',
|
|
250
|
+
'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
|
|
251
|
+
'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
|
|
252
|
+
// version independent messages
|
|
253
|
+
'odata-spec-violation-key-array': {
|
|
254
|
+
std: 'Unexpected array type for element $(NAME)',
|
|
255
|
+
scalar: 'Unexpected array type'
|
|
256
|
+
},
|
|
257
|
+
'odata-spec-violation-key-null': {
|
|
258
|
+
std: 'Expected key element $(NAME) to be not nullable', // structured
|
|
259
|
+
scalar: 'Expected key element to be not nullable' // flat
|
|
260
|
+
},
|
|
261
|
+
'odata-spec-violation-key-type': {
|
|
262
|
+
std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
|
|
263
|
+
scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
|
|
264
|
+
},
|
|
265
|
+
'odata-spec-violation-type': 'Expected element to have a type',
|
|
266
|
+
'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
|
|
267
|
+
'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
|
|
214
268
|
}
|
|
215
269
|
|
|
216
270
|
/**
|
package/lib/base/messages.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Functions and classes for syntax messages
|
|
2
2
|
|
|
3
|
+
// See internalDoc/ReportingMessages.md and lib/base/message-registry.js for details.
|
|
4
|
+
|
|
3
5
|
'use strict';
|
|
4
6
|
|
|
5
7
|
const term = require('../utils/term');
|
|
@@ -92,14 +94,17 @@ class CompilationError extends Error {
|
|
|
92
94
|
|
|
93
95
|
/** @type {boolean} model */
|
|
94
96
|
this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics
|
|
95
|
-
//
|
|
97
|
+
// property `model` is only set with options.attachValidNames:
|
|
96
98
|
Object.defineProperty( this, 'model', { value: model || undefined, configurable: true } );
|
|
97
99
|
}
|
|
98
100
|
toString() { // does not really help -> set message
|
|
99
101
|
return this.message.includes('\n')
|
|
100
102
|
? this.message
|
|
101
|
-
: this.message + '\n' + this.
|
|
103
|
+
: this.message + '\n' + this.messages.map( m => m.toString() ).join('\n');
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Use `.messages` instead.
|
|
107
|
+
*/
|
|
103
108
|
get errors() {
|
|
104
109
|
return this.messages;
|
|
105
110
|
}
|
|
@@ -210,6 +215,7 @@ const severitySpecs = {
|
|
|
210
215
|
* @returns {CSN.MessageSeverity}
|
|
211
216
|
*
|
|
212
217
|
* TODO: we should pass options as usual
|
|
218
|
+
* TODO: should be part of the returned function
|
|
213
219
|
*/
|
|
214
220
|
function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable ) {
|
|
215
221
|
const spec = centralMessages[id] || { severity };
|
|
@@ -294,6 +300,30 @@ function searchForLocation( model, path ) {
|
|
|
294
300
|
return lastLocation;
|
|
295
301
|
}
|
|
296
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Create the `message` functions to emit messages.
|
|
305
|
+
* See internalDoc/ReportingMessages.md for detail
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```
|
|
309
|
+
* const { createMessageFunctions } = require(‘../base/messages’);
|
|
310
|
+
* function module( …, options ) {
|
|
311
|
+
* const { message, info, throwWithError } = createMessageFunctions( options, moduleName );
|
|
312
|
+
* // [...]
|
|
313
|
+
* message( 'message-id', <location>, <text-arguments>, <severity>, <text> );
|
|
314
|
+
* info( 'message-id', <location>, [<text-arguments>,] <text> );
|
|
315
|
+
* // [...]
|
|
316
|
+
* throwWithError();
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
* @param {CSN.Options} [options]
|
|
320
|
+
* @param {string} [moduleName]
|
|
321
|
+
* @param {object} [model=null] the CSN or XSN model, used for convenience
|
|
322
|
+
*/
|
|
323
|
+
function createMessageFunctions( options, moduleName, model = null ) {
|
|
324
|
+
return makeMessageFunction( model, options, moduleName, true );
|
|
325
|
+
}
|
|
326
|
+
|
|
297
327
|
/**
|
|
298
328
|
* Create the `message` function to emit messages.
|
|
299
329
|
*
|
|
@@ -312,8 +342,9 @@ function searchForLocation( model, path ) {
|
|
|
312
342
|
* @param {object} model
|
|
313
343
|
* @param {CSN.Options} [options]
|
|
314
344
|
* @param {string} [moduleName]
|
|
345
|
+
* @param {boolean} [throwOnlyWithNew=false] behave like createMessageFunctions
|
|
315
346
|
*/
|
|
316
|
-
function makeMessageFunction( model, options =
|
|
347
|
+
function makeMessageFunction( model, options, moduleName = null, throwOnlyWithNew = false ) {
|
|
317
348
|
// ensure message consistency during runtime with --test-mode
|
|
318
349
|
if (options.testMode)
|
|
319
350
|
_check$Init( options );
|
|
@@ -327,11 +358,12 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
|
|
|
327
358
|
*
|
|
328
359
|
* @type {CSN.Message[]}
|
|
329
360
|
*/
|
|
330
|
-
let messages = options
|
|
331
|
-
|
|
361
|
+
let messages = options.messages || [];
|
|
362
|
+
let hasNewError = false;
|
|
332
363
|
return {
|
|
333
|
-
message, error, warning, info, debug,
|
|
334
|
-
|
|
364
|
+
message, error, warning, info, debug, messages,
|
|
365
|
+
throwWithError: (throwOnlyWithNew ? throwWithError : throwWithAnyError),
|
|
366
|
+
callTransparently, moduleName,
|
|
335
367
|
};
|
|
336
368
|
|
|
337
369
|
function _message(id, location, textOrArguments, severity, texts = null) {
|
|
@@ -342,7 +374,6 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
|
|
|
342
374
|
texts = { std: textOrArguments };
|
|
343
375
|
textOrArguments = {};
|
|
344
376
|
}
|
|
345
|
-
|
|
346
377
|
if (id) {
|
|
347
378
|
if (options.testMode && !options.$recompile)
|
|
348
379
|
_check$Consistency( id, moduleName, severity, texts, options )
|
|
@@ -360,6 +391,8 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
|
|
|
360
391
|
msg.$location.address = { definition };
|
|
361
392
|
|
|
362
393
|
messages.push( msg );
|
|
394
|
+
hasNewError = hasNewError || msg.severity === 'Error' &&
|
|
395
|
+
!(options.testMode && msg.messageId && isDowngradable( msg.messageId, moduleName ));
|
|
363
396
|
if (!hasMessageArray)
|
|
364
397
|
console.error( messageString( msg ) );
|
|
365
398
|
return msg;
|
|
@@ -488,6 +521,11 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
|
|
|
488
521
|
return _message(id, location, textOrArguments, 'Debug', texts);
|
|
489
522
|
}
|
|
490
523
|
|
|
524
|
+
function throwWithError() {
|
|
525
|
+
if (hasNewError)
|
|
526
|
+
throw new CompilationError( messages, options.attachValidNames && model );
|
|
527
|
+
}
|
|
528
|
+
|
|
491
529
|
/**
|
|
492
530
|
* Throws a CompilationError exception if there is at least one error message
|
|
493
531
|
* in the model's messages after reclassifying existing messages according to
|
|
@@ -495,22 +533,20 @@ function makeMessageFunction( model, options = model.options || {}, moduleName =
|
|
|
495
533
|
* If `--test-mode` is enabled, this function will only throw if the
|
|
496
534
|
* error *cannot* be downgraded to a warning. This is done to ensure that
|
|
497
535
|
* developers do not rely on certain errors leading to an exception.
|
|
498
|
-
*
|
|
499
|
-
* @param {CSN.Message[]} [msgs] Which messages to check for. Default: The ones of
|
|
500
|
-
* this makeMessageFunction() scope.
|
|
501
536
|
*/
|
|
502
|
-
function
|
|
503
|
-
if (!
|
|
537
|
+
function throwWithAnyError() {
|
|
538
|
+
if (!messages || !messages.length)
|
|
504
539
|
return;
|
|
505
|
-
reclassifyMessagesForModule(
|
|
540
|
+
reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
|
|
506
541
|
const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
|
|
507
|
-
if (hasError(
|
|
508
|
-
throw new CompilationError(
|
|
542
|
+
if (hasError( messages, moduleName ))
|
|
543
|
+
throw new CompilationError( messages, options.attachValidNames && model );
|
|
509
544
|
}
|
|
510
545
|
|
|
511
546
|
/**
|
|
512
547
|
* Collects all messages during the call of the callback function instead of
|
|
513
548
|
* storing them in the model. Returns the collected messages.
|
|
549
|
+
* Not yet in use.
|
|
514
550
|
*
|
|
515
551
|
* @param {Function} callback
|
|
516
552
|
* @param {...any} args
|
|
@@ -613,6 +649,8 @@ const paramsTransform = {
|
|
|
613
649
|
// more complex convenience:
|
|
614
650
|
names: transformManyWith( quoted ),
|
|
615
651
|
number: n => n,
|
|
652
|
+
line: l => l,
|
|
653
|
+
col: c => c,
|
|
616
654
|
literal: l => l,
|
|
617
655
|
art: transformArg,
|
|
618
656
|
service: transformArg,
|
|
@@ -993,12 +1031,12 @@ function homeSortName( { home, messageId } ) {
|
|
|
993
1031
|
|
|
994
1032
|
/**
|
|
995
1033
|
* Removes duplicate messages from the given messages array without destroying
|
|
996
|
-
* references to the array.
|
|
1034
|
+
* references to the array, i.e. removes them in-place.
|
|
997
1035
|
*
|
|
998
|
-
* Does NOT keep the original order!
|
|
1036
|
+
* _Note_: Does NOT keep the original order!
|
|
999
1037
|
*
|
|
1000
1038
|
* Two messages are the same if they have the same message hash. See messageHash().
|
|
1001
|
-
* If one of the two is more precise then it replaces the other.
|
|
1039
|
+
* If one of the two is more precise, then it replaces the other.
|
|
1002
1040
|
* A message is more precise if it is contained in the other or if
|
|
1003
1041
|
* the first does not have an endLine/endCol.
|
|
1004
1042
|
*
|
|
@@ -1197,7 +1235,10 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1197
1235
|
if (!csnPath[index + 1]) {
|
|
1198
1236
|
result += select();
|
|
1199
1237
|
}
|
|
1200
|
-
|
|
1238
|
+
// only print last query prop for paths like
|
|
1239
|
+
// [... 'query', 'SELECT', 'from', 'SELECT', 'elements', 'struct'] -> select:2/element:"struct"
|
|
1240
|
+
// no from in the semantic location in this case
|
|
1241
|
+
else if (queryProps.includes(csnPath[index + 1]) && (!csnPath[index + 2] || query.isOnlySelect)) {
|
|
1201
1242
|
const clause = csnPath[index + 1];
|
|
1202
1243
|
result += select();
|
|
1203
1244
|
result += `/${ clause }`;
|
|
@@ -1266,7 +1307,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1266
1307
|
if (currentThing.as)
|
|
1267
1308
|
result += `:${ _quoted(currentThing.as) }`;
|
|
1268
1309
|
else
|
|
1269
|
-
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.join('.')) }` : '';
|
|
1310
|
+
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.map(r => r.id ? r.id : r).join('.')) }` : '';
|
|
1270
1311
|
|
|
1271
1312
|
break;
|
|
1272
1313
|
}
|
|
@@ -1380,6 +1421,7 @@ module.exports = {
|
|
|
1380
1421
|
messageStringMultiline,
|
|
1381
1422
|
messageContext,
|
|
1382
1423
|
searchName,
|
|
1424
|
+
createMessageFunctions,
|
|
1383
1425
|
makeMessageFunction,
|
|
1384
1426
|
artName,
|
|
1385
1427
|
handleMessages,
|
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
|
-
keylessManagedAssoc: true,
|
|
23
22
|
foreignKeyConstraints: true,
|
|
24
23
|
toRename: true,
|
|
25
24
|
addTextsLanguageAssoc: true,
|
|
@@ -28,9 +27,9 @@ const availableBetaFlags = {
|
|
|
28
27
|
mapAssocToJoinCardinality: true,
|
|
29
28
|
ignoreAssocPublishingInUnion: true,
|
|
30
29
|
nestedProjections: true,
|
|
30
|
+
enableUniversalCsn: true,
|
|
31
|
+
windowFunctions: true,
|
|
31
32
|
// disabled by --beta-mode
|
|
32
|
-
pretransformedCSN: false,
|
|
33
|
-
renderSQL: false,
|
|
34
33
|
nestedServices: false,
|
|
35
34
|
};
|
|
36
35
|
|
|
@@ -35,8 +35,33 @@ function validateDefaultValues(member, memberName, prop, path) {
|
|
|
35
35
|
* @param {CSN.Path} path Path to the member
|
|
36
36
|
*/
|
|
37
37
|
function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
|
|
38
|
-
if (member.default && prop === 'params' && this.options.
|
|
38
|
+
if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
|
|
39
39
|
this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* For HANA CDS, we render a default for a mixin if the projected entity contains
|
|
44
|
+
* a derived association with a default defined on it. This leads to a deployment error
|
|
45
|
+
* and should be warned about.
|
|
46
|
+
*
|
|
47
|
+
* @param {CSN.Element} member Member to validate
|
|
48
|
+
* @param {string} memberName Name of the member
|
|
49
|
+
* @param {string} prop Property being looped over
|
|
50
|
+
* @param {CSN.Path} path Path to the member
|
|
51
|
+
*/
|
|
52
|
+
function warnAboutDefaultOnAssociationForHanaCds(member, memberName, prop, path) {
|
|
53
|
+
const art = this.csn.definitions[path[1]];
|
|
54
|
+
if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
|
|
55
|
+
this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
|
|
56
|
+
{
|
|
57
|
+
std: 'Unexpected default defined on association',
|
|
58
|
+
comp: 'Unexpected default defined on composition',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
validateDefaultValues,
|
|
65
|
+
rejectParamDefaultsInHanaCds,
|
|
66
|
+
warnAboutDefaultOnAssociationForHanaCds,
|
|
67
|
+
};
|
package/lib/checks/elements.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
|
|
4
4
|
const { isGeoTypeName } = require('../compiler/builtins');
|
|
5
|
-
const { isBetaEnabled } = require('../base/model');
|
|
6
5
|
|
|
7
6
|
// Only to be used with validator.js - a correct `this` value needs to be provided!
|
|
8
7
|
|
|
@@ -103,15 +102,11 @@ function checkVirtualElement(member) {
|
|
|
103
102
|
* @param {CSN.Artifact} art The artifact
|
|
104
103
|
*/
|
|
105
104
|
function checkManagedAssoc(art) {
|
|
106
|
-
forEachMemberRecursively(art, (member
|
|
105
|
+
forEachMemberRecursively(art, (member) => {
|
|
107
106
|
if (this.csnUtils.isAssocOrComposition(member.type) &&
|
|
108
107
|
!isManagedComposition.bind(this)(member)) {
|
|
109
108
|
if (member.on)
|
|
110
109
|
return;
|
|
111
|
-
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && (!member.keys || member.keys.length === 0)) {
|
|
112
|
-
this.error(null, member.$path, { name: memberName },
|
|
113
|
-
`The managed association $(NAME) has no foreign keys`);
|
|
114
|
-
}
|
|
115
110
|
const max = member.cardinality && member.cardinality.max;
|
|
116
111
|
if (max === '*' || Number(max) > 1) {
|
|
117
112
|
const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isBetaEnabled } = require('../base/model');
|
|
4
|
-
|
|
5
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -20,17 +18,13 @@ function validateForeignKeys(member) {
|
|
|
20
18
|
|
|
21
19
|
// Declared as arrow-function to keep scope the same (this value)
|
|
22
20
|
const handleAssociation = (mem) => {
|
|
23
|
-
let elementCount = 0;
|
|
24
21
|
for (let i = 0; i < mem.keys.length; i++) {
|
|
25
22
|
if (mem.keys[i].ref) {
|
|
26
23
|
if (!mem.keys[i]._art)
|
|
27
24
|
continue;
|
|
28
25
|
// eslint-disable-next-line no-use-before-define
|
|
29
26
|
checkForItems(mem.keys[i]._art);
|
|
30
|
-
elementCount++;
|
|
31
27
|
}
|
|
32
|
-
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && elementCount === 0)
|
|
33
|
-
this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
|
|
34
28
|
}
|
|
35
29
|
};
|
|
36
30
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Trigger a recompilation in case of an association without .keys and without .on
|
|
6
|
+
*
|
|
7
|
+
* @param {CSN.Element} member the element to be checked
|
|
8
|
+
* @param {string} memberName the elements name
|
|
9
|
+
* @param {string} prop which kind of member are we looking at -> only prop "elements"
|
|
10
|
+
*/
|
|
11
|
+
function managedWithoutKeys(member, memberName, prop) {
|
|
12
|
+
if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
|
|
13
|
+
throw new Error('Expected association to have either an on-condition or foreign keys.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = managedWithoutKeys;
|