├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── banner.png ├── correlation.png └── workflows │ └── auto-release.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── cspell.json ├── global.d.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── src ├── index.ts └── packages │ ├── core-parts │ ├── index.ts │ └── package.json │ ├── v2-plugin │ ├── index.ts │ ├── package.json │ ├── parsers.ts │ └── printers.ts │ └── v3-plugin │ ├── index.ts │ ├── package.json │ ├── parsers.ts │ └── printers.ts ├── tests ├── test-settings │ ├── index.ts │ └── package.json ├── v2-test │ ├── adaptor.ts │ ├── angular │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── astro │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── babel │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── html │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── markdown │ │ ├── __snapshots__ │ │ │ └── single-plugin.test.ts.snap │ │ └── single-plugin.test.ts │ ├── mdx │ │ ├── __snapshots__ │ │ │ └── single-plugin.test.ts.snap │ │ └── single-plugin.test.ts │ ├── package.json │ ├── svelte │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── typescript │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts │ ├── vitest.config.mts │ └── vue │ │ ├── multiple-plugin.test.ts │ │ ├── single-plugin.test.ts │ │ └── zero-plugin.test.ts └── v3-test │ ├── adaptor.ts │ ├── angular │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── astro │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── babel │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── html │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── markdown │ ├── __snapshots__ │ │ └── single-plugin.test.ts.snap │ └── single-plugin.test.ts │ ├── mdx │ ├── __snapshots__ │ │ └── single-plugin.test.ts.snap │ └── single-plugin.test.ts │ ├── package.json │ ├── svelte │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── typescript │ ├── issue-36.test.ts │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts │ ├── vitest.config.mts │ └── vue │ ├── multiple-plugin.test.ts │ ├── single-plugin.test.ts │ └── zero-plugin.test.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.*js 2 | **/*.d.ts 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | }, 6 | "plugins": ["import"], 7 | "settings": { 8 | "import/parsers": { 9 | "@typescript-eslint/parser": [".ts", ".mts", ".cts", ".d.ts"] 10 | }, 11 | "import/resolver": { 12 | "node": { 13 | "extensions": [".js", ".ts"] 14 | }, 15 | "typescript": { 16 | "alwaysTryTypes": true 17 | } 18 | } 19 | }, 20 | "rules": { 21 | "@typescript-eslint/consistent-type-imports": 2, 22 | "@typescript-eslint/no-loop-func": 0, 23 | "import/prefer-default-export": 0, 24 | "no-restricted-syntax": 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Dependency information 10 | 11 | ## Prettier configuration 12 | 13 | ## Steps to reproduce 14 | 15 | ## The current behavior 16 | 17 | ## The expected behavior 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe 10 | 11 | ## Describe the solution you'd like 12 | 13 | ## Describe alternatives you've considered 14 | 15 | ## Additional context 16 | -------------------------------------------------------------------------------- /.github/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ony3000/prettier-plugin-merge/db5f52416cfade28b1b56e261dc720d342941442/.github/banner.png -------------------------------------------------------------------------------- /.github/correlation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ony3000/prettier-plugin-merge/db5f52416cfade28b1b56e261dc720d342941442/.github/correlation.png -------------------------------------------------------------------------------- /.github/workflows/auto-release.yml: -------------------------------------------------------------------------------- 1 | name: Automated Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseLevel: 7 | description: 'Release Level' 8 | required: true 9 | default: 'patch' 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | 15 | jobs: 16 | publish-npm: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | registry-url: https://registry.npmjs.org/ 24 | - uses: pnpm/action-setup@v2 25 | with: 26 | run_install: true 27 | - name: Build a bundle 28 | run: pnpm run build 29 | - name: Bump a package version as github-actions bot 30 | run: | 31 | git config user.name "github-actions[bot]" 32 | git config user.email "github-actions[bot]@users.noreply.github.com" 33 | npm version ${{ inputs.releaseLevel }} -m "chore: release %s" 34 | git push && git push --tags 35 | - name: Publish package 36 | run: npm publish 37 | env: 38 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,visualstudiocode,node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,visualstudiocode,node 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | # General 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### macOS Patch ### 49 | # iCloud generated files 50 | *.icloud 51 | 52 | ### Node ### 53 | # Logs 54 | logs 55 | *.log 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | lerna-debug.log* 60 | .pnpm-debug.log* 61 | 62 | # Diagnostic reports (https://nodejs.org/api/report.html) 63 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 64 | 65 | # Runtime data 66 | pids 67 | *.pid 68 | *.seed 69 | *.pid.lock 70 | 71 | # Directory for instrumented libs generated by jscoverage/JSCover 72 | lib-cov 73 | 74 | # Coverage directory used by tools like istanbul 75 | coverage 76 | *.lcov 77 | 78 | # nyc test coverage 79 | .nyc_output 80 | 81 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 82 | .grunt 83 | 84 | # Bower dependency directory (https://bower.io/) 85 | bower_components 86 | 87 | # node-waf configuration 88 | .lock-wscript 89 | 90 | # Compiled binary addons (https://nodejs.org/api/addons.html) 91 | build/Release 92 | 93 | # Dependency directories 94 | node_modules/ 95 | jspm_packages/ 96 | 97 | # Snowpack dependency directory (https://snowpack.dev/) 98 | web_modules/ 99 | 100 | # TypeScript cache 101 | *.tsbuildinfo 102 | 103 | # Optional npm cache directory 104 | .npm 105 | 106 | # Optional eslint cache 107 | .eslintcache 108 | 109 | # Optional stylelint cache 110 | .stylelintcache 111 | 112 | # Microbundle cache 113 | .rpt2_cache/ 114 | .rts2_cache_cjs/ 115 | .rts2_cache_es/ 116 | .rts2_cache_umd/ 117 | 118 | # Optional REPL history 119 | .node_repl_history 120 | 121 | # Output of 'npm pack' 122 | *.tgz 123 | 124 | # Yarn Integrity file 125 | .yarn-integrity 126 | 127 | # dotenv environment variable files 128 | .env 129 | .env.development.local 130 | .env.test.local 131 | .env.production.local 132 | .env.local 133 | 134 | # parcel-bundler cache (https://parceljs.org/) 135 | .cache 136 | .parcel-cache 137 | 138 | # Next.js build output 139 | .next 140 | out 141 | 142 | # Nuxt.js build / generate output 143 | .nuxt 144 | dist 145 | 146 | # Gatsby files 147 | .cache/ 148 | # Comment in the public line in if your project uses Gatsby and not Next.js 149 | # https://nextjs.org/blog/next-9-1#public-directory-support 150 | # public 151 | 152 | # vuepress build output 153 | .vuepress/dist 154 | 155 | # vuepress v2.x temp and cache directory 156 | .temp 157 | 158 | # Docusaurus cache and generated files 159 | .docusaurus 160 | 161 | # Serverless directories 162 | .serverless/ 163 | 164 | # FuseBox cache 165 | .fusebox/ 166 | 167 | # DynamoDB Local files 168 | .dynamodb/ 169 | 170 | # TernJS port file 171 | .tern-port 172 | 173 | # Stores VSCode versions used for testing VSCode extensions 174 | .vscode-test 175 | 176 | # yarn v2 177 | .yarn/cache 178 | .yarn/unplugged 179 | .yarn/build-state.yml 180 | .yarn/install-state.gz 181 | .pnp.* 182 | 183 | ### Node Patch ### 184 | # Serverless Webpack directories 185 | .webpack/ 186 | 187 | # Optional stylelint cache 188 | 189 | # SvelteKit build / generate output 190 | .svelte-kit 191 | 192 | ### VisualStudioCode ### 193 | .vscode/* 194 | !.vscode/settings.json 195 | !.vscode/tasks.json 196 | !.vscode/launch.json 197 | !.vscode/extensions.json 198 | !.vscode/*.code-snippets 199 | 200 | # Local History for Visual Studio Code 201 | .history/ 202 | 203 | # Built Visual Studio Code Extensions 204 | *.vsix 205 | 206 | ### VisualStudioCode Patch ### 207 | # Ignore all local history of files 208 | .history 209 | .ionide 210 | 211 | ### Windows ### 212 | # Windows thumbnail cache files 213 | Thumbs.db 214 | Thumbs.db:encryptable 215 | ehthumbs.db 216 | ehthumbs_vista.db 217 | 218 | # Dump file 219 | *.stackdump 220 | 221 | # Folder config file 222 | [Dd]esktop.ini 223 | 224 | # Recycle Bin used on file shares 225 | $RECYCLE.BIN/ 226 | 227 | # Windows Installer files 228 | *.cab 229 | *.msi 230 | *.msix 231 | *.msm 232 | *.msp 233 | 234 | # Windows shortcuts 235 | *.lnk 236 | 237 | # End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,visualstudiocode,node 238 | 239 | # Logs 240 | logs 241 | *.log 242 | npm-debug.log* 243 | yarn-debug.log* 244 | yarn-error.log* 245 | pnpm-debug.log* 246 | lerna-debug.log* 247 | 248 | node_modules 249 | dist 250 | dist-ssr 251 | *.local 252 | 253 | # Editor directories and files 254 | .idea 255 | .DS_Store 256 | *.suo 257 | *.ntvs* 258 | *.njsproj 259 | *.sln 260 | *.sw? 261 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "plugins": ["@trivago/prettier-plugin-sort-imports"], 6 | "importOrder": ["", "^@/(.*)$", "^\\.(.*)$"], 7 | "importOrderSeparation": true 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "editorconfig.editorconfig", 5 | "esbenp.prettier-vscode", 6 | "streetsidesoftware.code-spell-checker" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "eslint.workingDirectories": [{ "mode": "auto" }] 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hyeonjong 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 | # prettier-plugin-merge 2 | 3 | A Prettier plugin that sequentially merges the formatting results of other Prettier plugins. 4 | 5 | ![Schematic diagram of how formats are merged.](.github/banner.png) 6 | 7 | ## Why prettier-plugin-merge? 8 | 9 | Prettier has its limitations. If two or more plugins are configured to format a particular language, Prettier will only use the last of those plugins. 10 | 11 | So, for example, if you configure it like this for JavaScript formatting, only `prettier-plugin-classnames` will be used. 12 | 13 | 14 | ```jsonc 15 | { 16 | "plugins": [ 17 | "prettier-plugin-tailwindcss", 18 | "prettier-plugin-classnames" // Prettier only uses this 19 | ] 20 | } 21 | ``` 22 | 23 | However, you can overcome this limitation by adding `prettier-plugin-merge` as the last plugin. 24 | 25 | 26 | ```diff 27 | { 28 | "plugins": [ 29 | "prettier-plugin-tailwindcss", 30 | - "prettier-plugin-classnames" 31 | + "prettier-plugin-classnames", 32 | + "prettier-plugin-merge" 33 | ] 34 | } 35 | ``` 36 | 37 | As mentioned above, Prettier uses the last of the plugins that can format a particular language (in this case JavaScript), so `prettier-plugin-merge` is used in the changed configuration. 38 | 39 | 40 | ```jsonc 41 | { 42 | "plugins": [ 43 | "prettier-plugin-tailwindcss", 44 | "prettier-plugin-classnames", 45 | "prettier-plugin-merge" // Prettier only uses this 46 | ] 47 | } 48 | ``` 49 | 50 | `prettier-plugin-merge` uses plugins written before this one in order, merging as much as possible the differences in formatting results depending on the presence of the plugin. 51 | 52 | So you can combine as many plugins as you want. 53 | 54 | ## Installation 55 | 56 | For Prettier v2: 57 | 58 | ```sh 59 | npm install -D prettier@^2 prettier-plugin-merge 60 | ``` 61 | 62 | For Prettier v3: 63 | 64 | ```sh 65 | npm install -D prettier prettier-plugin-merge 66 | ``` 67 | 68 | ## Configuration 69 | 70 | **Note**: This plugin MUST come last. Other plugins usually have no order constraints. However, if there are multiple plugins formatting the same area, the output may vary depending on the order of those plugins. 71 | 72 | JSON example: 73 | 74 | 75 | ```json 76 | { 77 | "plugins": [ 78 | "prettier-plugin-tailwindcss", 79 | "prettier-plugin-classnames", 80 | "prettier-plugin-merge" 81 | ] 82 | } 83 | ``` 84 | 85 | JS example (CommonJS module): 86 | 87 | ```javascript 88 | module.exports = { 89 | plugins: [ 90 | '@trivago/prettier-plugin-sort-imports', 91 | 'prettier-plugin-brace-style', 92 | 'prettier-plugin-merge', 93 | ], 94 | braceStyle: 'stroustrup', 95 | }; 96 | ``` 97 | 98 | JS example (ES module): 99 | 100 | ```javascript 101 | export default { 102 | plugins: [ 103 | 'prettier-plugin-brace-style', 104 | '@trivago/prettier-plugin-sort-imports', 105 | 'prettier-plugin-merge', 106 | ], 107 | importOrder: ['', '^@[^/]+/(.*)$', '^@/(.*)$', '^[./]'], 108 | importOrderSeparation: true, 109 | }; 110 | ``` 111 | 112 | ## Version correlation with sibling plugins 113 | 114 | Starting with `0.6.0`, when there is a minor release on one side, I plan to reflect that change on the other side as well if possible. 115 | 116 | ![Version correlation.](.github/correlation.png) 117 | 118 | ## Compatibility with other Prettier plugins 119 | 120 | All other plugins used with this plugin must be compatible with your version of Prettier. 121 | 122 | For example, suppose you have three plugins: 123 | 124 | - `prettier-plugin-A`: Only compatible with Prettier v2 125 | - `prettier-plugin-B`: Only compatible with Prettier v3 126 | - `prettier-plugin-X`: Compatible with both versions 127 | 128 | Prettier v2 users can only configure `prettier-plugin-A` and `prettier-plugin-X`, and Prettier v3 users can only configure `prettier-plugin-B` and `prettier-plugin-X`. 129 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": ["allman", "tailwindcss", "trivago", "astro"] 5 | } 6 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | type PrettierBaseOptions = { 3 | printWidth: number; 4 | tabWidth: number; 5 | useTabs: boolean; 6 | semi: boolean; 7 | singleQuote: boolean; 8 | jsxSingleQuote: boolean; 9 | trailingComma: 'none' | 'es5' | 'all'; 10 | bracketSpacing: boolean; 11 | bracketSameLine: boolean; 12 | jsxBracketSameLine: boolean; 13 | rangeStart: number; 14 | rangeEnd: number; 15 | requirePragma: boolean; 16 | insertPragma: boolean; 17 | proseWrap: 'always' | 'never' | 'preserve'; 18 | arrowParens: 'avoid' | 'always'; 19 | htmlWhitespaceSensitivity: 'css' | 'strict' | 'ignore'; 20 | endOfLine: 'auto' | 'lf' | 'crlf' | 'cr'; 21 | quoteProps: 'as-needed' | 'consistent' | 'preserve'; 22 | vueIndentScriptAndStyle: boolean; 23 | embeddedLanguageFormatting: 'auto' | 'off'; 24 | singleAttributePerLine: boolean; 25 | }; 26 | 27 | type SupportedParserNames = 28 | | 'babel' 29 | | 'typescript' 30 | | 'angular' 31 | | 'html' 32 | | 'vue' 33 | | 'astro' 34 | | 'svelte'; 35 | 36 | type FormattedTextAST = { 37 | type: 'FormattedText'; 38 | body: string; 39 | }; 40 | } 41 | 42 | export {}; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier-plugin-merge", 3 | "version": "0.7.4", 4 | "description": "A Prettier plugin that sequentially merges the formatting results of other Prettier plugins.", 5 | "keywords": [ 6 | "prettier", 7 | "plugin", 8 | "merge", 9 | "sequential", 10 | "formatting", 11 | "multiple" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/ony3000/prettier-plugin-merge.git" 16 | }, 17 | "license": "MIT", 18 | "author": "Hyeonjong ", 19 | "main": "dist/index.js", 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "build": "pnpm run clean:bundle && esbuild src/index.ts --bundle --outdir=dist --platform=node \"--external:prettier\" --minify", 25 | "build:plain": "pnpm run clean:bundle && esbuild src/index.ts --bundle --outdir=dist --platform=node \"--external:prettier\"", 26 | "clean": "pnpm run clean:bundle && pnpm -r exec rimraf node_modules/ && rimraf package.merged.json", 27 | "clean:bundle": "rimraf dist/", 28 | "lint": "eslint src/ tests/", 29 | "merge-deps": "rimraf package.merged.json && merge-packages ./ src/packages/core-parts/ src/packages/v2-plugin/ src/packages/v3-plugin/", 30 | "prepare-release": "pnpm run build:plain && npm version prerelease --preid=alpha --git-tag-version=false && pnpm run merge-deps && npm pack" 31 | }, 32 | "dependencies": { 33 | "diff": "5.1.0" 34 | }, 35 | "devDependencies": { 36 | "@trivago/prettier-plugin-sort-imports": "4.2.1", 37 | "@types/diff": "5.0.3", 38 | "@types/node": "20.2.5", 39 | "@types/prettier": "2.7.3", 40 | "@typescript-eslint/eslint-plugin": "5.54.0", 41 | "@typescript-eslint/parser": "5.54.0", 42 | "esbuild": "0.19.11", 43 | "eslint": "8.35.0", 44 | "eslint-config-airbnb-base": "15.0.0", 45 | "eslint-config-airbnb-typescript": "17.0.0", 46 | "eslint-config-prettier": "8.6.0", 47 | "eslint-import-resolver-node": "0.3.7", 48 | "eslint-import-resolver-typescript": "3.5.3", 49 | "eslint-plugin-import": "2.27.5", 50 | "merge-packages": "0.1.6", 51 | "prettier": "2.8.4", 52 | "rimraf": "5.0.1", 53 | "typescript": "4.9.5" 54 | }, 55 | "peerDependencies": { 56 | "prettier": "^2 || ^3" 57 | }, 58 | "packageManager": "pnpm@8.5.1", 59 | "engines": { 60 | "node": ">=14" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'src/packages/*' 3 | - 'tests/*' 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-import-module-exports 2 | import prettier from 'prettier'; 3 | 4 | if (prettier.version.startsWith('2.')) { 5 | // eslint-disable-next-line global-require 6 | module.exports = require('./packages/v2-plugin'); 7 | } else { 8 | // eslint-disable-next-line global-require 9 | module.exports = require('./packages/v3-plugin'); 10 | } 11 | -------------------------------------------------------------------------------- /src/packages/core-parts/index.ts: -------------------------------------------------------------------------------- 1 | import * as Diff from 'diff'; 2 | 3 | export type SubstitutePatch = 4 | | { 5 | type: 'keep'; 6 | value: string; 7 | from?: undefined; 8 | to?: undefined; 9 | } 10 | | { 11 | type: 'change'; 12 | value?: undefined; 13 | from: string; 14 | to: string; 15 | }; 16 | 17 | export function makePatches(oldStr: string, newStr: string): SubstitutePatch[] { 18 | if (oldStr === newStr) { 19 | return []; 20 | } 21 | 22 | const patches: SubstitutePatch[] = []; 23 | let temporaryText: string | null = null; 24 | 25 | Diff.diffLines(oldStr, newStr).forEach((change) => { 26 | if (change.added && change.removed) { 27 | throw new Error('Unexpected change'); 28 | } else if (change.removed) { 29 | if (temporaryText !== null) { 30 | patches.push({ type: 'change', from: temporaryText, to: '' }); 31 | } 32 | temporaryText = change.value; 33 | } else if (change.added) { 34 | if (temporaryText === null) { 35 | patches.push({ type: 'change', from: '', to: change.value }); 36 | } else { 37 | patches.push({ type: 'change', from: temporaryText, to: change.value }); 38 | temporaryText = null; 39 | } 40 | } else { 41 | if (temporaryText !== null) { 42 | patches.push({ type: 'change', from: temporaryText, to: '' }); 43 | temporaryText = null; 44 | } 45 | patches.push({ type: 'keep', value: change.value }); 46 | } 47 | }); 48 | 49 | return patches; 50 | } 51 | 52 | export function applyPatches(text: string, patchesPerPlugin: SubstitutePatch[][]): string { 53 | return patchesPerPlugin.reduce((patchedPrevText, patches) => { 54 | if (patches.length === 0) { 55 | return patchedPrevText; 56 | } 57 | 58 | let mutablePrevText = patchedPrevText; 59 | let scannedLength = 0; 60 | let conflictingPatches: SubstitutePatch[] = []; 61 | 62 | patches.forEach((patch) => { 63 | const scannedText = mutablePrevText.slice(0, scannedLength); 64 | const unScannedText = mutablePrevText.slice(scannedLength); 65 | 66 | if (patch.type === 'keep') { 67 | if (unScannedText.indexOf(patch.value) === -1) { 68 | let diffLength = 0; 69 | 70 | Diff.diffWords(patch.value, unScannedText.slice(0, patch.value.length * 2)) 71 | .slice(0, -1) 72 | .forEach(({ added, removed, value }) => { 73 | if (added) { 74 | diffLength += value.length; 75 | } else if (removed) { 76 | diffLength -= value.length; 77 | } 78 | }); 79 | 80 | scannedLength += patch.value.length + diffLength; 81 | } else { 82 | scannedLength += patch.value.length; 83 | } 84 | 85 | if (conflictingPatches.length) { 86 | conflictingPatches.push({ 87 | type: 'change', 88 | from: patch.value, 89 | to: patch.value, 90 | }); 91 | } 92 | } else { 93 | if (unScannedText.indexOf(patch.from) === -1) { 94 | let diffLength = 0; 95 | 96 | Diff.diffWords(patch.from, unScannedText.slice(0, patch.from.length * 2)) 97 | .slice(0, -1) 98 | .forEach(({ added, removed, value }) => { 99 | if (added) { 100 | diffLength += value.length; 101 | } else if (removed) { 102 | diffLength -= value.length; 103 | } 104 | }); 105 | 106 | scannedLength += patch.from.length + diffLength; 107 | conflictingPatches.push(patch); 108 | 109 | const conflictingFromText = conflictingPatches.map(({ from }) => from).join(''); 110 | const conflictingToText = conflictingPatches.map(({ to }) => to).join(''); 111 | 112 | const wordDiffs = Diff.diffWords(conflictingFromText, conflictingToText); 113 | const removedTextWithoutSpaces = wordDiffs 114 | .filter(({ removed }) => removed) 115 | .map(({ value }) => value.trim()) 116 | .join(''); 117 | const addedTextWithoutSpaces = wordDiffs 118 | .filter(({ added }) => added) 119 | .map(({ value }) => value.trim()) 120 | .join(''); 121 | 122 | if (removedTextWithoutSpaces === addedTextWithoutSpaces) { 123 | conflictingPatches = []; 124 | } else { 125 | // Note: A case study is needed. 126 | } 127 | } else { 128 | if (conflictingPatches.length === 0) { 129 | mutablePrevText = `${scannedText}${unScannedText.replace( 130 | patch.from, 131 | patch.to.replace(/\$/g, '$$$$'), 132 | )}`; 133 | scannedLength += patch.to.length; 134 | } else { 135 | conflictingPatches.push(patch); 136 | 137 | const conflictingFromText = conflictingPatches.map(({ from }) => from).join(''); 138 | const conflictingToText = conflictingPatches.map(({ to }) => to).join(''); 139 | 140 | const wordDiffs = Diff.diffWords(conflictingFromText, conflictingToText); 141 | const removedTextWithoutSpaces = wordDiffs 142 | .filter(({ removed }) => removed) 143 | .map(({ value }) => value.trim()) 144 | .join(''); 145 | const addedTextWithoutSpaces = wordDiffs 146 | .filter(({ added }) => added) 147 | .map(({ value }) => value.trim()) 148 | .join(''); 149 | 150 | if (removedTextWithoutSpaces === addedTextWithoutSpaces) { 151 | scannedLength += patch.from.length; 152 | conflictingPatches = []; 153 | } else { 154 | // Note: A case study is needed. 155 | } 156 | } 157 | } 158 | } 159 | }); 160 | 161 | return mutablePrevText; 162 | }, text); 163 | } 164 | -------------------------------------------------------------------------------- /src/packages/core-parts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "core-parts", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "diff": "5.1.0" 9 | }, 10 | "devDependencies": { 11 | "@types/diff": "5.0.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/packages/v2-plugin/index.ts: -------------------------------------------------------------------------------- 1 | export { parsers } from './parsers'; 2 | export { printers } from './printers'; 3 | -------------------------------------------------------------------------------- /src/packages/v2-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "v2-plugin", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "core-parts": "workspace:*", 9 | "prettier": "2.8.4" 10 | }, 11 | "devDependencies": { 12 | "@types/prettier": "2.7.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/packages/v2-plugin/parsers.ts: -------------------------------------------------------------------------------- 1 | import type { SubstitutePatch } from 'core-parts'; 2 | import { makePatches, applyPatches } from 'core-parts'; 3 | import type { Parser, ParserOptions, Plugin } from 'prettier'; 4 | import { format } from 'prettier'; 5 | import { parsers as babelParsers } from 'prettier/parser-babel'; 6 | import { parsers as htmlParsers } from 'prettier/parser-html'; 7 | import { parsers as typescriptParsers } from 'prettier/parser-typescript'; 8 | 9 | const EOL = '\n'; 10 | 11 | function formatAsCodeblock(text: string, options: ParserOptions, plugins?: Plugin[]) { 12 | let codeblockStart = '```'; 13 | const codeblockEnd = '```'; 14 | 15 | if (options.parser === 'babel') { 16 | codeblockStart = '```jsx'; 17 | } else if (options.parser === 'typescript') { 18 | codeblockStart = '```tsx'; 19 | } 20 | 21 | const formattedCodeblock = format(`${codeblockStart}${EOL}${text}${EOL}${codeblockEnd}`, { 22 | ...options, 23 | plugins: plugins ?? [], 24 | rangeEnd: Infinity, 25 | endOfLine: 'lf', 26 | parser: options.parentParser, 27 | parentParser: undefined, 28 | }); 29 | const formattedText = formattedCodeblock 30 | .trim() 31 | .slice(`${codeblockStart}${EOL}`.length, -`${EOL}${codeblockEnd}`.length); 32 | 33 | return formattedText; 34 | } 35 | 36 | function sequentialFormattingAndTryMerging( 37 | options: ParserOptions, 38 | plugins: Plugin[], 39 | languageImplementedPlugin?: Plugin, 40 | ): string { 41 | const customLanguageSupportedPlugins = languageImplementedPlugin 42 | ? [languageImplementedPlugin] 43 | : []; 44 | 45 | const { originalText } = options; 46 | const sequentialFormattingOptions = { 47 | ...options, 48 | rangeEnd: Infinity, 49 | endOfLine: 'lf' as const, 50 | plugins: customLanguageSupportedPlugins, 51 | }; 52 | 53 | const firstFormattedText = 54 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 55 | ? formatAsCodeblock(originalText, options) 56 | : format(originalText, sequentialFormattingOptions); 57 | 58 | /** 59 | * List of output differences according to the presence or absence of each plugin. 60 | */ 61 | const patchesPerPlugin: SubstitutePatch[][] = []; 62 | 63 | const sequentiallyMergedText = plugins.reduce((formattedPrevText, plugin) => { 64 | const temporaryFormattedText = 65 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 66 | ? formatAsCodeblock(formattedPrevText, sequentialFormattingOptions, [ 67 | ...customLanguageSupportedPlugins, 68 | plugin, 69 | ]) 70 | : format(formattedPrevText, { 71 | ...sequentialFormattingOptions, 72 | plugins: [...customLanguageSupportedPlugins, plugin], 73 | }); 74 | 75 | const temporaryFormattedTextWithoutPlugin = 76 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 77 | ? formatAsCodeblock(temporaryFormattedText, sequentialFormattingOptions) 78 | : format(temporaryFormattedText, sequentialFormattingOptions); 79 | 80 | patchesPerPlugin.push(makePatches(temporaryFormattedTextWithoutPlugin, temporaryFormattedText)); 81 | 82 | return applyPatches(temporaryFormattedTextWithoutPlugin, patchesPerPlugin); 83 | }, firstFormattedText); 84 | 85 | return sequentiallyMergedText; 86 | } 87 | 88 | function transformParser( 89 | parserName: SupportedParserNames, 90 | defaultParser: Parser, 91 | languageName?: string, 92 | ): Parser { 93 | return { 94 | ...defaultParser, 95 | parse: ( 96 | text: string, 97 | parsers: { [parserName: string]: Parser }, 98 | options: ParserOptions, 99 | ): FormattedTextAST => { 100 | const plugins = options.plugins.filter((plugin) => typeof plugin !== 'string') as Plugin[]; 101 | const pluginIndex = plugins.findIndex( 102 | (plugin) => 103 | Object.values(plugin.parsers ?? {}).every( 104 | (parser) => parser.astFormat === 'merging-ast', 105 | ) && 106 | Object.entries(plugin.printers ?? {}).every( 107 | ([astFormat, printer]) => 108 | astFormat === 'merging-ast' && Object.keys(printer).every((key) => key === 'print'), 109 | ), 110 | ); 111 | 112 | if (pluginIndex === -1) { 113 | throw new Error( 114 | "The structure of this plugin may have changed. If it's not in development, you may need to report an issue.", 115 | ); 116 | } 117 | 118 | let languageImplementedPlugin: Plugin | undefined; 119 | if (languageName) { 120 | languageImplementedPlugin = plugins 121 | .slice(0, pluginIndex) 122 | .filter((plugin) => plugin.languages?.some((language) => language.name === languageName)) 123 | .at(0); 124 | 125 | if (!languageImplementedPlugin) { 126 | throw new Error( 127 | `There doesn't seem to be any plugin that supports ${languageName} formatting.`, 128 | ); 129 | } 130 | } 131 | 132 | const parserImplementedPlugins = plugins 133 | .slice(0, pluginIndex) 134 | .filter((plugin) => plugin.parsers?.[parserName]); 135 | const result = sequentialFormattingAndTryMerging( 136 | { 137 | ...options, 138 | originalText: text, 139 | }, 140 | parserImplementedPlugins, 141 | languageImplementedPlugin, 142 | ); 143 | 144 | return { 145 | type: 'FormattedText', 146 | body: result, 147 | }; 148 | }, 149 | astFormat: 'merging-ast', 150 | }; 151 | } 152 | 153 | export const parsers: { [parserName: string]: Parser } = { 154 | babel: transformParser('babel', babelParsers.babel), 155 | typescript: transformParser('typescript', typescriptParsers.typescript), 156 | angular: transformParser('angular', htmlParsers.angular), 157 | html: transformParser('html', htmlParsers.html), 158 | vue: transformParser('vue', htmlParsers.vue), 159 | astro: transformParser('astro', {} as Parser, 'astro'), 160 | svelte: transformParser('svelte', {} as Parser, 'svelte'), 161 | }; 162 | -------------------------------------------------------------------------------- /src/packages/v2-plugin/printers.ts: -------------------------------------------------------------------------------- 1 | import type { AstPath, ParserOptions, Doc, Printer } from 'prettier'; 2 | 3 | function createPrinter(): Printer { 4 | function main( 5 | path: AstPath, 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | options: ParserOptions, 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | print: (path: AstPath) => Doc, 10 | ): Doc { 11 | const node = path.getValue(); 12 | 13 | if (node.type === 'FormattedText') { 14 | return node.body; 15 | } 16 | 17 | throw new Error(`Unknown node type: ${node?.type}`); 18 | } 19 | 20 | return { 21 | print: main, 22 | }; 23 | } 24 | 25 | export const printers: { [astFormat: string]: Printer } = { 26 | 'merging-ast': createPrinter(), 27 | }; 28 | -------------------------------------------------------------------------------- /src/packages/v3-plugin/index.ts: -------------------------------------------------------------------------------- 1 | export { parsers } from './parsers'; 2 | export { printers } from './printers'; 3 | -------------------------------------------------------------------------------- /src/packages/v3-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "v3-plugin", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "core-parts": "workspace:*", 9 | "prettier": "3.0.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/packages/v3-plugin/parsers.ts: -------------------------------------------------------------------------------- 1 | import type { SubstitutePatch } from 'core-parts'; 2 | import { makePatches, applyPatches } from 'core-parts'; 3 | import type { Parser, ParserOptions, Plugin } from 'prettier'; 4 | import { format } from 'prettier'; 5 | import { parsers as babelParsers } from 'prettier/plugins/babel'; 6 | import { parsers as htmlParsers } from 'prettier/plugins/html'; 7 | import { parsers as typescriptParsers } from 'prettier/plugins/typescript'; 8 | 9 | const EOL = '\n'; 10 | 11 | async function formatAsCodeblock(text: string, options: ParserOptions, plugins?: Plugin[]) { 12 | let codeblockStart = '```'; 13 | const codeblockEnd = '```'; 14 | 15 | if (options.parser === 'babel') { 16 | codeblockStart = '```jsx'; 17 | } else if (options.parser === 'typescript') { 18 | codeblockStart = '```tsx'; 19 | } 20 | 21 | const formattedCodeblock = await format(`${codeblockStart}${EOL}${text}${EOL}${codeblockEnd}`, { 22 | ...options, 23 | plugins: plugins ?? [], 24 | rangeEnd: Infinity, 25 | endOfLine: 'lf', 26 | parser: options.parentParser, 27 | parentParser: undefined, 28 | }); 29 | const formattedText = formattedCodeblock 30 | .trim() 31 | .slice(`${codeblockStart}${EOL}`.length, -`${EOL}${codeblockEnd}`.length); 32 | 33 | return formattedText; 34 | } 35 | 36 | async function sequentialFormattingAndTryMerging( 37 | options: ParserOptions, 38 | plugins: Plugin[], 39 | languageImplementedPlugin?: Plugin, 40 | ): Promise { 41 | const customLanguageSupportedPlugins = languageImplementedPlugin 42 | ? [languageImplementedPlugin] 43 | : []; 44 | 45 | const { originalText } = options; 46 | const sequentialFormattingOptions = { 47 | ...options, 48 | rangeEnd: Infinity, 49 | endOfLine: 'lf' as const, 50 | plugins: customLanguageSupportedPlugins, 51 | }; 52 | 53 | const firstFormattedTextPromise = 54 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 55 | ? formatAsCodeblock(originalText, options) 56 | : format(originalText, sequentialFormattingOptions); 57 | 58 | /** 59 | * List of output differences according to the presence or absence of each plugin. 60 | */ 61 | const patchesPerPlugin: SubstitutePatch[][] = []; 62 | 63 | const sequentiallyMergedText = await plugins.reduce>( 64 | async (formattedPrevTextPromise, plugin) => { 65 | const formattedPrevText = await formattedPrevTextPromise; 66 | 67 | const temporaryFormattedText = 68 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 69 | ? await formatAsCodeblock(formattedPrevText, sequentialFormattingOptions, [ 70 | ...customLanguageSupportedPlugins, 71 | plugin, 72 | ]) 73 | : await format(formattedPrevText, { 74 | ...sequentialFormattingOptions, 75 | plugins: [...customLanguageSupportedPlugins, plugin], 76 | }); 77 | 78 | const temporaryFormattedTextWithoutPlugin = 79 | options.parentParser === 'markdown' || options.parentParser === 'mdx' 80 | ? await formatAsCodeblock(temporaryFormattedText, sequentialFormattingOptions) 81 | : await format(temporaryFormattedText, sequentialFormattingOptions); 82 | 83 | patchesPerPlugin.push( 84 | makePatches(temporaryFormattedTextWithoutPlugin, temporaryFormattedText), 85 | ); 86 | 87 | return applyPatches(temporaryFormattedTextWithoutPlugin, patchesPerPlugin); 88 | }, 89 | firstFormattedTextPromise, 90 | ); 91 | 92 | return sequentiallyMergedText; 93 | } 94 | 95 | function transformParser( 96 | parserName: SupportedParserNames, 97 | defaultParser: Parser, 98 | languageName?: string, 99 | ): Parser { 100 | return { 101 | ...defaultParser, 102 | parse: async (text: string, options: ParserOptions): Promise => { 103 | const plugins = options.plugins.filter((plugin) => typeof plugin !== 'string') as Plugin[]; 104 | const pluginIndex = plugins.findIndex( 105 | (plugin) => 106 | Object.values(plugin.parsers ?? {}).every( 107 | (parser) => parser.astFormat === 'merging-ast', 108 | ) && 109 | Object.entries(plugin.printers ?? {}).every( 110 | ([astFormat, printer]) => 111 | astFormat === 'merging-ast' && Object.keys(printer).every((key) => key === 'print'), 112 | ), 113 | ); 114 | 115 | if (pluginIndex === -1) { 116 | throw new Error( 117 | "The structure of this plugin may have changed. If it's not in development, you may need to report an issue.", 118 | ); 119 | } 120 | 121 | let languageImplementedPlugin: Plugin | undefined; 122 | if (languageName) { 123 | languageImplementedPlugin = plugins 124 | .slice(0, pluginIndex) 125 | .filter((plugin) => plugin.languages?.some((language) => language.name === languageName)) 126 | .at(0); 127 | 128 | if (!languageImplementedPlugin) { 129 | throw new Error( 130 | `There doesn't seem to be any plugin that supports ${languageName} formatting.`, 131 | ); 132 | } 133 | } 134 | 135 | const parserImplementedPlugins = plugins 136 | .slice(0, pluginIndex) 137 | .filter((plugin) => plugin.parsers?.[parserName]); 138 | const result = await sequentialFormattingAndTryMerging( 139 | { 140 | ...options, 141 | originalText: text, 142 | }, 143 | parserImplementedPlugins, 144 | languageImplementedPlugin, 145 | ); 146 | 147 | return { 148 | type: 'FormattedText', 149 | body: result, 150 | }; 151 | }, 152 | astFormat: 'merging-ast', 153 | }; 154 | } 155 | 156 | export const parsers: { [parserName: string]: Parser } = { 157 | babel: transformParser('babel', babelParsers.babel), 158 | typescript: transformParser('typescript', typescriptParsers.typescript), 159 | angular: transformParser('angular', htmlParsers.angular), 160 | html: transformParser('html', htmlParsers.html), 161 | vue: transformParser('vue', htmlParsers.vue), 162 | astro: transformParser('astro', {} as Parser, 'astro'), 163 | svelte: transformParser('svelte', {} as Parser, 'svelte'), 164 | }; 165 | -------------------------------------------------------------------------------- /src/packages/v3-plugin/printers.ts: -------------------------------------------------------------------------------- 1 | import type { AstPath, ParserOptions, Doc, Printer } from 'prettier'; 2 | 3 | function createPrinter(): Printer { 4 | function main( 5 | path: AstPath, 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | options: ParserOptions, 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | print: (path: AstPath) => Doc, 10 | ): Doc { 11 | const { node } = path; 12 | 13 | if (node.type === 'FormattedText') { 14 | return node.body; 15 | } 16 | 17 | throw new Error(`Unknown node type: ${node?.type}`); 18 | } 19 | 20 | return { 21 | print: main, 22 | }; 23 | } 24 | 25 | export const printers: { [astFormat: string]: Printer } = { 26 | 'merging-ast': createPrinter(), 27 | }; 28 | -------------------------------------------------------------------------------- /tests/test-settings/index.ts: -------------------------------------------------------------------------------- 1 | export const baseOptions: PrettierBaseOptions = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: false, 7 | jsxSingleQuote: false, 8 | trailingComma: 'all', 9 | bracketSpacing: true, 10 | bracketSameLine: false, 11 | jsxBracketSameLine: false, 12 | rangeStart: 0, 13 | rangeEnd: Infinity, 14 | requirePragma: false, 15 | insertPragma: false, 16 | proseWrap: 'preserve', 17 | arrowParens: 'always', 18 | htmlWhitespaceSensitivity: 'css', 19 | endOfLine: 'lf', 20 | quoteProps: 'as-needed', 21 | vueIndentScriptAndStyle: false, 22 | embeddedLanguageFormatting: 'auto', 23 | singleAttributePerLine: false, 24 | }; 25 | 26 | export type Fixture = { 27 | name: string; 28 | input: string; 29 | output: string; 30 | options?: Partial; 31 | }; 32 | -------------------------------------------------------------------------------- /tests/test-settings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "test-settings", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /tests/v2-test/adaptor.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'prettier'; 2 | import { format } from 'prettier'; 3 | import { parsers as babelParsers } from 'prettier/parser-babel'; 4 | import { parsers as htmlParsers } from 'prettier/parser-html'; 5 | import { parsers as typescriptParsers } from 'prettier/parser-typescript'; 6 | import type { Fixture } from 'test-settings'; 7 | import { describe, expect, onTestFailed, test } from 'vitest'; 8 | 9 | // eslint-disable-next-line import/no-extraneous-dependencies 10 | import * as thisPlugin from '@/packages/v2-plugin'; 11 | 12 | export { thisPlugin }; 13 | 14 | export const noopPlugin: Plugin = { 15 | parsers: { 16 | babel: babelParsers.babel, 17 | typescript: typescriptParsers.typescript, 18 | angular: htmlParsers.angular, 19 | html: htmlParsers.html, 20 | vue: htmlParsers.vue, 21 | }, 22 | }; 23 | 24 | export const sortImportsPluginOptions = { 25 | importOrder: ['', '^@[^/]+/(.*)$', '^@/(.*)$', '^[./]'], 26 | importOrderSeparation: true, 27 | }; 28 | 29 | export const braceStylePluginOptions = { 30 | braceStyle: 'allman', 31 | }; 32 | 33 | export const classnamesPluginOptions = { 34 | endingPosition: 'absolute', 35 | }; 36 | 37 | export function testEach(fixtures: Fixture[], options: PrettierBaseOptions & { parser: string }) { 38 | test.each(fixtures)('$name', ({ input, output, options: fixtureOptions }) => { 39 | const fixedOptions = { 40 | ...options, 41 | ...(fixtureOptions ?? {}), 42 | }; 43 | const formattedText = format(input, fixedOptions); 44 | 45 | expect(formattedText).toBe(output); 46 | }); 47 | } 48 | 49 | export function testSnapshotEach( 50 | fixtures: Omit[], 51 | options: PrettierBaseOptions & { parser: string }, 52 | ) { 53 | describe.each(fixtures)('$name', ({ input, options: fixtureOptions }) => { 54 | const fixedOptions = { 55 | ...options, 56 | ...(fixtureOptions ?? {}), 57 | }; 58 | const formattedText = format(input, fixedOptions); 59 | let skipSecondTest = false; 60 | 61 | test('expectation', () => { 62 | onTestFailed(() => { 63 | skipSecondTest = true; 64 | }); 65 | 66 | expect(formattedText).toMatchSnapshot(); 67 | }); 68 | 69 | test('consistency; if there are no plugins or only one, adding a merge plugin should have the same result', ({ 70 | skip, 71 | }) => { 72 | const fixedPlugins = fixedOptions.plugins ?? []; 73 | 74 | if (skipSecondTest || fixedPlugins.length > 1) { 75 | skip(); 76 | } 77 | 78 | const formattedTextWithThisPlugin = format(formattedText, { 79 | ...fixedOptions, 80 | plugins: [...fixedPlugins, thisPlugin], 81 | }); 82 | 83 | expect(formattedTextWithThisPlugin).toBe(formattedText); 84 | }); 85 | }); 86 | } 87 | 88 | export function testErrorEach( 89 | fixtures: Fixture[], 90 | options: PrettierBaseOptions & { parser: string }, 91 | ) { 92 | test.each(fixtures)('$name', ({ input, options: fixtureOptions }) => { 93 | const fixedOptions = { 94 | ...options, 95 | ...(fixtureOptions ?? {}), 96 | }; 97 | 98 | expect(() => format(input, fixedOptions)).toThrowError(); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /tests/v2-test/angular/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - tailwindcss -> classnames', 14 | input: ` 15 | 20 | `, 21 | output: ` 29 | `, 30 | options: { 31 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-classnames', thisPlugin], 32 | ...classnamesPluginOptions, 33 | }, 34 | }, 35 | { 36 | name: 'two plugins with some overlapping formatting regions (2) - classnames -> tailwindcss', 37 | input: ` 38 | 43 | `, 44 | output: ` 51 | `, 52 | options: { 53 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-tailwindcss', thisPlugin], 54 | ...classnamesPluginOptions, 55 | }, 56 | }, 57 | ]; 58 | 59 | testEach(fixtures, options); 60 | -------------------------------------------------------------------------------- /tests/v2-test/angular/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, noopPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'tailwindcss plugin (1) - standalone use', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: ['prettier-plugin-tailwindcss'], 31 | }, 32 | }, 33 | { 34 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 52 | }, 53 | }, 54 | { 55 | name: 'classnames plugin (1) - standalone use', 56 | input: ` 57 | 62 | `, 63 | output: ` 71 | `, 72 | options: { 73 | plugins: ['prettier-plugin-classnames'], 74 | ...classnamesPluginOptions, 75 | }, 76 | }, 77 | { 78 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 79 | input: ` 80 | 85 | `, 86 | output: ` 94 | `, 95 | options: { 96 | plugins: ['prettier-plugin-classnames', thisPlugin], 97 | ...classnamesPluginOptions, 98 | }, 99 | }, 100 | { 101 | name: 'issue #31 (1) - standalone use', 102 | input: ` 103 | 108 | `, 109 | output: `\r\n`, 110 | options: { 111 | plugins: [noopPlugin], 112 | endOfLine: 'crlf', 113 | }, 114 | }, 115 | { 116 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 117 | input: ` 118 | 123 | `, 124 | output: `\r\n`, 125 | options: { 126 | plugins: [noopPlugin, thisPlugin], 127 | endOfLine: 'crlf', 128 | }, 129 | }, 130 | ]; 131 | 132 | testEach(fixtures, options); 133 | -------------------------------------------------------------------------------- /tests/v2-test/angular/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: [], 31 | }, 32 | }, 33 | { 34 | name: 'no plugins (2) - merge plugin alone has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: [thisPlugin], 52 | }, 53 | }, 54 | ]; 55 | 56 | testEach(fixtures, options); 57 | -------------------------------------------------------------------------------- /tests/v2-test/astro/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, braceStylePluginOptions, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - astro -> brace-style', 14 | input: ` 15 | --- 16 | function getDate() { 17 | return new Date(); 18 | } 19 | 20 | const now = getDate(); 21 | --- 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: `--- 28 | function getDate() 29 | { 30 | return new Date(); 31 | } 32 | 33 | const now = getDate(); 34 | --- 35 | 36 |
39 | Now: {now} 40 |
41 | `, 42 | options: { 43 | plugins: ['prettier-plugin-astro', 'prettier-plugin-brace-style', thisPlugin], 44 | ...braceStylePluginOptions, 45 | }, 46 | }, 47 | { 48 | name: 'two plugins with some overlapping formatting regions (2) - brace-style -> astro', 49 | input: ` 50 | --- 51 | function getDate() { 52 | return new Date(); 53 | } 54 | 55 | const now = getDate(); 56 | --- 57 | 58 |
59 | Now: {now} 60 |
61 | `, 62 | output: `--- 63 | function getDate() 64 | { 65 | return new Date(); 66 | } 67 | 68 | const now = getDate(); 69 | --- 70 | 71 |
74 | Now: {now} 75 |
76 | `, 77 | options: { 78 | plugins: ['prettier-plugin-brace-style', 'prettier-plugin-astro', thisPlugin], 79 | ...braceStylePluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'two plugins with some overlapping formatting regions (3) - astro -> tailwindcss', 84 | input: ` 85 | --- 86 | function getDate() { 87 | return new Date(); 88 | } 89 | 90 | const now = getDate(); 91 | --- 92 | 93 |
94 | Now: {now} 95 |
96 | `, 97 | output: `--- 98 | function getDate() { 99 | return new Date(); 100 | } 101 | 102 | const now = getDate(); 103 | --- 104 | 105 |
108 | Now: {now} 109 |
110 | `, 111 | options: { 112 | plugins: ['prettier-plugin-astro', 'prettier-plugin-tailwindcss', thisPlugin], 113 | }, 114 | }, 115 | { 116 | name: 'two plugins with some overlapping formatting regions (4) - tailwindcss -> astro', 117 | input: ` 118 | --- 119 | function getDate() { 120 | return new Date(); 121 | } 122 | 123 | const now = getDate(); 124 | --- 125 | 126 |
127 | Now: {now} 128 |
129 | `, 130 | output: `--- 131 | function getDate() { 132 | return new Date(); 133 | } 134 | 135 | const now = getDate(); 136 | --- 137 | 138 |
141 | Now: {now} 142 |
143 | `, 144 | options: { 145 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-astro', thisPlugin], 146 | }, 147 | }, 148 | { 149 | name: 'two plugins with some overlapping formatting regions (5) - astro -> classnames', 150 | input: ` 151 | --- 152 | function getDate() { 153 | return new Date(); 154 | } 155 | 156 | const now = getDate(); 157 | --- 158 | 159 |
160 | Now: {now} 161 |
162 | `, 163 | output: `--- 164 | function getDate() { 165 | return new Date(); 166 | } 167 | 168 | const now = getDate(); 169 | --- 170 | 171 |
175 | Now: {now} 176 |
177 | `, 178 | options: { 179 | plugins: ['prettier-plugin-astro', 'prettier-plugin-classnames', thisPlugin], 180 | ...classnamesPluginOptions, 181 | }, 182 | }, 183 | { 184 | name: 'two plugins with some overlapping formatting regions (6) - classnames -> astro', 185 | input: ` 186 | --- 187 | function getDate() { 188 | return new Date(); 189 | } 190 | 191 | const now = getDate(); 192 | --- 193 | 194 |
195 | Now: {now} 196 |
197 | `, 198 | output: `--- 199 | function getDate() { 200 | return new Date(); 201 | } 202 | 203 | const now = getDate(); 204 | --- 205 | 206 |
210 | Now: {now} 211 |
212 | `, 213 | options: { 214 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-astro', thisPlugin], 215 | ...classnamesPluginOptions, 216 | }, 217 | }, 218 | ]; 219 | 220 | testEach(fixtures, options); 221 | -------------------------------------------------------------------------------- /tests/v2-test/astro/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'astro plugin (1) - standalone use', 14 | input: ` 15 | --- 16 | function getDate() { 17 | return new Date(); 18 | } 19 | 20 | const now = getDate(); 21 | --- 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: `--- 28 | function getDate() { 29 | return new Date(); 30 | } 31 | 32 | const now = getDate(); 33 | --- 34 | 35 |
38 | Now: {now} 39 |
40 | `, 41 | options: { 42 | plugins: ['prettier-plugin-astro'], 43 | }, 44 | }, 45 | { 46 | name: 'astro plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 47 | input: ` 48 | --- 49 | function getDate() { 50 | return new Date(); 51 | } 52 | 53 | const now = getDate(); 54 | --- 55 | 56 |
57 | Now: {now} 58 |
59 | `, 60 | output: `--- 61 | function getDate() { 62 | return new Date(); 63 | } 64 | 65 | const now = getDate(); 66 | --- 67 | 68 |
71 | Now: {now} 72 |
73 | `, 74 | options: { 75 | plugins: ['prettier-plugin-astro', thisPlugin], 76 | }, 77 | }, 78 | ]; 79 | 80 | testEach(fixtures, options); 81 | -------------------------------------------------------------------------------- /tests/v2-test/astro/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testErrorEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1) - it will not work without the astro plugin', 14 | input: `any input`, 15 | output: `any output`, 16 | options: { 17 | plugins: [], 18 | }, 19 | }, 20 | { 21 | name: 'no plugins (2) - it will not work without the astro plugin', 22 | input: `this plugin does not have`, 23 | output: `a built-in astro compiler (or parser).`, 24 | options: { 25 | plugins: [thisPlugin], 26 | }, 27 | }, 28 | ]; 29 | 30 | testErrorEach(fixtures, options); 31 | -------------------------------------------------------------------------------- /tests/v2-test/babel/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'babel', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | import { CounterButton } from './parts'; 23 | import { CounterContainer } from '@/layouts'; 24 | import { useState } from 'react'; 25 | 26 | export default function Counter({ 27 | label = 'Counter', 28 | onChange = undefined, 29 | }) { 30 | const [count, setCount] = useState(0); 31 | 32 | const incrementHandler = () => { 33 | setCount((c) => c + 1); 34 | onChange?.(count + 1); 35 | }; 36 | 37 | return ( 38 | 39 | {label} 40 | {count} 41 | 42 | 43 | ); 44 | } 45 | `, 46 | output: `import { useState } from "react"; 47 | 48 | import { CounterContainer } from "@/layouts"; 49 | 50 | import { CounterButton } from "./parts"; 51 | 52 | export default function Counter({ label = "Counter", onChange = undefined }) { 53 | const [count, setCount] = useState(0); 54 | 55 | const incrementHandler = () => { 56 | setCount((c) => c + 1); 57 | onChange?.(count + 1); 58 | }; 59 | 60 | return ( 61 | 62 | {label} 63 | {count} 64 | 65 | 66 | ); 67 | } 68 | `, 69 | options: { 70 | plugins: ['@trivago/prettier-plugin-sort-imports'], 71 | ...sortImportsPluginOptions, 72 | }, 73 | }, 74 | { 75 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 76 | input: ` 77 | import { CounterButton } from './parts'; 78 | import { CounterContainer } from '@/layouts'; 79 | import { useState } from 'react'; 80 | 81 | export default function Counter({ 82 | label = 'Counter', 83 | onChange = undefined, 84 | }) { 85 | const [count, setCount] = useState(0); 86 | 87 | const incrementHandler = () => { 88 | setCount((c) => c + 1); 89 | onChange?.(count + 1); 90 | }; 91 | 92 | return ( 93 | 94 | {label} 95 | {count} 96 | 97 | 98 | ); 99 | } 100 | `, 101 | output: `import { useState } from "react"; 102 | 103 | import { CounterContainer } from "@/layouts"; 104 | 105 | import { CounterButton } from "./parts"; 106 | 107 | export default function Counter({ label = "Counter", onChange = undefined }) { 108 | const [count, setCount] = useState(0); 109 | 110 | const incrementHandler = () => { 111 | setCount((c) => c + 1); 112 | onChange?.(count + 1); 113 | }; 114 | 115 | return ( 116 | 117 | {label} 118 | {count} 119 | 120 | 121 | ); 122 | } 123 | `, 124 | options: { 125 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 126 | ...sortImportsPluginOptions, 127 | }, 128 | }, 129 | { 130 | name: 'brace-style plugin (1) - standalone use', 131 | input: ` 132 | import { CounterButton } from './parts'; 133 | import { CounterContainer } from '@/layouts'; 134 | import { useState } from 'react'; 135 | 136 | export default function Counter({ 137 | label = 'Counter', 138 | onChange = undefined, 139 | }) { 140 | const [count, setCount] = useState(0); 141 | 142 | const incrementHandler = () => { 143 | setCount((c) => c + 1); 144 | onChange?.(count + 1); 145 | }; 146 | 147 | return ( 148 | 149 | {label} 150 | {count} 151 | 152 | 153 | ); 154 | } 155 | `, 156 | output: `import { CounterButton } from "./parts"; 157 | import { CounterContainer } from "@/layouts"; 158 | import { useState } from "react"; 159 | 160 | export default function Counter({ label = "Counter", onChange = undefined }) 161 | { 162 | const [count, setCount] = useState(0); 163 | 164 | const incrementHandler = () => 165 | { 166 | setCount((c) => c + 1); 167 | onChange?.(count + 1); 168 | }; 169 | 170 | return ( 171 | 172 | {label} 173 | {count} 174 | 175 | 176 | ); 177 | } 178 | `, 179 | options: { 180 | plugins: ['prettier-plugin-brace-style'], 181 | ...braceStylePluginOptions, 182 | }, 183 | }, 184 | { 185 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 186 | input: ` 187 | import { CounterButton } from './parts'; 188 | import { CounterContainer } from '@/layouts'; 189 | import { useState } from 'react'; 190 | 191 | export default function Counter({ 192 | label = 'Counter', 193 | onChange = undefined, 194 | }) { 195 | const [count, setCount] = useState(0); 196 | 197 | const incrementHandler = () => { 198 | setCount((c) => c + 1); 199 | onChange?.(count + 1); 200 | }; 201 | 202 | return ( 203 | 204 | {label} 205 | {count} 206 | 207 | 208 | ); 209 | } 210 | `, 211 | output: `import { CounterButton } from "./parts"; 212 | import { CounterContainer } from "@/layouts"; 213 | import { useState } from "react"; 214 | 215 | export default function Counter({ label = "Counter", onChange = undefined }) 216 | { 217 | const [count, setCount] = useState(0); 218 | 219 | const incrementHandler = () => 220 | { 221 | setCount((c) => c + 1); 222 | onChange?.(count + 1); 223 | }; 224 | 225 | return ( 226 | 227 | {label} 228 | {count} 229 | 230 | 231 | ); 232 | } 233 | `, 234 | options: { 235 | plugins: ['prettier-plugin-brace-style', thisPlugin], 236 | ...braceStylePluginOptions, 237 | }, 238 | }, 239 | { 240 | name: 'tailwindcss plugin (1) - standalone use', 241 | input: ` 242 | import { CounterButton } from './parts'; 243 | import { CounterContainer } from '@/layouts'; 244 | import { useState } from 'react'; 245 | 246 | export default function Counter({ 247 | label = 'Counter', 248 | onChange = undefined, 249 | }) { 250 | const [count, setCount] = useState(0); 251 | 252 | const incrementHandler = () => { 253 | setCount((c) => c + 1); 254 | onChange?.(count + 1); 255 | }; 256 | 257 | return ( 258 | 259 | {label} 260 | {count} 261 | 262 | 263 | ); 264 | } 265 | `, 266 | output: `import { CounterButton } from "./parts"; 267 | import { CounterContainer } from "@/layouts"; 268 | import { useState } from "react"; 269 | 270 | export default function Counter({ label = "Counter", onChange = undefined }) { 271 | const [count, setCount] = useState(0); 272 | 273 | const incrementHandler = () => { 274 | setCount((c) => c + 1); 275 | onChange?.(count + 1); 276 | }; 277 | 278 | return ( 279 | 280 | {label} 281 | {count} 282 | 283 | 284 | ); 285 | } 286 | `, 287 | options: { 288 | plugins: ['prettier-plugin-tailwindcss'], 289 | }, 290 | }, 291 | { 292 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 293 | input: ` 294 | import { CounterButton } from './parts'; 295 | import { CounterContainer } from '@/layouts'; 296 | import { useState } from 'react'; 297 | 298 | export default function Counter({ 299 | label = 'Counter', 300 | onChange = undefined, 301 | }) { 302 | const [count, setCount] = useState(0); 303 | 304 | const incrementHandler = () => { 305 | setCount((c) => c + 1); 306 | onChange?.(count + 1); 307 | }; 308 | 309 | return ( 310 | 311 | {label} 312 | {count} 313 | 314 | 315 | ); 316 | } 317 | `, 318 | output: `import { CounterButton } from "./parts"; 319 | import { CounterContainer } from "@/layouts"; 320 | import { useState } from "react"; 321 | 322 | export default function Counter({ label = "Counter", onChange = undefined }) { 323 | const [count, setCount] = useState(0); 324 | 325 | const incrementHandler = () => { 326 | setCount((c) => c + 1); 327 | onChange?.(count + 1); 328 | }; 329 | 330 | return ( 331 | 332 | {label} 333 | {count} 334 | 335 | 336 | ); 337 | } 338 | `, 339 | options: { 340 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 341 | }, 342 | }, 343 | { 344 | name: 'classnames plugin (1) - standalone use', 345 | input: ` 346 | export function Callout({ children }) { 347 | return ( 348 |
349 | {children} 350 |
351 | ); 352 | } 353 | `, 354 | output: `export function Callout({ children }) { 355 | return ( 356 |
360 | {children} 361 |
362 | ); 363 | } 364 | `, 365 | options: { 366 | plugins: ['prettier-plugin-classnames'], 367 | ...classnamesPluginOptions, 368 | }, 369 | }, 370 | { 371 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 372 | input: ` 373 | export function Callout({ children }) { 374 | return ( 375 |
376 | {children} 377 |
378 | ); 379 | } 380 | `, 381 | output: `export function Callout({ children }) { 382 | return ( 383 |
387 | {children} 388 |
389 | ); 390 | } 391 | `, 392 | options: { 393 | plugins: ['prettier-plugin-classnames', thisPlugin], 394 | ...classnamesPluginOptions, 395 | }, 396 | }, 397 | { 398 | name: 'issue #31 (1) - standalone use', 399 | input: ` 400 | export function Callout({ children }) { 401 | return null; 402 | } 403 | `, 404 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 405 | options: { 406 | plugins: [noopPlugin], 407 | endOfLine: 'crlf', 408 | }, 409 | }, 410 | { 411 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 412 | input: ` 413 | export function Callout({ children }) { 414 | return null; 415 | } 416 | `, 417 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 418 | options: { 419 | plugins: [noopPlugin, thisPlugin], 420 | endOfLine: 'crlf', 421 | }, 422 | }, 423 | { 424 | name: 'issue #34 (1) - standalone use', 425 | input: ` 426 | export function $1_$$2_$$$3_$$$$4({ children }) { 427 | return
{children}
; 428 | } 429 | `, 430 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 431 | { 432 | return
{children}
; 433 | } 434 | `, 435 | options: { 436 | plugins: ['prettier-plugin-brace-style'], 437 | ...braceStylePluginOptions, 438 | }, 439 | }, 440 | { 441 | name: 'issue #34 (2) - a combination of a single plugin and a merge plugin also has no effect', 442 | input: ` 443 | export function $1_$$2_$$$3_$$$$4({ children }) { 444 | return
{children}
; 445 | } 446 | `, 447 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 448 | { 449 | return
{children}
; 450 | } 451 | `, 452 | options: { 453 | plugins: ['prettier-plugin-brace-style', thisPlugin], 454 | ...braceStylePluginOptions, 455 | }, 456 | }, 457 | ]; 458 | 459 | testEach(fixtures, options); 460 | -------------------------------------------------------------------------------- /tests/v2-test/babel/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'babel', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | import { CounterButton } from './parts'; 16 | import { CounterContainer } from '@/layouts'; 17 | import { useState } from 'react'; 18 | 19 | export default function Counter({ 20 | label = 'Counter', 21 | onChange = undefined, 22 | }) { 23 | const [count, setCount] = useState(0); 24 | 25 | const incrementHandler = () => { 26 | setCount((c) => c + 1); 27 | onChange?.(count + 1); 28 | }; 29 | 30 | return ( 31 | 32 | {label} 33 | {count} 34 | 35 | 36 | ); 37 | } 38 | `, 39 | output: `import { CounterButton } from "./parts"; 40 | import { CounterContainer } from "@/layouts"; 41 | import { useState } from "react"; 42 | 43 | export default function Counter({ label = "Counter", onChange = undefined }) { 44 | const [count, setCount] = useState(0); 45 | 46 | const incrementHandler = () => { 47 | setCount((c) => c + 1); 48 | onChange?.(count + 1); 49 | }; 50 | 51 | return ( 52 | 53 | {label} 54 | {count} 55 | 56 | 57 | ); 58 | } 59 | `, 60 | options: { 61 | plugins: [], 62 | }, 63 | }, 64 | { 65 | name: 'no plugins (2) - merge plugin alone has no effect', 66 | input: ` 67 | import { CounterButton } from './parts'; 68 | import { CounterContainer } from '@/layouts'; 69 | import { useState } from 'react'; 70 | 71 | export default function Counter({ 72 | label = 'Counter', 73 | onChange = undefined, 74 | }) { 75 | const [count, setCount] = useState(0); 76 | 77 | const incrementHandler = () => { 78 | setCount((c) => c + 1); 79 | onChange?.(count + 1); 80 | }; 81 | 82 | return ( 83 | 84 | {label} 85 | {count} 86 | 87 | 88 | ); 89 | } 90 | `, 91 | output: `import { CounterButton } from "./parts"; 92 | import { CounterContainer } from "@/layouts"; 93 | import { useState } from "react"; 94 | 95 | export default function Counter({ label = "Counter", onChange = undefined }) { 96 | const [count, setCount] = useState(0); 97 | 98 | const incrementHandler = () => { 99 | setCount((c) => c + 1); 100 | onChange?.(count + 1); 101 | }; 102 | 103 | return ( 104 | 105 | {label} 106 | {count} 107 | 108 | 109 | ); 110 | } 111 | `, 112 | options: { 113 | plugins: [thisPlugin], 114 | }, 115 | }, 116 | ]; 117 | 118 | testEach(fixtures, options); 119 | -------------------------------------------------------------------------------- /tests/v2-test/html/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - tailwindcss -> classnames', 14 | input: ` 15 | 20 | `, 21 | output: ` 29 | `, 30 | options: { 31 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-classnames', thisPlugin], 32 | ...classnamesPluginOptions, 33 | }, 34 | }, 35 | { 36 | name: 'two plugins with some overlapping formatting regions (2) - classnames -> tailwindcss', 37 | input: ` 38 | 43 | `, 44 | output: ` 51 | `, 52 | options: { 53 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-tailwindcss', thisPlugin], 54 | ...classnamesPluginOptions, 55 | }, 56 | }, 57 | ]; 58 | 59 | testEach(fixtures, options); 60 | -------------------------------------------------------------------------------- /tests/v2-test/html/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, noopPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'tailwindcss plugin (1) - standalone use', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: ['prettier-plugin-tailwindcss'], 31 | }, 32 | }, 33 | { 34 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 52 | }, 53 | }, 54 | { 55 | name: 'classnames plugin (1) - standalone use', 56 | input: ` 57 | 62 | `, 63 | output: ` 71 | `, 72 | options: { 73 | plugins: ['prettier-plugin-classnames'], 74 | ...classnamesPluginOptions, 75 | }, 76 | }, 77 | { 78 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 79 | input: ` 80 | 85 | `, 86 | output: ` 94 | `, 95 | options: { 96 | plugins: ['prettier-plugin-classnames', thisPlugin], 97 | ...classnamesPluginOptions, 98 | }, 99 | }, 100 | { 101 | name: 'issue #31 (1) - standalone use', 102 | input: ` 103 | 108 | `, 109 | output: `\r\n`, 110 | options: { 111 | plugins: [noopPlugin], 112 | endOfLine: 'crlf', 113 | }, 114 | }, 115 | { 116 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 117 | input: ` 118 | 123 | `, 124 | output: `\r\n`, 125 | options: { 126 | plugins: [noopPlugin, thisPlugin], 127 | endOfLine: 'crlf', 128 | }, 129 | }, 130 | ]; 131 | 132 | testEach(fixtures, options); 133 | -------------------------------------------------------------------------------- /tests/v2-test/html/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: [], 31 | }, 32 | }, 33 | { 34 | name: 'no plugins (2) - merge plugin alone has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: [thisPlugin], 52 | }, 53 | }, 54 | ]; 55 | 56 | testEach(fixtures, options); 57 | -------------------------------------------------------------------------------- /tests/v2-test/markdown/__snapshots__/single-plugin.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`'(1) This plugin doesn\\'t support markdown parser, so it leaves the output of each plugin as is.' > expectation 1`] = ` 4 | "\`\`\`jsx 5 | export function Foo({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | \`\`\` 13 | " 14 | `; 15 | -------------------------------------------------------------------------------- /tests/v2-test/markdown/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { testSnapshotEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'markdown', 9 | }; 10 | 11 | const fixtures: Omit[] = [ 12 | { 13 | name: "(1) This plugin doesn't support markdown parser, so it leaves the output of each plugin as is.", 14 | input: ` 15 | \`\`\`jsx 16 | export function Foo({ children }) { 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | } 23 | \`\`\` 24 | `, 25 | options: { 26 | plugins: ['prettier-plugin-classnames'], 27 | }, 28 | }, 29 | ]; 30 | 31 | testSnapshotEach(fixtures, options); 32 | -------------------------------------------------------------------------------- /tests/v2-test/mdx/__snapshots__/single-plugin.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`'(1) This plugin doesn\\'t support mdx parser, so it leaves the output of each plugin as is.' > expectation 1`] = ` 4 | "\`\`\`jsx 5 | export function Foo({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | \`\`\` 13 | " 14 | `; 15 | -------------------------------------------------------------------------------- /tests/v2-test/mdx/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { testSnapshotEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'mdx', 9 | }; 10 | 11 | const fixtures: Omit[] = [ 12 | { 13 | name: "(1) This plugin doesn't support mdx parser, so it leaves the output of each plugin as is.", 14 | input: ` 15 | \`\`\`jsx 16 | export function Foo({ children }) { 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | } 23 | \`\`\` 24 | `, 25 | options: { 26 | plugins: ['prettier-plugin-classnames'], 27 | }, 28 | }, 29 | ]; 30 | 31 | testSnapshotEach(fixtures, options); 32 | -------------------------------------------------------------------------------- /tests/v2-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "v2-test", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "vitest run --passWithNoTests" 9 | }, 10 | "devDependencies": { 11 | "@trivago/prettier-plugin-sort-imports": "4.2.1", 12 | "prettier": "2.8.4", 13 | "prettier-plugin-astro": "0.10.0", 14 | "prettier-plugin-brace-style": "0.7.0", 15 | "prettier-plugin-classnames": "0.7.5", 16 | "prettier-plugin-svelte": "2.10.1", 17 | "prettier-plugin-tailwindcss": "0.4.1", 18 | "test-settings": "workspace:*", 19 | "vite-tsconfig-paths": "4.2.3", 20 | "vitest": "1.1.3", 21 | "vue": "3.3.9" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/v2-test/svelte/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, braceStylePluginOptions, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - svelte -> brace-style', 14 | input: ` 15 | 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: ` 35 | 36 |
39 | Now: {now} 40 |
41 | `, 42 | options: { 43 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-brace-style', thisPlugin], 44 | ...braceStylePluginOptions, 45 | }, 46 | }, 47 | { 48 | name: 'two plugins with some overlapping formatting regions (2) - brace-style -> svelte', 49 | input: ` 50 | 57 | 58 |
59 | Now: {now} 60 |
61 | `, 62 | output: ` 70 | 71 |
74 | Now: {now} 75 |
76 | `, 77 | options: { 78 | plugins: ['prettier-plugin-brace-style', 'prettier-plugin-svelte', thisPlugin], 79 | ...braceStylePluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'two plugins with some overlapping formatting regions (3) - svelte -> tailwindcss', 84 | input: ` 85 | 92 | 93 |
94 | Now: {now} 95 |
96 | `, 97 | output: ` 104 | 105 |
108 | Now: {now} 109 |
110 | `, 111 | options: { 112 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-tailwindcss', thisPlugin], 113 | }, 114 | }, 115 | { 116 | name: 'two plugins with some overlapping formatting regions (4) - tailwindcss -> svelte', 117 | input: ` 118 | 125 | 126 |
127 | Now: {now} 128 |
129 | `, 130 | output: ` 137 | 138 |
141 | Now: {now} 142 |
143 | `, 144 | options: { 145 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-svelte', thisPlugin], 146 | }, 147 | }, 148 | { 149 | name: 'two plugins with some overlapping formatting regions (5) - svelte -> classnames', 150 | input: ` 151 | 158 | 159 |
160 | Now: {now} 161 |
162 | `, 163 | output: ` 170 | 171 |
175 | Now: {now} 176 |
177 | `, 178 | options: { 179 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-classnames', thisPlugin], 180 | ...classnamesPluginOptions, 181 | }, 182 | }, 183 | { 184 | name: 'two plugins with some overlapping formatting regions (6) - classnames -> svelte', 185 | input: ` 186 | 193 | 194 |
195 | Now: {now} 196 |
197 | `, 198 | output: ` 205 | 206 |
210 | Now: {now} 211 |
212 | `, 213 | options: { 214 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-svelte', thisPlugin], 215 | ...classnamesPluginOptions, 216 | }, 217 | }, 218 | ]; 219 | 220 | testEach(fixtures, options); 221 | -------------------------------------------------------------------------------- /tests/v2-test/svelte/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'svelte plugin (1) - standalone use', 14 | input: ` 15 | 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: ` 34 | 35 |
38 | Now: {now} 39 |
40 | `, 41 | options: { 42 | plugins: ['prettier-plugin-svelte'], 43 | }, 44 | }, 45 | { 46 | name: 'svelte plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 47 | input: ` 48 | 55 | 56 |
57 | Now: {now} 58 |
59 | `, 60 | output: ` 67 | 68 |
71 | Now: {now} 72 |
73 | `, 74 | options: { 75 | plugins: ['prettier-plugin-svelte', thisPlugin], 76 | }, 77 | }, 78 | ]; 79 | 80 | testEach(fixtures, options); 81 | -------------------------------------------------------------------------------- /tests/v2-test/svelte/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testErrorEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1) - it will not work without the svelte plugin', 14 | input: `any input`, 15 | output: `any output`, 16 | options: { 17 | plugins: [], 18 | }, 19 | }, 20 | { 21 | name: 'no plugins (2) - it will not work without the svelte plugin', 22 | input: `this plugin does not have`, 23 | output: `a built-in svelte compiler (or parser).`, 24 | options: { 25 | plugins: [thisPlugin], 26 | }, 27 | }, 28 | ]; 29 | 30 | testErrorEach(fixtures, options); 31 | -------------------------------------------------------------------------------- /tests/v2-test/typescript/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'typescript', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | import { CounterButton } from './parts'; 23 | import { CounterContainer } from '@/layouts'; 24 | import { useState } from 'react'; 25 | 26 | export default function Counter({ 27 | label = 'Counter', 28 | onChange = undefined, 29 | }) { 30 | const [count, setCount] = useState(0); 31 | 32 | const incrementHandler = () => { 33 | setCount((c) => c + 1); 34 | onChange?.(count + 1); 35 | }; 36 | 37 | return ( 38 | 39 | {label} 40 | {count} 41 | 42 | 43 | ); 44 | } 45 | `, 46 | output: `import { useState } from "react"; 47 | 48 | import { CounterContainer } from "@/layouts"; 49 | 50 | import { CounterButton } from "./parts"; 51 | 52 | export default function Counter({ label = "Counter", onChange = undefined }) { 53 | const [count, setCount] = useState(0); 54 | 55 | const incrementHandler = () => { 56 | setCount((c) => c + 1); 57 | onChange?.(count + 1); 58 | }; 59 | 60 | return ( 61 | 62 | {label} 63 | {count} 64 | 65 | 66 | ); 67 | } 68 | `, 69 | options: { 70 | plugins: ['@trivago/prettier-plugin-sort-imports'], 71 | ...sortImportsPluginOptions, 72 | }, 73 | }, 74 | { 75 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 76 | input: ` 77 | import { CounterButton } from './parts'; 78 | import { CounterContainer } from '@/layouts'; 79 | import { useState } from 'react'; 80 | 81 | export default function Counter({ 82 | label = 'Counter', 83 | onChange = undefined, 84 | }) { 85 | const [count, setCount] = useState(0); 86 | 87 | const incrementHandler = () => { 88 | setCount((c) => c + 1); 89 | onChange?.(count + 1); 90 | }; 91 | 92 | return ( 93 | 94 | {label} 95 | {count} 96 | 97 | 98 | ); 99 | } 100 | `, 101 | output: `import { useState } from "react"; 102 | 103 | import { CounterContainer } from "@/layouts"; 104 | 105 | import { CounterButton } from "./parts"; 106 | 107 | export default function Counter({ label = "Counter", onChange = undefined }) { 108 | const [count, setCount] = useState(0); 109 | 110 | const incrementHandler = () => { 111 | setCount((c) => c + 1); 112 | onChange?.(count + 1); 113 | }; 114 | 115 | return ( 116 | 117 | {label} 118 | {count} 119 | 120 | 121 | ); 122 | } 123 | `, 124 | options: { 125 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 126 | ...sortImportsPluginOptions, 127 | }, 128 | }, 129 | { 130 | name: 'brace-style plugin (1) - standalone use', 131 | input: ` 132 | import { CounterButton } from './parts'; 133 | import { CounterContainer } from '@/layouts'; 134 | import { useState } from 'react'; 135 | 136 | export default function Counter({ 137 | label = 'Counter', 138 | onChange = undefined, 139 | }) { 140 | const [count, setCount] = useState(0); 141 | 142 | const incrementHandler = () => { 143 | setCount((c) => c + 1); 144 | onChange?.(count + 1); 145 | }; 146 | 147 | return ( 148 | 149 | {label} 150 | {count} 151 | 152 | 153 | ); 154 | } 155 | `, 156 | output: `import { CounterButton } from "./parts"; 157 | import { CounterContainer } from "@/layouts"; 158 | import { useState } from "react"; 159 | 160 | export default function Counter({ label = "Counter", onChange = undefined }) 161 | { 162 | const [count, setCount] = useState(0); 163 | 164 | const incrementHandler = () => 165 | { 166 | setCount((c) => c + 1); 167 | onChange?.(count + 1); 168 | }; 169 | 170 | return ( 171 | 172 | {label} 173 | {count} 174 | 175 | 176 | ); 177 | } 178 | `, 179 | options: { 180 | plugins: ['prettier-plugin-brace-style'], 181 | ...braceStylePluginOptions, 182 | }, 183 | }, 184 | { 185 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 186 | input: ` 187 | import { CounterButton } from './parts'; 188 | import { CounterContainer } from '@/layouts'; 189 | import { useState } from 'react'; 190 | 191 | export default function Counter({ 192 | label = 'Counter', 193 | onChange = undefined, 194 | }) { 195 | const [count, setCount] = useState(0); 196 | 197 | const incrementHandler = () => { 198 | setCount((c) => c + 1); 199 | onChange?.(count + 1); 200 | }; 201 | 202 | return ( 203 | 204 | {label} 205 | {count} 206 | 207 | 208 | ); 209 | } 210 | `, 211 | output: `import { CounterButton } from "./parts"; 212 | import { CounterContainer } from "@/layouts"; 213 | import { useState } from "react"; 214 | 215 | export default function Counter({ label = "Counter", onChange = undefined }) 216 | { 217 | const [count, setCount] = useState(0); 218 | 219 | const incrementHandler = () => 220 | { 221 | setCount((c) => c + 1); 222 | onChange?.(count + 1); 223 | }; 224 | 225 | return ( 226 | 227 | {label} 228 | {count} 229 | 230 | 231 | ); 232 | } 233 | `, 234 | options: { 235 | plugins: ['prettier-plugin-brace-style', thisPlugin], 236 | ...braceStylePluginOptions, 237 | }, 238 | }, 239 | { 240 | name: 'tailwindcss plugin (1) - standalone use', 241 | input: ` 242 | import { CounterButton } from './parts'; 243 | import { CounterContainer } from '@/layouts'; 244 | import { useState } from 'react'; 245 | 246 | export default function Counter({ 247 | label = 'Counter', 248 | onChange = undefined, 249 | }) { 250 | const [count, setCount] = useState(0); 251 | 252 | const incrementHandler = () => { 253 | setCount((c) => c + 1); 254 | onChange?.(count + 1); 255 | }; 256 | 257 | return ( 258 | 259 | {label} 260 | {count} 261 | 262 | 263 | ); 264 | } 265 | `, 266 | output: `import { CounterButton } from "./parts"; 267 | import { CounterContainer } from "@/layouts"; 268 | import { useState } from "react"; 269 | 270 | export default function Counter({ label = "Counter", onChange = undefined }) { 271 | const [count, setCount] = useState(0); 272 | 273 | const incrementHandler = () => { 274 | setCount((c) => c + 1); 275 | onChange?.(count + 1); 276 | }; 277 | 278 | return ( 279 | 280 | {label} 281 | {count} 282 | 283 | 284 | ); 285 | } 286 | `, 287 | options: { 288 | plugins: ['prettier-plugin-tailwindcss'], 289 | }, 290 | }, 291 | { 292 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 293 | input: ` 294 | import { CounterButton } from './parts'; 295 | import { CounterContainer } from '@/layouts'; 296 | import { useState } from 'react'; 297 | 298 | export default function Counter({ 299 | label = 'Counter', 300 | onChange = undefined, 301 | }) { 302 | const [count, setCount] = useState(0); 303 | 304 | const incrementHandler = () => { 305 | setCount((c) => c + 1); 306 | onChange?.(count + 1); 307 | }; 308 | 309 | return ( 310 | 311 | {label} 312 | {count} 313 | 314 | 315 | ); 316 | } 317 | `, 318 | output: `import { CounterButton } from "./parts"; 319 | import { CounterContainer } from "@/layouts"; 320 | import { useState } from "react"; 321 | 322 | export default function Counter({ label = "Counter", onChange = undefined }) { 323 | const [count, setCount] = useState(0); 324 | 325 | const incrementHandler = () => { 326 | setCount((c) => c + 1); 327 | onChange?.(count + 1); 328 | }; 329 | 330 | return ( 331 | 332 | {label} 333 | {count} 334 | 335 | 336 | ); 337 | } 338 | `, 339 | options: { 340 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 341 | }, 342 | }, 343 | { 344 | name: 'classnames plugin (1) - standalone use', 345 | input: ` 346 | export function Callout({ children }) { 347 | return ( 348 |
349 | {children} 350 |
351 | ); 352 | } 353 | `, 354 | output: `export function Callout({ children }) { 355 | return ( 356 |
360 | {children} 361 |
362 | ); 363 | } 364 | `, 365 | options: { 366 | plugins: ['prettier-plugin-classnames'], 367 | ...classnamesPluginOptions, 368 | }, 369 | }, 370 | { 371 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 372 | input: ` 373 | export function Callout({ children }) { 374 | return ( 375 |
376 | {children} 377 |
378 | ); 379 | } 380 | `, 381 | output: `export function Callout({ children }) { 382 | return ( 383 |
387 | {children} 388 |
389 | ); 390 | } 391 | `, 392 | options: { 393 | plugins: ['prettier-plugin-classnames', thisPlugin], 394 | ...classnamesPluginOptions, 395 | }, 396 | }, 397 | { 398 | name: 'issue #31 (1) - standalone use', 399 | input: ` 400 | export function Callout({ children }) { 401 | return null; 402 | } 403 | `, 404 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 405 | options: { 406 | plugins: [noopPlugin], 407 | endOfLine: 'crlf', 408 | }, 409 | }, 410 | { 411 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 412 | input: ` 413 | export function Callout({ children }) { 414 | return null; 415 | } 416 | `, 417 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 418 | options: { 419 | plugins: [noopPlugin, thisPlugin], 420 | endOfLine: 'crlf', 421 | }, 422 | }, 423 | { 424 | name: 'issue #34 (1) - standalone use', 425 | input: ` 426 | export function $1_$$2_$$$3_$$$$4({ children }) { 427 | return
{children}
; 428 | } 429 | `, 430 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 431 | { 432 | return
{children}
; 433 | } 434 | `, 435 | options: { 436 | plugins: ['prettier-plugin-brace-style'], 437 | ...braceStylePluginOptions, 438 | }, 439 | }, 440 | { 441 | name: 'issue #34 (2) - a combination of a single plugin and a merge plugin also has no effect', 442 | input: ` 443 | export function $1_$$2_$$$3_$$$$4({ children }) { 444 | return
{children}
; 445 | } 446 | `, 447 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 448 | { 449 | return
{children}
; 450 | } 451 | `, 452 | options: { 453 | plugins: ['prettier-plugin-brace-style', thisPlugin], 454 | ...braceStylePluginOptions, 455 | }, 456 | }, 457 | ]; 458 | 459 | testEach(fixtures, options); 460 | -------------------------------------------------------------------------------- /tests/v2-test/typescript/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'typescript', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | import { CounterButton } from './parts'; 16 | import { CounterContainer } from '@/layouts'; 17 | import { useState } from 'react'; 18 | 19 | export default function Counter({ 20 | label = 'Counter', 21 | onChange = undefined, 22 | }) { 23 | const [count, setCount] = useState(0); 24 | 25 | const incrementHandler = () => { 26 | setCount((c) => c + 1); 27 | onChange?.(count + 1); 28 | }; 29 | 30 | return ( 31 | 32 | {label} 33 | {count} 34 | 35 | 36 | ); 37 | } 38 | `, 39 | output: `import { CounterButton } from "./parts"; 40 | import { CounterContainer } from "@/layouts"; 41 | import { useState } from "react"; 42 | 43 | export default function Counter({ label = "Counter", onChange = undefined }) { 44 | const [count, setCount] = useState(0); 45 | 46 | const incrementHandler = () => { 47 | setCount((c) => c + 1); 48 | onChange?.(count + 1); 49 | }; 50 | 51 | return ( 52 | 53 | {label} 54 | {count} 55 | 56 | 57 | ); 58 | } 59 | `, 60 | options: { 61 | plugins: [], 62 | }, 63 | }, 64 | { 65 | name: 'no plugins (2) - merge plugin alone has no effect', 66 | input: ` 67 | import { CounterButton } from './parts'; 68 | import { CounterContainer } from '@/layouts'; 69 | import { useState } from 'react'; 70 | 71 | export default function Counter({ 72 | label = 'Counter', 73 | onChange = undefined, 74 | }) { 75 | const [count, setCount] = useState(0); 76 | 77 | const incrementHandler = () => { 78 | setCount((c) => c + 1); 79 | onChange?.(count + 1); 80 | }; 81 | 82 | return ( 83 | 84 | {label} 85 | {count} 86 | 87 | 88 | ); 89 | } 90 | `, 91 | output: `import { CounterButton } from "./parts"; 92 | import { CounterContainer } from "@/layouts"; 93 | import { useState } from "react"; 94 | 95 | export default function Counter({ label = "Counter", onChange = undefined }) { 96 | const [count, setCount] = useState(0); 97 | 98 | const incrementHandler = () => { 99 | setCount((c) => c + 1); 100 | onChange?.(count + 1); 101 | }; 102 | 103 | return ( 104 | 105 | {label} 106 | {count} 107 | 108 | 109 | ); 110 | } 111 | `, 112 | options: { 113 | plugins: [thisPlugin], 114 | }, 115 | }, 116 | ]; 117 | 118 | testEach(fixtures, options); 119 | -------------------------------------------------------------------------------- /tests/v2-test/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import tsconfigPaths from 'vite-tsconfig-paths'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [tsconfigPaths()], 6 | }); 7 | -------------------------------------------------------------------------------- /tests/v2-test/vue/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'vue', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | 35 | 36 | 43 | `, 44 | output: ` 62 | 63 | 76 | `, 77 | options: { 78 | plugins: ['@trivago/prettier-plugin-sort-imports'], 79 | ...sortImportsPluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 84 | input: ` 85 | 98 | 99 | 106 | `, 107 | output: ` 125 | 126 | 139 | `, 140 | options: { 141 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 142 | ...sortImportsPluginOptions, 143 | }, 144 | }, 145 | { 146 | name: 'brace-style plugin (1) - standalone use', 147 | input: ` 148 | 161 | 162 | 169 | `, 170 | output: ` 186 | 187 | 201 | `, 202 | options: { 203 | plugins: ['prettier-plugin-brace-style'], 204 | ...braceStylePluginOptions, 205 | }, 206 | }, 207 | { 208 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 209 | input: ` 210 | 223 | 224 | 231 | `, 232 | output: ` 248 | 249 | 263 | `, 264 | options: { 265 | plugins: ['prettier-plugin-brace-style', thisPlugin], 266 | ...braceStylePluginOptions, 267 | }, 268 | }, 269 | { 270 | name: 'tailwindcss plugin (1) - standalone use', 271 | input: ` 272 | 285 | 286 | 293 | `, 294 | output: ` 310 | 311 | 324 | `, 325 | options: { 326 | plugins: ['prettier-plugin-tailwindcss'], 327 | }, 328 | }, 329 | { 330 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 331 | input: ` 332 | 345 | 346 | 353 | `, 354 | output: ` 370 | 371 | 384 | `, 385 | options: { 386 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 387 | }, 388 | }, 389 | { 390 | name: 'classnames plugin (1) - standalone use', 391 | input: ` 392 | 397 | `, 398 | output: ` 406 | `, 407 | options: { 408 | plugins: ['prettier-plugin-classnames'], 409 | ...classnamesPluginOptions, 410 | }, 411 | }, 412 | { 413 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 414 | input: ` 415 | 420 | `, 421 | output: ` 429 | `, 430 | options: { 431 | plugins: ['prettier-plugin-classnames', thisPlugin], 432 | ...classnamesPluginOptions, 433 | }, 434 | }, 435 | { 436 | name: 'issue #31 (1) - standalone use', 437 | input: ` 438 | 443 | `, 444 | output: `\r\n`, 445 | options: { 446 | plugins: [noopPlugin], 447 | endOfLine: 'crlf', 448 | }, 449 | }, 450 | { 451 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 452 | input: ` 453 | 458 | `, 459 | output: `\r\n`, 460 | options: { 461 | plugins: [noopPlugin, thisPlugin], 462 | endOfLine: 'crlf', 463 | }, 464 | }, 465 | ]; 466 | 467 | testEach(fixtures, options); 468 | -------------------------------------------------------------------------------- /tests/v2-test/vue/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'vue', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 28 | 29 | 36 | `, 37 | output: ` 53 | 54 | 67 | `, 68 | options: { 69 | plugins: [], 70 | }, 71 | }, 72 | { 73 | name: 'no plugins (2) - merge plugin alone has no effect', 74 | input: ` 75 | 88 | 89 | 96 | `, 97 | output: ` 113 | 114 | 127 | `, 128 | options: { 129 | plugins: [thisPlugin], 130 | }, 131 | }, 132 | ]; 133 | 134 | testEach(fixtures, options); 135 | -------------------------------------------------------------------------------- /tests/v3-test/adaptor.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'prettier'; 2 | import { format } from 'prettier'; 3 | import { parsers as babelParsers } from 'prettier/plugins/babel'; 4 | import { parsers as htmlParsers } from 'prettier/plugins/html'; 5 | import { parsers as typescriptParsers } from 'prettier/plugins/typescript'; 6 | import type { Fixture } from 'test-settings'; 7 | import { describe, expect, onTestFailed, test } from 'vitest'; 8 | 9 | // eslint-disable-next-line import/no-extraneous-dependencies 10 | import * as thisPlugin from '@/packages/v3-plugin'; 11 | 12 | export { thisPlugin }; 13 | 14 | export const noopPlugin: Plugin = { 15 | parsers: { 16 | babel: babelParsers.babel, 17 | typescript: typescriptParsers.typescript, 18 | angular: htmlParsers.angular, 19 | html: htmlParsers.html, 20 | vue: htmlParsers.vue, 21 | }, 22 | }; 23 | 24 | export const sortImportsPluginOptions = { 25 | importOrder: ['', '^@[^/]+/(.*)$', '^@/(.*)$', '^[./]'], 26 | importOrderSeparation: true, 27 | }; 28 | 29 | export const braceStylePluginOptions = { 30 | braceStyle: 'allman', 31 | }; 32 | 33 | export const classnamesPluginOptions = { 34 | endingPosition: 'absolute', 35 | }; 36 | 37 | export function testEach(fixtures: Fixture[], options: PrettierBaseOptions & { parser: string }) { 38 | test.each(fixtures)('$name', async ({ input, output, options: fixtureOptions }) => { 39 | const fixedOptions = { 40 | ...options, 41 | ...(fixtureOptions ?? {}), 42 | }; 43 | const formattedText = await format(input, fixedOptions); 44 | 45 | expect(formattedText).toBe(output); 46 | }); 47 | } 48 | 49 | export function testSnapshotEach( 50 | fixtures: Omit[], 51 | options: PrettierBaseOptions & { parser: string }, 52 | ) { 53 | describe.each(fixtures)('$name', async ({ input, options: fixtureOptions }) => { 54 | const fixedOptions = { 55 | ...options, 56 | ...(fixtureOptions ?? {}), 57 | }; 58 | const formattedText = await format(input, fixedOptions); 59 | let skipSecondTest = false; 60 | 61 | test('expectation', () => { 62 | onTestFailed(() => { 63 | skipSecondTest = true; 64 | }); 65 | 66 | expect(formattedText).toMatchSnapshot(); 67 | }); 68 | 69 | test('consistency; if there are no plugins or only one, adding a merge plugin should have the same result', async ({ 70 | skip, 71 | }) => { 72 | const fixedPlugins = fixedOptions.plugins ?? []; 73 | 74 | if (skipSecondTest || fixedPlugins.length > 1) { 75 | skip(); 76 | } 77 | 78 | const formattedTextWithThisPlugin = await format(formattedText, { 79 | ...fixedOptions, 80 | plugins: [...fixedPlugins, thisPlugin], 81 | }); 82 | 83 | expect(formattedTextWithThisPlugin).toBe(formattedText); 84 | }); 85 | }); 86 | } 87 | 88 | export function testErrorEach( 89 | fixtures: Fixture[], 90 | options: PrettierBaseOptions & { parser: string }, 91 | ) { 92 | test.each(fixtures)('$name', async ({ input, options: fixtureOptions }) => { 93 | const fixedOptions = { 94 | ...options, 95 | ...(fixtureOptions ?? {}), 96 | }; 97 | 98 | await expect(() => format(input, fixedOptions)).rejects.toThrowError(); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /tests/v3-test/angular/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - tailwindcss -> classnames', 14 | input: ` 15 | 20 | `, 21 | output: ` 29 | `, 30 | options: { 31 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-classnames', thisPlugin], 32 | ...classnamesPluginOptions, 33 | }, 34 | }, 35 | { 36 | name: 'two plugins with some overlapping formatting regions (2) - classnames -> tailwindcss', 37 | input: ` 38 | 43 | `, 44 | output: ` 51 | `, 52 | options: { 53 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-tailwindcss', thisPlugin], 54 | ...classnamesPluginOptions, 55 | }, 56 | }, 57 | ]; 58 | 59 | testEach(fixtures, options); 60 | -------------------------------------------------------------------------------- /tests/v3-test/angular/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, noopPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'tailwindcss plugin (1) - standalone use', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: ['prettier-plugin-tailwindcss'], 31 | }, 32 | }, 33 | { 34 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 52 | }, 53 | }, 54 | { 55 | name: 'classnames plugin (1) - standalone use', 56 | input: ` 57 | 62 | `, 63 | output: ` 71 | `, 72 | options: { 73 | plugins: ['prettier-plugin-classnames'], 74 | ...classnamesPluginOptions, 75 | }, 76 | }, 77 | { 78 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 79 | input: ` 80 | 85 | `, 86 | output: ` 94 | `, 95 | options: { 96 | plugins: ['prettier-plugin-classnames', thisPlugin], 97 | ...classnamesPluginOptions, 98 | }, 99 | }, 100 | { 101 | name: 'issue #31 (1) - standalone use', 102 | input: ` 103 | 108 | `, 109 | output: `\r\n`, 110 | options: { 111 | plugins: [noopPlugin], 112 | endOfLine: 'crlf', 113 | }, 114 | }, 115 | { 116 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 117 | input: ` 118 | 123 | `, 124 | output: `\r\n`, 125 | options: { 126 | plugins: [noopPlugin, thisPlugin], 127 | endOfLine: 'crlf', 128 | }, 129 | }, 130 | ]; 131 | 132 | testEach(fixtures, options); 133 | -------------------------------------------------------------------------------- /tests/v3-test/angular/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'angular', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: [], 31 | }, 32 | }, 33 | { 34 | name: 'no plugins (2) - merge plugin alone has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: [thisPlugin], 52 | }, 53 | }, 54 | ]; 55 | 56 | testEach(fixtures, options); 57 | -------------------------------------------------------------------------------- /tests/v3-test/astro/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, braceStylePluginOptions, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - astro -> brace-style', 14 | input: ` 15 | --- 16 | function getDate() { 17 | return new Date(); 18 | } 19 | 20 | const now = getDate(); 21 | --- 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: `--- 28 | function getDate() 29 | { 30 | return new Date(); 31 | } 32 | 33 | const now = getDate(); 34 | --- 35 | 36 |
39 | Now: {now} 40 |
41 | `, 42 | options: { 43 | plugins: ['prettier-plugin-astro', 'prettier-plugin-brace-style', thisPlugin], 44 | ...braceStylePluginOptions, 45 | }, 46 | }, 47 | { 48 | name: 'two plugins with some overlapping formatting regions (2) - brace-style -> astro', 49 | input: ` 50 | --- 51 | function getDate() { 52 | return new Date(); 53 | } 54 | 55 | const now = getDate(); 56 | --- 57 | 58 |
59 | Now: {now} 60 |
61 | `, 62 | output: `--- 63 | function getDate() 64 | { 65 | return new Date(); 66 | } 67 | 68 | const now = getDate(); 69 | --- 70 | 71 |
74 | Now: {now} 75 |
76 | `, 77 | options: { 78 | plugins: ['prettier-plugin-brace-style', 'prettier-plugin-astro', thisPlugin], 79 | ...braceStylePluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'two plugins with some overlapping formatting regions (3) - astro -> tailwindcss', 84 | input: ` 85 | --- 86 | function getDate() { 87 | return new Date(); 88 | } 89 | 90 | const now = getDate(); 91 | --- 92 | 93 |
94 | Now: {now} 95 |
96 | `, 97 | output: `--- 98 | function getDate() { 99 | return new Date(); 100 | } 101 | 102 | const now = getDate(); 103 | --- 104 | 105 |
108 | Now: {now} 109 |
110 | `, 111 | options: { 112 | plugins: ['prettier-plugin-astro', 'prettier-plugin-tailwindcss', thisPlugin], 113 | }, 114 | }, 115 | { 116 | name: 'two plugins with some overlapping formatting regions (4) - tailwindcss -> astro', 117 | input: ` 118 | --- 119 | function getDate() { 120 | return new Date(); 121 | } 122 | 123 | const now = getDate(); 124 | --- 125 | 126 |
127 | Now: {now} 128 |
129 | `, 130 | output: `--- 131 | function getDate() { 132 | return new Date(); 133 | } 134 | 135 | const now = getDate(); 136 | --- 137 | 138 |
141 | Now: {now} 142 |
143 | `, 144 | options: { 145 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-astro', thisPlugin], 146 | }, 147 | }, 148 | { 149 | name: 'two plugins with some overlapping formatting regions (5) - astro -> classnames', 150 | input: ` 151 | --- 152 | function getDate() { 153 | return new Date(); 154 | } 155 | 156 | const now = getDate(); 157 | --- 158 | 159 |
160 | Now: {now} 161 |
162 | `, 163 | output: `--- 164 | function getDate() { 165 | return new Date(); 166 | } 167 | 168 | const now = getDate(); 169 | --- 170 | 171 |
175 | Now: {now} 176 |
177 | `, 178 | options: { 179 | plugins: ['prettier-plugin-astro', 'prettier-plugin-classnames', thisPlugin], 180 | ...classnamesPluginOptions, 181 | }, 182 | }, 183 | { 184 | name: 'two plugins with some overlapping formatting regions (6) - classnames -> astro', 185 | input: ` 186 | --- 187 | function getDate() { 188 | return new Date(); 189 | } 190 | 191 | const now = getDate(); 192 | --- 193 | 194 |
195 | Now: {now} 196 |
197 | `, 198 | output: `--- 199 | function getDate() { 200 | return new Date(); 201 | } 202 | 203 | const now = getDate(); 204 | --- 205 | 206 |
210 | Now: {now} 211 |
212 | `, 213 | options: { 214 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-astro', thisPlugin], 215 | ...classnamesPluginOptions, 216 | }, 217 | }, 218 | ]; 219 | 220 | testEach(fixtures, options); 221 | -------------------------------------------------------------------------------- /tests/v3-test/astro/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'astro plugin (1) - standalone use', 14 | input: ` 15 | --- 16 | function getDate() { 17 | return new Date(); 18 | } 19 | 20 | const now = getDate(); 21 | --- 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: `--- 28 | function getDate() { 29 | return new Date(); 30 | } 31 | 32 | const now = getDate(); 33 | --- 34 | 35 |
38 | Now: {now} 39 |
40 | `, 41 | options: { 42 | plugins: ['prettier-plugin-astro'], 43 | }, 44 | }, 45 | { 46 | name: 'astro plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 47 | input: ` 48 | --- 49 | function getDate() { 50 | return new Date(); 51 | } 52 | 53 | const now = getDate(); 54 | --- 55 | 56 |
57 | Now: {now} 58 |
59 | `, 60 | output: `--- 61 | function getDate() { 62 | return new Date(); 63 | } 64 | 65 | const now = getDate(); 66 | --- 67 | 68 |
71 | Now: {now} 72 |
73 | `, 74 | options: { 75 | plugins: ['prettier-plugin-astro', thisPlugin], 76 | }, 77 | }, 78 | ]; 79 | 80 | testEach(fixtures, options); 81 | -------------------------------------------------------------------------------- /tests/v3-test/astro/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testErrorEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'astro', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1) - it will not work without the astro plugin', 14 | input: `any input`, 15 | output: `any output`, 16 | options: { 17 | plugins: [], 18 | }, 19 | }, 20 | { 21 | name: 'no plugins (2) - it will not work without the astro plugin', 22 | input: `this plugin does not have`, 23 | output: `a built-in astro compiler (or parser).`, 24 | options: { 25 | plugins: [thisPlugin], 26 | }, 27 | }, 28 | ]; 29 | 30 | testErrorEach(fixtures, options); 31 | -------------------------------------------------------------------------------- /tests/v3-test/babel/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'babel', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | import { CounterButton } from './parts'; 23 | import { CounterContainer } from '@/layouts'; 24 | import { useState } from 'react'; 25 | 26 | export default function Counter({ 27 | label = 'Counter', 28 | onChange = undefined, 29 | }) { 30 | const [count, setCount] = useState(0); 31 | 32 | const incrementHandler = () => { 33 | setCount((c) => c + 1); 34 | onChange?.(count + 1); 35 | }; 36 | 37 | return ( 38 | 39 | {label} 40 | {count} 41 | 42 | 43 | ); 44 | } 45 | `, 46 | output: `import { useState } from "react"; 47 | 48 | import { CounterContainer } from "@/layouts"; 49 | 50 | import { CounterButton } from "./parts"; 51 | 52 | export default function Counter({ label = "Counter", onChange = undefined }) { 53 | const [count, setCount] = useState(0); 54 | 55 | const incrementHandler = () => { 56 | setCount((c) => c + 1); 57 | onChange?.(count + 1); 58 | }; 59 | 60 | return ( 61 | 62 | {label} 63 | {count} 64 | 65 | 66 | ); 67 | } 68 | `, 69 | options: { 70 | plugins: ['@trivago/prettier-plugin-sort-imports'], 71 | ...sortImportsPluginOptions, 72 | }, 73 | }, 74 | { 75 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 76 | input: ` 77 | import { CounterButton } from './parts'; 78 | import { CounterContainer } from '@/layouts'; 79 | import { useState } from 'react'; 80 | 81 | export default function Counter({ 82 | label = 'Counter', 83 | onChange = undefined, 84 | }) { 85 | const [count, setCount] = useState(0); 86 | 87 | const incrementHandler = () => { 88 | setCount((c) => c + 1); 89 | onChange?.(count + 1); 90 | }; 91 | 92 | return ( 93 | 94 | {label} 95 | {count} 96 | 97 | 98 | ); 99 | } 100 | `, 101 | output: `import { useState } from "react"; 102 | 103 | import { CounterContainer } from "@/layouts"; 104 | 105 | import { CounterButton } from "./parts"; 106 | 107 | export default function Counter({ label = "Counter", onChange = undefined }) { 108 | const [count, setCount] = useState(0); 109 | 110 | const incrementHandler = () => { 111 | setCount((c) => c + 1); 112 | onChange?.(count + 1); 113 | }; 114 | 115 | return ( 116 | 117 | {label} 118 | {count} 119 | 120 | 121 | ); 122 | } 123 | `, 124 | options: { 125 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 126 | ...sortImportsPluginOptions, 127 | }, 128 | }, 129 | { 130 | name: 'brace-style plugin (1) - standalone use', 131 | input: ` 132 | import { CounterButton } from './parts'; 133 | import { CounterContainer } from '@/layouts'; 134 | import { useState } from 'react'; 135 | 136 | export default function Counter({ 137 | label = 'Counter', 138 | onChange = undefined, 139 | }) { 140 | const [count, setCount] = useState(0); 141 | 142 | const incrementHandler = () => { 143 | setCount((c) => c + 1); 144 | onChange?.(count + 1); 145 | }; 146 | 147 | return ( 148 | 149 | {label} 150 | {count} 151 | 152 | 153 | ); 154 | } 155 | `, 156 | output: `import { CounterButton } from "./parts"; 157 | import { CounterContainer } from "@/layouts"; 158 | import { useState } from "react"; 159 | 160 | export default function Counter({ label = "Counter", onChange = undefined }) 161 | { 162 | const [count, setCount] = useState(0); 163 | 164 | const incrementHandler = () => 165 | { 166 | setCount((c) => c + 1); 167 | onChange?.(count + 1); 168 | }; 169 | 170 | return ( 171 | 172 | {label} 173 | {count} 174 | 175 | 176 | ); 177 | } 178 | `, 179 | options: { 180 | plugins: ['prettier-plugin-brace-style'], 181 | ...braceStylePluginOptions, 182 | }, 183 | }, 184 | { 185 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 186 | input: ` 187 | import { CounterButton } from './parts'; 188 | import { CounterContainer } from '@/layouts'; 189 | import { useState } from 'react'; 190 | 191 | export default function Counter({ 192 | label = 'Counter', 193 | onChange = undefined, 194 | }) { 195 | const [count, setCount] = useState(0); 196 | 197 | const incrementHandler = () => { 198 | setCount((c) => c + 1); 199 | onChange?.(count + 1); 200 | }; 201 | 202 | return ( 203 | 204 | {label} 205 | {count} 206 | 207 | 208 | ); 209 | } 210 | `, 211 | output: `import { CounterButton } from "./parts"; 212 | import { CounterContainer } from "@/layouts"; 213 | import { useState } from "react"; 214 | 215 | export default function Counter({ label = "Counter", onChange = undefined }) 216 | { 217 | const [count, setCount] = useState(0); 218 | 219 | const incrementHandler = () => 220 | { 221 | setCount((c) => c + 1); 222 | onChange?.(count + 1); 223 | }; 224 | 225 | return ( 226 | 227 | {label} 228 | {count} 229 | 230 | 231 | ); 232 | } 233 | `, 234 | options: { 235 | plugins: ['prettier-plugin-brace-style', thisPlugin], 236 | ...braceStylePluginOptions, 237 | }, 238 | }, 239 | { 240 | name: 'tailwindcss plugin (1) - standalone use', 241 | input: ` 242 | import { CounterButton } from './parts'; 243 | import { CounterContainer } from '@/layouts'; 244 | import { useState } from 'react'; 245 | 246 | export default function Counter({ 247 | label = 'Counter', 248 | onChange = undefined, 249 | }) { 250 | const [count, setCount] = useState(0); 251 | 252 | const incrementHandler = () => { 253 | setCount((c) => c + 1); 254 | onChange?.(count + 1); 255 | }; 256 | 257 | return ( 258 | 259 | {label} 260 | {count} 261 | 262 | 263 | ); 264 | } 265 | `, 266 | output: `import { CounterButton } from "./parts"; 267 | import { CounterContainer } from "@/layouts"; 268 | import { useState } from "react"; 269 | 270 | export default function Counter({ label = "Counter", onChange = undefined }) { 271 | const [count, setCount] = useState(0); 272 | 273 | const incrementHandler = () => { 274 | setCount((c) => c + 1); 275 | onChange?.(count + 1); 276 | }; 277 | 278 | return ( 279 | 280 | {label} 281 | {count} 282 | 283 | 284 | ); 285 | } 286 | `, 287 | options: { 288 | plugins: ['prettier-plugin-tailwindcss'], 289 | }, 290 | }, 291 | { 292 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 293 | input: ` 294 | import { CounterButton } from './parts'; 295 | import { CounterContainer } from '@/layouts'; 296 | import { useState } from 'react'; 297 | 298 | export default function Counter({ 299 | label = 'Counter', 300 | onChange = undefined, 301 | }) { 302 | const [count, setCount] = useState(0); 303 | 304 | const incrementHandler = () => { 305 | setCount((c) => c + 1); 306 | onChange?.(count + 1); 307 | }; 308 | 309 | return ( 310 | 311 | {label} 312 | {count} 313 | 314 | 315 | ); 316 | } 317 | `, 318 | output: `import { CounterButton } from "./parts"; 319 | import { CounterContainer } from "@/layouts"; 320 | import { useState } from "react"; 321 | 322 | export default function Counter({ label = "Counter", onChange = undefined }) { 323 | const [count, setCount] = useState(0); 324 | 325 | const incrementHandler = () => { 326 | setCount((c) => c + 1); 327 | onChange?.(count + 1); 328 | }; 329 | 330 | return ( 331 | 332 | {label} 333 | {count} 334 | 335 | 336 | ); 337 | } 338 | `, 339 | options: { 340 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 341 | }, 342 | }, 343 | { 344 | name: 'classnames plugin (1) - standalone use', 345 | input: ` 346 | export function Callout({ children }) { 347 | return ( 348 |
349 | {children} 350 |
351 | ); 352 | } 353 | `, 354 | output: `export function Callout({ children }) { 355 | return ( 356 |
360 | {children} 361 |
362 | ); 363 | } 364 | `, 365 | options: { 366 | plugins: ['prettier-plugin-classnames'], 367 | ...classnamesPluginOptions, 368 | }, 369 | }, 370 | { 371 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 372 | input: ` 373 | export function Callout({ children }) { 374 | return ( 375 |
376 | {children} 377 |
378 | ); 379 | } 380 | `, 381 | output: `export function Callout({ children }) { 382 | return ( 383 |
387 | {children} 388 |
389 | ); 390 | } 391 | `, 392 | options: { 393 | plugins: ['prettier-plugin-classnames', thisPlugin], 394 | ...classnamesPluginOptions, 395 | }, 396 | }, 397 | { 398 | name: 'issue #15', 399 | input: ` 400 | export function Counter() { 401 | return {count}; 402 | } 403 | `, 404 | output: `export function Counter() { 405 | return {count}; 406 | } 407 | `, 408 | options: { 409 | plugins: [require.resolve('prettier-plugin-tailwindcss'), thisPlugin], 410 | }, 411 | }, 412 | { 413 | name: 'issue #31 (1) - standalone use', 414 | input: ` 415 | export function Callout({ children }) { 416 | return null; 417 | } 418 | `, 419 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 420 | options: { 421 | plugins: [noopPlugin], 422 | endOfLine: 'crlf', 423 | }, 424 | }, 425 | { 426 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 427 | input: ` 428 | export function Callout({ children }) { 429 | return null; 430 | } 431 | `, 432 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 433 | options: { 434 | plugins: [noopPlugin, thisPlugin], 435 | endOfLine: 'crlf', 436 | }, 437 | }, 438 | { 439 | name: 'issue #34 (1) - standalone use', 440 | input: ` 441 | export function $1_$$2_$$$3_$$$$4({ children }) { 442 | return
{children}
; 443 | } 444 | `, 445 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 446 | { 447 | return
{children}
; 448 | } 449 | `, 450 | options: { 451 | plugins: ['prettier-plugin-brace-style'], 452 | ...braceStylePluginOptions, 453 | }, 454 | }, 455 | { 456 | name: 'issue #34 (2) - a combination of a single plugin and a merge plugin also has no effect', 457 | input: ` 458 | export function $1_$$2_$$$3_$$$$4({ children }) { 459 | return
{children}
; 460 | } 461 | `, 462 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 463 | { 464 | return
{children}
; 465 | } 466 | `, 467 | options: { 468 | plugins: ['prettier-plugin-brace-style', thisPlugin], 469 | ...braceStylePluginOptions, 470 | }, 471 | }, 472 | ]; 473 | 474 | testEach(fixtures, options); 475 | -------------------------------------------------------------------------------- /tests/v3-test/babel/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'babel', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | import { CounterButton } from './parts'; 16 | import { CounterContainer } from '@/layouts'; 17 | import { useState } from 'react'; 18 | 19 | export default function Counter({ 20 | label = 'Counter', 21 | onChange = undefined, 22 | }) { 23 | const [count, setCount] = useState(0); 24 | 25 | const incrementHandler = () => { 26 | setCount((c) => c + 1); 27 | onChange?.(count + 1); 28 | }; 29 | 30 | return ( 31 | 32 | {label} 33 | {count} 34 | 35 | 36 | ); 37 | } 38 | `, 39 | output: `import { CounterButton } from "./parts"; 40 | import { CounterContainer } from "@/layouts"; 41 | import { useState } from "react"; 42 | 43 | export default function Counter({ label = "Counter", onChange = undefined }) { 44 | const [count, setCount] = useState(0); 45 | 46 | const incrementHandler = () => { 47 | setCount((c) => c + 1); 48 | onChange?.(count + 1); 49 | }; 50 | 51 | return ( 52 | 53 | {label} 54 | {count} 55 | 56 | 57 | ); 58 | } 59 | `, 60 | options: { 61 | plugins: [], 62 | }, 63 | }, 64 | { 65 | name: 'no plugins (2) - merge plugin alone has no effect', 66 | input: ` 67 | import { CounterButton } from './parts'; 68 | import { CounterContainer } from '@/layouts'; 69 | import { useState } from 'react'; 70 | 71 | export default function Counter({ 72 | label = 'Counter', 73 | onChange = undefined, 74 | }) { 75 | const [count, setCount] = useState(0); 76 | 77 | const incrementHandler = () => { 78 | setCount((c) => c + 1); 79 | onChange?.(count + 1); 80 | }; 81 | 82 | return ( 83 | 84 | {label} 85 | {count} 86 | 87 | 88 | ); 89 | } 90 | `, 91 | output: `import { CounterButton } from "./parts"; 92 | import { CounterContainer } from "@/layouts"; 93 | import { useState } from "react"; 94 | 95 | export default function Counter({ label = "Counter", onChange = undefined }) { 96 | const [count, setCount] = useState(0); 97 | 98 | const incrementHandler = () => { 99 | setCount((c) => c + 1); 100 | onChange?.(count + 1); 101 | }; 102 | 103 | return ( 104 | 105 | {label} 106 | {count} 107 | 108 | 109 | ); 110 | } 111 | `, 112 | options: { 113 | plugins: [thisPlugin], 114 | }, 115 | }, 116 | ]; 117 | 118 | testEach(fixtures, options); 119 | -------------------------------------------------------------------------------- /tests/v3-test/html/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - tailwindcss -> classnames', 14 | input: ` 15 | 20 | `, 21 | output: ` 29 | `, 30 | options: { 31 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-classnames', thisPlugin], 32 | ...classnamesPluginOptions, 33 | }, 34 | }, 35 | { 36 | name: 'two plugins with some overlapping formatting regions (2) - classnames -> tailwindcss', 37 | input: ` 38 | 43 | `, 44 | output: ` 51 | `, 52 | options: { 53 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-tailwindcss', thisPlugin], 54 | ...classnamesPluginOptions, 55 | }, 56 | }, 57 | ]; 58 | 59 | testEach(fixtures, options); 60 | -------------------------------------------------------------------------------- /tests/v3-test/html/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, noopPlugin, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'tailwindcss plugin (1) - standalone use', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: ['prettier-plugin-tailwindcss'], 31 | }, 32 | }, 33 | { 34 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 52 | }, 53 | }, 54 | { 55 | name: 'classnames plugin (1) - standalone use', 56 | input: ` 57 | 62 | `, 63 | output: ` 71 | `, 72 | options: { 73 | plugins: ['prettier-plugin-classnames'], 74 | ...classnamesPluginOptions, 75 | }, 76 | }, 77 | { 78 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 79 | input: ` 80 | 85 | `, 86 | output: ` 94 | `, 95 | options: { 96 | plugins: ['prettier-plugin-classnames', thisPlugin], 97 | ...classnamesPluginOptions, 98 | }, 99 | }, 100 | { 101 | name: 'issue #31 (1) - standalone use', 102 | input: ` 103 | 108 | `, 109 | output: `\r\n`, 110 | options: { 111 | plugins: [noopPlugin], 112 | endOfLine: 'crlf', 113 | }, 114 | }, 115 | { 116 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 117 | input: ` 118 | 123 | `, 124 | output: `\r\n`, 125 | options: { 126 | plugins: [noopPlugin, thisPlugin], 127 | endOfLine: 'crlf', 128 | }, 129 | }, 130 | ]; 131 | 132 | testEach(fixtures, options); 133 | -------------------------------------------------------------------------------- /tests/v3-test/html/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'html', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 20 | `, 21 | output: ` 28 | `, 29 | options: { 30 | plugins: [], 31 | }, 32 | }, 33 | { 34 | name: 'no plugins (2) - merge plugin alone has no effect', 35 | input: ` 36 | 41 | `, 42 | output: ` 49 | `, 50 | options: { 51 | plugins: [thisPlugin], 52 | }, 53 | }, 54 | ]; 55 | 56 | testEach(fixtures, options); 57 | -------------------------------------------------------------------------------- /tests/v3-test/markdown/__snapshots__/single-plugin.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`'(1) This plugin doesn\\'t support markdown parser, so it leaves the output of each plugin as is.' > expectation 1`] = ` 4 | "\`\`\`jsx 5 | export function Foo({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | \`\`\` 13 | " 14 | `; 15 | -------------------------------------------------------------------------------- /tests/v3-test/markdown/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { testSnapshotEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'markdown', 9 | }; 10 | 11 | const fixtures: Omit[] = [ 12 | { 13 | name: "(1) This plugin doesn't support markdown parser, so it leaves the output of each plugin as is.", 14 | input: ` 15 | \`\`\`jsx 16 | export function Foo({ children }) { 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | } 23 | \`\`\` 24 | `, 25 | options: { 26 | plugins: ['prettier-plugin-classnames'], 27 | }, 28 | }, 29 | ]; 30 | 31 | testSnapshotEach(fixtures, options); 32 | -------------------------------------------------------------------------------- /tests/v3-test/mdx/__snapshots__/single-plugin.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`'(1) This plugin doesn\\'t support mdx parser, so it leaves the output of each plugin as is.' > expectation 1`] = ` 4 | "\`\`\`jsx 5 | export function Foo({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | \`\`\` 13 | " 14 | `; 15 | -------------------------------------------------------------------------------- /tests/v3-test/mdx/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { testSnapshotEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'mdx', 9 | }; 10 | 11 | const fixtures: Omit[] = [ 12 | { 13 | name: "(1) This plugin doesn't support mdx parser, so it leaves the output of each plugin as is.", 14 | input: ` 15 | \`\`\`jsx 16 | export function Foo({ children }) { 17 | return ( 18 |
19 | {children} 20 |
21 | ); 22 | } 23 | \`\`\` 24 | `, 25 | options: { 26 | plugins: ['prettier-plugin-classnames'], 27 | }, 28 | }, 29 | ]; 30 | 31 | testSnapshotEach(fixtures, options); 32 | -------------------------------------------------------------------------------- /tests/v3-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "v3-test", 4 | "version": "0.0.0", 5 | "author": "Hyeonjong ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "vitest run --passWithNoTests" 9 | }, 10 | "devDependencies": { 11 | "@trivago/prettier-plugin-sort-imports": "4.2.1", 12 | "prettier": "3.0.3", 13 | "prettier-plugin-astro": "0.11.0", 14 | "prettier-plugin-brace-style": "0.7.0", 15 | "prettier-plugin-classnames": "0.7.5", 16 | "prettier-plugin-space-before-function-paren": "0.0.7", 17 | "prettier-plugin-svelte": "3.0.0", 18 | "prettier-plugin-tailwindcss": "0.5.2", 19 | "test-settings": "workspace:*", 20 | "vite-tsconfig-paths": "4.2.3", 21 | "vitest": "1.1.3", 22 | "vue": "3.3.9" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/v3-test/svelte/multiple-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, braceStylePluginOptions, classnamesPluginOptions, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'two plugins with some overlapping formatting regions (1) - svelte -> brace-style', 14 | input: ` 15 | 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: ` 35 | 36 |
39 | Now: {now} 40 |
41 | `, 42 | options: { 43 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-brace-style', thisPlugin], 44 | ...braceStylePluginOptions, 45 | }, 46 | }, 47 | { 48 | name: 'two plugins with some overlapping formatting regions (2) - brace-style -> svelte', 49 | input: ` 50 | 57 | 58 |
59 | Now: {now} 60 |
61 | `, 62 | output: ` 70 | 71 |
74 | Now: {now} 75 |
76 | `, 77 | options: { 78 | plugins: ['prettier-plugin-brace-style', 'prettier-plugin-svelte', thisPlugin], 79 | ...braceStylePluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'two plugins with some overlapping formatting regions (3) - svelte -> tailwindcss', 84 | input: ` 85 | 92 | 93 |
94 | Now: {now} 95 |
96 | `, 97 | output: ` 104 | 105 |
108 | Now: {now} 109 |
110 | `, 111 | options: { 112 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-tailwindcss', thisPlugin], 113 | }, 114 | }, 115 | { 116 | name: 'two plugins with some overlapping formatting regions (4) - tailwindcss -> svelte', 117 | input: ` 118 | 125 | 126 |
127 | Now: {now} 128 |
129 | `, 130 | output: ` 137 | 138 |
141 | Now: {now} 142 |
143 | `, 144 | options: { 145 | plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-svelte', thisPlugin], 146 | }, 147 | }, 148 | { 149 | name: 'two plugins with some overlapping formatting regions (5) - svelte -> classnames', 150 | input: ` 151 | 158 | 159 |
160 | Now: {now} 161 |
162 | `, 163 | output: ` 170 | 171 |
175 | Now: {now} 176 |
177 | `, 178 | options: { 179 | plugins: ['prettier-plugin-svelte', 'prettier-plugin-classnames', thisPlugin], 180 | ...classnamesPluginOptions, 181 | }, 182 | }, 183 | { 184 | name: 'two plugins with some overlapping formatting regions (6) - classnames -> svelte', 185 | input: ` 186 | 193 | 194 |
195 | Now: {now} 196 |
197 | `, 198 | output: ` 205 | 206 |
210 | Now: {now} 211 |
212 | `, 213 | options: { 214 | plugins: ['prettier-plugin-classnames', 'prettier-plugin-svelte', thisPlugin], 215 | ...classnamesPluginOptions, 216 | }, 217 | }, 218 | ]; 219 | 220 | testEach(fixtures, options); 221 | -------------------------------------------------------------------------------- /tests/v3-test/svelte/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'svelte plugin (1) - standalone use', 14 | input: ` 15 | 22 | 23 |
24 | Now: {now} 25 |
26 | `, 27 | output: ` 34 | 35 |
38 | Now: {now} 39 |
40 | `, 41 | options: { 42 | plugins: ['prettier-plugin-svelte'], 43 | }, 44 | }, 45 | { 46 | name: 'svelte plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 47 | input: ` 48 | 55 | 56 |
57 | Now: {now} 58 |
59 | `, 60 | output: ` 67 | 68 |
71 | Now: {now} 72 |
73 | `, 74 | options: { 75 | plugins: ['prettier-plugin-svelte', thisPlugin], 76 | }, 77 | }, 78 | ]; 79 | 80 | testEach(fixtures, options); 81 | -------------------------------------------------------------------------------- /tests/v3-test/svelte/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testErrorEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'svelte', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1) - it will not work without the svelte plugin', 14 | input: `any input`, 15 | output: `any output`, 16 | options: { 17 | plugins: [], 18 | }, 19 | }, 20 | { 21 | name: 'no plugins (2) - it will not work without the svelte plugin', 22 | input: `this plugin does not have`, 23 | output: `a built-in svelte compiler (or parser).`, 24 | options: { 25 | plugins: [thisPlugin], 26 | }, 27 | }, 28 | ]; 29 | 30 | testErrorEach(fixtures, options); 31 | -------------------------------------------------------------------------------- /tests/v3-test/typescript/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'typescript', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | import { CounterButton } from './parts'; 23 | import { CounterContainer } from '@/layouts'; 24 | import { useState } from 'react'; 25 | 26 | export default function Counter({ 27 | label = 'Counter', 28 | onChange = undefined, 29 | }) { 30 | const [count, setCount] = useState(0); 31 | 32 | const incrementHandler = () => { 33 | setCount((c) => c + 1); 34 | onChange?.(count + 1); 35 | }; 36 | 37 | return ( 38 | 39 | {label} 40 | {count} 41 | 42 | 43 | ); 44 | } 45 | `, 46 | output: `import { useState } from "react"; 47 | 48 | import { CounterContainer } from "@/layouts"; 49 | 50 | import { CounterButton } from "./parts"; 51 | 52 | export default function Counter({ label = "Counter", onChange = undefined }) { 53 | const [count, setCount] = useState(0); 54 | 55 | const incrementHandler = () => { 56 | setCount((c) => c + 1); 57 | onChange?.(count + 1); 58 | }; 59 | 60 | return ( 61 | 62 | {label} 63 | {count} 64 | 65 | 66 | ); 67 | } 68 | `, 69 | options: { 70 | plugins: ['@trivago/prettier-plugin-sort-imports'], 71 | ...sortImportsPluginOptions, 72 | }, 73 | }, 74 | { 75 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 76 | input: ` 77 | import { CounterButton } from './parts'; 78 | import { CounterContainer } from '@/layouts'; 79 | import { useState } from 'react'; 80 | 81 | export default function Counter({ 82 | label = 'Counter', 83 | onChange = undefined, 84 | }) { 85 | const [count, setCount] = useState(0); 86 | 87 | const incrementHandler = () => { 88 | setCount((c) => c + 1); 89 | onChange?.(count + 1); 90 | }; 91 | 92 | return ( 93 | 94 | {label} 95 | {count} 96 | 97 | 98 | ); 99 | } 100 | `, 101 | output: `import { useState } from "react"; 102 | 103 | import { CounterContainer } from "@/layouts"; 104 | 105 | import { CounterButton } from "./parts"; 106 | 107 | export default function Counter({ label = "Counter", onChange = undefined }) { 108 | const [count, setCount] = useState(0); 109 | 110 | const incrementHandler = () => { 111 | setCount((c) => c + 1); 112 | onChange?.(count + 1); 113 | }; 114 | 115 | return ( 116 | 117 | {label} 118 | {count} 119 | 120 | 121 | ); 122 | } 123 | `, 124 | options: { 125 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 126 | ...sortImportsPluginOptions, 127 | }, 128 | }, 129 | { 130 | name: 'brace-style plugin (1) - standalone use', 131 | input: ` 132 | import { CounterButton } from './parts'; 133 | import { CounterContainer } from '@/layouts'; 134 | import { useState } from 'react'; 135 | 136 | export default function Counter({ 137 | label = 'Counter', 138 | onChange = undefined, 139 | }) { 140 | const [count, setCount] = useState(0); 141 | 142 | const incrementHandler = () => { 143 | setCount((c) => c + 1); 144 | onChange?.(count + 1); 145 | }; 146 | 147 | return ( 148 | 149 | {label} 150 | {count} 151 | 152 | 153 | ); 154 | } 155 | `, 156 | output: `import { CounterButton } from "./parts"; 157 | import { CounterContainer } from "@/layouts"; 158 | import { useState } from "react"; 159 | 160 | export default function Counter({ label = "Counter", onChange = undefined }) 161 | { 162 | const [count, setCount] = useState(0); 163 | 164 | const incrementHandler = () => 165 | { 166 | setCount((c) => c + 1); 167 | onChange?.(count + 1); 168 | }; 169 | 170 | return ( 171 | 172 | {label} 173 | {count} 174 | 175 | 176 | ); 177 | } 178 | `, 179 | options: { 180 | plugins: ['prettier-plugin-brace-style'], 181 | ...braceStylePluginOptions, 182 | }, 183 | }, 184 | { 185 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 186 | input: ` 187 | import { CounterButton } from './parts'; 188 | import { CounterContainer } from '@/layouts'; 189 | import { useState } from 'react'; 190 | 191 | export default function Counter({ 192 | label = 'Counter', 193 | onChange = undefined, 194 | }) { 195 | const [count, setCount] = useState(0); 196 | 197 | const incrementHandler = () => { 198 | setCount((c) => c + 1); 199 | onChange?.(count + 1); 200 | }; 201 | 202 | return ( 203 | 204 | {label} 205 | {count} 206 | 207 | 208 | ); 209 | } 210 | `, 211 | output: `import { CounterButton } from "./parts"; 212 | import { CounterContainer } from "@/layouts"; 213 | import { useState } from "react"; 214 | 215 | export default function Counter({ label = "Counter", onChange = undefined }) 216 | { 217 | const [count, setCount] = useState(0); 218 | 219 | const incrementHandler = () => 220 | { 221 | setCount((c) => c + 1); 222 | onChange?.(count + 1); 223 | }; 224 | 225 | return ( 226 | 227 | {label} 228 | {count} 229 | 230 | 231 | ); 232 | } 233 | `, 234 | options: { 235 | plugins: ['prettier-plugin-brace-style', thisPlugin], 236 | ...braceStylePluginOptions, 237 | }, 238 | }, 239 | { 240 | name: 'tailwindcss plugin (1) - standalone use', 241 | input: ` 242 | import { CounterButton } from './parts'; 243 | import { CounterContainer } from '@/layouts'; 244 | import { useState } from 'react'; 245 | 246 | export default function Counter({ 247 | label = 'Counter', 248 | onChange = undefined, 249 | }) { 250 | const [count, setCount] = useState(0); 251 | 252 | const incrementHandler = () => { 253 | setCount((c) => c + 1); 254 | onChange?.(count + 1); 255 | }; 256 | 257 | return ( 258 | 259 | {label} 260 | {count} 261 | 262 | 263 | ); 264 | } 265 | `, 266 | output: `import { CounterButton } from "./parts"; 267 | import { CounterContainer } from "@/layouts"; 268 | import { useState } from "react"; 269 | 270 | export default function Counter({ label = "Counter", onChange = undefined }) { 271 | const [count, setCount] = useState(0); 272 | 273 | const incrementHandler = () => { 274 | setCount((c) => c + 1); 275 | onChange?.(count + 1); 276 | }; 277 | 278 | return ( 279 | 280 | {label} 281 | {count} 282 | 283 | 284 | ); 285 | } 286 | `, 287 | options: { 288 | plugins: ['prettier-plugin-tailwindcss'], 289 | }, 290 | }, 291 | { 292 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 293 | input: ` 294 | import { CounterButton } from './parts'; 295 | import { CounterContainer } from '@/layouts'; 296 | import { useState } from 'react'; 297 | 298 | export default function Counter({ 299 | label = 'Counter', 300 | onChange = undefined, 301 | }) { 302 | const [count, setCount] = useState(0); 303 | 304 | const incrementHandler = () => { 305 | setCount((c) => c + 1); 306 | onChange?.(count + 1); 307 | }; 308 | 309 | return ( 310 | 311 | {label} 312 | {count} 313 | 314 | 315 | ); 316 | } 317 | `, 318 | output: `import { CounterButton } from "./parts"; 319 | import { CounterContainer } from "@/layouts"; 320 | import { useState } from "react"; 321 | 322 | export default function Counter({ label = "Counter", onChange = undefined }) { 323 | const [count, setCount] = useState(0); 324 | 325 | const incrementHandler = () => { 326 | setCount((c) => c + 1); 327 | onChange?.(count + 1); 328 | }; 329 | 330 | return ( 331 | 332 | {label} 333 | {count} 334 | 335 | 336 | ); 337 | } 338 | `, 339 | options: { 340 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 341 | }, 342 | }, 343 | { 344 | name: 'classnames plugin (1) - standalone use', 345 | input: ` 346 | export function Callout({ children }) { 347 | return ( 348 |
349 | {children} 350 |
351 | ); 352 | } 353 | `, 354 | output: `export function Callout({ children }) { 355 | return ( 356 |
360 | {children} 361 |
362 | ); 363 | } 364 | `, 365 | options: { 366 | plugins: ['prettier-plugin-classnames'], 367 | ...classnamesPluginOptions, 368 | }, 369 | }, 370 | { 371 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 372 | input: ` 373 | export function Callout({ children }) { 374 | return ( 375 |
376 | {children} 377 |
378 | ); 379 | } 380 | `, 381 | output: `export function Callout({ children }) { 382 | return ( 383 |
387 | {children} 388 |
389 | ); 390 | } 391 | `, 392 | options: { 393 | plugins: ['prettier-plugin-classnames', thisPlugin], 394 | ...classnamesPluginOptions, 395 | }, 396 | }, 397 | { 398 | name: 'issue #15', 399 | input: ` 400 | export function Counter() { 401 | return {count}; 402 | } 403 | `, 404 | output: `export function Counter() { 405 | return {count}; 406 | } 407 | `, 408 | options: { 409 | plugins: [require.resolve('prettier-plugin-tailwindcss'), thisPlugin], 410 | }, 411 | }, 412 | { 413 | name: 'issue #31 (1) - standalone use', 414 | input: ` 415 | export function Callout({ children }) { 416 | return null; 417 | } 418 | `, 419 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 420 | options: { 421 | plugins: [noopPlugin], 422 | endOfLine: 'crlf', 423 | }, 424 | }, 425 | { 426 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 427 | input: ` 428 | export function Callout({ children }) { 429 | return null; 430 | } 431 | `, 432 | output: `export function Callout({ children }) {\r\n return null;\r\n}\r\n`, 433 | options: { 434 | plugins: [noopPlugin, thisPlugin], 435 | endOfLine: 'crlf', 436 | }, 437 | }, 438 | { 439 | name: 'issue #34 (1) - standalone use', 440 | input: ` 441 | export function $1_$$2_$$$3_$$$$4({ children }) { 442 | return
{children}
; 443 | } 444 | `, 445 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 446 | { 447 | return
{children}
; 448 | } 449 | `, 450 | options: { 451 | plugins: ['prettier-plugin-brace-style'], 452 | ...braceStylePluginOptions, 453 | }, 454 | }, 455 | { 456 | name: 'issue #34 (2) - a combination of a single plugin and a merge plugin also has no effect', 457 | input: ` 458 | export function $1_$$2_$$$3_$$$$4({ children }) { 459 | return
{children}
; 460 | } 461 | `, 462 | output: `export function $1_$$2_$$$3_$$$$4({ children }) 463 | { 464 | return
{children}
; 465 | } 466 | `, 467 | options: { 468 | plugins: ['prettier-plugin-brace-style', thisPlugin], 469 | ...braceStylePluginOptions, 470 | }, 471 | }, 472 | ]; 473 | 474 | testEach(fixtures, options); 475 | -------------------------------------------------------------------------------- /tests/v3-test/typescript/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'typescript', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | import { CounterButton } from './parts'; 16 | import { CounterContainer } from '@/layouts'; 17 | import { useState } from 'react'; 18 | 19 | export default function Counter({ 20 | label = 'Counter', 21 | onChange = undefined, 22 | }) { 23 | const [count, setCount] = useState(0); 24 | 25 | const incrementHandler = () => { 26 | setCount((c) => c + 1); 27 | onChange?.(count + 1); 28 | }; 29 | 30 | return ( 31 | 32 | {label} 33 | {count} 34 | 35 | 36 | ); 37 | } 38 | `, 39 | output: `import { CounterButton } from "./parts"; 40 | import { CounterContainer } from "@/layouts"; 41 | import { useState } from "react"; 42 | 43 | export default function Counter({ label = "Counter", onChange = undefined }) { 44 | const [count, setCount] = useState(0); 45 | 46 | const incrementHandler = () => { 47 | setCount((c) => c + 1); 48 | onChange?.(count + 1); 49 | }; 50 | 51 | return ( 52 | 53 | {label} 54 | {count} 55 | 56 | 57 | ); 58 | } 59 | `, 60 | options: { 61 | plugins: [], 62 | }, 63 | }, 64 | { 65 | name: 'no plugins (2) - merge plugin alone has no effect', 66 | input: ` 67 | import { CounterButton } from './parts'; 68 | import { CounterContainer } from '@/layouts'; 69 | import { useState } from 'react'; 70 | 71 | export default function Counter({ 72 | label = 'Counter', 73 | onChange = undefined, 74 | }) { 75 | const [count, setCount] = useState(0); 76 | 77 | const incrementHandler = () => { 78 | setCount((c) => c + 1); 79 | onChange?.(count + 1); 80 | }; 81 | 82 | return ( 83 | 84 | {label} 85 | {count} 86 | 87 | 88 | ); 89 | } 90 | `, 91 | output: `import { CounterButton } from "./parts"; 92 | import { CounterContainer } from "@/layouts"; 93 | import { useState } from "react"; 94 | 95 | export default function Counter({ label = "Counter", onChange = undefined }) { 96 | const [count, setCount] = useState(0); 97 | 98 | const incrementHandler = () => { 99 | setCount((c) => c + 1); 100 | onChange?.(count + 1); 101 | }; 102 | 103 | return ( 104 | 105 | {label} 106 | {count} 107 | 108 | 109 | ); 110 | } 111 | `, 112 | options: { 113 | plugins: [thisPlugin], 114 | }, 115 | }, 116 | ]; 117 | 118 | testEach(fixtures, options); 119 | -------------------------------------------------------------------------------- /tests/v3-test/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import tsconfigPaths from 'vite-tsconfig-paths'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [tsconfigPaths()], 6 | }); 7 | -------------------------------------------------------------------------------- /tests/v3-test/vue/single-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { 5 | thisPlugin, 6 | noopPlugin, 7 | sortImportsPluginOptions, 8 | braceStylePluginOptions, 9 | classnamesPluginOptions, 10 | testEach, 11 | } from '../adaptor'; 12 | 13 | const options = { 14 | ...baseOptions, 15 | parser: 'vue', 16 | }; 17 | 18 | const fixtures: Fixture[] = [ 19 | { 20 | name: 'sort-imports plugin (1) - standalone use', 21 | input: ` 22 | 35 | 36 | 43 | `, 44 | output: ` 62 | 63 | 76 | `, 77 | options: { 78 | plugins: ['@trivago/prettier-plugin-sort-imports'], 79 | ...sortImportsPluginOptions, 80 | }, 81 | }, 82 | { 83 | name: 'sort-imports plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 84 | input: ` 85 | 98 | 99 | 106 | `, 107 | output: ` 125 | 126 | 139 | `, 140 | options: { 141 | plugins: ['@trivago/prettier-plugin-sort-imports', thisPlugin], 142 | ...sortImportsPluginOptions, 143 | }, 144 | }, 145 | { 146 | name: 'brace-style plugin (1) - standalone use', 147 | input: ` 148 | 161 | 162 | 169 | `, 170 | output: ` 186 | 187 | 201 | `, 202 | options: { 203 | plugins: ['prettier-plugin-brace-style'], 204 | ...braceStylePluginOptions, 205 | }, 206 | }, 207 | { 208 | name: 'brace-style plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 209 | input: ` 210 | 223 | 224 | 231 | `, 232 | output: ` 248 | 249 | 263 | `, 264 | options: { 265 | plugins: ['prettier-plugin-brace-style', thisPlugin], 266 | ...braceStylePluginOptions, 267 | }, 268 | }, 269 | { 270 | name: 'tailwindcss plugin (1) - standalone use', 271 | input: ` 272 | 285 | 286 | 293 | `, 294 | output: ` 310 | 311 | 324 | `, 325 | options: { 326 | plugins: ['prettier-plugin-tailwindcss'], 327 | }, 328 | }, 329 | { 330 | name: 'tailwindcss plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 331 | input: ` 332 | 345 | 346 | 353 | `, 354 | output: ` 370 | 371 | 384 | `, 385 | options: { 386 | plugins: ['prettier-plugin-tailwindcss', thisPlugin], 387 | }, 388 | }, 389 | { 390 | name: 'classnames plugin (1) - standalone use', 391 | input: ` 392 | 397 | `, 398 | output: ` 406 | `, 407 | options: { 408 | plugins: ['prettier-plugin-classnames'], 409 | ...classnamesPluginOptions, 410 | }, 411 | }, 412 | { 413 | name: 'classnames plugin (2) - a combination of a single plugin and a merge plugin also has no effect', 414 | input: ` 415 | 420 | `, 421 | output: ` 429 | `, 430 | options: { 431 | plugins: ['prettier-plugin-classnames', thisPlugin], 432 | ...classnamesPluginOptions, 433 | }, 434 | }, 435 | { 436 | name: 'issue #31 (1) - standalone use', 437 | input: ` 438 | 443 | `, 444 | output: `\r\n`, 445 | options: { 446 | plugins: [noopPlugin], 447 | endOfLine: 'crlf', 448 | }, 449 | }, 450 | { 451 | name: 'issue #31 (2) - a combination of a single plugin and a merge plugin also has no effect', 452 | input: ` 453 | 458 | `, 459 | output: `\r\n`, 460 | options: { 461 | plugins: [noopPlugin, thisPlugin], 462 | endOfLine: 'crlf', 463 | }, 464 | }, 465 | ]; 466 | 467 | testEach(fixtures, options); 468 | -------------------------------------------------------------------------------- /tests/v3-test/vue/zero-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import type { Fixture } from 'test-settings'; 2 | import { baseOptions } from 'test-settings'; 3 | 4 | import { thisPlugin, testEach } from '../adaptor'; 5 | 6 | const options = { 7 | ...baseOptions, 8 | parser: 'vue', 9 | }; 10 | 11 | const fixtures: Fixture[] = [ 12 | { 13 | name: 'no plugins (1)', 14 | input: ` 15 | 28 | 29 | 36 | `, 37 | output: ` 53 | 54 | 67 | `, 68 | options: { 69 | plugins: [], 70 | }, 71 | }, 72 | { 73 | name: 'no plugins (2) - merge plugin alone has no effect', 74 | input: ` 75 | 88 | 89 | 96 | `, 97 | output: ` 113 | 114 | 127 | `, 128 | options: { 129 | plugins: [thisPlugin], 130 | }, 131 | }, 132 | ]; 133 | 134 | testEach(fixtures, options); 135 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "ESNext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "baseUrl": ".", 15 | "paths": { 16 | "@/*": ["./src/*"] 17 | } 18 | }, 19 | "include": ["**/*.ts", "**/*.mts"], 20 | "exclude": ["node_modules"] 21 | } 22 | --------------------------------------------------------------------------------