Skip to content

Commit dc33bac

Browse files
authored
Merge pull request #4 from JoelMiller74/update-code-coverage
feat: enhance test coverage with additional formatting cases
2 parents 18db3da + 689f756 commit dc33bac

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

src/formatter/engine.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,18 @@ export function formatTsql(text: string, { options, config, profile }: EngineCon
247247
const clauseMatcher = new RegExp(`^(${indentCentralClauses.join('|')})\\b`, 'i');
248248
out = out.split('\n').map((ln: string) => (clauseMatcher.test(ln) ? alignLine(ln) : ln)).join('\n');
249249
// Optionally align SELECT items if requested
250+
// Note: When central alignment targets 'SELECT', we first ensure items are on
251+
// separate lines (splitting by commas if they are on a single line), then apply
252+
// padding to each item so they visually align to the configured column. This
253+
// makes alignment predictable and avoids ambiguity when items start on one line.
250254
if (indentCentralClauses.map(c => c.toUpperCase()).includes('SELECT')) {
251255
out = out.replace(/SELECT\s+([\s\S]*?)\s+FROM/gi, (m, list) => {
252-
const lines = list.split(/\n+/);
256+
let lines = list.split(/\n+/).map((l: string) => l.trim());
257+
// If items are not already split by newline, split by commas and preserve trailing commas
258+
if (lines.length === 1) {
259+
const items = list.split(',').map((s: string) => s.trim());
260+
lines = items.map((i: string, idx: number) => (idx < items.length - 1 ? `${i},` : i));
261+
}
253262
const aligned = lines.map((l: string) => alignLine(l));
254263
return `SELECT\n${aligned.join('\n')} FROM`;
255264
});

src/test/formatter.vitest.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,119 @@ describe('Formatter Engine (Vitest)', () => {
9898
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineBeforeSemicolon: true, linesBetweenQueries: 2 }), profile: {} });
9999
expect(out.includes('\n;\n')).toBe(true);
100100
});
101+
102+
it('dense operators', () => {
103+
const t = 'WHERE a = 10 AND b > 5';
104+
const out = formatTsql(t, { options: {} as any, config: cfg({ denseOperators: true }), profile: {} });
105+
expect(out).toBe('WHERE a=10AND\nb>5');
106+
});
107+
108+
it('parenthesis spacing outside', () => {
109+
const t = 'COUNT(*)';
110+
const out = formatTsql(t, { options: {} as any, config: cfg({ parenthesisSpacing: 'outside' }), profile: {} });
111+
expect(out).toBe('COUNT( * ) ');
112+
});
113+
114+
it('join alignment left', () => {
115+
const t = 'SELECT a FROM t INNER JOIN x ON t.id=x.id';
116+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterFrom: true, joinAlignment: 'left' }), profile: {} });
117+
expect(/\nINNER JOIN /.test(out)).toBe(true);
118+
});
119+
120+
it('logical operator newline before', () => {
121+
const t = 'WHERE a = 1 AND b = 2';
122+
const out = formatTsql(t, { options: {} as any, config: cfg({ logicalOperatorNewline: 'before' }), profile: {} });
123+
expect(/\nAND /.test(out)).toBe(true);
124+
});
125+
126+
it('alias keyword enable for table aliases', () => {
127+
const t = 'SELECT a FROM dbo.Table t';
128+
const out = formatTsql(t, { options: {} as any, config: cfg({ aliasKeyword: 'enable' }), profile: {} });
129+
expect(out).toBe('SELECT\n a\nFROM dbo.TABLE AS t');
130+
});
131+
132+
it('columns comma placement ignore', () => {
133+
const t = 'SELECT a, b, c FROM t';
134+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterSelect: true, columnsCommaPlacement: 'ignore' }), profile: {} });
135+
expect(/SELECT\n\s+a\n\s+b\n\s+c\nFROM/.test(out)).toBe(true);
136+
});
137+
138+
it('expression width wrapping', () => {
139+
const t = 'SELECT verylongcolumnname, anotherverylongcolumnname FROM t';
140+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterSelect: true, expressionWidth: 20, safeWrapDelimiters: ['comma'] }), profile: {} });
141+
expect(out.includes('\n')).toBe(true);
142+
});
143+
144+
it('cte style inline', () => {
145+
const t = 'WITH c AS (SELECT * FROM t) SELECT * FROM c';
146+
const out = formatTsql(t, { options: {} as any, config: cfg({ cteStyle: 'inline' }), profile: {} });
147+
expect(out).toBe('WITH c AS(SELECT\n *\nFROM t) SELECT\n *\nFROM c');
148+
});
149+
150+
it('window function compact style', () => {
151+
const t = 'SELECT ROW_NUMBER() OVER(PARTITION BY a ORDER BY b) FROM t';
152+
const out = formatTsql(t, { options: {} as any, config: cfg({ windowFunctionStyle: 'compact' }), profile: {} });
153+
expect(/OVER\(PARTITION BY a ORDER BY b\)/.test(out)).toBe(true);
154+
});
155+
156+
it('align column definitions false', () => {
157+
const t = 'CREATE TABLE t (id INT, name VARCHAR(50))';
158+
const out = formatTsql(t, { options: {} as any, config: cfg({ alignColumnDefinitions: false }), profile: {} });
159+
expect(out).toBe('CREATE TABLE t(id INT, name VARCHAR(50))');
160+
});
161+
162+
it('bracket identifiers ignore', () => {
163+
const t = 'SELECT a FROM [dbo].[Table]';
164+
const out = formatTsql(t, { options: {} as any, config: cfg({ bracketIdentifiers: 'ignore' }), profile: {} });
165+
expect(out).toBe('SELECT\n a\nFROM [dbo].[TABLE]');
166+
});
167+
168+
it('tabulate alias', () => {
169+
const t = 'SELECT a AS alias, verylongcolumn AS longer FROM t';
170+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterSelect: true, tabulateAlias: true }), profile: {} });
171+
expect(out).toBe('SELECT\n a AS alias, verylongcolumn AS longer\nFROM t');
172+
});
173+
174+
it('expression width wrapping without delimiters', () => {
175+
const t = 'SELECT verylongcolumnnamethatexceeds FROM t';
176+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterSelect: true, expressionWidth: 20 }), profile: {} });
177+
expect(out.includes('\n')).toBe(true);
178+
});
179+
180+
it('alias keyword remove for table aliases', () => {
181+
const t = 'SELECT a FROM dbo.Table AS t';
182+
const out = formatTsql(t, { options: {} as any, config: cfg({ aliasKeyword: 'remove' }), profile: {} });
183+
expect(out).toBe('SELECT\n a\nFROM dbo.TABLE t');
184+
});
185+
186+
it('central indent alignment', () => {
187+
const t = 'SELECT a FROM t\nWHERE b = 1';
188+
const out = formatTsql(t, { options: {} as any, config: cfg({ indentStyle: 'central', indentStyleMode: 'enable', indentAlignColumn: 20, indentCentralClauses: ['WHERE'] }), profile: {} });
189+
expect(out.includes(' WHERE')).toBe(true);
190+
});
191+
192+
it('central indent alignment select', () => {
193+
const t = 'SELECT a, b FROM t';
194+
const out = formatTsql(t, { options: {} as any, config: cfg({ newlineAfterSelect: true, indentStyle: 'central', indentStyleMode: 'enable', indentAlignColumn: 10, indentCentralClauses: ['SELECT'] }), profile: {} });
195+
// Verify SELECT items are split and aligned consistently.
196+
expect(/SELECT\s*\n\s+a,\n\s+b\s+FROM/.test(out)).toBe(true);
197+
});
198+
199+
it('window function multiline style', () => {
200+
const t = 'SELECT ROW_NUMBER() OVER(PARTITION BY a ORDER BY b) FROM t';
201+
const out = formatTsql(t, { options: {} as any, config: cfg({ windowFunctionStyle: 'multiline' }), profile: {} });
202+
expect(/OVER\s*\(\s*\n\s*PARTITION BY/.test(out)).toBe(true);
203+
});
204+
205+
it('align column definitions true', () => {
206+
const t = 'CREATE TABLE t (id INT, name VARCHAR(50))';
207+
const out = formatTsql(t, { options: {} as any, config: cfg({ alignColumnDefinitions: true }), profile: {} });
208+
expect(out).toBe('CREATE TABLE t (\n id INT,\n name VARCHAR(50)\n))');
209+
});
210+
211+
it('bracket identifiers remove', () => {
212+
const t = 'SELECT [a] FROM [t]';
213+
const out = formatTsql(t, { options: {} as any, config: cfg({ bracketIdentifiers: 'remove' }), profile: {} });
214+
expect(out).toBe('SELECT\n a\nFROM t');
215+
});
101216
});

0 commit comments

Comments
 (0)