@sap/cds-compiler 2.10.4 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -15,6 +15,7 @@ const publicOptionsNewAPI = [
15
15
  'severities',
16
16
  'messages',
17
17
  'withLocations',
18
+ 'defaultBinaryLength',
18
19
  'defaultStringLength',
19
20
  'csnFlavor',
20
21
  // DB
@@ -23,7 +24,8 @@ const publicOptionsNewAPI = [
23
24
  'sqlChangeMode',
24
25
  'allowCsnDowngrade',
25
26
  'joinfk',
26
- 'magicVars',
27
+ 'magicVars', // deprecated
28
+ 'variableReplacements',
27
29
  // ODATA
28
30
  'odataVersion',
29
31
  'odataFormat',
@@ -47,9 +49,11 @@ const privateOptions = [
47
49
  'traceParserAmb',
48
50
  'testMode',
49
51
  'testSortCsn',
50
- 'constraintsNotEnforced',
51
- 'constraintsNotValidated',
52
- 'skipDbConstraints',
52
+ 'constraintsAsAlter',
53
+ 'integrityNotEnforced',
54
+ 'integrityNotValidated',
55
+ 'assertIntegrity',
56
+ 'assertIntegrityType',
53
57
  'noRecompile',
54
58
  'internalMsg',
55
59
  'disableHanaComments', // in case of issues with hana comment rendering
@@ -80,9 +84,9 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
80
84
  for (const name of overallOptions) {
81
85
  // Ensure that arrays are not passed as a reference!
82
86
  // This caused issues with the way messages are handled in processMessages
83
- if (Array.isArray(input[name]) && inputOptionNames.indexOf(name) !== -1)
87
+ if (Array.isArray(input[name]) && inputOptionNames.includes(name))
84
88
  options[name] = [ ...input[name] ];
85
- else if (inputOptionNames.indexOf(name) !== -1)
89
+ else if (inputOptionNames.includes(name))
86
90
  options[name] = input[name];
87
91
  }
88
92
 
@@ -109,6 +113,10 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
109
113
  mapToOldNames(optionName, optionValue);
110
114
  }
111
115
 
116
+ // Convenience for $user -> $user.id replacement
117
+ if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')
118
+ options.variableReplacements.$user = { id: options.variableReplacements.$user };
119
+
112
120
  /**
113
121
  * Map a new-style option to it's old format
114
122
  *
@@ -130,6 +138,7 @@ function translateOptions(input = {}, defaults = {}, hardRequire = {},
130
138
  case 'sqlMapping':
131
139
  options.names = optionValue;
132
140
  break;
141
+ // No need to remap variableReplacements here - we use the new mechanism with that directly
133
142
  case 'magicVars':
134
143
  if (optionValue.user)
135
144
  options.user = optionValue.user;
@@ -149,7 +158,7 @@ module.exports = {
149
158
  sql: (options) => {
150
159
  const hardOptions = { src: 'sql' };
151
160
  const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
152
- const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
161
+ const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming', 'constraints-as-alter-sqlite' ], 'to.sql');
153
162
 
154
163
  const result = Object.assign({}, processed);
155
164
  result.toSql = Object.assign({}, processed);
@@ -157,7 +166,7 @@ module.exports = {
157
166
  return result;
158
167
  },
159
168
  hdi: (options) => {
160
- const hardOptions = { src: 'hdi' };
169
+ const hardOptions = { src: 'hdi', constraintsAsAlter: false };
161
170
  const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
162
171
  const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
163
172
 
@@ -25,13 +25,14 @@ const booleanValidator = {
25
25
  /**
26
26
  * Generate a Validator that validates that the
27
27
  * input is a string and one of the available options.
28
+ * The validation of the option values is case-insensitive.
28
29
  *
29
30
  * @param {any} availableValues Available values
30
31
  * @returns {Validator} Return a validator for a string in an expected range
31
32
  */
32
33
  function generateStringValidator(availableValues) {
33
34
  return {
34
- validate: val => typeof val === 'string' && availableValues.indexOf(val) !== -1,
35
+ validate: val => typeof val === 'string' && availableValues.some( av => av.toLowerCase() === val.toLowerCase() ),
35
36
  expected: (val) => {
36
37
  return typeof val !== 'string' ? 'type string' : availableValues.join(', ');
37
38
  },
@@ -70,6 +71,14 @@ const validators = {
70
71
  return val === null ? val : `type ${ typeof val }`;
71
72
  },
72
73
  },
74
+ // TODO: Maybe do a deep validation of the whole object with leafs?
75
+ variableReplacements: {
76
+ validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
77
+ expected: () => 'type object',
78
+ found: (val) => {
79
+ return val === null ? val : `type ${ typeof val }`;
80
+ },
81
+ },
73
82
  messages: {
74
83
  validate: val => Array.isArray(val),
75
84
  expected: () => 'type array',
@@ -89,10 +98,17 @@ const validators = {
89
98
  expected: () => 'type array of string',
90
99
  found: val => `type ${ typeof val }`,
91
100
  },
101
+ defaultBinaryLength: {
102
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
103
+
104
+ expected: () => 'Integer literal',
105
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
106
+ },
92
107
  defaultStringLength: {
93
- validate: val => Number.isInteger(val),
108
+ validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
109
+
94
110
  expected: () => 'Integer literal',
95
- found: val => `type ${ typeof val }`,
111
+ found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
96
112
  },
97
113
  csnFlavor: {
98
114
  validate: val => typeof val === 'string',
@@ -102,6 +118,12 @@ const validators = {
102
118
  dictionaryPrototype: {
103
119
  validate: () => true,
104
120
  },
121
+ assertIntegrity: {
122
+ validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
123
+ expected: () => 'a boolean or a string with value \'individual\'',
124
+ found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
125
+ },
126
+ assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
105
127
  };
106
128
 
107
129
  const allCombinationValidators = {
@@ -120,6 +142,11 @@ const allCombinationValidators = {
120
142
  severity: 'warning',
121
143
  getMessage: () => 'Option "beta" was used. This option should not be used in productive scenarios!',
122
144
  },
145
+ 'constraints-as-alter-sqlite': {
146
+ validate: options => options.constraintsAsAlter && options.sqlDialect && options.sqlDialect === 'sqlite',
147
+ severity: 'warning',
148
+ getMessage: options => `Option 'constraintsAsAlter' is ignored for sqlDialect '${ options.sqlDialect }'`,
149
+ },
123
150
  };
124
151
  /* eslint-disable jsdoc/no-undefined-types */
125
152
  /**
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
 
@@ -303,9 +303,9 @@ function transformSQLOptions(model, options) {
303
303
  }
304
304
  // move the locale option(if provided) under user.locale
305
305
  if (options.locale) {
306
- options.user
306
+ options.user = options.user
307
307
  ? Object.assign(options.user, { locale: options.locale })
308
- : options.user = { locale: options.locale };
308
+ : { locale: options.locale };
309
309
  delete options.locale;
310
310
  }
311
311
  }
@@ -395,19 +395,14 @@ function toRenameWithCsn(csn, options) {
395
395
  });
396
396
 
397
397
  // Assemble result
398
- let result = {
398
+ return {
399
399
  rename : toRenameDdl(forHanaCsn, options),
400
400
  options
401
401
  };
402
-
403
- return result;
404
402
  }
405
403
 
406
404
  function alterConstraintsWithCsn(csn, options) {
407
405
  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
406
 
412
407
  const {
413
408
  drop, alter, names, src, violations
@@ -420,6 +415,10 @@ function alterConstraintsWithCsn(csn, options) {
420
415
  dialect: 'hana',
421
416
  names: names || 'plain'
422
417
  }
418
+
419
+ // Of course we want the database constraints
420
+ options.assertIntegrityType = 'DB';
421
+
423
422
  const transformedOptions = transformSQLOptions(csn, options);
424
423
  const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
425
424
  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
  }
@@ -11,7 +11,7 @@ module.exports = {
11
11
  'DISTINCT',
12
12
  'EXISTS',
13
13
  'EXTRACT',
14
- 'FALSE',
14
+ 'FALSE', // boolean
15
15
  'FROM',
16
16
  'IN',
17
17
  'KEY',
@@ -22,8 +22,9 @@ module.exports = {
22
22
  'ON',
23
23
  'SELECT',
24
24
  'SOME',
25
+ 'WHEN',
25
26
  'TRIM',
26
- 'TRUE',
27
+ 'TRUE', // boolean
27
28
  'WHERE',
28
29
  'WITH',
29
30
  ],
@@ -42,6 +42,10 @@ const centralMessages = {
42
42
  'anno-definition': { severity: 'Warning' },
43
43
  'anno-duplicate': { severity: 'Error', configurableFor: true }, // does not hurt us
44
44
  'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
45
+ 'anno-invalid-sql-element': { severity: 'Error'}, // @sql.prepend/append
46
+ 'anno-invalid-sql-struct': { severity: 'Error'}, // @sql.prepend/append
47
+ 'anno-invalid-sql-view': { severity: 'Error' }, // @sql.prepend/append
48
+ 'anno-invalid-sql-view-element': { severity: 'Error'}, // @sql.prepend/append
45
49
  'anno-undefined-action': { severity: 'Info' },
46
50
  'anno-undefined-art': { severity: 'Info' }, // for annotate statement (for CDL path root)
47
51
  'anno-undefined-def': { severity: 'Info' }, // for annotate statement (for CSN or CDL path cont)
@@ -60,7 +64,6 @@ const centralMessages = {
60
64
 
61
65
  'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
62
66
 
63
- 'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
64
67
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
65
68
 
66
69
  // Structured types were warned about but made CSN un-recompilable.
@@ -69,7 +72,7 @@ const centralMessages = {
69
72
  // TODO: rename to ref-expected-XYZ
70
73
  'expected-type': { severity: 'Error' },
71
74
  'ref-sloppy-type': { severity: 'Error' },
72
- 'ref-invalid-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
75
+ 'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
73
76
  'expected-actionparam-type': { severity: 'Error' },
74
77
  'ref-sloppy-actionparam-type': { severity: 'Error' },
75
78
  'expected-event-type': { severity: 'Error' },
@@ -89,6 +92,7 @@ const centralMessages = {
89
92
  'query-undefined-element': { severity: 'Error' },
90
93
  'query-unexpected-assoc-hdbcds': { severity: 'Error' },
91
94
  'query-unexpected-structure-hdbcds': { severity: 'Error' },
95
+ 'query-ignoring-param-nullability': { severity: 'Info' },
92
96
 
93
97
  'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
94
98
  'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
@@ -99,6 +103,7 @@ const centralMessages = {
99
103
  'ref-undefined-def': { severity: 'Error' },
100
104
  'ref-undefined-var': { severity: 'Error' },
101
105
  'ref-undefined-element': { severity: 'Error' },
106
+ 'ref-unknown-var': { severity: 'Info' },
102
107
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
103
108
  'ref-undefined-param': { severity: 'Error' },
104
109
  'ref-rejected-on': { severity: 'Error' },
@@ -118,6 +123,7 @@ const centralMessages = {
118
123
  'syntax-anno-after-params': { severity: 'Error', configurableFor: true }, // does not hurt
119
124
  'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
120
125
  'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
126
+ 'syntax-csn-expected-length': { severity: 'Error' },
121
127
  'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
122
128
  'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
123
129
  'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed
@@ -125,14 +131,22 @@ const centralMessages = {
125
131
  'syntax-fragile-alias': { severity: 'Error', configurableFor: true },
126
132
  'syntax-fragile-ident': { severity: 'Error', configurableFor: true },
127
133
 
134
+ 'syntax-invalid-text-block' : { severity: 'Error' },
135
+ 'syntax-unknown-escape': { severity: 'Error', configurableFor: true },
136
+ 'syntax-invalid-escape': { severity: 'Error' },
137
+ 'syntax-missing-escape': { severity: 'Error' },
138
+
128
139
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
129
140
 
141
+ 'def-missing-element': { severity: 'Error' },
142
+
130
143
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
131
144
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
132
145
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
133
146
  'odata-spec-violation-array': { severity: 'Warning' }, // more than 30 chars
134
147
  'odata-spec-violation-constraints': { severity: 'Info' }, // more than 30 chars
135
148
  'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
149
+ 'odata-spec-violation-no-key': { severity: 'Warning' },
136
150
  'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars
137
151
  'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars
138
152
  'odata-spec-violation-key-type': { severity: 'Warning' }, // more than 30 chars
@@ -143,7 +157,7 @@ const centralMessages = {
143
157
  // For messageIds, where no text has been provided via code (central def)
144
158
  const centralMessageTexts = {
145
159
  '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',
160
+ 'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
147
161
  'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
148
162
  'syntax-csn-expected-object': 'Expected object for property $(PROP)',
149
163
  'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',
@@ -160,6 +174,27 @@ const centralMessageTexts = {
160
174
  $tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
161
175
  mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
162
176
  },
177
+ 'syntax-csn-expected-length': {
178
+ std: 'Expected array in $(PROP) to have at least $(N) items',
179
+ one: 'Expected array in $(PROP) to have at least one item',
180
+ suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
181
+ },
182
+ 'syntax-invalid-text-block': 'Missing newline in text block',
183
+ 'syntax-unknown-escape': 'Unknown escape sequence $(CODE)',
184
+ 'syntax-invalid-escape': {
185
+ std: 'Invalid escape sequence $(CODE)',
186
+ octal: 'Octal escape sequences are not supported. Use unicode escapes instead',
187
+ whitespace: 'Unknown escape sequence: Can\'t escape whitespace',
188
+ codepoint: 'Undefined code-point for $(CODE)',
189
+ 'unicode-hex': 'Expected hexadecimal numbers for unicode escape but found $(CODE)',
190
+ 'hex-count': 'Expected $(NUMBER) hexadecimal numbers for escape sequence but found $(CODE)',
191
+ 'unicode-brace': 'Missing closing brace for unicode escape sequence',
192
+ 'language-identifier': 'Escape sequences in text-block\'s language identifier are not allowed',
193
+ },
194
+ 'syntax-missing-escape': {
195
+ std: 'Missing escape. Replace $(CODE) with $(NEWCODE)',
196
+ placeholder: 'Placeholders are not supported. Replace $(CODE) with $(NEWCODE)',
197
+ },
163
198
  'ref-undefined-def': {
164
199
  std: 'Artifact $(ART) has not been found',
165
200
  // TODO: proposal 'No definition of $(NAME) found',
@@ -171,17 +206,20 @@ const centralMessageTexts = {
171
206
  std: 'Element $(ART) has not been found',
172
207
  element: 'Artifact $(ART) has no element $(MEMBER)'
173
208
  },
209
+ 'ref-unknown-var': {
210
+ std: 'Replacement $(ID) not found'
211
+ },
174
212
  'ref-rejected-on': {
175
213
  std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
176
214
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
177
215
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
178
216
  },
179
- 'ref-invalid-typeof': {
180
- std: 'Do not use $(KEYWORD) for the type reference here',
181
- type: 'Do not use $(KEYWORD) for the type of a type',
182
- event: 'Do not use $(KEYWORD) for the type of an event',
183
- param: 'Do not use $(KEYWORD) for the type of a parameter',
184
- select: 'Do not use $(KEYWORD) for type references in queries',
217
+ 'type-unexpected-typeof': {
218
+ std: 'Unexpected $(KEYWORD) for the type reference here',
219
+ type: 'Unexpected $(KEYWORD) in type of a type definition',
220
+ event: 'Unexpected $(KEYWORD) for the type of an event',
221
+ param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
222
+ select: 'Unexpected $(KEYWORD) for type references in queries',
185
223
  },
186
224
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
187
225
  'anno-undefined-def': 'Artifact $(ART) has not been found',
@@ -200,16 +238,21 @@ const centralMessageTexts = {
200
238
  param: 'Artifact $(ART) has no parameter $(MEMBER)'
201
239
  },
202
240
 
241
+ 'def-missing-element': {
242
+ std: 'Expecting entity to have at least one non-virtual element',
243
+ view: 'Expecting view to have at least one non-virtual element'
244
+ },
245
+
203
246
  'duplicate-definition': {
204
247
  std: 'Duplicate definition of $(NAME)',
205
248
  absolute: 'Duplicate definition of artifact $(NAME)',
206
- namespace: 'Other definition blocks $(NAME) for namespace name',
249
+ annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
207
250
  element: 'Duplicate definition of element $(NAME)',
208
251
  enum: 'Duplicate definition of enum $(NAME)',
209
252
  key: 'Duplicate definition of key $(NAME)',
210
253
  action: 'Duplicate definition of action or function $(NAME)',
211
254
  param: 'Duplicate definition of parameter $(NAME)',
212
- $tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
255
+ alias: 'Duplicate definition of table alias or mixin $(NAME)',
213
256
  },
214
257
 
215
258
  // TODO: rename to ref-expected-XYZ
@@ -228,6 +271,10 @@ const centralMessageTexts = {
228
271
 
229
272
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
230
273
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
274
+ 'query-ignoring-param-nullability': {
275
+ std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
276
+ sql: 'Ignoring nullability constraint on parameter when generating SQL view'
277
+ },
231
278
 
232
279
  'ref-sloppy-type': 'A type or an element is expected here',
233
280
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -262,9 +309,15 @@ const centralMessageTexts = {
262
309
  std: 'Unexpected $(TYPE) mapped to $(ID) as type for key element $(NAME)', // structured
263
310
  scalar: 'Unexpected $(TYPE) mapped to $(ID) as type for key element' // flat
264
311
  },
312
+ 'odata-spec-violation-no-key': 'Expected entity to have a primary key',
265
313
  'odata-spec-violation-type': 'Expected element to have a type',
266
314
  'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',
267
315
  'odata-spec-violation-namespace': 'Expected service name not to be one of the reserved names $(NAMES)',
316
+ // Other odata/edm errors
317
+ 'odata-definition-exists': {
318
+ std: 'Entity can\'t be created due to name collision with existing definition $(NAME)',
319
+ proxy: 'No proxy entity created due to name collision with existing definition $(NAME) of kind $(KIND)'
320
+ }
268
321
  }
269
322
 
270
323
  /**
@@ -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
 
@@ -670,13 +672,16 @@ function ignoreTextTransform() {
670
672
 
671
673
  function transformManyWith( t, sorted ) {
672
674
  return function transformMany( many, r, args, texts ) {
673
- const prop = ['none','one'][ many.length ];
674
- if (!prop || !texts[prop] || args['#'] ) {
675
- const names = many.map(t);
676
- return (sorted ? names.sort() : names).join(', ');
677
- }
675
+ const prop = ['none','one','two'][ many.length ];
676
+ const names = many.map(t);
677
+ if (sorted)
678
+ names.sort();
679
+ if (!prop || !texts[prop] || args['#'] )
680
+ return names.join(', ');
678
681
  r['#'] = prop; // text variant
679
- return many.length && t( many[0] );
682
+ if (many.length === 2)
683
+ r.second = names[1];
684
+ return many.length && names[0];
680
685
  };
681
686
  }
682
687
 
@@ -791,7 +796,7 @@ function replaceInString( text, params ) {
791
796
  start = pattern.lastIndex;
792
797
  }
793
798
  parts.push( text.substring( start ) );
794
- let remain = ('#' in params) ? [] : Object.keys( params ).filter( n => params[n] );
799
+ let remain = (params['#']) ? [] : Object.keys( params ).filter( n => params[n] );
795
800
  return (remain.length)
796
801
  ? parts.join('') + '; ' +
797
802
  remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
@@ -843,7 +848,10 @@ function messageHash(msg) {
843
848
  if(!msg.home)
844
849
  return messageString(msg);
845
850
  const copy = {...msg};
846
- copy.$location = undefined;
851
+ // Note: This is a hack. deduplicateMessages() would otherwise remove
852
+ // all but one message about duplicated artifacts.
853
+ if (!msg.messageId || !msg.messageId.includes('duplicate'))
854
+ copy.$location = undefined;
847
855
  return messageString(copy);
848
856
  }
849
857
 
@@ -853,19 +861,26 @@ function messageHash(msg) {
853
861
  *
854
862
  * Example:
855
863
  * ```txt
856
- * Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
864
+ * Error[message-id]: Can't find type `nu` in this scope
857
865
  * |
858
- * <source>.cds:3:11, at entity:“E”
866
+ * <source>.cds:3:11, at entity:“E”/element:“e
859
867
  * ```
868
+ *
860
869
  * @param {CSN.Message} err
861
870
  * @param {object} [config = {}]
862
871
  * @param {boolean} [config.normalizeFilename] Replace windows `\` with forward slashes `/`.
863
872
  * @param {boolean} [config.noMessageId]
864
873
  * @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
865
- * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
874
+ * @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
875
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
876
+ * coloring will be used. If 'auto', we will decide based on certain factors such
877
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
878
+ * unset.
866
879
  * @returns {string}
867
880
  */
868
881
  function messageStringMultiline( err, config = {} ) {
882
+ colorTerm.changeColorMode(config ? config.color : 'auto');
883
+
869
884
  const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';
870
885
  const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
871
886
  const home = !err.home ? '' : ('at ' + err.home);
@@ -878,7 +893,7 @@ function messageStringMultiline( err, config = {} ) {
878
893
  location += ', '
879
894
  }
880
895
  else if (!home)
881
- return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
896
+ return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;
882
897
 
883
898
  let lineSpacer = '';
884
899
  if (config.withLineSpacer) {
@@ -886,8 +901,7 @@ function messageStringMultiline( err, config = {} ) {
886
901
  lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
887
902
  }
888
903
 
889
- // TODO: use ':' before text
890
- return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
904
+ return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
891
905
  }
892
906
 
893
907
  /**
@@ -901,9 +915,15 @@ function messageStringMultiline( err, config = {} ) {
901
915
  * @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
902
916
  * from `lib/utils/file.js`
903
917
  * @param {CSN.Message} err Error object containing all details like line, message, etc.
918
+ * @param {object} [config = {}]
919
+ * @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
920
+ * coloring will be used. If 'auto', we will decide based on certain factors such
921
+ * as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
922
+ * unset.
904
923
  * @returns {string}
905
924
  */
906
- function messageContext(sourceLines, err) {
925
+ function messageContext(sourceLines, err, config) {
926
+ colorTerm.changeColorMode(config ? config.color : 'auto');
907
927
  const MAX_COL_LENGTH = 100;
908
928
 
909
929
  const loc = err.$location;
@@ -955,7 +975,7 @@ function messageContext(sourceLines, err) {
955
975
  // Indicate that the error is further to the right.
956
976
  if (endColumn === MAX_COL_LENGTH)
957
977
  highlighter = highlighter.replace(' ^', '..^');
958
- msg += indent + '| ' + term.asSeverity(severity, highlighter);
978
+ msg += indent + '| ' + colorTerm.severity(severity, highlighter);
959
979
 
960
980
  } else if (maxLine !== endLine) {
961
981
  // error spans more lines which we don't print
@@ -1068,7 +1088,7 @@ function deduplicateMessages( messages ) {
1068
1088
 
1069
1089
  function shortArtName( art ) {
1070
1090
  const { name } = art;
1071
- if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null ) &&
1091
+ if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1072
1092
  !name.absolute.includes(':'))
1073
1093
  return quote.name( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
1074
1094
  return artName( art );
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,
@@ -28,7 +27,7 @@ const availableBetaFlags = {
28
27
  ignoreAssocPublishingInUnion: true,
29
28
  nestedProjections: true,
30
29
  enableUniversalCsn: true,
31
- windowFunctions: true,
30
+ sqlSnippets: true,
32
31
  // disabled by --beta-mode
33
32
  nestedServices: false,
34
33
  };
@@ -55,15 +54,18 @@ function isBetaEnabled( options, feature ) {
55
54
  /**
56
55
  * Test for deprecated feature, stored in option `deprecated`.
57
56
  * With that, the value of `deprecated` is a dictionary of feature=>Boolean.
57
+ * If no `feature` is provided, checks if any deprecated option is set.
58
58
  *
59
59
  * Please do not move this function to the "option processor" code.
60
60
  *
61
61
  * @param {object} options Options
62
- * @param {string} feature Feature to check for
62
+ * @param {string} [feature] Feature to check for
63
63
  * @returns {boolean}
64
64
  */
65
- function isDeprecatedEnabled( options, feature ) {
65
+ function isDeprecatedEnabled( options, feature = null ) {
66
66
  const { deprecated } = options;
67
+ if(!feature)
68
+ return !!deprecated;
67
69
  return deprecated && typeof deprecated === 'object' && deprecated[feature];
68
70
  }
69
71