@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.
- package/CHANGELOG.md +31 -0
- package/bin/cds_remove_invalid_whitespace.js +5 -3
- package/bin/cds_update_identifiers.js +9 -6
- package/bin/cdsc.js +79 -59
- package/bin/cdsse.js +14 -10
- package/bin/cdsv2m.js +3 -1
- package/lib/api/options.js +28 -6
- package/lib/base/message-registry.js +15 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/checks.js +70 -50
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/generate.js +8 -2
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +78 -31
- package/lib/compiler/shared.js +3 -3
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +10 -0
- package/lib/compiler/xpr-rewrite.js +1 -1
- package/lib/edm/annotations/edmJson.js +42 -39
- package/lib/edm/annotations/genericTranslation.js +55 -55
- package/lib/edm/annotations/preprocessAnnotations.js +5 -5
- package/lib/edm/csn2edm.js +21 -16
- package/lib/edm/edm.js +62 -62
- package/lib/edm/edmAnnoPreprocessor.js +2 -2
- package/lib/edm/edmInboundChecks.js +1 -1
- package/lib/edm/edmPreprocessor.js +32 -32
- package/lib/edm/edmUtils.js +8 -8
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +77 -81
- package/lib/gen/Dictionary.json +3062 -3072
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1238 -1236
- package/lib/json/from-csn.js +1 -1
- package/lib/json/to-csn.js +30 -3
- package/lib/language/genericAntlrParser.js +16 -0
- package/lib/main.d.ts +79 -1
- package/lib/model/csnRefs.js +12 -5
- package/lib/model/xprAsTree.js +71 -0
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +46 -32
- package/lib/parsers/CdlGrammar.g4 +33 -28
- package/lib/parsers/Lexer.js +1 -1
- package/lib/parsers/XprTree.js +25 -16
- package/lib/render/toCdl.js +902 -414
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +8 -0
- package/lib/render/utils/common.js +2 -2
- package/lib/render/utils/operators.js +160 -0
- package/lib/render/utils/pretty.js +337 -0
- package/lib/sql-identifier.js +7 -9
- package/lib/transform/addTenantFields.js +39 -41
- package/lib/transform/db/applyTransformations.js +4 -4
- package/lib/transform/db/assertUnique.js +6 -5
- package/lib/transform/db/associations.js +3 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
- package/lib/transform/db/assocsToQueries/utils.js +8 -0
- package/lib/transform/db/backlinks.js +19 -14
- package/lib/transform/db/constraints.js +6 -6
- package/lib/transform/db/expansion.js +1 -1
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +1 -1
- package/lib/transform/db/processSqlServices.js +3 -3
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/temporal.js +7 -9
- package/lib/transform/db/views.js +6 -6
- package/lib/transform/draft/odata.js +2 -0
- package/lib/transform/effective/annotations.js +1 -1
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/main.js +1 -0
- package/lib/transform/effective/service.js +2 -2
- package/lib/transform/forRelationalDB.js +11 -5
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
- package/lib/transform/parseExpr.js +2 -2
- package/lib/transform/transformUtils.js +9 -7
- package/lib/transform/translateAssocsToJoins.js +0 -2
- package/lib/transform/universalCsn/coreComputed.js +2 -2
- package/lib/utils/moduleResolve.js +7 -5
- package/package.json +1 -1
- package/share/messages/def-upcoming-virtual-change.md +55 -0
- package/share/messages/file-unexpected-case-mismatch.md +61 -0
- package/share/messages/message-explanations.json +2 -0
- package/lib/transform/braceExpression.js +0 -77
package/lib/render/toHdbcds.js
CHANGED
|
@@ -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',
|
|
574
|
+
return fuzzyIndex.map(token => (token === 'on' ? { xpr: [ 'on', { xpr: { ref: columnName.split('.') } } ] } : token));
|
|
575
575
|
}
|
|
576
576
|
}
|
|
577
577
|
|
package/lib/render/toSql.js
CHANGED
|
@@ -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] !== '
|
|
84
|
+
tokens[i] !== '.' &&
|
|
85
85
|
// next token
|
|
86
|
-
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
|
+
};
|
package/lib/sql-identifier.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
93
|
-
return name;
|
|
91
|
+
return name;
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
function delimitedId( name, dialect ) {
|