├── .eslintrc-any.json ├── .eslintrc.json ├── .gitignore ├── .gitlab-ci.yml ├── .readme ├── developing.md ├── mixed-rules.svg └── rules.svg ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── _config.yml ├── dev_resources ├── mixed-rules.xml └── rules.xml ├── docs └── rules │ ├── invalid-regex-rule.md │ └── required-regex-rule.md ├── lib ├── index.js ├── rules │ ├── common-fields-definitions.js │ ├── invalid-regex-rule.js │ └── required-regex-rule.js └── utils │ ├── check-utils.js │ ├── create-utils.js │ ├── options-utils.js │ └── report-utils.js ├── package-lock.json ├── package.json └── tests ├── lib ├── index.test.js └── rules │ ├── .eslintrc.json │ ├── case-001-minified.js │ ├── case-002-minified.js │ ├── case-003-stack.txt │ ├── case-004-stack.txt │ ├── case-005-stack.txt │ ├── e2e-tests.js │ ├── invalid-regex-detailed-rule.e2e-test.js │ ├── invalid-regex-rule.e2e-test.js │ ├── required-regex-detailed-rule.e2e-test.js │ └── required-regex-rule.e2e-test.js └── tests.js /.eslintrc-any.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "plugin:base-style-config/common-rules" ], 3 | "plugins": [ "base-style-config" ], 4 | "parser": "any-eslint-parser" 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:base-style-config/js-rules", 4 | "plugin:base-style-config/import-rules" 5 | ], 6 | "env": { 7 | "node": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 6 11 | }, 12 | "root": true, 13 | "rules": { 14 | "global-require": "error", 15 | "init-declarations": "off", 16 | "max-params": "off", 17 | "no-console": "error", 18 | "no-invalid-this": "error", 19 | "no-mixed-requires": "error", 20 | "no-new-require": "error", 21 | "no-param-reassign": "off", 22 | "object-curly-newline": ["error", { 23 | "consistent": true 24 | } 25 | ], 26 | "quotes": [ 27 | "error", 28 | "single" 29 | ], 30 | "semi": [ 31 | "error", 32 | "never" 33 | ], 34 | "valid-jsdoc": "off" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | .gradle/ 3 | build/ 4 | node_modules/ 5 | # Files 6 | *.tgz 7 | *.log 8 | # IDEs 9 | .vscode 10 | .vscode/ 11 | .project 12 | .classpath 13 | .settings/ 14 | *.sublime-project 15 | *.sublime-workspace 16 | .idea/ 17 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:latest 2 | 3 | cache: 4 | key: "${CI_COMMIT_SHA}" 5 | untracked: true 6 | paths: 7 | - node_modules/ 8 | 9 | stages: 10 | - inception 11 | - assess 12 | - test 13 | - assemble 14 | 15 | create-cache: 16 | stage: inception 17 | script: 18 | - npm install 19 | cache: 20 | key: "${CI_COMMIT_SHA}" 21 | policy: push 22 | untracked: true 23 | paths: 24 | - node_modules/ 25 | 26 | assess: 27 | stage: assess 28 | script: 29 | - npm run lint 30 | cache: 31 | key: "${CI_COMMIT_SHA}" 32 | policy: pull 33 | untracked: true 34 | paths: 35 | - node_modules/ 36 | 37 | test: 38 | stage: test 39 | script: 40 | - npm test 41 | cache: 42 | key: "${CI_COMMIT_SHA}" 43 | policy: pull 44 | untracked: true 45 | paths: 46 | - node_modules/ 47 | coverage: '/Branches.*?(\d+(?:\.\d+)?)%/' 48 | artifacts: 49 | when: always 50 | paths: 51 | - build/coverage/lcov-report/ 52 | reports: 53 | coverage_report: 54 | coverage_format: cobertura 55 | path: build/coverage/cobertura-coverage.xml 56 | 57 | pages: 58 | stage: assemble 59 | dependencies: 60 | - test 61 | script: 62 | - mkdir -p to_public 63 | - touch to_public/index.html 64 | - mv build/coverage/lcov-report/ to_public/coverage 65 | - mv to_public public 66 | artifacts: 67 | paths: 68 | - public 69 | only: 70 | - master 71 | -------------------------------------------------------------------------------- /.readme/developing.md: -------------------------------------------------------------------------------- 1 | # Extending/Developing 2 | 3 | ## Prerequisites 4 | 5 | * [NodeJS](https://nodejs.org/en/download)/npm [1]. 6 | * [Git](https://git-scm.com/downloads) (if you are going to clone the project). 7 | 8 | > [1] [Downloading NodeJS](https://nodejs.org/en/download) will also download and provide [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-installer-to-install-nodejs-and-npm). 9 | 10 | ## Getting it 11 | 12 | [Clone](https://help.github.com/articles/cloning-a-repository/) the project in the desired folder by executing: 13 | 14 | ```sh 15 | git clone https://github.com/gmullerb/eslint-plugin-regex 16 | ``` 17 | 18 | or 19 | 20 | ```sh 21 | git clone https://gitlab.com/gmullerb/eslint-plugin-regex 22 | ``` 23 | 24 | (you can also fork) 25 | 26 | ## Set up 27 | 28 | Run: 29 | 30 | ```sh 31 | npm install 32 | ``` 33 | 34 | It will install project dependencies, as [eslint](https://www.npmjs.com/package/eslint), [any-eslint-parser](https://www.npmjs.com/package/any-eslint-parser), [eslint-plugin-base-style-config](https://www.npmjs.com/package/eslint-plugin-base-style-config), etc. 35 | 36 | > Recommendation: Immediately after installation, run `npm run check` to be sure that initial code is "ok". 37 | 38 | ### Npm scripts 39 | 40 | [`package.json`](../package.json): 41 | 42 | * `lint.common`: checks common style of "all" files defined in [`.eslintrc-any.json`](../.eslintrc-any.json) using [any-eslint-parser](https://www.npmjs.com/package/any-eslint-parser). 43 | * `lint.source`: checks eslint style of `js` files. 44 | * `lint`: runs both lints (`lint.common` and `lint.source`). 45 | * `test.only`: runs test. 46 | * `test`: runs test with coverage report. 47 | 48 | Additionally: 49 | 50 | * `npm run check`: will execute all tasks (`lint`, `test`, etc.). 51 | * `npm run`: will list all available script/task for the project. 52 | 53 | ## Folders structure 54 | 55 | ``` 56 | /lib 57 | /rules 58 | /utils 59 | /tests 60 | /lib 61 | /rules 62 | ``` 63 | 64 | - `lib/rules`: Rules files. 65 | - `lib/utils`: Utilities files. 66 | - `tests/lib/rules`: Test files. 67 | 68 | ## Main documentation 69 | 70 | [Back](../README.md) 71 | -------------------------------------------------------------------------------- /.readme/mixed-rules.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
regex/*required*1 rule
regex/re...
pattern1
pattern1
patternN
patternN
regex/*invalid*2 rule
regex/in...
patternA
patternA
patternZ
patternZ
regex/*required*4 rule
regex/re...
pattern41
pattern41
pattern4N
pattern4N
regex/*invalid*3 rule
regex/in...
pattern3A
pattern3A
pattern3Z
pattern3Z
regex/*invalid*N rule
regex/in...
patternNA
patternNA
patternNZ
patternNZ
Text is not SVG - cannot display
4 | -------------------------------------------------------------------------------- /.readme/rules.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
regex/required rule
regex/re...
pattern1
pattern1
patternN
patternN
regex/invalid rule
regex/in...
patternA
patternA
patternZ
patternZ
Text is not SVG - cannot display
4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ESLint Plugin Regex Change Log 2 | 3 | ## 1.10.0 - August 2022 4 | 5 | * Closes #19, Allows to add additional RegEx flags. 6 | * Fixes bug `RangeError: Maximum call stack size exceeded`. 7 | * Replaces the recursive approach to a loop one. 8 | * Improves code. 9 | * Clones `$`. 10 | * Simplifies code. 11 | * Improves code performance. 12 | * Improves code coverage. 13 | * Adds JSDoc to improve Learnability and Maintainability. 14 | * Improves project configuration. 15 | * Renames `readme` folder to `.readme` in order to prevent it to be packed. 16 | * Removes `.npmignore` (It doesn't work when using `files`: https://github.com/npm/npm/issues/4479) 17 | * Updates CI configuration. 18 | * Updates Documentation. 19 | 20 | ## 1.9.1 - July 2022 21 | 22 | * Fixes duplicate entries bug that happens when adding custom regex rule with same name. 23 | * Message changed from `Error: "SomeRuleName" can not be used as eslint-plugin-regex rule name` to `Error: "SomeRuleName" already defined as eslint-plugin-regex rule name`. 24 | * Updates documentation. 25 | * Changes background color to white for images. 26 | 27 | ## 1.9.0 - April 2022 28 | 29 | * Closes #15, Allowing mixing different error level. 30 | * This bug required that the same rule can be defined with different error levels, that is not possible at the moment on eslint. Error level is not available at plugin level (as indicate by ESLint official documentation: "Keep in mind that the error level is not part of context.options, as the error level cannot be known or modified from inside a rule"). So I came up a different approach, which not only **allows to use "the same" rule with different error levels**, but also **allows Mixing custom set of regex rules**, this is useful for creating package/libraries/plugins with your own set of rules and sharing it with others. 31 | * Fixes #16, Unable to use both "ignore" and "inspect" inside "files" property. 32 | * Fixes an issue with remaining source calculation and minified files. 33 | * Removes `replacement.function === 'string'` validation, it's not necessary, eslint already validates json. 34 | * Improves project configuration. 35 | * Now uses [`any-eslint-parser`](https://www.npmjs.com/package/any-eslint-parser) 36 | * Improves documentation. 37 | * Mixing 38 | * Fixes spelling in tests. 39 | 40 | ## 1.8.0 - August 2021 41 | 42 | * Closes #11, Improves the specification of the number of the line of the found error for the final report, Thanks to Maxim-Mazurok for His collaboration. 43 | 44 | ## 1.7.0 - February 2021 45 | 46 | * Closes #6, Removes the requirement of `return` presence for replacement functions for invalid patterns. 47 | 48 | ## 1.6.0 - February 2021 49 | 50 | * Adds additional parameter `$` to replacement function for invalid patterns in order to allow smaller definitions. 51 | 52 | ## 1.5.0 - February 2021 53 | 54 | * Adds capturing groups to replacement functions for invalid patterns. 55 | 56 | ## 1.4.0 - February 2021 57 | 58 | * Adds replacements with functions for invalid patterns. 59 | 60 | ## 1.3.0 - January 2021 61 | 62 | * Adds replacements for invalid patterns. 63 | * Improves code coverage. 64 | * Improves documentation. 65 | * Improves project configuration. 66 | 67 | ## 1.2.1 - August 2020 68 | 69 | * Fixes #4 peerDependencies. 70 | * Updates README file. 71 | 72 | ## 1.2.0 - August 2020 73 | 74 | * Adds the specific line to the report for multiline regex (Big debt, Found some time, Done, Yes!). 75 | * Updates README file. 76 | 77 | ## 1.1.0 - April 2020 78 | 79 | * Adds handling for detailed patterns. 80 | * Updates README file. 81 | * Improves project configuration. 82 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gonzalo Müller Bravo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |  4 |

5 | 6 |

ESLint rules using Regular Expressions

7 | 8 | [![eslint-plugin-regex](https://badgen.net/badge/homepage/eslint-plugin-regex/blue)](https://eslint-plugin-regex.github.io/) 9 | [![eslint-plugin-regex](https://badgen.net/badge/npm%20pack/eslint-plugin-regex/blue)](https://www.npmjs.com/package/eslint-plugin-regex) 10 | [![ ](https://badgen.net/npm/v/eslint-plugin-regex)](https://www.npmjs.com/package/eslint-plugin-regex) 11 | [![ ](https://badgen.net/npm/node/eslint-plugin-regex)](https://www.npmjs.com/package/eslint-plugin-regex) 12 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE.txt) 13 | [![ ](https://gitlab.com/gmullerb/eslint-plugin-regex/badges/master/coverage.svg)](https://gmullerb.gitlab.io/eslint-plugin-regex/coverage/index.html) 14 | [![Github repo](https://badgen.net/badge/icon/github?icon=github&label)](https://github.com/gmullerb/eslint-plugin-regex) 15 | [![Gitlab repo](https://badgen.net/badge/icon/gitlab?icon=gitlab&label)](https://gitlab.com/gmullerb/eslint-plugin-regex) 16 | 17 | __________________ 18 | 19 | ## Quick Start 20 | 21 | 1 . Add dependencies: 22 | 23 | `package.json`: 24 | 25 | ```json 26 | "engines" : { 27 | "node" : ">=6.0.0" 28 | }, 29 | "devDependencies": { 30 | "eslint": ">=4.0.0", 31 | "eslint-plugin-regex": "1.10.0", 32 | ``` 33 | 34 | 2 . Configure eslint: 35 | 36 | Short configuration: 37 | 38 | `.eslintrc.json`: 39 | 40 | ```json 41 | { 42 | "plugins": [ 43 | "regex" 44 | ], 45 | "rules": { 46 | "regex/invalid": [ 47 | "error", [ 48 | "invalidRegex1", 49 | "invalidRegexN" 50 | ] 51 | ], 52 | "regex/required": [ 53 | "error", [ 54 | "requiredRegex1", 55 | "requiredRegexN" 56 | ], 57 | "ignoreFilesRegex" 58 | ] 59 | } 60 | } 61 | ``` 62 | 63 | Files will be checked for the absence of `invalidRegex1` and `invalidRegexN`, and for the presence of `requiredRegex1` and `requiredRegexN`, and files with name matching `ignoreFilesRegex` will not be checked. 64 | 65 | Detailed configuration: 66 | 67 | `.eslintrc.json`: 68 | 69 | ```json 70 | { 71 | "plugins": [ 72 | "regex" 73 | ], 74 | "rules": { 75 | "regex/invalid": [ 76 | "error", [{ 77 | "regex": "invalidRegex1", 78 | "replacement": "newValue" 79 | }, { 80 | "id": "regexIdN", 81 | "message": "errorMessageN", 82 | "regex": "invalidRegexN", 83 | "files": { 84 | "ignore": "ignoreFilesRegexN" 85 | } 86 | } 87 | ] 88 | ], 89 | "regex/required": [ 90 | "error", [{ 91 | "id": "regexId1", 92 | "regex": "requiredRegex1", 93 | "message": "errorMessage1", 94 | "files": { 95 | "inspect": "inspectFilesRegex1" 96 | } 97 | }, { 98 | "regex": "requiredRegexN", 99 | "files": { 100 | "ignore": "ignoreFilesRegexA", 101 | "inspect": "inspectFilesRegexZ" 102 | } 103 | } 104 | ] 105 | ] 106 | } 107 | } 108 | ``` 109 | 110 | Files will be checked for: 111 | 112 | * The absence of `invalidRegex1` but if found it will be replaced with `newValue`. 113 | * The absence of `invalidRegexN` only in files with name not matching `ignoreFilesRegexN`, but if found `errorMessageN` will be shown. 114 | * The presence of `requiredRegex1` only in files with name matching `inspectFilesRegex1`, but if not found `errorMessage1` will be shown. 115 | * The presence of `requiredRegexN` only in files with name matching `inspectFilesRegexA` and not matching `ignoreFilesRegexZ`. 116 | 117 | __________________ 118 | 119 | ## Goals 120 | 121 | The idea is to allow the creation of different eslint rules based on Regular Expressions in order to have some "freedom" to create quick ESLint custom rules. 122 | 123 | ## Rules 124 | 125 | Name | Fixable | Description 126 | ----------------------------------------------------- | ------- | ----------- 127 | [`regex/invalid`](docs/rules/invalid-regex-rule.md) | Yes | checks that specified patterns are not found 128 | [`regex/required`](docs/rules/required-regex-rule.md) | No | checks that specified patterns are found 129 | 130 | Each rule defines a set of patterns: 131 | 132 | ![Rules](.readme/rules.svg) 133 | 134 | ### 📏 `regex/invalid` 135 | 136 | This rule checks that specified patterns are not found in files, i.e. **Invalid patterns**. 137 | 138 | ✏ Example of **incorrect** code for this rule: 139 | 140 | ```javascript 141 | /* eslint regex/invalid: ['error', ['"']] */ 142 | 143 | const text = 'Hello "My Friend"' 144 | ``` 145 | 146 | The error message will reflect the exact location, e.g.: 147 | 148 | ```bash 149 | /path/to/some.js 150 | 34:25 error Invalid regular expression /"/gm found regex/invalid 151 | ``` 152 | 153 | ✏ Example of **correct** code for this rule: 154 | 155 | ```javascript 156 | /* eslint regex/invalid: ['error', ['"']] */ 157 | 158 | const text = 'Hello \'My Friend\'' 159 | ``` 160 | 161 | ### 📏 `regex/required` 162 | 163 | This rule looks for specific patterns that must be present in each file, i.e. **Required patterns**. 164 | 165 | ✏ Example of **incorrect** code for this rule: 166 | 167 | ```javascript 168 | /* eslint regex/required: ["error", ["^// Copyright My Friend"]] */ 169 | 170 | const text = 'Hello "My Friend"' 171 | ``` 172 | 173 | The error message will point to the beginning of the file, e.g.: 174 | 175 | ```sh 176 | /path/to/some.js 177 | 1:1 error Required regular expression /^\/\/ Copyright My Friend/gm not found in file regex/required 178 | ``` 179 | 180 | ✏ Example of **correct** code for this rule: 181 | 182 | ```javascript 183 | /* eslint regex/required: ["error", ["^// Copyright My Friend"]] */ 184 | 185 | // Copyright My Friend 186 | const text = 'Hello "My Friend"' 187 | ``` 188 | 189 | ### Options 190 | 191 | Both rule has two options: 192 | 193 | * **array** of patterns definitions to analyze. [REQUIRED] 194 | * Each pattern definition can be 'Short' or 'Detailed'. 195 | * a **string** representing the regular expression for ignoring files for all patterns. [OPTIONAL] 196 | 197 | ```json 198 | [ 199 | "error", 200 | [ 201 | "regex1", 202 | "regexN" 203 | ], 204 | "ignoreFilesRegex" 205 | ] 206 | ``` 207 | 208 | #### The *string* representing the regular expression 209 | 210 | Remember, Slashes (`/`) are not required in the string that defines the regex, 211 | 212 | e.g. To get the following regex `/^(test|spec)$/`, define: 213 | 214 | * **`"^(test|spec)$"`**, when using `.eslintrc.js` or `.eslintrc.json`. 215 | 216 | e.g. To get the following regex `/\bhttp:/`, define: 217 | 218 | * **`"\bhttp:"`**, when using `.eslintrc.js`, or 219 | * **`"\\bhttp:"`**, when using `.eslintrc.json`. (backslash needs to be double in a json file) 220 | 221 | e.g. To get the following regex `/.*test\.js/`, define: 222 | 223 | * **`".*test\.js"`**, when using `.eslintrc.js`, or 224 | * **`".*test\\.js"`**, when using `.eslintrc.json`. (backslash needs to be double in a json file) 225 | 226 | #### Short pattern definition 227 | 228 | Each pattern is specified by just a **`string`** representing the regular expression, i.e. `"regex"` 229 | 230 | ```json 231 | { 232 | "regex/invalid": [ 233 | "error", 234 | [ 235 | "invalidRegex1", 236 | "invalidRegexN" 237 | ] 238 | ], 239 | "regex/required": [ 240 | "error", 241 | [ 242 | "requiredRegex1", 243 | "requiredRegexN" 244 | ] 245 | ] 246 | } 247 | ``` 248 | 249 | #### Detailed pattern definition 250 | 251 | It is specified by an `object`, with the following fields: 252 | 253 | * `regex`: A **required** `string` for `regex/required` and `regex/invalid` representing the **Regular expression to look for**. [REQUIRED] 254 | * `flags`: A combination of flags, `i`, `s` and/or `u`, to be used by the Regular Expression. [OPTIONAL] 255 | * `replacement` for `regex/invalid` [1]: [OPTIONAL] 256 | * An optional `string` used to replace the **invalid** found pattern, or 257 | * An optional `object` that establish how the **invalid** found pattern will be replaced: 258 | * `function`: used to replace the **invalid** found pattern. 259 | * It will receive 3 parameters: `text`, `captured` and `$`, that can be used as desired. 260 | * It must return a `string` value, if not, return value will be ignored. 261 | * Its definition must be only the body of the function. 262 | * One must be defined, either the `string` or `function`. 263 | * `id`: An optional `string` representing the **Pattern Id**. [OPTIONAL] 264 | * `message`: An optional `string` specifying the **Message to be shown when an error happens** (invalid `regex` is found or required `regex` is not found). [OPTIONAL] 265 | * `files`: An optional `object` specifying which files to analyze: [OPTIONAL] 266 | * `ignore`: A `string` representing **Regular expression of the files to be ignored** when validating this specific pattern. 267 | * `inspect`: A `string` representing **Regular expression of the files to be inspected** when validating this specific pattern. 268 | 269 | ```json 270 | { 271 | "id": "regexId", 272 | "regex": "regex", 273 | "flags": "isu", 274 | "replacement": "replacementString", 275 | "message": "errorMessage", 276 | "files": { 277 | "ignore": "ignoreFilesRegex", 278 | "inspect": "inspectFilesRegex" 279 | } 280 | } 281 | ``` 282 | 283 | > * `regex` is the **only** Required field. 284 | > * When `ignore` and `inspect` are present, `ignore` takes precedence. 285 | > * Global ignore file pattern, takes precedence over `files` patterns. 286 | > 287 | > [1] In order to fix issues `eslint` must be run with `--fix` option. 288 | 289 | Using `message` is pretty useful since it will give a better understanding to the developer when an error happens: 290 | 291 | e.g. Given the following definition: 292 | 293 | ```json 294 | { 295 | "regex": "someRegex", 296 | "message": "The Useful Error MessagE" 297 | } 298 | ``` 299 | 300 | then shown error will be similar to: 301 | 302 | ```sh 303 | /path/to/some.js 304 | 1:1 error The Useful Error MessagE regex/required 305 | ``` 306 | 307 | or 308 | 309 | ```bash 310 | /path/to/some.js 311 | 34:25 error The Useful Error MessagE regex/invalid 312 | ``` 313 | 314 | instead of 315 | 316 | ```sh 317 | /path/to/some.js 318 | 1:1 error Required regular expression /someRegex/gm not found in file regex/required 319 | ``` 320 | 321 | or 322 | 323 | ```bash 324 | /path/to/some.js 325 | 34:25 error Invalid regular expression /someRegex/gm found regex/invalid 326 | ``` 327 | 328 | ##### Definition of the Function used to replace the *invalid* found pattern 329 | 330 | Definition of the function must be done as a `string` in 1 line, and the following rules apply: 331 | 332 | * It must return a `string` value, if not, return value will be ignored, i.e. it will silently fail. 333 | * Its definition must be **only the body of the function**. 334 | * For "simple" functions where the `return` is found at the beginning of the body of the function and the **exact** word *return* is not present, `return` can be omitted. 335 | * If the function has invalid Javascript code, the function will be ignored, i.e. it will silently fail. 336 | 337 | Function will receive 3 parameters, to be used as desired: 338 | 339 | * `text`: a `string` with the value of the invalid text found. 340 | * `captured`: an `array` of strings with the values of the captured groups for the regex. 341 | * `$`: an `array` of strings, with the value of the invalid text found plus the values of the captured groups for the regex, i.e. 342 | * `$[0]` = `text`: a `string` with the value of the invalid text found. 343 | * `$[1..]` = `captured`: an `array` of strings with the values of the captured groups for the regex. 344 | * `$[1]` = `captured[0]` and so on. 345 | * It allows smaller definitions. 346 | 347 | **Using parameter `text`** 348 | 349 | e.g. 350 | 351 | ```javascript 352 | function(text, captured, $) { 353 | return text.trim() 354 | } 355 | ``` 356 | 357 | `"return text.trim()"` => only the body of the function + returns a `string` value based on `text` 358 | 359 | Having the following rule in `.eslintrc.json`: 360 | 361 | ```json 362 | { 363 | "id": "regexIdN", 364 | "regex": "\\serror\\w*\\s", 365 | "replacement": { 366 | "function": "return text.trim()" 367 | } 368 | } 369 | ``` 370 | 371 | or using `$`: 372 | 373 | ```json 374 | { 375 | "id": "regexIdN", 376 | "regex": "\\serror\\w*\\s", 377 | "replacement": { 378 | "function": "return $[0].trim()" 379 | } 380 | } 381 | ``` 382 | 383 | then, given: 384 | 385 | `example.js` 386 | 387 | ```javascript 388 | const exception = " error19 " 389 | ``` 390 | 391 | when linting with fix, the result will be: 392 | 393 | ```javascript 394 | const exception = "error19" 395 | ``` 396 | 397 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 398 | 399 | ```json 400 | { 401 | "id": "regexIdN", 402 | "regex": "\\serror\\w*\\s", 403 | "replacement": { 404 | "function": "text.trim()" 405 | } 406 | } 407 | ``` 408 | 409 | or 410 | 411 | ```json 412 | { 413 | "id": "regexIdN", 414 | "regex": "\\serror\\w*\\s", 415 | "replacement": { 416 | "function": "$[0].trim()" 417 | } 418 | } 419 | ``` 420 | 421 | **Using parameter `captured`** 422 | 423 | e.g. 424 | 425 | `"return captured[0]"` => only the body of the function + returns a `string` value based on `captured` 426 | 427 | Having the following rule in `.eslintrc.json`: 428 | 429 | ```json 430 | { 431 | "id": "regexIdN", 432 | "regex": "\\serror(\\w*)\\s", 433 | "replacement": { 434 | "function": "return captured[0]" 435 | } 436 | } 437 | ``` 438 | 439 | or using `$`: 440 | 441 | ```json 442 | { 443 | "id": "regexIdN", 444 | "regex": "\\serror(\\w*)\\s", 445 | "replacement": { 446 | "function": "return $[1]" 447 | } 448 | } 449 | ``` 450 | 451 | then, given: 452 | 453 | `example.js` 454 | 455 | ```javascript 456 | const exception = " error19 " 457 | ``` 458 | 459 | when linting with fix, the result will be: 460 | 461 | ```javascript 462 | const exception = "19" 463 | ``` 464 | 465 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 466 | 467 | ```json 468 | { 469 | "id": "regexIdN", 470 | "regex": "\\serror(\\w*)\\s", 471 | "replacement": { 472 | "function": "captured[0]" 473 | } 474 | } 475 | ``` 476 | 477 | or 478 | 479 | ```json 480 | { 481 | "id": "regexIdN", 482 | "regex": "\\serror(\\w*)\\s", 483 | "replacement": { 484 | "function": "$[1]" 485 | } 486 | } 487 | ``` 488 | 489 | **Using parameters `text` and `captured`** 490 | 491 | e.g. 492 | 493 | `"return text + ' = ' + captured[0] + ' + ' + captured[1] + ' = ' + (parseInt(captured[0]) + parseInt(captured[1]))"` => only the body of the function + returns a `string` value based on `text` and `captured` 494 | 495 | Having the following rule in `.eslintrc.json`: 496 | 497 | ```json 498 | { 499 | "id": "regexIdN", 500 | "regex": "(\\d+)\\+(\\d+)", 501 | "replacement": { 502 | "function": "return text + ' = ' + captured[0] + ' + ' + captured[1] + ' = ' + (parseInt(captured[0]) + parseInt(captured[1]))" 503 | } 504 | } 505 | ``` 506 | 507 | or using `$`: 508 | 509 | ```json 510 | { 511 | "id": "regexIdN", 512 | "regex": "(\\d+)\\+(\\d+)", 513 | "replacement": { 514 | "function": "return $[0] + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 515 | } 516 | } 517 | ``` 518 | 519 | or : 520 | 521 | ```json 522 | { 523 | "id": "regexIdN", 524 | "regex": "(\\d+)\\+(\\d+)", 525 | "replacement": { 526 | "function": "return text + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 527 | } 528 | } 529 | ``` 530 | 531 | or : 532 | 533 | ```json 534 | { 535 | "id": "regexIdN", 536 | "regex": "(\\d+)\\+(\\d+)", 537 | "replacement": { 538 | "function": "return `${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`" 539 | } 540 | } 541 | ``` 542 | 543 | then, given: 544 | 545 | `example.js` 546 | 547 | ```javascript 548 | const sum = "4+5" 549 | ``` 550 | 551 | when linting with fix, the result will be: 552 | 553 | ```javascript 554 | const sum = "4+5 = 4 + 5 = 9" 555 | ``` 556 | 557 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 558 | 559 | ```json 560 | { 561 | "id": "regexIdN", 562 | "regex": "(\\d+)\\+(\\d+)", 563 | "replacement": { 564 | "function": "text + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 565 | } 566 | } 567 | ``` 568 | 569 | or : 570 | 571 | ```json 572 | { 573 | "id": "regexIdN", 574 | "regex": "(\\d+)\\+(\\d+)", 575 | "replacement": { 576 | "function": "`${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`" 577 | } 578 | } 579 | ``` 580 | 581 | **When `return` keyword is required** 582 | 583 | e.g. 584 | 585 | e.g. `const result = text === 'superb' ? 'Superb' : text; return result` => only the body of the function + returns a `string` value based on `text`. 586 | 587 | Since the `return` is not found at the beginning of the body of the function, `return` cannot be omitted, then rule definition will be as usual: 588 | 589 | ```json 590 | { 591 | "id": "regexIdN", 592 | "regex": "\\w+", 593 | "replacement": { 594 | "function": "const result = text === 'superb' ? 'Superb' : text; return result" 595 | } 596 | } 597 | ``` 598 | 599 | > Some cases may use Comma operator, e.g. `"function": "result = text === 'superb' ? 'Superb' : text, result"` 600 | 601 | e.g. `return text === 'return' ? 'Return' : text` => only the body of the function + returns a `string` value based on `text`. 602 | 603 | Since the *exact* word *return* is present, this will **required** `return`, then rule definition will be as usual: 604 | 605 | ```json 606 | { 607 | "id": "regexIdN", 608 | "regex": "\\w+", 609 | "replacement": { 610 | "function": "return text === 'return' ? 'Return' : text" 611 | } 612 | } 613 | ``` 614 | 615 | Following case does not required `return`: 616 | 617 | e.g. `return text === 'Return' ? 'RETURN' : text` => only the body of the function + returns a `string` value based on `text`. 618 | 619 | Since the **exact** word *return* is not present, this will allow the following rule definition to be: 620 | 621 | ```json 622 | { 623 | "id": "regexIdN", 624 | "regex": "\\w+", 625 | "replacement": { 626 | "function": "text === 'Return' ? 'RETURN' : text" 627 | } 628 | } 629 | ``` 630 | 631 | ###### Debugging of the Replacement Function for *invalid* found pattern 632 | 633 | * It is possible to add `console` statements to print some information in the Replacement Function. 634 | 635 | ```json 636 | { 637 | "regex": "\\serror(\\w*)\\s", 638 | "replacement": { 639 | "function": "const extract = captured[0]; console.log(extract); return extract" 640 | } 641 | } 642 | ``` 643 | 644 | ##### RegExp Flags 645 | 646 | The following flags can be add to the regex: 647 | 648 | * `i`: For case insensitive search. 649 | * `s`: To allow `.` to match newline characters. 650 | * `u`: To treat the regex as a sequence of unicode code points. 651 | 652 | To define the flags to be used, employ the field `flags` in the detailed pattern: 653 | 654 | * A combination of flags can be used, e.g. `"is"`. 655 | * Order of flags is irrelevant, e.g. `"si"`. 656 | * It's case insensitive, e.g. `"iS"`, `"Is"` and `"IS"` are the same. 657 | * Invalid flags will be reported as an error by eslint. 658 | 659 | > By default, `"gm"` is always added by the engine (since It's required). 660 | 661 | e.g. 662 | 663 | Having the following detailed pattern: 664 | 665 | ```json 666 | { 667 | "regex": "invalid", 668 | "flags": "i" 669 | } 670 | ``` 671 | 672 | `Invalid`, `inValid`, `INvalid` or `INVALID` will match. 673 | 674 | ### String to Regular expression conversion 675 | 676 | Internally, each string from the array will be converted into a Regular Expression with `global` and `multiline` options, e.g.: 677 | 678 | `"someRegex"` will be transformed into `/someRegex/gm` 679 | 680 | > Remember that backslash needs to be double in strings of a json file, e.g. To get the following regex `/\bhttp:/` define the following string `"\\bhttp:"`. 681 | 682 | ### Empty Meta characters 683 | 684 | For some special cases when using meta characters that may result in an empty match, e.g. `^`, eslint-plugin-regex will report only the first case found, and after that case is fixed, the following will be report, if present. 685 | 686 | e.g. 687 | 688 | ```json 689 | { 690 | "regex": "^(?!(?:(feature|fix|docs|config|refactor|revert|test).*[\\.:]$)|(\\*\\s\\w.*\\.$)|$)" 691 | } 692 | ``` 693 | 694 | `/path/to/some.js`: 695 | 696 | ```text 697 | config(ALL): 698 | 699 | * Use eslint-plugin-regex for commit message linting 700 | * Use eslint-plugin-regex for commit message linting 701 | ``` 702 | 703 | When linting, `eslint-plugin-regex` will only report the first case: 704 | 705 | ```bash 706 | /path/to/some.js 707 | 3:1 error Invalid regular expression /^(?!(?:(feature|fix|docs|config|refactor|revert|test).*[\\.:]$)|(\\*\\s\\w.*\\.$)|$)/gm found regex/invalid 708 | ``` 709 | 710 | 4:1 error will not be reported until 3:1 is fixed. 711 | 712 | > The issue is that having an empty match does not allow the regex engine to move forward. 713 | 714 | ### Error report 715 | 716 | The 'Short pattern definition' errors are reported with the following structure: 717 | 718 | Given `someRegex`, the following message will be shown on error: 719 | 720 | ``` 721 | Invalid regular expression /someRegex/gm found 722 | ``` 723 | 724 | or 725 | 726 | ``` 727 | Required regular expression /someRegex/gm not found in file 728 | ``` 729 | 730 | The 'Detailed pattern definition' errors are reported with the following rules: 731 | 732 | A . If `message` is present then that **exact message is reported**. 733 | B . If `id` is present then: 734 | 735 | Given `"id": "someRegexId"`, the following message will be shown on error: 736 | 737 | ``` 738 | Invalid regular expression 'someRegexId' found 739 | ``` 740 | 741 | or 742 | 743 | ``` 744 | Required regular expression 'someRegexId' not found in file 745 | ``` 746 | 747 | C . If neither `message` nor `id` is present then the 'Short pattern definition' error message is shown. 748 | 749 | > * `message` takes precedence over `id`. 750 | > * Although `id` is a quick solution (and useful when creating and testing a rule), using `message` will give more information to the team about the issue. 751 | 752 | ### Mixing 753 | 754 | #### Mixing pattern types 755 | 756 | It is possible to use both type of definitions, 'Short pattern definition' with 'Detailed pattern definition', in the array of patterns. 757 | 758 | `.eslintrc.json`: 759 | 760 | ```json 761 | { 762 | "plugins": [ 763 | "regex" 764 | ], 765 | "rules": { 766 | "regex/invalid": [ 767 | "error", [ 768 | "invalidRegex1", 769 | "invalidRegex2", 770 | { 771 | "regex": "invalidRegex3", 772 | "message": "errorMessage1", 773 | "files": { 774 | "inspect": "inspectFilesRegex1" 775 | } 776 | }, 777 | { 778 | "id": "regexIdN", 779 | "regex": "invalidRegexN", 780 | "files": { 781 | "ignore": "ignoreFilesRegexN" 782 | } 783 | } 784 | ] 785 | ] 786 | } 787 | } 788 | ``` 789 | 790 | * `invalidRegex1` and `invalidRegex2` are 'Short pattern definition'. 791 | * `invalidRegex3` and `invalidRegexN` are 'Detailed pattern definition'. 792 | 793 | #### Mixing rules 794 | 795 | ##### Mixing error levels 796 | 797 | Rules names have synonyms: 798 | 799 | `regex/invalid` = `regex/invalid-warn` = `regex/invalid-error` = `regex/another-invalid` = `regex/other-invalid`. 800 | 801 | `regex/required` = `regex/required-warn` = `regex/required-error` = `regex/another-required` = `regex/other-required`. 802 | 803 | * Synonyms are just that, synonyms, do not imply any level of error, but some are provided to increase readability, e.g. `regex/invalid-warn` does not imply `warn` level. 804 | * This will allow to mix different error levels. 805 | 806 | It is possible to set different error level: `error`, `warn` and `off`. For this use a synonym for the regex rule name: 807 | 808 | `.eslintrc.json`: 809 | 810 | ```json 811 | { 812 | "plugins": [ 813 | "regex" 814 | ], 815 | "rules": { 816 | "regex/invalid": [ 817 | "error", 818 | [ 819 | "invalidRegex1", 820 | "invalidRegexN" 821 | ] 822 | ], 823 | "regex/required": [ 824 | "error", 825 | [ 826 | "requiredRegex1", 827 | "requiredRegexN" 828 | ] 829 | ], 830 | "regex/invalid-error": [ 831 | "error", [ 832 | "invalidRegexA1", 833 | "invalidRegexA2", 834 | { 835 | "regex": "invalidRegexA3", 836 | "message": "errorMessage1", 837 | "files": { 838 | "inspect": "inspectFilesRegexA1" 839 | } 840 | }, 841 | { 842 | "id": "regexIdN", 843 | "regex": "invalidRegexN", 844 | "files": { 845 | "ignore": "ignoreFilesRegexAN" 846 | } 847 | } 848 | ], 849 | ], 850 | "regex/invalid-warn": [ 851 | "warn", [ 852 | "invalidRegexB1", 853 | "invalidRegexB2", 854 | { 855 | "regex": "invalidRegexB3", 856 | "message": "errorMessage1", 857 | "files": { 858 | "inspect": "inspectFilesRegex1" 859 | } 860 | }, 861 | { 862 | "id": "regexIdN", 863 | "regex": "invalidRegexBN", 864 | "files": { 865 | "ignore": "ignoreFilesRegexN" 866 | } 867 | } 868 | ], 869 | ], 870 | "regex/other-invalid": [ 871 | "off", [ 872 | "invalidRegexC1", 873 | "invalidRegexC2", 874 | { 875 | "regex": "invalidRegexB3", 876 | "message": "errorMessage1", 877 | "files": { 878 | "inspect": "inspectFilesRegexC1" 879 | } 880 | }, 881 | { 882 | "id": "regexIdN", 883 | "regex": "invalidRegexBN", 884 | "files": { 885 | "ignore": "ignoreFilesRegexCN" 886 | } 887 | } 888 | ], 889 | ], 890 | "regex/required-warn": [ 891 | "warn", 892 | [ 893 | "requiredRegexA1", 894 | "requiredRegexAN" 895 | ] 896 | ] 897 | } 898 | } 899 | ``` 900 | 901 | * Rules with invalid patterns and `error` level: `regex/invalid` and `regex/invalid-error`. 902 | * Rules with invalid patterns and `off` level: `regex/other-invalid`. 903 | * Rules with required patterns and `error` level: `regex/required`. 904 | * Rules with required patterns and `warn` level: `regex/required-warn`. 905 | 906 | ##### Custom set of regex rules 907 | 908 | Creating and Using a Custom Set of regex rules **requires using `js` files**. 909 | 910 | ###### Named Regex Rules approach 911 | 912 | A regex rule can be named with a custom name. The Rule name can be anything that **includes `invalid`, `disuse`, `avoid`, `required` or `use`**, ignoring letter case, and with the restrictions of predefined names (`invalid`, `disuse`, `avoid`, `invalid-warn`, `invalid-error`, `another-invalid`, `other-invalid`, `required`, `use`, `required-warn`, `required-error`, `another-required` and `other-required`). 913 | 914 | * `regex/*invalid*`, `regex/*disuse*` or `regex/*avoid*` for invalid patterns. 915 | * `regex/*required*` or `regex/*use*` for required patterns. 916 | 917 | ![Mixed Rules](.readme/mixed-rules.svg) 918 | 919 | > In the name `invalid`, `disuse` and `avoid` will take precedence over `required` and `use`, e.g. If custom regex rule name has both `avoid` and `use` in the name, then the respective regex patterns will be consider invalid patterns. 920 | 921 | **`addRegexRuleName` must be used to add the custom regex rule name to the set of `eslint-plugin-regex` rules.** 922 | 923 | ```javascript 924 | const { addRegexRuleName } = require('eslint-plugin-regex') 925 | 926 | addRegexRuleName('*invalid*') 927 | addRegexRuleName('*required*') 928 | ``` 929 | 930 | * If the custom regex rule name is already defined, then an error will be shown: 931 | 932 | ```sh 933 | Error: Cannot read config file: /path/to/.eslintrc.js 934 | Error: "SomeRuleName" already defined as eslint-plugin-regex rule name 935 | ``` 936 | 937 | ***Local Custom Regex rules*** 938 | 939 | Create a local `.eslintrc.js`: 940 | 941 | 1 . Add rule name using `addRegexRuleName`. 942 | 2 . Define `eslint-plugin-regex` custom regex rule. 943 | 944 | ```javascript 945 | const { addRegexRuleName } = require('eslint-plugin-regex') 946 | 947 | addRegexRuleName('invalid-custom-890') 948 | 949 | module.exports = { 950 | plugins: [ 'regex' ], 951 | rules: { 952 | 'regex/invalid-custom-890': [ 953 | 'error', [ 954 | { 955 | regex: 'invalidRegexBN', 956 | files: { 957 | ignore: 'ignoreFilesRegexCN' 958 | } 959 | } 960 | ] 961 | ] 962 | } 963 | } 964 | ``` 965 | 966 | ***Custom Regex rules package*** 967 | 968 | Create a custom ESLint package and add the custom regex rules with a **"unique"** name for each regex rule defined in the package, so it can be use with other package of regex rules or local regex rules. 969 | 970 | Custom package `index.js`: 971 | 972 | ```javascript 973 | const { addRegexRuleName } = require('eslint-plugin-regex') 974 | 975 | addRegexRuleName('invalid-custom-890') 976 | 977 | module.exports = { 978 | configs: { 979 | 'someRegexRule1': { 980 | plugins: [ 'regex' ], 981 | rules: { 982 | 'regex/invalid-custom-890': [ 983 | 'error', [ 984 | { 985 | regex: 'invalidRegexBN', 986 | files: { 987 | ignore: 'ignoreFilesRegexCN' 988 | } 989 | } 990 | ] 991 | ] 992 | } 993 | } 994 | } 995 | } 996 | ``` 997 | 998 | * This custom package defines 1 rule named `regex/invalid-custom-890` with only 1 invalid pattern with `error` as a default error level. 999 | 1000 | > An online example can be checked at [`eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules). 1001 | > For more information on how to create a custom ESLint package check [ESLint official documentation: Working with Plugins](https://eslint.org/docs/developer-guide/working-with-plugins) 1002 | 1003 | then use it, 1004 | 1005 | Some project `.eslintrc.json`: 1006 | 1007 | ```json 1008 | { "extends": [ "plugin:the-eslint-plugin/someRegexRule1", 1009 | ``` 1010 | 1011 | to change the default error level set by the package: 1012 | 1013 | ```json 1014 | { 1015 | "extends": [ "plugin:the-eslint-plugin/someRegexRule1" ], 1016 | "rules": { 1017 | "regex/invalid-custom-890": "warn" 1018 | 1019 | ``` 1020 | 1021 | mixing with other regex rules: 1022 | 1023 | ```json 1024 | { 1025 | "extends": [ "plugin:the-eslint-plugin/someRegexRule1" ], 1026 | "rules": { 1027 | "regex/invalid-custom-890": "warn", 1028 | "regex/required": [ 1029 | "error", 1030 | [ 1031 | "requiredRegex1", 1032 | "requiredRegexN" 1033 | ] 1034 | ], 1035 | "regex/invalid-error": [ 1036 | "error", [ 1037 | "invalidRegexA1", 1038 | "invalidRegexA2", 1039 | { 1040 | "regex": "invalidRegexA3", 1041 | "message": "errorMessage1", 1042 | "files": { 1043 | "inspect": "inspectFilesRegexA1" 1044 | } 1045 | }, 1046 | { 1047 | "id": "regexIdN", 1048 | "regex": "invalidRegexN", 1049 | "files": { 1050 | "ignore": "ignoreFilesRegexAN" 1051 | } 1052 | } 1053 | ], 1054 | ], 1055 | ``` 1056 | 1057 | ***Advantages*** 1058 | 1059 | * Using Named Regex rule name will allow to have a **set of different regex rule**: 1060 | * Each rule with totally different settings. 1061 | * Allow to mix different regular expressions. 1062 | * Allow to mix different error levels. 1063 | * etc. 1064 | * Easily create custom regex rules package. 1065 | * When using Named Regex Rules, shown **errors will be even more specific**, e.g.: 1066 | 1067 | ```javascript 1068 | const { addRegexRuleName } = require('eslint-plugin-regex') 1069 | 1070 | addRegexRuleName('required-custom-896') 1071 | ``` 1072 | 1073 | then, if an error happens, the output will be something similar to: 1074 | 1075 | ```sh 1076 | /path/to/some.js 1077 | 1:1 error Required regular expression /requiredRegex/gm not found in file regex/required-custom-896 1078 | ``` 1079 | 1080 | instead of 1081 | 1082 | ```sh 1083 | /path/to/some.js 1084 | 1:1 error Required regular expression /requiredRegex/gm not found in file regex/required 1085 | ``` 1086 | 1087 | ###### Import/Export approach 1088 | 1089 | Create a custom npm package using either with `json` or `js` files and add the custom regex rules. 1090 | 1091 | Custom package `index.js`: 1092 | 1093 | with complete rule definition: 1094 | 1095 | ```javascript 1096 | module.exports = { 1097 | regex: 'invalidRegexBN', 1098 | files: { 1099 | ignore: 'ignoreFilesRegexCN' 1100 | } 1101 | } 1102 | ``` 1103 | 1104 | or 1105 | 1106 | ```javascript 1107 | module.exports = { 1108 | regex: 'invalidRegexBN', 1109 | } 1110 | ``` 1111 | 1112 | or with only regex definition: 1113 | 1114 | ```javascript 1115 | module.exports = 'invalidRegexBN' 1116 | ``` 1117 | 1118 | or with multiple complete rule definition: 1119 | 1120 | ```javascript 1121 | module.exports = { 1122 | ruleName1: { 1123 | regex: 'invalidRegex1', 1124 | files: { 1125 | ignore: 'ignoreFilesRegex1' 1126 | } 1127 | }, 1128 | ruleNameN: { 1129 | regex: 'invalidRegexN', 1130 | files: { 1131 | ignore: 'ignoreFilesRegexN' 1132 | } 1133 | } 1134 | } 1135 | ``` 1136 | 1137 | or 1138 | 1139 | ```javascript 1140 | module.exports = { 1141 | ruleName1: { 1142 | regex: 'invalidRegex1', 1143 | }, 1144 | ruleNameN: { 1145 | regex: 'invalidRegexN', 1146 | } 1147 | } 1148 | ``` 1149 | 1150 | or with multiple only regex definition: 1151 | 1152 | ```javascript 1153 | module.exports = { 1154 | ruleName1: 'invalidRegex1', 1155 | ruleNameN: 'invalidRegexN' 1156 | } 1157 | ``` 1158 | 1159 | or using `json` files: 1160 | 1161 | ```json 1162 | { 1163 | "regex": "invalidRegexBN", 1164 | "files": { 1165 | "ignore": "ignoreFilesRegexCN" 1166 | } 1167 | } 1168 | ``` 1169 | 1170 | or 1171 | 1172 | ```json 1173 | { 1174 | "regex": "invalidRegexBN", 1175 | } 1176 | ``` 1177 | 1178 | or 1179 | 1180 | ```json 1181 | { 1182 | "ruleName1": { 1183 | "regex": "invalidRegex1", 1184 | "files": { 1185 | "ignore": "ignoreFilesRegex1" 1186 | } 1187 | }, 1188 | "ruleNameN": { 1189 | "regex": "invalidRegexN", 1190 | "files": { 1191 | "ignore": "ignoreFilesRegexN" 1192 | } 1193 | } 1194 | } 1195 | ``` 1196 | 1197 | or 1198 | 1199 | ```json 1200 | { 1201 | "ruleName1": { 1202 | "regex": "invalidRegex1", 1203 | }, 1204 | "ruleNameN": { 1205 | "regex": "invalidRegexN", 1206 | } 1207 | } 1208 | ``` 1209 | 1210 | or 1211 | 1212 | ```json 1213 | { 1214 | "ruleName1": "invalidRegex1", 1215 | "ruleNameN": "invalidRegexN" 1216 | } 1217 | ``` 1218 | 1219 | > Different approaches can be defined, these are only a glance. 1220 | > For more information on how to create a custom npm package check [Contributing packages to the registry](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) 1221 | 1222 | Then use the custom package: 1223 | 1224 | Some project `.eslintrc.js`: 1225 | 1226 | ```javascript 1227 | import * as SomeESLintSetOfRegexRulesPackage1 from 'the-custom-package1' 1228 | import * as SomeESLintSetOfRegexRulesPackage2 from 'the-custom-package2' 1229 | 1230 | module.exports = { 1231 | plugins: ["regex"], 1232 | rules: { 1233 | "regex/invalid": [ 1234 | 'error', [ 1235 | SomeESLintSetOfRegexRulesPackage1.ruleName1, 1236 | SomeESLintSetOfRegexRulesPackage1.ruleNameN, 1237 | SomeESLintSetOfRegexRulesPackage2.ruleName1, 1238 | SomeESLintSetOfRegexRulesPackage2.ruleNameN 1239 | ] 1240 | ], 1241 | ``` 1242 | 1243 | or using synonyms to mix error levels: 1244 | 1245 | ```javascript 1246 | import * as SomeESLintSetOfRegexRulesPackage1 from 'the-custom-package1' 1247 | import * as SomeESLintSetOfRegexRulesPackage2 from 'the-custom-package2' 1248 | 1249 | module.exports = { 1250 | plugins: ["regex"], 1251 | rules: { 1252 | 'regex/invalid-error': [ 1253 | 'error', [ 1254 | SomeESLintSetOfRegexRulesPackage1.ruleNameN, 1255 | SomeESLintSetOfRegexRulesPackage2.ruleName1 1256 | ] 1257 | ], 1258 | 'regex/invalid-warn': [ 1259 | 'warn', [ 1260 | SomeESLintSetOfRegexRulesPackage1.ruleName1, 1261 | SomeESLintSetOfRegexRulesPackage2.ruleNameN 1262 | ] 1263 | ] 1264 | ``` 1265 | 1266 | ### `regex/invalid` vs `regex/required` 1267 | 1268 | Both rule were design with *binary* approach: 1269 | 1270 | * `regex/invalid`: pattern **is not present** => any presence of the *specific* pattern in a file is invalid. 1271 | * `regex/required`: pattern **is present** => only 1 presence of the *specific* pattern in a file is required. 1272 | 1273 | Array of patterns represent different logical operation for each rule: 1274 | 1275 | * `regex/invalid`: **OR** => the presence in a file of *any* of the patterns defined in the *array* is invalid. 1276 | * `regex/required`: **AND** => the presence in file of *all* of the patterns defined in the *array* is required. 1277 | 1278 | ### Examples 1279 | 1280 | Check: 1281 | 1282 | * [invalid-regex Basic rule tests](tests/lib/rules/invalid-regex-rule.e2e-test.js) 1283 | * [invalid-regex Detailed rule tests](tests/lib/rules/invalid-regex-detailed-rule.e2e-test.js) 1284 | * [required-regex Basic rule tests](tests/lib/rules/required-regex-rule.e2e-test.js) 1285 | * [required-regex Detailed rule tests](tests/lib/rules/required-regex-detailed-rule.e2e-test.js) 1286 | * [The set of Regex Rules of `eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules) 1287 | 1288 | __________________ 1289 | 1290 | ## Prerequisites 1291 | 1292 | * [`"eslint": ">=4.0.0"`](https://www.npmjs.com/package/eslint). 1293 | 1294 | __________________ 1295 | 1296 | ## Evolution 1297 | 1298 | [`CHANGELOG.md`](CHANGELOG.md): contains the information about changes in each version, chronologically ordered ([Keep a Changelog](http://keepachangelog.com)). 1299 | 1300 | ## Extending/Developing 1301 | 1302 | [Developing](js/.readme/developing.md) 1303 | 1304 | ## Contributing 1305 | 1306 | * **Use it**. 1307 | * **Share it**. 1308 | * [Give it a Star](https://github.com/gmullerb/eslint-plugin-regex). 1309 | * [Propose changes or improvements](https://github.com/gmullerb/eslint-plugin-regex/issues). 1310 | * [Report bugs](https://github.com/gmullerb/eslint-plugin-regex/issues). 1311 | 1312 | ## License 1313 | 1314 | [MIT License](LICENSE.txt) 1315 | 1316 | __________________ 1317 | 1318 | ## Remember 1319 | 1320 | * Use code style verification tools => Encourages Best Practices, Efficiency, Readability and Learnability. 1321 | * Code Review everything => Encourages Functional suitability, Performance Efficiency and Teamwork. 1322 | * If viable, Start testing early => Encourages Reliability and Maintainability. 1323 | 1324 | ## Additional words 1325 | 1326 | Don't forget: 1327 | 1328 | * **Love what you do**. 1329 | * **Learn everyday**. 1330 | * **Learn yourself**. 1331 | * **Share your knowledge**. 1332 | * **Think different!**. 1333 | * **Learn from the past, dream on the future, live and enjoy the present to the max!**. 1334 | * **Enjoy and Value the Quest** (It's where you learn and grow). 1335 | 1336 | At life: 1337 | 1338 | * Let's act, not complain. 1339 | * Be flexible. 1340 | 1341 | At work: 1342 | 1343 | * Let's give solutions, not questions. 1344 | * Aim to simplicity not intellectualism. 1345 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: eslint-plugin-regex -------------------------------------------------------------------------------- /dev_resources/mixed-rules.xml: -------------------------------------------------------------------------------- 1 | 7Zpdb5swFIZ/TS4nGdtAuGxp0+2ilaZUqrQ7DztgFXDmmCXdr59ZTIL5UDOahqpCqlQ4/gCfx8evj8kMhdnuTpJ1ci8oS2cQ0N0M3cwgdBCEs/IP0Je9xfeMIZacmkpHw5L/YcYIjLXglG2sikqIVPG1bYxEnrNIWTYipdja1VYitZ+6JjFrGZYRSdvWJ05VUr0dONq/Mh4n5skImoKMVHWNYZMQKrY1E7qdoVAKofZX2S5kaem7yi37doue0sN7SZarUxrcLe7BN+o+3ih/EfpB8P0R0S+ml98kLcx4zcuql8oBuhfta31zvU24Yss1icqSrcatbYnKUn3n6MuNkuL54CQ9vusVT9OnyjmlISFRUkh2Vza9mZsaoUiF1Pe5yJkxLc3jTf2y82emosR6UtVuBhFgczAv+zPDYVKxXa+fnIP39axlImNKvugqVQMY7JuYCYvR/nZ7pI98wzSpkXdcYyRmxsWHro9U9IUB8x+QYAuSZLEeH1xI9qvgktFlsVrxnR4XkIV2XBOhdoZqua3m7oapxnQlcmViEuLq3nRclpee5jpYrlIe59qWcUrLwmtiDJH2OpOm5YJkPC3dGoqMR/odlyTf6H/3S1OhjhQEgQ6ysyCFHraQem2kh4CuI8XvRRS1iK6J0n7KnRY7KYqcMmr8/bYQxF0haAxXefzvkdhtTArN4idgiHnNKHyVaH+Y1qaV450pbKuYNIwdpwMyuiRk3Af5YYI8EDKCyIYM8ciQ3Y8toFSozavqSRjW+7TzEHKRrZ5odPX0etWT59rIjXjCzySeYQjAucTT8xyLqDu2ePp96+rVh1xXV4QG1H37unqI0ndYV915QzzB2OI574P8Y4I8ELKPGuLpjC2ezpR+tpdbYIcirEJzPAV1Tk1A8WfS0HMmoD7y7F0RaEO9rIg6vSkonnLQwbGL7a0ScjswX3aF7U1C8ZSFDo7mwD5OQtUOeTzMUxraOA3yXFtFvfFV9MREFH0mET1nIgqDBtOOndGFRbQ3FUVTLjoYM2jsldDoItqbjKIpGx18lOs2Dgrx6CIaTCJqI2pkLbhjO3vpT6HgNBF9mES0myn27RQF+2OLKGwfAlXfySYRHRy6vm9hduHYIlotph2YJxEd/rXNPix00fuJaHnad/ix0r+y2i++0O1f 2 | -------------------------------------------------------------------------------- /dev_resources/rules.xml: -------------------------------------------------------------------------------- 1 | 7Zhdb5swFIZ/DZeTDIYELlvaZLvIpCmVKu3OwydgFXDmOEu6X79DcyB8FLVr06WaKkUKfn1s4/fx8Wnq8LjYz41YZwstIXc8JvcOv3I8z+We51QfJu8PSlALqVGSgo7CUv0GEhmpWyVh0wm0WudWrbtiossSEtvRhDF61w1b6by76lqkMBCWiciH6q2SNqvfjh31z6DSjFYOSS9EHUrCJhNS71oSv3Z4bLS2h6diH0NeWVe7chg3G+ltXstAaZ8zYD5bsC8yuLmy01k8jaJvN1x+oll+iXxL26WXtff1/nEWtBobl7tMWViuRVL17JA2apktcmy5+LixRt81HuH+Llcqz29rbyohE0m2NTCvhl6FFBHrXBtsl7oEkpa0PMVXk9+BTbLOSvU4x+MMQhZW89F2wFjYj/rkNu7joQVdgDX3GFIPqA8enVcvjA7t3ZE+n1JM1iLvBiQKOnFpM/cRCz4Qmb+g5A0oGUhxg97MwM+tMiCx12zRsj48tMEODGsZ3ZNaNFe6tJSMnl+3aeKqv/JYYZZc5CotUSuUlFXnpSAhQb/B0MiZKFReGRrrQiX4jktRbvBrsaSANkwWRZhdp4EZhR2YnA1hNqnchum/FUs+YLkWFo0q3QE8o7elBEmGvy77/Meyj4SLMn1Y0g96pwJh/GDAYdJPwCeRjmdo61y5kxNBDtwu5OARyPxfQvbHIH/9gPxCyJz5Xcj1FXw2yMH7rp1S282ThVOAj3+hnYZQ4PUK5/TshXMyWjhViaL6r+pmHDN2qroZRFGXZXTuujkdu1Iv3uWVuhIyksHrr9QmQd/gSg0mvbrpn7tuhmOQv39AfiHkqdurm5O3q5vVD5Lm9+xDX+t/Avz6Dw== 2 | -------------------------------------------------------------------------------- /docs/rules/invalid-regex-rule.md: -------------------------------------------------------------------------------- 1 | # Looks for Invalid regular expressions to be reported for each file 2 | 3 | ## Rule Details 4 | 5 | This rule looks for **Invalid regular expressions** to be reported for each file. 6 | 7 | Example of **incorrect** code for this rule: 8 | 9 | ```js 10 | /* eslint regex/invalid: ['error', ['"']] */ 11 | 12 | const text = 'Hello "My Friend"' 13 | ``` 14 | 15 | Example of **correct** code for this rule: 16 | 17 | ```js 18 | /* eslint regex/invalid: ['error', ['"']] */ 19 | 20 | const text = 'Hello \'My Friend\'' 21 | ``` 22 | 23 | ## Options 24 | 25 | * **array** of patterns definitions to analyze. [REQUIRED] 26 | * Each pattern definition can be 'Short' or 'Detailed'. 27 | * a **string** representing the regular expression for ignoring files for all patterns. [OPTIONAL] 28 | * Slashes (`/`) are not required in the string, e.g. To get the following regex `/.*test\.js/` define the following string `".*test\.js"` when using `.eslintrc.js` or `".*test\\.js"` when using `.eslintrc.json` (backslash needs to de double in a json file). 29 | 30 | ### Short pattern definition 31 | 32 | It is specified by just a regular expression `string`, i.e. `"regex"` 33 | 34 | * Slashes (`/`) are not required in the string, e.g. To get the following regex `/\bhttp:/` define the following string `"\bhttp:"` when using `.eslintrc.js` or `"\\bhttp:"` when using `.eslintrc.json` (backslash needs to de double in a json file). 35 | 36 | `.eslintrc.json`: 37 | 38 | ```json 39 | { 40 | "plugins": [ 41 | "regex" 42 | ], 43 | "rules": { 44 | "regex/invalid": [ 45 | "error", [ 46 | "invalidRegex1", 47 | "invalidRegexN" 48 | ], 49 | ".*test\\.js" 50 | ] 51 | } 52 | } 53 | ``` 54 | 55 | ### Detailed pattern definition 56 | 57 | It is specified by an `object`, with the following fields: 58 | 59 | * `regex`: A **required** `string` for `regex/required` and `regex/invalid` representing the **Regular expression to look for**. [REQUIRED] 60 | * `flags`: A combination of flags, `i`, `s` and/or `u`, to be used by the Regular Expression. [OPTIONAL] 61 | * `replacement` for `regex/invalid` [1]: [OPTIONAL] 62 | * An optional `string` used to replace the **invalid** found pattern, or 63 | * An optional `object` that establish how the **invalid** found pattern will be replaced: 64 | * `function`: used to replace the **invalid** found pattern. 65 | * It will receive 3 parameters: `text`, `captured` and `$`, that can be used as desired. 66 | * It must return a `string` value, if not, return value will be ignored. 67 | * Its definition must be only the body of the function. 68 | * One must be defined, either the `string` or `function`. 69 | * `id`: An optional `string` representing the **Pattern Id**. [OPTIONAL] 70 | * `message`: An optional `string` specifying the **Message to be shown when an error happens** (invalid `regex` is found or required `regex` is not found). [OPTIONAL] 71 | * `files`: An optional `object` specifying which files to analyze: [OPTIONAL] 72 | * `ignore`: A `string` representing **Regular expression of the files to be ignored** when validating this specific pattern. 73 | * `inspect`: A `string` representing **Regular expression of the files to be inspected** when validating this specific pattern. 74 | 75 | ```json 76 | { 77 | "id": "regexId", 78 | "regex": "regex", 79 | "flags": "isu", 80 | "replacement": "replacementString", 81 | "message": "errorMessage", 82 | "files": { 83 | "ignore": "ignoreFilesRegex", 84 | "inspect": "inspectFilesRegex" 85 | } 86 | } 87 | ``` 88 | 89 | > * `regex` is the only Required field. Slashes (`/`) are not required in the string, e.g. To get the following regex `/\bhttp:/`: 90 | > * when using `.eslintrc.js`, define the following string `"\bhttp:"`, or 91 | > * when using `.eslintrc.json`, define `"\\bhttp:"` (backslash needs to de double in a json file). 92 | > * When `ignore` and `inspect` are present, `ignore` takes precedence. 93 | > * Global ignore file pattern, takes precedence over `files` patterns. 94 | 95 | > [1] In order to fix issue eslint must be run with `--fix` option. 96 | 97 | `.eslintrc.json`: 98 | 99 | ```json 100 | { 101 | "plugins": [ 102 | "regex" 103 | ], 104 | "rules": { 105 | "regex/invalid": [ 106 | "error", [{ 107 | "regex": "invalidRegex1", 108 | "message": "errorMessage1", 109 | "replacement": "newValue" 110 | }, { 111 | "id": "regexIdN", 112 | "regex": "invalidRegexN", 113 | "files": { 114 | "ignore": "ignoreFilesRegexN" 115 | } 116 | } 117 | ] 118 | ] 119 | } 120 | } 121 | ``` 122 | 123 | #### Definition of the Function used to replace the invalid found pattern 124 | 125 | Definition of the function must be done as a `string` in 1 line, and the following rules apply: 126 | 127 | * It must return a `string` value, if not, return value will be ignored, i.e. it will silently fail. 128 | * Its definition must be **only the body of the function**. 129 | * For "simple" functions where the `return` is found at the beginning of the body of the function and the **exact** word *return* is not present, `return` can be omitted. 130 | * If the function has invalid Javascript code, the function will be ignored, i.e. it will silently fail. 131 | 132 | Function will receive 3 parameters, to be used as desired: 133 | 134 | * `text`: a `string` with the value of the invalid text found. 135 | * `captured`: an `array` of strings with the values of the captured groups for the regex. 136 | * `$`: an `array` of strings, with the value of the invalid text found plus the values of the captured groups for the regex. 137 | * `$[0]` = `text`: a `string` with the value of the invalid text found. 138 | * `$[1..]` = `captured`: an `array` of strings with the values of the captured groups for the regex. 139 | * `$[1]` = `captured[0]` and so on. 140 | * It allows smaller definitions. 141 | 142 | **e.g. Using parameter `text`** 143 | 144 | `"return text.trim()"` => only the body of the function + returns a `string` value based on `text` 145 | 146 | Having the following rule in `.eslintrc.json`: 147 | 148 | ```json 149 | { 150 | "id": "regexIdN", 151 | "regex": "\\serror\\w*\\s", 152 | "replacement": { 153 | "function": "return text.trim()" 154 | } 155 | } 156 | ``` 157 | 158 | or using `$`: 159 | 160 | ```json 161 | { 162 | "id": "regexIdN", 163 | "regex": "\\serror\\w*\\s", 164 | "replacement": { 165 | "function": "return $[0].trim()" 166 | } 167 | } 168 | ``` 169 | 170 | then, given: 171 | 172 | `example.js` 173 | 174 | ```js 175 | const exception = " error19 " 176 | ``` 177 | 178 | when linting with fix, the result will be: 179 | 180 | ```js 181 | const exception = "error19" 182 | ``` 183 | 184 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 185 | 186 | ```json 187 | { 188 | "id": "regexIdN", 189 | "regex": "\\serror\\w*\\s", 190 | "replacement": { 191 | "function": "text.trim()" 192 | } 193 | } 194 | ``` 195 | 196 | or 197 | 198 | ```json 199 | { 200 | "id": "regexIdN", 201 | "regex": "\\serror\\w*\\s", 202 | "replacement": { 203 | "function": "$[0].trim()" 204 | } 205 | } 206 | ``` 207 | 208 | **e.g. Using parameter `captured`** 209 | 210 | `"return captured[0]"` => only the body of the function + returns a `string` value based on `captured` 211 | 212 | Having the following rule in `.eslintrc.json`: 213 | 214 | ```json 215 | { 216 | "id": "regexIdN", 217 | "regex": "\\serror(\\w*)\\s", 218 | "replacement": { 219 | "function": "return captured[0]" 220 | } 221 | } 222 | ``` 223 | 224 | or using `$`: 225 | 226 | ```json 227 | { 228 | "id": "regexIdN", 229 | "regex": "\\serror(\\w*)\\s", 230 | "replacement": { 231 | "function": "return $[1]" 232 | } 233 | } 234 | ``` 235 | 236 | then, given: 237 | 238 | `example.js` 239 | 240 | ```js 241 | const exception = " error19 " 242 | ``` 243 | 244 | when linting with fix, the result will be: 245 | 246 | ```js 247 | const exception = "19" 248 | ``` 249 | 250 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 251 | 252 | ```json 253 | { 254 | "id": "regexIdN", 255 | "regex": "\\serror(\\w*)\\s", 256 | "replacement": { 257 | "function": "captured[0]" 258 | } 259 | } 260 | ``` 261 | 262 | or 263 | 264 | ```json 265 | { 266 | "id": "regexIdN", 267 | "regex": "\\serror(\\w*)\\s", 268 | "replacement": { 269 | "function": "$[1]" 270 | } 271 | } 272 | ``` 273 | 274 | **e.g. Using parameters `text` and `captured`** 275 | 276 | `"return text + ' = ' + captured[0] + ' + ' + captured[1] + ' = ' + (parseInt(captured[0]) + parseInt(captured[1]))"` => only the body of the function + returns a `string` value based on `text` and `captured` 277 | 278 | Having the following rule in `.eslintrc.json`: 279 | 280 | ```json 281 | { 282 | "id": "regexIdN", 283 | "regex": "(\\d+)\\+(\\d+)", 284 | "replacement": { 285 | "function": "return text + ' = ' + captured[0] + ' + ' + captured[1] + ' = ' + (parseInt(captured[0]) + parseInt(captured[1]))" 286 | } 287 | } 288 | ``` 289 | 290 | or using `$`: 291 | 292 | ```json 293 | { 294 | "id": "regexIdN", 295 | "regex": "(\\d+)\\+(\\d+)", 296 | "replacement": { 297 | "function": "return $[0] + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 298 | } 299 | } 300 | ``` 301 | 302 | or : 303 | 304 | ```json 305 | { 306 | "id": "regexIdN", 307 | "regex": "(\\d+)\\+(\\d+)", 308 | "replacement": { 309 | "function": "return text + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 310 | } 311 | } 312 | ``` 313 | 314 | or : 315 | 316 | ```json 317 | { 318 | "id": "regexIdN", 319 | "regex": "(\\d+)\\+(\\d+)", 320 | "replacement": { 321 | "function": "return `${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`" 322 | } 323 | } 324 | ``` 325 | 326 | then, given: 327 | 328 | `example.js` 329 | 330 | ```js 331 | const sum = "4+5" 332 | ``` 333 | 334 | when linting with fix, the result will be: 335 | 336 | ```js 337 | const sum = "4+5 = 4 + 5 = 9" 338 | ``` 339 | 340 | As the body of the function is "simple", i.e. the `return` is found at the beginning of the body of the function, and besides, the word *return* is not present, then the definition could be done as: 341 | 342 | ```json 343 | { 344 | "id": "regexIdN", 345 | "regex": "(\\d+)\\+(\\d+)", 346 | "replacement": { 347 | "function": "text + ' = ' + $[1] + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))" 348 | } 349 | } 350 | ``` 351 | 352 | or : 353 | 354 | ```json 355 | { 356 | "id": "regexIdN", 357 | "regex": "(\\d+)\\+(\\d+)", 358 | "replacement": { 359 | "function": "`${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`" 360 | } 361 | } 362 | ``` 363 | 364 | **e.g. `return` required** 365 | 366 | e.g. `const result = text === 'superb' ? 'Superb' : text; return result` => only the body of the function + returns a `string` value based on `text`. 367 | 368 | Since the `return` is not found at the beginning of the body of the function, `return` cannot be omitted, then rule definition will be as usual: 369 | 370 | ```json 371 | { 372 | "id": "regexIdN", 373 | "regex": "\\w+", 374 | "replacement": { 375 | "function": "const result = text === 'superb' ? 'Superb' : text; return result" 376 | } 377 | } 378 | ``` 379 | 380 | > Some cases may use Comma operator, e.g. `"function": "result = text === 'superb' ? 'Superb' : text, result"` 381 | 382 | e.g. `return text === 'return' ? 'Return' : text` => only the body of the function + returns a `string` value based on `text`. 383 | 384 | Since the *exact* word *return* is present, this will **required** `return`, then rule definition will be as usual: 385 | 386 | ```json 387 | { 388 | "id": "regexIdN", 389 | "regex": "\\w+", 390 | "replacement": { 391 | "function": "return text === 'return' ? 'Return' : text" 392 | } 393 | } 394 | ``` 395 | 396 | Following case does not required `return`: 397 | 398 | e.g. `return text === 'Return' ? 'RETURN' : text` => only the body of the function + returns a `string` value based on `text`. 399 | 400 | Since the **exact** word *return* is not present, this will allow the following rule definition to be: 401 | 402 | ```json 403 | { 404 | "id": "regexIdN", 405 | "regex": "\\w+", 406 | "replacement": { 407 | "function": "text === 'Return' ? 'RETURN' : text" 408 | } 409 | } 410 | ``` 411 | 412 | ##### Debugging of the Replacement Function for invalid found pattern 413 | 414 | * It is possible to add `console` statements to print some information in the Replacement Function. 415 | 416 | ```json 417 | { 418 | regex: '\\serror(\\w*)\\s', 419 | replacement: { 420 | function: 'const extract = captured[0]; console.log(extract); return extract' 421 | } 422 | } 423 | ``` 424 | 425 | ### String to Regular expression conversion 426 | 427 | Internally, each string from the array will be converted into a Regular Expression with `global` and `multiline` options, e.g.: 428 | 429 | `"invalidRegex1"` will be transformed into `/invalidRegex1/gm` 430 | 431 | When the pattern is found, the error message will reflect the exact location, e.g.: 432 | 433 | ```bash 434 | 34:25 error Invalid regular expression /invalidRegex1/gm found regex/invalid 435 | ``` 436 | 437 | ### Examples 438 | 439 | Check: 440 | 441 | * [invalid-regex Basic rule tests](tests/lib/rules/invalid-regex-rule.e2e-test.js) 442 | * [invalid-regex Detailed rule tests](tests/lib/rules/invalid-regex-detailed-rule.e2e-test.js) 443 | * [The set of Regex Rules of `eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules) 444 | 445 | ## Related Rules 446 | 447 | * [`regex/required`](docs/rules/required-regex-rule.md). 448 | 449 | ## More information 450 | 451 | * [`eslint-plugin-regex`](../README.md) 452 | * [For a set of Regex Rules examples check `eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules) 453 | -------------------------------------------------------------------------------- /docs/rules/required-regex-rule.md: -------------------------------------------------------------------------------- 1 | # Looks for Required regular expressions that must be present in each file 2 | 3 | ## Rule Details 4 | 5 | This rule looks for **Required regular expressions** that must be present in each file. 6 | 7 | Example of **incorrect** code for this rule: 8 | 9 | ```js 10 | /* eslint regex/required: ["error", ["^// Copyright My Friend"]] */ 11 | 12 | const text = 'Hello "My Friend"' 13 | ``` 14 | 15 | Example of **correct** code for this rule: 16 | 17 | ```js 18 | /* eslint regex/required: ["error", ["^// Copyright My Friend"]] */ 19 | 20 | // Copyright My Friend 21 | const text = 'Hello "My Friend"' 22 | ``` 23 | 24 | ## Options 25 | 26 | * **array** of patterns definitions to analyze. [REQUIRED] 27 | * Each pattern definition can be 'Short' or 'Detailed'. 28 | * a **string** representing the regular expression for ignoring files for all patterns. [OPTIONAL] 29 | * Slashes (`/`) are not required in the string, e.g. To get the following regex `/.*test\.js/` define the following string `".*test\.js"` when using `.eslintrc.js` or `".*test\\.js"` when using `.eslintrc.json` (backslash needs to de double in a json file). 30 | 31 | ### Short pattern definition 32 | 33 | It is specified by just a regular expression `string`, i.e. `"regex"` 34 | 35 | * Slashes (`/`) are not required in the string, e.g. To get the following regex `/\bhttp:/` define the following string `"\bhttp:"` when using `.eslintrc.js` or `"\\bhttp:"` when using `.eslintrc.json` (backslash needs to de double in a json file). 36 | 37 | `.eslintrc.json`: 38 | 39 | ```json 40 | { 41 | "plugins": [ 42 | "regex" 43 | ], 44 | "rules": { 45 | "regex/required": [ 46 | "error", [ 47 | "requiredRegex1", 48 | "requiredRegexN" 49 | ], 50 | ".*test\\.js" 51 | ] 52 | } 53 | } 54 | ``` 55 | 56 | ### Detailed pattern definition 57 | 58 | It is specified by an `object`, with the following fields: 59 | 60 | * `regex`: A **required** `string` for `regex/required` and `regex/invalid` representing the **Regular expression to look for**. [REQUIRED] 61 | * `flags`: A combination of flags, `i`, `s` and/or `u`, to be used by the Regular Expression. [OPTIONAL] 62 | * `id`: An optional `string` representing the **Pattern Id**. [OPTIONAL] 63 | * `message`: An optional `string` specifying the **Message to be shown when an error happens** (invalid `regex` is found or required `regex` is not found). [OPTIONAL] 64 | * `files`: An optional `object` specifying which files to analyze: [OPTIONAL] 65 | * `ignore`: A `string` representing **Regular expression of the files to be ignored** when validating this specific pattern. 66 | * `inspect`: A `string` representing **Regular expression of the files to be inspected** when validating this specific pattern. 67 | 68 | ```json 69 | { 70 | "id": "regexId", 71 | "regex": "regex", 72 | "flags": "isu", 73 | "message": "errorMessage", 74 | "files": { 75 | "ignore": "ignoreFilesRegex", 76 | "inspect": "inspectFilesRegex" 77 | } 78 | } 79 | ``` 80 | 81 | > * `regex` is the only Required field. Slashes (`/`) are not required in the string, e.g. To get the following regex `/\bhttp:/`: 82 | > * when using `.eslintrc.js`, define the following string `"\bhttp:"`, or 83 | > * when using `.eslintrc.json`, define `"\\bhttp:"` (backslash needs to de double in a json file). 84 | > * When `ignore` and `inspect` are present, `ignore` takes precedence. 85 | > * Global ignore file pattern, takes precedence over `files` patterns. 86 | 87 | `.eslintrc.json`: 88 | 89 | ```json 90 | { 91 | "plugins": [ 92 | "regex" 93 | ], 94 | "rules": { 95 | "regex/required": [ 96 | "error", [{ 97 | "id": "regexId1", 98 | "regex": "requiredRegex1", 99 | "files": { 100 | "inspect": "inspectFilesRegex1" 101 | } 102 | }, { 103 | "regex": "requiredRegexN", 104 | "message": "errorMessageN", 105 | "files": { 106 | "ignore": "ignoreFilesRegexN", 107 | "inspect": "inspectFilesRegexN" 108 | } 109 | } 110 | ] 111 | ] 112 | } 113 | } 114 | ``` 115 | 116 | ### String to Regular expression conversion 117 | 118 | Internally, each string from the array will be converted into a Regular Expression with `global` and `multiline` options, e.g.: 119 | 120 | `"requiredRegex1"` will be transformed into `/requiredRegex1/gm` 121 | 122 | ### Examples 123 | 124 | Check: 125 | 126 | * [required-regex Basic rule tests](tests/lib/rules/required-regex-rule.e2e-test.js) 127 | * [required-regex Detailed rule tests](tests/lib/rules/required-regex-detailed-rule.e2e-test.js) 128 | * [The set of Regex Rules of `eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules) 129 | 130 | ## Related Rules 131 | 132 | * [`regex/invalid`](docs/rules/invalid-regex-rule.md). 133 | 134 | ## More information 135 | 136 | * [`eslint-plugin-regex`](../README.md) 137 | * [For a set of Regex Rules examples check `eslint-plugin-base-style-config`](https://github.com/gmullerb/base-style-config/tree/master/js#regex-rules) 138 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | /* eslint-disable global-require */ 4 | 5 | const rulesNames = ['required', 'use', 'required-warn', 'required-error', 'another-required', 'other-required', 'invalid', 'disuse', 'avoid', 'invalid-warn', 'invalid-error', 'another-invalid', 'other-invalid'] 6 | 7 | module.exports = { 8 | addRegexRuleName: (ruleName) => { 9 | if (/use|avoid|disuse|invalid|required/i.test(ruleName)) { 10 | const candidate = ruleName.toUpperCase() 11 | if (rulesNames.findIndex(ruleName => ruleName.toUpperCase() === candidate) !== -1) { 12 | throw new Error(`"${ruleName}" already defined as eslint-plugin-regex rule name`) 13 | } 14 | rulesNames.push(ruleName) 15 | } 16 | }, 17 | rules: new Proxy({}, { 18 | ownKeys: () => rulesNames, 19 | getOwnPropertyDescriptor: () => ({ configurable: true, enumerable: true }), 20 | get(rules, ruleName) { 21 | return /avoid|disuse|invalid/i.test(ruleName) 22 | ? require('./rules/invalid-regex-rule.js') 23 | : /use|required/i.test(ruleName) 24 | ? require('./rules/required-regex-rule.js') 25 | : undefined 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /lib/rules/common-fields-definitions.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | 4 | module.exports = { 5 | REGEX_FLAGS_FIELD_DEFINITION: { 6 | title: 'Regular expression allowed flags', 7 | description: 'A combination of "i", "s" and "u" regular expression flags', 8 | type: 'string', 9 | pattern: '^[isuISU]{1,3}$' 10 | }, 11 | FILES_FIELD_DEFINITION: { 12 | type: 'object', 13 | properties: { 14 | ignore: { 15 | title: 'Ignore file pattern', 16 | description: 'Regular expression of the files to be ignored when validating this specific pattern', 17 | type: 'string', 18 | minLength: 2 19 | }, 20 | inspect: { 21 | title: 'Inspect file pattern', 22 | description: 'Regular expression of the files to be inspected when validating this specific pattern', 23 | type: 'string', 24 | minLength: 2 25 | } 26 | }, 27 | minProperties: 1, 28 | maxProperties: 2 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/rules/invalid-regex-rule.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | const { buildCreateFunction } = require('../utils/create-utils.js') 4 | const { formatReportMessage } = require('../utils/report-utils.js') 5 | const { shouldCheck } = require('../utils/check-utils.js') 6 | 7 | const { REGEX_FLAGS_FIELD_DEFINITION, FILES_FIELD_DEFINITION } = require('./common-fields-definitions.js') 8 | 9 | /** 10 | * @param {string} source Text that was checked. 11 | * @param {number} foundAt Start position where the regex was found. 12 | * @returns {{ line: number, column: number }} Location required to report the error. 13 | */ 14 | function foundStartLocation(source, foundAt) { 15 | return Array.from(source.substring(0, foundAt)) 16 | .reduce((result, char) => char !== '\n' 17 | ? { line: result.line, column: result.column + 1 } 18 | : { line: result.line + 1, column: 0 }, 19 | { line: 1, column: 0 }) 20 | } 21 | 22 | /** 23 | * @typedef {{ $: [], matchStart: number, nextChar: number }} MatchDetail 24 | */ 25 | 26 | /** 27 | * @param {string} source Text to inspect 28 | * @param {{ regex: RegExp, id: string | undefined, message: string | undefined }} pattern Information of the pattern to be use to inspect. 29 | * @param {*} replace Replacement function 30 | * @param {Function} report Report function 31 | */ 32 | function checkRegex(source, pattern, replace, report) { 33 | /** 34 | * @param {string} source Text to be checked. 35 | * @param {RegExp} regex Regular Expression use to check. 36 | * @returns {MatchDetail} Result from the check. 37 | */ 38 | const checkRegexInSource = () => { 39 | const foundDetail = pattern.regex.exec(source) 40 | return !!foundDetail && { 41 | $: foundDetail, 42 | matchStart: foundDetail.index, 43 | nextChar: pattern.regex.lastIndex 44 | } 45 | } 46 | const message = formatReportMessage( 47 | pattern, 48 | from => `Invalid regular expression ${from} found` 49 | ) 50 | pattern.regex.test('') 51 | let matchDetail = { matchStart: -1 } 52 | while(matchDetail.matchStart !== matchDetail.nextChar && (matchDetail = checkRegexInSource())) { 53 | report({ 54 | loc: { start: foundStartLocation(source, matchDetail.matchStart) }, 55 | message, 56 | fix: replace(0, matchDetail) 57 | }) 58 | } 59 | } 60 | 61 | function buildReplacementFunction(replacement) { 62 | try { 63 | const replacementFunction = new Function('text', 'captured', '$', /\breturn\b/.test(replacement) ? replacement : `return ${replacement}`) // eslint-disable-line no-new-func 64 | return $ => { 65 | try { 66 | const replacement = replacementFunction($[0], $.slice(1), $) 67 | if (typeof replacement === 'string') { 68 | return replacement 69 | } 70 | } 71 | catch(e) {} 72 | return $[0] 73 | } 74 | } 75 | catch(e) { 76 | return null 77 | } 78 | } 79 | 80 | /** 81 | * @callback ReplacementFunction 82 | * @param {number} from Index of the source where the replacement start 83 | * @param {MatchDetail} matchDetail Information for replacement 84 | */ 85 | 86 | /** 87 | * @param {string | { function: string } | undefined} replacement Data to create the replacement function. 88 | * @returns {ReplacementFunction} Replacement function 89 | */ 90 | function createReplacement(replacement) { 91 | switch (typeof replacement) { 92 | case 'string': 93 | return (from, matchDetail) => fixer => fixer.replaceTextRange([from + matchDetail.matchStart, from + matchDetail.nextChar], replacement) 94 | case 'object':{ 95 | const replacementFunction = buildReplacementFunction(replacement.function) 96 | if (typeof replacementFunction === 'function') { 97 | return (from, matchDetail) => 98 | fixer => fixer.replaceTextRange( 99 | [from + matchDetail.matchStart, from + matchDetail.nextChar], 100 | replacementFunction(Array.from(matchDetail.$)) 101 | ) 102 | } 103 | } 104 | } 105 | return () => undefined 106 | } 107 | 108 | function checkPatterns(fileName, source, patterns, report) { 109 | patterns.forEach(pattern => shouldCheck(pattern.files, fileName) && 110 | checkRegex(source, pattern, createReplacement(pattern.details.replacement), report)) 111 | } 112 | 113 | module.exports = { 114 | meta: { 115 | type: 'suggestion', 116 | fixable: 'code', 117 | docs: { 118 | description: 'Invalid regular expressions to be reported', 119 | category: 'Stylistic Issues', 120 | url: 'https://eslint-plugin-regex.github.io/docs/rules/invalid-regex-rule.html' 121 | }, 122 | schema: [{ 123 | title: 'Invalid regular expressions', 124 | description: 'Invalid regular expressions settings', 125 | type: 'array', 126 | items: { 127 | oneOf: [{ 128 | title: 'Invalid pattern', 129 | description: 'Invalid pattern to be reported', 130 | type: 'string', 131 | minLength: 1 132 | }, { 133 | title: 'Invalid detailed pattern', 134 | description: 'Invalid pattern to be looked with possible custom message, custom ignored file pattern and custom inspect file pattern', 135 | type: 'object', 136 | properties: { 137 | id: { 138 | title: 'Invalid pattern Id', 139 | description: 'Invalid pattern Id to be reported', 140 | type: 'string', 141 | minLength: 2 142 | }, 143 | regex: { 144 | title: 'Invalid pattern', 145 | description: 'Invalid regular expression to look for', 146 | type: 'string', 147 | minLength: 1 148 | }, 149 | flags: REGEX_FLAGS_FIELD_DEFINITION, 150 | replacement: { 151 | oneOf:[{ 152 | title: 'Replacement', 153 | description: 'Replacement for invalid pattern', 154 | type: 'string' 155 | }, { 156 | title: 'Detailed replacement', 157 | description: 'Detailed replacements for invalid patterns', 158 | type: 'object', 159 | properties: { 160 | function: { 161 | title: 'Replacement function', 162 | description: 'Function used to replace the found pattern. It receives the found text and must return the replacement text', 163 | type: 'string', 164 | minLength: 1 165 | } 166 | }, 167 | minProperties: 1, 168 | maxProperties: 1 169 | }] 170 | }, 171 | message: { 172 | title: 'Invalid message', 173 | description: 'Message to be shown when Invalid pattern is found', 174 | type: 'string', 175 | minLength: 3 176 | }, 177 | files: FILES_FIELD_DEFINITION 178 | }, 179 | required: ['regex'] 180 | }] 181 | }, 182 | minItems: 1 183 | }, { 184 | title: 'Ignore file pattern', 185 | description: 'Regular expressions of the files to be ignored when validating all the defined patterns', 186 | type: 'string', 187 | minLength: 2 188 | }] 189 | }, 190 | create: buildCreateFunction(checkPatterns) 191 | } 192 | -------------------------------------------------------------------------------- /lib/rules/required-regex-rule.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | const { buildCreateFunction } = require('../utils/create-utils.js') 4 | const { formatReportMessage } = require('../utils/report-utils.js') 5 | const { shouldCheck } = require('../utils/check-utils.js') 6 | 7 | const { REGEX_FLAGS_FIELD_DEFINITION, FILES_FIELD_DEFINITION } = require('./common-fields-definitions.js') 8 | 9 | function checkPatterns(fileName, source, patterns, report, node) { 10 | patterns.forEach(pattern => { 11 | if (shouldCheck(pattern.files, fileName) && !pattern.regex.test(source)) { 12 | report({ 13 | node, 14 | message: formatReportMessage( 15 | pattern, 16 | from => `Required regular expression ${from} not found in file` 17 | ) 18 | }) 19 | } 20 | }) 21 | } 22 | 23 | module.exports = { 24 | meta: { 25 | type: 'suggestion', 26 | docs: { 27 | description: 'Required regular expressions to be looked', 28 | category: 'Stylistic Issues', 29 | url: 'https://eslint-plugin-regex.github.io/docs/rules/required-regex-rule.html' 30 | }, 31 | schema: [{ 32 | title: 'Required regular expressions', 33 | description: 'Required regular expressions settings', 34 | type: 'array', 35 | items: { 36 | oneOf: [{ 37 | title: 'Required pattern', 38 | description: 'Required pattern to be looked', 39 | type: 'string', 40 | minLength: 1 41 | }, { 42 | title: 'Required detailed pattern', 43 | description: 'Required pattern to be looked with possible custom message, custom ignored file pattern and custom inspect file pattern', 44 | type: 'object', 45 | properties: { 46 | id: { 47 | title: 'Required pattern Id', 48 | description: 'Required pattern Id to be reported', 49 | type: 'string', 50 | minLength: 2 51 | }, 52 | regex: { 53 | title: 'Required pattern', 54 | description: 'Required regular expression to be looked', 55 | type: 'string', 56 | minLength: 1 57 | }, 58 | flags: REGEX_FLAGS_FIELD_DEFINITION, 59 | message: { 60 | title: 'Required message', 61 | description: 'Message to be shown when Required pattern is not found', 62 | type: 'string', 63 | minLength: 3 64 | }, 65 | files: FILES_FIELD_DEFINITION 66 | }, 67 | required: ['regex'] 68 | }] 69 | }, 70 | minItems: 1 71 | }, { 72 | title: 'Ignore file pattern', 73 | description: 'Regular expressions of the files to be ignored when validating all the defined patterns', 74 | type: 'string' 75 | }] 76 | }, 77 | create: buildCreateFunction(checkPatterns) 78 | } 79 | -------------------------------------------------------------------------------- /lib/utils/check-utils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | 4 | /** 5 | * @param {{ignore: RegExp, inspect: RegExp} | false} [files] Patterns that indicate which files to inspect and which to ignore. 6 | * @param {string} fileName Name of the file. 7 | * @returns {boolean} true if the file should be checked. 8 | */ 9 | module.exports.shouldCheck = function (files, fileName) { 10 | return !files || (!files.ignore.test(fileName) && files.inspect.test(fileName)) 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/create-utils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | const { fromOptions } = require('../utils/options-utils.js') 4 | 5 | module.exports.buildCreateFunction = function (checkPatterns) { 6 | return (context) => { 7 | const options = fromOptions(context.options) 8 | return { 9 | Program: function (node) { 10 | const fileName = context.getFilename() 11 | if (!options.ignoreFilePattern.test(context.getFilename())) { 12 | checkPatterns(fileName, context.getSourceCode(node).getText(), options.patterns, context.report, node) 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/options-utils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | 4 | function fitIgnoreFilePattern(pattern) { 5 | return new RegExp(pattern || '^$') 6 | } 7 | 8 | /** 9 | * @typedef {{ ignore: RegExp, inspect: RegExp }} FilesRegex 10 | */ 11 | 12 | /** 13 | * @param {{ ignore: string, inspect: string } | undefined} filesPatterns Information to create Regular Expressions. 14 | * @returns {FilesRegex | false} Regular Expressions for filtering files. 15 | */ 16 | function filesRegex(filesPatterns) { 17 | return !!filesPatterns && { 18 | ignore: fitIgnoreFilePattern(filesPatterns.ignore), 19 | inspect: new RegExp(filesPatterns.inspect || '^.*$') 20 | } 21 | } 22 | 23 | const VALID_FLAGS_REGEX = /[isu]/ 24 | 25 | /** 26 | * @param {string | undefined} flagsSource required flags for regex. 27 | * @returns {string} flags for regex to be used for linting. 28 | */ 29 | function buildRegExpFlags(flagsSource) { 30 | let flags = 'gm' 31 | if (flagsSource) { 32 | const E = flagsSource.length 33 | for(let e = 0;e !== E;e++) { 34 | const flag = flagsSource.charAt(e).toLowerCase() 35 | if(VALID_FLAGS_REGEX.test(flag) && flags.indexOf(flag) === -1) { 36 | flags += flag 37 | } 38 | } 39 | } 40 | return flags 41 | } 42 | 43 | /** 44 | * @param {string | {regex: string, flags: string}} regexSource source for pattern creation, obtained from rule configuration (e.g. .eslintrc.json). 45 | * @returns {{regex: RegExp, details: (Object | undefined), files: (FilesRegex | false)}} pattern to be used for linting. 46 | */ 47 | function extractPattern(regexSource) { 48 | return typeof regexSource !== 'string' 49 | ? { 50 | regex: new RegExp(regexSource.regex, buildRegExpFlags(regexSource.flags)), 51 | details: regexSource, 52 | files: filesRegex(regexSource.files) 53 | } 54 | : { 55 | regex: new RegExp(regexSource, 'gm'), 56 | details: {} 57 | } 58 | } 59 | 60 | /** 61 | * @param {*[]} options settings coming from rule configuration (e.g. .eslintrc.json). 62 | * @returns {Object} internal settings required by eslint-plugin-regex to work properly. 63 | */ 64 | module.exports.fromOptions = function (options) { 65 | return { 66 | ignoreFilePattern: fitIgnoreFilePattern(options[1]), 67 | patterns: options[0].map(extractPattern) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/utils/report-utils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | 4 | /** 5 | * @param {{ regex: RegExp, id: string | undefined, message: string | undefined }} pattern Information of the pattern used to inspect. 6 | * @param {Function} createMsg This function will create the message for Invalid or Required pattern. 7 | * @returns {string} Message to report an error. 8 | */ 9 | module.exports.formatReportMessage = function (pattern, createMsg) { 10 | return !!pattern.details.message 11 | ? pattern.details.message 12 | : !!pattern.details.id 13 | ? createMsg(`'${pattern.details.id}'`) 14 | : createMsg(`${pattern.regex.toString()}`) 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-regex", 3 | "description": "ESLint rules using Regular Expression", 4 | "version": "1.10.0", 5 | "license": "MIT", 6 | "author": "Gonzalo Müller Bravo", 7 | "main": "lib/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "homepage": "https://eslint-plugin-regex.github.io", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/gmullerb/eslint-plugin-regex" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/gmullerb/eslint-plugin-regex/issues" 18 | }, 19 | "keywords": [ 20 | "eslint-plugin-regex", 21 | "eslint", 22 | "regex-rule", 23 | "regex", 24 | "regexp", 25 | "eslint-regex", 26 | "eslint-rule", 27 | "eslint-regex-rule", 28 | "rule", 29 | "expression", 30 | "regular-expression", 31 | "eslint-regular-expression", 32 | "eslint-regular-expression-rule", 33 | "eslintrule", 34 | "regular-expression-rule", 35 | "eslintplugin", 36 | "eslint-plugin", 37 | "plugin", 38 | "coding style", 39 | "coding standards", 40 | "code checker", 41 | "code linter", 42 | "code quality", 43 | "code standards", 44 | "code style", 45 | "syntax", 46 | "lint", 47 | "linter", 48 | "jshint", 49 | "js-hint", 50 | "jslint", 51 | "js-lint", 52 | "js lint", 53 | "js quality", 54 | "js rules", 55 | "js rules", 56 | "js standards", 57 | "js style", 58 | "style guide", 59 | "style checker", 60 | "style linter", 61 | "standard", 62 | "standard style", 63 | "style" 64 | ], 65 | "scripts": { 66 | "lint": "npm run lint.common && npm run lint.source", 67 | "lint.common": "npm run title --title=\"Lint Common\" -s && eslint --config .eslintrc-any.json \"**/[\\.a-zA-Z]*.+(js|jsx|ts|tsx|json|yml|xml|sh|txt|md|svg|properties|gradle|java|cpp|c|html|css|groovy)\" \"**/.+(|gitignore|npmignore)\" --no-eslintrc --ignore-pattern \"build\" --ignore-pattern \"tests/lib/rules/case-*.*\"", 68 | "lint.source": "npm run title --title=\"Lint Source Code\" -s && eslint --color \"**/*.js\" --ignore-pattern \"tests/lib/rules/case-*.js\"", 69 | "test.only": "node tests/tests.js && node tests/lib/rules/e2e-tests.js", 70 | "test": "npm run title --title=\"Test With Coverage\" -s && mkdir -p build && nyc npm run test.only", 71 | "check": "npm run title --title=\"Install\" -s && npm install && npm run lint && npm test && mkdir -p build && cd build && npm pack ../", 72 | "postcheck": "echo \"check OK\"", 73 | "check.all": "npm run check && npm audit", 74 | "prepublishOnly": "npm run check", 75 | "prepack": "npm run title --title=\"Build Package\" -s", 76 | "title": "echo ==== \"\\033[1;;33m\"$npm_config_title\"\\033[0m\" ====" 77 | }, 78 | "engines": { 79 | "node": ">=6.0.0" 80 | }, 81 | "peerDependencies": { 82 | "eslint": ">=4.0.0" 83 | }, 84 | "devDependencies": { 85 | "any-eslint-parser": "^1.0.1", 86 | "eslint": "^8.13.0", 87 | "eslint-plugin-base-style-config": "^2.9.2", 88 | "eslint-plugin-import": "^2.25.4", 89 | "jasmine": "^4.0.2", 90 | "nyc": "^15.1.0" 91 | }, 92 | "nyc": { 93 | "check-coverage": true, 94 | "branches": 85, 95 | "functions": 50, 96 | "statements": 25, 97 | "reporter": [ 98 | "lcov", 99 | "cobertura", 100 | "text", 101 | "text-summary" 102 | ], 103 | "report-dir": "build/coverage", 104 | "temp-dir": "build/coverage" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/lib/index.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Gonzalo Müller Bravo. 2 | // Licensed under the MIT License (MIT), see LICENSE.txt 3 | const { addRegexRuleName, rules } = require('../../lib') 4 | const invalidRegexRule = require('../../lib/rules/invalid-regex-rule.js') 5 | const requiredRegexRule = require('../../lib/rules/required-regex-rule.js') 6 | 7 | describe('index tests', () => { 8 | it('should add invalid regex rule name when valid', () => { 9 | addRegexRuleName('someInvalid') 10 | 11 | expect(Object.keys(rules).find(rule => rule === 'someInvalid')).toBe('someInvalid') 12 | expect(rules.someInvalid).toBe(invalidRegexRule) 13 | }) 14 | 15 | it('should add invalid disuse regex rule name when valid', () => { 16 | addRegexRuleName('some-disuse') 17 | 18 | expect(Object.keys(rules).find(rule => rule === 'some-disuse')).toBe('some-disuse') 19 | expect(rules['some-disuse']).toBe(invalidRegexRule) 20 | }) 21 | 22 | it('should add invalid avoid regex rule name when valid', () => { 23 | addRegexRuleName('avoid some') 24 | 25 | expect(Object.keys(rules).find(rule => rule === 'avoid some')).toBe('avoid some') 26 | expect(rules['avoid some']).toBe(invalidRegexRule) 27 | }) 28 | 29 | it('should add required regex rule name when valid', () => { 30 | addRegexRuleName('someREQUIRED') 31 | 32 | expect(Object.keys(rules).find(rule => rule === 'someREQUIRED')).toBe('someREQUIRED') 33 | expect(rules.someREQUIRED).toBe(requiredRegexRule) 34 | }) 35 | 36 | it('should add required use regex rule name when valid', () => { 37 | addRegexRuleName('someUSE') 38 | 39 | expect(Object.keys(rules).find(rule => rule === 'someUSE')).toBe('someUSE') 40 | expect(rules.someUSE).toBe(requiredRegexRule) 41 | }) 42 | 43 | it('should not add regex rule name when invalid name', () => { 44 | addRegexRuleName('some') 45 | 46 | expect(Object.keys(rules).find(rule => rule === 'some')).toBeUndefined() 47 | }) 48 | 49 | describe('invalid rule names', () => { 50 | const rulesNames = ['required', 'required-warn', 'required-error', 'another-required', 'other-required', 'invalid', 'invalid-warn', 'invalid-error', 'another-invalid', 'other-invalid'] 51 | 52 | for(const ruleName of rulesNames) { 53 | const testRuleName = ruleName.toUpperCase() 54 | it(`should not add regex rule name when equal to ${ruleName}`, () => { 55 | try { 56 | addRegexRuleName(testRuleName) 57 | fail(`Should not add "${ruleName}" name`) 58 | } 59 | catch(error) { 60 | expect(error.toString()).toBe(`Error: "${testRuleName}" already defined as eslint-plugin-regex rule name`) 61 | } 62 | }) 63 | } 64 | 65 | it('should fail adding same rule name more than once', () => { 66 | addRegexRuleName('Same Required Rule Name') 67 | try { 68 | addRegexRuleName('Same Required Rule Name') 69 | fail('Should not add "Same Required Rule Name" name') 70 | } 71 | catch(error) { 72 | expect(error.toString()).toBe('Error: "Same Required Rule Name" already defined as eslint-plugin-regex rule name') 73 | } 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/lib/rules/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "global-require": "off", 4 | "no-console": "off", 5 | "no-throw-literal": "off", 6 | "max-lines": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/lib/rules/case-001-minified.js: -------------------------------------------------------------------------------- 1 | 2 | (function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V