Skip to content

Single dialect import #511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 4, 2022
Merged
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
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
{ "avoidEscape": true, "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": "error",
"@typescript-eslint/no-non-null-assertion": "error"
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/consistent-type-exports": "error"
},
"settings": {
"import/resolver": {
Expand Down
16 changes: 16 additions & 0 deletions src/allDialects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export { bigquery } from './languages/bigquery/bigquery.formatter.js';
export { db2 } from './languages/db2/db2.formatter.js';
export { hive } from './languages/hive/hive.formatter.js';
export { mariadb } from './languages/mariadb/mariadb.formatter.js';
export { mysql } from './languages/mysql/mysql.formatter.js';
export { n1ql } from './languages/n1ql/n1ql.formatter.js';
export { plsql } from './languages/plsql/plsql.formatter.js';
export { postgresql } from './languages/postgresql/postgresql.formatter.js';
export { redshift } from './languages/redshift/redshift.formatter.js';
export { spark } from './languages/spark/spark.formatter.js';
export { sqlite } from './languages/sqlite/sqlite.formatter.js';
export { sql } from './languages/sql/sql.formatter.js';
export { trino } from './languages/trino/trino.formatter.js';
export { transactsql } from './languages/transactsql/transactsql.formatter.js';
export { singlestoredb } from './languages/singlestoredb/singlestoredb.formatter.js';
export { snowflake } from './languages/snowflake/snowflake.formatter.js';
17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export * from './sqlFormatter.js';
export { supportedDialects, format, formatDialect } from './sqlFormatter.js';
export { expandPhrases } from './expandPhrases.js';

// Intentionally use "export *" syntax here to make sure when adding a new SQL dialect
// we wouldn't forget to expose it in our public API.
export * from './allDialects.js';

// NB! To re-export types the "export type" syntax is required by webpack.
// Otherwise webpack build will fail.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's an eslint-typescript rule here we can leverage, use-consistent-type-exports

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make a PR to update eslint rules soon

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. I added the rule on my own. Seems to work just as needed.

export type {
SqlLanguage,
FormatOptionsWithLanguage,
FormatOptionsWithDialect,
} from './sqlFormatter.js';
export type {
IndentStyle,
KeywordCase,
Expand All @@ -7,4 +20,4 @@ export type {
FormatOptions,
} from './FormatOptions.js';
export type { DialectOptions } from './dialect.js';
export { expandPhrases } from './expandPhrases.js';
export type { ConfigError } from './validateConfig.js';
129 changes: 38 additions & 91 deletions src/sqlFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,26 @@
import { bigquery } from './languages/bigquery/bigquery.formatter.js';
import { db2 } from './languages/db2/db2.formatter.js';
import { hive } from './languages/hive/hive.formatter.js';
import { mariadb } from './languages/mariadb/mariadb.formatter.js';
import { mysql } from './languages/mysql/mysql.formatter.js';
import { n1ql } from './languages/n1ql/n1ql.formatter.js';
import { plsql } from './languages/plsql/plsql.formatter.js';
import { postgresql } from './languages/postgresql/postgresql.formatter.js';
import { redshift } from './languages/redshift/redshift.formatter.js';
import { spark } from './languages/spark/spark.formatter.js';
import { sqlite } from './languages/sqlite/sqlite.formatter.js';
import { sql } from './languages/sql/sql.formatter.js';
import { trino } from './languages/trino/trino.formatter.js';
import { transactsql } from './languages/transactsql/transactsql.formatter.js';
import { singlestoredb } from './languages/singlestoredb/singlestoredb.formatter.js';
import { snowflake } from './languages/snowflake/snowflake.formatter.js';
import * as allDialects from './allDialects.js';

import { FormatOptions } from './FormatOptions.js';
import { ParamItems } from './formatter/Params.js';
import { createDialect, DialectOptions } from './dialect.js';
import Formatter from './formatter/Formatter.js';
import { ConfigError, validateConfig } from './validateConfig.js';

export const formatters = {
bigquery,
db2,
hive,
mariadb,
mysql,
n1ql,
plsql,
postgresql,
redshift,
singlestoredb,
snowflake,
spark,
sql,
sqlite,
transactsql,
trino,
tsql: transactsql, // alias for transactsql
const formatters = {
...allDialects,
tsql: allDialects.transactsql, // alias for transactsql
};
export type SqlLanguage = keyof typeof formatters;
export const supportedDialects = Object.keys(formatters);

export interface FormatOptionsWithLanguage extends FormatOptions {
language: SqlLanguage | DialectOptions;
}
export type FormatOptionsWithLanguage = Partial<FormatOptions> & {
language?: SqlLanguage;
};

export type FormatOptionsWithDialect = Partial<FormatOptions> & {
dialect: DialectOptions;
};

const defaultOptions: FormatOptionsWithLanguage = {
language: 'sql',
const defaultOptions: FormatOptions = {
tabWidth: 2,
useTabs: false,
keywordCase: 'preserve',
Expand All @@ -65,10 +38,32 @@ const defaultOptions: FormatOptionsWithLanguage = {
* Format whitespace in a query to make it easier to read.
*
* @param {string} query - input SQL query string
* @param {Partial<FormatOptionsWithLanguage>} cfg Configuration options (see docs in README)
* @param {FormatOptionsWithLanguage} cfg Configuration options (see docs in README)
* @return {string} formatted query
*/
export const format = (query: string, cfg: FormatOptionsWithLanguage = {}): string => {
if (typeof cfg.language === 'string' && !supportedDialects.includes(cfg.language)) {
throw new ConfigError(`Unsupported SQL dialect: ${cfg.language}`);
}

return formatDialect(query, {
...cfg,
dialect: formatters[cfg.language || 'sql'],
});
};

/**
* Like the above format(), but language parameter is mandatory
* and must be a Dialect object instead of a string.
*
* @param {string} query - input SQL query string
* @param {FormatOptionsWithDialect} cfg Configuration options (see docs in README)
* @return {string} formatted query
*/
export const format = (query: string, cfg: Partial<FormatOptionsWithLanguage> = {}): string => {
export const formatDialect = (
query: string,
{ dialect, ...cfg }: FormatOptionsWithDialect
): string => {
if (typeof query !== 'string') {
throw new Error('Invalid query argument. Expected string, instead got ' + typeof query);
}
Expand All @@ -78,55 +73,7 @@ export const format = (query: string, cfg: Partial<FormatOptionsWithLanguage> =
...cfg,
});

const dialectOptions: DialectOptions =
typeof options.language === 'string' ? formatters[options.language] : options.language;

return new Formatter(createDialect(dialectOptions), options).format(query);
return new Formatter(createDialect(dialect), options).format(query);
};

export class ConfigError extends Error {}

function validateConfig(cfg: FormatOptionsWithLanguage): FormatOptionsWithLanguage {
if (typeof cfg.language === 'string' && !supportedDialects.includes(cfg.language)) {
throw new ConfigError(`Unsupported SQL dialect: ${cfg.language}`);
}

if ('multilineLists' in cfg) {
throw new ConfigError('multilineLists config is no more supported.');
}
if ('newlineBeforeOpenParen' in cfg) {
throw new ConfigError('newlineBeforeOpenParen config is no more supported.');
}
if ('newlineBeforeCloseParen' in cfg) {
throw new ConfigError('newlineBeforeCloseParen config is no more supported.');
}
if ('aliasAs' in cfg) {
throw new ConfigError('aliasAs config is no more supported.');
}

if (cfg.expressionWidth <= 0) {
throw new ConfigError(
`expressionWidth config must be positive number. Received ${cfg.expressionWidth} instead.`
);
}

if (cfg.commaPosition === 'before' && cfg.useTabs) {
throw new ConfigError(
'commaPosition: before does not work when tabs are used for indentation.'
);
}

if (cfg.params && !validateParams(cfg.params)) {
// eslint-disable-next-line no-console
console.warn('WARNING: All "params" option values should be strings.');
}

return cfg;
}

function validateParams(params: ParamItems | string[]): boolean {
const paramValues = params instanceof Array ? params : Object.values(params);
return paramValues.every(p => typeof p === 'string');
}

export type FormatFn = typeof format;
43 changes: 43 additions & 0 deletions src/validateConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { FormatOptions } from './FormatOptions.js';
import { ParamItems } from './formatter/Params.js';

export class ConfigError extends Error {}

export function validateConfig(cfg: FormatOptions): FormatOptions {
if ('multilineLists' in cfg) {
throw new ConfigError('multilineLists config is no more supported.');
}
if ('newlineBeforeOpenParen' in cfg) {
throw new ConfigError('newlineBeforeOpenParen config is no more supported.');
}
if ('newlineBeforeCloseParen' in cfg) {
throw new ConfigError('newlineBeforeCloseParen config is no more supported.');
}
if ('aliasAs' in cfg) {
throw new ConfigError('aliasAs config is no more supported.');
}

if (cfg.expressionWidth <= 0) {
throw new ConfigError(
`expressionWidth config must be positive number. Received ${cfg.expressionWidth} instead.`
);
}

if (cfg.commaPosition === 'before' && cfg.useTabs) {
throw new ConfigError(
'commaPosition: before does not work when tabs are used for indentation.'
);
}

if (cfg.params && !validateParams(cfg.params)) {
// eslint-disable-next-line no-console
console.warn('WARNING: All "params" option values should be strings.');
}

return cfg;
}

function validateParams(params: ParamItems | string[]): boolean {
const paramValues = params instanceof Array ? params : Object.values(params);
return paramValues.every(p => typeof p === 'string');
}
17 changes: 9 additions & 8 deletions test/sqlFormatter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import dedent from 'dedent-js';

import { format, SqlLanguage } from '../src/sqlFormatter.js';
import { sqlite } from '../src/languages/sqlite/sqlite.formatter.js';
import { format, formatDialect, SqlLanguage, sqlite } from '../src/index.js';

describe('sqlFormatter', () => {
it('throws error when unsupported language parameter specified', () => {
Expand Down Expand Up @@ -56,11 +55,13 @@ describe('sqlFormatter', () => {
}).toThrow('aliasAs config is no more supported.');
});

it('allows passing Dialect config object as a language parameter', () => {
expect(format('SELECT [foo], `bar`;', { language: sqlite })).toBe(dedent`
SELECT
[foo],
\`bar\`;
`);
describe('formatDialect()', () => {
it('allows passing Dialect config object as a dialect parameter', () => {
expect(formatDialect('SELECT [foo], `bar`;', { dialect: sqlite })).toBe(dedent`
SELECT
[foo],
\`bar\`;
`);
});
});
});