@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { makeMessageFunction, handleMessages } = require('../base/messages');
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
- // TODO: Replace with message.throwWithError():
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,
@@ -194,7 +194,7 @@ module.exports = {
194
194
  'OTHERS',
195
195
  'TIES',
196
196
  ],
197
- // HANA keywords, used to warn in 'toSql' renderer with dialect 'hana' or in 'toHana' renderer (both with 'plain' names only)
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
  }
@@ -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' }, // TODO: Remove if shared.js uses makeMessageFunction()
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-constraints': { severity: 'Warning' }, // more than 30 chars
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
  /**
@@ -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
- // TODO: remove property `model`
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.errors.map( m => m.toString() ).join('\n');
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 = model.options || {}, moduleName = null ) {
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 && options.messages || [];
331
-
361
+ let messages = options.messages || [];
362
+ let hasNewError = false;
332
363
  return {
333
- message, error, warning, info, debug,
334
- messages, throwWithError, callTransparently
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 throwWithError(msgs = messages) {
503
- if (!msgs || !msgs.length)
537
+ function throwWithAnyError() {
538
+ if (!messages || !messages.length)
504
539
  return;
505
- reclassifyMessagesForModule(msgs, severities, moduleName); // TODO: no, at the beginning of the module
540
+ reclassifyMessagesForModule( messages, severities, moduleName ); // TODO: no, at the beginning of the module
506
541
  const hasError = options.testMode ? hasNonDowngradableErrors : hasErrors;
507
- if (hasError( msgs, moduleName ))
508
- throw new CompilationError( msgs, options.attachValidNames && model );
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: cannot find value `nu` in this scope *
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
- * Return message string with location if present.
851
+ * Returns a message string with file- and semantic location if present
852
+ * in multiline form.
807
853
  *
808
854
  * Example:
809
- * Error: cannot find value `nu` in this scope
810
- * <source>.cds:3:11, at entity:“E”
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
- else if (queryProps.includes(csnPath[index + 1]) && !csnPath[index + 2]) {
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,