@sap/cds-compiler 2.7.0 → 2.11.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 (87) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/bin/cdsc.js +42 -25
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +10 -0
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +17 -33
  7. package/lib/api/options.js +25 -13
  8. package/lib/api/validate.js +33 -9
  9. package/lib/backends.js +9 -8
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +26 -2
  13. package/lib/base/messages.js +25 -9
  14. package/lib/base/model.js +5 -3
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/onConditions.js +5 -0
  17. package/lib/checks/selectItems.js +4 -0
  18. package/lib/checks/types.js +26 -2
  19. package/lib/checks/unknownMagic.js +41 -0
  20. package/lib/checks/validator.js +7 -2
  21. package/lib/compiler/assert-consistency.js +18 -5
  22. package/lib/compiler/base.js +65 -0
  23. package/lib/compiler/builtins.js +30 -1
  24. package/lib/compiler/checks.js +5 -2
  25. package/lib/compiler/definer.js +145 -120
  26. package/lib/compiler/index.js +16 -4
  27. package/lib/compiler/propagator.js +5 -2
  28. package/lib/compiler/resolver.js +207 -47
  29. package/lib/compiler/shared.js +47 -200
  30. package/lib/compiler/utils.js +173 -0
  31. package/lib/edm/annotations/genericTranslation.js +183 -187
  32. package/lib/edm/csn2edm.js +94 -98
  33. package/lib/edm/edm.js +16 -20
  34. package/lib/edm/edmPreprocessor.js +302 -115
  35. package/lib/edm/edmUtils.js +31 -12
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +28 -1
  38. package/lib/gen/language.tokens +79 -69
  39. package/lib/gen/languageLexer.interp +28 -1
  40. package/lib/gen/languageLexer.js +879 -805
  41. package/lib/gen/languageLexer.tokens +71 -62
  42. package/lib/gen/languageParser.js +5308 -4308
  43. package/lib/json/from-csn.js +59 -30
  44. package/lib/json/to-csn.js +354 -105
  45. package/lib/language/antlrParser.js +11 -0
  46. package/lib/language/errorStrategy.js +1 -0
  47. package/lib/language/genericAntlrParser.js +81 -14
  48. package/lib/language/language.g4 +163 -31
  49. package/lib/main.d.ts +136 -17
  50. package/lib/main.js +7 -1
  51. package/lib/model/api.js +78 -0
  52. package/lib/model/csnRefs.js +115 -32
  53. package/lib/model/csnUtils.js +71 -33
  54. package/lib/model/enrichCsn.js +36 -9
  55. package/lib/model/revealInternalProperties.js +20 -4
  56. package/lib/modelCompare/compare.js +2 -1
  57. package/lib/optionProcessor.js +33 -16
  58. package/lib/render/.eslintrc.json +3 -1
  59. package/lib/render/DuplicateChecker.js +1 -1
  60. package/lib/render/toCdl.js +60 -17
  61. package/lib/render/toHdbcds.js +122 -74
  62. package/lib/render/toSql.js +57 -32
  63. package/lib/render/utils/common.js +6 -10
  64. package/lib/sql-identifier.js +6 -1
  65. package/lib/transform/db/constraints.js +273 -119
  66. package/lib/transform/db/draft.js +9 -6
  67. package/lib/transform/db/expansion.js +19 -7
  68. package/lib/transform/db/flattening.js +31 -7
  69. package/lib/transform/db/transformExists.js +344 -66
  70. package/lib/transform/db/views.js +438 -0
  71. package/lib/transform/forHanaNew.js +65 -436
  72. package/lib/transform/forOdataNew.js +21 -10
  73. package/lib/transform/localized.js +2 -0
  74. package/lib/transform/odata/attachPath.js +19 -4
  75. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  76. package/lib/transform/odata/referenceFlattener.js +44 -38
  77. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  78. package/lib/transform/odata/structuralPath.js +72 -0
  79. package/lib/transform/odata/structureFlattener.js +13 -10
  80. package/lib/transform/odata/typesExposure.js +22 -12
  81. package/lib/transform/transformUtilsNew.js +55 -9
  82. package/lib/transform/translateAssocsToJoins.js +11 -17
  83. package/lib/transform/universalCsnEnricher.js +67 -0
  84. package/lib/utils/file.js +5 -3
  85. package/lib/utils/term.js +65 -42
  86. package/lib/utils/timetrace.js +48 -26
  87. package/package.json +1 -1
@@ -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 = {
@@ -70,6 +70,14 @@ const validators = {
70
70
  return val === null ? val : `type ${ typeof val }`;
71
71
  },
72
72
  },
73
+ // TODO: Maybe do a deep validation of the whole object with leafs?
74
+ variableReplacements: {
75
+ validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
76
+ expected: () => 'type object',
77
+ found: (val) => {
78
+ return val === null ? val : `type ${ typeof val }`;
79
+ },
80
+ },
73
81
  messages: {
74
82
  validate: val => Array.isArray(val),
75
83
  expected: () => 'type array',
@@ -89,14 +97,32 @@ const validators = {
89
97
  expected: () => 'type array of string',
90
98
  found: val => `type ${ typeof val }`,
91
99
  },
100
+ defaultBinaryLength: {
101
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
102
+
103
+ expected: () => 'Integer literal',
104
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
105
+ },
92
106
  defaultStringLength: {
93
- validate: val => Number.isInteger(val),
107
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
108
+
94
109
  expected: () => 'Integer literal',
110
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
111
+ },
112
+ csnFlavor: {
113
+ validate: val => typeof val === 'string',
114
+ expected: () => 'type string',
95
115
  found: val => `type ${ typeof val }`,
96
116
  },
97
117
  dictionaryPrototype: {
98
118
  validate: () => true,
99
119
  },
120
+ assertIntegrity: {
121
+ validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
122
+ expected: () => 'a boolean or a string with value \'individual\'',
123
+ found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
124
+ },
125
+ assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
100
126
  };
101
127
 
102
128
  const allCombinationValidators = {
@@ -122,16 +148,17 @@ const allCombinationValidators = {
122
148
  * Use a custom validator or "default" custom validator, fallback to Boolean validator.
123
149
  *
124
150
  * @param {object} options Flat options object to validate
151
+ * @param {string} moduleName The called module, e.g. 'for.odata', 'to.hdi'. Needed to initialize the message functions
125
152
  * @param {object} [customValidators] Map of custom validators to use
126
153
  * @param {string[]} [combinationValidators] Validate option combinations
127
154
  * @returns {void}
128
155
  * @throws {CompilationError} Throws in case of invalid option usage
129
156
  */
130
- function validate(options, customValidators = {}, combinationValidators = []) {
157
+ function validate(options, moduleName, customValidators = {}, combinationValidators = []) {
131
158
  // TODO: issuing messages in this function looks very strange...
132
159
  {
133
160
  const messageCollector = { messages: [] };
134
- const { error, throwWithError } = makeMessageFunction(null, messageCollector);
161
+ const { error, throwWithError } = makeMessageFunction(null, messageCollector, moduleName);
135
162
 
136
163
  for (const optionName of Object.keys(options)) {
137
164
  const optionValue = options[optionName];
@@ -143,7 +170,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
143
170
  throwWithError();
144
171
  }
145
172
 
146
- const message = makeMessageFunction(null, options);
173
+ const message = makeMessageFunction(null, options, moduleName);
147
174
 
148
175
  for (const combinationValidatorName of combinationValidators.concat([ 'beta-no-test' ])) {
149
176
  const combinationValidator = allCombinationValidators[combinationValidatorName];
@@ -151,10 +178,7 @@ function validate(options, customValidators = {}, combinationValidators = []) {
151
178
  message[combinationValidator.severity]('invalid-option-combination', null, {}, combinationValidator.getMessage(options));
152
179
  }
153
180
 
154
- // TODO: Replace with message.throwWithError():
155
- // But be aware that it only throws with non-configurable errors and that this
156
- // will lead to issues in test3. See #6037
157
- handleMessages(undefined, options);
181
+ message.throwWithError();
158
182
  }
159
183
  /* eslint-enable jsdoc/no-undefined-types */
160
184
 
package/lib/backends.js CHANGED
@@ -14,7 +14,7 @@ const { csn2edm, csn2edmAll } = require('./edm/csn2edm');
14
14
  const { mergeOptions } = require('./model/csnUtils');
15
15
  const { isBetaEnabled } = require('./base/model');
16
16
  const { optionProcessor } = require('./optionProcessor')
17
- const timetrace = require('./utils/timetrace');
17
+ const { timetrace } = require('./utils/timetrace');
18
18
  const { makeMessageFunction } = require('./base/messages');
19
19
  const { forEachDefinition } = require('./model/csnUtils');
20
20
 
@@ -92,9 +92,9 @@ function toOdataWithCsn(csn, options) {
92
92
  // Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
93
93
  // using 'options'
94
94
  function preparedCsnToEdmx(csn, service, options) {
95
- let edmx = csn2edm(csn, service, options).toXML('all');
95
+ const e = csn2edm(csn, service, options)
96
96
  return {
97
- edmx,
97
+ edmx: (e ? e.toXML('all') : undefined)
98
98
  };
99
99
  }
100
100
 
@@ -115,9 +115,9 @@ function preparedCsnToEdmxAll(csn, options) {
115
115
  function preparedCsnToEdm(csn, service, options) {
116
116
  // Merge options; override OData version as edm json is always v4
117
117
  options = mergeOptions(options, { toOdata : { version : 'v4' }});
118
- const edmj = csn2edm(csn, service, options).toJSON();
118
+ const e = csn2edm(csn, service, options);
119
119
  return {
120
- edmj,
120
+ edmj: (e ? e.toJSON() : undefined)
121
121
  };
122
122
  }
123
123
 
@@ -405,9 +405,6 @@ function toRenameWithCsn(csn, options) {
405
405
 
406
406
  function alterConstraintsWithCsn(csn, options) {
407
407
  const { error } = makeMessageFunction(csn, options, 'manageConstraints');
408
- // Requires beta mode
409
- if (!isBetaEnabled(options, 'foreignKeyConstraints'))
410
- error(null, null, 'ALTER TABLE statements for adding/modifying referential constraints are only available in beta mode');
411
408
 
412
409
  const {
413
410
  drop, alter, names, src, violations
@@ -420,6 +417,10 @@ function alterConstraintsWithCsn(csn, options) {
420
417
  dialect: 'hana',
421
418
  names: names || 'plain'
422
419
  }
420
+
421
+ // Of course we want the database constraints
422
+ options.assertIntegrityType = 'DB';
423
+
423
424
  const transformedOptions = transformSQLOptions(csn, options);
424
425
  const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
425
426
  const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions, 'to.sql');
@@ -28,6 +28,7 @@ function dictAdd( dict, name, entry, duplicateCallback ) {
28
28
  }
29
29
 
30
30
  function dictForEach( dict, callback ) {
31
+ // TODO: probably define an extra dictForEachArray()
31
32
  for (const name in dict) {
32
33
  const entry = dict[name];
33
34
  if (Array.isArray(entry)) {
@@ -35,7 +36,7 @@ function dictForEach( dict, callback ) {
35
36
  }
36
37
  else {
37
38
  callback( entry );
38
- if (Array.isArray(entry.$duplicates))
39
+ if (Array.isArray( entry.$duplicates ))
39
40
  entry.$duplicates.forEach( callback );
40
41
  }
41
42
  }
@@ -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
  }
@@ -118,6 +118,7 @@ const centralMessages = {
118
118
  'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
119
119
  'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
120
120
  'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
121
+ 'syntax-csn-expected-length': { severity: 'Error' },
121
122
  'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
122
123
  'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
123
124
  'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
@@ -132,6 +133,11 @@ const centralMessages = {
132
133
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
133
134
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
134
135
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
136
+ 'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
137
+ 'odata-spec-violation-no-key': { severity: 'Warning' },
138
+ 'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
139
+ 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
140
+ 'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
135
141
  'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
136
142
  'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
137
143
  };
@@ -156,6 +162,11 @@ const centralMessageTexts = {
156
162
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
157
163
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
158
164
  },
165
+ 'syntax-csn-expected-length': {
166
+ std: 'Expected array in $(PROP) to have at least $(N) items',
167
+ one: 'Expected array in $(PROP) to have at least one item',
168
+ suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
169
+ },
159
170
  'ref-undefined-def': {
160
171
  std: 'Artifact $(ART) has not been found',
161
172
  // TODO: proposal 'No definition of $(NAME) found',
@@ -199,13 +210,13 @@ const centralMessageTexts = {
199
210
  'duplicate-definition': {
200
211
  std: 'Duplicate definition of $(NAME)',
201
212
  absolute: 'Duplicate definition of artifact $(NAME)',
202
- namespace: 'Other definition blocks $(NAME) for namespace name',
213
+ annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
203
214
  element: 'Duplicate definition of element $(NAME)',
204
215
  enum: 'Duplicate definition of enum $(NAME)',
205
216
  key: 'Duplicate definition of key $(NAME)',
206
217
  action: 'Duplicate definition of action or function $(NAME)',
207
218
  param: 'Duplicate definition of parameter $(NAME)',
208
- $tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
219
+ alias: 'Duplicate definition of table alias or mixin $(NAME)',
209
220
  },
210
221
 
211
222
  // TODO: rename to ref-expected-XYZ
@@ -246,6 +257,19 @@ const centralMessageTexts = {
246
257
  'odata-spec-violation-assoc': 'Unexpected association in structured type for $(API)',
247
258
  'odata-spec-violation-constraints': 'Partial referential constraints produced for $(API)',
248
259
  // version independent messages
260
+ 'odata-spec-violation-key-array': {
261
+ std: 'Unexpected array type for element $(NAME)',
262
+ scalar: 'Unexpected array type'
263
+ },
264
+ 'odata-spec-violation-key-null': {
265
+ std: 'Expected key element $(NAME) to be not nullable', // structured
266
+ scalar: 'Expected key element to be not nullable' // flat
267
+ },
268
+ 'odata-spec-violation-key-type': {
269
+ std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
270
+ scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
271
+ },
272
+ 'odata-spec-violation-no-key': 'Expected entity to have a primary key',
249
273
  'odata-spec-violation-type': 'Expected element to have a type',
250
274
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
251
275
  'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
@@ -4,7 +4,7 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const term = require('../utils/term');
7
+ const { term } = require('../utils/term');
8
8
  const { locationString } = require('./location');
9
9
  const { isDeprecatedEnabled } = require('./model');
10
10
  const { centralMessages, centralMessageTexts } = require('./message-registry');
@@ -15,6 +15,8 @@ const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
+ // term instance for messages
19
+ const colorTerm = term();
18
20
 
19
21
  // Functions ensuring message consistency during runtime with --test-mode
20
22
 
@@ -649,6 +651,8 @@ const paramsTransform = {
649
651
  // more complex convenience:
650
652
  names: transformManyWith( quoted ),
651
653
  number: n => n,
654
+ line: l => l,
655
+ col: c => c,
652
656
  literal: l => l,
653
657
  art: transformArg,
654
658
  service: transformArg,
@@ -851,19 +855,26 @@ function messageHash(msg) {
851
855
  *
852
856
  * Example:
853
857
  * ```txt
854
- * Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
858
+ * Error[message-id]: Can't find type `nu` in this scope
855
859
  * |
856
- * <source>.cds:3:11, at entity:“E”
860
+ * <source>.cds:3:11, at entity:“E”/element:“e
857
861
  * ```
862
+ *
858
863
  * @param {CSN.Message} err
859
864
  * @param {object} [config = {}]
860
865
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
861
866
  * @param {boolean} [config.noMessageId]
862
867
  * @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
863
- * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
868
+ * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
869
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
870
+ * coloring will be used. If 'auto', we will decide based on certain factors such
871
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
872
+ * unset.
864
873
  * @returns {string}
865
874
  */
866
875
  function messageStringMultiline( err, config = {} ) {
876
+ colorTerm.changeColorMode(config ? config.color : 'auto');
877
+
867
878
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
868
879
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
869
880
  const home = !err.home ? '' : ('at ' + err.home);
@@ -876,7 +887,7 @@ function messageStringMultiline( err, config = {} ) {
876
887
  location += ', '
877
888
  }
878
889
  else if (!home)
879
- return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
890
+ return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
880
891
 
881
892
  let lineSpacer = '';
882
893
  if (config.withLineSpacer) {
@@ -884,8 +895,7 @@ function messageStringMultiline( err, config = {} ) {
884
895
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
885
896
  }
886
897
 
887
- // TODO: use ':' before text
888
- return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
898
+ return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
889
899
  }
890
900
 
891
901
  /**
@@ -899,9 +909,15 @@ function messageStringMultiline( err, config = {} ) {
899
909
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
900
910
  * from `lib/utils/file.js`
901
911
  * @param {CSN.Message} err Error object containing all details like line, message, etc.
912
+ * @param {object} [config = {}]
913
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
914
+ * coloring will be used. If 'auto', we will decide based on certain factors such
915
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
916
+ * unset.
902
917
  * @returns {string}
903
918
  */
904
- function messageContext(sourceLines, err) {
919
+ function messageContext(sourceLines, err, config) {
920
+ colorTerm.changeColorMode(config ? config.color : 'auto');
905
921
  const MAX_COL_LENGTH = 100;
906
922
 
907
923
  const loc = err.$location;
@@ -953,7 +969,7 @@ function messageContext(sourceLines, err) {
953
969
  // Indicate that the error is further to the right.
954
970
  if (endColumn === MAX_COL_LENGTH)
955
971
  highlighter = highlighter.replace(' ^', '..^');
956
- msg += indent + '| ' + term.asSeverity(severity, highlighter);
972
+ msg += indent + '| ' + colorTerm.severity(severity, highlighter);
957
973
 
958
974
  } else if (maxLine !== endLine) {
959
975
  // error spans more lines which we don't print
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
- foreignKeyConstraints: true,
23
22
  toRename: true,
24
23
  addTextsLanguageAssoc: true,
25
24
  assocsWithParams: true,
@@ -54,15 +53,18 @@ function isBetaEnabled( options, feature ) {
54
53
  /**
55
54
  * Test for deprecated feature, stored in option `deprecated`.
56
55
  * With that, the value of `deprecated` is a dictionary of feature=>Boolean.
56
+ * If no `feature` is provided, checks if any deprecated option is set.
57
57
  *
58
58
  * Please do not move this function to the "option processor" code.
59
59
  *
60
60
  * @param {object} options Options
61
- * @param {string} feature Feature to check for
61
+ * @param {string} [feature] Feature to check for
62
62
  * @returns {boolean}
63
63
  */
64
- function isDeprecatedEnabled( options, feature ) {
64
+ function isDeprecatedEnabled( options, feature = null ) {
65
65
  const { deprecated } = options;
66
+ if(!feature)
67
+ return !!deprecated;
66
68
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
67
69
  }
68
70
 
@@ -30,7 +30,11 @@ function createOptionProcessor() {
30
30
  optionClashes: [],
31
31
  option,
32
32
  command,
33
- positionalArgument,
33
+ positionalArgument: (argumentDefinition) => {
34
+ // Default positional arguments; may be overwritten by commands.
35
+ _positionalArguments(argumentDefinition);
36
+ return optionProcessor;
37
+ },
34
38
  help,
35
39
  processCmdLine,
36
40
  verifyOptions,
@@ -66,7 +70,12 @@ function createOptionProcessor() {
66
70
  /** @type {object} */
67
71
  const command = {
68
72
  options: {},
73
+ positionalArguments: [],
69
74
  option,
75
+ positionalArgument: (argumentDefinition) => {
76
+ _positionalArguments(argumentDefinition, command.positionalArguments);
77
+ return command;
78
+ },
70
79
  help,
71
80
  ..._parseCommandString(cmdString)
72
81
  };
@@ -96,28 +105,34 @@ function createOptionProcessor() {
96
105
  }
97
106
 
98
107
  /**
99
- * Adds positional arguments to the command line processor. Instructs the processor
100
- * to either require N positional arguments or a dynamic number (but at least one)
101
- * @param {string} positionalArgumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
108
+ * Set the positional arguments to the command line processor. Instructs the processor
109
+ * to either require N positional arguments or a dynamic number (but at least one).
110
+ * Note that you can only call this function once. Only the last invocation sets
111
+ * the positional arguments.
112
+ *
113
+ * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
114
+ * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
102
115
  */
103
- function positionalArgument(positionalArgumentDefinition) {
104
- if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
116
+ function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
117
+ if (argList.find((arg) => arg.isDynamic)) {
105
118
  throw new Error(`Can't add positional arguments after a dynamic one`);
106
119
  }
107
120
 
108
- const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
109
- const args = positionalArgumentDefinition.split(' ');
121
+ const registeredNames = argList.map((arg) => arg.name);
122
+ const args = argumentDefinition.split(' ');
110
123
 
111
124
  for (const arg of args) {
112
- const argName = arg.replace('<', '').replace('>', '').replace('...', '');
125
+ // Remove braces, dots and camelify.
126
+ const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
127
+
113
128
  if (registeredNames.includes(argName)) {
114
- throw new Error(`Duplicate positional argument ${arg}`);
129
+ throw new Error(`Duplicate positional argument: ${arg}`);
115
130
  }
116
131
  if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
117
132
  throw new Error(`Unknown positional argument syntax: ${arg}`)
118
133
  }
119
134
 
120
- optionProcessor.positionalArguments.push({
135
+ argList.push({
121
136
  name: argName,
122
137
  isDynamic: isDynamicPositionalArgument(arg),
123
138
  required: true
@@ -125,7 +140,6 @@ function createOptionProcessor() {
125
140
 
126
141
  registeredNames.push(argName);
127
142
  }
128
- return optionProcessor;
129
143
  }
130
144
 
131
145
  /**
@@ -390,21 +404,38 @@ function createOptionProcessor() {
390
404
  }
391
405
 
392
406
  // Complain about first missing positional arguments
393
- const missingArg = optionProcessor.positionalArguments.find((arg) => arg.required && !result.args[arg.name]);
407
+ const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
394
408
  if (missingArg) {
395
- result.errors.push(`Missing positional argument: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
409
+ const forCommand = result.command ? ` for '${ result.command }'` : '';
410
+ result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
396
411
  }
397
412
 
398
413
  return result;
399
414
 
415
+ /**
416
+ * Specific commands may have custom positional arguments.
417
+ * If the current one does, use it instead of the defaults.
418
+ *
419
+ * @returns {object[]} Array of positional argument configurations.
420
+ */
421
+ function getCurrentPositionArguments() {
422
+ const cmd = optionProcessor.commands[result.command];
423
+ const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
+ return args;
425
+ }
426
+
400
427
  function processPositionalArgument(argumentValue) {
401
- if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
428
+ const argList = getCurrentPositionArguments();
429
+ if ( result.args.length === 0 && argList.length === 0 )
402
430
  return;
403
- const inBounds = result.args.length < optionProcessor.positionalArguments.length;
404
- const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;
405
- const nextUnsetArgument = optionProcessor.positionalArguments[lastIndex];
431
+ const inBounds = result.args.length < argList.length;
432
+ const lastIndex = inBounds ? result.args.length : argList.length - 1;
433
+ const nextUnsetArgument = argList[lastIndex];
406
434
  if (!inBounds && !nextUnsetArgument.isDynamic) {
407
- result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
435
+ if (result.command)
436
+ result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
437
+ else
438
+ result.errors.push(`Too many arguments. Expected ${argList.length}`);
408
439
  return;
409
440
  }
410
441
  result.args.length += 1;
@@ -486,7 +517,10 @@ function createOptionProcessor() {
486
517
  }
487
518
 
488
519
  if(options) {
489
- ['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
520
+ [
521
+ 'defaultBinaryLength', 'defaultStringLength',
522
+ /*'length', 'precision', 'scale'*/
523
+ ].forEach(facet => {
490
524
  if(options[facet] && isNaN(options[facet])) {
491
525
  result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
492
526
  } else {
@@ -577,12 +611,12 @@ function isLongOption(opt) {
577
611
 
578
612
  // Check if 'opt' looks like a "<foobar>" parameter
579
613
  function isParam(opt) {
580
- return /^<[a-zA-Z]+>$/.test(opt);
614
+ return /^<[a-zA-Z-]+>$/.test(opt);
581
615
  }
582
616
 
583
617
  // Check if 'arg' looks like "<foobar...>"
584
618
  function isDynamicPositionalArgument(arg) {
585
- return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
619
+ return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
586
620
  }
587
621
 
588
622
  module.exports = {
@@ -59,6 +59,11 @@ function otherSideIsValidDollarSelf(on, startIndex) {
59
59
  */
60
60
  function validateOnCondition(member, memberName, property, path) {
61
61
  if (member && member.on) {
62
+ // complain about nullability constraint on managed composition
63
+ if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
64
+ this.warning(null, path.concat([ 'on' ]),
65
+ 'Unexpected nullability constraint defined on managed composition');
66
+ }
62
67
  for (let i = 0; i < member.on.length; i++) {
63
68
  if (member.on[i].ref) {
64
69
  const { ref } = member.on[i];
@@ -23,6 +23,10 @@ function validateSelectItems(query) {
23
23
  'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
24
24
  }
25
25
  }
26
+ else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
27
+ this.error(null, selectItem.$path,
28
+ 'Window functions are not supported by SAP HANA CDS');
29
+ }
26
30
  });
27
31
  // .call() with 'this' to ensure we have access to the options
28
32
  rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
@@ -1,9 +1,28 @@
1
1
  'use strict';
2
2
 
3
- const { getUtils, isBuiltinType } = require('../model/csnUtils');
3
+ const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
7
+ /**
8
+ * Scale must not be 'variable' or 'floating'
9
+ *
10
+ * scale property is always propagated
11
+ *
12
+ * @param {CSN.Element} member the element to be checked
13
+ * @param {string} memberName the elements name
14
+ * @param {string} prop which kind of member are we looking at -> only prop "elements"
15
+ * @param {CSN.Path} path the path to the member
16
+ */
17
+ function checkDecimalScale(member, memberName, prop, path) {
18
+ if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
19
+ // skip is already filtered in validator, here for completeness
20
+ hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
21
+ return;
22
+ if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
23
+ this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
24
+ }
25
+
7
26
  /**
8
27
  * View parameter for hana must be of scalar type
9
28
  *
@@ -165,4 +184,9 @@ function hasArtifactTypeInformation(artifact) {
165
184
  artifact.type; // => `type A : [type of] Integer`
166
185
  }
167
186
 
168
- module.exports = { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType, checkTypeIsScalar };
187
+ module.exports = {
188
+ checkTypeDefinitionHasType,
189
+ checkElementTypeDefinitionHasType,
190
+ checkTypeIsScalar,
191
+ checkDecimalScale,
192
+ };