@sap/cds-compiler 2.4.4 → 2.10.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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- 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 +27 -9
- 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 +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- 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/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- 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
|
}
|
package/lib/base/location.js
CHANGED
|
@@ -14,9 +14,9 @@ const { copyPropIfExist } = require('../utils/objectUtils');
|
|
|
14
14
|
* @returns {CSN.Location}
|
|
15
15
|
*/
|
|
16
16
|
function combinedLocation( start, end ) {
|
|
17
|
-
if (!start)
|
|
17
|
+
if (!start || !start.location)
|
|
18
18
|
return end.location;
|
|
19
|
-
else if (!end)
|
|
19
|
+
else if (!end || !end.location)
|
|
20
20
|
return start.location;
|
|
21
21
|
const loc = {
|
|
22
22
|
file: start.location.file,
|
|
@@ -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,13 +130,21 @@ 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-
|
|
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
|
|
104
139
|
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
|
|
105
140
|
'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
|
|
106
141
|
};
|
|
107
142
|
|
|
108
143
|
// For messageIds, where no text has been provided via code (central def)
|
|
109
144
|
const centralMessageTexts = {
|
|
145
|
+
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
|
|
146
|
+
'anno-unexpected-ellipsis': 'Unexpected $(CODE) in annotation assignment',
|
|
147
|
+
'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
|
|
110
148
|
'syntax-csn-expected-object': 'Expected object for property $(PROP)',
|
|
111
149
|
'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
|
|
112
150
|
'syntax-csn-expected-natnum': 'Expected non-negative number for property $(PROP)',
|
|
@@ -187,6 +225,10 @@ const centralMessageTexts = {
|
|
|
187
225
|
'expected-target': 'An entity or an aspect is expected here',
|
|
188
226
|
'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
|
|
189
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
|
+
|
|
190
232
|
'ref-sloppy-type': 'A type or an element is expected here',
|
|
191
233
|
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
|
|
192
234
|
'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
|
|
@@ -200,9 +242,29 @@ const centralMessageTexts = {
|
|
|
200
242
|
},
|
|
201
243
|
|
|
202
244
|
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
|
|
203
|
-
'odata-spec-violation-attribute': 'EDM Property $(NAME) has no attribute $(PROP)',
|
|
204
|
-
'odata-spec-violation-property-name': 'EDM Property $(NAME) must not have the same name as the declaring $(TYPE)',
|
|
205
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)',
|
|
206
268
|
}
|
|
207
269
|
|
|
208
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
|
|
@@ -612,6 +648,10 @@ const paramsTransform = {
|
|
|
612
648
|
keyword: quote.word,
|
|
613
649
|
// more complex convenience:
|
|
614
650
|
names: transformManyWith( quoted ),
|
|
651
|
+
number: n => n,
|
|
652
|
+
line: l => l,
|
|
653
|
+
col: c => c,
|
|
654
|
+
literal: l => l,
|
|
615
655
|
art: transformArg,
|
|
616
656
|
service: transformArg,
|
|
617
657
|
sorted_arts: transformManyWith( transformArg, true ),
|
|
@@ -621,8 +661,13 @@ const paramsTransform = {
|
|
|
621
661
|
offending: tokenSymbol,
|
|
622
662
|
expecting: transformManyWith( tokenSymbol ),
|
|
623
663
|
// msg: m => m,
|
|
664
|
+
$reviewed: ignoreTextTransform,
|
|
624
665
|
};
|
|
625
666
|
|
|
667
|
+
function ignoreTextTransform() {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
|
|
626
671
|
function transformManyWith( t, sorted ) {
|
|
627
672
|
return function transformMany( many, r, args, texts ) {
|
|
628
673
|
const prop = ['none','one'][ many.length ];
|
|
@@ -766,7 +811,7 @@ function weakLocation( loc ) {
|
|
|
766
811
|
* Return message string with location if present in compact form (i.e. one line)
|
|
767
812
|
*
|
|
768
813
|
* Example:
|
|
769
|
-
* <source>.cds:3:11: Error:
|
|
814
|
+
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
|
|
770
815
|
*
|
|
771
816
|
* @param {CSN.Message} err
|
|
772
817
|
* @param {boolean} [normalizeFilename]
|
|
@@ -803,12 +848,15 @@ function messageHash(msg) {
|
|
|
803
848
|
}
|
|
804
849
|
|
|
805
850
|
/**
|
|
806
|
-
*
|
|
851
|
+
* Returns a message string with file- and semantic location if present
|
|
852
|
+
* in multiline form.
|
|
807
853
|
*
|
|
808
854
|
* Example:
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
855
|
+
* ```txt
|
|
856
|
+
* Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
|
|
857
|
+
* |
|
|
858
|
+
* <source>.cds:3:11, at entity:“E”
|
|
859
|
+
* ```
|
|
812
860
|
* @param {CSN.Message} err
|
|
813
861
|
* @param {object} [config = {}]
|
|
814
862
|
* @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
|
|
@@ -983,12 +1031,12 @@ function homeSortName( { home, messageId } ) {
|
|
|
983
1031
|
|
|
984
1032
|
/**
|
|
985
1033
|
* Removes duplicate messages from the given messages array without destroying
|
|
986
|
-
* references to the array.
|
|
1034
|
+
* references to the array, i.e. removes them in-place.
|
|
987
1035
|
*
|
|
988
|
-
* Does NOT keep the original order!
|
|
1036
|
+
* _Note_: Does NOT keep the original order!
|
|
989
1037
|
*
|
|
990
1038
|
* Two messages are the same if they have the same message hash. See messageHash().
|
|
991
|
-
* 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.
|
|
992
1040
|
* A message is more precise if it is contained in the other or if
|
|
993
1041
|
* the first does not have an endLine/endCol.
|
|
994
1042
|
*
|
|
@@ -1187,7 +1235,10 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1187
1235
|
if (!csnPath[index + 1]) {
|
|
1188
1236
|
result += select();
|
|
1189
1237
|
}
|
|
1190
|
-
|
|
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)) {
|
|
1191
1242
|
const clause = csnPath[index + 1];
|
|
1192
1243
|
result += select();
|
|
1193
1244
|
result += `/${ clause }`;
|
|
@@ -1199,6 +1250,11 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1199
1250
|
inColumn = true;
|
|
1200
1251
|
inQuery = false;
|
|
1201
1252
|
}
|
|
1253
|
+
else if (inElement) {
|
|
1254
|
+
result += select();
|
|
1255
|
+
elements.push(step);
|
|
1256
|
+
inQuery = false;
|
|
1257
|
+
}
|
|
1202
1258
|
}
|
|
1203
1259
|
else if ( inMixin ) {
|
|
1204
1260
|
if (step === 'on') {
|
|
@@ -1251,7 +1307,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1251
1307
|
if (currentThing.as)
|
|
1252
1308
|
result += `:${ _quoted(currentThing.as) }`;
|
|
1253
1309
|
else
|
|
1254
|
-
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('.')) }` : '';
|
|
1255
1311
|
|
|
1256
1312
|
break;
|
|
1257
1313
|
}
|
|
@@ -1310,7 +1366,7 @@ function constructSemanticLocationFromCsnPath(csnPath, model) {
|
|
|
1310
1366
|
let totalQueryDepth = 0;
|
|
1311
1367
|
|
|
1312
1368
|
let isFound = false;
|
|
1313
|
-
traverseQuery(rootQuery, null, (q, querySelect) => {
|
|
1369
|
+
traverseQuery(rootQuery, null, null, (q, querySelect) => {
|
|
1314
1370
|
if ( querySelect )
|
|
1315
1371
|
totalQueryDepth += 1;
|
|
1316
1372
|
if ( querySelect && !isFound)
|
|
@@ -1365,6 +1421,7 @@ module.exports = {
|
|
|
1365
1421
|
messageStringMultiline,
|
|
1366
1422
|
messageContext,
|
|
1367
1423
|
searchName,
|
|
1424
|
+
createMessageFunctions,
|
|
1368
1425
|
makeMessageFunction,
|
|
1369
1426
|
artName,
|
|
1370
1427
|
handleMessages,
|