Skip to content

Commit 8bc3fc4

Browse files
committed
Formatter improvements
- The formatter option `whereClauseOperatorsIndented` has been deprecated and will always be applied. - A new boolean formatter option named `newLineAfterKeywords` has been added and will ensure that there is always a new line after any keyword. (#137) - `TYPEOF` fields will now always be included on their own line be default, or will span multiple lines, split by keywords if `newLineAfterKeywords` is set to true. (#135) resolves #137 resolves #135
1 parent 9cee5a3 commit 8bc3fc4

File tree

10 files changed

+457
-125
lines changed

10 files changed

+457
-125
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
# Changelog
22

3+
## 3.2.0
4+
5+
March 27, 2021
6+
7+
A number of improvements to the formatter have been made with this release.
8+
9+
- The formatter option `whereClauseOperatorsIndented` has been deprecated and will always be applied.
10+
- A new boolean formatter option named `newLineAfterKeywords` has been added and will ensure that there is always a new line after any keyword. (#137)
11+
- `TYPEOF` fields will now always be included on their own line be default, or will span multiple lines, split by keywords if `newLineAfterKeywords` is set to true. (#135)
12+
13+
## Example
14+
15+
`SELECT Id, TYPEOF What WHEN Account THEN Phone, NumberOfEmployees WHEN Opportunity THEN Amount, CloseDate ELSE Name, Email END, Name FROM Event`
16+
17+
`formatOptions: { newLineAfterKeywords: true, fieldMaxLineLength: 1 },`
18+
19+
```sql
20+
SELECT
21+
Id,
22+
TYPEOF What
23+
WHEN
24+
Account
25+
THEN
26+
Phone, NumberOfEmployees
27+
WHEN
28+
Opportunity
29+
THEN
30+
Amount, CloseDate
31+
ELSE
32+
Name, Email
33+
END,
34+
Name
35+
FROM
36+
Event
37+
```
38+
339
## 3.1.0
440

541
March 27, 2021

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,14 @@ Many of hte utility functions are provided to easily determine the shape of spec
9696

9797
**FormatOptions**
9898

99-
| Property | Type | Description | required | default |
100-
| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- |
101-
| numIndent | number | The number of tab characters to indent. | FALSE | 1 |
102-
| fieldMaxLineLength | number | The number of characters that the fields should take up before making a new line. Set this to 1 to have every field on its own line. | FALSE | 60 |
103-
| fieldSubqueryParensOnOwnLine | boolean | If true, the opening and closing parentheses will be on their own line for subqueries. | FALSE | TRUE |
104-
| whereClauseOperatorsIndented | boolean | If true, indents the where clause operators. | FALSE | FALSE |
105-
| logging | boolean | Print out logging statements to the console about the format operation. | FALSE | FALSE |
99+
| Property | Type | Description | required | default |
100+
| -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- |
101+
| numIndent | number | The number of tab characters to indent. | FALSE | 1 |
102+
| fieldMaxLineLength | number | The number of characters that the fields should take up before making a new line. Set this to 1 to have every field on its own line. | FALSE | 60 |
103+
| fieldSubqueryParensOnOwnLine | boolean | If true, the opening and closing parentheses will be on their own line for subqueries. | FALSE | TRUE |
104+
| newLineAfterKeywords | boolean | Adds a new line and indent after all keywords (such as SELECT, FROM, WHERE, ORDER BY, etc..) Setting this to true will add new lines in other places as well, such as complex WHERE clauses | FALSE | FALSE |
105+
| ~~whereClauseOperatorsIndented~~ | boolean | **Deprecated** If true, indents the where clause operators. | FALSE | FALSE |
106+
| logging | boolean | Print out logging statements to the console about the format operation. | FALSE | FALSE |
106107

107108
## Examples
108109

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
textarea,
2+
pre {
3+
-moz-tab-size: 2;
4+
-o-tab-size: 2;
5+
tab-size: 2;
6+
}

docs/src/modules/my/queryComposer/queryComposer.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<ui-checkbox name="fieldSubqueryParensOnOwnLine" value={fieldSubqueryParensOnOwnLine} disabled={formDisabled} onchange={handleChange}>
88
Subquery parenthesis on own line - <code class="font-semibold">fieldSubqueryParensOnOwnLine</code>
99
</ui-checkbox>
10-
<ui-checkbox name="whereClauseOperatorsIndented" value={whereClauseOperatorsIndented} disabled={formDisabled} onchange={handleChange}>
11-
Indent items in WHERE clause - <code class="font-semibold">whereClauseOperatorsIndented</code>
10+
<ui-checkbox name="newLineAfterKeywords" value={newLineAfterKeywords} disabled={formDisabled} onchange={handleChange}>
11+
Add newline and indent after keywords - <code class="font-semibold">newLineAfterKeywords</code>
1212
</ui-checkbox>
1313
<ui-input
1414
name="fieldMaxLineLength"

docs/src/modules/my/queryComposer/queryComposer.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Query, composeQuery } from 'soql-parser-js';
33
import * as hljs from 'highlight.js/lib/highlight.js';
44
hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql'));
55

6-
const DEFAULT_LINE_LEN = 60;
6+
const DEFAULT_LINE_LEN = 1;
77
const NOT_DIGIT_RGX = /[^\d]/g;
88

99
export default class QueryComposer extends LightningElement {
@@ -21,7 +21,7 @@ export default class QueryComposer extends LightningElement {
2121
@track composedQuery: string;
2222
@track formatOutput = true;
2323
@track fieldSubqueryParensOnOwnLine = true;
24-
@track whereClauseOperatorsIndented = false;
24+
@track newLineAfterKeywords = true;
2525
@track fieldMaxLineLength = DEFAULT_LINE_LEN;
2626
hasRendered = false;
2727
fieldMaxLineLengthTransformFn = (value: string): string => {
@@ -40,15 +40,15 @@ export default class QueryComposer extends LightningElement {
4040
`// format: ${this.formatOutput},\n` +
4141
`// formatOptions: {\n` +
4242
`// fieldSubqueryParensOnOwnLine: ${this.fieldSubqueryParensOnOwnLine},\n` +
43-
`// whereClauseOperatorsIndented: ${this.whereClauseOperatorsIndented},\n` +
43+
`// newLineAfterKeywords: ${this.newLineAfterKeywords},\n` +
4444
`// fieldMaxLineLength: ${this.fieldMaxLineLength},\n` +
4545
`// }\n` +
4646
`// });\n`;
4747

4848
element.innerText =
4949
`// composeQuery(parsedQuery), {\n` +
5050
`// format: ${this.formatOutput},\n` +
51-
`// formatOptions: { fieldSubqueryParensOnOwnLine: ${this.fieldSubqueryParensOnOwnLine}, whereClauseOperatorsIndented: ${this.whereClauseOperatorsIndented}, fieldMaxLineLength: ${this.fieldMaxLineLength} }\n` +
51+
`// formatOptions: { fieldSubqueryParensOnOwnLine: ${this.fieldSubqueryParensOnOwnLine}, newLineAfterKeywords: ${this.newLineAfterKeywords}, fieldMaxLineLength: ${this.fieldMaxLineLength} }\n` +
5252
`// });\n`;
5353

5454
// element.innerText = `// composeQuery(parsedQuery, { format: ${this.formatOutput}, formatOptions: { fieldSubqueryParensOnOwnLine, whereClauseOperatorsIndented, fieldMaxLineLength } });`;
@@ -59,10 +59,10 @@ export default class QueryComposer extends LightningElement {
5959
composeQuery() {
6060
try {
6161
if (this.parsedQuery) {
62-
const { fieldSubqueryParensOnOwnLine, whereClauseOperatorsIndented, fieldMaxLineLength } = this;
62+
const { fieldSubqueryParensOnOwnLine, newLineAfterKeywords, fieldMaxLineLength } = this;
6363
this.composedQuery = composeQuery(JSON.parse(JSON.stringify(this.parsedQuery)), {
6464
format: this.formatOutput,
65-
formatOptions: { fieldSubqueryParensOnOwnLine, whereClauseOperatorsIndented, fieldMaxLineLength }
65+
formatOptions: { fieldSubqueryParensOnOwnLine, newLineAfterKeywords, fieldMaxLineLength }
6666
});
6767
this.highlight();
6868
}
@@ -82,8 +82,8 @@ export default class QueryComposer extends LightningElement {
8282
this.fieldSubqueryParensOnOwnLine = value;
8383
break;
8484
}
85-
case 'whereClauseOperatorsIndented': {
86-
this.whereClauseOperatorsIndented = value;
85+
case 'newLineAfterKeywords': {
86+
this.newLineAfterKeywords = value;
8787
break;
8888
}
8989
case 'fieldMaxLineLength': {

src/composer/composer.ts

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -138,79 +138,86 @@ export class Compose {
138138
public parseQuery(query: Query | Subquery): string {
139139
const fieldData: FieldData = {
140140
fields: this.parseFields(query.fields).map(field => ({
141-
text: field,
142-
isSubquery: field.startsWith('('),
141+
text: field.text,
142+
typeOfClause: field.typeOfClause,
143+
isSubquery: field.text.startsWith('('),
143144
prefix: '',
144145
suffix: '',
145146
})),
146147
isSubquery: utils.isSubquery(query),
147148
lineBreaks: [],
148149
};
149150

150-
let output = `SELECT `;
151+
let output = this.formatter.formatClause('SELECT').trimStart();
151152

152153
// Format fields based on configuration
153154
this.formatter.formatFields(fieldData);
154155

156+
let fieldsOutput = '';
155157
fieldData.fields.forEach(field => {
156-
output += `${field.prefix}${field.text}${field.suffix}`;
158+
if (Array.isArray(field.typeOfClause)) {
159+
fieldsOutput += `${field.prefix}${this.formatter.formatTyeOfField(field.text, field.typeOfClause)}${field.suffix}`;
160+
} else {
161+
fieldsOutput += `${field.prefix}${field.text}${field.suffix}`;
162+
}
157163
});
164+
output += this.formatter.formatText(fieldsOutput);
158165

159166
output += this.formatter.formatClause('FROM');
160167

161168
if (utils.isSubquery(query)) {
162169
const sObjectPrefix = query.sObjectPrefix || [];
163170
sObjectPrefix.push(query.relationshipName);
164-
output += ` ${sObjectPrefix.join('.')}${utils.get(query.sObjectAlias, '', ' ')}`;
171+
output += this.formatter.formatText(`${sObjectPrefix.join('.')}${utils.get(query.sObjectAlias, '', ' ')}`);
165172
} else {
166-
output += ` ${query.sObject}${utils.get(query.sObjectAlias, '', ' ')}`;
173+
output += this.formatter.formatText(`${query.sObject}${utils.get(query.sObjectAlias, '', ' ')}`);
167174
}
168175
this.log(output);
169176

170177
if (query.usingScope) {
171178
output += this.formatter.formatClause('USING SCOPE');
172-
output += ` ${query.usingScope}`;
179+
output += this.formatter.formatText(query.usingScope);
173180
this.log(output);
174181
}
175182

176183
if (query.where) {
177184
output += this.formatter.formatClause('WHERE');
178-
output += ` ${this.parseWhereOrHavingClause(query.where)}`;
185+
output += this.formatter.formatText(this.parseWhereOrHavingClause(query.where));
179186
this.log(output);
180187
}
181188

182189
if (query.groupBy) {
183190
output += this.formatter.formatClause('GROUP BY');
184-
output += ` ${this.parseGroupByClause(query.groupBy)}`;
191+
output += this.formatter.formatText(this.parseGroupByClause(query.groupBy));
185192
this.log(output);
186193
if (query.groupBy.having) {
187194
output += this.formatter.formatClause('HAVING');
188-
output += ` ${this.parseWhereOrHavingClause(query.groupBy.having)}`;
195+
output += this.formatter.formatText(this.parseWhereOrHavingClause(query.groupBy.having));
189196
this.log(output);
190197
}
191198
}
192199

193200
if (query.orderBy && (!Array.isArray(query.orderBy) || query.orderBy.length > 0)) {
194201
output += this.formatter.formatClause('ORDER BY');
195-
output += ` ${this.parseOrderBy(query.orderBy)}`;
202+
output += this.formatter.formatText(this.parseOrderBy(query.orderBy));
196203
this.log(output);
197204
}
198205

199206
if (utils.isNumber(query.limit)) {
200207
output += this.formatter.formatClause('LIMIT');
201-
output += ` ${query.limit}`;
208+
output += this.formatter.formatText(`${query.limit}`);
202209
this.log(output);
203210
}
204211

205212
if (utils.isNumber(query.offset)) {
206213
output += this.formatter.formatClause('OFFSET');
207-
output += ` ${query.offset}`;
214+
output += this.formatter.formatText(`${query.offset}`);
208215
this.log(output);
209216
}
210217

211218
if (query.withDataCategory) {
212219
output += this.formatter.formatClause('WITH DATA CATEGORY');
213-
output += ` ${this.parseWithDataCategory(query.withDataCategory)}`;
220+
output += this.formatter.formatText(this.parseWithDataCategory(query.withDataCategory));
214221
this.log(output);
215222
}
216223

@@ -221,13 +228,13 @@ export class Compose {
221228

222229
if (query.for) {
223230
output += this.formatter.formatClause('FOR');
224-
output += ` ${query.for}`;
231+
output += this.formatter.formatText(query.for);
225232
this.log(output);
226233
}
227234

228235
if (query.update) {
229236
output += this.formatter.formatClause('UPDATE');
230-
output += ` ${query.update}`;
237+
output += this.formatter.formatText(query.update);
231238
this.log(output);
232239
}
233240

@@ -240,34 +247,44 @@ export class Compose {
240247
* @param fields
241248
* @returns fields
242249
*/
243-
public parseFields(fields: FieldType[]): string[] {
250+
public parseFields(fields: FieldType[]): { text: string; typeOfClause?: string[] }[] {
244251
return fields.map(field => {
252+
let text = '';
253+
let typeOfClause: string[];
254+
245255
const objPrefix = (field as any).objectPrefix ? `${(field as any).objectPrefix}.` : '';
246256
switch (field.type) {
247257
case 'Field': {
248-
return `${objPrefix}${field.field}${field.alias ? ` ${field.alias}` : ''}`;
258+
text = `${objPrefix}${field.field}${field.alias ? ` ${field.alias}` : ''}`;
259+
break;
249260
}
250261
case 'FieldFunctionExpression': {
251262
let params = '';
252263
if (field.parameters) {
253264
params = field.parameters
254-
.map(param => (utils.isString(param) ? param : this.parseFields([param as FieldFunctionExpression])))
265+
.map(param => (utils.isString(param) ? param : this.parseFields([param as FieldFunctionExpression]).map(param => param.text)))
255266
.join(', ');
256267
}
257-
return `${field.functionName}(${params})${field.alias ? ` ${field.alias}` : ''}`;
268+
text = `${field.functionName}(${params})${field.alias ? ` ${field.alias}` : ''}`;
269+
break;
258270
}
259271
case 'FieldRelationship': {
260-
return `${objPrefix}${field.relationships.join('.')}.${field.field}${utils.hasAlias(field) ? ` ${field.alias}` : ''}`;
272+
text = `${objPrefix}${field.relationships.join('.')}.${field.field}${utils.hasAlias(field) ? ` ${field.alias}` : ''}`;
273+
break;
261274
}
262275
case 'FieldSubquery': {
263-
return this.formatter.formatSubquery(this.parseQuery(field.subquery));
276+
text = this.formatter.formatSubquery(this.parseQuery(field.subquery));
277+
break;
264278
}
265279
case 'FieldTypeof': {
266-
return this.parseTypeOfField(field);
280+
typeOfClause = this.parseTypeOfField(field);
281+
text = typeOfClause.join(' ');
282+
break;
267283
}
268284
default:
269285
break;
270286
}
287+
return { text, typeOfClause };
271288
});
272289
}
273290

@@ -277,14 +294,11 @@ export class Compose {
277294
* @param typeOfField
278295
* @returns type of field
279296
*/
280-
public parseTypeOfField(typeOfField: FieldTypeOf): string {
281-
let output = `TYPEOF ${typeOfField.field} `;
282-
output += typeOfField.conditions
283-
.map(cond => {
284-
return `${cond.type} ${utils.get(cond.objectType, ' THEN ')}${cond.fieldList.join(', ')}`;
285-
})
286-
.join(' ');
287-
output += ` END`;
297+
public parseTypeOfField(typeOfField: FieldTypeOf): string[] {
298+
const output = [`TYPEOF ${typeOfField.field}`].concat(
299+
typeOfField.conditions.map(condition => this.formatter.formatTypeofFieldCondition(condition)),
300+
);
301+
output.push(`END`);
288302
return output;
289303
}
290304

@@ -296,29 +310,39 @@ export class Compose {
296310
* @param priorIsNegationOperator - do not set this when calling manually. Recursive call will set this to ensure proper formatting.
297311
* @returns where clause
298312
*/
299-
public parseWhereOrHavingClause(whereOrHaving: WhereClause | HavingClause): string {
313+
public parseWhereOrHavingClause(whereOrHaving: WhereClause | HavingClause, tabOffset = 0, priorConditionIsNegation = false): string {
300314
let output = '';
301315
const left = whereOrHaving.left;
316+
let trimPrecedingOutput = false;
302317
if (left) {
303-
output += utils.generateParens(left.openParen, '(');
318+
output += this.formatter.formatParens(left.openParen, '(', utils.isNegationCondition(left));
304319
if (!utils.isNegationCondition(left)) {
305-
output += utils.isValueFunctionCondition(left) ? this.parseFn(left.fn) : left.field;
306-
output += ` ${left.operator} `;
320+
tabOffset = tabOffset + (left.openParen || 0) - (left.closeParen || 0);
321+
if (priorConditionIsNegation) {
322+
tabOffset++;
323+
}
324+
let expression = '';
325+
expression += utils.isValueFunctionCondition(left) ? this.parseFn(left.fn) : left.field;
326+
expression += ` ${left.operator} `;
307327

308328
if (utils.isValueQueryCondition(left)) {
309-
output += this.formatter.formatSubquery(this.parseQuery(left.valueQuery), 1, true);
329+
expression += this.formatter.formatSubquery(this.parseQuery(left.valueQuery), 1, true);
310330
} else {
311-
output += utils.getAsArrayStr(utils.getWhereValue(left.value, left.literalType));
331+
expression += utils.getAsArrayStr(utils.getWhereValue(left.value, left.literalType));
312332
}
313-
output += utils.generateParens(left.closeParen, ')');
333+
output += this.formatter.formatWithIndent(expression);
334+
output += this.formatter.formatParens(left.closeParen, ')', priorConditionIsNegation);
314335
}
315336
}
316337
if (utils.isWhereOrHavingClauseWithRightCondition(whereOrHaving)) {
338+
const operator = utils.get(whereOrHaving.operator);
339+
trimPrecedingOutput = operator === 'NOT';
317340
const formattedData = this.formatter.formatWhereClauseOperators(
318-
utils.get(whereOrHaving.operator),
319-
this.parseWhereOrHavingClause(whereOrHaving.right),
341+
operator,
342+
this.parseWhereOrHavingClause(whereOrHaving.right, tabOffset, utils.isNegationCondition(left)),
343+
tabOffset,
320344
);
321-
return `${output}${formattedData}`.trim();
345+
return `${trimPrecedingOutput ? output.trimRight() : output}${formattedData}`.trim();
322346
} else {
323347
return output.trim();
324348
}

0 commit comments

Comments
 (0)