Skip to content

Commit c3ea743

Browse files
committed
Implement disable/enable comments
Fixes #703
1 parent b8455d7 commit c3ea743

File tree

8 files changed

+124
-4
lines changed

8 files changed

+124
-4
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,34 @@ format('SELECT * FROM tbl', {
6161
});
6262
```
6363

64+
### Disabling the formatter
65+
66+
You can disable the formatter for a section of SQL by surrounding it with disable/enable comments:
67+
68+
```sql
69+
/* sql-formatter-disable */
70+
SELECT * FROM tbl1;
71+
/* sql-formatter-enable */
72+
SELECT * FROM tbl2;
73+
```
74+
75+
which produces:
76+
77+
```sql
78+
/* sql-formatter-disable */
79+
SELECT * FROM tbl1;
80+
/* sql-formatter-enable */
81+
SELECT
82+
*
83+
FROM
84+
tbl2;
85+
```
86+
87+
The formatter doesn't even parse the code between these comments.
88+
So in case there's some SQL that happens to crash SQL Formatter,
89+
you can at comment the culprit out (at least until the issue gets
90+
fixed in SQL Formatter).
91+
6492
### Placeholders replacement
6593

6694
In addition to formatting, this library can also perform placeholder replacement in prepared SQL statements:

src/formatter/ExpressionFormatter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
CaseElseNode,
3131
DataTypeNode,
3232
ParameterizedDataTypeNode,
33+
DisableCommentNode,
3334
} from '../parser/ast.js';
3435

3536
import Layout, { WS } from './Layout.js';
@@ -134,6 +135,8 @@ export default class ExpressionFormatter {
134135
return this.formatLineComment(node);
135136
case NodeType.block_comment:
136137
return this.formatBlockComment(node);
138+
case NodeType.disable_comment:
139+
return this.formatBlockComment(node);
137140
case NodeType.data_type:
138141
return this.formatDataType(node);
139142
case NodeType.keyword:
@@ -367,8 +370,8 @@ export default class ExpressionFormatter {
367370
}
368371
}
369372

370-
private formatBlockComment(node: BlockCommentNode) {
371-
if (this.isMultilineBlockComment(node)) {
373+
private formatBlockComment(node: BlockCommentNode | DisableCommentNode) {
374+
if (node.type === NodeType.block_comment && this.isMultilineBlockComment(node)) {
372375
this.splitBlockComment(node.text).forEach(line => {
373376
this.layout.add(WS.NEWLINE, WS.INDENT, line);
374377
});

src/lexer/Tokenizer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export default class Tokenizer {
3131
// the Tokenizer config options specified for each SQL dialect
3232
private buildRulesBeforeParams(cfg: TokenizerOptions): TokenRule[] {
3333
return this.validRules([
34+
{
35+
type: TokenType.BLOCK_COMMENT,
36+
regex:
37+
/(\/\* *sql-formatter-disable *\*\/[\s\S]*?(?:\/\* *sql-formatter-enable *\*\/|$))/uy,
38+
},
3439
{
3540
type: TokenType.BLOCK_COMMENT,
3641
regex: cfg.nestedBlockComments ? new NestedComment() : /(\/\*[^]*?\*\/)/uy,

src/lexer/token.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export enum TokenType {
3333
CLOSE_PAREN = 'CLOSE_PAREN',
3434
LINE_COMMENT = 'LINE_COMMENT',
3535
BLOCK_COMMENT = 'BLOCK_COMMENT',
36+
// Text between /* sql-formatter-disable */ and /* sql-formatter-enable */
37+
DISABLE_COMMENT = 'DISABLE_COMMENT',
3638
NUMBER = 'NUMBER',
3739
NAMED_PARAMETER = 'NAMED_PARAMETER',
3840
QUOTED_PARAMETER = 'QUOTED_PARAMETER',

src/parser/ast.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export enum NodeType {
2424
comma = 'comma',
2525
line_comment = 'line_comment',
2626
block_comment = 'block_comment',
27+
disable_comment = 'disable_comment',
2728
}
2829

2930
interface BaseNode {
@@ -178,7 +179,13 @@ export interface BlockCommentNode extends BaseNode {
178179
precedingWhitespace: string;
179180
}
180181

181-
export type CommentNode = LineCommentNode | BlockCommentNode;
182+
export interface DisableCommentNode extends BaseNode {
183+
type: NodeType.disable_comment;
184+
text: string;
185+
precedingWhitespace: string;
186+
}
187+
188+
export type CommentNode = LineCommentNode | BlockCommentNode | DisableCommentNode;
182189

183190
export type AstNode =
184191
| ClauseNode
@@ -202,4 +209,5 @@ export type AstNode =
202209
| OperatorNode
203210
| CommaNode
204211
| LineCommentNode
205-
| BlockCommentNode;
212+
| BlockCommentNode
213+
| DisableCommentNode;

src/parser/grammar.ne

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,10 @@ comment -> %BLOCK_COMMENT {%
380380
precedingWhitespace: token.precedingWhitespace,
381381
})
382382
%}
383+
comment -> %DISABLE_COMMENT {%
384+
([token]) => ({
385+
type: NodeType.disable_comment,
386+
text: token.text,
387+
precedingWhitespace: token.precedingWhitespace,
388+
})
389+
%}

test/behavesLikeSqlFormatter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import supportsLogicalOperatorNewline from './options/logicalOperatorNewline.js'
1717
import supportsParamTypes from './options/paramTypes.js';
1818
import supportsWindowFunctions from './features/windowFunctions.js';
1919
import supportsFunctionCase from './options/functionCase.js';
20+
import supportsDisableComment from './features/disableComment.js';
2021

2122
/**
2223
* Core tests for all SQL formatters
2324
*/
2425
export default function behavesLikeSqlFormatter(format: FormatFn) {
26+
supportsDisableComment(format);
2527
supportsCase(format);
2628
supportsNumbers(format);
2729
supportsWith(format);

test/features/disableComment.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import dedent from 'dedent-js';
2+
3+
import { FormatFn } from '../../src/sqlFormatter.js';
4+
5+
export default function supportsDisableComment(format: FormatFn) {
6+
it('does not format text between /* sql-formatter-disable */ and /* sql-formatter-enable */', () => {
7+
const result = format(dedent`
8+
SELECT foo FROM bar;
9+
/* sql-formatter-disable */
10+
SELECT foo FROM bar;
11+
/* sql-formatter-enable */
12+
SELECT foo FROM bar;
13+
`);
14+
15+
expect(result).toBe(dedent`
16+
SELECT
17+
foo
18+
FROM
19+
bar;
20+
21+
/* sql-formatter-disable */
22+
SELECT foo FROM bar;
23+
/* sql-formatter-enable */
24+
SELECT
25+
foo
26+
FROM
27+
bar;
28+
`);
29+
});
30+
31+
it('does not format text after /* sql-formatter-disable */ until end of file', () => {
32+
const result = format(dedent`
33+
SELECT foo FROM bar;
34+
/* sql-formatter-disable */
35+
SELECT foo FROM bar;
36+
37+
SELECT foo FROM bar;
38+
`);
39+
40+
expect(result).toBe(dedent`
41+
SELECT
42+
foo
43+
FROM
44+
bar;
45+
46+
/* sql-formatter-disable */
47+
SELECT foo FROM bar;
48+
49+
SELECT foo FROM bar;
50+
`);
51+
});
52+
53+
it('does not parse code between disable/enable comments', () => {
54+
const result = format(dedent`
55+
SELECT /*sql-formatter-disable*/ ?!{}[] /*sql-formatter-enable*/ FROM bar;
56+
`);
57+
58+
expect(result).toBe(dedent`
59+
SELECT
60+
/*sql-formatter-disable*/ ?!{}[] /*sql-formatter-enable*/
61+
FROM
62+
bar;
63+
`);
64+
});
65+
}

0 commit comments

Comments
 (0)