Skip to content

Commit 1bb5d23

Browse files
authored
Merge PR #242: Move AliasAS processing out of formatting phase
2 parents 483a4a5 + 6e4975d commit 1bb5d23

File tree

3 files changed

+89
-55
lines changed

3 files changed

+89
-55
lines changed

src/core/AliasAs.ts

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,66 @@
1-
import type { AliasMode } from 'src/types';
1+
import type { AliasMode, FormatOptions } from 'src/types';
2+
import AsTokenFactory from './AsTokenFactory';
23

3-
import { isCommand, isToken, type Token, TokenType } from './token';
4+
import { isCommand, isToken, type Token, TokenType, EOF_TOKEN, isReserved } from './token';
45

5-
export interface TokenStream {
6-
isWithinSelect(): boolean;
7-
getPreviousReservedToken(): Token;
8-
tokenLookBehind(n?: number): Token;
9-
tokenLookAhead(n?: number): Token;
10-
}
11-
12-
/** Decides addition and removal of AS tokens */
6+
/** Adds and removes AS tokens as configured by aliasAs option */
137
export default class AliasAs {
14-
constructor(private aliasAs: AliasMode, private formatter: TokenStream) {}
8+
private index = 0;
9+
private tokens: Token[] = [];
10+
private previousReservedToken: Token = EOF_TOKEN;
11+
private previousCommandToken: Token = EOF_TOKEN;
12+
private asTokenFactory: AsTokenFactory;
13+
private aliasAs: AliasMode;
14+
15+
constructor(cfg: FormatOptions, tokens: Token[]) {
16+
this.aliasAs = cfg.aliasAs;
17+
this.asTokenFactory = new AsTokenFactory(cfg.keywordCase, tokens);
18+
this.tokens = tokens;
19+
}
20+
21+
/** Returns tokens with AS tokens added/removed as needed */
22+
public process(): Token[] {
23+
const processedTokens: Token[] = [];
24+
25+
for (this.index = 0; this.index < this.tokens.length; this.index++) {
26+
const token = this.tokens[this.index];
27+
28+
if (isReserved(token)) {
29+
this.previousReservedToken = token;
30+
if (token.type === TokenType.RESERVED_COMMAND) {
31+
this.previousCommandToken = token;
32+
}
33+
}
34+
35+
if (isToken.AS(token)) {
36+
if (!this.shouldRemove()) {
37+
processedTokens.push(token);
38+
}
39+
} else if (
40+
token.type === TokenType.IDENT ||
41+
token.type === TokenType.NUMBER ||
42+
token.type === TokenType.STRING ||
43+
token.type === TokenType.VARIABLE
44+
) {
45+
if (this.shouldAddBefore(token)) {
46+
processedTokens.push(this.asTokenFactory.token());
47+
}
48+
49+
processedTokens.push(token);
50+
51+
if (this.shouldAddAfter()) {
52+
processedTokens.push(this.asTokenFactory.token());
53+
}
54+
} else {
55+
processedTokens.push(token);
56+
}
57+
}
58+
59+
return processedTokens;
60+
}
1561

1662
/** True when AS keyword should be added *before* current token */
17-
public shouldAddBefore(token: Token): boolean {
63+
private shouldAddBefore(token: Token): boolean {
1864
return this.isMissingTableAlias(token) || this.isMissingSelectColumnAlias(token);
1965
}
2066

@@ -31,7 +77,7 @@ export default class AliasAs {
3177
const nextToken = this.lookAhead();
3278
return (
3379
(this.aliasAs === 'always' || this.aliasAs === 'select') &&
34-
this.formatter.isWithinSelect() &&
80+
this.isWithinSelect() &&
3581
token.type === TokenType.IDENT &&
3682
(isToken.END(prevToken) ||
3783
((prevToken.type === TokenType.IDENT || prevToken.type === TokenType.NUMBER) &&
@@ -40,16 +86,16 @@ export default class AliasAs {
4086
}
4187

4288
/** True when AS keyword should be added *after* current token */
43-
public shouldAddAfter(): boolean {
89+
private shouldAddAfter(): boolean {
4490
return this.isEdgeCaseCTE() || this.isEdgeCaseCreateTable() || this.isMissingTypeCastAs();
4591
}
4692

4793
// checks for CAST(«expression» [AS] type)
4894
private isMissingTypeCastAs(): boolean {
4995
return (
5096
this.aliasAs === 'never' &&
51-
this.formatter.isWithinSelect() &&
52-
isToken.CAST(this.formatter.getPreviousReservedToken()) &&
97+
this.isWithinSelect() &&
98+
isToken.CAST(this.getPreviousReservedToken()) &&
5399
isToken.AS(this.lookAhead()) &&
54100
(this.lookAhead(2).type === TokenType.IDENT ||
55101
this.lookAhead(2).type === TokenType.RESERVED_KEYWORD) &&
@@ -79,23 +125,31 @@ export default class AliasAs {
79125
}
80126

81127
/* True when the current AS token should be discarded */
82-
public shouldRemove(): boolean {
128+
private shouldRemove(): boolean {
83129
return this.aliasAs === 'never' || (this.aliasAs === 'select' && this.isRemovableNonSelectAs());
84130
}
85131

86132
private isRemovableNonSelectAs(): boolean {
87133
return (
88134
this.lookBehind().value === ')' && // ) [AS] alias but not SELECT (a) [AS] alpha
89-
!this.formatter.isWithinSelect() &&
135+
!this.isWithinSelect() &&
90136
this.lookAhead().value !== '(' // skip WITH foo [AS] ( ...
91137
);
92138
}
93139

94-
private lookBehind(n?: number): Token {
95-
return this.formatter.tokenLookBehind(n);
140+
public getPreviousReservedToken(): Token {
141+
return this.previousReservedToken;
142+
}
143+
144+
public isWithinSelect(): boolean {
145+
return isToken.SELECT(this.previousCommandToken);
146+
}
147+
148+
private lookBehind(n = 1): Token {
149+
return this.lookAhead(-n);
96150
}
97151

98-
private lookAhead(n?: number): Token {
99-
return this.formatter.tokenLookAhead(n);
152+
private lookAhead(n = 1): Token {
153+
return this.tokens[this.index + n] || EOF_TOKEN;
100154
}
101155
}

src/core/Formatter.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import Params from './Params';
33
import Tokenizer from './Tokenizer';
44
import formatCommaPositions from './formatCommaPositions';
55
import formatAliasPositions from './formatAliasPositions';
6-
import AsTokenFactory from './AsTokenFactory';
76
import Parser, { type Statement } from './Parser';
87
import StatementFormatter from './StatementFormatter';
9-
import { type Token } from './token';
108
import { indentString } from './config';
9+
import AliasAs from './AliasAs';
1110

1211
/** Main formatter class that produces a final output string from list of tokens */
1312
export default class Formatter {
@@ -44,18 +43,17 @@ export default class Formatter {
4443
*/
4544
public format(query: string): string {
4645
const tokens = this.cachedTokenizer().tokenize(query);
47-
const ast = new Parser(tokens).parse();
48-
const formattedQuery = this.formatAst(ast, tokens);
46+
const processedTokens = new AliasAs(this.cfg, tokens).process();
47+
const ast = new Parser(processedTokens).parse();
48+
const formattedQuery = this.formatAst(ast);
4949
const finalQuery = this.postFormat(formattedQuery);
5050

5151
return finalQuery.trimEnd();
5252
}
5353

54-
private formatAst(statements: Statement[], tokens: Token[]): string {
55-
const asTokenFactory = new AsTokenFactory(this.cfg.keywordCase, tokens);
56-
54+
private formatAst(statements: Statement[]): string {
5755
return statements
58-
.map(stat => new StatementFormatter(this.cfg, this.params, asTokenFactory).format(stat))
56+
.map(stat => new StatementFormatter(this.cfg, this.params).format(stat))
5957
.join('\n'.repeat(this.cfg.linesBetweenQueries + 1));
6058
}
6159

src/core/StatementFormatter.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import InlineBlock from './InlineBlock';
66
import Params from './Params';
77
import { isReserved, isCommand, isToken, type Token, TokenType, EOF_TOKEN } from './token';
88
import toTabularFormat from './tabularStyle';
9-
import AliasAs from './AliasAs';
10-
import AsTokenFactory from './AsTokenFactory';
119
import { type Statement } from './Parser';
1210
import { indentString, isTabularStyle } from './config';
1311
import WhitespaceBuilder, { WS } from './WhitespaceBuilder';
@@ -17,9 +15,7 @@ export default class StatementFormatter {
1715
private cfg: FormatOptions;
1816
private indentation: Indentation;
1917
private inlineBlock: InlineBlock;
20-
private aliasAs: AliasAs;
2118
private params: Params;
22-
private asTokenFactory: AsTokenFactory;
2319
private query: WhitespaceBuilder;
2420

2521
private currentNewline = true;
@@ -28,13 +24,11 @@ export default class StatementFormatter {
2824
private tokens: Token[] = [];
2925
private index = -1;
3026

31-
constructor(cfg: FormatOptions, params: Params, asTokenFactory: AsTokenFactory) {
27+
constructor(cfg: FormatOptions, params: Params) {
3228
this.cfg = cfg;
3329
this.indentation = new Indentation(indentString(cfg));
3430
this.inlineBlock = new InlineBlock(this.cfg.expressionWidth);
35-
this.aliasAs = new AliasAs(this.cfg.aliasAs, this);
3631
this.params = params;
37-
this.asTokenFactory = asTokenFactory;
3832
this.query = new WhitespaceBuilder(this.indentation);
3933
}
4034

@@ -99,18 +93,10 @@ export default class StatementFormatter {
9993
}
10094

10195
/**
102-
* Formats word tokens + any potential AS tokens for aliases
96+
* Formats ident/string/number/variable tokens
10397
*/
10498
private formatWord(token: Token) {
105-
if (this.aliasAs.shouldAddBefore(token)) {
106-
this.query.add(this.show(this.asTokenFactory.token()), WS.SPACE);
107-
}
108-
10999
this.query.add(this.show(token), WS.SPACE);
110-
111-
if (this.aliasAs.shouldAddAfter()) {
112-
this.query.add(this.show(this.asTokenFactory.token()), WS.SPACE);
113-
}
114100
}
115101

116102
/**
@@ -230,13 +216,9 @@ export default class StatementFormatter {
230216
}
231217

232218
/**
233-
* Formats a Reserved Keyword onto query, skipping AS if disabled
219+
* Formats a Reserved Keyword onto query
234220
*/
235221
private formatKeyword(token: Token) {
236-
if (isToken.AS(token) && this.aliasAs.shouldRemove()) {
237-
return;
238-
}
239-
240222
this.query.add(this.show(token), WS.SPACE);
241223
}
242224

@@ -439,22 +421,22 @@ export default class StatementFormatter {
439421
}
440422

441423
/** Returns the latest encountered reserved keyword token */
442-
public getPreviousReservedToken(): Token {
424+
private getPreviousReservedToken(): Token {
443425
return this.previousReservedToken;
444426
}
445427

446428
/** True when currently within SELECT command */
447-
public isWithinSelect(): boolean {
429+
private isWithinSelect(): boolean {
448430
return isToken.SELECT(this.previousCommandToken);
449431
}
450432

451433
/** Fetches nth previous token from the token stream */
452-
public tokenLookBehind(n = 1): Token {
434+
private tokenLookBehind(n = 1): Token {
453435
return this.tokens[this.index - n] || EOF_TOKEN;
454436
}
455437

456438
/** Fetches nth next token from the token stream */
457-
public tokenLookAhead(n = 1): Token {
439+
private tokenLookAhead(n = 1): Token {
458440
return this.tokens[this.index + n] || EOF_TOKEN;
459441
}
460442
}

0 commit comments

Comments
 (0)