├── .commitlintrc ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-push ├── .prettierignore ├── .prettierrc.json ├── .tool-versions ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs └── rules │ ├── filename-blocklist.md │ ├── filename-naming-convention.md │ ├── folder-match-with-fex.md │ ├── folder-naming-convention.md │ └── no-index.md ├── eslint.config.js ├── examples └── basic │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ └── src │ ├── components │ └── Button │ │ ├── Button.module.css │ │ ├── Button.tsx │ │ └── __tests__ │ │ └── Button.test.tsx │ ├── ko-fi.webp │ ├── main.js │ ├── main.md │ ├── models │ └── user.models.ts │ └── utils │ ├── __tests__ │ └── formatDate.test.ts │ └── formatDate.ts ├── lib ├── constants │ ├── message.js │ ├── naming-convention.js │ ├── next-js-naming-convention.js │ └── regex.js ├── index.js ├── rules │ ├── filename-blocklist.js │ ├── filename-naming-convention.js │ ├── folder-match-with-fex.js │ ├── folder-naming-convention.js │ └── no-index.js └── utils │ ├── doc.js │ ├── filename.js │ ├── rule.js │ ├── utility.js │ └── validation.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── tea.yaml ├── tests └── lib │ └── rules │ ├── filename-blocklist.posix.js │ ├── filename-blocklist.windows.js │ ├── filename-naming-convention.posix.js │ ├── filename-naming-convention.windows.js │ ├── folder-match-with-fex.posix.js │ ├── folder-match-with-fex.windows.js │ ├── folder-naming-convention.posix.js │ ├── folder-naming-convention.windows.js │ ├── no-index.posix.js │ └── no-index.windows.js └── tsconfig.json /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dukeluo 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: huanluo 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **Expected behavior** 13 | A clear and concise description of what you expected to happen. 14 | 15 | **Plugin configuration** 16 | Your configuration of this plugin. 17 | 18 | **Project structure** 19 | If applicable, add a minimal reproducible project structure generated by the `tree` cmd like the one below. 20 | 21 | ```sh 22 | . 23 | ├── create_folder_file.sh 24 | ├── kebab-case 25 | │ ├── __tests__ 26 | │ │ ├── b.js 27 | │ │ └── test.js 28 | │ └── index.js 29 | ├── package-lock.json 30 | ├── package.json 31 | └── src 32 | └── kebab-case 33 | └── index.js 34 | ``` 35 | 36 | **Desktop (please complete the following information):** 37 | 38 | - OS: [e.g. macOS] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [created] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version-file: .tool-versions 19 | - run: npm ci 20 | - run: npm run lint 21 | - run: npm run test 22 | 23 | publish-npm: 24 | needs: build 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | id-token: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version-file: .tool-versions 34 | registry-url: 'https://registry.npmjs.org' 35 | - run: npm ci 36 | - run: npm run build 37 | - run: npm publish --provenance --access public 38 | env: 39 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Running test suite 2 | on: [push, pull_request] 3 | permissions: 4 | contents: read 5 | statuses: write 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | platform: [ubuntu-latest, macos-latest, windows-latest] 11 | runs-on: ${{ matrix.platform }} 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version-file: .tool-versions 17 | - run: npm install 18 | - run: npm run lint 19 | - run: npm run example:lint 20 | - run: npm run test 21 | - run: npm run test:report 22 | - name: Upload coverage reports to Codecov 23 | uses: codecov/codecov-action@v4 24 | with: 25 | directory: coverage 26 | token: ${{ secrets.CODECOV_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | .vscode 5 | dist 6 | coverage 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit $1 && npm run lint:fix 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run test && npm run example:lint 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | .vscode 5 | dist 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "quoteProps": "as-needed", 6 | "trailingComma": "es5", 7 | "bracketSpacing": true, 8 | "arrowParens": "always", 9 | "embeddedLanguageFormatting": "auto", 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 20.19.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.3.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v3.2.0...v3.3.0) - 2025-06-09 9 | 10 | ### Added 11 | 12 | - add `ignoreWords` parameter to `folder-naming-convention` rule 13 | 14 | ## [3.2.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v3.1.0...v3.2.0) - 2025-04-13 15 | 16 | ### Added 17 | 18 | - add support for TypeScript declaration file 19 | 20 | ## [3.1.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v3.0.0...v3.1.0) - 2025-02-16 21 | 22 | ### Added 23 | 24 | - add support for css,json,markdown parsers 25 | 26 | ### Changed 27 | 28 | - improve documentation 29 | 30 | ## [3.0.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.8.0...v3.0.0) - 2025-01-30 31 | 32 | ### Added 33 | 34 | - the plugin can lint non-js/ts files (e.g., images, styles, etc.) with processor `eslint-processor-check-file` 35 | - the rule `filename-naming-convention` can be used with a new built-in naming convention `NEXT_JS_PAGE_ROUTER_FILENAME_CASE` which is used for Next.js page router project 36 | - the rule `no-index` can set `errorMessage` property to customize the error message 37 | - the rule `folder-match-with-fex` can set `errorMessage` property to customize the error message 38 | - the rule `folder-naming-convention` can set `errorMessage` property to customize the error message 39 | 40 | ### Changed 41 | 42 | - the error message of the rule `folder-match-with-fex` uses filename without path 43 | - improve documentation 44 | 45 | ### Fixed 46 | 47 | - `NEXT_JS_APP_ROUTER_CASE` can support filename route like `rss.xml` 48 | 49 | ### Removed 50 | 51 | - remove legacy support for `context` object 52 | - remove legacy support for rule `filename-blocklist` 53 | - remove legacy configuration support 54 | 55 | ## [2.8.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.7.1...v2.8.0) - 2024-04-14 56 | 57 | ### Added 58 | 59 | - support flat config for ESLint v9.0.0 60 | 61 | ## [2.7.1](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.7.0...v2.7.1) - 2024-02-24 62 | 63 | ### Fixed 64 | 65 | - the rule `filename-blocklist` can set the suggested glob pattern as empty string when `errorMessage` is set 66 | 67 | ## [2.7.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.6.2...v2.7.0) - 2024-02-18 68 | 69 | ### Added 70 | 71 | - the rule `filename-naming-convention` can set `errorMessage` property to customize the error message 72 | - the rule `filename-blocklist` can set `errorMessage` property to customize the error message 73 | 74 | ### Changed 75 | 76 | - Node.js version should >= 18 77 | 78 | ## [2.6.2](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.6.1...v2.6.2) - 2023-08-13 79 | 80 | ### Fixed 81 | 82 | - the naming convention `NEXT_JS_APP_ROUTER_CASE` can support Next.js Private Folders 83 | 84 | ## [2.6.1](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.6.0...v2.6.1) - 2023-07-30 85 | 86 | ### Fixed 87 | 88 | - fix bundle mistake in v2.6.0 89 | 90 | ## [2.6.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.5.0...v2.6.0) - 2023-07-30 91 | 92 | ### Added 93 | 94 | - added ES Module support 95 | 96 | ### Deprecated 97 | 98 | - deprecated Node.js 14 support 99 | 100 | ## [2.5.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.4.0...v2.5.0) - 2023-07-05 101 | 102 | ### Added 103 | 104 | - the rule `folder-naming-convention` can be used with a new built-in naming convention `NEXT_JS_APP_ROUTER_CASE` 105 | 106 | ## [2.4.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.3.0...v2.4.0) - 2023-06-12 107 | 108 | ### Fixed 109 | 110 | - the rule `folder-naming-convention` can work well with `*` in the glob 111 | 112 | ### Added 113 | 114 | - the rule `no-index` can ignore middle extensions 115 | 116 | ### Changed 117 | 118 | - improve documentation 119 | 120 | ## [2.3.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.2.0...v2.3.0) - 2023-05-13 121 | 122 | ### Fixed 123 | 124 | - the rule `folder-naming-convention` can use a pattern with multiple matchers to select target folders 125 | 126 | ### Added 127 | 128 | - the rule `filename-blocklist` should report an error when blocklist pattern object isn't an object type 129 | - the rule `filename-naming-convention` should report an error when naming pattern object isn't an object type 130 | - the rule `folder-match-with-fex` should report an error when naming pattern object isn't an object type 131 | - the rule `folder-naming-convention` should report an error when naming pattern object isn't an object type 132 | 133 | ### Changed 134 | 135 | - unify the style of error messages for the existing rules 136 | - upgrade dependencies to the latest version 137 | 138 | ## [2.2.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.1.0...v2.2.0) - 2023-04-01 139 | 140 | ### Added 141 | 142 | - the rule `filename-naming-convention` can use prefined match syntax 143 | 144 | ## [2.1.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v2.0.0...v2.1.0) - 2023-03-25 145 | 146 | ### Fixed 147 | 148 | - the rule `filename-blacklist` can specify the target file by its file path 149 | 150 | ### Deprecated 151 | 152 | - the rule `filename-blacklist` can specify the target file by its filename 153 | 154 | ## [2.0.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.3.1...v2.0.0) - 2023-03-05 155 | 156 | ### Changed 157 | 158 | - the rule `filename-blacklist` renamed to `filename-blocklist` 159 | - the rule `filename-naming-convention` show filename without path in error message 160 | 161 | ### Removed 162 | 163 | - the rule `filename-naming-convention` can specify the target file by its extension 164 | 165 | ## [1.3.1](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.3.0...v1.3.1) - 2023-01-15 166 | 167 | ### Fixed 168 | 169 | - fix builtin `CAMEL_CASE` glob expression 170 | 171 | ## [1.3.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.2.3...v1.3.0) - 2022-11-05 172 | 173 | ### Added 174 | 175 | - the rule `filename-blacklist` can blacklist filenames by pattern 176 | 177 | ## [1.2.3](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.2.2...v1.2.3) -2022-09-22 178 | 179 | ### Fixed 180 | 181 | - fix builtin `SNAKE_CASE`, `KEBAB_CASE` and `SCREAMING_SNAKE_CASE` glob expressions 182 | 183 | ### Changed 184 | 185 | - reduce npm package size 186 | 187 | ## [1.2.2](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.2.1...v1.2.2) -2022-07-15 188 | 189 | ### Fixed 190 | 191 | - enhance support for the Windows operating system 192 | 193 | ## [1.2.1](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.2.0...v1.2.1) -2022-07-09 194 | 195 | ### Added 196 | 197 | - add CHANGELOG 198 | 199 | ### Changed 200 | 201 | - add Windows operation system support 202 | 203 | ### Fixed 204 | 205 | - fix get wrong folder issue when `eslint` is worked with processors 206 | 207 | ## [1.2.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.1.0...v1.2.0) - 2022-07-04 208 | 209 | ### Added 210 | 211 | - the rule `filename-naming-convention` can specify the target file by its file path 212 | - the rule `filename-naming-convention` can set `ignoreMiddleExtensions` property to ignore middle extensions when matching naming pattern 213 | 214 | ### Changed 215 | 216 | - optimize docs 217 | 218 | ### Deprecated 219 | 220 | - the rule `filename-naming-convention` can specify the target file by its extension 221 | 222 | ## [1.1.0](https://github.com/dukeluo/eslint-plugin-check-file/compare/v1.0.0...v1.1.0) - 2022-02-23 223 | 224 | ### Added 225 | 226 | - `check-file/folder-naming-convention`: Enforce a consistent naming pattern for the name of the specified folder 227 | 228 | ## [1.0.0](https://github.com/dukeluo/eslint-plugin-check-file/releases/tag/v1.0.0) - 2022-01-11 229 | 230 | ### Added 231 | 232 | - `check-file/folder-match-with-fex`: Enforce a consistent naming pattern for the folder of the specified file extension 233 | - `check-file/filename-naming-convention`: Enforce a consistent naming pattern for the filename of the specified file extension 234 | - `check-file/no-index`: A file cannot be named "index" 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-check-file 2 | 3 | [![NPM Version][npm-image]][downloads-url] 4 | [![NPM Downloads][downloads-image]][downloads-url] 5 | [![NPM License][license-image]][downloads-url] 6 | [![Test Workflow Status][test-workflow-image]][workflow-url] 7 | [![Test Coverage][test-coverage-image]][test-coverage-url] 8 | [![Follow Author on X][x-follow-image]][x-follow-url] 9 | 10 | An ESLint plugin that enforces consistent naming conventions for files and folders in your project. It helps maintain a clean and organized codebase by allowing you to define and enforce specific patterns for filenames and directory structures. 11 | 12 | ## Installation 13 | 14 | You'll first need to install [ESLint](https://eslint.org/): 15 | 16 | ```sh 17 | npm i eslint --save-dev 18 | ``` 19 | 20 | Next, install 21 | `eslint-plugin-check-file`: 22 | 23 | ```sh 24 | npm install eslint-plugin-check-file --save-dev 25 | ``` 26 | 27 | ## Usage 28 | 29 | This plugin supports ESLint's [flat configuration](https://eslint.org/docs/latest/use/configure/configuration-files). Here's a complete example: 30 | 31 | ```javascript 32 | import checkFile from 'eslint-plugin-check-file'; 33 | 34 | export default [ 35 | { 36 | // optional: add this processor to files which not processed by other processors but still require linting 37 | files: ['**/*.yaml', '**/*.webp'], 38 | processor: 'check-file/eslint-processor-check-file', 39 | }, 40 | { 41 | files: ['src/**/*.*'], 42 | plugins: { 43 | 'check-file': checkFile, 44 | }, 45 | rules: { 46 | 'check-file/no-index': 'error', 47 | 'check-file/filename-blocklist': [ 48 | 'error', 49 | { 50 | '**/*.model.ts': '*.models.ts', 51 | '**/*.util.ts': '*.utils.ts', 52 | }, 53 | ], 54 | 'check-file/folder-match-with-fex': [ 55 | 'error', 56 | { 57 | '*.test.{js,jsx,ts,tsx}': '**/__tests__/', 58 | '*.styled.{jsx,tsx}': '**/components/', 59 | }, 60 | ], 61 | 'check-file/filename-naming-convention': [ 62 | 'error', 63 | { 64 | '**/*.{jsx,tsx}': 'PASCAL_CASE', 65 | '**/*.{js,ts}': 'CAMEL_CASE', 66 | }, 67 | ], 68 | 'check-file/folder-naming-convention': [ 69 | 'error', 70 | { 71 | 'src/components/*/': 'PASCAL_CASE', 72 | 'src/!(components)/**/!(__tests__)/': 'CAMEL_CASE', 73 | }, 74 | ], 75 | }, 76 | }, 77 | ]; 78 | ``` 79 | 80 | ## Supported Rules 81 | 82 | - [check-file/no-index](docs/rules/no-index.md): A file cannot be named "index" 83 | - [check-file/filename-blocklist](docs/rules/filename-blocklist.md): Blocklist filenames by pattern 84 | - [check-file/folder-match-with-fex](docs/rules/folder-match-with-fex.md): Enforce a consistent naming pattern for folder names for specified files 85 | - [check-file/filename-naming-convention](docs/rules/filename-naming-convention.md): Enforce a consistent naming pattern for filenames for specified files 86 | - [check-file/folder-naming-convention](docs/rules/folder-naming-convention.md): Enforce a consistent naming pattern for folder names for specified folders 87 | 88 | ## Version Compatibility 89 | 90 | Version 3.x and above only support ESLint's flat configuration. For legacy configuration support, please use version 2.x. 91 | 92 | ## Support 93 | 94 | If you find this plugin helpful, consider supporting the project: 95 | 96 | [![GitHub Sponsors][github-sponsors-image]][github-sponsors-url] 97 | 98 | [![Ko-fi][ko-fi-image]][ko-fi-url] 99 | 100 | [npm-image]: https://img.shields.io/npm/v/eslint-plugin-check-file.svg 101 | [downloads-image]: https://img.shields.io/npm/dm/eslint-plugin-check-file.svg 102 | [license-image]: https://img.shields.io/npm/l/eslint-plugin-check-file 103 | [test-workflow-image]: https://img.shields.io/github/actions/workflow/status/dukeluo/eslint-plugin-check-file/test.yml?label=test 104 | [test-coverage-image]: https://img.shields.io/codecov/c/gh/dukeluo/eslint-plugin-check-file 105 | [ko-fi-image]: https://ko-fi.com/img/githubbutton_sm.svg 106 | [x-follow-image]: https://img.shields.io/badge/follow-@ihuanluo-black 107 | [downloads-url]: https://www.npmjs.com/package/eslint-plugin-check-file 108 | [workflow-url]: https://github.com/dukeluo/eslint-plugin-check-file/actions 109 | [test-coverage-url]: https://app.codecov.io/gh/dukeluo/eslint-plugin-check-file 110 | [ko-fi-url]: https://ko-fi.com/huanluo 111 | [x-follow-url]: https://x.com/ihuanluo 112 | [github-sponsors-image]: https://img.shields.io/github/sponsors/dukeluo?label=Sponsor%20me%20on%20GitHub%20Sponsors 113 | [github-sponsors-url]: https://github.com/sponsors/dukeluo 114 | -------------------------------------------------------------------------------- /docs/rules/filename-blocklist.md: -------------------------------------------------------------------------------- 1 | # The filename should be blocklisted (filename-blocklist) 2 | 3 | Allows you to blocklist certain filename patterns. 4 | 5 | ## Rule Details 6 | 7 | This rule aims to maintain a consistent naming scheme. This rule uses the glob match syntax to declare blocklisted and preferred filename patterns. 8 | 9 | If the rule had been set as follows: 10 | 11 | ```js 12 | ... 13 | 'check-file/filename-blocklist': ['error', { '**/*.model.ts': '*.models.ts' }], 14 | ... 15 | ``` 16 | 17 | Examples of **incorrect** filename with path for this rule: 18 | 19 | ```sh 20 | src/foo.model.ts 21 | src/bar.model.ts 22 | ``` 23 | 24 | Examples of **correct** filename with path for this rule: 25 | 26 | ```sh 27 | src/foo.models.ts 28 | src/bar.models.ts 29 | ``` 30 | 31 | ### Options 32 | 33 | #### blocklist pattern object 34 | 35 | The key is used to declare the blocklisted filename pattern, while the value is used to hint at the correct filename that should be used instead. Both the key and value in the blocklist pattern object are glob expressions. The plugin will only check blocklisted pattern you explicitly provided: 36 | 37 | ```js 38 | export default [ 39 | { 40 | plugins: { 41 | 'check-file': checkFile, 42 | }, 43 | rules: { 44 | 'check-file/filename-blocklist': [ 45 | 'error', 46 | { 47 | '**/*.model.ts': '*.models.ts', 48 | '**/*.util.ts': '*.utils.ts', 49 | }, 50 | ], 51 | }, 52 | }, 53 | ]; 54 | ``` 55 | 56 | #### rule configuration object 57 | 58 | ##### `errorMessage` 59 | 60 | Customizes the error message displayed when a file is blocked due to matching a blocklisted filename pattern. It offers two placeholders for dynamic content: 61 | 62 | - `{{ target }}`: Represents the filename of the blocked file. 63 | - `{{ pattern }}`: Represents the blocklisted filename pattern. 64 | 65 | When `errorMessage` is set, the suggested glob pattern is not necessary, it can be set as empty string. 66 | 67 | ```js 68 | export default [ 69 | { 70 | plugins: { 71 | 'check-file': checkFile, 72 | }, 73 | rules: { 74 | 'check-file/filename-blocklist': [ 75 | 'error', 76 | { '*.models.ts': '' }, 77 | { 78 | errorMessage: 79 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 80 | }, 81 | ], 82 | }, 83 | }, 84 | ]; 85 | ``` 86 | 87 | ## Further Reading 88 | 89 | - [micromatch](https://github.com/micromatch/micromatch) 90 | - [glob]() 91 | - [testing glob expression online 1](https://globster.xyz) 92 | - [testing glob expression online 2](https://www.digitalocean.com/community/tools/glob) 93 | -------------------------------------------------------------------------------- /docs/rules/filename-naming-convention.md: -------------------------------------------------------------------------------- 1 | # The filename should follow the filename naming convention (filename-naming-convention) 2 | 3 | Allows you to enforce a consistent naming pattern for the filename of the specified file. 4 | 5 | ## Rule Details 6 | 7 | This rule aims to format the filename of the specified file. This rule uses the glob match syntax to match target files and declare the naming pattern for the filename. 8 | 9 | There are six basic naming conventions built into this rule, including `CAMEL_CASE`, `PASCAL_CASE`, `SNAKE_CASE`, `KEBAB_CASE`, `SCREAMING_SNAKE_CASE` and `FLAT_CASE`. 10 | 11 | And there is also a special naming convention for Next.js page router project, which is `NEXT_JS_PAGE_ROUTER_FILENAME_CASE`, you can use it to ensure the filename of the page router is consistent with the naming convention. You can read more about it under [`NEXT_JS_PAGE_ROUTER_FILENAME_CASE`](#NEXT_JS_PAGE_ROUTER_FILENAME_CASE). 12 | 13 | | Formatting | Name | 14 | | ----------- | ---------------------- | 15 | | helloWorld | `CAMEL_CASE` | 16 | | HelloWorld | `PASCAL_CASE` | 17 | | hello_world | `SNAKE_CASE` | 18 | | hello-world | `KEBAB_CASE` | 19 | | HELLO_WORLD | `SCREAMING_SNAKE_CASE` | 20 | | helloworld | `FLAT_CASE` | 21 | 22 | If the rule had been set as follows: 23 | 24 | ```js 25 | ... 26 | 'check-file/filename-naming-convention': ['error', { 'src/services/*.js': 'PASCAL_CASE' }], 27 | ... 28 | ``` 29 | 30 | Examples of **incorrect** filename with path for this rule: 31 | 32 | ```sh 33 | src/services/downloadService.js 34 | src/services/downloadservice.js 35 | src/services/download-service.js 36 | src/services/download_service.js 37 | ``` 38 | 39 | Examples of **correct** filename with path for this rule: 40 | 41 | ```sh 42 | src/services/DownloadService.js 43 | src/download-service.js // this file is not be specified by the target pattern, so it is skipped 44 | ``` 45 | 46 | In addition to the built-in naming conventions, you can also set custom naming patterns using glob match syntax. The following code shows an example of how to ensure that all your `js` files are named begin with `__`: 47 | 48 | ```js 49 | ... 50 | 'check-file/filename-naming-convention': ['error', {'**/*.js': '__+([a-z])'}], 51 | ... 52 | ``` 53 | 54 | **Tip:** To selecte all your `js` files, your can use the glob expression `**/*.js`. 55 | 56 | ### `NEXT_JS_PAGE_ROUTER_FILENAME_CASE` 57 | 58 | The `NEXT_JS_PAGE_ROUTER_FILENAME_CASE` aims to support a wide range of filename naming convention in Next.js Page Router projects. If you would like to enforce a camelCase naming convention for your filename, but also support Next.js' Dynamic segments, Catch-all segments and Optional Catch-all Segments, this pattern is for you. 59 | 60 | ```js 61 | ... 62 | 'check-file/filename-naming-convention': [ 63 | 'error', 64 | { 65 | 'src/**/*': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 66 | }, 67 | ], 68 | ... 69 | ``` 70 | 71 | While `NEXT_JS_PAGE_ROUTER_FILENAME_CASE` covers many naming cases, it's possible that some cases may be missing. If you come across any missing cases, I encourage you to open an issue and provide the necessary details. Your feedback will help me improve and enhance the naming convention. 72 | 73 | ### Prefined Match Syntax 74 | 75 | Prefined match syntax allow you to capture specific part of the target file pattern and use it in your naming convention pattern. This syntax is particularly useful when you want to make a file to be named the same as its parent folder. 76 | 77 | To use prefined match in your rule set, you can use the `` syntax. The index refers to the position where the glob matcher occurs in the target file pattern expression, starting with `0`. Read more about glob capture groups in the [micromatch documentation](https://github.com/micromatch/micromatch#capture). 78 | 79 | If the rule had been set as follows: 80 | 81 | ```js 82 | ... 83 | 'check-file/filename-naming-convention': ['error', { '**/*/!(index).*': '<1>' }, { 'ignoreMiddleExtensions': true }], 84 | ... 85 | ``` 86 | 87 | Examples of **incorrect** filename with path for this rule: 88 | 89 | ```sh 90 | src/Portal/type.ts 91 | src/Portal/base.tsx 92 | ``` 93 | 94 | Examples of **correct** filename with path for this rule: 95 | 96 | ```sh 97 | src/Portal/index.ts 98 | src/Portal/Portal.test.tsx 99 | src/Portal/Portal.tsx 100 | src/Portal/Portal.types.ts 101 | ``` 102 | 103 | ### Options 104 | 105 | #### naming pattern object 106 | 107 | The key is used to select target files, while the value is used to declare the naming pattern for the filename. You can specify a different naming pattern for different files. The plugin will only check files you explicitly selected: 108 | 109 | ```js 110 | export default [ 111 | { 112 | plugins: { 113 | 'check-file': checkFile, 114 | }, 115 | rules: { 116 | 'check-file/filename-naming-convention': [ 117 | 'error', 118 | { 119 | '**/*.{jsx,tsx}': 'CAMEL_CASE', 120 | '**/*.{js,ts}': 'KEBAB_CASE', 121 | }, 122 | ], 123 | }, 124 | }, 125 | ]; 126 | ``` 127 | 128 | #### rule configuration object 129 | 130 | ##### `ignoreMiddleExtensions` 131 | 132 | If `true`, the rule will ignore the middle extensions of the filename. 133 | 134 | In some cases, you may want to ignore the middle extensions of the filename. For example, you want to lint the base name of the config and test/spec files—e.g., `babel.config.js` and `date.test.js`, you can do so by setting the `ignoreMiddleExtensions` option to `true`, and the rule will only validate its base name, in this case the base name will be `babel` and `date`. 135 | 136 | ```js 137 | export default [ 138 | { 139 | plugins: { 140 | 'check-file': checkFile, 141 | }, 142 | rules: { 143 | 'check-file/filename-naming-convention': [ 144 | 'error', 145 | { 146 | '**/*.{jsx,tsx}': 'CAMEL_CASE', 147 | '**/*.{js,ts}': 'KEBAB_CASE', 148 | }, 149 | { 150 | ignoreMiddleExtensions: true, 151 | }, 152 | ], 153 | }, 154 | }, 155 | ]; 156 | ``` 157 | 158 | ##### `errorMessage` 159 | 160 | Customizes the error message displayed when a file's filename doesn't match the declared naming pattern. It offers two placeholders for dynamic content: 161 | 162 | - `{{ target }}`: Represents the filename of the non-matching file. 163 | - `{{ pattern }}`: Represents the naming pattern. 164 | 165 | ```js 166 | export default [ 167 | { 168 | plugins: { 169 | 'check-file': checkFile, 170 | }, 171 | rules: { 172 | 'check-file/filename-naming-convention': [ 173 | 'error', 174 | { '**/*/!(index).*': '<1>' }, 175 | { 176 | errorMessage: 177 | 'The file "{{ target }}" does not match file naming convention defined("{{ pattern }}") for this project, see contribute.md for details', 178 | }, 179 | ], 180 | }, 181 | }, 182 | ]; 183 | ``` 184 | 185 | ## Further Reading 186 | 187 | - [micromatch](https://github.com/micromatch/micromatch) 188 | - [glob]() 189 | - [testing glob expression online 1](https://globster.xyz) 190 | - [testing glob expression online 2](https://www.digitalocean.com/community/tools/glob) 191 | -------------------------------------------------------------------------------- /docs/rules/folder-match-with-fex.md: -------------------------------------------------------------------------------- 1 | # The folder should match the naming pattern specified by its file (folder-match-with-fex) 2 | 3 | Allows you to enforce a consistent naming pattern for the specified files' folder names. 4 | 5 | ## Rule Details 6 | 7 | This rule aims to format the folder of the specified files. It will be useful when you want to group the specified files into a folder. This rule uses the glob match syntax to match target files and declare the naming pattern for their folder names. 8 | 9 | If the rule had been set as follows: 10 | 11 | ```js 12 | ... 13 | 'check-file/folder-match-with-fex': ['error', {'*.test.js': '**/__tests__/'}], 14 | ... 15 | ``` 16 | 17 | For the file `foo.test.js`, examples of **incorrect** folder for this rule: 18 | 19 | ```sh 20 | bar/_tests_/foo.test.js 21 | ``` 22 | 23 | For the file `foo.test.js`, examples of **correct** folder for this rule: 24 | 25 | ```sh 26 | bar/__tests__/foo.test.js 27 | ``` 28 | 29 | ### Options 30 | 31 | #### naming pattern object 32 | 33 | The key is used to select target files, while the value is used to declare the naming pattern for their folder names. You can specify a different folder naming pattern for different target files. The plugin will only check files you explicitly provided: 34 | 35 | ```js 36 | export default [ 37 | { 38 | plugins: { 39 | 'check-file': checkFile, 40 | }, 41 | rules: { 42 | 'check-file/folder-match-with-fex': [ 43 | 'error', 44 | { 45 | '*.test.{js,jsx,ts,tsx}': '**/__tests__/', 46 | '*.styled.{jsx,tsx}': '**/pages/', 47 | }, 48 | ], 49 | }, 50 | }, 51 | ]; 52 | ``` 53 | 54 | #### rule configuration object 55 | 56 | ##### `errorMessage` 57 | 58 | Customizes the error message displayed when a file's folder does not match the naming pattern. It offers two placeholders for dynamic content: 59 | 60 | - `{{ target }}`: Represents the target file. 61 | - `{{ pattern }}`: Represents the naming pattern for the target file's folder name. 62 | 63 | ```js 64 | export default [ 65 | { 66 | plugins: { 67 | 'check-file': checkFile, 68 | }, 69 | rules: { 70 | 'check-file/folder-match-with-fex': [ 71 | 'error', 72 | { 73 | '*.test.{js,jsx,ts,tsx}': '**/__tests__/', 74 | '*.styled.{jsx,tsx}': '**/pages/', 75 | }, 76 | { 77 | errorMessage: 78 | 'The folder of the file "{{ target }}" does not match the "{{ pattern }}" pattern, see contribute.md for details', 79 | }, 80 | ], 81 | }, 82 | }, 83 | ]; 84 | ``` 85 | 86 | ## Further Reading 87 | 88 | - [micromatch](https://github.com/micromatch/micromatch) 89 | - [glob]() 90 | - [testing glob expression online 1](https://globster.xyz) 91 | - [testing glob expression online 2](https://www.digitalocean.com/community/tools/glob) 92 | -------------------------------------------------------------------------------- /docs/rules/folder-naming-convention.md: -------------------------------------------------------------------------------- 1 | # The folder should follow the folder naming convention (folder-naming-convention) 2 | 3 | Allows you to enforce a consistent naming pattern for the name of the specified folder. 4 | 5 | ## Rule Details 6 | 7 | This rule aims to format the name of the specified folder. This rule uses the glob match syntax to match target folders and declare the naming pattern for the folder name. 8 | 9 | There are six basic naming conventions built into this rule, including `CAMEL_CASE`, `PASCAL_CASE`, `SNAKE_CASE`, `KEBAB_CASE`, `SCREAMING_SNAKE_CASE` and `FLAT_CASE`. 10 | 11 | Additionally, there is a naming convention called `NEXT_JS_APP_ROUTER_CASE` used to format folder names in Next.js projects that use the App Router. You can read more about it under [`NEXT_JS_APP_ROUTER_CASE`](#NEXT_JS_APP_ROUTER_CASE). 12 | 13 | | Formatting | Name | 14 | | ----------- | ---------------------- | 15 | | helloWorld | `CAMEL_CASE` | 16 | | HelloWorld | `PASCAL_CASE` | 17 | | hello_world | `SNAKE_CASE` | 18 | | hello-world | `KEBAB_CASE` | 19 | | HELLO_WORLD | `SCREAMING_SNAKE_CASE` | 20 | | helloworld | `FLAT_CASE` | 21 | 22 | If the rule had been set as follows: 23 | 24 | ```js 25 | ... 26 | 'check-file/folder-naming-convention': ['error', { 'src/**/': 'CAMEL_CASE' }], 27 | ... 28 | ``` 29 | 30 | Examples of **incorrect** folder name for this rule: 31 | 32 | ```sh 33 | src/Components/DisplayLabel/displayLabel.js 34 | src/components/DisplayLabel/displayLabel.js 35 | src/components/displayLabel/DisplayLabel.js 36 | ``` 37 | 38 | Examples of **correct** folder name for this rule: 39 | 40 | ```sh 41 | src/components/displayLabel/displayLabel.js 42 | ``` 43 | 44 | In addition to the built-in naming conventions, you can also set custom naming patterns using glob match syntax. The following code shows an example of how to ensure that all the folders under the `components` folder are named begin with `__`: 45 | 46 | ```js 47 | ... 48 | 'check-file/folder-naming-convention': ['error', [{ 'components/*/': '__+([a-z])' }]], 49 | ... 50 | ``` 51 | 52 | **Tip:** To exclude `__tests__` folder in `src`, use the glob expression `src/**/!(__tests__)/` to get the target folders. 53 | 54 | ### `NEXT_JS_APP_ROUTER_CASE` 55 | 56 | The `NEXT_JS_APP_ROUTER_CASE` aims to support a wide range of named constructs in Next.js App Router projects. 57 | 58 | If you would like to enforce a kebab-case naming convention for your folders, but also support Next.js' Standard routes, Dynamic segments, Catch-all segments, Optional Catch-all Segments, Route groups, and Named slots, this pattern is for you. 59 | 60 | When using the pattern, all your folders need to be kebab-cased, but you can also do dynamic segments with camel case, so that it flows more natural with the code, i.e. 61 | 62 | ``` 63 | /src/app/help-pages/[pageId] 64 | ``` 65 | 66 | This is powerful because it allows you to have consistent link names, i.e. the links will be kebab cased, which is the most natural way of naming URL path segments. 67 | 68 | But when you get in code and the dynamic segment needs to be passed to your component, you'll have a camel cased variable, i.e. 69 | 70 | ``` 71 | // /src/app/help-pages/[pageId]/page.tsx 72 | 73 | // If we have named our parameter "page-id", then you'd get "page-id" inside of 74 | // the params object, and you'd have to escape it, which would look nasty 75 | export const Page({ params: { pageId } }) { ... } 76 | ``` 77 | 78 | Besides this, the custom pattern should support all other Next.js naming conventions. 79 | 80 | While `NEXT_JS_APP_ROUTER_CASE` covers many naming cases, it's possible that some cases may be missing. If you come across any missing cases, I encourage you to open an issue and provide the necessary details. Your feedback will help me improve and enhance the naming convention. 81 | 82 | ### Options 83 | 84 | #### naming pattern object 85 | 86 | The key is used to select target folders, while the value is used to declare the naming pattern for the folder name. You can specify a different naming pattern for different target folders. The plugin will only check folders you explicitly provided: 87 | 88 | ```js 89 | export default [ 90 | { 91 | plugins: { 92 | 'check-file': checkFile, 93 | }, 94 | rules: { 95 | 'check-file/folder-naming-convention': [ 96 | 'error', 97 | { 98 | 'src/**/': 'CAMEL_CASE', 99 | 'mocks/*/': 'KEBAB_CASE', 100 | }, 101 | ], 102 | }, 103 | }, 104 | ]; 105 | ``` 106 | 107 | #### rule configuration object 108 | 109 | ##### `errorMessage` 110 | 111 | Customizes the error message displayed when a folder does not match the naming pattern. It offers two placeholders for dynamic content: 112 | 113 | - `{{ target }}`: Represents the target folder. 114 | - `{{ pattern }}`: Represents the naming pattern for the target folder. 115 | 116 | ##### `ignoreWords` 117 | 118 | An array of folder names to ignore during naming convention validation. Folders whose names exactly match any string in this array will be skipped and not validated against the naming pattern. 119 | 120 | ```js 121 | export default [ 122 | { 123 | plugins: { 124 | 'check-file': checkFile, 125 | }, 126 | rules: { 127 | 'check-file/folder-naming-convention': [ 128 | 'error', 129 | { 130 | 'src/**/': 'CAMEL_CASE', 131 | 'mocks/*/': 'KEBAB_CASE', 132 | }, 133 | { 134 | errorMessage: 135 | 'The folder "{{ target }}" does not match the "{{ pattern }}" pattern, see contribute.md for details', 136 | ignoreWords: ['skip_word_a', 'skip_word_b'], 137 | }, 138 | ], 139 | }, 140 | }, 141 | ]; 142 | ``` 143 | 144 | With this configuration, folders named `skip_word_a` or `skip_word_b` will not be validated against the naming pattern, allowing paths like `mocks/skip_word_a/app-mock.ts` to pass validation even though `skip_word_a` doesn't follow the `KEBAB_CASE` pattern. 145 | 146 | ```js 147 | export default [ 148 | { 149 | plugins: { 150 | 'check-file': checkFile, 151 | }, 152 | rules: { 153 | 'check-file/folder-naming-convention': [ 154 | 'error', 155 | { 156 | 'src/**/': 'CAMEL_CASE', 157 | 'mocks/*/': 'KEBAB_CASE', 158 | }, 159 | { 160 | errorMessage: 161 | 'The folder "{{ target }}" does not match the "{{ pattern }}" pattern, see contribute.md for details', 162 | }, 163 | ], 164 | }, 165 | }, 166 | ]; 167 | ``` 168 | 169 | ## Further Reading 170 | 171 | - [micromatch](https://github.com/micromatch/micromatch) 172 | - [glob]() 173 | - [testing glob expression online 1](https://globster.xyz) 174 | - [testing glob expression online 2](https://www.digitalocean.com/community/tools/glob) 175 | -------------------------------------------------------------------------------- /docs/rules/no-index.md: -------------------------------------------------------------------------------- 1 | # A file cannot be named "index" (no-index) 2 | 3 | Prevents files from being named "index". 4 | 5 | ## Rule Details 6 | 7 | This rule aims to prevent files from being named "index", which will lead to files having meaningful names. 8 | 9 | Examples of **incorrect** filename for this rule: 10 | 11 | ```sh 12 | index.js 13 | index.ts 14 | ``` 15 | 16 | Examples of **correct** filename for this rule: 17 | 18 | ```sh 19 | calculatePrice.js 20 | login.tsx 21 | ``` 22 | 23 | ### Options 24 | 25 | #### rule configuration object 26 | 27 | ##### `ignoreMiddleExtensions` 28 | 29 | If `true`, the rule will ignore the middle extensions of the filename. 30 | 31 | In some cases, you may want to ignore the middle extensions of the filename. For example, you want to lint the base name of the config files, e.g. `index.config.js`, you can do so by setting the `ignoreMiddleExtensions` option to `true`, and the rule will only validate its base name, in this case the base name will be `index`. 32 | 33 | ```js 34 | export default [ 35 | { 36 | plugins: { 37 | 'check-file': checkFile, 38 | }, 39 | rules: { 40 | 'check-file/no-index': [ 41 | 'error', 42 | { 43 | ignoreMiddleExtensions: true, 44 | }, 45 | ], 46 | }, 47 | }, 48 | ]; 49 | ``` 50 | 51 | ##### `errorMessage` 52 | 53 | Customizes the error message displayed when a file is being named "index". It offers one placeholder for dynamic content: 54 | 55 | - `{{ target }}`: Represents the filename of the blocked file. 56 | 57 | ```js 58 | export default [ 59 | { 60 | plugins: { 61 | 'check-file': checkFile, 62 | }, 63 | rules: { 64 | 'check-file/no-index': [ 65 | 'error', 66 | { 67 | errorMessage: 68 | 'The file "{{ target }}" is not allowed to be named "index", see contribute.md for details', 69 | }, 70 | ], 71 | }, 72 | }, 73 | ]; 74 | ``` 75 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import eslintPlugin from 'eslint-plugin-eslint-plugin'; 3 | import jsdoc from 'eslint-plugin-jsdoc'; 4 | import node from 'eslint-plugin-n'; 5 | import prettier from 'eslint-plugin-prettier/recommended'; 6 | 7 | export default [ 8 | { 9 | ignores: ['dist'], 10 | }, 11 | js.configs.recommended, 12 | eslintPlugin.configs['flat/recommended'], 13 | jsdoc.configs['flat/recommended'], 14 | node.configs['flat/recommended'], 15 | prettier, 16 | ]; 17 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic Example 2 | 3 | This example demonstrates how to use `eslint-plugin-check-file` to enforce consistent file and folder naming conventions. 4 | -------------------------------------------------------------------------------- /examples/basic/eslint.config.js: -------------------------------------------------------------------------------- 1 | import checkFile from '../../lib/index.js'; 2 | 3 | export default [ 4 | { 5 | files: ['src/**/*.webp', 'src/**/*.md', 'src/**/*.css'], 6 | processor: 'check-file/eslint-processor-check-file', 7 | }, 8 | { 9 | files: ['src/**/*.*'], 10 | plugins: { 11 | 'check-file': checkFile, 12 | }, 13 | rules: { 14 | 'check-file/no-index': 'error', 15 | 'check-file/filename-blocklist': [ 16 | 'error', 17 | { 18 | '**/*.model.ts': '*.models.ts', 19 | '**/*.util.ts': '*.utils.ts', 20 | }, 21 | ], 22 | 'check-file/folder-match-with-fex': [ 23 | 'error', 24 | { 25 | '*.test.{js,jsx,ts,tsx}': '**/__tests__/', 26 | '*.styled.{jsx,tsx}': '**/components/', 27 | }, 28 | ], 29 | 'check-file/filename-naming-convention': [ 30 | 'error', 31 | { 32 | '**/*.{jsx,tsx}': 'PASCAL_CASE', 33 | '**/*.{js,ts}': 'CAMEL_CASE', 34 | '**/*.css': 'PASCAL_CASE', 35 | '**/*.md': 'FLAT_CASE', 36 | '**/*.webp': 'KEBAB_CASE', 37 | }, 38 | { 39 | ignoreMiddleExtensions: true, 40 | }, 41 | ], 42 | 'check-file/folder-naming-convention': [ 43 | 'error', 44 | { 45 | 'src/components/*/': 'PASCAL_CASE', 46 | 'src/!(components)/**/!(__tests__)/': 'CAMEL_CASE', 47 | }, 48 | ], 49 | }, 50 | }, 51 | ]; 52 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "lint": "eslint ." 6 | }, 7 | "devDependencies": { 8 | "eslint": "^9.15.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/basic/src/components/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | /* Button.module.css */ 2 | -------------------------------------------------------------------------------- /examples/basic/src/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | // Button.tsx 2 | -------------------------------------------------------------------------------- /examples/basic/src/components/Button/__tests__/Button.test.tsx: -------------------------------------------------------------------------------- 1 | // Button.test.tsx 2 | -------------------------------------------------------------------------------- /examples/basic/src/ko-fi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dukeluo/eslint-plugin-check-file/ed88e9fddce87415d9b8b36c6c28fda48ad491a7/examples/basic/src/ko-fi.webp -------------------------------------------------------------------------------- /examples/basic/src/main.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | -------------------------------------------------------------------------------- /examples/basic/src/main.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/basic/src/models/user.models.ts: -------------------------------------------------------------------------------- 1 | // user.models.ts 2 | -------------------------------------------------------------------------------- /examples/basic/src/utils/__tests__/formatDate.test.ts: -------------------------------------------------------------------------------- 1 | // formatDate.test.ts 2 | -------------------------------------------------------------------------------- /examples/basic/src/utils/formatDate.ts: -------------------------------------------------------------------------------- 1 | // formatDate.ts 2 | -------------------------------------------------------------------------------- /lib/constants/message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Message templates 3 | * @author Huan Luo 4 | */ 5 | 6 | export const NAMING_PATTERN_OBJECT_ERROR_MESSAGE = 7 | 'The naming pattern object "{{ value }}" does not appear to be an Object type, please double-check it and try again'; 8 | 9 | export const PATTERN_ERROR_MESSAGE = 10 | 'There is an invalid pattern "{{ value }}", please double-check it and try again'; 11 | 12 | export const PREFINED_MATCH_SYNTAX_ERROR_MESSAGE = 13 | 'The prefined match "{{ namingPattern }}" is not found in the pattern "{{ filenamePattern }}", please double-check it and try again'; 14 | 15 | export const FILENAME_BLOCKLIST_ERROR_MESSAGE = 16 | 'The filename "{{ filename }}" matches the blocklisted "{{ blockListPattern }}" pattern, use a pattern like "{{ suggestion }}" instead'; 17 | 18 | export const FILENAME_NAMING_CONVENTION_ERROR_MESSAGE = 19 | 'The filename "{{ filename }}" does not match the "{{ originalNamingPattern }}" pattern'; 20 | 21 | export const FOLDER_MATCH_WITH_FEX_ERROR_MESSAGE = 22 | 'The folder of the file "{{ filename }}" does not match the "{{ folderPattern }}" pattern'; 23 | 24 | export const FOLDER_NAMING_CONVENTION_ERROR_MESSAGE = 25 | 'The folder "{{ folder }}" does not match the "{{ namingPattern }}" pattern'; 26 | 27 | export const NO_INDEX_ERROR_MESSAGE = 'The filename "index" is not allowed'; 28 | -------------------------------------------------------------------------------- /lib/constants/naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Built in filename naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | /** 7 | * @example hello, helloWorld 8 | */ 9 | export const CAMEL_CASE = '+([a-z])*([a-z0-9])*([A-Z]*([a-z0-9]))'; 10 | 11 | /** 12 | * @example Hello, HelloWorld 13 | */ 14 | export const PASCAL_CASE = '*([A-Z]*([a-z0-9]))'; 15 | 16 | /** 17 | * @example hello, hello_world 18 | */ 19 | export const SNAKE_CASE = '+([a-z])*([a-z0-9])*(_+([a-z0-9]))'; 20 | 21 | /** 22 | * @example hello, hello-world 23 | */ 24 | export const KEBAB_CASE = '+([a-z])*([a-z0-9])*(-+([a-z0-9]))'; 25 | 26 | /** 27 | * @example HELLO, HELLO_WORLD 28 | */ 29 | export const SCREAMING_SNAKE_CASE = '+([A-Z])*([A-Z0-9])*(_+([A-Z0-9]))'; 30 | 31 | /** 32 | * @example hello, helloworld 33 | */ 34 | export const FLAT_CASE = '+([a-z0-9])'; 35 | -------------------------------------------------------------------------------- /lib/constants/next-js-naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Built in next js naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import { CAMEL_CASE, KEBAB_CASE } from './naming-convention.js'; 7 | 8 | /** 9 | * @example [helpPageId] 10 | */ 11 | const NEXT_JS_DYNAMIC_SEGMENTS = `\\[${CAMEL_CASE}\\]`; 12 | 13 | /** 14 | * @example [...auth] 15 | */ 16 | const NEXT_JS_CATCH_ALL_SEGMENTS = `\\[...${CAMEL_CASE}\\]`; 17 | 18 | /** 19 | * @example [[...auth]] 20 | */ 21 | const NEXT_JS_OPTIONAL_CATCH_ALL_SEGMENTS = `\\[\\[...${CAMEL_CASE}\\]\\]`; 22 | 23 | /** 24 | * @example (auth) 25 | */ 26 | const NEXT_JS_ROUTE_GROUPS = `\\(${KEBAB_CASE}\\)`; 27 | 28 | /** 29 | * @example \@feed 30 | */ 31 | const NEXT_JS_NAMED_SLOTS = `\\@${KEBAB_CASE}`; 32 | 33 | /** 34 | * @example \_components 35 | */ 36 | const NEXT_JS_PRIVATE_FOLDERS = `\\_${KEBAB_CASE}`; 37 | 38 | /** 39 | * @example rss.xml 40 | */ 41 | const NEXT_JS_FILENAME_ROUTE = `+([a-z])?(.+([a-z]))`; 42 | 43 | /** 44 | * @example app, [helpPageId], [...auth], [[...auth]], (auth), \@feed 45 | */ 46 | export const NEXT_JS_APP_ROUTER_CASE = `@(${KEBAB_CASE}|${NEXT_JS_FILENAME_ROUTE}|${NEXT_JS_DYNAMIC_SEGMENTS}|${NEXT_JS_CATCH_ALL_SEGMENTS}|${NEXT_JS_OPTIONAL_CATCH_ALL_SEGMENTS}|${NEXT_JS_ROUTE_GROUPS}|${NEXT_JS_NAMED_SLOTS}|${NEXT_JS_PRIVATE_FOLDERS})`; 47 | 48 | /** 49 | * @example _app, _document, index, [helpPageId], [...auth], [[...auth]] 50 | */ 51 | export const NEXT_JS_PAGE_ROUTER_FILENAME_CASE = `@(_app|_document|404|500|_error|index|${CAMEL_CASE}|${NEXT_JS_DYNAMIC_SEGMENTS}|${NEXT_JS_CATCH_ALL_SEGMENTS}|${NEXT_JS_OPTIONAL_CATCH_ALL_SEGMENTS})`; 52 | -------------------------------------------------------------------------------- /lib/constants/regex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Regex pattern constants 3 | * @author Huan Luo 4 | */ 5 | 6 | /** 7 | * @example <1> 8 | */ 9 | export const PREFINED_MATCH_SYNTAX_REGEXP = /^<(\d+)>$/; 10 | 11 | /** 12 | * @example C:\ 13 | */ 14 | export const WINDOWS_DRIVE_LETTER_REGEXP = /^[A-Za-z]:\\/; 15 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Entry point for all rules 3 | * @author Huan Luo 4 | */ 5 | 6 | import FilenameBlocklist from './rules/filename-blocklist.js'; 7 | import FilenameNamingConvention from './rules/filename-naming-convention.js'; 8 | import FolderMatchWithFex from './rules/folder-match-with-fex.js'; 9 | import FolderNamingConvention from './rules/folder-naming-convention.js'; 10 | import NoIndex from './rules/no-index.js'; 11 | 12 | const rules = { 13 | 'filename-blocklist': FilenameBlocklist, 14 | 'filename-naming-convention': FilenameNamingConvention, 15 | 'folder-match-with-fex': FolderMatchWithFex, 16 | 'folder-naming-convention': FolderNamingConvention, 17 | 'no-index': NoIndex, 18 | }; 19 | 20 | const plugin = { 21 | meta: { 22 | name: 'eslint-plugin-check-file', 23 | version: '3.3.0', 24 | }, 25 | rules, 26 | processors: { 27 | 'eslint-processor-check-file': { 28 | preprocess(_, filename) { 29 | return [ 30 | { 31 | text: '', 32 | filename: filename, 33 | }, 34 | ]; 35 | }, 36 | postprocess(messages) { 37 | return [].concat(...messages); 38 | }, 39 | supportsAutofix: true, 40 | }, 41 | }, 42 | }; 43 | 44 | export default plugin; 45 | -------------------------------------------------------------------------------- /lib/rules/filename-blocklist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The filename should be blocklisted. 3 | * @author Florian Ehmke, Huan Luo 4 | */ 5 | 6 | import { 7 | FILENAME_BLOCKLIST_ERROR_MESSAGE, 8 | NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 9 | PATTERN_ERROR_MESSAGE, 10 | } from '../constants/message.js'; 11 | import { getDocUrl } from '../utils/doc.js'; 12 | import { getFilePath, getFilename } from '../utils/filename.js'; 13 | import { matchRule } from '../utils/rule.js'; 14 | import { isEmpty, isNotEmpty } from '../utils/utility.js'; 15 | import { 16 | validateNamingPatternObject, 17 | globPatternValidator, 18 | } from '../utils/validation.js'; 19 | 20 | /** 21 | * @type {import('eslint').Rule.RuleModule} 22 | */ 23 | export default { 24 | meta: { 25 | type: 'layout', 26 | docs: { 27 | description: 'The filename should be blocklisted', 28 | category: 'Layout & Formatting', 29 | recommended: false, 30 | url: getDocUrl('filename-blocklist'), 31 | }, 32 | fixable: null, 33 | schema: [ 34 | { 35 | additionalProperties: { 36 | type: 'string', 37 | }, 38 | }, 39 | { 40 | type: 'object', 41 | properties: { 42 | errorMessage: { type: 'string' }, 43 | }, 44 | }, 45 | ], 46 | messages: { 47 | invalidObject: NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 48 | invalidPattern: PATTERN_ERROR_MESSAGE, 49 | noMatch: FILENAME_BLOCKLIST_ERROR_MESSAGE, 50 | }, 51 | }, 52 | 53 | create(context) { 54 | const rule = (node) => { 55 | const rules = context.options[0]; 56 | const errorMessage = context.options[1]?.errorMessage ?? ''; 57 | const error = validateNamingPatternObject( 58 | rules, 59 | globPatternValidator, 60 | isEmpty(errorMessage) ? globPatternValidator : () => true 61 | ); 62 | 63 | if (error) { 64 | context.report({ 65 | node, 66 | messageId: error.type, 67 | data: { 68 | value: error.payload, 69 | }, 70 | }); 71 | return; 72 | } 73 | 74 | const filenameWithPath = getFilePath(context); 75 | const filename = getFilename(filenameWithPath); 76 | 77 | for (const [blockListPattern, useInsteadPattern] of Object.entries( 78 | rules 79 | )) { 80 | const matchResult = matchRule(filenameWithPath, blockListPattern); 81 | 82 | if (matchResult) { 83 | isNotEmpty(errorMessage) 84 | ? context.report({ 85 | node, 86 | // eslint-disable-next-line eslint-plugin/prefer-message-ids 87 | message: errorMessage, 88 | data: { 89 | target: filename, 90 | pattern: blockListPattern, 91 | }, 92 | }) 93 | : context.report({ 94 | node, 95 | messageId: 'noMatch', 96 | data: { 97 | filename, 98 | blockListPattern, 99 | suggestion: useInsteadPattern, 100 | }, 101 | }); 102 | return; 103 | } 104 | } 105 | }; 106 | 107 | return { 108 | Document: rule, 109 | Program: rule, 110 | root: rule, 111 | StyleSheet: rule, 112 | }; 113 | }, 114 | }; 115 | -------------------------------------------------------------------------------- /lib/rules/filename-naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The filename should follow the filename naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import { 7 | FILENAME_NAMING_CONVENTION_ERROR_MESSAGE, 8 | NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 9 | PATTERN_ERROR_MESSAGE, 10 | PREFINED_MATCH_SYNTAX_ERROR_MESSAGE, 11 | } from '../constants/message.js'; 12 | import { getDocUrl } from '../utils/doc.js'; 13 | import { getBasename, getFilePath, getFilename } from '../utils/filename.js'; 14 | import { 15 | matchRule, 16 | transformRuleWithPrefinedMatchSyntax, 17 | } from '../utils/rule.js'; 18 | import { isNotEmpty } from '../utils/utility.js'; 19 | import { 20 | filenameNamingPatternValidator, 21 | globPatternValidator, 22 | nextJsFilenameNamingPatternValidator, 23 | validateNamingPatternObject, 24 | } from '../utils/validation.js'; 25 | 26 | /** 27 | * @type {import('eslint').Rule.RuleModule} 28 | */ 29 | export default { 30 | meta: { 31 | type: 'layout', 32 | docs: { 33 | description: 'The filename should follow the filename naming convention', 34 | category: 'Layout & Formatting', 35 | recommended: false, 36 | url: getDocUrl('filename-naming-convention'), 37 | }, 38 | fixable: null, 39 | schema: [ 40 | { 41 | additionalProperties: { 42 | type: 'string', 43 | }, 44 | }, 45 | { 46 | type: 'object', 47 | properties: { 48 | ignoreMiddleExtensions: { type: 'boolean' }, 49 | errorMessage: { type: 'string' }, 50 | }, 51 | }, 52 | ], 53 | messages: { 54 | invalidObject: NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 55 | invalidPattern: PATTERN_ERROR_MESSAGE, 56 | invalidPrefinedMatch: PREFINED_MATCH_SYNTAX_ERROR_MESSAGE, 57 | noMatch: FILENAME_NAMING_CONVENTION_ERROR_MESSAGE, 58 | }, 59 | }, 60 | 61 | create(context) { 62 | const rule = (node) => { 63 | const rules = context.options[0]; 64 | const error = validateNamingPatternObject( 65 | rules, 66 | globPatternValidator, 67 | (p) => 68 | filenameNamingPatternValidator(p) || 69 | nextJsFilenameNamingPatternValidator(p) 70 | ); 71 | 72 | if (error) { 73 | context.report({ 74 | node, 75 | messageId: error.type, 76 | data: { 77 | value: error.payload, 78 | }, 79 | }); 80 | return; 81 | } 82 | 83 | const filenameWithPath = getFilePath(context); 84 | const filename = getFilename(filenameWithPath); 85 | const ignoreMiddleExtensions = 86 | context.options[1]?.ignoreMiddleExtensions ?? false; 87 | const errorMessage = context.options[1]?.errorMessage ?? ''; 88 | 89 | for (const [ 90 | originalFilenamePattern, 91 | originalNamingPattern, 92 | ] of Object.entries(rules)) { 93 | try { 94 | const [filenamePattern, namingPattern] = 95 | transformRuleWithPrefinedMatchSyntax( 96 | [originalFilenamePattern, originalNamingPattern], 97 | filenameWithPath 98 | ); 99 | 100 | const matchResult = matchRule( 101 | filenameWithPath, 102 | filenamePattern, 103 | getBasename(filename, ignoreMiddleExtensions), 104 | namingPattern 105 | ); 106 | 107 | if (matchResult) { 108 | throw { 109 | type: 'noMatch', 110 | payload: { 111 | filename, 112 | originalNamingPattern, 113 | }, 114 | }; 115 | } 116 | } catch (error) { 117 | isNotEmpty(errorMessage) && error.type === 'noMatch' 118 | ? context.report({ 119 | node, 120 | // eslint-disable-next-line eslint-plugin/prefer-message-ids 121 | message: errorMessage, 122 | data: { 123 | target: error.payload.filename, 124 | pattern: error.payload.originalNamingPattern, 125 | }, 126 | }) 127 | : context.report({ 128 | node, 129 | messageId: error.type, 130 | data: error.payload, 131 | }); 132 | } 133 | } 134 | }; 135 | 136 | return { 137 | Document: rule, 138 | Program: rule, 139 | root: rule, 140 | StyleSheet: rule, 141 | }; 142 | }, 143 | }; 144 | -------------------------------------------------------------------------------- /lib/rules/folder-match-with-fex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should match the naming pattern specified by its file 3 | * @author Huan Luo 4 | */ 5 | 6 | import { 7 | FOLDER_MATCH_WITH_FEX_ERROR_MESSAGE, 8 | NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 9 | PATTERN_ERROR_MESSAGE, 10 | } from '../constants/message.js'; 11 | import { getDocUrl } from '../utils/doc.js'; 12 | import { getFilePath, getFilename, getFolderPath } from '../utils/filename.js'; 13 | import { matchRule } from '../utils/rule.js'; 14 | import { isNotEmpty } from '../utils/utility.js'; 15 | import { 16 | globPatternValidator, 17 | validateNamingPatternObject, 18 | } from '../utils/validation.js'; 19 | 20 | /** 21 | * @type {import('eslint').Rule.RuleModule} 22 | */ 23 | export default { 24 | meta: { 25 | type: 'layout', 26 | docs: { 27 | description: 28 | 'The folder should match the naming pattern specified by its file', 29 | category: 'Layout & Formatting', 30 | recommended: false, 31 | url: getDocUrl('folder-match-with-fex'), 32 | }, 33 | fixable: null, 34 | schema: [ 35 | { 36 | additionalProperties: { 37 | type: 'string', 38 | }, 39 | }, 40 | { 41 | type: 'object', 42 | properties: { 43 | errorMessage: { type: 'string' }, 44 | }, 45 | }, 46 | ], 47 | messages: { 48 | invalidObject: NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 49 | invalidPattern: PATTERN_ERROR_MESSAGE, 50 | noMatch: FOLDER_MATCH_WITH_FEX_ERROR_MESSAGE, 51 | }, 52 | }, 53 | 54 | create(context) { 55 | const rule = (node) => { 56 | const rules = context.options[0]; 57 | const error = validateNamingPatternObject( 58 | rules, 59 | globPatternValidator, 60 | globPatternValidator 61 | ); 62 | 63 | if (error) { 64 | context.report({ 65 | node, 66 | messageId: error.type, 67 | data: { 68 | value: error.payload, 69 | }, 70 | }); 71 | return; 72 | } 73 | 74 | const filenameWithPath = getFilePath(context); 75 | const filename = getFilename(filenameWithPath); 76 | const folderPath = getFolderPath(filenameWithPath); 77 | const errorMessage = context.options[1]?.errorMessage ?? ''; 78 | 79 | for (const [fexPattern, folderPattern] of Object.entries(rules)) { 80 | const matchResult = matchRule( 81 | filename, 82 | fexPattern, 83 | folderPath, 84 | folderPattern 85 | ); 86 | 87 | if (matchResult) { 88 | isNotEmpty(errorMessage) 89 | ? context.report({ 90 | node, 91 | // eslint-disable-next-line eslint-plugin/prefer-message-ids 92 | message: errorMessage, 93 | data: { 94 | target: filename, 95 | pattern: folderPattern, 96 | }, 97 | }) 98 | : context.report({ 99 | node, 100 | messageId: 'noMatch', 101 | data: { 102 | filename, 103 | folderPattern, 104 | }, 105 | }); 106 | return; 107 | } 108 | } 109 | }; 110 | 111 | return { 112 | Document: rule, 113 | Program: rule, 114 | root: rule, 115 | StyleSheet: rule, 116 | }; 117 | }, 118 | }; 119 | -------------------------------------------------------------------------------- /lib/rules/folder-naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should follow the folder naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import micromatch from 'micromatch'; 7 | import { 8 | FOLDER_NAMING_CONVENTION_ERROR_MESSAGE, 9 | NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 10 | PATTERN_ERROR_MESSAGE, 11 | } from '../constants/message.js'; 12 | import * as BASIC_NAMING_CONVENTION from '../constants/naming-convention.js'; 13 | import * as NEXT_JS_NAMING_CONVENTION from '../constants/next-js-naming-convention.js'; 14 | import { getDocUrl } from '../utils/doc.js'; 15 | import { 16 | getAllFolders, 17 | getFilePath, 18 | getFolderPath, 19 | getSubPaths, 20 | } from '../utils/filename.js'; 21 | import { isNil, isNotEmpty } from '../utils/utility.js'; 22 | import { 23 | folderNamingPatternValidator, 24 | globPatternValidator, 25 | validateNamingPatternObject, 26 | } from '../utils/validation.js'; 27 | 28 | /** 29 | * @type {import('eslint').Rule.RuleModule} 30 | */ 31 | export default { 32 | meta: { 33 | type: 'layout', 34 | docs: { 35 | description: 'The folder should follow the folder naming convention', 36 | category: 'Layout & Formatting', 37 | recommended: false, 38 | url: getDocUrl('folder-naming-convention'), 39 | }, 40 | fixable: null, 41 | schema: [ 42 | { 43 | additionalProperties: { 44 | type: 'string', 45 | }, 46 | }, 47 | { 48 | type: 'object', 49 | properties: { 50 | errorMessage: { type: 'string' }, 51 | ignoreWords: { 52 | type: 'array', 53 | items: { type: 'string' }, 54 | }, 55 | }, 56 | }, 57 | ], 58 | messages: { 59 | invalidObject: NAMING_PATTERN_OBJECT_ERROR_MESSAGE, 60 | invalidPattern: PATTERN_ERROR_MESSAGE, 61 | noMatch: FOLDER_NAMING_CONVENTION_ERROR_MESSAGE, 62 | }, 63 | }, 64 | 65 | create(context) { 66 | const rule = (node) => { 67 | const rules = context.options[0]; 68 | const error = validateNamingPatternObject( 69 | rules, 70 | globPatternValidator, 71 | folderNamingPatternValidator 72 | ); 73 | 74 | if (error) { 75 | context.report({ 76 | node, 77 | messageId: error.type, 78 | data: { 79 | value: error.payload, 80 | }, 81 | }); 82 | return; 83 | } 84 | 85 | const filenameWithPath = getFilePath(context); 86 | const folderPath = getFolderPath(filenameWithPath); 87 | const errorMessage = context.options[1]?.errorMessage ?? ''; 88 | const ignoreWords = context.options[1]?.ignoreWords ?? []; 89 | 90 | for (const [folderPattern, namingPattern] of Object.entries(rules)) { 91 | if ( 92 | !micromatch.isMatch(folderPath, folderPattern, { contains: true }) 93 | ) { 94 | continue; 95 | } 96 | for (const path of getSubPaths(folderPath)) { 97 | const matchedPaths = micromatch.capture(folderPattern, path, { 98 | dot: true, 99 | }); 100 | 101 | if (isNil(matchedPaths)) continue; 102 | 103 | const folders = matchedPaths 104 | .filter(isNotEmpty) 105 | .reduce((s, p) => s.concat(getAllFolders(p)), []); 106 | 107 | for (const folder of folders) { 108 | // Skip validation if the folder name is in the ignore list 109 | if (ignoreWords.includes(folder)) { 110 | continue; 111 | } 112 | 113 | if ( 114 | !micromatch.isMatch( 115 | folder, 116 | BASIC_NAMING_CONVENTION[namingPattern] || 117 | NEXT_JS_NAMING_CONVENTION[namingPattern] || 118 | namingPattern 119 | ) 120 | ) { 121 | isNotEmpty(errorMessage) 122 | ? context.report({ 123 | node, 124 | // eslint-disable-next-line eslint-plugin/prefer-message-ids 125 | message: errorMessage, 126 | data: { 127 | target: folder, 128 | pattern: namingPattern, 129 | }, 130 | }) 131 | : context.report({ 132 | node, 133 | messageId: 'noMatch', 134 | data: { 135 | folder, 136 | namingPattern, 137 | }, 138 | }); 139 | return; 140 | } 141 | } 142 | } 143 | } 144 | }; 145 | 146 | return { 147 | Document: rule, 148 | Program: rule, 149 | root: rule, 150 | StyleSheet: rule, 151 | }; 152 | }, 153 | }; 154 | -------------------------------------------------------------------------------- /lib/rules/no-index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file A file cannot be named "index" 3 | * @author Huan Luo 4 | */ 5 | 6 | import { NO_INDEX_ERROR_MESSAGE } from '../constants/message.js'; 7 | import { getDocUrl } from '../utils/doc.js'; 8 | import { getBasename, getFilePath, getFilename } from '../utils/filename.js'; 9 | import { isNotEmpty } from '../utils/utility.js'; 10 | 11 | /** 12 | * @type {import('eslint').Rule.RuleModule} 13 | */ 14 | export default { 15 | meta: { 16 | type: 'layout', 17 | docs: { 18 | description: 'A file cannot be named "index"', 19 | category: 'Layout & Formatting', 20 | recommended: false, 21 | url: getDocUrl('no-index'), 22 | }, 23 | fixable: null, 24 | schema: [ 25 | { 26 | type: 'object', 27 | properties: { 28 | ignoreMiddleExtensions: { type: 'boolean' }, 29 | errorMessage: { type: 'string' }, 30 | }, 31 | }, 32 | ], 33 | messages: { 34 | noIndex: NO_INDEX_ERROR_MESSAGE, 35 | }, 36 | }, 37 | 38 | create(context) { 39 | const rule = (node) => { 40 | const ignoreMiddleExtensions = 41 | context.options[0]?.ignoreMiddleExtensions ?? false; 42 | const errorMessage = context.options[0]?.errorMessage ?? ''; 43 | const filenameWithPath = getFilePath(context); 44 | const filename = getFilename(filenameWithPath); 45 | const basename = getBasename(filename, ignoreMiddleExtensions); 46 | 47 | if (basename === 'index') { 48 | isNotEmpty(errorMessage) 49 | ? context.report({ 50 | node, 51 | // eslint-disable-next-line eslint-plugin/prefer-message-ids 52 | message: errorMessage, 53 | data: { 54 | target: filename, 55 | }, 56 | }) 57 | : context.report({ 58 | node, 59 | messageId: 'noIndex', 60 | }); 61 | return; 62 | } 63 | }; 64 | 65 | return { 66 | Document: rule, 67 | Program: rule, 68 | root: rule, 69 | StyleSheet: rule, 70 | }; 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /lib/utils/doc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Utils about document 3 | * @author Huan Luo 4 | */ 5 | 6 | /** 7 | * @returns {string} rule document url 8 | * @param {string} rule rule name 9 | */ 10 | export const getDocUrl = (rule) => 11 | `https://github.com/dukeluo/eslint-plugin-check-file/blob/main/docs/rules/${rule}.md`; 12 | -------------------------------------------------------------------------------- /lib/utils/filename.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Utils about filename 3 | * @author Huan Luo 4 | */ 5 | 6 | import { join, posix, sep } from 'path'; 7 | import { WINDOWS_DRIVE_LETTER_REGEXP } from '../constants/regex.js'; 8 | import { isNotEmpty, pipe } from './utility.js'; 9 | 10 | /** 11 | * @returns {string} filename without path 12 | * @param {string} p filename concat with path in posix style 13 | */ 14 | export const getFilename = (p) => posix.basename(p); 15 | 16 | /** 17 | * @returns {string} path of folder 18 | * @param {string} p filename concat with path in posix style 19 | */ 20 | export const getFolderPath = (p) => posix.join(posix.dirname(p), posix.sep); 21 | 22 | /** 23 | * @returns {string} base name 24 | * @param {string} filename filename without path 25 | * @param {boolean} [ignoreMiddleExtensions] flag to ignore middle extensions 26 | */ 27 | export const getBasename = (filename, ignoreMiddleExtensions = false) => 28 | filename.substring( 29 | 0, 30 | ignoreMiddleExtensions ? filename.indexOf('.') : filename.lastIndexOf('.') 31 | ); 32 | 33 | /** 34 | * @returns {string[]} all folders 35 | * @param {string} p path of folder in posix style 36 | */ 37 | export const getAllFolders = (p) => p.split(posix.sep).filter(isNotEmpty); 38 | 39 | /** 40 | * @example 41 | * returns ['src/', 'src/DisplayLabel/', 'src/DisplayLabel/__tests__/', 'DisplayLabel/__tests__] 42 | * getSubPaths('src/DisplayLabel/__tests__/'); 43 | * @returns {string[]} subpaths 44 | * @param {string} p path of folder in posix style 45 | */ 46 | export const getSubPaths = (p) => { 47 | const folders = getAllFolders(p); 48 | let subPaths = []; 49 | 50 | const walk = (array) => 51 | array.reduce((acc, folder, index) => { 52 | const subpath = posix.join(acc, folder, posix.sep); 53 | 54 | if (index >= 1) subPaths.push(subpath); 55 | 56 | return subpath; 57 | }, ''); 58 | 59 | for (let i = 0; i < folders.length; i++) { 60 | walk(folders.slice(i)); 61 | } 62 | subPaths.unshift(posix.join(folders[0], posix.sep)); 63 | 64 | return subPaths; 65 | }; 66 | 67 | /** 68 | * @returns {string} path from repository root 69 | * @param {string} fullPath filename with full path 70 | * @param {string} repositoryRoot path of repository root 71 | */ 72 | const getPathFromRepositoryRoot = (fullPath, repositoryRoot) => 73 | fullPath.replace(join(repositoryRoot, sep), ''); 74 | 75 | /** 76 | * @returns {string} file path in posix style 77 | * @param {string} p file path based on the operating system 78 | */ 79 | const toPosixPath = (p) => p.split(sep).join(posix.sep); 80 | 81 | /** 82 | * @returns {string} file path without drive letter on windows 83 | * @param {string} p file path on windows 84 | */ 85 | const removeDriveLetter = (p) => p.replace(WINDOWS_DRIVE_LETTER_REGEXP, ''); 86 | 87 | /** 88 | * @returns {string} file path in posix style 89 | * @param {import('eslint').Rule.RuleContext} context rule eslint context 90 | */ 91 | export const getFilePath = (context) => { 92 | const pathFromRoot = getPathFromRepositoryRoot( 93 | context.physicalFilename, 94 | context.cwd 95 | ); 96 | 97 | return pipe(removeDriveLetter, toPosixPath)(pathFromRoot); 98 | }; 99 | -------------------------------------------------------------------------------- /lib/utils/rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Utils about rule 3 | * @author David Ratier, Huan Luo 4 | */ 5 | 6 | import micromatch from 'micromatch'; 7 | import * as NAMING_CONVENTION from '../constants/naming-convention.js'; 8 | import * as NEXT_JS_NAMING_CONVENTION from '../constants/next-js-naming-convention.js'; 9 | import { PREFINED_MATCH_SYNTAX_REGEXP } from '../constants/regex.js'; 10 | import { isEmpty, isNil } from './utility.js'; 11 | 12 | /** 13 | * Takes in a rule and transforms it if it contains prefined match syntax 14 | * @typedef {string} filenamePattern original filename pattern 15 | * @typedef {string} namingPattern original naming pattern 16 | * @typedef {[filenamePattern, namingPattern]} rule 17 | * @param {rule} rule original rule 18 | * @param {string} filenameWithPath filename with path 19 | * @returns {rule} new rule 20 | * @throws {import("./validation.js").ValidationError} if a prefined match syntax referenced in the naming pattern is not found in the filename pattern 21 | */ 22 | export const transformRuleWithPrefinedMatchSyntax = ( 23 | [filenamePattern, namingPattern], 24 | filenameWithPath 25 | ) => { 26 | const keyCaptureGroups = micromatch.capture( 27 | filenamePattern, 28 | filenameWithPath 29 | ); 30 | 31 | if (isNil(keyCaptureGroups)) { 32 | return [filenamePattern, namingPattern]; 33 | } 34 | 35 | const valueCaptureGroups = [ 36 | ...namingPattern.matchAll(new RegExp(PREFINED_MATCH_SYNTAX_REGEXP, 'g')), 37 | ]; 38 | 39 | if (isEmpty(valueCaptureGroups)) { 40 | return [filenamePattern, namingPattern]; 41 | } 42 | 43 | const newNamingPattern = valueCaptureGroups.reduce((value, group) => { 44 | const groupIndex = +group[1]; 45 | 46 | if (isNil(keyCaptureGroups[groupIndex])) { 47 | throw { 48 | type: 'invalidPrefinedMatch', 49 | payload: { 50 | namingPattern, 51 | filenamePattern, 52 | }, 53 | }; 54 | } 55 | 56 | return value.replace(group[0], keyCaptureGroups[groupIndex]); 57 | }, namingPattern); 58 | 59 | return [filenamePattern, newNamingPattern]; 60 | }; 61 | 62 | /** 63 | * @returns {object | undefined} undefined or object with non-matching file path and naming pattern 64 | * @param {string} filePath file path 65 | * @param {string} targetFilePathPattern path pattern of the target file 66 | * @param {string} [targetNaming] target naming 67 | * @param {string} [targetNamingPattern] naming pattern of the target naming 68 | */ 69 | export const matchRule = ( 70 | filePath, 71 | targetFilePathPattern, 72 | targetNaming, 73 | targetNamingPattern 74 | ) => { 75 | if (!micromatch.isMatch(filePath, targetFilePathPattern)) { 76 | return; 77 | } else if ( 78 | targetNaming && 79 | targetNamingPattern && 80 | micromatch.isMatch( 81 | targetNaming, 82 | NAMING_CONVENTION[targetNamingPattern] || 83 | NEXT_JS_NAMING_CONVENTION[targetNamingPattern] || 84 | targetNamingPattern 85 | ) 86 | ) { 87 | return; 88 | } else { 89 | return { 90 | path: filePath, 91 | pattern: targetNamingPattern, 92 | }; 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /lib/utils/utility.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file A utility file containing useful functions 3 | * @author Huan Luo 4 | */ 5 | 6 | /** 7 | * Checks if the given argument is an object 8 | * @param {any} x - The argument to check 9 | * @returns {boolean} - True if the argument is an object, false otherwise 10 | */ 11 | export const isObject = (x) => 12 | Object.prototype.toString.call(x) === '[object Object]'; 13 | 14 | /** 15 | * Checks if the given argument is an array 16 | * @param {any} x - The argument to check 17 | * @returns {boolean} - True if the argument is an array, false otherwise 18 | */ 19 | const isArray = (x) => 20 | x != null && 21 | x.length >= 0 && 22 | Object.prototype.toString.call(x) === '[object Array]'; 23 | 24 | /** 25 | * Checks if a value is undefined or null 26 | * @param {any} x - The value to check 27 | * @returns {boolean} - Returns true if the value is undefined or null, false otherwise 28 | */ 29 | export const isNil = (x) => x === undefined || x === null; 30 | 31 | /** 32 | * Checks if a value is an empty value 33 | * @param {any} x - The value to check 34 | * @returns {boolean} - Returns true if the value is an empty value, false otherwise 35 | */ 36 | export const isEmpty = (x) => 37 | x === '' || 38 | (isArray(x) && x.length === 0) || 39 | (isObject(x) && Object.keys(x).length === 0); 40 | 41 | /** 42 | * Negates a boolean value 43 | * @param {boolean} x - The boolean value to negate 44 | * @returns {boolean} The negated boolean value 45 | */ 46 | const not = (x) => !x; 47 | 48 | /** 49 | * Callback for file path 50 | * @callback callback 51 | * @param {unknown} p 52 | */ 53 | /** 54 | * @returns {callback} piped function 55 | * @param {callback[]} fns callback functions 56 | */ 57 | export const pipe = 58 | (...fns) => 59 | (x) => 60 | fns.reduce((v, f) => f(v), x); 61 | 62 | /** 63 | * Checks if a value isn't an empty value 64 | * @param {any} x - The value to check 65 | * @returns {boolean} - Returns true if the value isn't an empty value, false otherwise 66 | */ 67 | export const isNotEmpty = pipe(isEmpty, not); 68 | -------------------------------------------------------------------------------- /lib/utils/validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Utils about validation 3 | * @author Huan Luo 4 | */ 5 | 6 | import isGlob from 'is-glob'; 7 | import * as BASIC_NAMING_CONVENTION from '../constants/naming-convention.js'; 8 | import { PREFINED_MATCH_SYNTAX_REGEXP } from '../constants/regex.js'; 9 | import { isObject } from './utility.js'; 10 | 11 | /** 12 | * @typedef {object} ValidationError 13 | * @property {string} type - The type of validation error 14 | * @property {*} payload - An optional payload associated with the validation error 15 | */ 16 | /** 17 | * Validator 18 | * @callback validator 19 | * @param {string} p pattern string 20 | */ 21 | /** 22 | * @returns {ValidationError | undefined} undefined or validation result 23 | * @param {any} config naming pattern object configured by user 24 | * @param {validator} keyValidator settings key validator 25 | * @param {validator} valueValidator settings value validator 26 | */ 27 | export const validateNamingPatternObject = ( 28 | config, 29 | keyValidator, 30 | valueValidator 31 | ) => { 32 | if (!isObject(config)) { 33 | return { type: 'invalidObject', payload: config }; 34 | } 35 | for (const [key, value] of Object.entries(config)) { 36 | if (!keyValidator(key)) { 37 | return { type: 'invalidPattern', payload: key }; 38 | } else if (!valueValidator(value)) { 39 | return { type: 'invalidPattern', payload: value }; 40 | } 41 | } 42 | }; 43 | 44 | /** 45 | * @returns {boolean} true if pattern is a valid naming pattern 46 | * @param {string} namingPattern pattern string 47 | */ 48 | const basicNamingPatternValidator = (namingPattern) => 49 | Object.keys(BASIC_NAMING_CONVENTION).includes(namingPattern); 50 | 51 | /** 52 | * @returns {boolean} true if pattern is a valid naming pattern 53 | * @param {string} namingPattern pattern string 54 | */ 55 | const nextJsFolderNamingPatternValidator = (namingPattern) => 56 | ['NEXT_JS_APP_ROUTER_CASE'].includes(namingPattern); 57 | 58 | /** 59 | * @returns {boolean} true if pattern is a valid naming pattern 60 | * @param {string} namingPattern pattern string 61 | */ 62 | export const nextJsFilenameNamingPatternValidator = (namingPattern) => 63 | ['NEXT_JS_PAGE_ROUTER_FILENAME_CASE'].includes(namingPattern); 64 | 65 | /** 66 | * @returns {boolean} true if pattern is a valid glob pattern 67 | * @param {string} pattern pattern string 68 | */ 69 | export const globPatternValidator = isGlob; 70 | 71 | /** 72 | * @returns {boolean} true if pattern is a valid filename naming pattern 73 | * @param {string} namingPattern pattern string 74 | */ 75 | export const filenameNamingPatternValidator = (namingPattern) => 76 | globPatternValidator(namingPattern) || 77 | basicNamingPatternValidator(namingPattern) || 78 | PREFINED_MATCH_SYNTAX_REGEXP.test(namingPattern); 79 | 80 | /** 81 | * @returns {boolean} true if pattern is a valid filename naming pattern 82 | * @param {string} namingPattern pattern string 83 | */ 84 | export const folderNamingPatternValidator = (namingPattern) => 85 | globPatternValidator(namingPattern) || 86 | basicNamingPatternValidator(namingPattern) || 87 | nextJsFolderNamingPatternValidator(namingPattern); 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-check-file", 3 | "version": "3.3.0", 4 | "description": "ESLint rules for consistent filename and folder. Allows you to enforce a consistent naming pattern for the filename and folder", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin", 8 | "eslint-plugin", 9 | "folder", 10 | "path", 11 | "file", 12 | "filename", 13 | "glob-matching", 14 | "naming-conventions" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/dukeluo/eslint-plugin-check-file.git" 19 | }, 20 | "author": "Huan Luo (https://shaiwang.life)", 21 | "funding": [ 22 | { 23 | "type": "ko_fi", 24 | "url": "https://ko-fi.com/huanluo" 25 | }, 26 | { 27 | "type": "github", 28 | "url": "https://github.com/sponsors/dukeluo" 29 | } 30 | ], 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/dukeluo/eslint-plugin-check-file/issues" 34 | }, 35 | "homepage": "https://github.com/dukeluo/eslint-plugin-check-file", 36 | "type": "module", 37 | "main": "dist/index.cjs", 38 | "module": "dist/index.js", 39 | "types": "dist/index.d.ts", 40 | "exports": { 41 | ".": { 42 | "require": { 43 | "types": "./dist/index.d.cts", 44 | "default": "./dist/index.cjs" 45 | }, 46 | "import": { 47 | "types": "./dist/index.d.ts", 48 | "default": "./dist/index.js" 49 | } 50 | } 51 | }, 52 | "files": [ 53 | "dist" 54 | ], 55 | "scripts": { 56 | "prepare": "husky", 57 | "build": "rollup -c", 58 | "lint": "eslint .", 59 | "lint:fix": "eslint . --fix && prettier --write **/*.md", 60 | "test": "c8 mocha --loader=esmock tests --recursive", 61 | "test:report": "c8 report -r=lcov", 62 | "test:cli": "mocha --loader=esmock", 63 | "example:lint": "npm run lint --workspace=basic" 64 | }, 65 | "dependencies": { 66 | "is-glob": "^4.0.3", 67 | "micromatch": "^4.0.8" 68 | }, 69 | "devDependencies": { 70 | "@commitlint/cli": "^19.6.0", 71 | "@commitlint/config-conventional": "^19.6.0", 72 | "@eslint/js": "^9.15.0", 73 | "@rollup/plugin-terser": "^0.4.4", 74 | "c8": "^10.1.2", 75 | "eslint": "^9.15.0", 76 | "eslint-plugin-eslint-plugin": "^6.3.2", 77 | "eslint-plugin-jsdoc": "^50.5.0", 78 | "eslint-plugin-n": "^17.14.0", 79 | "eslint-plugin-prettier": "^5.2.1", 80 | "esmock": "^2.6.9", 81 | "husky": "^9.1.7", 82 | "mocha": "^10.8.2", 83 | "prettier": "^3.3.3", 84 | "rollup": "^4.27.4", 85 | "rollup-plugin-dts": "^6.2.1", 86 | "typescript": "^5.8.3" 87 | }, 88 | "engines": { 89 | "node": ">=18" 90 | }, 91 | "peerDependencies": { 92 | "eslint": ">=9.0.0" 93 | }, 94 | "workspaces": [ 95 | "examples/basic" 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rollup configuration. 3 | * @author Huan Luo 4 | */ 5 | 6 | import terser from '@rollup/plugin-terser'; 7 | import dts from 'rollup-plugin-dts'; 8 | 9 | export default [ 10 | { 11 | input: 'lib/index.js', 12 | output: [ 13 | { 14 | format: 'cjs', 15 | file: 'dist/index.cjs', 16 | banner: 17 | '/*! @author Huan Luo (https://shaiwang.life) */', 18 | }, 19 | { 20 | format: 'es', 21 | file: 'dist/index.js', 22 | banner: 23 | '/*! @author Huan Luo (https://shaiwang.life) */', 24 | }, 25 | ], 26 | plugins: [terser()], 27 | external: ['is-glob', 'micromatch', 'path'], 28 | }, 29 | { 30 | input: 'lib/index.js', 31 | output: [ 32 | { 33 | format: 'cjs', 34 | file: 'dist/index.d.cts', 35 | banner: 36 | '/*! @author Huan Luo (https://shaiwang.life) */', 37 | }, 38 | { 39 | format: 'es', 40 | file: 'dist/index.d.ts', 41 | banner: 42 | '/*! @author Huan Luo (https://shaiwang.life) */', 43 | }, 44 | ], 45 | plugins: [dts()], 46 | }, 47 | ]; 48 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x1303a6b852A6d4dC024C721E46089FB3fd5d9b94' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /tests/lib/rules/filename-blocklist.posix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The filename should be blocklisted 3 | * @author Florian Ehmke, Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { posix } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/filename-blocklist.js', 12 | {}, 13 | { 14 | path: posix, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "filename-blocklist with option: [{ 'src/*.models.ts': '*.model.ts', 'src/*.utils.ts': '*.util.ts' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 'not-src/foo.model.ts', 27 | options: [ 28 | { 29 | 'src/*.models.ts': '*.model.ts', 30 | 'src/*.utils.ts': '*.util.ts', 31 | }, 32 | ], 33 | }, 34 | { 35 | code: "var foo = 'bar';", 36 | filename: 'not-src/foo.util.ts', 37 | options: [ 38 | { 39 | 'src/*.models.ts': '*.model.ts', 40 | 'src/*.utils.ts': '*.util.ts', 41 | }, 42 | ], 43 | }, 44 | { 45 | code: "var foo = 'bar';", 46 | filename: 'not-src/foo.apis.ts', 47 | options: [ 48 | { 49 | 'src/*.models.ts': '*.model.ts', 50 | 'src/*.utils.ts': '*.util.ts', 51 | }, 52 | ], 53 | }, 54 | ], 55 | invalid: [ 56 | { 57 | code: "var foo = 'bar';", 58 | filename: 'src/foo.models.ts', 59 | options: [ 60 | { 61 | 'src/*.models.ts': '*.model.ts', 62 | 'src/*.utils.ts': '*.util.ts', 63 | }, 64 | ], 65 | errors: [ 66 | { 67 | message: 68 | 'The filename "foo.models.ts" matches the blocklisted "src/*.models.ts" pattern, use a pattern like "*.model.ts" instead', 69 | column: 1, 70 | line: 1, 71 | }, 72 | ], 73 | }, 74 | { 75 | code: "var foo = 'bar';", 76 | filename: 'src/foo.utils.ts', 77 | options: [ 78 | { 79 | 'src/*.models.ts': '*.model.ts', 80 | 'src/*.utils.ts': '*.util.ts', 81 | }, 82 | ], 83 | errors: [ 84 | { 85 | message: 86 | 'The filename "foo.utils.ts" matches the blocklisted "src/*.utils.ts" pattern, use a pattern like "*.util.ts" instead', 87 | column: 1, 88 | line: 1, 89 | }, 90 | ], 91 | }, 92 | ], 93 | } 94 | ); 95 | 96 | ruleTester.run( 97 | "filename-blocklist with option: [{ 'src/*.models.ts': 'FOO' }]", 98 | rule, 99 | { 100 | valid: [], 101 | 102 | invalid: [ 103 | { 104 | code: "var foo = 'bar';", 105 | filename: 'src/foo.models.ts', 106 | options: [{ 'src/*.models.ts': 'FOO' }], 107 | errors: [ 108 | { 109 | message: 110 | 'There is an invalid pattern "FOO", please double-check it and try again', 111 | column: 1, 112 | line: 1, 113 | }, 114 | ], 115 | }, 116 | ], 117 | } 118 | ); 119 | 120 | ruleTester.run( 121 | "filename-blocklist with option: [{ 'models.ts': '*.model.ts' }]", 122 | rule, 123 | { 124 | valid: [], 125 | 126 | invalid: [ 127 | { 128 | code: "var foo = 'bar';", 129 | filename: 'src/foo.models.ts', 130 | options: [{ 'models.ts': '*.model.ts' }], 131 | errors: [ 132 | { 133 | message: 134 | 'There is an invalid pattern "models.ts", please double-check it and try again', 135 | column: 1, 136 | line: 1, 137 | }, 138 | ], 139 | }, 140 | ], 141 | } 142 | ); 143 | 144 | ruleTester.run('filename-blocklist with option: []', rule, { 145 | valid: [], 146 | 147 | invalid: [ 148 | { 149 | code: "var foo = 'bar';", 150 | filename: 'src/foo.models.ts', 151 | options: [], 152 | errors: [ 153 | { 154 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 155 | column: 1, 156 | line: 1, 157 | }, 158 | ], 159 | }, 160 | ], 161 | }); 162 | 163 | ruleTester.run( 164 | "filename-blocklist with option: [{'src/*.models.ts': '*.model.ts'}, { errorMessage: 'The file \"{{ target }}\" is blocked since it since it matches the blocklisted pattern \"{{ pattern }}\", see contribute.md for details' }]", 165 | rule, 166 | { 167 | valid: [ 168 | { 169 | code: "var foo = 'bar';", 170 | filename: 'src/foo.apis.ts', 171 | options: [ 172 | { 'src/*.models.ts': '*.model.ts' }, 173 | { 174 | errorMessage: 175 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 176 | }, 177 | ], 178 | }, 179 | ], 180 | 181 | invalid: [ 182 | { 183 | code: "var foo = 'bar';", 184 | filename: 'src/foo.models.ts', 185 | options: [ 186 | { 'src/*.models.ts': '*.model.ts' }, 187 | { 188 | errorMessage: 189 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 190 | }, 191 | ], 192 | errors: [ 193 | { 194 | message: 195 | 'The file "foo.models.ts" is blocked since it since it matches the blocklisted pattern "src/*.models.ts", see contribute.md for details', 196 | column: 1, 197 | line: 1, 198 | }, 199 | ], 200 | }, 201 | ], 202 | } 203 | ); 204 | 205 | ruleTester.run( 206 | "filename-blocklist with option: [{'src/*.models.ts': ''}, { errorMessage: 'The file \"{{ target }}\" is blocked since it since it matches the blocklisted pattern \"{{ pattern }}\", see contribute.md for details' }]", 207 | rule, 208 | { 209 | valid: [ 210 | { 211 | code: "var foo = 'bar';", 212 | filename: 'src/foo.apis.ts', 213 | options: [ 214 | { 'src/*.models.ts': '' }, 215 | { 216 | errorMessage: 217 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 218 | }, 219 | ], 220 | }, 221 | ], 222 | 223 | invalid: [ 224 | { 225 | code: "var foo = 'bar';", 226 | filename: 'src/foo.models.ts', 227 | options: [ 228 | { 'src/*.models.ts': '' }, 229 | { 230 | errorMessage: 231 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 232 | }, 233 | ], 234 | errors: [ 235 | { 236 | message: 237 | 'The file "foo.models.ts" is blocked since it since it matches the blocklisted pattern "src/*.models.ts", see contribute.md for details', 238 | column: 1, 239 | line: 1, 240 | }, 241 | ], 242 | }, 243 | ], 244 | } 245 | ); 246 | 247 | ruleTester.run( 248 | "filename-blocklist with option: [{'src/*.models.ts': ''}]", 249 | rule, 250 | { 251 | valid: [], 252 | 253 | invalid: [ 254 | { 255 | code: "var foo = 'bar';", 256 | filename: 'src/foo.models.ts', 257 | options: [{ 'src/*.models.ts': '' }], 258 | errors: [ 259 | { 260 | message: 261 | 'There is an invalid pattern "", please double-check it and try again', 262 | column: 1, 263 | line: 1, 264 | }, 265 | ], 266 | }, 267 | ], 268 | } 269 | ); 270 | -------------------------------------------------------------------------------- /tests/lib/rules/filename-blocklist.windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The filename should be blocklisted 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { win32 } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/filename-blocklist.js', 12 | {}, 13 | { 14 | path: win32, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "filename-blocklist with option on Windows: [{ 'src/*.models.ts': '*.model.ts' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 'C:\\Users\\Administrator\\Downloads\\wai\\src\\foo.model.ts', 27 | options: [{ 'src/*.models.ts': '*.model.ts' }], 28 | }, 29 | { 30 | code: "var foo = 'bar';", 31 | filename: 'src\\foo.model.ts', 32 | options: [{ 'src/*.models.ts': '*.model.ts' }], 33 | }, 34 | ], 35 | invalid: [ 36 | { 37 | code: "var foo = 'bar';", 38 | filename: 'src\\foo.models.ts', 39 | options: [{ 'src/*.models.ts': '*.model.ts' }], 40 | errors: [ 41 | { 42 | message: 43 | 'The filename "foo.models.ts" matches the blocklisted "src/*.models.ts" pattern, use a pattern like "*.model.ts" instead', 44 | column: 1, 45 | line: 1, 46 | }, 47 | ], 48 | }, 49 | ], 50 | } 51 | ); 52 | 53 | ruleTester.run( 54 | "filename-blocklist with option on Windows: [{ 'src/*.models.ts': 'FOO' }]", 55 | rule, 56 | { 57 | valid: [], 58 | 59 | invalid: [ 60 | { 61 | code: "var foo = 'bar';", 62 | filename: 'src\\foo.models.ts', 63 | options: [{ 'src/*.models.ts': 'FOO' }], 64 | errors: [ 65 | { 66 | message: 67 | 'There is an invalid pattern "FOO", please double-check it and try again', 68 | column: 1, 69 | line: 1, 70 | }, 71 | ], 72 | }, 73 | ], 74 | } 75 | ); 76 | 77 | ruleTester.run( 78 | "filename-blocklist with option on Windows: [{ 'models.ts': '*.model.ts' }]", 79 | rule, 80 | { 81 | valid: [], 82 | 83 | invalid: [ 84 | { 85 | code: "var foo = 'bar';", 86 | filename: 'src\\foo.models.ts', 87 | options: [{ 'models.ts': '*.model.ts' }], 88 | errors: [ 89 | { 90 | message: 91 | 'There is an invalid pattern "models.ts", please double-check it and try again', 92 | column: 1, 93 | line: 1, 94 | }, 95 | ], 96 | }, 97 | ], 98 | } 99 | ); 100 | 101 | ruleTester.run('filename-blocklist with option on Windows: []', rule, { 102 | valid: [], 103 | 104 | invalid: [ 105 | { 106 | code: "var foo = 'bar';", 107 | filename: 'src\\foo.models.ts', 108 | options: [], 109 | errors: [ 110 | { 111 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 112 | column: 1, 113 | line: 1, 114 | }, 115 | ], 116 | }, 117 | ], 118 | }); 119 | 120 | ruleTester.run( 121 | "filename-blocklist with option on Windows: [{'src/*.models.ts': '*.model.ts'}, { errorMessage: 'The file \"{{ target }}\" is blocked since it since it matches the blocklisted pattern \"{{ pattern }}\", see contribute.md for details' }]", 122 | rule, 123 | { 124 | valid: [ 125 | { 126 | code: "var foo = 'bar';", 127 | filename: 'src\\foo.apis.ts', 128 | options: [ 129 | { 'src/*.models.ts': '*.model.ts' }, 130 | { 131 | errorMessage: 132 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 133 | }, 134 | ], 135 | }, 136 | ], 137 | 138 | invalid: [ 139 | { 140 | code: "var foo = 'bar';", 141 | filename: 'src\\foo.models.ts', 142 | options: [ 143 | { 'src/*.models.ts': '*.model.ts' }, 144 | { 145 | errorMessage: 146 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 147 | }, 148 | ], 149 | errors: [ 150 | { 151 | message: 152 | 'The file "foo.models.ts" is blocked since it since it matches the blocklisted pattern "src/*.models.ts", see contribute.md for details', 153 | column: 1, 154 | line: 1, 155 | }, 156 | ], 157 | }, 158 | ], 159 | } 160 | ); 161 | 162 | ruleTester.run( 163 | "filename-blocklist with option on Windows: [{'src/*.models.ts': ''}, { errorMessage: 'The file \"{{ target }}\" is blocked since it since it matches the blocklisted pattern \"{{ pattern }}\", see contribute.md for details' }]", 164 | rule, 165 | { 166 | valid: [ 167 | { 168 | code: "var foo = 'bar';", 169 | filename: 'src\\foo.apis.ts', 170 | options: [ 171 | { 'src/*.models.ts': '' }, 172 | { 173 | errorMessage: 174 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 175 | }, 176 | ], 177 | }, 178 | ], 179 | 180 | invalid: [ 181 | { 182 | code: "var foo = 'bar';", 183 | filename: 'src\\foo.models.ts', 184 | options: [ 185 | { 'src/*.models.ts': '' }, 186 | { 187 | errorMessage: 188 | 'The file "{{ target }}" is blocked since it since it matches the blocklisted pattern "{{ pattern }}", see contribute.md for details', 189 | }, 190 | ], 191 | errors: [ 192 | { 193 | message: 194 | 'The file "foo.models.ts" is blocked since it since it matches the blocklisted pattern "src/*.models.ts", see contribute.md for details', 195 | column: 1, 196 | line: 1, 197 | }, 198 | ], 199 | }, 200 | ], 201 | } 202 | ); 203 | 204 | ruleTester.run( 205 | "filename-blocklist with option on Windows: [{'src/*.models.ts': ''}]", 206 | rule, 207 | { 208 | valid: [], 209 | 210 | invalid: [ 211 | { 212 | code: "var foo = 'bar';", 213 | filename: 'src\\foo.models.ts', 214 | options: [{ 'src/*.models.ts': '' }], 215 | errors: [ 216 | { 217 | message: 218 | 'There is an invalid pattern "", please double-check it and try again', 219 | column: 1, 220 | line: 1, 221 | }, 222 | ], 223 | }, 224 | ], 225 | } 226 | ); 227 | -------------------------------------------------------------------------------- /tests/lib/rules/filename-naming-convention.windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The filename should follow the filename naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { win32 } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/filename-naming-convention.js', 12 | {}, 13 | { 14 | path: win32, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 27 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\login.jsx', 28 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 29 | }, 30 | { 31 | code: "var foo = 'bar';", 32 | filename: 'src\\components\\login.jsx', 33 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 34 | }, 35 | { 36 | code: "var foo = 'bar';", 37 | filename: 'src\\login.jsx', 38 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 39 | }, 40 | { 41 | code: "var foo = 'bar';", 42 | filename: 'login.jsx', 43 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 44 | }, 45 | { 46 | code: "var foo = 'bar';", 47 | filename: 'src\\utils\\calculatePrice.js', 48 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 49 | }, 50 | { 51 | code: "var foo = 'bar';", 52 | filename: 'src\\calculatePrice.js', 53 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 54 | }, 55 | { 56 | code: "var foo = 'bar';", 57 | filename: 'calculatePrice.js', 58 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 59 | }, 60 | { 61 | code: "var foo = 'bar';", 62 | filename: 'src\\classes\\g2tClass.js', 63 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 64 | }, 65 | ], 66 | 67 | invalid: [ 68 | { 69 | code: "var foo = 'bar';", 70 | filename: 'src\\utils\\CalculatePrice.js', 71 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 72 | errors: [ 73 | { 74 | message: 75 | 'The filename "CalculatePrice.js" does not match the "CAMEL_CASE" pattern', 76 | column: 1, 77 | line: 1, 78 | }, 79 | ], 80 | }, 81 | { 82 | code: "var foo = 'bar';", 83 | filename: 84 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculate_price.js', 85 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 86 | errors: [ 87 | { 88 | message: 89 | 'The filename "calculate_price.js" does not match the "CAMEL_CASE" pattern', 90 | column: 1, 91 | line: 1, 92 | }, 93 | ], 94 | }, 95 | ], 96 | } 97 | ); 98 | 99 | ruleTester.run( 100 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }]", 101 | rule, 102 | { 103 | valid: [ 104 | { 105 | code: "var foo = 'bar';", 106 | filename: 107 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\Login.jsx', 108 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 109 | }, 110 | { 111 | code: "var foo = 'bar';", 112 | filename: 'src\\components\\Login.jsx', 113 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 114 | }, 115 | { 116 | code: "var foo = 'bar';", 117 | filename: 'src\\utils\\CalculatePrice.js', 118 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 119 | }, 120 | { 121 | code: "var foo = 'bar';", 122 | filename: 'src\\utils\\Calculate2Price.js', 123 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 124 | }, 125 | ], 126 | 127 | invalid: [ 128 | { 129 | code: "var foo = 'bar';", 130 | filename: 'src\\utils\\calculatePrice.js', 131 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 132 | errors: [ 133 | { 134 | message: 135 | 'The filename "calculatePrice.js" does not match the "PASCAL_CASE" pattern', 136 | column: 1, 137 | line: 1, 138 | }, 139 | ], 140 | }, 141 | { 142 | code: "var foo = 'bar';", 143 | filename: 144 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 145 | options: [{ '**/*.js': 'PASCAL_CASE', '**/*.jsx': 'PASCAL_CASE' }], 146 | errors: [ 147 | { 148 | message: 149 | 'The filename "calculatePrice.js" does not match the "PASCAL_CASE" pattern', 150 | column: 1, 151 | line: 1, 152 | }, 153 | ], 154 | }, 155 | ], 156 | } 157 | ); 158 | 159 | ruleTester.run( 160 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }]", 161 | rule, 162 | { 163 | valid: [ 164 | { 165 | code: "var foo = 'bar';", 166 | filename: 167 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\login.jsx', 168 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 169 | }, 170 | { 171 | code: "var foo = 'bar';", 172 | filename: 'src\\components\\login.jsx', 173 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 174 | }, 175 | { 176 | code: "var foo = 'bar';", 177 | filename: 'src\\utils\\calculate_price.js', 178 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 179 | }, 180 | { 181 | code: "var foo = 'bar';", 182 | filename: 'src\\utils\\i18n_test.js', 183 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 184 | }, 185 | ], 186 | 187 | invalid: [ 188 | { 189 | code: "var foo = 'bar';", 190 | filename: 191 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 192 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 193 | errors: [ 194 | { 195 | message: 196 | 'The filename "calculatePrice.js" does not match the "SNAKE_CASE" pattern', 197 | column: 1, 198 | line: 1, 199 | }, 200 | ], 201 | }, 202 | { 203 | code: "var foo = 'bar';", 204 | filename: 'src\\utils\\calculatePrice.js', 205 | options: [{ '**/*.js': 'SNAKE_CASE', '**/*.jsx': 'SNAKE_CASE' }], 206 | errors: [ 207 | { 208 | message: 209 | 'The filename "calculatePrice.js" does not match the "SNAKE_CASE" pattern', 210 | column: 1, 211 | line: 1, 212 | }, 213 | ], 214 | }, 215 | ], 216 | } 217 | ); 218 | 219 | ruleTester.run( 220 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }]", 221 | rule, 222 | { 223 | valid: [ 224 | { 225 | code: "var foo = 'bar';", 226 | filename: 'src\\components\\login.jsx', 227 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 228 | }, 229 | { 230 | code: "var foo = 'bar';", 231 | filename: 232 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\login.jsx', 233 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 234 | }, 235 | { 236 | code: "var foo = 'bar';", 237 | filename: 'src\\utils\\calculate-price.js', 238 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 239 | }, 240 | { 241 | code: "var foo = 'bar';", 242 | filename: 'src\\utils\\i18n-test.js', 243 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 244 | }, 245 | ], 246 | 247 | invalid: [ 248 | { 249 | code: "var foo = 'bar';", 250 | filename: 251 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 252 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 253 | errors: [ 254 | { 255 | message: 256 | 'The filename "calculatePrice.js" does not match the "KEBAB_CASE" pattern', 257 | column: 1, 258 | line: 1, 259 | }, 260 | ], 261 | }, 262 | { 263 | code: "var foo = 'bar';", 264 | filename: 'src\\utils\\calculatePrice.js', 265 | options: [{ '**/*.js': 'KEBAB_CASE', '**/*.jsx': 'KEBAB_CASE' }], 266 | errors: [ 267 | { 268 | message: 269 | 'The filename "calculatePrice.js" does not match the "KEBAB_CASE" pattern', 270 | column: 1, 271 | line: 1, 272 | }, 273 | ], 274 | }, 275 | ], 276 | } 277 | ); 278 | 279 | ruleTester.run( 280 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'SCREAMING_SNAKE_CASE', '**/*.jsx': 'SCREAMING_SNAKE_CASE' }]", 281 | rule, 282 | { 283 | valid: [ 284 | { 285 | code: "var foo = 'bar';", 286 | filename: 287 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\LOGIN.jsx', 288 | options: [ 289 | { 290 | '**/*.js': 'SCREAMING_SNAKE_CASE', 291 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 292 | }, 293 | ], 294 | }, 295 | { 296 | code: "var foo = 'bar';", 297 | filename: 'src\\components\\LOGIN.jsx', 298 | options: [ 299 | { 300 | '**/*.js': 'SCREAMING_SNAKE_CASE', 301 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 302 | }, 303 | ], 304 | }, 305 | { 306 | code: "var foo = 'bar';", 307 | filename: 'src\\utils\\CALCULATE_PRICE.js', 308 | options: [ 309 | { 310 | '**/*.js': 'SCREAMING_SNAKE_CASE', 311 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 312 | }, 313 | ], 314 | }, 315 | { 316 | code: "var foo = 'bar';", 317 | filename: 'src\\utils\\CALCULATE100_PRICE.js', 318 | options: [ 319 | { 320 | '**/*.js': 'SCREAMING_SNAKE_CASE', 321 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 322 | }, 323 | ], 324 | }, 325 | ], 326 | 327 | invalid: [ 328 | { 329 | code: "var foo = 'bar';", 330 | filename: 331 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 332 | options: [ 333 | { 334 | '**/*.js': 'SCREAMING_SNAKE_CASE', 335 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 336 | }, 337 | ], 338 | errors: [ 339 | { 340 | message: 341 | 'The filename "calculatePrice.js" does not match the "SCREAMING_SNAKE_CASE" pattern', 342 | column: 1, 343 | line: 1, 344 | }, 345 | ], 346 | }, 347 | { 348 | code: "var foo = 'bar';", 349 | filename: 'src\\utils\\calculatePrice.js', 350 | options: [ 351 | { 352 | '**/*.js': 'SCREAMING_SNAKE_CASE', 353 | '**/*.jsx': 'SCREAMING_SNAKE_CASE', 354 | }, 355 | ], 356 | errors: [ 357 | { 358 | message: 359 | 'The filename "calculatePrice.js" does not match the "SCREAMING_SNAKE_CASE" pattern', 360 | column: 1, 361 | line: 1, 362 | }, 363 | ], 364 | }, 365 | ], 366 | } 367 | ); 368 | 369 | ruleTester.run( 370 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }]", 371 | rule, 372 | { 373 | valid: [ 374 | { 375 | code: "var foo = 'bar';", 376 | filename: 377 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\login.jsx', 378 | options: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }], 379 | }, 380 | { 381 | code: "var foo = 'bar';", 382 | filename: 'src\\components\\login.jsx', 383 | options: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }], 384 | }, 385 | { 386 | code: "var foo = 'bar';", 387 | filename: 'src\\utils\\calculateprice.js', 388 | options: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }], 389 | }, 390 | ], 391 | 392 | invalid: [ 393 | { 394 | code: "var foo = 'bar';", 395 | filename: 'src\\utils\\calculatePrice.js', 396 | options: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }], 397 | errors: [ 398 | { 399 | message: 400 | 'The filename "calculatePrice.js" does not match the "FLAT_CASE" pattern', 401 | column: 1, 402 | line: 1, 403 | }, 404 | ], 405 | }, 406 | { 407 | code: "var foo = 'bar';", 408 | filename: 409 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 410 | options: [{ '**/*.js': 'FLAT_CASE', '**/*.jsx': 'FLAT_CASE' }], 411 | errors: [ 412 | { 413 | message: 414 | 'The filename "calculatePrice.js" does not match the "FLAT_CASE" pattern', 415 | column: 1, 416 | line: 1, 417 | }, 418 | ], 419 | }, 420 | ], 421 | } 422 | ); 423 | 424 | ruleTester.run( 425 | "filename-naming-convention with option on Windows: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }]", 426 | rule, 427 | { 428 | valid: [ 429 | { 430 | code: "var foo = 'bar';", 431 | filename: 432 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\components\\__login.jsx', 433 | options: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }], 434 | }, 435 | { 436 | code: "var foo = 'bar';", 437 | filename: 'src\\components\\__login.jsx', 438 | options: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }], 439 | }, 440 | { 441 | code: "var foo = 'bar';", 442 | filename: 'src\\utils\\__calculateprice.js', 443 | options: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }], 444 | }, 445 | ], 446 | 447 | invalid: [ 448 | { 449 | code: "var foo = 'bar';", 450 | filename: 451 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 452 | options: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }], 453 | errors: [ 454 | { 455 | message: 456 | 'The filename "calculatePrice.js" does not match the "__+([a-z])" pattern', 457 | column: 1, 458 | line: 1, 459 | }, 460 | ], 461 | }, 462 | { 463 | code: "var foo = 'bar';", 464 | filename: 'src\\utils\\calculatePrice.js', 465 | options: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }], 466 | errors: [ 467 | { 468 | message: 469 | 'The filename "calculatePrice.js" does not match the "__+([a-z])" pattern', 470 | column: 1, 471 | line: 1, 472 | }, 473 | ], 474 | }, 475 | ], 476 | } 477 | ); 478 | 479 | ruleTester.run( 480 | 'filename-naming-convention with fex that has not been set on Windows', 481 | rule, 482 | { 483 | valid: [ 484 | { 485 | code: "var foo = 'bar';", 486 | filename: 487 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\CalculatePrice.ts', 488 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 489 | }, 490 | { 491 | code: "var foo = 'bar';", 492 | filename: 'src\\utils\\CalculatePrice.ts', 493 | options: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }], 494 | }, 495 | ], 496 | 497 | invalid: [], 498 | } 499 | ); 500 | 501 | ruleTester.run( 502 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'FOO', '.jsx': 'FLAT_CASE' }]", 503 | rule, 504 | { 505 | valid: [], 506 | 507 | invalid: [ 508 | { 509 | code: "var foo = 'bar';", 510 | filename: 511 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 512 | options: [{ '**/*.js': 'FOO', '.jsx': 'FLAT_CASE' }], 513 | errors: [ 514 | { 515 | message: 516 | 'There is an invalid pattern "FOO", please double-check it and try again', 517 | column: 1, 518 | line: 1, 519 | }, 520 | ], 521 | }, 522 | { 523 | code: "var foo = 'bar';", 524 | filename: 'src\\utils\\calculatePrice.js', 525 | options: [{ '**/*.js': 'FOO', '.jsx': 'FLAT_CASE' }], 526 | errors: [ 527 | { 528 | message: 529 | 'There is an invalid pattern "FOO", please double-check it and try again', 530 | column: 1, 531 | line: 1, 532 | }, 533 | ], 534 | }, 535 | ], 536 | } 537 | ); 538 | 539 | ruleTester.run( 540 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'CAMEL_CASE', '.jsx': 'FLAT_CASE' }]", 541 | rule, 542 | { 543 | valid: [], 544 | 545 | invalid: [ 546 | { 547 | code: "var foo = 'bar';", 548 | filename: 549 | 'C:\\Users\\Administrator\\Downloads\\wai\\src\\utils\\calculatePrice.js', 550 | options: [{ '**/*.js': 'CAMEL_CASE', '.jsx': 'FLAT_CASE' }], 551 | errors: [ 552 | { 553 | message: 554 | 'There is an invalid pattern ".jsx", please double-check it and try again', 555 | column: 1, 556 | line: 1, 557 | }, 558 | ], 559 | }, 560 | { 561 | code: "var foo = 'bar';", 562 | filename: 'src\\utils\\calculatePrice.js', 563 | options: [{ '**/*.js': 'CAMEL_CASE', '.jsx': 'FLAT_CASE' }], 564 | errors: [ 565 | { 566 | message: 567 | 'There is an invalid pattern ".jsx", please double-check it and try again', 568 | column: 1, 569 | line: 1, 570 | }, 571 | ], 572 | }, 573 | ], 574 | } 575 | ); 576 | 577 | ruleTester.run( 578 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'NEXT_JS_APP_ROUTER_CASE' }]", 579 | rule, 580 | { 581 | valid: [], 582 | 583 | invalid: [ 584 | { 585 | code: "var foo = 'bar';", 586 | filename: 'src\\utils\\calculatePrice.js', 587 | options: [{ '**/*.js': 'NEXT_JS_APP_ROUTER_CASE' }], 588 | errors: [ 589 | { 590 | message: 591 | 'There is an invalid pattern "NEXT_JS_APP_ROUTER_CASE", please double-check it and try again', 592 | column: 1, 593 | line: 1, 594 | }, 595 | ], 596 | }, 597 | ], 598 | } 599 | ); 600 | 601 | ruleTester.run( 602 | "filename-naming-convention with option on Windows: [{ 'src/services/*.js': 'PASCAL_CASE', 'src/composables/*.js': 'CAMEL_CASE' }]", 603 | rule, 604 | { 605 | valid: [ 606 | { 607 | code: "var foo = 'bar';", 608 | filename: 'src\\services\\DownloadService.js', 609 | options: [ 610 | { 611 | 'src/services/*.js': 'PASCAL_CASE', 612 | 'src/composables/*.js': 'CAMEL_CASE', 613 | }, 614 | ], 615 | }, 616 | { 617 | code: "var foo = 'bar';", 618 | filename: 'src\\composables\\useDownloadApi.js', 619 | options: [ 620 | { 621 | 'src/services/*.js': 'PASCAL_CASE', 622 | 'src/composables/*.js': 'CAMEL_CASE', 623 | }, 624 | ], 625 | }, 626 | { 627 | code: "var foo = 'bar';", 628 | filename: 'src\\utils\\CalculatePrice.ts', 629 | options: [ 630 | { 631 | 'src/services/*.js': 'PASCAL_CASE', 632 | 'src/composables/*.js': 'CAMEL_CASE', 633 | }, 634 | ], 635 | }, 636 | ], 637 | 638 | invalid: [ 639 | { 640 | code: "var foo = 'bar';", 641 | filename: 'src\\services\\downloadService.js', 642 | options: [ 643 | { 644 | 'src/services/*.js': 'PASCAL_CASE', 645 | 'src/composables/*.js': 'CAMEL_CASE', 646 | }, 647 | ], 648 | errors: [ 649 | { 650 | message: 651 | 'The filename "downloadService.js" does not match the "PASCAL_CASE" pattern', 652 | column: 1, 653 | line: 1, 654 | }, 655 | ], 656 | }, 657 | { 658 | code: "var foo = 'bar';", 659 | filename: 'src\\composables\\UseDownloadApi.js', 660 | options: [ 661 | { 662 | 'src/services/*.js': 'PASCAL_CASE', 663 | 'src/composables/*.js': 'CAMEL_CASE', 664 | }, 665 | ], 666 | errors: [ 667 | { 668 | message: 669 | 'The filename "UseDownloadApi.js" does not match the "CAMEL_CASE" pattern', 670 | column: 1, 671 | line: 1, 672 | }, 673 | ], 674 | }, 675 | ], 676 | } 677 | ); 678 | 679 | ruleTester.run( 680 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }]", 681 | rule, 682 | { 683 | valid: [ 684 | { 685 | code: "var foo = 'bar';", 686 | filename: 'src\\pages\\_app.js', 687 | options: [ 688 | { 689 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 690 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 691 | }, 692 | ], 693 | }, 694 | { 695 | code: "var foo = 'bar';", 696 | filename: 'src\\pages\\_document.js', 697 | options: [ 698 | { 699 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 700 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 701 | }, 702 | ], 703 | }, 704 | { 705 | code: "var foo = 'bar';", 706 | filename: 'src\\pages\\_error.js', 707 | options: [ 708 | { 709 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 710 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 711 | }, 712 | ], 713 | }, 714 | { 715 | code: "var foo = 'bar';", 716 | filename: 'src\\pages\\404.js', 717 | options: [ 718 | { 719 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 720 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 721 | }, 722 | ], 723 | }, 724 | { 725 | code: "var foo = 'bar';", 726 | filename: 'src\\pages\\500.js', 727 | options: [ 728 | { 729 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 730 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 731 | }, 732 | ], 733 | }, 734 | { 735 | code: "var foo = 'bar';", 736 | filename: 'src\\pages\\blog\\index.js', 737 | options: [ 738 | { 739 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 740 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 741 | }, 742 | ], 743 | }, 744 | { 745 | code: "var foo = 'bar';", 746 | filename: 'src\\services\\getBlogPosts.js', 747 | options: [ 748 | { 749 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 750 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 751 | }, 752 | ], 753 | }, 754 | { 755 | code: "var foo = 'bar';", 756 | filename: 'src\\pages\\blog\\[post].js', 757 | options: [ 758 | { 759 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 760 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 761 | }, 762 | ], 763 | }, 764 | { 765 | code: "var foo = 'bar';", 766 | filename: 'src\\pages\\blog\\[blogPost].js', 767 | options: [ 768 | { 769 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 770 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 771 | }, 772 | ], 773 | }, 774 | { 775 | code: "var foo = 'bar';", 776 | filename: 'src\\pages\\blog\\[[...slug]].js', 777 | options: [ 778 | { 779 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 780 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 781 | }, 782 | ], 783 | }, 784 | { 785 | code: "var foo = 'bar';", 786 | filename: 'src\\pages\\blog\\[...params].js', 787 | options: [ 788 | { 789 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 790 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 791 | }, 792 | ], 793 | }, 794 | { 795 | code: "var foo = 'bar';", 796 | filename: 'src\\pages\\blog\\[category]\\[post].js', 797 | options: [ 798 | { 799 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 800 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 801 | }, 802 | ], 803 | }, 804 | ], 805 | 806 | invalid: [ 807 | { 808 | code: "var foo = 'bar';", 809 | filename: 'src\\utils\\CALCULATE_PRICE.js', 810 | options: [ 811 | { 812 | '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 813 | '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', 814 | }, 815 | ], 816 | errors: [ 817 | { 818 | message: 819 | 'The filename "CALCULATE_PRICE.js" does not match the "NEXT_JS_PAGE_ROUTER_FILENAME_CASE" pattern', 820 | column: 1, 821 | line: 1, 822 | }, 823 | ], 824 | }, 825 | ], 826 | } 827 | ); 828 | 829 | ruleTester.run( 830 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'CAMEL_CASE' }, { ignoreMiddleExtensions: true }]", 831 | rule, 832 | { 833 | valid: [ 834 | { 835 | code: "var foo = 'bar';", 836 | filename: 'src\\utils\\date.js', 837 | options: [ 838 | { '**/*.js': 'CAMEL_CASE' }, 839 | { ignoreMiddleExtensions: true }, 840 | ], 841 | }, 842 | { 843 | code: "var foo = 'bar';", 844 | filename: 'src\\utils\\date.test.js', 845 | options: [ 846 | { '**/*.js': 'CAMEL_CASE' }, 847 | { ignoreMiddleExtensions: true }, 848 | ], 849 | }, 850 | { 851 | code: "var foo = 'bar';", 852 | filename: 'src\\utils\\date.spec.js', 853 | options: [ 854 | { '**/*.js': 'CAMEL_CASE' }, 855 | { ignoreMiddleExtensions: true }, 856 | ], 857 | }, 858 | { 859 | code: "var foo = 'bar';", 860 | filename: 'src\\utils\\date.spec.test.js', 861 | options: [ 862 | { '**/*.js': 'CAMEL_CASE' }, 863 | { ignoreMiddleExtensions: true }, 864 | ], 865 | }, 866 | ], 867 | 868 | invalid: [ 869 | { 870 | code: "var foo = 'bar';", 871 | filename: 'src\\utils\\Date.js', 872 | options: [ 873 | { '**/*.js': 'CAMEL_CASE' }, 874 | { ignoreMiddleExtensions: true }, 875 | ], 876 | errors: [ 877 | { 878 | message: 879 | 'The filename "Date.js" does not match the "CAMEL_CASE" pattern', 880 | column: 1, 881 | line: 1, 882 | }, 883 | ], 884 | }, 885 | { 886 | code: "var foo = 'bar';", 887 | filename: 'src\\utils\\Date.test.js', 888 | options: [ 889 | { '**/*.js': 'CAMEL_CASE' }, 890 | { ignoreMiddleExtensions: true }, 891 | ], 892 | errors: [ 893 | { 894 | message: 895 | 'The filename "Date.test.js" does not match the "CAMEL_CASE" pattern', 896 | column: 1, 897 | line: 1, 898 | }, 899 | ], 900 | }, 901 | { 902 | code: "var foo = 'bar';", 903 | filename: 'src\\utils\\Date.spec.js', 904 | options: [ 905 | { '**/*.js': 'CAMEL_CASE' }, 906 | { ignoreMiddleExtensions: true }, 907 | ], 908 | errors: [ 909 | { 910 | message: 911 | 'The filename "Date.spec.js" does not match the "CAMEL_CASE" pattern', 912 | column: 1, 913 | line: 1, 914 | }, 915 | ], 916 | }, 917 | { 918 | code: "var foo = 'bar';", 919 | filename: 'src\\utils\\date_util.spec.js', 920 | options: [ 921 | { '**/*.js': 'CAMEL_CASE' }, 922 | { ignoreMiddleExtensions: true }, 923 | ], 924 | errors: [ 925 | { 926 | message: 927 | 'The filename "date_util.spec.js" does not match the "CAMEL_CASE" pattern', 928 | column: 1, 929 | line: 1, 930 | }, 931 | ], 932 | }, 933 | { 934 | code: "var foo = 'bar';", 935 | filename: 'src\\utils\\date_util.spec.test.js', 936 | options: [ 937 | { '**/*.js': 'CAMEL_CASE' }, 938 | { ignoreMiddleExtensions: true }, 939 | ], 940 | errors: [ 941 | { 942 | message: 943 | 'The filename "date_util.spec.test.js" does not match the "CAMEL_CASE" pattern', 944 | column: 1, 945 | line: 1, 946 | }, 947 | ], 948 | }, 949 | ], 950 | } 951 | ); 952 | 953 | ruleTester.run( 954 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }, { ignoreMiddleExtensions: false }]", 955 | rule, 956 | { 957 | valid: [ 958 | { 959 | code: "var foo = 'bar';", 960 | filename: 'src\\components\\login.jsx', 961 | options: [ 962 | { '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }, 963 | { ignoreMiddleExtensions: false }, 964 | ], 965 | }, 966 | { 967 | code: "var foo = 'bar';", 968 | filename: 'src\\utils\\calculatePrice.js', 969 | options: [ 970 | { '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }, 971 | { ignoreMiddleExtensions: false }, 972 | ], 973 | }, 974 | ], 975 | 976 | invalid: [ 977 | { 978 | code: "var foo = 'bar';", 979 | filename: 'src\\utils\\CalculatePrice.js', 980 | options: [ 981 | { '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }, 982 | { ignoreMiddleExtensions: false }, 983 | ], 984 | errors: [ 985 | { 986 | message: 987 | 'The filename "CalculatePrice.js" does not match the "CAMEL_CASE" pattern', 988 | column: 1, 989 | line: 1, 990 | }, 991 | ], 992 | }, 993 | { 994 | code: "var foo = 'bar';", 995 | filename: 'src\\utils\\date.test.js', 996 | options: [ 997 | { '**/*.js': 'CAMEL_CASE', '**/*.jsx': 'CAMEL_CASE' }, 998 | { ignoreMiddleExtensions: false }, 999 | ], 1000 | errors: [ 1001 | { 1002 | message: 1003 | 'The filename "date.test.js" does not match the "CAMEL_CASE" pattern', 1004 | column: 1, 1005 | line: 1, 1006 | }, 1007 | ], 1008 | }, 1009 | ], 1010 | } 1011 | ); 1012 | 1013 | ruleTester.run( 1014 | "filename-naming-convention with option on Windows: [{ '**/*/!(index).*': '<1>' }, { ignoreMiddleExtensions: true }]", 1015 | rule, 1016 | { 1017 | valid: [ 1018 | { 1019 | code: "var foo = 'bar';", 1020 | filename: 'src\\components\\featureA\\index.js', 1021 | options: [ 1022 | { '**/*/!(index).*': '<1>' }, 1023 | { ignoreMiddleExtensions: true }, 1024 | ], 1025 | }, 1026 | { 1027 | code: "var foo = 'bar';", 1028 | filename: 'src\\components\\featureA\\featureA.jsx', 1029 | options: [ 1030 | { '**/*/!(index).*': '<1>' }, 1031 | { ignoreMiddleExtensions: true }, 1032 | ], 1033 | }, 1034 | { 1035 | code: "var foo = 'bar';", 1036 | filename: 'src\\components\\featureA\\featureA.specs.js', 1037 | options: [ 1038 | { '**/*/!(index).*': '<1>' }, 1039 | { ignoreMiddleExtensions: true }, 1040 | ], 1041 | }, 1042 | ], 1043 | 1044 | invalid: [ 1045 | { 1046 | code: "var foo = 'bar';", 1047 | filename: 'src\\components\\featureA\\featureB.jsx', 1048 | options: [ 1049 | { '**/*/!(index).*': '<1>' }, 1050 | { ignoreMiddleExtensions: true }, 1051 | ], 1052 | errors: [ 1053 | { 1054 | message: 1055 | 'The filename "featureB.jsx" does not match the "<1>" pattern', 1056 | column: 1, 1057 | line: 1, 1058 | }, 1059 | ], 1060 | }, 1061 | { 1062 | code: "var foo = 'bar';", 1063 | filename: 'src\\components\\featureA\\featureB.specs.js', 1064 | options: [ 1065 | { '**/*/!(index).*': '<1>' }, 1066 | { ignoreMiddleExtensions: true }, 1067 | ], 1068 | errors: [ 1069 | { 1070 | message: 1071 | 'The filename "featureB.specs.js" does not match the "<1>" pattern', 1072 | column: 1, 1073 | line: 1, 1074 | }, 1075 | ], 1076 | }, 1077 | ], 1078 | } 1079 | ); 1080 | 1081 | ruleTester.run( 1082 | "filename-naming-convention with option on Windows: [{ '**/*/!(index).*': '<9>' }]", 1083 | rule, 1084 | { 1085 | valid: [], 1086 | invalid: [ 1087 | { 1088 | code: "var foo = 'bar';", 1089 | filename: 'src\\components\\featureA\\featureA.jsx', 1090 | options: [{ '**/*/!(index).*': '<9>' }], 1091 | errors: [ 1092 | { 1093 | message: 1094 | 'The prefined match "<9>" is not found in the pattern "**/*/!(index).*", please double-check it and try again', 1095 | column: 1, 1096 | line: 1, 1097 | }, 1098 | ], 1099 | }, 1100 | ], 1101 | } 1102 | ); 1103 | 1104 | ruleTester.run( 1105 | "filename-naming-convention with option on Windows: [{ '**/*/!(index).*': '<1>' }, { errorMessage: 'The file \"{{ target }}\" does not match file naming convention defined(\"{{ pattern }}\") for this project, see contribute.md for details'}]", 1106 | rule, 1107 | { 1108 | valid: [], 1109 | invalid: [ 1110 | { 1111 | code: "var foo = 'bar';", 1112 | filename: 'src\\components\\featureA\\featureB.jsx', 1113 | options: [ 1114 | { '**/*/!(index).*': '<1>' }, 1115 | { 1116 | errorMessage: 1117 | 'The file "{{ target }}" does not match file naming convention defined("{{ pattern }}") for this project, see contribute.md for details', 1118 | }, 1119 | ], 1120 | errors: [ 1121 | { 1122 | message: 1123 | 'The file "featureB.jsx" does not match file naming convention defined("<1>") for this project, see contribute.md for details', 1124 | column: 1, 1125 | line: 1, 1126 | }, 1127 | ], 1128 | }, 1129 | ], 1130 | } 1131 | ); 1132 | 1133 | ruleTester.run('filename-naming-convention with option on Windows: []', rule, { 1134 | valid: [], 1135 | invalid: [ 1136 | { 1137 | code: "var foo = 'bar';", 1138 | filename: 'src\\components\\featureA\\featureA.jsx', 1139 | options: [], 1140 | errors: [ 1141 | { 1142 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 1143 | column: 1, 1144 | line: 1, 1145 | }, 1146 | ], 1147 | }, 1148 | ], 1149 | }); 1150 | -------------------------------------------------------------------------------- /tests/lib/rules/folder-match-with-fex.posix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should match the naming pattern specified by its file 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { posix } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/folder-match-with-fex.js', 12 | {}, 13 | { 14 | path: posix, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "folder-match-with-fex with option: [{ '*.js': '**/__tests__/' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: '/__tests__/foo.test.js', 27 | options: [{ '*.js': '**/__tests__/' }], 28 | }, 29 | { 30 | code: "var foo = 'bar';", 31 | filename: '__tests__/foo.test.js', 32 | options: [{ '*.js': '**/__tests__/' }], 33 | }, 34 | { 35 | code: "var foo = 'bar';", 36 | filename: 'bar/__tests__/foo.test.js', 37 | options: [{ '*.js': '**/__tests__/' }], 38 | }, 39 | { 40 | code: "var foo = 'bar';", 41 | filename: '/bar/__tests__/foo.test.js', 42 | options: [{ '*.js': '**/__tests__/' }], 43 | }, 44 | ], 45 | 46 | invalid: [ 47 | { 48 | code: "var foo = 'bar';", 49 | filename: '/bar/__test__/foo.test.js', 50 | options: [{ '*.js': '**/__tests__/' }], 51 | errors: [ 52 | { 53 | message: 54 | 'The folder of the file "foo.test.js" does not match the "**/__tests__/" pattern', 55 | column: 1, 56 | line: 1, 57 | }, 58 | ], 59 | }, 60 | ], 61 | } 62 | ); 63 | 64 | ruleTester.run( 65 | "folder-match-with-fex with option: [{ '*.js': '*/__tests__/' }]", 66 | rule, 67 | { 68 | valid: [ 69 | { 70 | code: "var foo = 'bar';", 71 | filename: '/__tests__/foo.test.js', 72 | options: [{ '*.js': '*/__tests__/' }], 73 | }, 74 | { 75 | code: "var foo = 'bar';", 76 | filename: 'bar/__tests__/foo.test.js', 77 | options: [{ '*.js': '*/__tests__/' }], 78 | }, 79 | ], 80 | 81 | invalid: [ 82 | { 83 | code: "var foo = 'bar';", 84 | filename: '/__test__/foo.test.js', 85 | options: [{ '*.js': '*/__tests__/' }], 86 | errors: [ 87 | { 88 | message: 89 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 90 | column: 1, 91 | line: 1, 92 | }, 93 | ], 94 | }, 95 | { 96 | code: "var foo = 'bar';", 97 | filename: '/bar/__tests__/foo.test.js', 98 | options: [{ '*.js': '*/__tests__/' }], 99 | errors: [ 100 | { 101 | message: 102 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 103 | column: 1, 104 | line: 1, 105 | }, 106 | ], 107 | }, 108 | { 109 | code: "var foo = 'bar';", 110 | filename: '__tests__/foo.test.js', 111 | options: [{ '*.js': '*/__tests__/' }], 112 | errors: [ 113 | { 114 | message: 115 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 116 | column: 1, 117 | line: 1, 118 | }, 119 | ], 120 | }, 121 | ], 122 | } 123 | ); 124 | 125 | ruleTester.run( 126 | "folder-match-with-fex with option: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }]", 127 | rule, 128 | { 129 | valid: [ 130 | { 131 | code: "var foo = 'bar';", 132 | filename: 'bar/__tests__/foo.test.js', 133 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 134 | }, 135 | { 136 | code: "var foo = 'bar';", 137 | filename: 'bar/__tests__/foo.test.jsx', 138 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 139 | }, 140 | { 141 | code: "var foo = 'bar';", 142 | filename: 'bar/__tests__/foo.test.ts', 143 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 144 | }, 145 | { 146 | code: "var foo = 'bar';", 147 | filename: 'bar/__tests__/foo.test.tsx', 148 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 149 | }, 150 | ], 151 | 152 | invalid: [ 153 | { 154 | code: "var foo = 'bar';", 155 | filename: 'bar/_tests_/foo.test.js', 156 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 157 | errors: [ 158 | { 159 | message: 160 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 161 | column: 1, 162 | line: 1, 163 | }, 164 | ], 165 | }, 166 | { 167 | code: "var foo = 'bar';", 168 | filename: 'bar/_tests_/foo.test.jsx', 169 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 170 | errors: [ 171 | { 172 | message: 173 | 'The folder of the file "foo.test.jsx" does not match the "*/__tests__/" pattern', 174 | column: 1, 175 | line: 1, 176 | }, 177 | ], 178 | }, 179 | { 180 | code: "var foo = 'bar';", 181 | filename: 'bar/_tests_/foo.test.ts', 182 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 183 | errors: [ 184 | { 185 | message: 186 | 'The folder of the file "foo.test.ts" does not match the "*/__tests__/" pattern', 187 | column: 1, 188 | line: 1, 189 | }, 190 | ], 191 | }, 192 | { 193 | code: "var foo = 'bar';", 194 | filename: 'bar/_tests_/foo.test.tsx', 195 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 196 | errors: [ 197 | { 198 | message: 199 | 'The folder of the file "foo.test.tsx" does not match the "*/__tests__/" pattern', 200 | column: 1, 201 | line: 1, 202 | }, 203 | ], 204 | }, 205 | ], 206 | } 207 | ); 208 | 209 | ruleTester.run( 210 | "folder-match-with-fex with option: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }]", 211 | rule, 212 | { 213 | valid: [ 214 | { 215 | code: "var foo = 'bar';", 216 | filename: 'bar/__tests__/foo.test.js', 217 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 218 | }, 219 | { 220 | code: "var foo = 'bar';", 221 | filename: 'bar/__tests__/foo.test.ts', 222 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 223 | }, 224 | ], 225 | 226 | invalid: [ 227 | { 228 | code: "var foo = 'bar';", 229 | filename: 'bar/_tests_/foo.test.js', 230 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 231 | errors: [ 232 | { 233 | message: 234 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 235 | column: 1, 236 | line: 1, 237 | }, 238 | ], 239 | }, 240 | { 241 | code: "var foo = 'bar';", 242 | filename: 'bar/_tests_/foo.test.ts', 243 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 244 | errors: [ 245 | { 246 | message: 247 | 'The folder of the file "foo.test.ts" does not match the "*/__tests__/" pattern', 248 | column: 1, 249 | line: 1, 250 | }, 251 | ], 252 | }, 253 | ], 254 | } 255 | ); 256 | 257 | ruleTester.run('folder-match-with-fex with fex that has not been set', rule, { 258 | valid: [ 259 | { 260 | code: "var foo = 'bar';", 261 | filename: '/bar/__test__/foo.test.ts', 262 | options: [{ '*.js': '**/__tests__/', '*.jsx': '**/__tests__/' }], 263 | }, 264 | ], 265 | 266 | invalid: [], 267 | }); 268 | 269 | ruleTester.run( 270 | "folder-match-with-fex with option: [{ '*.test.js': 'FOO', '*.test.ts': '*/__tests__/' }]", 271 | rule, 272 | { 273 | valid: [], 274 | 275 | invalid: [ 276 | { 277 | code: "var foo = 'bar';", 278 | filename: 'bar/__tests__/foo.test.js', 279 | options: [{ '*.test.js': 'FOO', '*.test.ts': '*/__tests__/' }], 280 | errors: [ 281 | { 282 | message: 283 | 'There is an invalid pattern "FOO", please double-check it and try again', 284 | column: 1, 285 | line: 1, 286 | }, 287 | ], 288 | }, 289 | ], 290 | } 291 | ); 292 | 293 | ruleTester.run( 294 | "folder-match-with-fex with option: [{ '*.test.js': '*/__tests__/', '.test.ts': '*/__tests__/' }]", 295 | rule, 296 | { 297 | valid: [], 298 | 299 | invalid: [ 300 | { 301 | code: "var foo = 'bar';", 302 | filename: 'bar/__tests__/foo.test.js', 303 | options: [{ '*.test.js': '*/__tests__/', '.test.ts': '*/__tests__/' }], 304 | errors: [ 305 | { 306 | message: 307 | 'There is an invalid pattern ".test.ts", please double-check it and try again', 308 | column: 1, 309 | line: 1, 310 | }, 311 | ], 312 | }, 313 | ], 314 | } 315 | ); 316 | 317 | ruleTester.run('folder-match-with-fex with option: []', rule, { 318 | valid: [], 319 | 320 | invalid: [ 321 | { 322 | code: "var foo = 'bar';", 323 | filename: 'bar/__tests__/foo.test.js', 324 | options: [], 325 | errors: [ 326 | { 327 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 328 | column: 1, 329 | line: 1, 330 | }, 331 | ], 332 | }, 333 | ], 334 | }); 335 | 336 | ruleTester.run( 337 | "folder-match-with-fex with option: [{ '*.test.js': '*/__tests__/' }, { errorMessage: 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details' }]", 338 | rule, 339 | { 340 | valid: [ 341 | { 342 | code: "var foo = 'bar';", 343 | filename: 'bar/__tests__/foo.test.js', 344 | options: [ 345 | { '*.test.js': '*/__tests__/' }, 346 | { 347 | errorMessage: 348 | 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 349 | }, 350 | ], 351 | }, 352 | ], 353 | 354 | invalid: [ 355 | { 356 | code: "var foo = 'bar';", 357 | filename: 'bar/_tests_/foo.test.js', 358 | options: [ 359 | { '*.test.js': '*/__tests__/' }, 360 | { 361 | errorMessage: 362 | 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 363 | }, 364 | ], 365 | errors: [ 366 | { 367 | message: 368 | 'The folder of the file foo.test.js does not match the */__tests__/ pattern, see contribute.md for details', 369 | column: 1, 370 | line: 1, 371 | }, 372 | ], 373 | }, 374 | ], 375 | } 376 | ); 377 | -------------------------------------------------------------------------------- /tests/lib/rules/folder-match-with-fex.windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should match the naming pattern specified by its file 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { win32 } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/folder-match-with-fex.js', 12 | {}, 13 | { 14 | path: win32, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "folder-match-with-fex with option on Windows: [{ '*.js': '**/__tests__/' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 'C:\\__tests__\\foo.test.js', 27 | options: [{ '*.js': '**/__tests__/' }], 28 | }, 29 | { 30 | code: "var foo = 'bar';", 31 | filename: '__tests__\\foo.test.js', 32 | options: [{ '*.js': '**/__tests__/' }], 33 | }, 34 | { 35 | code: "var foo = 'bar';", 36 | filename: 'bar\\__tests__\\foo.test.js', 37 | options: [{ '*.js': '**/__tests__/' }], 38 | }, 39 | { 40 | code: "var foo = 'bar';", 41 | filename: 'C:\\bar\\__tests__\\foo.test.js', 42 | options: [{ '*.js': '**/__tests__/' }], 43 | }, 44 | ], 45 | 46 | invalid: [ 47 | { 48 | code: "var foo = 'bar';", 49 | filename: 'C:\\bar\\__test__\\foo.test.js', 50 | options: [{ '*.js': '**/__tests__/' }], 51 | errors: [ 52 | { 53 | message: 54 | 'The folder of the file "foo.test.js" does not match the "**/__tests__/" pattern', 55 | column: 1, 56 | line: 1, 57 | }, 58 | ], 59 | }, 60 | ], 61 | } 62 | ); 63 | 64 | ruleTester.run( 65 | "folder-match-with-fex with option on Windows: [{ '*.js': '*/__tests__/' }]", 66 | rule, 67 | { 68 | valid: [ 69 | { 70 | code: "var foo = 'bar';", 71 | filename: 'bar\\__tests__\\foo.test.js', 72 | options: [{ '*.js': '*/__tests__/' }], 73 | }, 74 | ], 75 | 76 | invalid: [ 77 | { 78 | code: "var foo = 'bar';", 79 | filename: 'C:\\__tests__\\foo.test.js', 80 | options: [{ '*.js': '*/__tests__/' }], 81 | errors: [ 82 | { 83 | message: 84 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 85 | column: 1, 86 | line: 1, 87 | }, 88 | ], 89 | }, 90 | { 91 | code: "var foo = 'bar';", 92 | filename: 'C:\\__test__\\foo.test.js', 93 | options: [{ '*.js': '*/__tests__/' }], 94 | errors: [ 95 | { 96 | message: 97 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 98 | column: 1, 99 | line: 1, 100 | }, 101 | ], 102 | }, 103 | { 104 | code: "var foo = 'bar';", 105 | filename: '__tests__\\foo.test.js', 106 | options: [{ '*.js': '*/__tests__/' }], 107 | errors: [ 108 | { 109 | message: 110 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 111 | column: 1, 112 | line: 1, 113 | }, 114 | ], 115 | }, 116 | ], 117 | } 118 | ); 119 | 120 | ruleTester.run( 121 | "folder-match-with-fex with option on Windows: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }]", 122 | rule, 123 | { 124 | valid: [ 125 | { 126 | code: "var foo = 'bar';", 127 | filename: 'bar\\__tests__\\foo.test.js', 128 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 129 | }, 130 | { 131 | code: "var foo = 'bar';", 132 | filename: 'bar\\__tests__\\foo.test.jsx', 133 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 134 | }, 135 | { 136 | code: "var foo = 'bar';", 137 | filename: 'bar\\__tests__\\foo.test.ts', 138 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 139 | }, 140 | { 141 | code: "var foo = 'bar';", 142 | filename: 'bar\\__tests__\\foo.test.tsx', 143 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 144 | }, 145 | ], 146 | 147 | invalid: [ 148 | { 149 | code: "var foo = 'bar';", 150 | filename: 'bar\\_tests_\\foo.test.js', 151 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 152 | errors: [ 153 | { 154 | message: 155 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 156 | column: 1, 157 | line: 1, 158 | }, 159 | ], 160 | }, 161 | { 162 | code: "var foo = 'bar';", 163 | filename: 'bar\\_tests_\\foo.test.jsx', 164 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 165 | errors: [ 166 | { 167 | message: 168 | 'The folder of the file "foo.test.jsx" does not match the "*/__tests__/" pattern', 169 | column: 1, 170 | line: 1, 171 | }, 172 | ], 173 | }, 174 | { 175 | code: "var foo = 'bar';", 176 | filename: 'bar\\_tests_\\foo.test.ts', 177 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 178 | errors: [ 179 | { 180 | message: 181 | 'The folder of the file "foo.test.ts" does not match the "*/__tests__/" pattern', 182 | column: 1, 183 | line: 1, 184 | }, 185 | ], 186 | }, 187 | { 188 | code: "var foo = 'bar';", 189 | filename: 'bar\\_tests_\\foo.test.tsx', 190 | options: [{ '*.test.{js,jsx,ts,tsx}': '*/__tests__/' }], 191 | errors: [ 192 | { 193 | message: 194 | 'The folder of the file "foo.test.tsx" does not match the "*/__tests__/" pattern', 195 | column: 1, 196 | line: 1, 197 | }, 198 | ], 199 | }, 200 | ], 201 | } 202 | ); 203 | 204 | ruleTester.run( 205 | "folder-match-with-fex with option on Windows: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }]", 206 | rule, 207 | { 208 | valid: [ 209 | { 210 | code: "var foo = 'bar';", 211 | filename: 'bar\\__tests__\\foo.test.js', 212 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 213 | }, 214 | { 215 | code: "var foo = 'bar';", 216 | filename: 'bar\\__tests__\\foo.test.ts', 217 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 218 | }, 219 | ], 220 | 221 | invalid: [ 222 | { 223 | code: "var foo = 'bar';", 224 | filename: 'bar\\_tests_\\foo.test.js', 225 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 226 | errors: [ 227 | { 228 | message: 229 | 'The folder of the file "foo.test.js" does not match the "*/__tests__/" pattern', 230 | column: 1, 231 | line: 1, 232 | }, 233 | ], 234 | }, 235 | { 236 | code: "var foo = 'bar';", 237 | filename: 'bar\\_tests_\\foo.test.ts', 238 | options: [{ '*.test.js': '*/__tests__/', '*.test.ts': '*/__tests__/' }], 239 | errors: [ 240 | { 241 | message: 242 | 'The folder of the file "foo.test.ts" does not match the "*/__tests__/" pattern', 243 | column: 1, 244 | line: 1, 245 | }, 246 | ], 247 | }, 248 | ], 249 | } 250 | ); 251 | 252 | ruleTester.run( 253 | 'folder-match-with-fex with fex that has not been set on Windows', 254 | rule, 255 | { 256 | valid: [ 257 | { 258 | code: "var foo = 'bar';", 259 | filename: 'C:\\bar\\__test__\\foo.test.ts', 260 | options: [{ '*.js': '**/__tests__/', '*.jsx': '**/__tests__/' }], 261 | }, 262 | ], 263 | 264 | invalid: [], 265 | } 266 | ); 267 | 268 | ruleTester.run( 269 | "folder-match-with-fex with option on Windows: [{ '*.test.js': 'FOO', '*.test.ts': '*/__tests__/' }]", 270 | rule, 271 | { 272 | valid: [], 273 | 274 | invalid: [ 275 | { 276 | code: "var foo = 'bar';", 277 | filename: 'bar\\__tests__\\foo.test.js', 278 | options: [{ '*.test.js': 'FOO', '*.test.ts': '*/__tests__/' }], 279 | errors: [ 280 | { 281 | message: 282 | 'There is an invalid pattern "FOO", please double-check it and try again', 283 | column: 1, 284 | line: 1, 285 | }, 286 | ], 287 | }, 288 | ], 289 | } 290 | ); 291 | 292 | ruleTester.run( 293 | "folder-match-with-fex with option on Windows: [{ '*.test.js': '*/__tests__/', '.test.ts': '*/__tests__/' }]", 294 | rule, 295 | { 296 | valid: [], 297 | 298 | invalid: [ 299 | { 300 | code: "var foo = 'bar';", 301 | filename: 'bar\\__tests__\\foo.test.js', 302 | options: [{ '*.test.js': '*/__tests__/', '.test.ts': '*/__tests__/' }], 303 | errors: [ 304 | { 305 | message: 306 | 'There is an invalid pattern ".test.ts", please double-check it and try again', 307 | column: 1, 308 | line: 1, 309 | }, 310 | ], 311 | }, 312 | ], 313 | } 314 | ); 315 | 316 | ruleTester.run('folder-match-with-fex with option on Windows: []', rule, { 317 | valid: [], 318 | 319 | invalid: [ 320 | { 321 | code: "var foo = 'bar';", 322 | filename: 'bar\\__tests__\\foo.test.js', 323 | options: [], 324 | errors: [ 325 | { 326 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 327 | column: 1, 328 | line: 1, 329 | }, 330 | ], 331 | }, 332 | ], 333 | }); 334 | 335 | ruleTester.run( 336 | "folder-match-with-fex with option on Windows: [{ '*.test.js': '*/__tests__/' }, { errorMessage: 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details' }]", 337 | rule, 338 | { 339 | valid: [ 340 | { 341 | code: "var foo = 'bar';", 342 | filename: 'bar\\__tests__\\foo.test.js', 343 | options: [ 344 | { '*.test.js': '*/__tests__/' }, 345 | { 346 | errorMessage: 347 | 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 348 | }, 349 | ], 350 | }, 351 | ], 352 | 353 | invalid: [ 354 | { 355 | code: "var foo = 'bar';", 356 | filename: 'bar\\_tests_\\foo.test.js', 357 | options: [ 358 | { '*.test.js': '*/__tests__/' }, 359 | { 360 | errorMessage: 361 | 'The folder of the file {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 362 | }, 363 | ], 364 | errors: [ 365 | { 366 | message: 367 | 'The folder of the file foo.test.js does not match the */__tests__/ pattern, see contribute.md for details', 368 | column: 1, 369 | line: 1, 370 | }, 371 | ], 372 | }, 373 | ], 374 | } 375 | ); 376 | -------------------------------------------------------------------------------- /tests/lib/rules/folder-naming-convention.posix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should follow the folder naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { posix } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/folder-naming-convention.js', 12 | {}, 13 | { 14 | path: posix, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "folder-naming-convention with option: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 'src/components/DisplayLabel/__tests__/displayLabel.test.js', 27 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 28 | }, 29 | ], 30 | 31 | invalid: [ 32 | { 33 | code: "var foo = 'bar';", 34 | filename: 'src/Components/DisplayLabel/__tests__/displayLabel.test.js', 35 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 36 | errors: [ 37 | { 38 | message: 39 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 40 | column: 1, 41 | line: 1, 42 | }, 43 | ], 44 | }, 45 | { 46 | code: "var foo = 'bar';", 47 | filename: 'src/components/displayLabel/__tests__/displayLabel.test.js', 48 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 49 | errors: [ 50 | { 51 | message: 52 | 'The folder "displayLabel" does not match the "PASCAL_CASE" pattern', 53 | column: 1, 54 | line: 1, 55 | }, 56 | ], 57 | }, 58 | ], 59 | } 60 | ); 61 | 62 | ruleTester.run( 63 | "folder-naming-convention with option: [{ 'src/**/': 'CAMEL_CASE' }]", 64 | rule, 65 | { 66 | valid: [ 67 | { 68 | code: "var foo = 'bar';", 69 | filename: 'src/components/displayLabel/displayLabel.js', 70 | options: [{ 'src/**/': 'CAMEL_CASE' }], 71 | }, 72 | ], 73 | 74 | invalid: [ 75 | { 76 | code: "var foo = 'bar';", 77 | filename: 'src/Components/DisplayLabel/displayLabel.js', 78 | options: [{ 'src/**/': 'CAMEL_CASE' }], 79 | errors: [ 80 | { 81 | message: 82 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 83 | column: 1, 84 | line: 1, 85 | }, 86 | ], 87 | }, 88 | { 89 | code: "var foo = 'bar';", 90 | filename: 'src/components/DisplayLabel/displayLabel.js', 91 | options: [{ 'src/**/': 'CAMEL_CASE' }], 92 | errors: [ 93 | { 94 | message: 95 | 'The folder "DisplayLabel" does not match the "CAMEL_CASE" pattern', 96 | column: 1, 97 | line: 1, 98 | }, 99 | ], 100 | }, 101 | ], 102 | } 103 | ); 104 | 105 | ruleTester.run( 106 | "folder-naming-convention with option: [{ 'src/!(pages)/**': 'CAMEL_CASE' }]", 107 | rule, 108 | { 109 | valid: [ 110 | { 111 | code: "var foo = 'bar';", 112 | filename: 'src/components/displayLabel/displayLabel.js', 113 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 114 | }, 115 | { 116 | code: "var foo = 'bar';", 117 | filename: 'src/components/visual/interactiveButton/index.js', 118 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 119 | }, 120 | { 121 | code: "var foo = 'bar';", 122 | filename: 'src/pages/home/index.js', 123 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 124 | }, 125 | { 126 | code: "var foo = 'bar';", 127 | filename: 'src/pages/Home/index.js', 128 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 129 | }, 130 | ], 131 | 132 | invalid: [ 133 | { 134 | code: "var foo = 'bar';", 135 | filename: 'src/Components/DisplayLabel/displayLabel.js', 136 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 137 | errors: [ 138 | { 139 | message: 140 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 141 | column: 1, 142 | line: 1, 143 | }, 144 | ], 145 | }, 146 | { 147 | code: "var foo = 'bar';", 148 | filename: 'src/components/DisplayLabel/displayLabel.js', 149 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 150 | errors: [ 151 | { 152 | message: 153 | 'The folder "DisplayLabel" does not match the "CAMEL_CASE" pattern', 154 | column: 1, 155 | line: 1, 156 | }, 157 | ], 158 | }, 159 | { 160 | code: "var foo = 'bar';", 161 | filename: 'src/components/visual/interactive-button/index.js', 162 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 163 | errors: [ 164 | { 165 | message: 166 | 'The folder "interactive-button" does not match the "CAMEL_CASE" pattern', 167 | column: 1, 168 | line: 1, 169 | }, 170 | ], 171 | }, 172 | { 173 | code: "var foo = 'bar';", 174 | filename: 'src/Pages/home/index.js', 175 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 176 | errors: [ 177 | { 178 | message: 179 | 'The folder "Pages" does not match the "CAMEL_CASE" pattern', 180 | column: 1, 181 | line: 1, 182 | }, 183 | ], 184 | }, 185 | ], 186 | } 187 | ); 188 | 189 | ruleTester.run( 190 | "folder-naming-convention with option: [{ 'components/*/': '__+([a-z])' }]", 191 | rule, 192 | { 193 | valid: [ 194 | { 195 | code: "var foo = 'bar';", 196 | filename: 'src/components/__displaylabel/index.js', 197 | options: [{ 'components/*/': '__+([a-z])' }], 198 | }, 199 | ], 200 | 201 | invalid: [ 202 | { 203 | code: "var foo = 'bar';", 204 | filename: 'src/components/_displayLabel/index.js', 205 | options: [{ 'components/*/': '__+([a-z])' }], 206 | errors: [ 207 | { 208 | message: 209 | 'The folder "_displayLabel" does not match the "__+([a-z])" pattern', 210 | column: 1, 211 | line: 1, 212 | }, 213 | ], 214 | }, 215 | { 216 | code: "var foo = 'bar';", 217 | filename: 'src/components/__displayLabel/index.js', 218 | options: [{ 'components/*/': '__+([a-z])' }], 219 | errors: [ 220 | { 221 | message: 222 | 'The folder "__displayLabel" does not match the "__+([a-z])" pattern', 223 | column: 1, 224 | line: 1, 225 | }, 226 | ], 227 | }, 228 | ], 229 | } 230 | ); 231 | 232 | ruleTester.run( 233 | 'folder-naming-convention with folder that has not been set', 234 | rule, 235 | { 236 | valid: [ 237 | { 238 | code: "var foo = 'bar';", 239 | filename: 'scripts/build.js', 240 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 241 | }, 242 | ], 243 | 244 | invalid: [], 245 | } 246 | ); 247 | 248 | ruleTester.run( 249 | "folder-naming-convention with option: [{ '*/__tests__/': 'FOO', 'src/*/': 'CAMEL_CASE' }]", 250 | rule, 251 | { 252 | valid: [], 253 | 254 | invalid: [ 255 | { 256 | code: "var foo = 'bar';", 257 | filename: 'src/utils/calculatePrice.js', 258 | options: [{ '*/__tests__/': 'FOO', 'src/*/': 'CAMEL_CASE' }], 259 | errors: [ 260 | { 261 | message: 262 | 'There is an invalid pattern "FOO", please double-check it and try again', 263 | column: 1, 264 | line: 1, 265 | }, 266 | ], 267 | }, 268 | ], 269 | } 270 | ); 271 | 272 | ruleTester.run( 273 | "filename-naming-convention with option: [{ '*/__tests__/': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }]", 274 | rule, 275 | { 276 | valid: [], 277 | 278 | invalid: [ 279 | { 280 | code: "var foo = 'bar';", 281 | filename: 'src/utils/calculatePrice.js', 282 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }], 283 | errors: [ 284 | { 285 | message: 286 | 'There is an invalid pattern "src/", please double-check it and try again', 287 | column: 1, 288 | line: 1, 289 | }, 290 | ], 291 | }, 292 | ], 293 | } 294 | ); 295 | 296 | ruleTester.run('filename-naming-convention with option: []', rule, { 297 | valid: [], 298 | 299 | invalid: [ 300 | { 301 | code: "var foo = 'bar';", 302 | filename: 'src/utils/calculatePrice.js', 303 | options: [], 304 | errors: [ 305 | { 306 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 307 | column: 1, 308 | line: 1, 309 | }, 310 | ], 311 | }, 312 | ], 313 | }); 314 | 315 | ruleTester.run( 316 | "filename-naming-convention with option: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }]", 317 | rule, 318 | { 319 | valid: [], 320 | 321 | invalid: [ 322 | { 323 | code: "var foo = 'bar';", 324 | filename: 'src/utils/calculatePrice.js', 325 | options: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }], 326 | errors: [ 327 | { 328 | message: 329 | 'There is an invalid pattern "NEXT_JS_PAGE_ROUTER_FILENAME_CASE", please double-check it and try again', 330 | column: 1, 331 | line: 1, 332 | }, 333 | ], 334 | }, 335 | ], 336 | } 337 | ); 338 | 339 | ruleTester.run( 340 | 'filename-naming-convention with option: [{ "*": "KEBAB_CASE"}]', 341 | rule, 342 | { 343 | valid: [ 344 | { 345 | code: "var foo = 'bar';", 346 | filename: 'src/Login/Utils/validationUtils.js', 347 | options: [{ '*': 'KEBAB_CASE' }], 348 | }, 349 | { 350 | code: "var foo = 'bar';", 351 | filename: 'src/login/utils/validation.js', 352 | options: [{ '*': 'KEBAB_CASE' }], 353 | }, 354 | { 355 | code: "var foo = 'bar';", 356 | filename: 'src/Index.js', 357 | options: [{ '*': 'KEBAB_CASE' }], 358 | }, 359 | { 360 | code: "var foo = 'bar';", 361 | filename: 'main.js', 362 | options: [{ '*': 'KEBAB_CASE' }], 363 | }, 364 | ], 365 | 366 | invalid: [ 367 | { 368 | code: "var foo = 'bar';", 369 | filename: 'SRC/login/utils/validation.js', 370 | options: [{ '*': 'KEBAB_CASE' }], 371 | errors: [ 372 | { 373 | message: 'The folder "SRC" does not match the "KEBAB_CASE" pattern', 374 | column: 1, 375 | line: 1, 376 | }, 377 | ], 378 | }, 379 | { 380 | code: "var foo = 'bar';", 381 | filename: 'Src/index.js', 382 | options: [{ '*': 'KEBAB_CASE' }], 383 | errors: [ 384 | { 385 | message: 'The folder "Src" does not match the "KEBAB_CASE" pattern', 386 | column: 1, 387 | line: 1, 388 | }, 389 | ], 390 | }, 391 | ], 392 | } 393 | ); 394 | 395 | ruleTester.run( 396 | "folder-naming-convention with option: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }]", 397 | rule, 398 | { 399 | valid: [ 400 | { 401 | code: "var foo = 'bar';", 402 | filename: 'src/app/page.ts', 403 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 404 | }, 405 | { 406 | code: "var foo = 'bar';", 407 | filename: 'src/app/example-route/page.ts', 408 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 409 | }, 410 | { 411 | code: "var foo = 'bar';", 412 | filename: 'src/app/users/[userId]/page.ts', 413 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 414 | }, 415 | { 416 | code: "var foo = 'bar';", 417 | filename: 'src/app/[...auth]/route.ts', 418 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 419 | }, 420 | { 421 | code: "var foo = 'bar';", 422 | filename: 'src/app/shop/[[...shopId]]/page.ts', 423 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 424 | }, 425 | { 426 | code: "var foo = 'bar';", 427 | filename: 'src/app/@auth/page.ts', 428 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 429 | }, 430 | { 431 | code: "var foo = 'bar';", 432 | filename: 'src/app/(marketing)/page.ts', 433 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 434 | }, 435 | { 436 | code: "var foo = 'bar';", 437 | filename: 'src/app/_components/page.ts', 438 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 439 | }, 440 | { 441 | code: "var foo = 'bar';", 442 | filename: 'src/app/rss.xml/route.ts', 443 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 444 | }, 445 | ], 446 | 447 | invalid: [ 448 | { 449 | code: "var foo = 'bar';", 450 | filename: 'src/app/exampleRoute/page.ts', 451 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 452 | errors: [ 453 | { 454 | message: 455 | 'The folder "exampleRoute" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 456 | column: 1, 457 | line: 1, 458 | }, 459 | ], 460 | }, 461 | { 462 | code: "var foo = 'bar';", 463 | filename: 'src/app/users/[userId/page.ts', 464 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 465 | errors: [ 466 | { 467 | message: 468 | 'The folder "[userId" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 469 | column: 1, 470 | line: 1, 471 | }, 472 | ], 473 | }, 474 | { 475 | code: "var foo = 'bar';", 476 | filename: 'src/app/users/userId]/page.ts', 477 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 478 | errors: [ 479 | { 480 | message: 481 | 'The folder "userId]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 482 | column: 1, 483 | line: 1, 484 | }, 485 | ], 486 | }, 487 | { 488 | code: "var foo = 'bar';", 489 | filename: 'src/app/[..auth]/route.ts', 490 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 491 | errors: [ 492 | { 493 | message: 494 | 'The folder "[..auth]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 495 | column: 1, 496 | line: 1, 497 | }, 498 | ], 499 | }, 500 | { 501 | code: "var foo = 'bar';", 502 | filename: 'src/app/[...auth/route.ts', 503 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 504 | errors: [ 505 | { 506 | message: 507 | 'The folder "[...auth" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 508 | column: 1, 509 | line: 1, 510 | }, 511 | ], 512 | }, 513 | { 514 | code: "var foo = 'bar';", 515 | filename: 'src/app/...auth]/route.ts', 516 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 517 | errors: [ 518 | { 519 | message: 520 | 'The folder "...auth]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 521 | column: 1, 522 | line: 1, 523 | }, 524 | ], 525 | }, 526 | { 527 | code: "var foo = 'bar';", 528 | filename: 'src/app/shop/[[...shopId]/page.ts', 529 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 530 | errors: [ 531 | { 532 | message: 533 | 'The folder "[[...shopId]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 534 | column: 1, 535 | line: 1, 536 | }, 537 | ], 538 | }, 539 | { 540 | code: "var foo = 'bar';", 541 | filename: 'src/app/shop/[...shopId]]/page.ts', 542 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 543 | errors: [ 544 | { 545 | message: 546 | 'The folder "[...shopId]]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 547 | column: 1, 548 | line: 1, 549 | }, 550 | ], 551 | }, 552 | { 553 | code: "var foo = 'bar';", 554 | filename: 'src/app/shop/[[..shopId]]/page.ts', 555 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 556 | errors: [ 557 | { 558 | message: 559 | 'The folder "[[..shopId]]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 560 | column: 1, 561 | line: 1, 562 | }, 563 | ], 564 | }, 565 | { 566 | code: "var foo = 'bar';", 567 | filename: 'src/app/@authMarker/page.ts', 568 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 569 | errors: [ 570 | { 571 | message: 572 | 'The folder "@authMarker" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 573 | column: 1, 574 | line: 1, 575 | }, 576 | ], 577 | }, 578 | { 579 | code: "var foo = 'bar';", 580 | filename: 'src/app/(marketingSpeak)/page.ts', 581 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 582 | errors: [ 583 | { 584 | message: 585 | 'The folder "(marketingSpeak)" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 586 | column: 1, 587 | line: 1, 588 | }, 589 | ], 590 | }, 591 | { 592 | code: "var foo = 'bar';", 593 | filename: 'src/app/rss.xml.xl/route.ts', 594 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 595 | errors: [ 596 | { 597 | message: 598 | 'The folder "rss.xml.xl" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 599 | column: 1, 600 | line: 1, 601 | }, 602 | ], 603 | }, 604 | { 605 | code: "var foo = 'bar';", 606 | filename: 'src/app/Rss.xml/route.ts', 607 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 608 | errors: [ 609 | { 610 | message: 611 | 'The folder "Rss.xml" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 612 | column: 1, 613 | line: 1, 614 | }, 615 | ], 616 | }, 617 | ], 618 | } 619 | ); 620 | 621 | ruleTester.run( 622 | "folder-naming-convention with option: [{ 'src/**/': 'CAMEL_CASE' }, { errorMessage: 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details' }]", 623 | rule, 624 | { 625 | valid: [ 626 | { 627 | code: "var foo = 'bar';", 628 | filename: 'src/components/displayLabel/displayLabel.js', 629 | options: [ 630 | { 'src/**/': 'CAMEL_CASE' }, 631 | { 632 | errorMessage: 633 | 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 634 | }, 635 | ], 636 | }, 637 | ], 638 | 639 | invalid: [ 640 | { 641 | code: "var foo = 'bar';", 642 | filename: 'src/components/DisplayLabel/displayLabel.js', 643 | options: [ 644 | { 'src/**/': 'CAMEL_CASE' }, 645 | { 646 | errorMessage: 647 | 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 648 | }, 649 | ], 650 | errors: [ 651 | { 652 | message: 653 | 'The folder DisplayLabel does not match the CAMEL_CASE pattern, see contribute.md for details', 654 | column: 1, 655 | line: 1, 656 | }, 657 | ], 658 | }, 659 | ], 660 | } 661 | ); 662 | 663 | ruleTester.run( 664 | "folder-naming-convention with option: [{ 'mocks/**/': 'KEBAB_CASE' }, { ignoreWords: ['skip_word_a', 'skip_word_b'] }]", 665 | rule, 666 | { 667 | valid: [ 668 | { 669 | code: "var foo = 'bar';", 670 | filename: 'mocks/skip_word_a/app-mock.ts', 671 | options: [ 672 | { 'mocks/**/': 'KEBAB_CASE' }, 673 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 674 | ], 675 | }, 676 | { 677 | code: "var foo = 'bar';", 678 | filename: 'mocks/skip_word_b/another-mock.ts', 679 | options: [ 680 | { 'mocks/**/': 'KEBAB_CASE' }, 681 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 682 | ], 683 | }, 684 | { 685 | code: "var foo = 'bar';", 686 | filename: 'mocks/valid-kebab-case/mock.ts', 687 | options: [ 688 | { 'mocks/**/': 'KEBAB_CASE' }, 689 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 690 | ], 691 | }, 692 | ], 693 | 694 | invalid: [ 695 | { 696 | code: "var foo = 'bar';", 697 | filename: 'mocks/InvalidCamelCase/mock.ts', 698 | options: [ 699 | { 'mocks/**/': 'KEBAB_CASE' }, 700 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 701 | ], 702 | errors: [ 703 | { 704 | message: 705 | 'The folder "InvalidCamelCase" does not match the "KEBAB_CASE" pattern', 706 | column: 1, 707 | line: 1, 708 | }, 709 | ], 710 | }, 711 | ], 712 | } 713 | ); 714 | -------------------------------------------------------------------------------- /tests/lib/rules/folder-naming-convention.windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file The folder should follow the folder naming convention 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { win32 } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/folder-naming-convention.js', 12 | {}, 13 | { 14 | path: win32, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run( 20 | "folder-naming-convention with option on Windows: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }]", 21 | rule, 22 | { 23 | valid: [ 24 | { 25 | code: "var foo = 'bar';", 26 | filename: 27 | 'src\\components\\DisplayLabel\\__tests__\\displayLabel.test.js', 28 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 29 | }, 30 | ], 31 | 32 | invalid: [ 33 | { 34 | code: "var foo = 'bar';", 35 | filename: 36 | 'src\\Components\\DisplayLabel\\__tests__\\displayLabel.test.js', 37 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 38 | errors: [ 39 | { 40 | message: 41 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 42 | column: 1, 43 | line: 1, 44 | }, 45 | ], 46 | }, 47 | { 48 | code: "var foo = 'bar';", 49 | filename: 50 | 'src\\components\\displayLabel\\__tests__\\displayLabel.test.js', 51 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 52 | errors: [ 53 | { 54 | message: 55 | 'The folder "displayLabel" does not match the "PASCAL_CASE" pattern', 56 | column: 1, 57 | line: 1, 58 | }, 59 | ], 60 | }, 61 | ], 62 | } 63 | ); 64 | 65 | ruleTester.run( 66 | "folder-naming-convention with option on Windows: [{ 'src/**/': 'CAMEL_CASE' }]", 67 | rule, 68 | { 69 | valid: [ 70 | { 71 | code: "var foo = 'bar';", 72 | filename: 'src\\components\\displayLabel\\displayLabel.js', 73 | options: [{ 'src/**/': 'CAMEL_CASE' }], 74 | }, 75 | ], 76 | 77 | invalid: [ 78 | { 79 | code: "var foo = 'bar';", 80 | filename: 'src\\Components\\DisplayLabel\\displayLabel.js', 81 | options: [{ 'src/**/': 'CAMEL_CASE' }], 82 | errors: [ 83 | { 84 | message: 85 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 86 | column: 1, 87 | line: 1, 88 | }, 89 | ], 90 | }, 91 | { 92 | code: "var foo = 'bar';", 93 | filename: 'src\\components\\DisplayLabel\\displayLabel.js', 94 | options: [{ 'src/**/': 'CAMEL_CASE' }], 95 | errors: [ 96 | { 97 | message: 98 | 'The folder "DisplayLabel" does not match the "CAMEL_CASE" pattern', 99 | column: 1, 100 | line: 1, 101 | }, 102 | ], 103 | }, 104 | ], 105 | } 106 | ); 107 | 108 | ruleTester.run( 109 | "folder-naming-convention with option on Windows: [{ 'src/!(pages)/**': 'CAMEL_CASE' }]", 110 | rule, 111 | { 112 | valid: [ 113 | { 114 | code: "var foo = 'bar';", 115 | filename: 'src\\components\\displayLabel\\displayLabel.js', 116 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 117 | }, 118 | { 119 | code: "var foo = 'bar';", 120 | filename: 'src\\components\\visual\\interactiveButton\\index.js', 121 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 122 | }, 123 | { 124 | code: "var foo = 'bar';", 125 | filename: 'src\\pages\\home\\index.js', 126 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 127 | }, 128 | { 129 | code: "var foo = 'bar';", 130 | filename: 'src\\pages\\Home\\index.js', 131 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 132 | }, 133 | ], 134 | 135 | invalid: [ 136 | { 137 | code: "var foo = 'bar';", 138 | filename: 'src\\Components\\DisplayLabel\\displayLabel.js', 139 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 140 | errors: [ 141 | { 142 | message: 143 | 'The folder "Components" does not match the "CAMEL_CASE" pattern', 144 | column: 1, 145 | line: 1, 146 | }, 147 | ], 148 | }, 149 | { 150 | code: "var foo = 'bar';", 151 | filename: 'src\\components\\DisplayLabel\\displayLabel.js', 152 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 153 | errors: [ 154 | { 155 | message: 156 | 'The folder "DisplayLabel" does not match the "CAMEL_CASE" pattern', 157 | column: 1, 158 | line: 1, 159 | }, 160 | ], 161 | }, 162 | { 163 | code: "var foo = 'bar';", 164 | filename: 'src\\components\\visual\\interactive-button\\index.js', 165 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 166 | errors: [ 167 | { 168 | message: 169 | 'The folder "interactive-button" does not match the "CAMEL_CASE" pattern', 170 | column: 1, 171 | line: 1, 172 | }, 173 | ], 174 | }, 175 | { 176 | code: "var foo = 'bar';", 177 | filename: 'src\\Pages\\home\\index.js', 178 | options: [{ 'src/!(pages)/**': 'CAMEL_CASE' }], 179 | errors: [ 180 | { 181 | message: 182 | 'The folder "Pages" does not match the "CAMEL_CASE" pattern', 183 | column: 1, 184 | line: 1, 185 | }, 186 | ], 187 | }, 188 | ], 189 | } 190 | ); 191 | 192 | ruleTester.run( 193 | "folder-naming-convention with option on Windows: [{ 'components/*/': '__+([a-z])' }]", 194 | rule, 195 | { 196 | valid: [ 197 | { 198 | code: "var foo = 'bar';", 199 | filename: 'src\\components\\__displaylabel\\index.js', 200 | options: [{ 'components/*/': '__+([a-z])' }], 201 | }, 202 | ], 203 | 204 | invalid: [ 205 | { 206 | code: "var foo = 'bar';", 207 | filename: 'src\\components\\_displayLabel\\index.js', 208 | options: [{ 'components/*/': '__+([a-z])' }], 209 | errors: [ 210 | { 211 | message: 212 | 'The folder "_displayLabel" does not match the "__+([a-z])" pattern', 213 | column: 1, 214 | line: 1, 215 | }, 216 | ], 217 | }, 218 | { 219 | code: "var foo = 'bar';", 220 | filename: 'src\\components\\__displayLabel\\index.js', 221 | options: [{ 'components/*/': '__+([a-z])' }], 222 | errors: [ 223 | { 224 | message: 225 | 'The folder "__displayLabel" does not match the "__+([a-z])" pattern', 226 | column: 1, 227 | line: 1, 228 | }, 229 | ], 230 | }, 231 | ], 232 | } 233 | ); 234 | 235 | ruleTester.run( 236 | 'folder-naming-convention with folder that has not been set on Windows', 237 | rule, 238 | { 239 | valid: [ 240 | { 241 | code: "var foo = 'bar';", 242 | filename: 'scripts\\build.js', 243 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/*/': 'CAMEL_CASE' }], 244 | }, 245 | ], 246 | 247 | invalid: [], 248 | } 249 | ); 250 | 251 | ruleTester.run( 252 | "folder-naming-convention with option on Windows: [{ '*/__tests__/': 'FOO', 'src/*/': 'CAMEL_CASE' }]", 253 | rule, 254 | { 255 | valid: [], 256 | 257 | invalid: [ 258 | { 259 | code: "var foo = 'bar';", 260 | filename: 'src\\utils\\calculatePrice.js', 261 | options: [{ '*/__tests__/': 'FOO', 'src/*/': 'CAMEL_CASE' }], 262 | errors: [ 263 | { 264 | message: 265 | 'There is an invalid pattern "FOO", please double-check it and try again', 266 | column: 1, 267 | line: 1, 268 | }, 269 | ], 270 | }, 271 | ], 272 | } 273 | ); 274 | 275 | ruleTester.run( 276 | "filename-naming-convention with option on Windows: [{ '*/__tests__/': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }]", 277 | rule, 278 | { 279 | valid: [], 280 | 281 | invalid: [ 282 | { 283 | code: "var foo = 'bar';", 284 | filename: 'src\\utils\\calculatePrice.js', 285 | options: [{ '*/__tests__/': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }], 286 | errors: [ 287 | { 288 | message: 289 | 'There is an invalid pattern "src/", please double-check it and try again', 290 | column: 1, 291 | line: 1, 292 | }, 293 | ], 294 | }, 295 | ], 296 | } 297 | ); 298 | 299 | ruleTester.run('filename-naming-convention with option on Windows: []', rule, { 300 | valid: [], 301 | 302 | invalid: [ 303 | { 304 | code: "var foo = 'bar';", 305 | filename: 'src\\utils\\calculatePrice.js', 306 | options: [], 307 | errors: [ 308 | { 309 | message: `The naming pattern object "undefined" does not appear to be an Object type, please double-check it and try again`, 310 | column: 1, 311 | line: 1, 312 | }, 313 | ], 314 | }, 315 | ], 316 | }); 317 | 318 | ruleTester.run( 319 | "filename-naming-convention with option on Windows: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }]", 320 | rule, 321 | { 322 | valid: [], 323 | 324 | invalid: [ 325 | { 326 | code: "var foo = 'bar';", 327 | filename: 'src\\utils\\calculatePrice.js', 328 | options: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }], 329 | errors: [ 330 | { 331 | message: 332 | 'There is an invalid pattern "NEXT_JS_PAGE_ROUTER_FILENAME_CASE", please double-check it and try again', 333 | column: 1, 334 | line: 1, 335 | }, 336 | ], 337 | }, 338 | ], 339 | } 340 | ); 341 | 342 | ruleTester.run( 343 | 'filename-naming-convention with option on Windows: [{ "*": "KEBAB_CASE"}]', 344 | rule, 345 | { 346 | valid: [ 347 | { 348 | code: "var foo = 'bar';", 349 | filename: 'src\\Login\\Utils\\validationUtils.js', 350 | options: [{ '*': 'KEBAB_CASE' }], 351 | }, 352 | { 353 | code: "var foo = 'bar';", 354 | filename: 'src\\login\\utils\\validation.js', 355 | options: [{ '*': 'KEBAB_CASE' }], 356 | }, 357 | { 358 | code: "var foo = 'bar';", 359 | filename: 'src\\Index.js', 360 | options: [{ '*': 'KEBAB_CASE' }], 361 | }, 362 | { 363 | code: "var foo = 'bar';", 364 | filename: 'main.js', 365 | options: [{ '*': 'KEBAB_CASE' }], 366 | }, 367 | ], 368 | 369 | invalid: [ 370 | { 371 | code: "var foo = 'bar';", 372 | filename: 'SRC\\login\\utils\\validation.js', 373 | options: [{ '*': 'KEBAB_CASE' }], 374 | errors: [ 375 | { 376 | message: 'The folder "SRC" does not match the "KEBAB_CASE" pattern', 377 | column: 1, 378 | line: 1, 379 | }, 380 | ], 381 | }, 382 | { 383 | code: "var foo = 'bar';", 384 | filename: 'Src\\index.js', 385 | options: [{ '*': 'KEBAB_CASE' }], 386 | errors: [ 387 | { 388 | message: 'The folder "Src" does not match the "KEBAB_CASE" pattern', 389 | column: 1, 390 | line: 1, 391 | }, 392 | ], 393 | }, 394 | ], 395 | } 396 | ); 397 | 398 | ruleTester.run( 399 | "folder-naming-convention with option on Windows: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }]", 400 | rule, 401 | { 402 | valid: [ 403 | { 404 | code: "var foo = 'bar';", 405 | filename: 'src\\app\\page.ts', 406 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 407 | }, 408 | { 409 | code: "var foo = 'bar';", 410 | filename: 'src\\app\\example-route\\page.ts', 411 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 412 | }, 413 | { 414 | code: "var foo = 'bar';", 415 | filename: 'src\\app\\users\\[userId]\\page.ts', 416 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 417 | }, 418 | { 419 | code: "var foo = 'bar';", 420 | filename: 'src\\app\\[...auth]\\route.ts', 421 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 422 | }, 423 | { 424 | code: "var foo = 'bar';", 425 | filename: 'src\\app\\shop\\[[...shopId]]\\page.ts', 426 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 427 | }, 428 | { 429 | code: "var foo = 'bar';", 430 | filename: 'src\\app\\@auth\\page.ts', 431 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 432 | }, 433 | { 434 | code: "var foo = 'bar';", 435 | filename: 'src\\app\\(marketing)\\page.ts', 436 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 437 | }, 438 | { 439 | code: "var foo = 'bar';", 440 | filename: 'src\\app\\_components\\page.ts', 441 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 442 | }, 443 | { 444 | code: "var foo = 'bar';", 445 | filename: 'src\\app\\rss.xml\\route.ts', 446 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 447 | }, 448 | ], 449 | 450 | invalid: [ 451 | { 452 | code: "var foo = 'bar';", 453 | filename: 'src\\app\\exampleRoute\\page.ts', 454 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 455 | errors: [ 456 | { 457 | message: 458 | 'The folder "exampleRoute" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 459 | column: 1, 460 | line: 1, 461 | }, 462 | ], 463 | }, 464 | { 465 | code: "var foo = 'bar';", 466 | filename: 'src\\app\\users\\[userId\\page.ts', 467 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 468 | errors: [ 469 | { 470 | message: 471 | 'The folder "[userId" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 472 | column: 1, 473 | line: 1, 474 | }, 475 | ], 476 | }, 477 | { 478 | code: "var foo = 'bar';", 479 | filename: 'src\\app\\users\\userId]\\page.ts', 480 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 481 | errors: [ 482 | { 483 | message: 484 | 'The folder "userId]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 485 | column: 1, 486 | line: 1, 487 | }, 488 | ], 489 | }, 490 | { 491 | code: "var foo = 'bar';", 492 | filename: 'src\\app\\[..auth]\\route.ts', 493 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 494 | errors: [ 495 | { 496 | message: 497 | 'The folder "[..auth]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 498 | column: 1, 499 | line: 1, 500 | }, 501 | ], 502 | }, 503 | { 504 | code: "var foo = 'bar';", 505 | filename: 'src\\app\\[...auth\\route.ts', 506 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 507 | errors: [ 508 | { 509 | message: 510 | 'The folder "[...auth" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 511 | column: 1, 512 | line: 1, 513 | }, 514 | ], 515 | }, 516 | { 517 | code: "var foo = 'bar';", 518 | filename: 'src\\app\\...auth]\\route.ts', 519 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 520 | errors: [ 521 | { 522 | message: 523 | 'The folder "...auth]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 524 | column: 1, 525 | line: 1, 526 | }, 527 | ], 528 | }, 529 | { 530 | code: "var foo = 'bar';", 531 | filename: 'src\\app\\shop\\[[...shopId]\\page.ts', 532 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 533 | errors: [ 534 | { 535 | message: 536 | 'The folder "[[...shopId]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 537 | column: 1, 538 | line: 1, 539 | }, 540 | ], 541 | }, 542 | { 543 | code: "var foo = 'bar';", 544 | filename: 'src\\app\\shop\\[...shopId]]\\page.ts', 545 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 546 | errors: [ 547 | { 548 | message: 549 | 'The folder "[...shopId]]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 550 | column: 1, 551 | line: 1, 552 | }, 553 | ], 554 | }, 555 | { 556 | code: "var foo = 'bar';", 557 | filename: 'src\\app\\shop\\[[..shopId]]\\page.ts', 558 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 559 | errors: [ 560 | { 561 | message: 562 | 'The folder "[[..shopId]]" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 563 | column: 1, 564 | line: 1, 565 | }, 566 | ], 567 | }, 568 | { 569 | code: "var foo = 'bar';", 570 | filename: 'src\\app\\@authMarker\\page.ts', 571 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 572 | errors: [ 573 | { 574 | message: 575 | 'The folder "@authMarker" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 576 | column: 1, 577 | line: 1, 578 | }, 579 | ], 580 | }, 581 | { 582 | code: "var foo = 'bar';", 583 | filename: 'src\\app\\(marketingSpeak)\\page.ts', 584 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 585 | errors: [ 586 | { 587 | message: 588 | 'The folder "(marketingSpeak)" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 589 | column: 1, 590 | line: 1, 591 | }, 592 | ], 593 | }, 594 | { 595 | code: "var foo = 'bar';", 596 | filename: 'src\\app\\rss.xml.xl\\route.ts', 597 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 598 | errors: [ 599 | { 600 | message: 601 | 'The folder "rss.xml.xl" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 602 | column: 1, 603 | line: 1, 604 | }, 605 | ], 606 | }, 607 | { 608 | code: "var foo = 'bar';", 609 | filename: 'src\\app\\Rss.xml\\route.ts', 610 | options: [{ 'src/**/': 'NEXT_JS_APP_ROUTER_CASE' }], 611 | errors: [ 612 | { 613 | message: 614 | 'The folder "Rss.xml" does not match the "NEXT_JS_APP_ROUTER_CASE" pattern', 615 | column: 1, 616 | line: 1, 617 | }, 618 | ], 619 | }, 620 | ], 621 | } 622 | ); 623 | 624 | ruleTester.run( 625 | "folder-naming-convention with option on Windows: [{ 'src/**/': 'CAMEL_CASE' }, { errorMessage: 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details' }]", 626 | rule, 627 | { 628 | valid: [ 629 | { 630 | code: "var foo = 'bar';", 631 | filename: 'src\\components\\displayLabel\\displayLabel.js', 632 | options: [ 633 | { 'src/**/': 'CAMEL_CASE' }, 634 | { 635 | errorMessage: 636 | 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 637 | }, 638 | ], 639 | }, 640 | ], 641 | 642 | invalid: [ 643 | { 644 | code: "var foo = 'bar';", 645 | filename: 'src\\components\\DisplayLabel\\displayLabel.js', 646 | options: [ 647 | { 'src/**/': 'CAMEL_CASE' }, 648 | { 649 | errorMessage: 650 | 'The folder {{ target }} does not match the {{ pattern }} pattern, see contribute.md for details', 651 | }, 652 | ], 653 | errors: [ 654 | { 655 | message: 656 | 'The folder DisplayLabel does not match the CAMEL_CASE pattern, see contribute.md for details', 657 | column: 1, 658 | line: 1, 659 | }, 660 | ], 661 | }, 662 | ], 663 | } 664 | ); 665 | 666 | ruleTester.run( 667 | "folder-naming-convention with option: [{ 'mocks/**/': 'KEBAB_CASE' }, { ignoreWords: ['skip_word_a', 'skip_word_b'] }]", 668 | rule, 669 | { 670 | valid: [ 671 | { 672 | code: "var foo = 'bar';", 673 | filename: 'mocks\\skip_word_a\\app-mock.ts', 674 | options: [ 675 | { 'mocks/**/': 'KEBAB_CASE' }, 676 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 677 | ], 678 | }, 679 | { 680 | code: "var foo = 'bar';", 681 | filename: 'mocks\\skip_word_b\\another-mock.ts', 682 | options: [ 683 | { 'mocks/**/': 'KEBAB_CASE' }, 684 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 685 | ], 686 | }, 687 | { 688 | code: "var foo = 'bar';", 689 | filename: 'mocks\\valid-kebab-case\\mock.ts', 690 | options: [ 691 | { 'mocks/**/': 'KEBAB_CASE' }, 692 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 693 | ], 694 | }, 695 | ], 696 | 697 | invalid: [ 698 | { 699 | code: "var foo = 'bar';", 700 | filename: 'mocks\\InvalidCamelCase\\mock.ts', 701 | options: [ 702 | { 'mocks/**/': 'KEBAB_CASE' }, 703 | { ignoreWords: ['skip_word_a', 'skip_word_b'] }, 704 | ], 705 | errors: [ 706 | { 707 | message: 708 | 'The folder "InvalidCamelCase" does not match the "KEBAB_CASE" pattern', 709 | column: 1, 710 | line: 1, 711 | }, 712 | ], 713 | }, 714 | ], 715 | } 716 | ); 717 | -------------------------------------------------------------------------------- /tests/lib/rules/no-index.posix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file A file cannot be named "index" 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { posix } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/no-index.js', 12 | {}, 13 | { 14 | path: posix, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run('no-index', rule, { 20 | valid: [ 21 | { 22 | code: "var foo = 'bar';", 23 | filename: 'src/components/login.jsx', 24 | }, 25 | { 26 | code: "var foo = 'bar';", 27 | filename: 'src/utils/calculatePrice.js', 28 | }, 29 | { 30 | code: "var foo = 'bar';", 31 | filename: 'src/utils/index.config.js', 32 | }, 33 | { 34 | code: "var foo = 'bar';", 35 | filename: 'index.config.ts', 36 | }, 37 | ], 38 | 39 | invalid: [ 40 | { 41 | code: "var foo = 'bar';", 42 | filename: 'src/utils/index.js', 43 | errors: [ 44 | { 45 | message: 'The filename "index" is not allowed', 46 | column: 1, 47 | line: 1, 48 | }, 49 | ], 50 | }, 51 | { 52 | code: "var foo = 'bar';", 53 | filename: 'src/utils/index.ts', 54 | errors: [ 55 | { 56 | message: 'The filename "index" is not allowed', 57 | column: 1, 58 | line: 1, 59 | }, 60 | ], 61 | }, 62 | ], 63 | }); 64 | 65 | ruleTester.run( 66 | 'no-index with option: [{ ignoreMiddleExtensions: true }]', 67 | rule, 68 | { 69 | valid: [ 70 | { 71 | code: "var foo = 'bar';", 72 | filename: 'src/components/login.jsx', 73 | options: [{ ignoreMiddleExtensions: true }], 74 | }, 75 | { 76 | code: "var foo = 'bar';", 77 | filename: 'calculatePrice.js', 78 | options: [{ ignoreMiddleExtensions: true }], 79 | }, 80 | ], 81 | 82 | invalid: [ 83 | { 84 | code: "var foo = 'bar';", 85 | filename: 'src/utils/index.js', 86 | options: [{ ignoreMiddleExtensions: true }], 87 | errors: [ 88 | { 89 | message: 'The filename "index" is not allowed', 90 | column: 1, 91 | line: 1, 92 | }, 93 | ], 94 | }, 95 | { 96 | code: "var foo = 'bar';", 97 | filename: 'index.ts', 98 | options: [{ ignoreMiddleExtensions: true }], 99 | errors: [ 100 | { 101 | message: 'The filename "index" is not allowed', 102 | column: 1, 103 | line: 1, 104 | }, 105 | ], 106 | }, 107 | { 108 | code: "var foo = 'bar';", 109 | filename: 'src/utils/index.config.js', 110 | options: [{ ignoreMiddleExtensions: true }], 111 | errors: [ 112 | { 113 | message: 'The filename "index" is not allowed', 114 | column: 1, 115 | line: 1, 116 | }, 117 | ], 118 | }, 119 | { 120 | code: "var foo = 'bar';", 121 | filename: 'index.config.ts', 122 | options: [{ ignoreMiddleExtensions: true }], 123 | errors: [ 124 | { 125 | message: 'The filename "index" is not allowed', 126 | column: 1, 127 | line: 1, 128 | }, 129 | ], 130 | }, 131 | ], 132 | } 133 | ); 134 | 135 | ruleTester.run( 136 | 'no-index with option: [{ errorMessage: "The file {{ target }} is not allowed to be named index" }]', 137 | rule, 138 | { 139 | valid: [ 140 | { 141 | code: "var foo = 'bar';", 142 | filename: 'index.config.ts', 143 | options: [ 144 | { 145 | errorMessage: 146 | 'The file {{ target }} is not allowed to be named index', 147 | }, 148 | ], 149 | }, 150 | ], 151 | 152 | invalid: [ 153 | { 154 | code: "var foo = 'bar';", 155 | filename: 'src/utils/index.js', 156 | options: [ 157 | { 158 | errorMessage: 159 | 'The file {{ target }} is not allowed to be named index', 160 | }, 161 | ], 162 | errors: [ 163 | { 164 | message: 'The file index.js is not allowed to be named index', 165 | column: 1, 166 | line: 1, 167 | }, 168 | ], 169 | }, 170 | ], 171 | } 172 | ); 173 | -------------------------------------------------------------------------------- /tests/lib/rules/no-index.windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file A file cannot be named "index" 3 | * @author Huan Luo 4 | */ 5 | 6 | import { RuleTester } from 'eslint'; 7 | import esmock from 'esmock'; 8 | import { win32 } from 'path'; 9 | 10 | const rule = await esmock( 11 | '../../../lib/rules/no-index.js', 12 | {}, 13 | { 14 | path: win32, 15 | } 16 | ); 17 | const ruleTester = new RuleTester(); 18 | 19 | ruleTester.run('no-index on Windows', rule, { 20 | valid: [ 21 | { 22 | code: "var foo = 'bar';", 23 | filename: 'C:\\Users\\Administrator\\Downloads\\wai\\src\\main.js', 24 | }, 25 | { 26 | code: "var foo = 'bar';", 27 | filename: 'src\\main.ts', 28 | }, 29 | { 30 | code: "var foo = 'bar';", 31 | filename: 'src\\utils\\index.config.js', 32 | }, 33 | { 34 | code: "var foo = 'bar';", 35 | filename: 'index.config.ts', 36 | }, 37 | ], 38 | 39 | invalid: [ 40 | { 41 | code: "var foo = 'bar';", 42 | filename: 'C:\\Users\\Administrator\\Downloads\\wai\\src\\index.js', 43 | errors: [ 44 | { 45 | message: 'The filename "index" is not allowed', 46 | column: 1, 47 | line: 1, 48 | }, 49 | ], 50 | }, 51 | { 52 | code: "var foo = 'bar';", 53 | filename: 'src\\index.ts', 54 | errors: [ 55 | { 56 | message: 'The filename "index" is not allowed', 57 | column: 1, 58 | line: 1, 59 | }, 60 | ], 61 | }, 62 | ], 63 | }); 64 | 65 | ruleTester.run( 66 | 'no-index with option on Windows: [{ ignoreMiddleExtensions: true }]', 67 | rule, 68 | { 69 | valid: [ 70 | { 71 | code: "var foo = 'bar';", 72 | filename: 'src\\components\\login.jsx', 73 | options: [{ ignoreMiddleExtensions: true }], 74 | }, 75 | { 76 | code: "var foo = 'bar';", 77 | filename: 'calculatePrice.js', 78 | options: [{ ignoreMiddleExtensions: true }], 79 | }, 80 | ], 81 | 82 | invalid: [ 83 | { 84 | code: "var foo = 'bar';", 85 | filename: 'src\\utils\\index.js', 86 | options: [{ ignoreMiddleExtensions: true }], 87 | errors: [ 88 | { 89 | message: 'The filename "index" is not allowed', 90 | column: 1, 91 | line: 1, 92 | }, 93 | ], 94 | }, 95 | { 96 | code: "var foo = 'bar';", 97 | filename: 'index.ts', 98 | options: [{ ignoreMiddleExtensions: true }], 99 | errors: [ 100 | { 101 | message: 'The filename "index" is not allowed', 102 | column: 1, 103 | line: 1, 104 | }, 105 | ], 106 | }, 107 | { 108 | code: "var foo = 'bar';", 109 | filename: 'src\\utils\\index.config.js', 110 | options: [{ ignoreMiddleExtensions: true }], 111 | errors: [ 112 | { 113 | message: 'The filename "index" is not allowed', 114 | column: 1, 115 | line: 1, 116 | }, 117 | ], 118 | }, 119 | { 120 | code: "var foo = 'bar';", 121 | filename: 'index.config.ts', 122 | options: [{ ignoreMiddleExtensions: true }], 123 | errors: [ 124 | { 125 | message: 'The filename "index" is not allowed', 126 | column: 1, 127 | line: 1, 128 | }, 129 | ], 130 | }, 131 | ], 132 | } 133 | ); 134 | 135 | ruleTester.run( 136 | 'no-index with option on Windows: [{ errorMessage: "The file {{ target }} is not allowed to be named index" }]', 137 | rule, 138 | { 139 | valid: [ 140 | { 141 | code: "var foo = 'bar';", 142 | filename: 'index.config.ts', 143 | options: [ 144 | { 145 | errorMessage: 146 | 'The file {{ target }} is not allowed to be named index', 147 | }, 148 | ], 149 | }, 150 | ], 151 | 152 | invalid: [ 153 | { 154 | code: "var foo = 'bar';", 155 | filename: 'src\\utils\\index.js', 156 | options: [ 157 | { 158 | errorMessage: 159 | 'The file {{ target }} is not allowed to be named index', 160 | }, 161 | ], 162 | errors: [ 163 | { 164 | message: 'The file index.js is not allowed to be named index', 165 | column: 1, 166 | line: 1, 167 | }, 168 | ], 169 | }, 170 | ], 171 | } 172 | ); 173 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "target": "es2022", 8 | "module": "nodenext" 9 | }, 10 | "include": ["lib", "tests"] 11 | } 12 | --------------------------------------------------------------------------------