├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── docs ├── preview.gif └── preview.png ├── extension-syntax-only ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json └── syntaxes │ └── grammar.json ├── extension ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── schemas │ └── tsconfig.schema.json ├── src │ ├── index.ts │ └── vscode-ts-extension.ts ├── tsconfig.json └── yarn.lock ├── regex-help.mjs └── test.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}/extension" 15 | ] 16 | }, 17 | { 18 | "name": "Extension (syntax only)", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}/extension-syntax-only" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jan Kühle 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VS Code extension: SQL tagged template literals 2 | 3 | A VS Code extension, which enables syntax highlighting for template literals tagged with an `sql` function in JavaScript and TypeScript files. 4 | 5 | There are 2 version of the extension available: 6 | 7 | - [extension](./extension): Syntax highlighting, syntax validation, type checking and formatting 8 | - [extension-syntax-only](./extension-syntax-only): Syntax highlighting only 9 | 10 | ![GIF of code snippet showing SQL syntax](./docs/preview.gif) 11 | 12 | ## Interesting bits about the grammar 13 | 14 | - To debug the grammer, check the language scopes using the - `Developer: Inspect Editor Tokens and Scopes` command. 15 | - If `patterns[].name` does not start with `string.js`, the code matched by the pattern (beginning and end) are not highlighted as JavaScript code. 16 | - The keys in `beginCaptures` and `endCaptures` refer to the capture groups in the corresponding regex, where `0` refers to the entire match. By assigning capture groups a name, they can be highlighted accordingly. E.g. using `entity.name.function.ts` makes the string matched by the capture group be highlighted as a function name. 17 | - The pattern `source.ts#template-substitution-element` refers to the variables in the template literal, e.g. `${userId}`. It should probably be the first pattern in the list to ensure these are highlighted properly. 18 | - The pattern `source.ts#string-character-escape` refers to escaped backtics in the template literal, e.g. `\``. It should come before the SQL patterns in the list in order to not confuse SQL. 19 | - A pattern cannot span multiple lines. To match something across multiple lines, you have to use nested patterns; [good explanation](https://github.com/Microsoft/vscode-textmate/issues/41#issuecomment-358459018). 20 | - The [TypeScript language definition](https://github.com/microsoft/vscode/blob/main/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json) can be useful to lookup and use existing patterns. The `sql()` function syntax in this repository makes use of some of the patterns used in a `#function-call`. 21 | 22 | ## Publish new version 23 | 24 | _(For maintainers)_ 25 | 26 | 1. Update version in package.json and CHANGELOG.md 27 | 28 | 2. Publish new version 29 | 30 | ``` 31 | npm install -g vsce 32 | 33 | REPO=https://github.com/frigus02/vscode-sql-tagged-template-literals/raw/main/ 34 | 35 | cd extension/ 36 | vsce publish --baseImagesUrl $REPO/extension/ 37 | 38 | cd extension-syntax-only/ 39 | vsce publish --baseImagesUrl $REPO/extension-syntax-only/ 40 | ``` 41 | 42 | 3. Tag new version 43 | 44 | ``` 45 | git tag extension-vX.X.X 46 | git tag extension-syntax-only-vX.X.X 47 | ``` 48 | 49 | ## Thanks 50 | 51 | This is based on several great existing extensions: 52 | 53 | - https://github.com/mjbvz/vscode-comment-tagged-templates 54 | - https://github.com/ForbesLindesay/vscode-sql-template-literal 55 | - https://github.com/Ladeiras/vscode-sql-template-literal 56 | -------------------------------------------------------------------------------- /docs/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frigus02/vscode-sql-tagged-template-literals/b7729d96b5e180fc8a2610e78113422f51b183e8/docs/preview.gif -------------------------------------------------------------------------------- /docs/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frigus02/vscode-sql-tagged-template-literals/b7729d96b5e180fc8a2610e78113422f51b183e8/docs/preview.png -------------------------------------------------------------------------------- /extension-syntax-only/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.0.19] - 2021-02-26 10 | 11 | ### Added 12 | 13 | - Support `sql` and `sqlFragment` as private functions on classes. E.g.: 14 | 15 | ```ts 16 | class Test { 17 | #sql; 18 | run() { 19 | this.#sql`SELECT * FROM users`; 20 | } 21 | } 22 | ``` 23 | 24 | ## [0.0.18] - 2021-02-05 25 | 26 | ### Added 27 | 28 | - Match `sql` and `sqlFragment` case insensitively. 29 | 30 | ### Changed 31 | 32 | - Improved README 33 | 34 | ## [0.0.17] - 2021-01-15 35 | 36 | ### Changed 37 | 38 | - Updated TypeScript grammar to latest version 39 | 40 | ### Fixed 41 | 42 | - Fixed incorrect syntax highlighting when using `sql` as a regular function ([#16](https://github.com/frigus02/vscode-sql-tagged-template-literals/issues/16)) 43 | 44 | ## [0.0.16] - 2020-12-04 45 | 46 | ### Added 47 | 48 | - Support syntax highlighting in \*.svelte files 49 | 50 | ## [0.0.15] - 2020-11-04 51 | 52 | ### Fixed 53 | 54 | - Allow for escaped backticks in SQL queries: 55 | 56 | ```ts 57 | /*sql*/ `SELECT * FROM \`users\``; 58 | ``` 59 | 60 | ## [0.0.14] - 2020-10-21 61 | 62 | ### Added 63 | 64 | - Syntax highlighting for `sql` tag function, which has types or is part of an object: 65 | 66 | ```ts 67 | const myObj = { 68 | sql(s: TemplateStringsArray, ...values: any[]) {}, 69 | }; 70 | 71 | myObj.sql`SELECT * FROM users`; 72 | ``` 73 | 74 | ## [0.0.13] - 2020-06-29 75 | 76 | ### Added 77 | 78 | - Support for highlighting template literals with an `/* SQL */` comment in front, e.g. 79 | 80 | ```ts 81 | const query = /* SQL */ ` 82 | SELECT * FROM users 83 | `; 84 | ``` 85 | 86 | Thanks [@n1ru4l](https://github.com/n1ru4l). 87 | 88 | ## [0.0.12] - 2020-04-28 89 | 90 | ### Added 91 | 92 | - Add new extension [SQL tagged template literals (syntax only)](https://marketplace.visualstudio.com/items?itemName=frigus02.vscode-sql-tagged-template-literals-syntax-only), which is the same extension but syntax highlighting only. 93 | 94 | ## [0.0.11] - 2019-09-19 95 | 96 | ### Added 97 | 98 | - Add SQL syntax highlighting for `sqlFragment` tag, e.g.: 99 | 100 | ```ts 101 | const queryFragment = sqlFragment` 102 | WHERE user_id = 0 103 | `; 104 | ``` 105 | 106 | ## [0.0.8] - 2019-09-01 107 | 108 | ### Added 109 | 110 | - Support for type arguments and optional chaining for the sql function, e.g.: 111 | 112 | ```ts 113 | const query = foo.sql?.("get-users")` 114 | SELECT * FROM users 115 | `; 116 | ``` 117 | 118 | ## [0.0.4] - 2019-06-10 119 | 120 | ### Added 121 | 122 | - Support for simple tagged templates (no function calls), e.g.: 123 | 124 | ```ts 125 | const query = sql`SELECT * FROM users`; 126 | ``` 127 | 128 | ## [0.0.3] - 2019-06-10 129 | 130 | ### Added 131 | 132 | - Support for sql functions with template literal params, e.g.: 133 | 134 | ```ts 135 | const query = sql(`get-users`)` 136 | SELECT * FROM users 137 | `; 138 | ``` 139 | 140 | ## [0.0.2] - 2019-06-03 141 | 142 | ### Added 143 | 144 | - Support for multiline sql function calls, e.g.: 145 | 146 | ```ts 147 | const query = sql( 148 | "get-users-with-a-very-very-very-very-very-very-very-long-name" 149 | )` 150 | SELECT * FROM users 151 | `; 152 | ``` 153 | 154 | ## 0.0.1 - 2019-05-27 155 | 156 | - First release. 157 | 158 | [unreleased]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.19...HEAD 159 | [0.0.19]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.18...v0.0.19 160 | [0.0.18]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.17...v0.0.18 161 | [0.0.17]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.16...v0.0.17 162 | [0.0.16]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.15...v0.0.16 163 | [0.0.15]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.14...v0.0.15 164 | [0.0.14]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.13...v0.0.14 165 | [0.0.13]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.12...v0.0.13 166 | [0.0.12]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.11...v0.0.12 167 | [0.0.11]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.8...v0.0.11 168 | [0.0.8]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.4...v0.0.8 169 | [0.0.4]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.3...v0.0.4 170 | [0.0.3]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.2...v0.0.3 171 | [0.0.2]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.1...v0.0.2 172 | -------------------------------------------------------------------------------- /extension-syntax-only/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jan Kühle 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /extension-syntax-only/README.md: -------------------------------------------------------------------------------- 1 | # VS Code extension: SQL tagged template literals (syntax only) 2 | 3 | A VS Code extension, which enables SQL syntax highlighting for template literals tagged with an `sql` or `sqlFragment` function in JavaScript and TypeScript files. 4 | 5 | ![Image of code snippet showing SQL syntax highlighting](../docs/preview.png) 6 | 7 | Supported are: 8 | 9 | - Tagged template literals: 10 | 11 | ```ts 12 | sql`SELECT * FROM user`; 13 | sqlFragment`WHERE id = ${id}`; 14 | ``` 15 | 16 | - Functions returning a template tag: 17 | 18 | ```ts 19 | sql("get-user")`SELECT * FROM user`; 20 | sqlFragment("filter-by-id")`WHERE id = ${id}`; 21 | ``` 22 | 23 | - Comments before template literals: 24 | 25 | ```ts 26 | /* sql * / `SELECT * FROM user` 27 | /* sqlFragment */ `WHERE id = ${id}`; 28 | ``` 29 | 30 | - And most combinations with most TypeScript features. Some examples: 31 | 32 | ```ts 33 | sql>`SELECT * FROM user`; 34 | nested?.optional?.sql`SELECT * FROM user`; 35 | sql("with", Infinity, `params`)`SELECT * FROM user`; 36 | ``` 37 | 38 | If you're using PostgreSQL, have a look at [SQL tagged template literals](https://marketplace.visualstudio.com/items?itemName=frigus02.vscode-sql-tagged-template-literals), which is the same extension but also supports SQL syntax validation, type checking and formatting. 39 | 40 | ## Thanks 41 | 42 | This is based on several great existing extensions: 43 | 44 | - https://github.com/mjbvz/vscode-comment-tagged-templates 45 | - https://github.com/ForbesLindesay/vscode-sql-template-literal 46 | - https://github.com/Ladeiras/vscode-sql-template-literal 47 | -------------------------------------------------------------------------------- /extension-syntax-only/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-sql-tagged-template-literals-syntax-only", 3 | "displayName": "SQL tagged template literals (syntax only)", 4 | "description": "Syntax highlighting for SQL in template literals tagged with `sql` or `sqlFragment` function", 5 | "version": "0.0.19", 6 | "license": "MIT", 7 | "publisher": "frigus02", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/frigus02/vscode-sql-tagged-template-literals" 11 | }, 12 | "engines": { 13 | "vscode": "^1.34.0" 14 | }, 15 | "categories": [ 16 | "Programming Languages" 17 | ], 18 | "contributes": { 19 | "grammars": [ 20 | { 21 | "injectTo": [ 22 | "source.js", 23 | "source.js.jsx", 24 | "source.jsx", 25 | "source.svelte", 26 | "source.ts", 27 | "source.tsx" 28 | ], 29 | "scopeName": "inline.tagged-template-sql", 30 | "path": "./syntaxes/grammar.json", 31 | "embeddedLanguages": { 32 | "meta.embedded.block.sql": "sql" 33 | } 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /extension-syntax-only/syntaxes/grammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "__COMMENT__": "TypeScript patterns are taken from https://github.com/microsoft/vscode/blob/74623bc93c5482cbf6fbb78346167cb39717b703/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json", 3 | "scopeName": "inline.tagged-template-sql", 4 | "injectionSelector": "L:source -comment -string", 5 | "patterns": [ 6 | { 7 | "__COMMENT__": "Literals tagged with an sql function (including optional accessors and types), e.g. sql('user-by-id')`SELECT ...`", 8 | "begin": "(?:([_$[:alpha:]][_$[:alnum:]]*)(?:\\s*(\\??\\.)\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*\\s*(\\??\\.))?\\s*(#?(?i)sql|sqlFragment(?-i))\\s*(?=(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", 9 | "beginCaptures": { 10 | "1": { "name": "meta.function-call.ts variable.other.object.ts" }, 11 | "2": { 12 | "name": "meta.function-call.ts punctuation.accessor.optional.ts" 13 | }, 14 | "3": { "name": "meta.function-call.ts variable.other.object.ts" }, 15 | "4": { 16 | "name": "meta.function-call.ts punctuation.accessor.optional.ts" 17 | }, 18 | "5": { "name": "meta.function-call.ts entity.name.function.ts" } 19 | }, 20 | "end": "(?<=(`|\\)\\s*[^\\s`]))", 21 | "patterns": [ 22 | { "include": "source.ts#comment" }, 23 | { "include": "source.ts#function-call-optionals" }, 24 | { "include": "source.ts#type-arguments" }, 25 | { "include": "source.ts#paren-expression" }, 26 | { "include": "#embedded-sql" } 27 | ] 28 | }, 29 | { 30 | "__COMMENT__": "Literals tagged with an sql comment, e.g. /*sql*/`SELECT ...`", 31 | "begin": "(/\\*\\s*(?i)sql(?-i)\\s*\\*/)\\s*(?=`)", 32 | "beginCaptures": { 33 | "1": { "name": "comment.block.ts" } 34 | }, 35 | "end": "(?<=`)", 36 | "patterns": [{ "include": "#embedded-sql" }] 37 | }, 38 | { 39 | "__COMMENT__": "Literals tagged with sql (including optional accessors and types), e.g. my.object?.sql`SELECT ...`. This is based on the 1st #template-call pattern in TypeScript.tmLanguage.json", 40 | "name": "string.template.ts", 41 | "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)(#?(?i)sql|sqlFragment(?-i))(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", 42 | "end": "(?<=`)", 43 | "patterns": [ 44 | { 45 | "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)(#?(?i)sql|sqlFragment(?-i)))", 46 | "end": "(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", 47 | "patterns": [ 48 | { "include": "source.ts#support-function-call-identifiers" }, 49 | { 50 | "name": "entity.name.function.tagged-template.ts", 51 | "match": "(#?(?i)sql|sqlFragment(?-i))" 52 | } 53 | ] 54 | }, 55 | { "include": "source.ts#type-arguments" }, 56 | { "include": "#embedded-sql" } 57 | ] 58 | }, 59 | { 60 | "__COMMENT__": "Literals tagged with sql (including optional types), e.g. sql`SELECT ...`. This is based on the 2nd #template-call pattern in TypeScript.tmLanguage.json", 61 | "name": "string.template.ts", 62 | "begin": "\\b((?i)sql|sqlFragment(?-i))\\s*(?=(<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|awaited|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", 63 | "beginCaptures": { 64 | "1": { "name": "entity.name.function.tagged-template.ts" } 65 | }, 66 | "end": "(?<=`)", 67 | "patterns": [ 68 | { "include": "source.ts#type-arguments" }, 69 | { "include": "#embedded-sql" } 70 | ] 71 | }, 72 | { 73 | "__COMMENT__": "Literals tagged with sql, e.g. sql`SELECT ...`. This is based on the 2nd #template pattern in TypeScript.tmLanguage.json", 74 | "name": "string.template.ts", 75 | "begin": "\\b((?i)sql|sqlFragment(?-i))\\s*(?=`)", 76 | "beginCaptures": { 77 | "1": { "name": "entity.name.function.tagged-template.ts" } 78 | }, 79 | "end": "(?<=`)", 80 | "patterns": [{ "include": "#embedded-sql" }] 81 | } 82 | ], 83 | "repository": { 84 | "embedded-sql": { 85 | "name": "string.template.ts", 86 | "contentName": "meta.embedded.block.sql", 87 | "begin": "`", 88 | "beginCaptures": { 89 | "0": { "name": "punctuation.definition.string.template.begin.js" } 90 | }, 91 | "end": "`", 92 | "endCaptures": { 93 | "0": { "name": "punctuation.definition.string.template.end.js" } 94 | }, 95 | "patterns": [ 96 | { "include": "source.ts#template-substitution-element" }, 97 | { "include": "source.ts#string-character-escape" }, 98 | { "include": "source.sql" }, 99 | { "include": "source.plpgsql.postgres" }, 100 | { "match": "." } 101 | ] 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /extension/.vscodeignore: -------------------------------------------------------------------------------- 1 | src/ 2 | 3 | **/*.map 4 | **/*.ts 5 | **/tsconfig.json 6 | **/tslint.json 7 | -------------------------------------------------------------------------------- /extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.0.19] - 2023-06-23 10 | 11 | ### Fixed 12 | 13 | - Update `typescript-sql-tagged-template-plugin` to fix crash caused by missing `fileExists` function. 14 | 15 | ## [0.0.18] - 2021-02-05 16 | 17 | ### Changed 18 | 19 | - Improved README 20 | 21 | ## [0.0.17] - 2021-01-02 22 | 23 | ### Added 24 | 25 | - Customize formatting by specifying a pgFormatter config file (updated `typescript-sql-tagged-template-plugin`). 26 | 27 | ## [0.0.16] - 2020-11-28 28 | 29 | ### Added 30 | 31 | - Formatting of SQL in tagged template literals (updated `typescript-sql-tagged-template-plugin`). 32 | 33 | Formatting uses [pgFormatter](https://github.com/darold/pgFormatter) and requires Perl. If you have Perl installed, it should work right away. If it doesn't work, make sure you're using the "TypeScript and JavaScript language features" formatter. Formatting will not work if you're using another formatter like Prettier. 34 | 35 | ## [0.0.15] - 2020-11-04 36 | 37 | ### Fixed 38 | 39 | - Allow for escaped backticks in SQL queries: 40 | 41 | ```ts 42 | /*sql*/ `SELECT * FROM \`users\``; 43 | ``` 44 | 45 | ## [0.0.14] - 2020-10-21 46 | 47 | ### Added 48 | 49 | - Syntax highlighting for `sql` tag function, which has types or is part of an object: 50 | 51 | ```ts 52 | const myObj = { 53 | sql(s: TemplateStringsArray, ...values: any[]) {}, 54 | }; 55 | 56 | myObj.sql`SELECT * FROM users`; 57 | ``` 58 | 59 | ## [0.0.13] - 2020-06-29 60 | 61 | ### Added 62 | 63 | - Support for highlighting template literals with an `/* SQL */` comment in front, e.g. 64 | 65 | ```ts 66 | const query = /* SQL */ ` 67 | SELECT * FROM users 68 | `; 69 | ``` 70 | 71 | Thanks [@n1ru4l](https://github.com/n1ru4l). 72 | 73 | ## [0.0.12] - 2020-04-28 74 | 75 | ### Added 76 | 77 | - Add new extension [SQL tagged template literals (syntax only)](https://marketplace.visualstudio.com/items?itemName=frigus02.vscode-sql-tagged-template-literals-syntax-only), which is the same extension but syntax highlighting only. 78 | 79 | ## [0.0.11] - 2019-09-19 80 | 81 | ### Added 82 | 83 | - Add SQL syntax highlighting for `sqlFragment` tag, e.g.: 84 | 85 | ```ts 86 | const queryFragment = sqlFragment` 87 | WHERE user_id = 0 88 | `; 89 | ``` 90 | 91 | ## [0.0.10] - 2019-09-10 92 | 93 | ### Fixed 94 | 95 | - Type checking for nullable types like `string | null` (updated `typescript-sql-tagged-template-plugin`). 96 | 97 | ## [0.0.9] - 2019-09-03 98 | 99 | ### Fixed 100 | 101 | - Type checking for builtin types like `Date` or `BigInt` (updated `typescript-sql-tagged-template-plugin`). 102 | 103 | ## [0.0.8] - 2019-09-01 104 | 105 | ### Added 106 | 107 | - Support for type arguments and optional chaining for the sql function, e.g.: 108 | 109 | ```ts 110 | const query = foo.sql?.("get-users")` 111 | SELECT * FROM users 112 | `; 113 | ``` 114 | 115 | ## [0.0.7] - 2019-08-22 116 | 117 | ### Added 118 | 119 | - Better resolution of database schema file for `typescript-sql-tagged-template-plugin`. The path can now be specified relative to the workspace, absolute or relative to the `tsconfig.json`. 120 | 121 | ## [0.0.6] - 2019-08-21 122 | 123 | ### Fixed 124 | 125 | - Syntax and type checking did not work because the `typescript-sql-tagged-template-plugin` plugin had a bug, which caused it to crash while loading. Updated the plugin to fix this. 126 | 127 | ## [0.0.5] - 2019-08-21 128 | 129 | ### Added 130 | 131 | - Simple syntax and type checking using the [typescript-sql-tagged-template-plugin](https://github.com/frigus02/typescript-sql-tagged-template-plugin). 132 | 133 | ## [0.0.4] - 2019-06-10 134 | 135 | ### Added 136 | 137 | - Support for simple tagged templates (no function calls), e.g.: 138 | 139 | ```ts 140 | const query = sql`SELECT * FROM users`; 141 | ``` 142 | 143 | ## [0.0.3] - 2019-06-10 144 | 145 | ### Added 146 | 147 | - Support for sql functions with template literal params, e.g.: 148 | 149 | ```ts 150 | const query = sql(`get-users`)` 151 | SELECT * FROM users 152 | `; 153 | ``` 154 | 155 | ## [0.0.2] - 2019-06-03 156 | 157 | ### Added 158 | 159 | - Support for multiline sql function calls, e.g.: 160 | 161 | ```ts 162 | const query = sql( 163 | "get-users-with-a-very-very-very-very-very-very-very-long-name" 164 | )` 165 | SELECT * FROM users 166 | `; 167 | ``` 168 | 169 | ## 0.0.1 - 2019-05-27 170 | 171 | - First release. 172 | 173 | [unreleased]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.19...HEAD 174 | [0.0.19]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.18...v0.0.19 175 | [0.0.18]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.17...v0.0.18 176 | [0.0.17]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.16...v0.0.17 177 | [0.0.16]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.15...v0.0.16 178 | [0.0.15]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.14...v0.0.15 179 | [0.0.14]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.13...v0.0.14 180 | [0.0.13]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.12...v0.0.13 181 | [0.0.12]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.11...v0.0.12 182 | [0.0.11]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.10...v0.0.11 183 | [0.0.10]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.9...v0.0.10 184 | [0.0.9]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.8...v0.0.9 185 | [0.0.8]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.7...v0.0.8 186 | [0.0.7]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.6...v0.0.7 187 | [0.0.6]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.5...v0.0.6 188 | [0.0.5]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.4...v0.0.5 189 | [0.0.4]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.3...v0.0.4 190 | [0.0.3]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.2...v0.0.3 191 | [0.0.2]: https://github.com/frigus02/vscode-sql-tagged-template-literals/compare/v0.0.1...v0.0.2 192 | -------------------------------------------------------------------------------- /extension/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jan Kühle 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /extension/README.md: -------------------------------------------------------------------------------- 1 | # VS Code extension: SQL tagged template literals 2 | 3 | A VS Code extension, which enables SQL syntax highlighting and simple type checking for template literals tagged with an `sql` or `sqlFragment` function in JavaScript and TypeScript files. 4 | 5 | ![GIF of code snippet showing SQL syntax highlighting and type errors](../docs/preview.gif) 6 | 7 | Features: 8 | 9 | - Syntax highlighting 10 | - Syntax validation and type checking (TypeScript only) 11 | - Formatting (VS Code TypeScript/JavaScript formatter only) 12 | 13 | If you're not using PostgreSQL, syntax validation and type checking may not work properly. Have a look at [SQL tagged template literals (syntax only)](https://marketplace.visualstudio.com/items?itemName=frigus02.vscode-sql-tagged-template-literals-syntax-only), which is the same extension but syntax highlighting only. 14 | 15 | ## Syntax highlighting 16 | 17 | Syntax highlighting works for `sql` and `sqlFragment` template tags and functions: 18 | 19 | - Tagged template literals: 20 | 21 | ```ts 22 | sql`SELECT * FROM user`; 23 | sqlFragment`WHERE id = ${id}`; 24 | ``` 25 | 26 | - Functions returning a template tag: 27 | 28 | ```ts 29 | sql("get-user")`SELECT * FROM user`; 30 | sqlFragment("filter-by-id")`WHERE id = ${id}`; 31 | ``` 32 | 33 | - Comments before template literals: 34 | 35 | ```ts 36 | /* sql */ `SELECT * FROM user` 37 | /* sqlFragment */ `WHERE id = ${id}`; 38 | ``` 39 | 40 | - And most combinations with most TypeScript features. Some examples: 41 | 42 | ```ts 43 | sql>`SELECT * FROM user`; 44 | nested?.optional?.sql`SELECT * FROM user`; 45 | sql("with", Infinity, `params`)`SELECT * FROM user`; 46 | ``` 47 | 48 | ## Syntax validation, type checking and formatting 49 | 50 | The extension includes the [typescript-sql-tagged-template-plugin](https://github.com/frigus02/typescript-sql-tagged-template-plugin) to support SQL syntax validation, type checking and formatting. This comes with the following limitations: 51 | 52 | - It only works in TypeScript files 53 | 54 | - It only works with the `sql` template tag/function 55 | 56 | - Type checking requires your database schema in a special format (see the [plugin documentation](https://github.com/frigus02/typescript-sql-tagged-template-plugin#configuration) for details) 57 | 58 | - Formatting requires Perl to be installed on your machine and that you use the VS Code built-in formatter (TypeScript and JavaScript language features) 59 | 60 | ## Thanks 61 | 62 | This is based on several great existing extensions: 63 | 64 | - https://github.com/mjbvz/vscode-comment-tagged-templates 65 | - https://github.com/ForbesLindesay/vscode-sql-template-literal 66 | - https://github.com/Ladeiras/vscode-sql-template-literal 67 | -------------------------------------------------------------------------------- /extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-sql-tagged-template-literals", 3 | "displayName": "SQL tagged template literals", 4 | "description": "Syntax highlighting, validation and type checking for PostgreSQL in template literals tagged with `sql` function", 5 | "version": "0.0.19", 6 | "license": "MIT", 7 | "publisher": "frigus02", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/frigus02/vscode-sql-tagged-template-literals" 11 | }, 12 | "engines": { 13 | "vscode": "^1.79.0" 14 | }, 15 | "categories": [ 16 | "Programming Languages" 17 | ], 18 | "activationEvents": [ 19 | "onLanguage:typescript", 20 | "onLanguage:typescriptreact" 21 | ], 22 | "main": "./build/index.js", 23 | "contributes": { 24 | "configuration": { 25 | "title": "SQL tagged template literals", 26 | "properties": { 27 | "frigus02.vscode-sql-tagged-template-literals.enableDiagnostics": { 28 | "scope": "window", 29 | "type": "boolean", 30 | "default": true, 31 | "markdownDescription": "Enable diagnostics by parsing SQL statements and validating types using the specified schema file." 32 | }, 33 | "frigus02.vscode-sql-tagged-template-literals.enableFormat": { 34 | "scope": "window", 35 | "type": "boolean", 36 | "default": true, 37 | "markdownDescription": "Enable formatting support for SQL in tagged template literals using [pgFormatter](https://github.com/darold/pgFormatter). Requires Perl." 38 | }, 39 | "frigus02.vscode-sql-tagged-template-literals.schemaFile": { 40 | "scope": "window", 41 | "type": "string", 42 | "markdownDescription": "Path to JSON file describing the types used in your database schema. See [plugin documentation](https://github.com/frigus02/typescript-sql-tagged-template-plugin) for how to generate the file. You can specify the path in different ways:\n- Relative to the workspace root (only if you're working in a VS Code workspace with a single workspace folder)\n- Relative to the `tsconfig.json` file\n- Absolute" 43 | }, 44 | "frigus02.vscode-sql-tagged-template-literals.defaultSchemaName": { 45 | "scope": "window", 46 | "type": "string", 47 | "markdownDescription": "Databse schema name used when SQL statements don't specify a schema. Defaults to `public`." 48 | }, 49 | "frigus02.vscode-sql-tagged-template-literals.pgFormatterConfigFile": { 50 | "scope": "window", 51 | "type": "string", 52 | "markdownDescription": "Path to pgFormatter config file. Use this to customize SQL formatting. See [pg_format.config.sample](https://github.com/darold/pgFormatter/blob/v4.4/doc/pg_format.conf.sample) for available options. You can specify the path in different ways:\n- Relative to the workspace root (only if you're working in a VS Code workspace with a single workspace folder)\n- Relative to the `tsconfig.json` file\n- Absolute" 53 | } 54 | } 55 | }, 56 | "typescriptServerPlugins": [ 57 | { 58 | "name": "typescript-sql-tagged-template-plugin", 59 | "enableForWorkspaceTypeScriptVersions": true 60 | } 61 | ], 62 | "jsonValidation": [ 63 | { 64 | "fileMatch": "tsconfig*.json", 65 | "url": "./schemas/tsconfig.schema.json" 66 | } 67 | ] 68 | }, 69 | "dependencies": { 70 | "typescript-sql-tagged-template-plugin": "0.3.0" 71 | }, 72 | "devDependencies": { 73 | "@types/node": "^20.3.1", 74 | "@types/vscode": "^1.79.0", 75 | "typescript": "^5.1.3" 76 | }, 77 | "extensionDependencies": [ 78 | "vscode.typescript-language-features", 79 | "frigus02.vscode-sql-tagged-template-literals-syntax-only" 80 | ], 81 | "scripts": { 82 | "vscode:prepublish": "yarn build", 83 | "build": "yarn tsc" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /extension/schemas/tsconfig.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "TS Lint plugin contributions to package.json", 4 | "type": "object", 5 | "properties": { 6 | "compilerOptions": { 7 | "type": "object", 8 | "properties": { 9 | "plugins": { 10 | "type": "array", 11 | "items": { 12 | "if": { 13 | "properties": { 14 | "name": { 15 | "enum": ["typescript-sql-tagged-template-plugin"] 16 | } 17 | }, 18 | "required": ["name"] 19 | }, 20 | "then": { 21 | "properties": { 22 | "enableDiagnostics": { 23 | "type": "boolean", 24 | "default": true, 25 | "description": "Enable diagnostics by parsing SQL statements and validating types using the specified schema file." 26 | }, 27 | "enableFormat": { 28 | "type": "boolean", 29 | "default": true, 30 | "description": "Enable formatting support for SQL in tagged template literals using pgFormatter. Requires Perl." 31 | }, 32 | "schemaFile": { 33 | "type": "string", 34 | "description": "Path to JSON file describing the types used in youe database schema. See https://github.com/frigus02/typescript-sql-tagged-template-plugin for more information." 35 | }, 36 | "defaultSchemaName": { 37 | "type": "string", 38 | "default": "public", 39 | "description": "Databse schema name used when SQL statements don't specify a schema." 40 | }, 41 | "pgFormatterConfigFile": { 42 | "type": "string", 43 | "description": "Path to pgFormatter config file. Use this to customize SQL formatting. See https://github.com/darold/pgFormatter/blob/v4.4/doc/pg_format.conf.sample for available options. If the path is relative, it's resolved relative to the tsconfig.json." 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /extension/src/index.ts: -------------------------------------------------------------------------------- 1 | import { access as accessCallback } from "fs"; 2 | import { resolve as resolvePath } from "path"; 3 | import { promisify } from "util"; 4 | import * as vscode from "vscode"; 5 | import { getTypeScriptLanguageFeaturesExtensionAPI } from "./vscode-ts-extension"; 6 | 7 | const accessFile = promisify(accessCallback); 8 | const pluginId = "typescript-sql-tagged-template-plugin"; 9 | const extensionId = "frigus02.vscode-sql-tagged-template-literals"; 10 | 11 | interface Configuration { 12 | enableDiagnostics: boolean; 13 | enableFormat: boolean; 14 | schemaFile?: string; 15 | defaultSchemaName?: string; 16 | pgFormatterConfigFile?: string; 17 | } 18 | 19 | const resolveFileWorkspaceRelative = async (file: string): Promise => { 20 | const workspaceFolders = vscode.workspace.workspaceFolders; 21 | if (workspaceFolders && workspaceFolders.length === 1) { 22 | const workspaceRoot = workspaceFolders[0].uri.fsPath; 23 | const resolvedPath = resolvePath(workspaceRoot, file); 24 | try { 25 | // If the file does not exist, we want to fall back to a path relative 26 | // to a tsconfig.json, which is what the plugin will do by default. 27 | await accessFile(resolvedPath); 28 | return resolvedPath; 29 | } catch (e) {} 30 | } 31 | 32 | return file; 33 | }; 34 | 35 | const getConfiguration = async (): Promise => { 36 | const config = vscode.workspace.getConfiguration(extensionId); 37 | const enableDiagnostics = config.get("enableDiagnostics", true); 38 | const enableFormat = config.get("enableFormat", true); 39 | const schemaFile = config.get("schemaFile", undefined); 40 | const defaultSchemaName = config.get( 41 | "defaultSchemaName", 42 | undefined 43 | ); 44 | const pgFormatterConfigFile = config.get( 45 | "pgFormatterConfigFile", 46 | undefined 47 | ); 48 | 49 | return { 50 | enableDiagnostics, 51 | enableFormat, 52 | schemaFile: schemaFile && (await resolveFileWorkspaceRelative(schemaFile)), 53 | defaultSchemaName, 54 | pgFormatterConfigFile: 55 | pgFormatterConfigFile && 56 | (await resolveFileWorkspaceRelative(pgFormatterConfigFile)), 57 | }; 58 | }; 59 | 60 | export async function activate(context: vscode.ExtensionContext) { 61 | const api = getTypeScriptLanguageFeaturesExtensionAPI(); 62 | if (!api) { 63 | return; 64 | } 65 | 66 | vscode.workspace.onDidChangeConfiguration( 67 | async (e) => { 68 | if (e.affectsConfiguration(extensionId)) { 69 | api.configurePlugin(pluginId, await getConfiguration()); 70 | } 71 | }, 72 | undefined, 73 | context.subscriptions 74 | ); 75 | 76 | api.configurePlugin(pluginId, await getConfiguration()); 77 | } 78 | -------------------------------------------------------------------------------- /extension/src/vscode-ts-extension.ts: -------------------------------------------------------------------------------- 1 | // VS Code bundles an extension called "Language Features for TypeScript and 2 | // JavaScript files". 3 | // Using this extension we can configure TypeScript language server plugins 4 | // from this extension. The types `ApiV0` and `Api` in this file have been 5 | // copied from: 6 | // https://github.com/microsoft/vscode/blob/db8368395f65b584eec79e74e8d4a47aa0c60349/extensions/typescript-language-features/src/api.ts 7 | 8 | import * as vscode from "vscode"; 9 | 10 | interface ApiV0 { 11 | configurePlugin(pluginId: string, configuration: {}): void; 12 | } 13 | 14 | interface Api { 15 | getAPI(version: 0): ApiV0 | undefined; 16 | } 17 | 18 | export const getTypeScriptLanguageFeaturesExtensionAPI = () => { 19 | const extension = vscode.extensions.getExtension( 20 | "vscode.typescript-language-features" 21 | ); 22 | if (!extension) { 23 | return; 24 | } 25 | 26 | if (!extension.exports || !extension.exports.getAPI) { 27 | return; 28 | } 29 | 30 | const api = extension.exports as Api; 31 | return api.getAPI(0); 32 | }; 33 | -------------------------------------------------------------------------------- /extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "outDir": "build", 6 | "rootDir": "src", 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extension/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^20.3.1": 6 | version "20.3.1" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" 8 | integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== 9 | 10 | "@types/vscode@^1.79.0": 11 | version "1.79.1" 12 | resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.79.1.tgz#ab568315f9c844a8f4fa8f168b2d6dbf1f58dd02" 13 | integrity sha512-Ikwc4YbHABzqthrWfeAvItaAIfX9mdjMWxqNgTpGjhgOu0TMRq9LzyZ2yBK0JhYqoSjEubEPawf6zJgnl6Egtw== 14 | 15 | pg-query-emscripten@^0.1.0: 16 | version "0.1.0" 17 | resolved "https://registry.yarnpkg.com/pg-query-emscripten/-/pg-query-emscripten-0.1.0.tgz#a4c6c7c6b3645ab0a4436bf650f3bcdf5b530c23" 18 | integrity sha512-Awwf0s6gyVZEmZeMLXnXZOIQveqVEBFhxMkR0UcMmijwX/y9FW1od71X1hNRtGFWrU/ML5sxerZyH20pvpLGuA== 19 | 20 | typescript-sql-tagged-template-plugin@0.3.0: 21 | version "0.3.0" 22 | resolved "https://registry.yarnpkg.com/typescript-sql-tagged-template-plugin/-/typescript-sql-tagged-template-plugin-0.3.0.tgz#e36e3c8e64956e8aeaec5d9375f6fda67030f475" 23 | integrity sha512-Wu9rrqe4eJ87BNzZ/HVzxKsNWJYXSqqpvuRunn3n4Cq3USY0kYADC0tzDlg9FAn842MHByxRB4YMTjC1ofPqNA== 24 | dependencies: 25 | pg-query-emscripten "^0.1.0" 26 | typescript-template-language-service-decorator "^2.3.2" 27 | 28 | typescript-template-language-service-decorator@^2.3.2: 29 | version "2.3.2" 30 | resolved "https://registry.yarnpkg.com/typescript-template-language-service-decorator/-/typescript-template-language-service-decorator-2.3.2.tgz#095c1b5ea88c839d9b202fad3ec8c87c1b062953" 31 | integrity sha512-hN0zNkr5luPCeXTlXKxsfBPlkAzx86ZRM1vPdL7DbEqqWoeXSxplACy98NpKpLmXsdq7iePUzAXloCAoPKBV6A== 32 | 33 | typescript@^5.1.3: 34 | version "5.1.3" 35 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" 36 | integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== 37 | -------------------------------------------------------------------------------- /regex-help.mjs: -------------------------------------------------------------------------------- 1 | import { promises as fs } from "fs"; 2 | 3 | const prettify = (regexJson) => { 4 | const regex = JSON.parse(regexJson); 5 | if (typeof regex !== "string") { 6 | throw new Error("regex is not single JSON string"); 7 | } 8 | 9 | let result = ""; 10 | let escaped = false; 11 | let indent = 0; 12 | for (let i = 0; i < regex.length; i++) { 13 | const c = regex[i]; 14 | if (c === "\\" && !escaped) { 15 | result += c; 16 | escaped = true; 17 | } else if (escaped) { 18 | result += c; 19 | escaped = false; 20 | } else if (c === "(") { 21 | indent++; 22 | result += c; 23 | result += "\n" + " ".repeat(indent * 4); 24 | } else if (c === ")") { 25 | indent--; 26 | result += "\n" + " ".repeat(indent * 4); 27 | result += c; 28 | } else if (c === "|") { 29 | result += c; 30 | result += "\n" + " ".repeat(indent * 4); 31 | } else { 32 | result += c; 33 | } 34 | } 35 | 36 | if (escaped || indent > 0) { 37 | throw new Error("invalid regex: still escaped or indent>0 at EOF"); 38 | } 39 | 40 | return result; 41 | }; 42 | 43 | const minify = (regex) => { 44 | const validateIndent = (at, indent) => { 45 | const spaces = indent * 4; 46 | for (let i = 0; i < spaces; i++) { 47 | at++; 48 | if (regex.length <= at || regex[at] !== " ") { 49 | throw new Error("expected space"); 50 | } 51 | } 52 | 53 | return spaces; 54 | }; 55 | const validateNewLineAndIndent = (at, indent) => { 56 | at++; 57 | if (regex.length <= at || regex[at] !== "\n") { 58 | throw new Error("expected newline"); 59 | } 60 | 61 | return 1 + validateIndent(at, indent); 62 | }; 63 | const validateIndentAndClosingBacket = (at, indent) => { 64 | const spaces = validateIndent(at, indent); 65 | 66 | at += spaces + 1; 67 | if (regex.length <= at || regex[at] !== ")") { 68 | throw new Error( 69 | `expected closing bracket at ${at} and found "${ 70 | regex[at] 71 | }" in "${regex.substr(at - 10, 20)}"` 72 | ); 73 | } 74 | 75 | return spaces + 1; 76 | }; 77 | 78 | regex = regex.trimEnd(); 79 | let result = ""; 80 | let escaped = false; 81 | let indent = 0; 82 | for (let i = 0; i < regex.length; i++) { 83 | const c = regex[i]; 84 | if (c === "\\" && !escaped) { 85 | result += c; 86 | escaped = true; 87 | } else if (escaped) { 88 | result += c; 89 | escaped = false; 90 | } else if (c === "(") { 91 | indent++; 92 | result += c; 93 | i += validateNewLineAndIndent(i, indent); 94 | } else if (c === "\n") { 95 | indent--; 96 | i += validateIndentAndClosingBacket(i, indent); 97 | result += ")"; 98 | } else if (c === "|") { 99 | result += c; 100 | i += validateNewLineAndIndent(i, indent); 101 | } else { 102 | result += c; 103 | } 104 | } 105 | 106 | if (escaped || indent > 0) { 107 | throw new Error("invalid regex: still escaped or indent>0 at EOF"); 108 | } 109 | 110 | return JSON.stringify(result); 111 | }; 112 | 113 | const main = async () => { 114 | if (process.argv.length !== 4) { 115 | console.log(`Usage: ${process.argv[1]} `); 116 | process.exitCode = 1; 117 | return; 118 | } 119 | 120 | const [cmd, regexFile] = process.argv.slice(2); 121 | const regex = await fs.readFile(regexFile, "utf8"); 122 | 123 | let result; 124 | switch (cmd) { 125 | case "prettify": 126 | result = prettify(regex); 127 | break; 128 | case "minify": 129 | result = minify(regex); 130 | break; 131 | default: 132 | throw new Error("invalid command"); 133 | } 134 | 135 | console.log(result); 136 | }; 137 | 138 | main().catch((err) => { 139 | console.error(err); 140 | process.exitCode = 1; 141 | }); 142 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | function sqlTag() { 2 | interface Query { 3 | text: string; 4 | values: any[]; 5 | } 6 | 7 | const sql = ( 8 | strings: TemplateStringsArray, 9 | ...values: any[] 10 | ): Query => ({ 11 | text: String.raw(strings, ...values.map((_, i) => `$${i + 1}`)), 12 | values, 13 | }); 14 | 15 | const createOrder = (userId: string, notes: string | null) => sql` 16 | INSERT INTO orders( 17 | user_id 18 | notes, 19 | status, 20 | created_at, 21 | updated_at 22 | ) VALUES( 23 | ${userId}, 24 | ${notes}, 25 | 'created', 26 | NOW(), 27 | NOW() 28 | ) 29 | RETURNING * 30 | `; 31 | 32 | interface Order { 33 | order_id: number; 34 | } 35 | 36 | const getOrder = (orderId: number) => sql` 37 | SELECT 38 | * 39 | FROM 40 | orders 41 | WHERE 42 | order_id = ${orderId} 43 | `; 44 | 45 | const obj = { sql }; 46 | 47 | const getAllOrders = () => obj.sql` 48 | SELECT 49 | * 50 | FROM 51 | orders 52 | `; 53 | 54 | class Test { 55 | sql: typeof sql; 56 | #sql: typeof sql; 57 | run() { 58 | this.sql`SELECT * FROM users`; 59 | this.#sql`SELECT * FROM users`; 60 | } 61 | } 62 | } 63 | 64 | function sqlComment() { 65 | const createOrder = (userId: string, notes: string | null) => /* sql */ ` 66 | INSERT INTO orders( 67 | user_id 68 | notes, 69 | status, 70 | created_at, 71 | updated_at 72 | ) VALUES( 73 | ${userId}, 74 | ${notes}, 75 | 'created', 76 | NOW(), 77 | NOW() 78 | ) 79 | RETURNING * 80 | `; 81 | 82 | const createTable = () => /*sql*/ ` 83 | CREATE TABLE \`teams\` ( 84 | \`Id\` INT AUTO_INCREMENT PRIMARY KEY 85 | ) 86 | `; 87 | } 88 | 89 | function sqlFunction() { 90 | interface Query { 91 | name: string; 92 | text: string; 93 | values: any[]; 94 | } 95 | 96 | const sql = (name: string) => ( 97 | strings: TemplateStringsArray, 98 | ...values: any[] 99 | ): Query => ({ 100 | name, 101 | text: String.raw(strings, ...values.map((_, i) => `$${i + 1}`)), 102 | values, 103 | }); 104 | 105 | const createOrder = (userId: string, notes: string | null) => sql( 106 | "create-order" 107 | )` 108 | INSERT INTO orders( 109 | user_id 110 | notes, 111 | status, 112 | created_at, 113 | updated_at 114 | ) VALUES( 115 | ${userId}, 116 | ${notes}, 117 | 'created', 118 | NOW(), 119 | NOW() 120 | ) 121 | RETURNING * 122 | `; 123 | 124 | interface Order { 125 | order_id: number; 126 | } 127 | 128 | const getOrder = (orderId: number) => sql("get-order")` 129 | SELECT 130 | * 131 | FROM 132 | orders 133 | WHERE 134 | order_id = ${orderId} 135 | `; 136 | 137 | const obj = { sql }; 138 | 139 | const getAllOrders = () => obj.sql("get-all-orders")` 140 | SELECT * FROM orders 141 | `; 142 | 143 | class Test { 144 | sql: typeof sql; 145 | #sql: typeof sql; 146 | run() { 147 | this.sql("run")`SELECT * FROM users`; 148 | this.#sql("run")`SELECT * FROM users`; 149 | } 150 | } 151 | } 152 | 153 | function normalSqlFunction() { 154 | const test = () => "sql"; 155 | const sql = () => "sql"; 156 | 157 | const a = test(); 158 | const b = sql(); 159 | const c = test(); 160 | } 161 | --------------------------------------------------------------------------------