├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── other.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── LICENSE ├── README.md ├── deployment ├── npm │ ├── .npmignore │ ├── README.md │ ├── index.d.ts │ ├── index.js │ ├── index.test.js │ ├── package-lock.json │ ├── package.json │ └── setup.js └── schema.json ├── dprint.json ├── rust-toolchain.toml ├── src ├── configuration │ ├── builder.rs │ ├── configuration.rs │ ├── mod.rs │ └── resolve_config.rs ├── format_text.rs ├── lib.rs └── wasm_plugin.rs └── tests ├── specs └── Basic │ └── Basic_All.txt └── test.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | dprint-plugin-sql version: x.x.x 13 | 14 | **Input Code** 15 | 16 | ```sql 17 | ``` 18 | 19 | **Expected Output** 20 | 21 | ```sql 22 | ``` 23 | 24 | **Actual Output** 25 | 26 | ```sql 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Something that doesn't fall in the other categories 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.config.kind }} ${{ matrix.config.os }} 8 | runs-on: ${{ matrix.config.os }} 9 | strategy: 10 | matrix: 11 | config: 12 | - os: ubuntu-latest 13 | kind: test_release 14 | - os: ubuntu-latest 15 | kind: test_debug 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | RUST_BACKTRACE: full 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: dsherret/rust-toolchain-file@v1 24 | - name: Install wasm32 target 25 | if: matrix.config.kind == 'test_release' 26 | run: rustup target add wasm32-unknown-unknown 27 | 28 | - uses: Swatinem/rust-cache@v2 29 | with: 30 | save-if: ${{ github.ref == 'refs/heads/main' }} 31 | 32 | - name: Build debug 33 | if: matrix.config.kind == 'test_debug' 34 | run: cargo build --verbose 35 | - name: Build release 36 | if: matrix.config.kind == 'test_release' 37 | run: cargo build --target wasm32-unknown-unknown --features wasm --release --verbose 38 | 39 | - name: Test debug 40 | if: matrix.config.kind == 'test_debug' 41 | run: cargo test --verbose 42 | - name: Test release 43 | if: matrix.config.kind == 'test_release' 44 | run: cargo test --release --verbose 45 | 46 | - name: Get tag version 47 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 48 | id: get_tag_version 49 | run: echo ::set-output name=TAG_VERSION::${GITHUB_REF/refs\/tags\//} 50 | 51 | # NPM 52 | - uses: actions/setup-node@v2 53 | if: matrix.config.kind == 'test_release' 54 | with: 55 | node-version: '18.x' 56 | registry-url: 'https://registry.npmjs.org' 57 | 58 | - name: Setup and test npm deployment 59 | if: matrix.config.kind == 'test_release' 60 | run: | 61 | cd deployment/npm 62 | npm install 63 | node setup.js ${{ steps.get_tag_version.outputs.TAG_VERSION }} 64 | npm run test 65 | 66 | - name: npm publish 67 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 68 | env: 69 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 70 | run: | 71 | cd deployment/npm 72 | npm publish --access public 73 | git reset --hard 74 | 75 | # CARGO PUBLISH 76 | - name: Cargo login 77 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 78 | run: cargo login ${{ secrets.CRATES_TOKEN }} 79 | 80 | - name: Cargo publish 81 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 82 | run: cargo publish 83 | 84 | # GITHUB RELEASE 85 | - name: Pre-release 86 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 87 | run: | 88 | # update config schema to have version 89 | sed -i 's/sql\/0.0.0/sql\/${{ steps.get_tag_version.outputs.TAG_VERSION }}/' deployment/schema.json 90 | # rename the wasm file 91 | (cd target/wasm32-unknown-unknown/release/ && mv dprint_plugin_sql.wasm plugin.wasm) 92 | - name: Release 93 | uses: softprops/action-gh-release@v1 94 | if: matrix.config.kind == 'test_release' && startsWith(github.ref, 'refs/tags/') 95 | env: 96 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | with: 98 | files: | 99 | target/wasm32-unknown-unknown/release/plugin.wasm 100 | deployment/schema.json 101 | body: | 102 | ## Install 103 | 104 | [Install](https://dprint.dev/install/) and [setup](https://dprint.dev/setup/) dprint. 105 | 106 | Then in your project's dprint configuration file: 107 | 108 | 1. Specify the plugin url in the `"plugins"` array. 109 | 2. Add a `"sql"` configuration property if desired. 110 | ```jsonc 111 | { 112 | // ...etc... 113 | "sql": { 114 | // sql config goes here 115 | }, 116 | "plugins": [ 117 | "https://plugins.dprint.dev/sql-${{ steps.get_tag_version.outputs.TAG_VERSION }}.wasm" 118 | ] 119 | } 120 | ``` 121 | 122 | ## JS Formatting API 123 | 124 | * [JS Formatter](https://github.com/dprint/js-formatter) - Browser/Deno and Node 125 | * [npm package](https://www.npmjs.com/package/@dprint/sql) 126 | draft: false 127 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseKind: 7 | description: 'Kind of release' 8 | default: 'minor' 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | required: true 14 | 15 | jobs: 16 | rust: 17 | name: release 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v3 24 | with: 25 | token: ${{ secrets.GH_DPRINTBOT_PAT }} 26 | 27 | - uses: denoland/setup-deno@v1 28 | - uses: dsherret/rust-toolchain-file@v1 29 | 30 | - name: Bump version and tag 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GH_DPRINTBOT_PAT }} 33 | GH_WORKFLOW_ACTOR: ${{ github.actor }} 34 | run: | 35 | git config user.email "${{ github.actor }}@users.noreply.github.com" 36 | git config user.name "${{ github.actor }}" 37 | deno run -A https://raw.githubusercontent.com/dprint/automation/0.5.1/tasks/publish_release.ts --${{github.event.inputs.releaseKind}} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | deployment/npm/buffer.generated.js 4 | deployment/npm/node_modules 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dprint-plugin-sql" 3 | version = "0.2.0" 4 | authors = ["David Sherret "] 5 | edition = "2021" 6 | homepage = "https://github.com/dprint/dprint-plugin-sql" 7 | keywords = ["formatting", "formatter", "sql"] 8 | license = "MIT" 9 | repository = "https://github.com/dprint/dprint-plugin-sql" 10 | description = "SQL formatter for dprint via sqlformat-rs." 11 | 12 | [lib] 13 | crate-type = ["lib", "cdylib"] 14 | 15 | [profile.release] 16 | opt-level = 3 17 | debug = false 18 | lto = true 19 | debug-assertions = false 20 | overflow-checks = false 21 | panic = "abort" 22 | 23 | [features] 24 | wasm = ["serde_json", "dprint-core/wasm"] 25 | 26 | [dependencies] 27 | anyhow = "1.0.51" 28 | dprint-core = { version = "0.62.1", features = ["formatting"] } 29 | serde = { version = "1.0.88", features = ["derive"] } 30 | serde_json = { version = "1.0", optional = true } 31 | sqlformat = "0.2.1" 32 | 33 | [dev-dependencies] 34 | dprint-development = "0.9.3" 35 | serde_json = { version = "1.0" } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2022 David Sherret 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dprint-plugin-sql 2 | 3 | [![](https://img.shields.io/crates/v/dprint-plugin-sql.svg)](https://crates.io/crates/dprint-plugin-sql) [![CI](https://github.com/dprint/dprint-plugin-sql/workflows/CI/badge.svg)](https://github.com/dprint/dprint-plugin-sql/actions?query=workflow%3ACI) 4 | 5 | Wrapper around [sqlformat-rs](https://github.com/shssoichiro/sqlformat-rs) for use as a formatting plugin for [dprint](https://github.com/dprint/dprint). 6 | -------------------------------------------------------------------------------- /deployment/npm/.npmignore: -------------------------------------------------------------------------------- 1 | setup.js 2 | index.test.js 3 | -------------------------------------------------------------------------------- /deployment/npm/README.md: -------------------------------------------------------------------------------- 1 | # @dprint/sql 2 | 3 | npm distribution of [dprint-plugin-sql](https://github.com/dprint/dprint-plugin-sql) which wraps [sqlformat-rs](https://github.com/shssoichiro/sqlformat-rs). 4 | 5 | Use this with [@dprint/formatter](https://github.com/dprint/js-formatter) or just use @dprint/formatter and download the [dprint-plugin-sql WASM file](https://github.com/dprint/dprint-plugin-sql/releases). 6 | 7 | ## Example 8 | 9 | ```ts 10 | import { createFromBuffer } from "@dprint/formatter"; 11 | import { getBuffer } from "@dprint/sql"; 12 | 13 | const formatter = createFromBuffer(getBuffer()); 14 | 15 | console.log( 16 | formatter.formatText("test.sql", "SELECT * FROM dbo.Something"), 17 | ); 18 | ``` 19 | -------------------------------------------------------------------------------- /deployment/npm/index.d.ts: -------------------------------------------------------------------------------- 1 | /** Gets a buffer representing the WASM module. */ 2 | export function getBuffer(): ArrayBuffer; 3 | -------------------------------------------------------------------------------- /deployment/npm/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets a buffer representing the WASM module. 3 | * @returns {ArrayBuffer} 4 | */ 5 | function getBuffer() { 6 | const encodedBuffer = require("./buffer.generated").encodedBuffer; 7 | return decodeEncodedBuffer(encodedBuffer); 8 | } 9 | 10 | /** 11 | * @param {string} encodedBuffer 12 | * @returns {ArrayBuffer} 13 | */ 14 | function decodeEncodedBuffer(encodedBuffer) { 15 | // https://stackoverflow.com/a/51473757/188246 16 | const binaryString = toBinaryString(); 17 | const bytes = new Uint8Array(binaryString.length); 18 | for (let i = 0; i < binaryString.length; i++) { 19 | bytes[i] = binaryString.charCodeAt(i); 20 | } 21 | return bytes.buffer; 22 | 23 | function toBinaryString() { 24 | if (typeof atob === "function") { 25 | return atob(encodedBuffer); 26 | } else { 27 | return Buffer.from(encodedBuffer, "base64").toString("binary"); 28 | } 29 | } 30 | } 31 | 32 | module.exports = { 33 | getBuffer, 34 | }; 35 | -------------------------------------------------------------------------------- /deployment/npm/index.test.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const assert = require("assert"); 3 | const createFromBuffer = require("@dprint/formatter").createFromBuffer; 4 | const getBuffer = require("./index").getBuffer; 5 | 6 | const formatter = createFromBuffer(getBuffer()); 7 | const result = formatter.formatText("file.sql", "SELECT * FROM dbo.Test"); 8 | 9 | assert.strictEqual(result, "SELECT\n *\nFROM\n dbo.Test\n"); 10 | -------------------------------------------------------------------------------- /deployment/npm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dprint/sql", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@dprint/formatter": { 8 | "version": "0.1.4", 9 | "resolved": "https://registry.npmjs.org/@dprint/formatter/-/formatter-0.1.4.tgz", 10 | "integrity": "sha512-L7PifpVn+u1fH+5jUNv6W2Ie5LYvG5rlXCB9hn/byozAj1WOjaZczwvWmYWNS2zObF3d+PjKf6ON+HCPHjqNvQ==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /deployment/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dprint/sql", 3 | "version": "0.0.0", 4 | "description": "Wasm buffer for dprint-plugin-sql.", 5 | "main": "./index.js", 6 | "types": "./index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/dprint/dprint-plugin-sql.git" 10 | }, 11 | "keywords": [ 12 | "sql", 13 | "code", 14 | "formatter", 15 | "dprint" 16 | ], 17 | "author": "David Sherret", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/dprint/dprint-plugin-sql/issues" 21 | }, 22 | "homepage": "https://github.com/dprint/dprint-plugin-sql#readme", 23 | "scripts": { 24 | "test": "node index.test.js" 25 | }, 26 | "devDependencies": { 27 | "@dprint/formatter": "~0.1.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /deployment/npm/setup.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const args = process.argv.slice(2); 5 | const wasmPath = path.join(__dirname, "../../target/wasm32-unknown-unknown/release/dprint_plugin_sql.wasm"); 6 | const wasmBytes = fs.readFileSync(wasmPath); 7 | 8 | let output = "module.exports.encodedBuffer = \""; 9 | output += wasmBytes.toString("base64"); 10 | output += "\";\n"; 11 | 12 | fs.writeFileSync(path.join(__dirname, "buffer.generated.js"), output); 13 | 14 | if (args.length > 0) { 15 | // update the version based on the first argument 16 | const packageJsonPath = path.join(__dirname, "package.json"); 17 | const packageJsonText = fs.readFileSync(packageJsonPath, "utf8"); 18 | const packageJson = JSON.parse(packageJsonText); 19 | packageJson.version = args[0]; 20 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2) + "\n"); 21 | } 22 | -------------------------------------------------------------------------------- /deployment/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://plugins.dprint.dev/dprint/dprint-plugin-sql/0.0.0/schema.json", 4 | "type": "object", 5 | "definitions": { 6 | "useTabs": { 7 | "description": "Whether to use tabs (true) or spaces (false).", 8 | "type": "boolean", 9 | "default": false, 10 | "oneOf": [{ 11 | "const": true, 12 | "description": "" 13 | }, { 14 | "const": false, 15 | "description": "" 16 | }] 17 | }, 18 | "newLineKind": { 19 | "description": "The kind of newline to use.", 20 | "type": "string", 21 | "default": "lf", 22 | "oneOf": [{ 23 | "const": "auto", 24 | "description": "For each file, uses the newline kind found at the end of the last line." 25 | }, { 26 | "const": "crlf", 27 | "description": "Uses carriage return, line feed." 28 | }, { 29 | "const": "lf", 30 | "description": "Uses line feed." 31 | }, { 32 | "const": "system", 33 | "description": "Uses the system standard (ex. crlf on Windows)." 34 | }] 35 | } 36 | }, 37 | "properties": { 38 | "indentWidth": { 39 | "description": "The number of characters for an indent.", 40 | "default": 2, 41 | "type": "number" 42 | }, 43 | "useTabs": { 44 | "$ref": "#/definitions/useTabs" 45 | }, 46 | "newLineKind": { 47 | "$ref": "#/definitions/newLineKind" 48 | }, 49 | "uppercase": { 50 | "description": "Use ALL CAPS for reserved words.", 51 | "default": false, 52 | "type": "boolean" 53 | }, 54 | "linesBetweenQueries": { 55 | "description": "Number of line breaks between quries.", 56 | "default": 1, 57 | "type": "number" 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "exec": { 3 | "associations": "**/*.rs", 4 | "rustfmt": "rustfmt" 5 | }, 6 | "excludes": [ 7 | "**/node_modules", 8 | "**/*-lock.json", 9 | "**/target" 10 | ], 11 | "plugins": [ 12 | "https://plugins.dprint.dev/typescript-0.85.1.wasm", 13 | "https://plugins.dprint.dev/json-0.17.4.wasm", 14 | "https://plugins.dprint.dev/markdown-0.15.3.wasm", 15 | "https://plugins.dprint.dev/toml-0.5.4.wasm", 16 | "https://plugins.dprint.dev/exec-0.3.5.json@d687dda57be0fe9a0088ccdaefa5147649ff24127d8b3ea227536c68ee7abeab" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.71.0" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /src/configuration/builder.rs: -------------------------------------------------------------------------------- 1 | use dprint_core::configuration::{ 2 | resolve_global_config, ConfigKeyMap, ConfigKeyValue, GlobalConfiguration, NewLineKind, 3 | }; 4 | 5 | use super::*; 6 | 7 | /// Formatting configuration builder. 8 | /// 9 | /// # Example 10 | /// 11 | /// ``` 12 | /// use dprint_plugin_sql::configuration::*; 13 | /// 14 | /// let config = ConfigurationBuilder::new() 15 | /// .uppercase(true) 16 | /// .build(); 17 | /// ``` 18 | pub struct ConfigurationBuilder { 19 | pub(super) config: ConfigKeyMap, 20 | global_config: Option, 21 | } 22 | 23 | impl ConfigurationBuilder { 24 | /// Constructs a new configuration builder. 25 | pub fn new() -> ConfigurationBuilder { 26 | ConfigurationBuilder { 27 | config: Default::default(), 28 | global_config: None, 29 | } 30 | } 31 | 32 | /// Gets the final configuration that can be used to format a file. 33 | pub fn build(&self) -> Configuration { 34 | if let Some(global_config) = &self.global_config { 35 | resolve_config(self.config.clone(), global_config).config 36 | } else { 37 | let global_config = resolve_global_config(Default::default(), &Default::default()).config; 38 | resolve_config(self.config.clone(), &global_config).config 39 | } 40 | } 41 | 42 | /// Set the global configuration. 43 | pub fn global_config(&mut self, global_config: GlobalConfiguration) -> &mut Self { 44 | self.global_config = Some(global_config); 45 | self 46 | } 47 | 48 | /// Whether to use tabs (true) or spaces (false). 49 | /// 50 | /// Default: `false` 51 | pub fn use_tabs(&mut self, value: bool) -> &mut Self { 52 | self.insert("useTabs", value.into()) 53 | } 54 | 55 | /// The number of columns for an indent. 56 | /// 57 | /// Default: `4` 58 | pub fn indent_width(&mut self, value: u8) -> &mut Self { 59 | self.insert("indentWidth", (value as i32).into()) 60 | } 61 | 62 | /// The kind of newline to use. 63 | /// Default: `NewLineKind::LineFeed` 64 | pub fn new_line_kind(&mut self, value: NewLineKind) -> &mut Self { 65 | self.insert("newLineKind", value.to_string().into()) 66 | } 67 | 68 | /// Use ALL CAPS for reserved words. 69 | /// Default: `false` 70 | pub fn uppercase(&mut self, value: bool) -> &mut Self { 71 | self.insert("uppercase", value.into()) 72 | } 73 | 74 | /// Number of line breaks between queries. 75 | /// Default: `1` 76 | pub fn lines_between_queries(&mut self, value: u8) -> &mut Self { 77 | self.insert("linesBetweenQueries", (value as i32).into()) 78 | } 79 | 80 | #[cfg(test)] 81 | pub(super) fn get_inner_config(&self) -> ConfigKeyMap { 82 | self.config.clone() 83 | } 84 | 85 | fn insert(&mut self, name: &str, value: ConfigKeyValue) -> &mut Self { 86 | self.config.insert(String::from(name), value); 87 | self 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use dprint_core::configuration::{resolve_global_config, NewLineKind}; 94 | 95 | use super::*; 96 | 97 | #[test] 98 | fn check_all_values_set() { 99 | let mut config = ConfigurationBuilder::new(); 100 | config 101 | .new_line_kind(NewLineKind::CarriageReturnLineFeed) 102 | .use_tabs(true) 103 | .indent_width(4) 104 | .uppercase(true) 105 | .lines_between_queries(2); 106 | 107 | let inner_config = config.get_inner_config(); 108 | assert_eq!(inner_config.len(), 5); 109 | let diagnostics = resolve_config( 110 | inner_config, 111 | &resolve_global_config(Default::default(), &Default::default()).config, 112 | ) 113 | .diagnostics; 114 | assert_eq!(diagnostics.len(), 0); 115 | } 116 | 117 | #[test] 118 | fn handle_global_config() { 119 | let mut global_config = ConfigKeyMap::new(); 120 | global_config.insert(String::from("newLineKind"), "crlf".into()); 121 | global_config.insert(String::from("useTabs"), true.into()); 122 | let global_config = resolve_global_config(global_config, &Default::default()).config; 123 | let mut config_builder = ConfigurationBuilder::new(); 124 | let config = config_builder.global_config(global_config).build(); 125 | assert_eq!(config.new_line_kind == NewLineKind::CarriageReturnLineFeed, true); 126 | assert_eq!(config.use_tabs, true); 127 | } 128 | 129 | #[test] 130 | fn use_defaults_when_global_not_set() { 131 | let global_config = resolve_global_config(Default::default(), &Default::default()).config; 132 | let mut config_builder = ConfigurationBuilder::new(); 133 | let config = config_builder.global_config(global_config).build(); 134 | assert_eq!(config.indent_width, 2); 135 | assert_eq!(config.new_line_kind == NewLineKind::LineFeed, true); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/configuration/configuration.rs: -------------------------------------------------------------------------------- 1 | use dprint_core::configuration::NewLineKind; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Serialize, Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct Configuration { 7 | pub use_tabs: bool, 8 | pub indent_width: u8, 9 | pub new_line_kind: NewLineKind, 10 | pub uppercase: bool, 11 | pub lines_between_queries: u8, 12 | } 13 | -------------------------------------------------------------------------------- /src/configuration/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod configuration; 3 | mod resolve_config; 4 | 5 | pub use builder::*; 6 | pub use configuration::*; 7 | pub use resolve_config::*; 8 | -------------------------------------------------------------------------------- /src/configuration/resolve_config.rs: -------------------------------------------------------------------------------- 1 | use super::Configuration; 2 | use dprint_core::configuration::*; 3 | 4 | /// Resolves configuration from a collection of key value strings. 5 | /// 6 | /// # Example 7 | /// 8 | /// ``` 9 | /// use dprint_core::configuration::ConfigKeyMap; 10 | /// use dprint_core::configuration::resolve_global_config; 11 | /// use dprint_plugin_sql::configuration::resolve_config; 12 | /// 13 | /// let config_map = ConfigKeyMap::new(); // get a collection of key value pairs from somewhere 14 | /// let global_config_result = resolve_global_config(config_map, &Default::default()); 15 | /// 16 | /// // check global_config_result.diagnostics here... 17 | /// 18 | /// let config_map = ConfigKeyMap::new(); // get a collection of k/v pairs from somewhere 19 | /// let config_result = resolve_config( 20 | /// config_map, 21 | /// &global_config_result.config 22 | /// ); 23 | /// 24 | /// // check config_result.diagnostics here and use config_result.config 25 | /// ``` 26 | pub fn resolve_config( 27 | config: ConfigKeyMap, 28 | global_config: &GlobalConfiguration, 29 | ) -> ResolveConfigurationResult { 30 | let mut diagnostics = Vec::new(); 31 | let mut config = config; 32 | 33 | let resolved_config = Configuration { 34 | use_tabs: get_value( 35 | &mut config, 36 | "useTabs", 37 | global_config 38 | .use_tabs 39 | .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.use_tabs), 40 | &mut diagnostics, 41 | ), 42 | indent_width: get_value( 43 | &mut config, 44 | "indentWidth", 45 | global_config 46 | .indent_width 47 | .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.indent_width), 48 | &mut diagnostics, 49 | ), 50 | new_line_kind: get_value( 51 | &mut config, 52 | "newLineKind", 53 | global_config 54 | .new_line_kind 55 | .unwrap_or(RECOMMENDED_GLOBAL_CONFIGURATION.new_line_kind), 56 | &mut diagnostics, 57 | ), 58 | uppercase: get_value(&mut config, "uppercase", false, &mut diagnostics), 59 | lines_between_queries: get_value(&mut config, "linesBetweenQueries", 1, &mut diagnostics), 60 | }; 61 | 62 | diagnostics.extend(get_unknown_property_diagnostics(config)); 63 | 64 | ResolveConfigurationResult { 65 | config: resolved_config, 66 | diagnostics, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/format_text.rs: -------------------------------------------------------------------------------- 1 | use super::configuration::Configuration; 2 | 3 | use anyhow::Result; 4 | use dprint_core::configuration::resolve_new_line_kind; 5 | use sqlformat::FormatOptions; 6 | use sqlformat::Indent; 7 | use sqlformat::QueryParams; 8 | use std::path::Path; 9 | 10 | pub fn format_text(_file_path: &Path, text: &str, config: &Configuration) -> Result> { 11 | let input_text = text; 12 | let text = sqlformat::format( 13 | text, 14 | &QueryParams::None, 15 | FormatOptions { 16 | indent: if config.use_tabs { 17 | Indent::Tabs 18 | } else { 19 | Indent::Spaces(config.indent_width) 20 | }, 21 | uppercase: config.uppercase, 22 | lines_between_queries: config.lines_between_queries, 23 | }, 24 | ); 25 | 26 | // ensure ends with newline 27 | let text = if !text.ends_with('\n') { 28 | let mut text = text; 29 | text.push('\n'); 30 | text 31 | } else { 32 | text 33 | }; 34 | 35 | // newline 36 | let text = if resolve_new_line_kind(&text, config.new_line_kind) == "\n" { 37 | text.replace("\r\n", "\n") 38 | } else { 39 | // lazy 40 | text.replace("\r\n", "\n").replace("\n", "\r\n") 41 | }; 42 | 43 | if text == input_text { 44 | Ok(None) 45 | } else { 46 | Ok(Some(text)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod configuration; 2 | mod format_text; 3 | 4 | pub use format_text::format_text; 5 | 6 | #[cfg(feature = "wasm")] 7 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 8 | mod wasm_plugin; 9 | 10 | #[cfg(feature = "wasm")] 11 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 12 | pub use wasm_plugin::*; 13 | -------------------------------------------------------------------------------- /src/wasm_plugin.rs: -------------------------------------------------------------------------------- 1 | use super::configuration::{resolve_config, Configuration}; 2 | 3 | use dprint_core::configuration::{ConfigKeyMap, GlobalConfiguration, ResolveConfigurationResult}; 4 | use dprint_core::generate_plugin_code; 5 | use dprint_core::plugins::FormatResult; 6 | use dprint_core::plugins::PluginInfo; 7 | use dprint_core::plugins::SyncPluginHandler; 8 | use std::path::Path; 9 | 10 | struct SqlPluginHandler {} 11 | 12 | impl SqlPluginHandler { 13 | pub const fn new() -> Self { 14 | Self {} 15 | } 16 | } 17 | 18 | impl SyncPluginHandler for SqlPluginHandler { 19 | fn resolve_config( 20 | &mut self, 21 | config: ConfigKeyMap, 22 | global_config: &GlobalConfiguration, 23 | ) -> ResolveConfigurationResult { 24 | resolve_config(config, global_config) 25 | } 26 | 27 | fn plugin_info(&mut self) -> PluginInfo { 28 | let version = env!("CARGO_PKG_VERSION").to_string(); 29 | PluginInfo { 30 | name: env!("CARGO_PKG_NAME").to_string(), 31 | version: version.clone(), 32 | config_key: "sql".to_string(), 33 | file_extensions: vec!["sql".to_string()], 34 | file_names: vec![], 35 | help_url: "https://dprint.dev/plugins/sql".to_string(), 36 | config_schema_url: format!( 37 | "https://plugins.dprint.dev/dprint/dprint-plugin-sql/{}/schema.json", 38 | version 39 | ), 40 | update_url: Some("https://plugins.dprint.dev/dprint/dprint-plugin-sql/latest.json".to_string()), 41 | } 42 | } 43 | 44 | fn license_text(&mut self) -> String { 45 | std::str::from_utf8(include_bytes!("../LICENSE")).unwrap().into() 46 | } 47 | 48 | fn format( 49 | &mut self, 50 | file_path: &Path, 51 | file_text: &str, 52 | config: &Configuration, 53 | _format_with_host: impl FnMut(&Path, String, &ConfigKeyMap) -> FormatResult, 54 | ) -> FormatResult { 55 | super::format_text(file_path, file_text, config) 56 | } 57 | } 58 | 59 | generate_plugin_code!(SqlPluginHandler, SqlPluginHandler::new()); 60 | -------------------------------------------------------------------------------- /tests/specs/Basic/Basic_All.txt: -------------------------------------------------------------------------------- 1 | == should format basic == 2 | SELECT * FROM dbo.Test WHERE test = 'asdf' 3 | GO 4 | 5 | 6 | 7 | update dbo.Test Set test=1 8 | GO 9 | 10 | [expect] 11 | SELECT 12 | * 13 | FROM 14 | dbo.Test 15 | WHERE 16 | test = 'asdf' 17 | GO 18 | update 19 | dbo.Test 20 | Set 21 | test = 1 22 | GO 23 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | extern crate dprint_development; 2 | extern crate dprint_plugin_sql; 3 | 4 | use std::path::PathBuf; 5 | 6 | use dprint_core::configuration::*; 7 | use dprint_development::*; 8 | use dprint_plugin_sql::configuration::{resolve_config, ConfigurationBuilder}; 9 | use dprint_plugin_sql::*; 10 | 11 | #[test] 12 | fn test_specs() { 13 | let global_config = resolve_global_config(Default::default(), &Default::default()).config; 14 | 15 | run_specs( 16 | &PathBuf::from("./tests/specs"), 17 | &ParseSpecOptions { 18 | default_file_name: "file.sql", 19 | }, 20 | &RunSpecsOptions { 21 | fix_failures: false, 22 | format_twice: true, 23 | }, 24 | { 25 | let global_config = global_config.clone(); 26 | move |file_path, file_text, spec_config| { 27 | let config_result = resolve_config(parse_config_key_map(spec_config), &global_config); 28 | ensure_no_diagnostics(&config_result.diagnostics); 29 | 30 | format_text(file_path, &file_text, &config_result.config) 31 | } 32 | }, 33 | move |_file_path, _file_text, _spec_config| panic!("Plugin does not support dprint-core tracing."), 34 | ) 35 | } 36 | 37 | #[test] 38 | fn should_handle_windows_newlines() { 39 | let config = ConfigurationBuilder::new().build(); 40 | let file_text = format_text(&PathBuf::from("file.sql"), "SELECT * FROM dbo.Test\r\n", &config).unwrap(); 41 | 42 | assert_eq!(file_text.unwrap(), "SELECT\n *\nFROM\n dbo.Test\n"); 43 | } 44 | --------------------------------------------------------------------------------