Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/languages/bigquery/bigquery.formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ function combineParameterizedTypes(tokens: Token[]) {

if ((isToken.ARRAY(token) || isToken.STRUCT(token)) && tokens[i + 1]?.text === '<') {
const endIndex = findClosingAngleBracketIndex(tokens, i + 1);
if (endIndex === -1) {
// Unbalanced angle brackets. There is no safe place to end the type,
// so leave the tokens as they are instead of swallowing the rest of
// the query into a single identifier.
processed.push(token);
continue;
}
const typeDefTokens = tokens.slice(i, endIndex + 1);
processed.push({
type: TokenType.IDENTIFIER,
Expand Down Expand Up @@ -265,5 +272,8 @@ function findClosingAngleBracketIndex(tokens: Token[], startIndex: number): numb
return i;
}
}
return tokens.length - 1;
// Brackets never balanced. Reporting "not found" lets the caller leave the
// tokens untouched; returning the last index used to make it merge the whole
// remaining token stream into one identifier.
return -1;
}
56 changes: 56 additions & 0 deletions test/bigquery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,60 @@ describe('BigQueryFormatter', () => {
expect(format(input, { linesBetweenQueries: 0 })).toBe(input);
});
});
describe('BigQuery parameterized types with comments', () => {
// A comment between ARRAY/STRUCT and "<" makes the outer type fail to
// combine, and the inner type is then combined on its own. When the angle
// brackets of that inner type never balance, the search for the closing
// bracket used to fall back to the last token, merging the whole rest of
// the query into a single identifier: the query after the type was
// silently destroyed. These tests lock down that the tokens are left
// alone instead.
const nestedTypeWithBlockComment = 'SELECT ARRAY /* c */ <ARRAY<INT64>>[1] FROM t;';
const nestedTypeWithLineComment = 'SELECT ARRAY -- c\n<ARRAY<INT64>>[1] FROM t;';

it('keeps the rest of the query after a nested type with a block comment', () => {
const result = format(nestedTypeWithBlockComment);
expect(result).toBe(dedent`
SELECT
ARRAY /* c */ < ARRAY < INT64 >> [1]
FROM
t;
`);
// The regression: FROM and t used to be merged into one identifier.
expect(result).toContain('FROM');
expect(result).not.toMatch(/FROMt/);
});

it('keeps the rest of the query after a nested type with a line comment', () => {
const result = format(nestedTypeWithLineComment);
expect(result).toBe(dedent`
SELECT
ARRAY -- c
< ARRAY < INT64 >> [1]
FROM
t;
`);
expect(result).not.toMatch(/FROMt/);
});

it('keeps a trailing alias after a nested type with a comment', () => {
const result = format('SELECT STRUCT /* c */ <a ARRAY<INT64>>(1) AS x FROM t;');
expect(result).toBe(dedent`
SELECT
STRUCT /* c */ < a ARRAY < INT64 >> (1) AS x
FROM
t;
`);
expect(result).not.toMatch(/ASx/);
});

it('keeps formatting nested types without comments unchanged', () => {
expect(format('SELECT ARRAY<ARRAY<INT64>>[1] FROM t;')).toBe(dedent`
SELECT
ARRAY<ARRAY<INT64>>[1]
FROM
t;
`);
});
});
});