Skip to content

Commit

Permalink
Include dialect name and help text to lexer error message
Browse files Browse the repository at this point in the history
  • Loading branch information
nene committed Nov 15, 2023
1 parent 49f611a commit c3efb63
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const createDialect = (options: DialectOptions): Dialect => {
};

const dialectFromOptions = (dialectOptions: DialectOptions): Dialect => ({
tokenizer: new Tokenizer(dialectOptions.tokenizerOptions),
tokenizer: new Tokenizer(dialectOptions.tokenizerOptions, dialectOptions.name),
formatOptions: processDialectFormatOptions(dialectOptions.formatOptions),
});

Expand Down
4 changes: 2 additions & 2 deletions src/lexer/Tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class Tokenizer {
private rulesBeforeParams: TokenRule[];
private rulesAfterParams: TokenRule[];

constructor(private cfg: TokenizerOptions) {
constructor(private cfg: TokenizerOptions, private dialectName: string) {
this.rulesBeforeParams = this.buildRulesBeforeParams(cfg);
this.rulesAfterParams = this.buildRulesAfterParams(cfg);
}
Expand All @@ -23,7 +23,7 @@ export default class Tokenizer {
...this.buildParamRules(this.cfg, paramTypesOverrides),
...this.rulesAfterParams,
];
const tokens = new TokenizerEngine(rules).tokenize(input);
const tokens = new TokenizerEngine(rules, this.dialectName).tokenize(input);
return this.cfg.postProcess ? this.cfg.postProcess(tokens) : tokens;
}

Expand Down
17 changes: 15 additions & 2 deletions src/lexer/TokenizerEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class TokenizerEngine {
private input = ''; // The input SQL string to process
private index = 0; // Current position in string

constructor(private rules: TokenRule[]) {}
constructor(private rules: TokenRule[], private dialectName: string) {}

/**
* Takes a SQL string and breaks it into tokens.
Expand Down Expand Up @@ -58,7 +58,20 @@ export default class TokenizerEngine {
private createParseError(): Error {
const text = this.input.slice(this.index, this.index + 10);
const { line, col } = lineColFromIndex(this.input, this.index);
return new Error(`Parse error: Unexpected "${text}" at line ${line} column ${col}`);
return new Error(
`Parse error: Unexpected "${text}" at line ${line} column ${col}.\n${this.dialectInfo()}`
);
}

private dialectInfo(): string {
if (this.dialectName === 'sql') {
return (
`This likely happens because you're using the default "sql" dialect.\n` +
`If possible, please select a more specific dialect (like sqlite, postgresql, etc).`
);
} else {
return `SQL dialect used: "${this.dialectName}".`;
}
}

private getWhitespace(): string | undefined {
Expand Down
24 changes: 20 additions & 4 deletions test/sqlFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@ describe('sqlFormatter', () => {
}).toThrow('Unsupported SQL dialect: blah');
});

it('throws error when encountering unsupported characters', () => {
expect(() => {
format('SELECT «weird-stuff»');
}).toThrow('Parse error: Unexpected "«weird-stu" at line 1 column 8');
describe('when encountering unsupported characters with default dialect', () => {
it('throws error suggesting a use of a more specific dialect', () => {
expect(() => {
format('SELECT «weird-stuff»');
}).toThrow(
`Parse error: Unexpected "«weird-stu" at line 1 column 8.\n` +
`This likely happens because you're using the default "sql" dialect.\n` +
`If possible, please select a more specific dialect (like sqlite, postgresql, etc).`
);
});
});

describe('when encountering unsupported characters with sqlite dialect', () => {
it('throws error including the name of the used dialect', () => {
expect(() => {
format('SELECT «weird-stuff»', { language: 'sqlite' });
}).toThrow(
`Parse error: Unexpected "«weird-stu" at line 1 column 8.\nSQL dialect used: "sqlite".`
);
});
});

it('throws error when encountering incorrect SQL grammar', () => {
Expand Down
27 changes: 15 additions & 12 deletions test/unit/Parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { createParser } from '../../src/parser/createParser.js';

describe('Parser', () => {
const parse = (sql: string) => {
const tokenizer = new Tokenizer({
reservedClauses: ['FROM', 'WHERE', 'LIMIT', 'CREATE TABLE'],
reservedSelect: ['SELECT'],
reservedSetOperations: ['UNION', 'UNION ALL'],
reservedJoins: ['JOIN'],
reservedFunctionNames: ['SQRT', 'CURRENT_TIME'],
reservedKeywords: ['BETWEEN', 'LIKE', 'ON', 'USING'],
operators: [':'],
extraParens: ['[]', '{}'],
stringTypes: ["''-qq"],
identTypes: ['""-qq'],
});
const tokenizer = new Tokenizer(
{
reservedClauses: ['FROM', 'WHERE', 'LIMIT', 'CREATE TABLE'],
reservedSelect: ['SELECT'],
reservedSetOperations: ['UNION', 'UNION ALL'],
reservedJoins: ['JOIN'],
reservedFunctionNames: ['SQRT', 'CURRENT_TIME'],
reservedKeywords: ['BETWEEN', 'LIKE', 'ON', 'USING'],
operators: [':'],
extraParens: ['[]', '{}'],
stringTypes: ["''-qq"],
identTypes: ['""-qq'],
},
'sql'
);

return createParser(tokenizer).parse(sql, {});
};
Expand Down
23 changes: 13 additions & 10 deletions test/unit/Tokenizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import Tokenizer from '../../src/lexer/Tokenizer.js';

describe('Tokenizer', () => {
const tokenize = (sql: string) =>
new Tokenizer({
reservedClauses: ['FROM', 'WHERE', 'LIMIT', 'CREATE TABLE'],
reservedSelect: ['SELECT'],
reservedSetOperations: ['UNION', 'UNION ALL'],
reservedJoins: ['JOIN'],
reservedFunctionNames: ['SQRT', 'CURRENT_TIME'],
reservedKeywords: ['BETWEEN', 'LIKE', 'ON', 'USING'],
stringTypes: ["''-qq"],
identTypes: ['""-qq'],
}).tokenize(sql, {});
new Tokenizer(
{
reservedClauses: ['FROM', 'WHERE', 'LIMIT', 'CREATE TABLE'],
reservedSelect: ['SELECT'],
reservedSetOperations: ['UNION', 'UNION ALL'],
reservedJoins: ['JOIN'],
reservedFunctionNames: ['SQRT', 'CURRENT_TIME'],
reservedKeywords: ['BETWEEN', 'LIKE', 'ON', 'USING'],
stringTypes: ["''-qq"],
identTypes: ['""-qq'],
},
'sql'
).tokenize(sql, {});

it('tokenizes whitespace to empty array', () => {
expect(tokenize(' \t\n \n\r ')).toEqual([]);
Expand Down

0 comments on commit c3efb63

Please sign in to comment.