From 841bc3591ccf2a149ef09eedb06546e893a4d5a1 Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 10 Nov 2023 04:23:28 +0800 Subject: [PATCH] feat: support `JSOX` **stringified** `paramTypes` option (#301) close #233 close #277 close #279 --- .changeset/config.json | 2 +- .changeset/fuzzy-llamas-hang.md | 5 + .codesandbox/ci.json | 2 +- .github/workflows/codeql.yml | 6 +- .github/workflows/vercel.yml | 4 +- package.json | 2 +- packages/sql/package.json | 4 +- packages/sql/shim.d.ts | 3 + packages/sql/src/index.ts | 33 ++-- .../test/__snapshots__/fixtures.spec.ts.snap | 183 ++++++++++++++++++ packages/sql/test/fixtures.spec.ts | 37 +++- packages/sql/test/fixtures/233.sql | 3 + packages/sql/test/fixtures/277.sql | 1 + packages/sql/test/fixtures/279.sql | 8 + pnpm-lock.yaml | 4 +- 15 files changed, 267 insertions(+), 30 deletions(-) create mode 100644 .changeset/fuzzy-llamas-hang.md create mode 100644 packages/sql/shim.d.ts create mode 100644 packages/sql/test/fixtures/233.sql create mode 100644 packages/sql/test/fixtures/277.sql create mode 100644 packages/sql/test/fixtures/279.sql 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==}