Skip to content

Commit 032c72d

Browse files
committed
Implement sqlFinalSemicolon option
Refs #50
1 parent e2dd95d commit 032c72d

File tree

5 files changed

+91
-18
lines changed

5 files changed

+91
-18
lines changed

src/options.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface SqlPluginOptions {
55
sqlKeywordCase: "preserve" | "upper" | "lower";
66
sqlParamTypes: NonNullable<CstParserOptions["paramTypes"]>;
77
sqlCanonicalSyntax: boolean;
8+
sqlFinalSemicolon: boolean;
89
sqlAcceptUnsupportedGrammar: boolean;
910
}
1011

@@ -52,6 +53,13 @@ export const options: SupportOptions = {
5253
"Enforces one true style of SQL syntax (adds and removes keywords)",
5354
// Since 0.11.0
5455
},
56+
sqlFinalSemicolon: {
57+
type: "boolean",
58+
category: "SQL",
59+
default: true,
60+
description: "Enforces a semicolon at the end of last SQL statement",
61+
// Since 0.13.0
62+
},
5563
sqlAcceptUnsupportedGrammar: {
5664
type: "boolean",
5765
category: "SQL",

src/syntax/program.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { AllPrettierOptions } from "src/options";
77
export const programMap: CstToDocMap<Program> = {
88
program: (print, node, path, options) =>
99
print("statements").map((doc, i) =>
10-
printStatement(doc, i, node.statements, options),
10+
i === node.statements.length - 1
11+
? printFinalStatement(doc, i, node.statements, options)
12+
: printStatement(doc, i, node.statements, options),
1113
),
1214
};
1315

@@ -31,3 +33,23 @@ const printStatement = (
3133
return [";", hardline, doc];
3234
}
3335
};
36+
37+
const printFinalStatement = (
38+
doc: Doc,
39+
i: number,
40+
statements: Node[],
41+
options: AllPrettierOptions<Program>,
42+
): Doc => {
43+
const stmtDoc = printStatement(doc, i, statements, options);
44+
if (statements[i].type === "empty") {
45+
// When the final statement is an empty statement,
46+
// this means we have inserted a semicolon and newline before it.
47+
// So nothing else is needed.
48+
return stmtDoc;
49+
} else {
50+
// If the last statement is not empty,
51+
// this means sqlFinalSemicolon option is false,
52+
// so we need to add an empty line after it.
53+
return [stmtDoc, hardline];
54+
}
55+
};

src/transform/transformCst.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ export const transformCst: TransformFn = (
1717
// so we don't need to worry about comments interfering with other transforms
1818
moveCommentsToRoot,
1919
stripTrailingCommas,
20-
addFinalSemicolon,
2120
];
21+
if (options.sqlFinalSemicolon) {
22+
transforms.push(addFinalSemicolon);
23+
}
2224
if (options.sqlCanonicalSyntax) {
2325
transforms.push(processAliasAs, canonicOperators, canonicKeywords);
2426
}

test/options/finalSemicolon.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import dedent from "dedent-js";
2+
import { rawPretty } from "../test_utils";
3+
4+
// In these tests we use rawPretty()
5+
// to also test that formatted file always ends with final newline.
6+
describe("sqlFinalSemicolon option", () => {
7+
describe("with sqlFinalSemicolon enabled (the default)", () => {
8+
it(`adds semicolon to statement without a semicolon`, async () => {
9+
expect(await rawPretty(`SELECT 1`)).toBe(dedent`
10+
SELECT 1;
11+
12+
`);
13+
});
14+
15+
it(`ensures semicolon after last statement`, async () => {
16+
expect(await rawPretty(`SELECT 1; SELECT 2; SELECT 3`)).toBe(dedent`
17+
SELECT 1;
18+
SELECT 2;
19+
SELECT 3;
20+
21+
`);
22+
});
23+
});
24+
25+
describe("with sqlFinalSemicolon disabled", () => {
26+
it(`does not add semicolon to statement without a semicolon`, async () => {
27+
expect(await rawPretty(`SELECT 1`, { sqlFinalSemicolon: false })).toBe(
28+
dedent`
29+
SELECT 1
30+
31+
`,
32+
);
33+
});
34+
35+
it(`adds no semicolon after last statement`, async () => {
36+
expect(
37+
await rawPretty(`SELECT 1; SELECT 2; SELECT 3`, {
38+
sqlFinalSemicolon: false,
39+
}),
40+
).toBe(dedent`
41+
SELECT 1;
42+
SELECT 2;
43+
SELECT 3
44+
45+
`);
46+
});
47+
48+
it(`does not remove an existing trailing semicolon`, async () => {
49+
expect(await rawPretty(`SELECT 1;`, { sqlFinalSemicolon: false })).toBe(
50+
dedent`
51+
SELECT 1;
52+
53+
`,
54+
);
55+
});
56+
});
57+
});

test/statement.test.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ import { rawPretty, rawTest } from "./test_utils";
44
// In these tests we use rawPretty()
55
// to also test that formatted file always ends with final newline.
66
describe("statement", () => {
7-
it(`adds semicolon to statement without a semicolon`, async () => {
8-
expect(await rawPretty(`SELECT 1`)).toBe(dedent`
9-
SELECT 1;
10-
11-
`);
12-
});
13-
147
it(`formats statement ending with semicolon`, async () => {
158
expect(await rawPretty(`SELECT 1;`)).toBe(dedent`
169
SELECT 1;
@@ -27,15 +20,6 @@ describe("statement", () => {
2720
`);
2821
});
2922

30-
it(`ensures semicolon after last statement`, async () => {
31-
expect(await rawPretty(`SELECT 1; SELECT 2; SELECT 3`)).toBe(dedent`
32-
SELECT 1;
33-
SELECT 2;
34-
SELECT 3;
35-
36-
`);
37-
});
38-
3923
it(`preserves empty line between statements`, async () => {
4024
rawTest(dedent`
4125
SELECT 1;

0 commit comments

Comments
 (0)