@syncular/typegen 0.5.1 → 0.6.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/README.md +75 -33
- package/dist/cli.js +43 -17
- package/dist/emit-queries-dart.js +167 -55
- package/dist/emit-queries-kotlin.js +166 -55
- package/dist/emit-queries-swift.js +182 -57
- package/dist/emit-queries.js +289 -123
- package/dist/fmt.d.ts +2 -2
- package/dist/fmt.js +323 -316
- package/dist/generate.d.ts +1 -1
- package/dist/generate.js +28 -9
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lsp.d.ts +1 -3
- package/dist/lsp.js +359 -185
- package/dist/manifest.d.ts +1 -3
- package/dist/manifest.js +1 -1
- package/dist/query-ir.js +53 -42
- package/dist/query.d.ts +115 -63
- package/dist/query.js +60 -11
- package/dist/syql-lexer.d.ts +2 -0
- package/dist/syql-lexer.js +9 -2
- package/dist/syql-lowering.d.ts +22 -0
- package/dist/syql-lowering.js +411 -0
- package/dist/syql-semantics.d.ts +76 -0
- package/dist/syql-semantics.js +551 -0
- package/dist/syql-validator.d.ts +35 -0
- package/dist/syql-validator.js +966 -0
- package/package.json +3 -3
- package/src/cli.ts +51 -21
- package/src/emit-queries-dart.ts +195 -69
- package/src/emit-queries-kotlin.ts +197 -69
- package/src/emit-queries-swift.ts +224 -68
- package/src/emit-queries.ts +413 -165
- package/src/fmt.ts +377 -303
- package/src/generate.ts +43 -9
- package/src/index.ts +3 -1
- package/src/lsp.ts +425 -215
- package/src/manifest.ts +2 -4
- package/src/query-ir.ts +52 -42
- package/src/query.ts +199 -79
- package/src/syql-lexer.ts +12 -1
- package/src/syql-lowering.ts +638 -0
- package/src/syql-semantics.ts +859 -0
- package/src/syql-validator.ts +1492 -0
- package/dist/syql.d.ts +0 -102
- package/dist/syql.js +0 -1141
- package/src/syql.ts +0 -1470
package/dist/fmt.js
CHANGED
|
@@ -1,130 +1,28 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `syncular fmt` — the canonical `.syql` formatter (DESIGN-queries.md §10,
|
|
3
|
-
* §12). One style, no options:
|
|
1
|
+
/** Canonical revision-1 SYQL formatter (§19).
|
|
4
2
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - declarations separated by one blank line; knobs on their own lines.
|
|
10
|
-
* - comments are preserved: a declaration's leading comment block stays
|
|
11
|
-
* above it; comments inside bodies stay on their own lines.
|
|
12
|
-
*
|
|
13
|
-
* The formatter is built on the same parser as the generator (`.syql` that
|
|
14
|
-
* does not parse does not format), and formatting is semantics-preserving
|
|
15
|
-
* by construction — `fmt` output re-parses to the same declarations.
|
|
3
|
+
* Formatting is a lossless-token rewrite: semantic/atomic tokens and comments
|
|
4
|
+
* keep their order and exact spelling, while trivia is regenerated. The pass
|
|
5
|
+
* parses both sides and refuses output unless their normalized semantic ASTs
|
|
6
|
+
* agree. This keeps the formatter on the same lexer/parser contract as codegen.
|
|
16
7
|
*/
|
|
17
8
|
import { TypegenError } from './errors.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
if (body.startsWith('--', i)) {
|
|
37
|
-
const nl = body.indexOf('\n', i);
|
|
38
|
-
const end = nl === -1 ? body.length : nl;
|
|
39
|
-
tokens.push({ kind: 'comment', text: body.slice(i, end).trimEnd() });
|
|
40
|
-
i = end;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (body.startsWith('/*', i)) {
|
|
44
|
-
const close = body.indexOf('*/', i + 2);
|
|
45
|
-
const end = close === -1 ? body.length : close + 2;
|
|
46
|
-
tokens.push({ kind: 'comment', text: body.slice(i, end) });
|
|
47
|
-
i = end;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (ch === "'") {
|
|
51
|
-
let j = i + 1;
|
|
52
|
-
for (;;) {
|
|
53
|
-
if (j >= body.length)
|
|
54
|
-
break;
|
|
55
|
-
if (body[j] === "'") {
|
|
56
|
-
if (body[j + 1] === "'")
|
|
57
|
-
j += 2;
|
|
58
|
-
else {
|
|
59
|
-
j += 1;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else
|
|
64
|
-
j += 1;
|
|
65
|
-
}
|
|
66
|
-
tokens.push({ kind: 'string', text: body.slice(i, j) });
|
|
67
|
-
i = j;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (ch === ':' && /[A-Za-z_]/.test(body[i + 1] ?? '')) {
|
|
71
|
-
let j = i + 1;
|
|
72
|
-
while (j < body.length && /[A-Za-z0-9_]/.test(body[j]))
|
|
73
|
-
j += 1;
|
|
74
|
-
tokens.push({ kind: 'param', text: body.slice(i, j) });
|
|
75
|
-
i = j;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (ch === '@' && /[A-Za-z_]/.test(body[i + 1] ?? '')) {
|
|
79
|
-
let j = i + 1;
|
|
80
|
-
while (j < body.length && /[A-Za-z0-9_]/.test(body[j]))
|
|
81
|
-
j += 1;
|
|
82
|
-
tokens.push({ kind: 'fragment', text: body.slice(i, j) });
|
|
83
|
-
i = j;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (/[A-Za-z_]/.test(ch)) {
|
|
87
|
-
let j = i + 1;
|
|
88
|
-
while (j < body.length && /[A-Za-z0-9_]/.test(body[j]))
|
|
89
|
-
j += 1;
|
|
90
|
-
tokens.push({ kind: 'word', text: body.slice(i, j) });
|
|
91
|
-
i = j;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Multi-char operators stay one token (`||`, `>=`, `<=`, `<>`, `!=`, `==`).
|
|
95
|
-
const two = body.slice(i, i + 2);
|
|
96
|
-
if (['||', '>=', '<=', '<>', '!=', '=='].includes(two)) {
|
|
97
|
-
tokens.push({ kind: 'punct', text: two });
|
|
98
|
-
i += 2;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
tokens.push({ kind: 'punct', text: ch });
|
|
102
|
-
i += 1;
|
|
103
|
-
}
|
|
104
|
-
return tokens;
|
|
105
|
-
}
|
|
106
|
-
/** Render tokens back to one line with canonical spacing + keyword case. */
|
|
107
|
-
function render(tokens) {
|
|
108
|
-
let out = '';
|
|
109
|
-
let prev;
|
|
110
|
-
for (const token of tokens) {
|
|
111
|
-
let text = token.text;
|
|
112
|
-
if (token.kind === 'word' && SQL_KEYWORDS.has(text.toLowerCase())) {
|
|
113
|
-
text = text.toLowerCase();
|
|
114
|
-
}
|
|
115
|
-
const glueLeft = token.kind === 'punct' &&
|
|
116
|
-
(text === ',' || text === ')' || text === ';' || text === '.');
|
|
117
|
-
const glueRight = prev !== undefined &&
|
|
118
|
-
((prev.kind === 'punct' && (prev.text === '(' || prev.text === '.')) ||
|
|
119
|
-
prev.kind === 'fragment');
|
|
120
|
-
if (out.length > 0 && !glueLeft && !glueRight)
|
|
121
|
-
out += ' ';
|
|
122
|
-
out += text;
|
|
123
|
-
prev = token;
|
|
124
|
-
}
|
|
125
|
-
return out;
|
|
126
|
-
}
|
|
127
|
-
/** Clause keywords that start a new line at paren depth 0. */
|
|
9
|
+
import { toSyqlSemanticAst } from './syql-ast.js';
|
|
10
|
+
import { parseSyqlSyntaxFile, } from './syql-parser.js';
|
|
11
|
+
const SQL_KEYWORDS = new Set(('abort action add after all alter analyze and as asc attach autoincrement ' +
|
|
12
|
+
'before begin between by cascade case cast check collate column commit ' +
|
|
13
|
+
'conflict constraint create cross current current_date current_time ' +
|
|
14
|
+
'current_timestamp database default deferrable deferred delete desc ' +
|
|
15
|
+
'detach distinct do drop each else end escape except exclude exclusive ' +
|
|
16
|
+
'exists explain fail filter first following for foreign from full ' +
|
|
17
|
+
'generated glob group groups having if ignore immediate in index indexed ' +
|
|
18
|
+
'initially inner insert instead intersect into is isnull join key last ' +
|
|
19
|
+
'left like limit match materialized natural no not nothing notnull null ' +
|
|
20
|
+
'nulls of offset on or order others outer over partition plan pragma ' +
|
|
21
|
+
'preceding primary query raise range recursive references regexp reindex ' +
|
|
22
|
+
'release rename replace restrict returning right rollback row rows savepoint ' +
|
|
23
|
+
'select set table temp temporary then ties to transaction trigger ' +
|
|
24
|
+
'unbounded union unique update using vacuum values view virtual when where ' +
|
|
25
|
+
'window with without asc desc').split(/\s+/));
|
|
128
26
|
const CLAUSE_STARTERS = new Set([
|
|
129
27
|
'select',
|
|
130
28
|
'from',
|
|
@@ -133,224 +31,333 @@ const CLAUSE_STARTERS = new Set([
|
|
|
133
31
|
'having',
|
|
134
32
|
'order',
|
|
135
33
|
'limit',
|
|
34
|
+
'offset',
|
|
136
35
|
'window',
|
|
137
36
|
'union',
|
|
138
37
|
'except',
|
|
139
38
|
'intersect',
|
|
140
39
|
]);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
40
|
+
function templates(file) {
|
|
41
|
+
return file.declarations.flatMap((declaration) => {
|
|
42
|
+
if (declaration.kind === 'predicate')
|
|
43
|
+
return [declaration.body];
|
|
44
|
+
return [
|
|
45
|
+
declaration.sql.body,
|
|
46
|
+
...(declaration.sort?.profiles.map((profile) => profile.order) ?? []),
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function inTemplate(ranges, token) {
|
|
51
|
+
return ranges.some((template) => token.span.start.offset >= template.span.start.offset &&
|
|
52
|
+
token.span.end.offset <= template.span.end.offset);
|
|
53
|
+
}
|
|
54
|
+
function normalizedAst(ast) {
|
|
55
|
+
return JSON.parse(JSON.stringify(ast, (_key, value) => {
|
|
56
|
+
if (value !== null &&
|
|
57
|
+
typeof value === 'object' &&
|
|
58
|
+
'kind' in value &&
|
|
59
|
+
'text' in value &&
|
|
60
|
+
value.kind === 'identifier' &&
|
|
61
|
+
typeof value.text === 'string') {
|
|
62
|
+
const token = value;
|
|
63
|
+
const lower = token.text.toLowerCase();
|
|
64
|
+
return {
|
|
65
|
+
kind: token.kind,
|
|
66
|
+
text: SQL_KEYWORDS.has(lower) ? lower : token.text,
|
|
67
|
+
};
|
|
160
68
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
69
|
+
return value;
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
function comments(file) {
|
|
73
|
+
return file.tokens
|
|
74
|
+
.filter((token) => token.kind === 'line-comment' || token.kind === 'block-comment')
|
|
75
|
+
.map((token) => token.text);
|
|
76
|
+
}
|
|
77
|
+
class TokenWriter {
|
|
78
|
+
#lines = [''];
|
|
79
|
+
#blockIndents = [];
|
|
80
|
+
#indent = 0;
|
|
81
|
+
#nextIndent = 0;
|
|
82
|
+
get line() {
|
|
83
|
+
return this.#lines[this.#lines.length - 1];
|
|
84
|
+
}
|
|
85
|
+
#setLine(value) {
|
|
86
|
+
this.#lines[this.#lines.length - 1] = value;
|
|
87
|
+
}
|
|
88
|
+
write(text, spaceBefore) {
|
|
89
|
+
if (this.line.length === 0) {
|
|
90
|
+
this.#setLine(' '.repeat(this.#indent + this.#nextIndent));
|
|
91
|
+
this.#nextIndent = 0;
|
|
170
92
|
}
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
93
|
+
if (spaceBefore &&
|
|
94
|
+
this.line.trim().length > 0 &&
|
|
95
|
+
!this.line.endsWith(' ')) {
|
|
96
|
+
this.#setLine(`${this.line} `);
|
|
97
|
+
}
|
|
98
|
+
this.#setLine(`${this.line}${text}`);
|
|
99
|
+
}
|
|
100
|
+
comment(text) {
|
|
101
|
+
if (this.line.trim().length > 0)
|
|
102
|
+
this.write('', true);
|
|
103
|
+
const pieces = text.split('\n');
|
|
104
|
+
this.write(pieces[0] ?? '', false);
|
|
105
|
+
for (const piece of pieces.slice(1)) {
|
|
106
|
+
this.newline();
|
|
107
|
+
// Internal block-comment indentation is part of the token. Do not add
|
|
108
|
+
// formatter indentation before its continuation text.
|
|
109
|
+
this.#setLine(piece);
|
|
110
|
+
}
|
|
111
|
+
this.newline();
|
|
112
|
+
}
|
|
113
|
+
newline(extraIndent = 0) {
|
|
114
|
+
this.#setLine(this.line.trimEnd());
|
|
115
|
+
if (this.line.length > 0 || this.#lines.length === 1)
|
|
116
|
+
this.#lines.push('');
|
|
117
|
+
this.#nextIndent = extraIndent;
|
|
118
|
+
}
|
|
119
|
+
blankline() {
|
|
120
|
+
this.newline();
|
|
121
|
+
while (this.#lines.length >= 2 &&
|
|
122
|
+
this.#lines[this.#lines.length - 2] === '') {
|
|
123
|
+
this.#lines.pop();
|
|
124
|
+
}
|
|
125
|
+
if (this.#lines[this.#lines.length - 1] !== '')
|
|
126
|
+
this.#lines.push('');
|
|
127
|
+
this.#lines.push('');
|
|
128
|
+
}
|
|
129
|
+
openBlock() {
|
|
130
|
+
const parentIndent = this.#indent;
|
|
131
|
+
this.write('{', true);
|
|
132
|
+
const leadingSpaces = this.line.length - this.line.trimStart().length;
|
|
133
|
+
this.#blockIndents.push({
|
|
134
|
+
close: leadingSpaces / 2,
|
|
135
|
+
parent: parentIndent,
|
|
136
|
+
});
|
|
137
|
+
this.#indent = leadingSpaces / 2 + 1;
|
|
138
|
+
this.newline();
|
|
139
|
+
}
|
|
140
|
+
indent() {
|
|
141
|
+
this.#indent += 1;
|
|
142
|
+
}
|
|
143
|
+
dedent() {
|
|
144
|
+
this.#indent -= 1;
|
|
145
|
+
}
|
|
146
|
+
closeBlock() {
|
|
147
|
+
const block = this.#blockIndents.pop();
|
|
148
|
+
if (block === undefined) {
|
|
149
|
+
throw new Error('formatter block indentation stack underflow');
|
|
150
|
+
}
|
|
151
|
+
if (this.line.trim().length > 0)
|
|
152
|
+
this.newline();
|
|
153
|
+
this.#indent = block.close;
|
|
154
|
+
this.write('}', false);
|
|
155
|
+
this.#indent = block.parent;
|
|
156
|
+
}
|
|
157
|
+
finish() {
|
|
158
|
+
while (this.#lines.length > 0 && this.#lines.at(-1)?.trim() === '') {
|
|
159
|
+
this.#lines.pop();
|
|
188
160
|
}
|
|
189
|
-
|
|
161
|
+
return `${this.#lines.join('\n')}\n`;
|
|
190
162
|
}
|
|
191
|
-
flush();
|
|
192
|
-
// Indent: clauses flush-left; WHERE continuation (`and …`) indented 2.
|
|
193
|
-
return lines.map((line) => /^(and|or)\b/.test(line) || line.startsWith('--') ? ` ${line}` : line);
|
|
194
163
|
}
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
164
|
+
function declarationParametersMultiline(tokens, openIndex, prefixLength) {
|
|
165
|
+
let depth = 0;
|
|
166
|
+
let parameters = 0;
|
|
167
|
+
let hasContent = false;
|
|
168
|
+
let length = prefixLength + 1;
|
|
169
|
+
for (let index = openIndex + 1; index < tokens.length; index += 1) {
|
|
170
|
+
const token = tokens[index];
|
|
171
|
+
if (token.kind === 'line-comment' || token.kind === 'block-comment') {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (token.text === '(')
|
|
175
|
+
depth += 1;
|
|
176
|
+
if (token.text === ')') {
|
|
177
|
+
if (depth === 0) {
|
|
178
|
+
if (hasContent)
|
|
179
|
+
parameters += 1;
|
|
180
|
+
return parameters >= 4 || length + 1 > 88;
|
|
181
|
+
}
|
|
182
|
+
depth -= 1;
|
|
183
|
+
}
|
|
184
|
+
if (depth === 0 && token.text === ',') {
|
|
185
|
+
parameters += 1;
|
|
186
|
+
hasContent = false;
|
|
206
187
|
}
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
188
|
+
else if (depth === 0) {
|
|
189
|
+
hasContent = true;
|
|
190
|
+
}
|
|
191
|
+
length += token.text.length + 1;
|
|
211
192
|
}
|
|
212
|
-
return
|
|
193
|
+
return false;
|
|
213
194
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
195
|
+
function needsSpace(previous, token) {
|
|
196
|
+
if (previous === undefined)
|
|
197
|
+
return false;
|
|
198
|
+
if ([',', ')', ']', ';', '.', '?', ':'].includes(token.text))
|
|
199
|
+
return false;
|
|
200
|
+
if (['(', '[', '.', '@'].includes(previous.text))
|
|
201
|
+
return false;
|
|
202
|
+
if (token.text === '(')
|
|
203
|
+
return false;
|
|
204
|
+
if (previous.text === ':')
|
|
205
|
+
return true;
|
|
206
|
+
if (token.kind === 'operator' || previous.kind === 'operator')
|
|
207
|
+
return true;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
function formatTokens(parsed) {
|
|
211
|
+
const writer = new TokenWriter();
|
|
212
|
+
const ranges = templates(parsed);
|
|
213
|
+
const tokens = parsed.tokens.filter((token) => token.kind !== 'whitespace' && token.kind !== 'eof');
|
|
214
|
+
let previous;
|
|
215
|
+
let braceDepth = 0;
|
|
216
|
+
let parenDepth = 0;
|
|
217
|
+
let importList = false;
|
|
218
|
+
let justClosedImportList = false;
|
|
219
|
+
let seenTopLevel = false;
|
|
220
|
+
let pendingBetween = 0;
|
|
221
|
+
let declarationParameters;
|
|
222
|
+
let awaitsDeclarationParameters = false;
|
|
223
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
224
|
+
const token = tokens[index];
|
|
225
|
+
const lower = token.text.toLowerCase();
|
|
226
|
+
const template = inTemplate(ranges, token);
|
|
227
|
+
const topLevelDeclaration = braceDepth === 0 &&
|
|
228
|
+
token.kind === 'identifier' &&
|
|
229
|
+
(lower === 'import' || lower === 'predicate' || lower === 'query');
|
|
230
|
+
if (topLevelDeclaration) {
|
|
231
|
+
if (seenTopLevel)
|
|
232
|
+
writer.blankline();
|
|
233
|
+
seenTopLevel = true;
|
|
234
|
+
previous = undefined;
|
|
235
|
+
awaitsDeclarationParameters = lower === 'predicate' || lower === 'query';
|
|
227
236
|
}
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
else if (previous?.text === '}' &&
|
|
238
|
+
!justClosedImportList &&
|
|
239
|
+
token.text !== ';' &&
|
|
240
|
+
token.text !== ',') {
|
|
241
|
+
writer.newline();
|
|
242
|
+
previous = undefined;
|
|
234
243
|
}
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
pending.push(source.slice(i, end));
|
|
239
|
-
i = end;
|
|
244
|
+
if (token.kind === 'line-comment' || token.kind === 'block-comment') {
|
|
245
|
+
writer.comment(token.text);
|
|
246
|
+
previous = undefined;
|
|
240
247
|
continue;
|
|
241
248
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
if (template &&
|
|
250
|
+
parenDepth === 0 &&
|
|
251
|
+
token.kind === 'identifier' &&
|
|
252
|
+
CLAUSE_STARTERS.has(lower) &&
|
|
253
|
+
writer.line.trim().length > 0) {
|
|
254
|
+
writer.newline();
|
|
255
|
+
previous = undefined;
|
|
256
|
+
pendingBetween = 0;
|
|
247
257
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
while (j < source.length) {
|
|
255
|
-
const c = source[j];
|
|
256
|
-
if (c === "'") {
|
|
257
|
-
j += 1;
|
|
258
|
-
while (j < source.length && source[j] !== "'")
|
|
259
|
-
j += 1;
|
|
258
|
+
if (template && parenDepth === 0 && lower === 'and') {
|
|
259
|
+
if (pendingBetween > 0)
|
|
260
|
+
pendingBetween -= 1;
|
|
261
|
+
else {
|
|
262
|
+
writer.newline(1);
|
|
263
|
+
previous = undefined;
|
|
260
264
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
265
|
+
}
|
|
266
|
+
else if (template && lower === 'between') {
|
|
267
|
+
pendingBetween += 1;
|
|
268
|
+
}
|
|
269
|
+
if (token.text === '{') {
|
|
270
|
+
if (previous?.kind === 'identifier' && previous.text === 'import') {
|
|
271
|
+
writer.write('{', true);
|
|
272
|
+
importList = true;
|
|
264
273
|
}
|
|
265
|
-
else
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
depth -= 1;
|
|
269
|
-
if (depth === 0) {
|
|
270
|
-
j += 1;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
274
|
+
else {
|
|
275
|
+
writer.openBlock();
|
|
276
|
+
braceDepth += 1;
|
|
273
277
|
}
|
|
274
|
-
|
|
278
|
+
previous = token;
|
|
279
|
+
continue;
|
|
275
280
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
* {@link TypegenError} when the source does not parse. */
|
|
282
|
-
export function formatSyql(file, source) {
|
|
283
|
-
const parsed = parseSyqlFile(file, source);
|
|
284
|
-
const comments = leadingComments(source);
|
|
285
|
-
const decls = [];
|
|
286
|
-
// Re-derive declaration order (fragments/queries interleave in-source);
|
|
287
|
-
// parseSyqlFile keeps per-kind order, so re-scan the source for kind
|
|
288
|
-
// keywords to interleave faithfully.
|
|
289
|
-
const orderRe = /\b(query|fragment)\s+([A-Za-z][A-Za-z0-9]*)/g;
|
|
290
|
-
const blanked = source
|
|
291
|
-
.replace(/--[^\n]*/g, (m) => ' '.repeat(m.length))
|
|
292
|
-
.replace(/\/\*[\s\S]*?\*\//g, (m) => ' '.repeat(m.length))
|
|
293
|
-
.replace(/'(?:[^']|'')*'/g, (m) => ' '.repeat(m.length));
|
|
294
|
-
const sequence = [];
|
|
295
|
-
let braceDepth = 0;
|
|
296
|
-
let lastIndex = 0;
|
|
297
|
-
for (let i = 0; i < blanked.length; i++) {
|
|
298
|
-
const c = blanked[i];
|
|
299
|
-
if (c === '{')
|
|
300
|
-
braceDepth += 1;
|
|
301
|
-
else if (c === '}')
|
|
302
|
-
braceDepth -= 1;
|
|
303
|
-
else if (braceDepth === 0) {
|
|
304
|
-
orderRe.lastIndex = i;
|
|
305
|
-
const m = orderRe.exec(blanked);
|
|
306
|
-
if (m !== null && m.index === i) {
|
|
307
|
-
sequence.push({ kind: m[1], name: m[2] });
|
|
308
|
-
i = orderRe.lastIndex - 1;
|
|
309
|
-
lastIndex = orderRe.lastIndex;
|
|
281
|
+
if (token.text === '}') {
|
|
282
|
+
if (importList && braceDepth === 0) {
|
|
283
|
+
writer.write('}', true);
|
|
284
|
+
importList = false;
|
|
285
|
+
justClosedImportList = true;
|
|
310
286
|
}
|
|
287
|
+
else {
|
|
288
|
+
writer.closeBlock();
|
|
289
|
+
braceDepth -= 1;
|
|
290
|
+
}
|
|
291
|
+
previous = token;
|
|
292
|
+
continue;
|
|
311
293
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
294
|
+
justClosedImportList = false;
|
|
295
|
+
if (token.text === '(') {
|
|
296
|
+
parenDepth += 1;
|
|
297
|
+
const multiline = awaitsDeclarationParameters &&
|
|
298
|
+
declarationParametersMultiline(tokens, index, writer.line.length);
|
|
299
|
+
if (awaitsDeclarationParameters) {
|
|
300
|
+
declarationParameters = { depth: parenDepth, multiline };
|
|
301
|
+
awaitsDeclarationParameters = false;
|
|
302
|
+
}
|
|
303
|
+
writer.write(token.text, needsSpace(previous, token));
|
|
304
|
+
if (multiline) {
|
|
305
|
+
writer.indent();
|
|
306
|
+
writer.newline();
|
|
307
|
+
}
|
|
308
|
+
previous = token;
|
|
309
|
+
continue;
|
|
327
310
|
}
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const dir = decl.orderBy.defaultDir === 'desc' ? ' desc' : '';
|
|
335
|
-
knobs.push(` orderBy ${decl.orderBy.allowed.join(' | ')} default ${decl.orderBy.defaultColumn}${dir}`);
|
|
311
|
+
if (token.text === ',' && declarationParameters?.depth === parenDepth) {
|
|
312
|
+
const next = tokens[index + 1];
|
|
313
|
+
if (!(declarationParameters.multiline === false && next?.text === ')')) {
|
|
314
|
+
writer.write(',', false);
|
|
315
|
+
if (declarationParameters.multiline)
|
|
316
|
+
writer.newline();
|
|
336
317
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
318
|
+
previous = token;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (token.text === ')' && declarationParameters?.depth === parenDepth) {
|
|
322
|
+
if (declarationParameters.multiline) {
|
|
323
|
+
if (previous?.text !== ',')
|
|
324
|
+
writer.write(',', false);
|
|
325
|
+
writer.dedent();
|
|
326
|
+
if (writer.line.trim().length > 0)
|
|
327
|
+
writer.newline();
|
|
345
328
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
lines.push('}');
|
|
329
|
+
writer.write(')', false);
|
|
330
|
+
declarationParameters = undefined;
|
|
331
|
+
parenDepth -= 1;
|
|
332
|
+
previous = token;
|
|
333
|
+
continue;
|
|
352
334
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
335
|
+
if (token.text === ')')
|
|
336
|
+
parenDepth -= 1;
|
|
337
|
+
const text = template && token.kind === 'identifier' && SQL_KEYWORDS.has(lower)
|
|
338
|
+
? lower
|
|
339
|
+
: token.text;
|
|
340
|
+
writer.write(text, needsSpace(previous, token));
|
|
341
|
+
if (token.text === ';') {
|
|
342
|
+
writer.newline();
|
|
343
|
+
previous = undefined;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
previous = token;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return writer.finish();
|
|
350
|
+
}
|
|
351
|
+
/** Format one `.syql` source. Invalid or non-equivalent output is rejected so
|
|
352
|
+
* the CLI never writes a partially understood file. */
|
|
353
|
+
export function formatSyql(file, source) {
|
|
354
|
+
const before = parseSyqlSyntaxFile(file, source);
|
|
355
|
+
const formatted = formatTokens(before);
|
|
356
|
+
const after = parseSyqlSyntaxFile(file, formatted);
|
|
357
|
+
if (JSON.stringify(normalizedAst(toSyqlSemanticAst(before))) !==
|
|
358
|
+
JSON.stringify(normalizedAst(toSyqlSemanticAst(after))) ||
|
|
359
|
+
JSON.stringify(comments(before)) !== JSON.stringify(comments(after))) {
|
|
360
|
+
throw new TypegenError(file, 'SYQL8001_FORMATTER_EQUIVALENCE: formatter output changed the revision-1 semantic AST or comment order');
|
|
361
|
+
}
|
|
362
|
+
return formatted;
|
|
356
363
|
}
|