Skip to content

Commit 8887d7e

Browse files
Merge pull request #2 from inferrinizzard/alias-as
Add options for AS in Alias
2 parents 474c69c + c02248c commit 8887d7e

File tree

6 files changed

+154
-13
lines changed

6 files changed

+154
-13
lines changed

src/core/Formatter.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@ import Indentation from './Indentation';
33
import InlineBlock from './InlineBlock';
44
import Params from './Params';
55
import { trimSpacesEnd } from '../utils';
6-
import { isAnd, isBetween, isLimit, Token } from './token';
6+
import {
7+
isAnd,
8+
isAs,
9+
isBetween,
10+
isEnd,
11+
isLimit,
12+
isReserved,
13+
isSelect,
14+
isTopLevel,
15+
Token,
16+
} from './token';
717
import Tokenizer from './Tokenizer';
818
import { FormatOptions } from '../sqlFormatter';
919

@@ -17,6 +27,7 @@ export default class Formatter {
1727
params: Params;
1828

1929
previousReservedToken: Token;
30+
withinSelect: boolean;
2031
tokens: Token[];
2132
index: number;
2233

@@ -42,6 +53,7 @@ export default class Formatter {
4253
this.params = new Params(this.cfg.params);
4354

4455
this.previousReservedToken = {} as Token;
56+
this.withinSelect = false;
4557
this.tokens = [];
4658
this.index = 0;
4759
}
@@ -92,15 +104,20 @@ export default class Formatter {
92104
} else if (token.type === tokenTypes.RESERVED_TOP_LEVEL) {
93105
formattedQuery = this.formatTopLevelReservedWord(token, formattedQuery);
94106
this.previousReservedToken = token;
107+
this.withinSelect = isSelect(token);
95108
} else if (token.type === tokenTypes.RESERVED_TOP_LEVEL_NO_INDENT) {
96109
formattedQuery = this.formatTopLevelReservedWordNoIndent(token, formattedQuery);
97110
this.previousReservedToken = token;
111+
this.withinSelect = false;
98112
} else if (token.type === tokenTypes.RESERVED_NEWLINE) {
99113
formattedQuery = this.formatNewlineReservedWord(token, formattedQuery);
100114
this.previousReservedToken = token;
101115
} else if (token.type === tokenTypes.RESERVED) {
102-
formattedQuery = this.formatWithSpaces(token, formattedQuery);
103-
this.previousReservedToken = token;
116+
if (!(isAs(token) && this.cfg.aliasAs === 'never')) {
117+
// do not format if skipping AS
118+
formattedQuery = this.formatWithSpaces(token, formattedQuery);
119+
this.previousReservedToken = token;
120+
}
104121
} else if (token.type === tokenTypes.OPEN_PAREN) {
105122
formattedQuery = this.formatOpeningParentheses(token, formattedQuery);
106123
} else if (token.type === tokenTypes.CLOSE_PAREN) {
@@ -126,12 +143,33 @@ export default class Formatter {
126143
) {
127144
formattedQuery = this.formatWithSpaces(token, formattedQuery, 'after');
128145
} else {
146+
if (this.cfg.aliasAs !== 'never')
147+
formattedQuery = this.formatAliases(token, formattedQuery);
129148
formattedQuery = this.formatWithSpaces(token, formattedQuery);
130149
}
131150
});
132151
return formattedQuery;
133152
}
134153

154+
formatAliases(token: Token, query: string) {
155+
const prevToken = this.tokenLookBehind();
156+
const nextToken = this.tokenLookAhead();
157+
const asToken = { type: tokenTypes.RESERVED, value: this.cfg.uppercase ? 'AS' : 'as' };
158+
159+
const missingTableAlias = // if table alias is missing and alias is always
160+
this.cfg.aliasAs === 'always' && token.type === tokenTypes.WORD && prevToken?.value === ')';
161+
162+
const missingSelectColumnAlias = // if select column alias is missing and alias is not never
163+
this.withinSelect &&
164+
token.type === tokenTypes.WORD &&
165+
(isEnd(prevToken) || // isAs(prevToken) ||
166+
(prevToken?.type === tokenTypes.WORD &&
167+
(nextToken?.value === ',' || isTopLevel(nextToken))));
168+
169+
if (missingTableAlias || missingSelectColumnAlias) return this.formatWithSpaces(asToken, query);
170+
return query;
171+
}
172+
135173
formatLineComment(token: Token, query: string) {
136174
return this.addNewline(query + this.show(token));
137175
}
@@ -243,17 +281,14 @@ export default class Formatter {
243281
}
244282

245283
// Converts token to string (uppercasing it if needed)
246-
show({ type, value }: Token) {
284+
show(token: Token) {
247285
if (
248-
type === tokenTypes.RESERVED ||
249-
type === tokenTypes.RESERVED_TOP_LEVEL ||
250-
type === tokenTypes.RESERVED_TOP_LEVEL_NO_INDENT ||
251-
type === tokenTypes.RESERVED_NEWLINE ||
252-
type === tokenTypes.OPEN_PAREN ||
253-
type === tokenTypes.CLOSE_PAREN
286+
isReserved(token) ||
287+
token.type === tokenTypes.OPEN_PAREN ||
288+
token.type === tokenTypes.CLOSE_PAREN
254289
) {
255-
return this.cfg.uppercase ? value.toUpperCase() : value.toLowerCase();
256-
} else return value;
290+
return this.cfg.uppercase ? token.value.toUpperCase() : token.value.toLowerCase();
291+
} else return token.value;
257292
}
258293

259294
addNewline(query: string) {

src/core/token.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface Token {
1111
const isToken = (type: TokenType, regex: RegExp) => (token: Token) =>
1212
token?.type === type && regex.test(token?.value);
1313

14+
export const isAs = isToken(tokenTypes.RESERVED, /^AS$/iu);
15+
1416
export const isAnd = isToken(tokenTypes.RESERVED_NEWLINE, /^AND$/iu);
1517

1618
export const isBetween = isToken(tokenTypes.RESERVED, /^BETWEEN$/iu);
@@ -24,3 +26,17 @@ export const isBy = isToken(tokenTypes.RESERVED, /^BY$/iu);
2426
export const isWindow = isToken(tokenTypes.RESERVED_TOP_LEVEL, /^WINDOW$/iu);
2527

2628
export const isEnd = isToken(tokenTypes.CLOSE_PAREN, /^END$/iu);
29+
30+
export const isTopLevel = (token: Token) =>
31+
token &&
32+
(token.type === tokenTypes.RESERVED_TOP_LEVEL ||
33+
token.type === tokenTypes.RESERVED_TOP_LEVEL_NO_INDENT);
34+
35+
export const isReserved = (token: Token) =>
36+
token &&
37+
(token.type === tokenTypes.RESERVED ||
38+
token.type === tokenTypes.RESERVED_NEWLINE ||
39+
token.type === tokenTypes.RESERVED_TOP_LEVEL ||
40+
token.type === tokenTypes.RESERVED_TOP_LEVEL_NO_INDENT);
41+
42+
export const isSelect = isToken(tokenTypes.RESERVED_TOP_LEVEL, /^SELECT$/iu);

src/languages/RedshiftFormatter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const reservedWords = [
5353
'DESC',
5454
'DISABLE',
5555
'DISTINCT',
56+
'DROP',
5657
'DO',
5758
'ELSE',
5859
'EMPTYASNULL',
@@ -107,7 +108,9 @@ const reservedWords = [
107108
'NATURAL',
108109
'NEW',
109110
'NOLOAD',
111+
'NOT',
110112
'NULL AS',
113+
'NULL',
111114
'NULLS',
112115
'OFF',
113116
'OFFLINE',

src/sqlFormatter.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface FormatOptions {
3636
indent: string;
3737
uppercase: boolean;
3838
newline: NewlineOptions;
39+
aliasAs: 'always' | 'never' | 'select';
3940
lineWidth: number;
4041
linesBetweenQueries: number;
4142
}
@@ -46,10 +47,11 @@ export interface FormatOptions {
4647
* @param {FormatOptions} cfg
4748
* @param {String} cfg.language Query language, default is Standard SQL
4849
* @param {String} cfg.indent Characters used for indentation, default is " " (2 spaces)
50+
* @param {Boolean} cfg.uppercase Converts keywords to uppercase
4951
* @param {NewlineOptions} cfg.newline Determines when to break words onto a newline;
5052
* @param {String} cfg.newline.mode always | never | lineWidth (break only when > line width) | itemCount (break when > itemCount) | hybrid (lineWidth OR itemCount)
5153
* @param {Integer} cfg.newline.itemCount Used when mode is itemCount or hybrid, must be >=0
52-
* @param {Boolean} cfg.uppercase Converts keywords to uppercase
54+
* @param {String} cfg.aliasAs Whether to use AS in column aliases in only SELECT clause, both SELECT and table aliases, or never
5355
* @param {Integer} cfg.linesBetweenQueries How many line breaks between queries
5456
* @param {ParamItems} cfg.params Collection of params for placeholder replacement
5557
* @return {String}
@@ -81,6 +83,7 @@ export const format = (query: string, cfg: Partial<FormatOptions> = {}): string
8183
uppercase: true,
8284
linesBetweenQueries: 1,
8385
newline: { mode: 'always' },
86+
aliasAs: 'select',
8487
lineWidth: 50,
8588
};
8689
cfg = { ...defaultOptions, ...cfg };

test/behavesLikeSqlFormatter.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import dedent from 'dedent-js';
22
import supportsComments from './features/comments';
33
import supportsConfigOptions from './features/configOptions';
44
import supportsOperators from './features/operators';
5+
import supportsAliases from './features/alias';
56

67
/**
78
* Core tests for all SQL formatters
89
* @param {Function} format
910
*/
1011
export default function behavesLikeSqlFormatter(format) {
12+
supportsAliases(format);
1113
supportsComments(format);
1214
supportsConfigOptions(format);
1315
supportsOperators(['=', '+', '-', '*', '/', '<>', '>', '<', '>=', '<=']);

test/features/alias.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import dedent from 'dedent-js';
2+
3+
/**
4+
* Tests support for alias options
5+
* @param {Function} format
6+
*/
7+
export default function supportsAliases(format) {
8+
it('supports always mode', () => {
9+
expect(
10+
format('SELECT a a_column, b AS bColumn FROM ( SELECT * FROM x ) y WHERE z;', {
11+
aliasAs: 'always',
12+
})
13+
).toBe(
14+
dedent(`
15+
SELECT
16+
a AS a_column,
17+
b AS bColumn
18+
FROM
19+
(
20+
SELECT
21+
*
22+
FROM
23+
x
24+
) AS y
25+
WHERE
26+
z;
27+
`)
28+
);
29+
});
30+
31+
it('supports never mode', () => {
32+
expect(
33+
format('SELECT a a_column, b AS bColumn FROM ( SELECT * FROM x ) y WHERE z;', {
34+
aliasAs: 'never',
35+
})
36+
).toBe(
37+
dedent(`
38+
SELECT
39+
a a_column,
40+
b bColumn
41+
FROM
42+
(
43+
SELECT
44+
*
45+
FROM
46+
x
47+
) y
48+
WHERE
49+
z;
50+
`)
51+
);
52+
});
53+
54+
it('supports select only mode', () => {
55+
expect(
56+
format('SELECT a a_column, b AS bColumn FROM ( SELECT * FROM x ) y WHERE z;', {
57+
aliasAs: 'select',
58+
})
59+
).toBe(
60+
dedent(`
61+
SELECT
62+
a AS a_column,
63+
b AS bColumn
64+
FROM
65+
(
66+
SELECT
67+
*
68+
FROM
69+
x
70+
) y
71+
WHERE
72+
z;
73+
`)
74+
);
75+
});
76+
77+
it('does not format non select clauses', () => {
78+
expect(format('CREATE TABLE items (a INT PRIMARY KEY, b TEXT);')).toBe(
79+
'CREATE TABLE items (a INT PRIMARY KEY, b TEXT);'
80+
);
81+
});
82+
}

0 commit comments

Comments
 (0)