diff --git a/.changeset/config.json b/.changeset/config.json
index 22ad5c5e..6af24a9f 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -10,6 +10,6 @@
"linked": [],
"access": "restricted",
"baseBranch": "master",
- "updateInternalDependencies": "minor",
+ "updateInternalDependencies": "patch",
"ignore": []
}
diff --git a/.changeset/fuzzy-llamas-hang.md b/.changeset/fuzzy-llamas-hang.md
new file mode 100644
index 00000000..527a7496
--- /dev/null
+++ b/.changeset/fuzzy-llamas-hang.md
@@ -0,0 +1,5 @@
+---
+"prettier-plugin-sql": patch
+---
+
+feat: support `JSOX` **stringified** `paramTypes` option
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 6589ed1c..f39103e6 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -1,4 +1,4 @@
{
- "node": "16",
+ "node": "18",
"sandboxes": []
}
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index f6c7acf6..508cfadb 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -2,9 +2,11 @@ name: 'CodeQL'
on:
push:
- branches: ['master']
+ branches:
+ - master
pull_request:
- branches: ['master']
+ branches:
+ - master
schedule:
- cron: '22 11 * * 6'
diff --git a/.github/workflows/vercel.yml b/.github/workflows/vercel.yml
index 1eea825c..3c685746 100644
--- a/.github/workflows/vercel.yml
+++ b/.github/workflows/vercel.yml
@@ -16,8 +16,8 @@ jobs:
id: branch
if: ${{ github.ref == 'refs/heads/master' }}
run: |
- echo "::set-output name=args::--prod"
- echo "::set-output name=comment::false"
+ echo "args=--prod" >> $GITHUB_OUTPUT
+ echo "comment=false" >> $GITHUB_OUTPUT
- name: Deploy
uses: amondnet/vercel-action@v25
diff --git a/package.json b/package.json
index 4ef67d16..9d57f262 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"workspaces": [
"packages/*"
],
- "packageManager": "pnpm@8.10.0",
+ "packageManager": "pnpm@8.10.2",
"scripts": {
"build": "run-p build:*",
"build:r": "r -f cjs",
diff --git a/packages/sql/package.json b/packages/sql/package.json
index 01a555d6..b2945aa8 100644
--- a/packages/sql/package.json
+++ b/packages/sql/package.json
@@ -20,7 +20,8 @@
},
"types": "./lib/index.d.ts",
"files": [
- "lib"
+ "lib",
+ "shim.d.ts"
],
"keywords": [
"sql",
@@ -44,6 +45,7 @@
"prettier": "^3.0.3"
},
"dependencies": {
+ "jsox": "^1.2.118",
"node-sql-parser": "^4.7.0",
"sql-formatter": "^12.2.4",
"tslib": "^2.6.0"
diff --git a/packages/sql/shim.d.ts b/packages/sql/shim.d.ts
new file mode 100644
index 00000000..d7d5374f
--- /dev/null
+++ b/packages/sql/shim.d.ts
@@ -0,0 +1,3 @@
+declare module 'jsox' {
+ export const JSOX: typeof JSON
+}
diff --git a/packages/sql/src/index.ts b/packages/sql/src/index.ts
index 7ff741ad..64a7da0b 100644
--- a/packages/sql/src/index.ts
+++ b/packages/sql/src/index.ts
@@ -1,7 +1,10 @@
+///
+
+import { JSOX } from 'jsox'
import type { AST, Option } from 'node-sql-parser'
import nodeSqlParser from 'node-sql-parser'
import type { Options, ParserOptions, Plugin } from 'prettier'
-import { format, type FormatOptions } from 'sql-formatter'
+import { format, type FormatOptions, type ParamTypes } from 'sql-formatter'
import { languages } from './languages.js'
@@ -20,6 +23,7 @@ export type SqlBaseOptions = Option &
Partial & {
language?: string
formatter?: typeof NODE_SQL_PARSER | typeof SQL_FORMATTER
+ paramTypes?: string
}
export type SqlOptions = ParserOptions & SqlBaseOptions
@@ -42,12 +46,21 @@ const SqlPlugin: Plugin = {
},
printers: {
sql: {
- print(path, { type, database, endOfLine, ...options }: SqlOptions) {
+ print(
+ path,
+ { type, database, endOfLine, paramTypes, ...options }: SqlOptions,
+ ) {
const value = path.node
let formatted =
typeof value === 'string'
- ? format(value, options)
+ ? format(value, {
+ ...options,
+ paramTypes:
+ paramTypes == null
+ ? undefined
+ : (JSOX.parse(paramTypes) as ParamTypes),
+ })
: parser.sqlify(value, { type, database })
// It can never be `auto`
@@ -297,6 +310,7 @@ const SqlPlugin: Plugin = {
type: 'choice',
description:
'Specifies parameter values to fill in for placeholders inside SQL for `sql-formatter`. This option is designed to be used through API (though nothing really prevents usage from command line).',
+ // TODO: migrate to stringified JSOX instead
choices: [
{
value: Array,
@@ -309,7 +323,7 @@ const SqlPlugin: Plugin = {
'`Object` of name-value pairs for named (and indexed) placeholders',
},
],
- // @ts-expect-error
+ // @ts-expect-error -- https://github.com/prettier/prettier/blob/953937dc63a8b37d2b7b7b6fc7f83a3201a716e9/src/main/normalize-options.js#L167
exception(value: unknown) {
return (
value == null ||
@@ -324,16 +338,9 @@ const SqlPlugin: Plugin = {
paramTypes: {
// since: '0.11.0',
category: 'Config',
- type: 'choice',
+ type: 'string',
description:
- 'Specifies parameter types to support when parsing SQL prepared statements for `sql-formatter`.',
- choices: [
- {
- value: Object,
- description:
- 'Specifies parameter types to support when parsing SQL prepared statements.',
- },
- ],
+ 'Specifies `JSOX` **stringified** parameter types to support when parsing SQL prepared statements for `sql-formatter`.',
},
type: {
// since: '0.1.0',
diff --git a/packages/sql/test/__snapshots__/fixtures.spec.ts.snap b/packages/sql/test/__snapshots__/fixtures.spec.ts.snap
index 6fb9ddb6..e2db3e53 100644
--- a/packages/sql/test/__snapshots__/fixtures.spec.ts.snap
+++ b/packages/sql/test/__snapshots__/fixtures.spec.ts.snap
@@ -8,6 +8,189 @@ WITH
"
`;
+exports[`parser and printer > should format all fixtures > 233.sql 1`] = `
+"CREATE TABLE
+ \\"test\\" (\\"id\\" uuid NOT NULL)
+WITH
+ (oids = false);
+"
+`;
+
+exports[`parser and printer > should format all fixtures > 277.sql 1`] = `
+[Error: Parse error at token: {table_name} at line 1 column 77
+Unexpected CUSTOM_PARAMETER token: {"type":"CUSTOM_PARAMETER","raw":"{table_name}","text":"{table_name}","start":76,"key":"{table_name}"}. Instead, I was expecting to see one of the following:
+
+A LINE_COMMENT token based on:
+ comment → ● %LINE_COMMENT
+ _$ebnf$1 → _$ebnf$1 ● comment
+ _ → ● _$ebnf$1
+ property_access → expression _ %DOT ● _ property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A BLOCK_COMMENT token based on:
+ comment → ● %BLOCK_COMMENT
+ _$ebnf$1 → _$ebnf$1 ● comment
+ _ → ● _$ebnf$1
+ property_access → expression _ %DOT ● _ property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A ARRAY_IDENTIFIER token based on:
+ array_subscript → ● %ARRAY_IDENTIFIER _ square_brackets
+ property_access$subexpression$1 → ● array_subscript
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A ARRAY_KEYWORD token based on:
+ array_subscript → ● %ARRAY_KEYWORD _ square_brackets
+ property_access$subexpression$1 → ● array_subscript
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A ASTERISK token based on:
+ all_columns_asterisk → ● %ASTERISK
+ property_access$subexpression$1 → ● all_columns_asterisk
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A IDENTIFIER token based on:
+ identifier$subexpression$1 → ● %IDENTIFIER
+ identifier → ● identifier$subexpression$1
+ property_access$subexpression$1 → ● identifier
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A QUOTED_IDENTIFIER token based on:
+ identifier$subexpression$1 → ● %QUOTED_IDENTIFIER
+ identifier → ● identifier$subexpression$1
+ property_access$subexpression$1 → ● identifier
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+A VARIABLE token based on:
+ identifier$subexpression$1 → ● %VARIABLE
+ identifier → ● identifier$subexpression$1
+ property_access$subexpression$1 → ● identifier
+ property_access → expression _ %DOT _ ● property_access$subexpression$1
+ asteriskless_andless_expression$subexpression$1 → ● property_access
+ asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
+ asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
+ asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
+ free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
+ free_form_sql → ● free_form_sql$subexpression$1
+ other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
+ other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
+ clause$subexpression$1 → ● other_clause
+ clause → ● clause$subexpression$1
+ expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
+ expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
+ statement → ● expressions_or_clauses statement$subexpression$1
+ main$ebnf$1 → main$ebnf$1 ● statement
+ main → ● main$ebnf$1
+]
+`;
+
+exports[`parser and printer > should format all fixtures > 279.sql 1`] = `
+"-- create db
+CREATE DATABASE test_db;
+
+-- connect test_db
+\\\\c test_db
+-- create schema
+CREATE SCHEMA test;
+"
+`;
+
exports[`parser and printer > should format all fixtures > basic.sql 1`] = `
"-- this is a comment
SELECT
diff --git a/packages/sql/test/fixtures.spec.ts b/packages/sql/test/fixtures.spec.ts
index 618705a7..ba43acf7 100644
--- a/packages/sql/test/fixtures.spec.ts
+++ b/packages/sql/test/fixtures.spec.ts
@@ -3,6 +3,7 @@ import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { format } from 'prettier'
+import type { ParamTypes } from 'sql-formatter'
import SqlPlugin, { type SqlFormatOptions } from 'prettier-plugin-sql'
@@ -10,6 +11,22 @@ const PARSER_OPTIONS: Record = {
144: {
language: 'postgresql',
},
+ 233: {
+ paramTypes: JSON.stringify({
+ named: [':'],
+ } satisfies ParamTypes),
+ },
+ 277: {
+ language: 'mysql',
+ paramTypes: JSON.stringify({
+ custom: [{ regex: String.raw`\{\w+\}` }],
+ } satisfies ParamTypes),
+ },
+ 279: {
+ paramTypes: JSON.stringify({
+ custom: [{ regex: String.raw`\\c` }],
+ } satisfies ParamTypes),
+ },
}
const _dirname =
@@ -25,14 +42,18 @@ describe('parser and printer', () => {
const caseName = filepath.slice(0, filepath.lastIndexOf('.'))
- const output = await format(input, {
- filepath,
- parser: 'sql',
- plugins: [SqlPlugin],
- ...PARSER_OPTIONS[caseName],
- })
-
- expect(output).toMatchSnapshot(filepath)
+ try {
+ const output = await format(input, {
+ filepath,
+ parser: 'sql',
+ plugins: [SqlPlugin],
+ ...PARSER_OPTIONS[caseName],
+ })
+
+ expect(output).toMatchSnapshot(filepath)
+ } catch (error) {
+ expect(error).toMatchSnapshot(filepath)
+ }
}
})
})
diff --git a/packages/sql/test/fixtures/233.sql b/packages/sql/test/fixtures/233.sql
new file mode 100644
index 00000000..7443302a
--- /dev/null
+++ b/packages/sql/test/fixtures/233.sql
@@ -0,0 +1,3 @@
+CREATE TABLE "test" (
+ "id" uuid NOT NULL
+) WITH (oids = false);
diff --git a/packages/sql/test/fixtures/277.sql b/packages/sql/test/fixtures/277.sql
new file mode 100644
index 00000000..7776ce4c
--- /dev/null
+++ b/packages/sql/test/fixtures/277.sql
@@ -0,0 +1 @@
+SELECT *, current_date - interval {interval} {period} FROM {database_name}.{table_name}
\ No newline at end of file
diff --git a/packages/sql/test/fixtures/279.sql b/packages/sql/test/fixtures/279.sql
new file mode 100644
index 00000000..b1eb8be3
--- /dev/null
+++ b/packages/sql/test/fixtures/279.sql
@@ -0,0 +1,8 @@
+-- create db
+CREATE DATABASE test_db;
+
+-- connect test_db
+\c test_db
+
+-- create schema
+CREATE SCHEMA test;
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0a985a53..3438b9c5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -151,6 +151,9 @@ importers:
packages/sql:
dependencies:
+ jsox:
+ specifier: ^1.2.118
+ version: 1.2.118
node-sql-parser:
specifier: ^4.7.0
version: 4.7.0
@@ -9812,7 +9815,6 @@ packages:
/jsox@1.2.118:
resolution: {integrity: sha512-ubYWn4WOc7HA7icvcQuIni1I7Xx4bI4KbRXbXzlr5e48hvdizeAbflBx97B629ZNH5RZnQ657Z5Z8dFgxFVrSQ==}
hasBin: true
- dev: true
/jsx-ast-utils@3.3.4:
resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==}