@sap/cds-compiler 5.8.2 → 5.9.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 (87) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cds_remove_invalid_whitespace.js +5 -3
  3. package/bin/cds_update_identifiers.js +9 -6
  4. package/bin/cdsc.js +79 -59
  5. package/bin/cdsse.js +14 -10
  6. package/bin/cdsv2m.js +3 -1
  7. package/lib/api/options.js +28 -6
  8. package/lib/base/message-registry.js +15 -4
  9. package/lib/checks/validator.js +3 -0
  10. package/lib/compiler/base.js +1 -1
  11. package/lib/compiler/checks.js +70 -50
  12. package/lib/compiler/extend.js +1 -1
  13. package/lib/compiler/generate.js +8 -2
  14. package/lib/compiler/index.js +1 -1
  15. package/lib/compiler/lsp-api.js +1 -1
  16. package/lib/compiler/propagator.js +2 -2
  17. package/lib/compiler/resolve.js +78 -31
  18. package/lib/compiler/shared.js +3 -3
  19. package/lib/compiler/tweak-assocs.js +1 -1
  20. package/lib/compiler/utils.js +10 -0
  21. package/lib/compiler/xpr-rewrite.js +1 -1
  22. package/lib/edm/annotations/edmJson.js +42 -39
  23. package/lib/edm/annotations/genericTranslation.js +55 -55
  24. package/lib/edm/annotations/preprocessAnnotations.js +5 -5
  25. package/lib/edm/csn2edm.js +21 -16
  26. package/lib/edm/edm.js +62 -62
  27. package/lib/edm/edmAnnoPreprocessor.js +2 -2
  28. package/lib/edm/edmInboundChecks.js +1 -1
  29. package/lib/edm/edmPreprocessor.js +32 -32
  30. package/lib/edm/edmUtils.js +8 -8
  31. package/lib/gen/CdlGrammar.checksum +1 -1
  32. package/lib/gen/CdlParser.js +77 -81
  33. package/lib/gen/Dictionary.json +3062 -3072
  34. package/lib/gen/language.checksum +1 -1
  35. package/lib/gen/language.interp +1 -1
  36. package/lib/gen/languageParser.js +1238 -1236
  37. package/lib/json/from-csn.js +1 -1
  38. package/lib/json/to-csn.js +30 -3
  39. package/lib/language/genericAntlrParser.js +16 -0
  40. package/lib/main.d.ts +79 -1
  41. package/lib/model/csnRefs.js +12 -5
  42. package/lib/model/xprAsTree.js +71 -0
  43. package/lib/modelCompare/utils/filter.js +1 -1
  44. package/lib/optionProcessor.js +46 -32
  45. package/lib/parsers/CdlGrammar.g4 +33 -28
  46. package/lib/parsers/Lexer.js +1 -1
  47. package/lib/parsers/XprTree.js +25 -16
  48. package/lib/render/toCdl.js +902 -414
  49. package/lib/render/toHdbcds.js +1 -1
  50. package/lib/render/toSql.js +8 -0
  51. package/lib/render/utils/common.js +2 -2
  52. package/lib/render/utils/operators.js +160 -0
  53. package/lib/render/utils/pretty.js +337 -0
  54. package/lib/sql-identifier.js +7 -9
  55. package/lib/transform/addTenantFields.js +39 -41
  56. package/lib/transform/db/applyTransformations.js +4 -4
  57. package/lib/transform/db/assertUnique.js +6 -5
  58. package/lib/transform/db/associations.js +3 -3
  59. package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
  60. package/lib/transform/db/assocsToQueries/utils.js +8 -0
  61. package/lib/transform/db/backlinks.js +19 -14
  62. package/lib/transform/db/constraints.js +6 -6
  63. package/lib/transform/db/expansion.js +1 -1
  64. package/lib/transform/db/flattening.js +2 -2
  65. package/lib/transform/db/groupByOrderBy.js +1 -1
  66. package/lib/transform/db/processSqlServices.js +3 -3
  67. package/lib/transform/db/rewriteCalculatedElements.js +2 -2
  68. package/lib/transform/db/temporal.js +7 -9
  69. package/lib/transform/db/views.js +6 -6
  70. package/lib/transform/draft/odata.js +2 -0
  71. package/lib/transform/effective/annotations.js +1 -1
  72. package/lib/transform/effective/associations.js +1 -1
  73. package/lib/transform/effective/main.js +1 -0
  74. package/lib/transform/effective/service.js +2 -2
  75. package/lib/transform/forRelationalDB.js +11 -5
  76. package/lib/transform/localized.js +2 -0
  77. package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
  78. package/lib/transform/parseExpr.js +2 -2
  79. package/lib/transform/transformUtils.js +9 -7
  80. package/lib/transform/translateAssocsToJoins.js +0 -2
  81. package/lib/transform/universalCsn/coreComputed.js +2 -2
  82. package/lib/utils/moduleResolve.js +7 -5
  83. package/package.json +1 -1
  84. package/share/messages/def-upcoming-virtual-change.md +55 -0
  85. package/share/messages/file-unexpected-case-mismatch.md +61 -0
  86. package/share/messages/message-explanations.json +2 -0
  87. package/lib/transform/braceExpression.js +0 -77
@@ -571,7 +571,7 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
571
571
  * @returns {Array} Modified expression array
572
572
  */
573
573
  function fixFuzzyIndex( fuzzyIndex, columnName ) {
574
- return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', '(', { ref: columnName.split('.') }, ')' ] } : token));
574
+ return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', { xpr: { ref: columnName.split('.') } } ] } : token));
575
575
  }
576
576
  }
577
577
 
@@ -30,6 +30,8 @@ const { manageConstraints, manageConstraint } = require('./manageConstraints');
30
30
  const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
31
31
  const { ModelError, CompilerAssertion } = require('../base/error');
32
32
  const { pathId } = require('../model/csnRefs');
33
+ const { transformExprOperators } = require('./utils/operators');
34
+ const { exprAsTree, condAsTree } = require('../model/xprAsTree');
33
35
 
34
36
  class SqlRenderEnvironment {
35
37
  indent = '';
@@ -154,6 +156,12 @@ function toSqlDdl( csn, options, messageFunctions ) {
154
156
  });
155
157
 
156
158
  function renderExpr( x, env ) {
159
+ // TODO:
160
+ // Can we to structurizing upfront?
161
+ // Neither condAsTree() nor exprAsTree() traverse `.args` / `.where`.
162
+ // This is fine, since renderExpr() calls itself for function arguments and filters.
163
+ x = Array.isArray(x) ? condAsTree(x) : exprAsTree(x);
164
+ x = transformExprOperators(x, options, messageFunctions, env);
157
165
  return exprRenderer.renderExpr(x, env);
158
166
  }
159
167
 
@@ -81,9 +81,9 @@ function beautifyExprArray( tokens ) {
81
81
  // No space after last token, after opening parentheses, before closing parentheses, before comma, before and after dot
82
82
  if (i !== tokens.length - 1 &&
83
83
  // current token
84
- tokens[i] !== '(' && tokens[i] !== '.' &&
84
+ tokens[i] !== '.' &&
85
85
  // next token
86
- tokens[i + 1] !== ')' && tokens[i + 1] !== '.' && tokens[i + 1] !== ',')
86
+ tokens[i + 1] !== '.' && tokens[i + 1] !== ',')
87
87
  result += ' ';
88
88
  }
89
89
  return result;
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ // Utilities to transform operators in expressions for SQL rendering.
4
+
5
+ const operatorsPerDialect = {
6
+ __proto__: null,
7
+ plain: {
8
+ __proto__: null,
9
+ '==': function equals(xpr) {
10
+ const lhs = xpr[0];
11
+ const rhs = xpr[2];
12
+ xpr.length = 0;
13
+ if (lhs?.val === null) // shorthand
14
+ xpr.push(rhs, 'is', 'null');
15
+ else if (rhs?.val === null)
16
+ xpr.push(lhs, 'is', 'null');
17
+ else
18
+ xpr.push(lhs, 'is', 'not', 'distinct', 'from', rhs);
19
+ },
20
+ '!=': function unequal(xpr) {
21
+ const lhs = xpr[0];
22
+ const rhs = xpr[2];
23
+ xpr.length = 0;
24
+ if (lhs?.val === null) // shorthand
25
+ xpr.push(rhs, 'is', 'not', 'null');
26
+ else if (rhs?.val === null)
27
+ xpr.push(lhs, 'is', 'not', 'null');
28
+ else
29
+ xpr.push(lhs, 'is', 'distinct', 'from', rhs);
30
+ },
31
+ },
32
+ hana: {
33
+ __proto__: null,
34
+ '==': function equals(xpr) {
35
+ const lhs = xpr[0];
36
+ const rhs = xpr[2];
37
+ xpr.length = 0;
38
+ if (lhs?.val === null) { // shorthand
39
+ xpr.push(rhs, 'is', 'null');
40
+ }
41
+ else if (rhs?.val === null) {
42
+ xpr.push(lhs, 'is', 'null');
43
+ }
44
+ else {
45
+ // (a IS NOT NULL AND b IS NOT NULL AND a = b) OR (a IS NULL AND b IS NULL)
46
+ xpr.push({
47
+ xpr: [
48
+ {
49
+ xpr: [ [ lhs, 'is', 'not', 'null' ], 'and', [ rhs, 'is', 'not', 'null' ], 'and', [ lhs, '=', rhs ] ],
50
+ },
51
+ 'or',
52
+ {
53
+ xpr: [ [ lhs, 'is', 'null' ], 'and', [ rhs, 'is', 'null' ] ],
54
+ },
55
+ ],
56
+ });
57
+ }
58
+ },
59
+ '!=': function unequal(xpr) {
60
+ const lhs = xpr[0];
61
+ const rhs = xpr[2];
62
+ xpr.length = 0;
63
+
64
+ if (lhs?.val === null) { // shorthand
65
+ xpr.push(rhs, 'is', 'not', 'null');
66
+ }
67
+ else if (rhs?.val === null) {
68
+ xpr.push(lhs, 'is', 'not', 'null');
69
+ }
70
+ else {
71
+ // `(a IS NULL OR b IS NULL OR a <> b) AND (a IS NOT NULL OR b IS NOT NULL)`
72
+ xpr.push({
73
+ xpr: [
74
+ {
75
+ xpr: [ [ lhs, 'is', 'null' ], 'or', [ rhs, 'is', 'null' ], 'or', [ lhs, '<>', rhs ] ],
76
+ },
77
+ 'and',
78
+ {
79
+ xpr: [ [ lhs, 'is', 'not', 'null' ], 'or', [ rhs, 'is', 'not', 'null' ] ],
80
+ },
81
+ ],
82
+ });
83
+ }
84
+ },
85
+ },
86
+ sqlite: {
87
+ __proto__: null,
88
+ '==': function equals(xpr) {
89
+ const rhs = xpr[2];
90
+ xpr.length = 1;
91
+ xpr.push('is', rhs);
92
+ },
93
+ '!=': function unequal(xpr) {
94
+ const rhs = xpr[2];
95
+ xpr.length = 1;
96
+ xpr.push('is', 'not', rhs);
97
+ },
98
+ },
99
+ };
100
+
101
+ operatorsPerDialect.postgres = operatorsPerDialect.plain;
102
+ operatorsPerDialect.h2 = operatorsPerDialect.plain;
103
+
104
+ /**
105
+ * Transform non-SQL operators to ones known by SQL. This includes `==` and `!=`.
106
+ * Expects structurized CSN! See xprAsTree.js
107
+ *
108
+ * @param {object[]|object} xpr
109
+ * @param {SqlOptions} options
110
+ * @param {object} messageFunctions
111
+ * @param {object} env
112
+ * @returns {object[]}
113
+ */
114
+ function transformExprOperators( xpr, options, messageFunctions, env ) {
115
+ // TODO: Reduce number of function arguments
116
+ const sqlDialect = options.sqlDialect || 'plain';
117
+ const operators = Object.assign(Object.create(null), operatorsPerDialect[sqlDialect] || operatorsPerDialect.plain);
118
+
119
+ if (!options.booleanEquality) {
120
+ // don't translate `!=` if the option isn't set; default to be changed in v6
121
+ delete operators['!='];
122
+ }
123
+
124
+ transformBinary(xpr);
125
+ return xpr;
126
+
127
+ function transformBinary( x ) {
128
+ if (!x || typeof x !== 'object')
129
+ return;
130
+
131
+ if (Array.isArray(x)) {
132
+ const op = x[1];
133
+ if (x.length === 3 && op in operators) {
134
+ transformBinary(x[0]); // left-hand-side
135
+ transformBinary(x[2]); // right-hand-side
136
+ operators[op](x);
137
+ }
138
+ else {
139
+ x.forEach(transformBinary);
140
+ }
141
+
142
+ if (x.length > 3 && op in operators) {
143
+ const keyword = typeof x[2] === 'string' && x[2].toLowerCase();
144
+ // e.g. `ref != SOME (SELECT num from Base)`
145
+ // Since semantics are unclear, reject it.
146
+ if (keyword === 'all' || keyword === 'some' || keyword === 'any') {
147
+ messageFunctions.error('expr-unsupported-equality', env.path, { op, keyword },
148
+ 'Operator $(OP) can\'t be used in combination with $(KEYWORD)');
149
+ }
150
+ }
151
+ }
152
+ else if (x.xpr) {
153
+ transformBinary(x.xpr);
154
+ }
155
+ }
156
+ }
157
+
158
+ module.exports = {
159
+ transformExprOperators,
160
+ };
@@ -0,0 +1,337 @@
1
+ 'use strict';
2
+
3
+ // Pretty Printer
4
+ //
5
+ // Based on https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
6
+ // by Philip Wadler. Implemented up to section 3, which includes some
7
+ // efficiency improvements.
8
+ //
9
+ // A similar algorithm is also used by the cds-lsp package, though more advanced
10
+ // and based on a formatting stream instead of these nested structures.
11
+ //
12
+ // The basic idea is that you can define the maximum width and the document
13
+ // is formatted best to fill that space.
14
+ // Groups should appear on a single line if possible.
15
+ //
16
+ // All function names come directly from the above paper and its
17
+ // Haskell implementation. Variable names have been changed to improve
18
+ // readability, e.g. `x` -> `doc`, etc.
19
+
20
+ /** Base document class. */
21
+ class Doc {}
22
+
23
+ const LINE_OR_SPACE = ' ';
24
+ const LINE_OR_EMPTY = '';
25
+
26
+ /**
27
+ * This class represents a newline which may or may not be inserted into
28
+ * the document, depending on the width.
29
+ * If no newline is to be inserted, `kind` stores whether we insert
30
+ * a space or no space instead. It is used by `flatten()`.
31
+ * If a newline is to be inserted, nesting is added, which may have been
32
+ * applied by `best()`/`be()`.
33
+ */
34
+ class Line extends Doc {
35
+ /**
36
+ * @param {string} kind LINE_OR_SPACE or LINE_OR_EMPTY
37
+ */
38
+ constructor(kind = LINE_OR_SPACE) {
39
+ super();
40
+ this.kind = kind;
41
+ this._indent = 0;
42
+ }
43
+ applyNest(n) {
44
+ this._indent = n;
45
+ }
46
+ toString() {
47
+ return `\n${' '.repeat(this._indent)}`;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Adds nesting to the given document. Nesting is applied to the underlying
53
+ * lines in `flatten()`. But when creating a document, Nest is useful
54
+ * to easily indent blocks.
55
+ */
56
+ class Nest extends Doc {
57
+ /**
58
+ * @param {number} indent
59
+ * @param {Doc|Doc[]|string} x
60
+ */
61
+ constructor(indent, x) {
62
+ super();
63
+ this.indent = indent;
64
+ this.x = x;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Represents a union of two documents. One flattened and the other structured.
70
+ * A Union is created by `group()`.
71
+ */
72
+ class Union extends Doc {
73
+ /**
74
+ * @param {Doc|Doc[]|string} x Flattened document.
75
+ * @param {Doc|Doc[]|string} y Structured document.
76
+ */
77
+ constructor(x, y) {
78
+ super();
79
+ this.x = x;
80
+ this.y = y;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * A newline if no space is available or a single space if enough space is available.
86
+ *
87
+ * @return {Line}
88
+ */
89
+ function line() {
90
+ return new Line(LINE_OR_SPACE);
91
+ }
92
+ /**
93
+ * A newline if no space is available or an empty string if enough space is available.
94
+ *
95
+ * @return {Line}
96
+ */
97
+ function lineOrEmpty() {
98
+ return new Line(LINE_OR_EMPTY);
99
+ }
100
+
101
+ /**
102
+ * Group the given document.
103
+ *
104
+ * @param {Doc|Doc[]|string} x
105
+ * @returns {Union}
106
+ */
107
+ function group( x ) {
108
+ return new Union(flatten(x), x);
109
+ }
110
+
111
+ /**
112
+ * Flatten the given document, with no regard to line width.
113
+ *
114
+ * @param {Doc|Doc[]|string} doc
115
+ * @returns {Doc|Doc[]|string}
116
+ */
117
+ function flatten( doc ) {
118
+ if (!doc)
119
+ return doc;
120
+ else if (Array.isArray(doc))
121
+ return doc.map(flatten).flat(Infinity);
122
+ else if (doc instanceof Line)
123
+ return doc.kind;
124
+ else if (doc instanceof Nest)
125
+ return flatten(doc.x);
126
+ else if (doc instanceof Union)
127
+ return doc.x;
128
+ else if (typeof doc === 'string')
129
+ return doc;
130
+ throw new Error(`unhandled case: ${typeof doc}`);
131
+ }
132
+
133
+ /**
134
+ * Nest the given document by `n` spaces.
135
+ *
136
+ * @param {number} n
137
+ * @param {Doc|Doc[]|string} doc
138
+ * @returns {Doc|string}
139
+ */
140
+ function nestBy( n, doc ) {
141
+ if (Array.isArray(doc) || doc instanceof Line) {
142
+ return new Nest(n, doc);
143
+ }
144
+ else if (doc instanceof Union) {
145
+ doc.y = nestBy(n, doc.y);
146
+ return doc;
147
+ }
148
+ else if (typeof doc === 'string') {
149
+ return doc; // nesting absorbed by string
150
+ }
151
+ else if (typeof doc === 'number' || typeof doc === 'boolean' || doc === null) {
152
+ return String(doc); // nesting absorbed by string
153
+ }
154
+
155
+ throw new Error(`unhandled case: ${typeof doc}`);
156
+ }
157
+
158
+ /**
159
+ * Convenience function which nests the given lines while making the last
160
+ * line not indented. Consider e.g. `[ 1, 2, 3 ]`, where each number should
161
+ * be on a separate line:
162
+ *
163
+ * ```
164
+ * [
165
+ * 1,
166
+ * 2,
167
+ * 3
168
+ * ]
169
+ * ```
170
+ *
171
+ * The last inserted Line must not be indented.
172
+ *
173
+ * @param {number} indent
174
+ * @param {string} open
175
+ * @param {Doc|Doc[]|string} content
176
+ * @param {string} close
177
+ * @return {Union}
178
+ */
179
+ function bracketBlock( indent, open, content, close ) {
180
+ return group([
181
+ open,
182
+ group([ nestBy(indent, [ line(), content ]), line() ] ),
183
+ close,
184
+ ]);
185
+ }
186
+
187
+ /**
188
+ * Returns the document that better fits the desired width and current column.
189
+ *
190
+ * @param {number} width The desired width of the document.
191
+ * @param {number} k Current with of the line. (width-k) is the remaining width.
192
+ * @param {Doc|Doc[]|string} x
193
+ * @param {Doc|Doc[]|string} y
194
+ * @returns {Doc|Doc[]|string}
195
+ */
196
+ function better( width, k, x, y ) {
197
+ if (fits(width - k, x))
198
+ return x;
199
+ return y;
200
+ }
201
+
202
+ /**
203
+ * Find the best version of the given document that fits the given
204
+ * width. This function returns a document that has no Union or Nest
205
+ * anymore, only strings and Line.
206
+ *
207
+ * @param {number} width The desired width of the document.
208
+ * @param {number} k Current with of the line. (width-k) is the remaining width.
209
+ * @param {Doc|Doc[]|string} doc
210
+ * @returns {Doc|Doc[]|string}
211
+ */
212
+ function best( width, k, doc ) {
213
+ return be(width, k, 0, doc);
214
+ }
215
+
216
+ /**
217
+ * Same as `best()`, but keeps track of the current nesting `i`.
218
+ *
219
+ * @param {number} width The desired width of the document.
220
+ * @param {number} k Current with of the line. (width-k) is the remaining width.
221
+ * @param {number} i Current nesting.
222
+ * @param {Doc|Doc[]|string} doc
223
+ * @returns {Doc|Doc[]|string|string|Line|*|*[]}
224
+ */
225
+ function be( width, k, i, doc ) {
226
+ if (!doc || typeof doc === 'string')
227
+ return doc;
228
+ if (doc instanceof Line) {
229
+ doc.applyNest(i);
230
+ return doc;
231
+ }
232
+ if (doc instanceof Nest)
233
+ return be(width, k, i + doc.indent, doc.x);
234
+ if (doc instanceof Union)
235
+ return better(width, k, be(width, k, i, doc.x), be(width, k, i, doc.y));
236
+ if (Array.isArray(doc)) {
237
+ const result = [];
238
+ for (const entry of doc) {
239
+ const b = be(width, k, i, entry);
240
+ if (typeof b === 'string')
241
+ k += b.length;
242
+ result.push(b);
243
+ }
244
+ return result;
245
+ }
246
+ throw new Error(`unhandled case: ${typeof doc}`);
247
+ }
248
+
249
+ /**
250
+ * Determines if the document fits into the line.
251
+ *
252
+ * @param {number} width The desired width of the document.
253
+ * @param {Doc|Doc[]|string} doc
254
+ * @returns {boolean}
255
+ */
256
+ function fits( width, doc ) {
257
+ const list = Array.isArray(doc) ? doc : [ doc ];
258
+ for (const entry of list) {
259
+ if (!entry || entry instanceof Line)
260
+ return true;
261
+ else if (entry instanceof Union)
262
+ throw new Error('fits() must only be called via best() which resolves Unions already');
263
+ else if (entry instanceof Nest)
264
+ throw new Error('fits() must only be called via best() which resolves Nest already');
265
+ else if (typeof entry === 'string')
266
+ width -= entry.length;
267
+
268
+ if (width < 0)
269
+ return false;
270
+ }
271
+ return true;
272
+ }
273
+
274
+ /**
275
+ * Layouts the given document without trying to use as few lines as possible,
276
+ * i.e. each Line is rendered as a newline.
277
+ * Requires Nest nodes to be resolved, i.e. pretty() was applied.
278
+ *
279
+ * @param {Doc[]|Doc} doc
280
+ * @return {string}
281
+ */
282
+ function layout( doc ) {
283
+ if (!doc)
284
+ return '';
285
+ else if (Array.isArray(doc))
286
+ return doc.map(layout).join('');
287
+ else if (doc instanceof Line)
288
+ return doc.toString();
289
+ else if (typeof doc === 'string')
290
+ return doc;
291
+ throw new Error(`unhandled case: ${typeof doc}`);
292
+ }
293
+
294
+ /**
295
+ * Layouts the given document is a "pretty way", that is: it tries
296
+ * to fill up the space of maxWidth characters while keeping it pretty.
297
+ * Adds a final newline character.
298
+ *
299
+ * @param {Doc[]|Doc} doc
300
+ * @param {Number} [maxWidth]
301
+ * @return {string}
302
+ */
303
+ function pretty( doc, maxWidth = 80 ) {
304
+ const b = best(maxWidth, 0, doc);
305
+ return layout(b);
306
+ }
307
+
308
+ /**
309
+ * Join the given list of documents by adding tokens between them.
310
+ * Example:
311
+ * joinDocuments([ 'foo', 'bar', 'foobar' ], [ ',', line() ])
312
+ * returns:
313
+ * [ 'foo', ',', line(), 'bar', ',', line(), 'foobar' ]
314
+ *
315
+ * @param {Doc[]} values
316
+ * @param {Doc[]} tokens
317
+ * @returns {Doc[]}
318
+ */
319
+ function joinDocuments( values, tokens ) {
320
+ const result = [];
321
+ for (let i = 0; i < values.length; i++) {
322
+ result.push(values[i]);
323
+ if (i !== values.length - 1)
324
+ result.push(...tokens);
325
+ }
326
+ return result;
327
+ }
328
+
329
+ module.exports = {
330
+ pretty,
331
+ nestBy,
332
+ line,
333
+ lineOrEmpty,
334
+ group,
335
+ bracketBlock,
336
+ joinDocuments,
337
+ };
@@ -44,31 +44,31 @@ const sqlDialects = {
44
44
  regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
45
45
  reservedWords: keywords.h2,
46
46
  effectiveName: name => name.toUpperCase(),
47
- asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
47
+ asDelimitedId: name => `"${ name.replace(/"/g, '""') }"`,
48
48
  },
49
49
  sqlite: {
50
50
  regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
51
51
  reservedWords: keywords.sqlite,
52
52
  effectiveName: name => name,
53
- asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
53
+ asDelimitedId: name => `"${ name.replace(/"/g, '""') }"`,
54
54
  },
55
55
  postgres: {
56
56
  regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
57
57
  reservedWords: keywords.postgres,
58
58
  effectiveName: name => name.toLowerCase(),
59
- asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
59
+ asDelimitedId: name => `"${ name.replace(/"/g, '""') }"`,
60
60
  },
61
61
  hana: {
62
62
  regularRegex: /^[A-Za-z_][A-Za-z_$#0-9]*$/,
63
63
  reservedWords: keywords.hana,
64
64
  effectiveName: name => name.toUpperCase(),
65
- asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
65
+ asDelimitedId: name => `"${ name.replace(/"/g, '""') }"`,
66
66
  },
67
67
  hdbcds: {
68
68
  regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
69
69
  reservedWords: keywords.hdbcds,
70
70
  effectiveName: name => name,
71
- asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
71
+ asDelimitedId: name => `"${ name.replace(/"/g, '""') }"`,
72
72
  },
73
73
  };
74
74
 
@@ -79,8 +79,7 @@ function smartId( name, dialect ) {
79
79
  if (s.regularRegex && !s.regularRegex.test( name ) ||
80
80
  s.reservedWords && s.reservedWords.includes( name.toUpperCase() ))
81
81
  return s.asDelimitedId( s.effectiveName( name ) );
82
- else
83
- return name;
82
+ return name;
84
83
  }
85
84
 
86
85
  function smartFuncId( name, dialect ) {
@@ -89,8 +88,7 @@ function smartFuncId( name, dialect ) {
89
88
  : dialect;
90
89
  if (s.regularRegex && !s.regularRegex.test( name ))
91
90
  return s.asDelimitedId( s.effectiveName( name ) );
92
- else
93
- return name;
91
+ return name;
94
92
  }
95
93
 
96
94
  function delimitedId( name, dialect ) {