eslint-plugin-regex


ESLint rules using Regular Expressions

eslint-plugin-regex eslint-plugin-regex   License  Github repo Gitlab repo


Quick Start

1 . Add dependencies:

package.json:

  "engines" : {
    "node" : ">=6.0.0"
  },
  "devDependencies": {
    "eslint": ">=4.0.0",
    "eslint-plugin-regex": "1.10.0",

2 . Configure eslint:

Short configuration:

.eslintrc.json:

{
  "plugins": [
    "regex"
  ],
  "rules": {
    "regex/invalid": [
      "error", [
        "invalidRegex1",
        "invalidRegexN"
      ]
    ],
    "regex/required": [
      "error", [
        "requiredRegex1",
        "requiredRegexN"
      ],
      "ignoreFilesRegex"
    ]
  }
}

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.

Detailed configuration:

.eslintrc.json:

{
  "plugins": [
    "regex"
  ],
  "rules": {
    "regex/invalid": [
      "error", [{
          "regex": "invalidRegex1",
          "replacement": "newValue"
        }, {
          "id": "regexIdN",
          "message": "errorMessageN",
          "regex": "invalidRegexN",
          "files": {
            "ignore": "ignoreFilesRegexN"
          }
        }
      ]
    ],
    "regex/required": [
      "error", [{
          "id": "regexId1",
          "regex": "requiredRegex1",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegex1"
          }
        }, {
          "regex": "requiredRegexN",
          "files": {
            "ignore": "ignoreFilesRegexA",
            "inspect": "inspectFilesRegexZ"
          }
        }
      ]
    ]
  }
}

Files will be checked for:


Goals

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.

Rules

Name Fixable Description
regex/invalid Yes checks that specified patterns are not found
regex/required No checks that specified patterns are found

Each rule defines a set of patterns:

Rules

📏 regex/invalid

This rule checks that specified patterns are not found in files, i.e. Invalid patterns.

✏ Example of incorrect code for this rule:

/* eslint regex/invalid: ['error', ['"']] */

const text = 'Hello "My Friend"'

The error message will reflect the exact location, e.g.:

/path/to/some.js
 34:25  error  Invalid regular expression /"/gm found  regex/invalid

✏ Example of correct code for this rule:

/* eslint regex/invalid: ['error', ['"']] */

const text = 'Hello \'My Friend\''

📏 regex/required

This rule looks for specific patterns that must be present in each file, i.e. Required patterns.

✏ Example of incorrect code for this rule:

/* eslint regex/required: ["error", ["^// Copyright My Friend"]] */

const text = 'Hello "My Friend"'

The error message will point to the beginning of the file, e.g.:

/path/to/some.js
 1:1  error  Required regular expression /^\/\/ Copyright My Friend/gm not found in file  regex/required

✏ Example of correct code for this rule:

/* eslint regex/required: ["error", ["^// Copyright My Friend"]] */

// Copyright My Friend
const text = 'Hello "My Friend"'

Options

Both rule has two options:

[
  "error",
  [
    "regex1",
    "regexN"
  ],
  "ignoreFilesRegex"
]

The string representing the regular expression

Remember, Slashes (/) are not required in the string that defines the regex,

e.g. To get the following regex /^(test|spec)$/, define:

e.g. To get the following regex /\bhttp:/, define:

e.g. To get the following regex /.*test\.js/, define:

Short pattern definition

Each pattern is specified by just a string representing the regular expression, i.e. "regex"

{
  "regex/invalid": [
    "error",
    [
      "invalidRegex1",
      "invalidRegexN"
    ]
  ],
  "regex/required": [
    "error",
    [
      "requiredRegex1",
      "requiredRegexN"
    ]
  ]
}

Detailed pattern definition

It is specified by an object, with the following fields:

{
  "id": "regexId",
  "regex": "regex",
  "flags": "isu",
  "replacement": "replacementString",
  "message": "errorMessage",
  "files": {
    "ignore": "ignoreFilesRegex",
    "inspect": "inspectFilesRegex"
  }
}

[1] In order to fix issues eslint must be run with --fix option.

Using message is pretty useful since it will give a better understanding to the developer when an error happens:

e.g. Given the following definition:

{
  "regex": "someRegex",
  "message": "The Useful Error MessagE"
}

then shown error will be similar to:

/path/to/some.js
 1:1  error  The Useful Error MessagE  regex/required

or

/path/to/some.js
 34:25  error  The Useful Error MessagE  regex/invalid

instead of

/path/to/some.js
 1:1  error  Required regular expression /someRegex/gm not found in file  regex/required

or

/path/to/some.js
 34:25  error  Invalid regular expression /someRegex/gm found  regex/invalid
Definition of the Function used to replace the invalid found pattern

Definition of the function must be done as a string in 1 line, and the following rules apply:

Function will receive 3 parameters, to be used as desired:

Using parameter text

e.g.

  function(text, captured, $) {
    return text.trim()
  }

"return text.trim()" => only the body of the function + returns a string value based on text

Having the following rule in .eslintrc.json:

{
  "id": "regexIdN",
  "regex": "\\serror\\w*\\s",
  "replacement": {
    "function": "return text.trim()"
  }
}

or using $:

{
  "id": "regexIdN",
  "regex": "\\serror\\w*\\s",
  "replacement": {
    "function": "return $[0].trim()"
  }
}

then, given:

example.js

const exception = " error19 "

when linting with fix, the result will be:

const exception = "error19"

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:

{
  "id": "regexIdN",
  "regex": "\\serror\\w*\\s",
  "replacement": {
    "function": "text.trim()"
  }
}

or

{
  "id": "regexIdN",
  "regex": "\\serror\\w*\\s",
  "replacement": {
    "function": "$[0].trim()"
  }
}

Using parameter captured

e.g.

"return captured[0]" => only the body of the function + returns a string value based on captured

Having the following rule in .eslintrc.json:

{
  "id": "regexIdN",
  "regex": "\\serror(\\w*)\\s",
  "replacement": {
    "function": "return captured[0]"
  }
}

or using $:

{
  "id": "regexIdN",
  "regex": "\\serror(\\w*)\\s",
  "replacement": {
    "function": "return $[1]"
  }
}

then, given:

example.js

const exception = " error19 "

when linting with fix, the result will be:

const exception = "19"

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:

{
  "id": "regexIdN",
  "regex": "\\serror(\\w*)\\s",
  "replacement": {
    "function": "captured[0]"
  }
}

or

{
  "id": "regexIdN",
  "regex": "\\serror(\\w*)\\s",
  "replacement": {
    "function": "$[1]"
  }
}

Using parameters text and captured

e.g.

"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

Having the following rule in .eslintrc.json:

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "return text + ' = ' + captured[0]  + ' + ' + captured[1] + ' = ' + (parseInt(captured[0]) + parseInt(captured[1]))"
  }
}

or using $:

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "return $[0] + ' = ' + $[1]  + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))"
  }
}

or :

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "return text + ' = ' + $[1]  + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))"
  }
}

or :

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "return `${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`"
  }
}

then, given:

example.js

const sum = "4+5"

when linting with fix, the result will be:

const sum = "4+5 = 4 + 5 = 9"

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:

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "text + ' = ' + $[1]  + ' + ' + $[2] + ' = ' + (parseInt($[1]) + parseInt($[2]))"
  }
}

or :

{
  "id": "regexIdN",
  "regex": "(\\d+)\\+(\\d+)",
  "replacement": {
    "function": "`${text} = ${captured[0]} + ${captured[1]} = ${parseInt($[1]) + parseInt($[2])}`"
  }
}

When return keyword is required

e.g.

e.g. const result = text === 'superb' ? 'Superb' : text; return result => only the body of the function + returns a string value based on text.

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:

{
  "id": "regexIdN",
  "regex": "\\w+",
  "replacement": {
    "function": "const result = text === 'superb' ? 'Superb' : text; return result"
  }
}

Some cases may use Comma operator, e.g. "function": "result = text === 'superb' ? 'Superb' : text, result"

e.g. return text === 'return' ? 'Return' : text => only the body of the function + returns a string value based on text.

Since the exact word return is present, this will required return, then rule definition will be as usual:

{
  "id": "regexIdN",
  "regex": "\\w+",
  "replacement": {
    "function": "return text === 'return' ? 'Return' : text"
  }
}

Following case does not required return:

e.g. return text === 'Return' ? 'RETURN' : text => only the body of the function + returns a string value based on text.

Since the exact word return is not present, this will allow the following rule definition to be:

{
  "id": "regexIdN",
  "regex": "\\w+",
  "replacement": {
    "function": "text === 'Return' ? 'RETURN' : text"
  }
}
Debugging of the Replacement Function for invalid found pattern
{
  "regex": "\\serror(\\w*)\\s",
  "replacement": {
    "function": "const extract = captured[0]; console.log(extract); return extract"
  }
}
RegExp Flags

The following flags can be add to the regex:

To define the flags to be used, employ the field flags in the detailed pattern:

By default, "gm" is always added by the engine (since It’s required).

e.g.

Having the following detailed pattern:

{
  "regex": "invalid",
  "flags": "i"
}

Invalid, inValid, INvalid or INVALID will match.

String to Regular expression conversion

Internally, each string from the array will be converted into a Regular Expression with global and multiline options, e.g.:

"someRegex" will be transformed into /someRegex/gm

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:".

Empty Meta characters

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.

e.g.

{
  "regex": "^(?!(?:(feature|fix|docs|config|refactor|revert|test).*[\\.:]$)|(\\*\\s\\w.*\\.$)|$)"
}

/path/to/some.js:

config(ALL):

* Use eslint-plugin-regex for commit message linting
* Use eslint-plugin-regex for commit message linting

When linting, eslint-plugin-regex will only report the first case:

/path/to/some.js
 3:1  error  Invalid regular expression /^(?!(?:(feature|fix|docs|config|refactor|revert|test).*[\\.:]$)|(\\*\\s\\w.*\\.$)|$)/gm found  regex/invalid

4:1 error will not be reported until 3:1 is fixed.

The issue is that having an empty match does not allow the regex engine to move forward.

Error report

The ‘Short pattern definition’ errors are reported with the following structure:

Given someRegex, the following message will be shown on error:

Invalid regular expression /someRegex/gm found

or

Required regular expression /someRegex/gm not found in file

The ‘Detailed pattern definition’ errors are reported with the following rules:

A . If message is present then that exact message is reported.
B . If id is present then:

Given "id": "someRegexId", the following message will be shown on error:

Invalid regular expression 'someRegexId' found

or

Required regular expression 'someRegexId' not found in file

C . If neither message nor id is present then the ‘Short pattern definition’ error message is shown.

Mixing

Mixing pattern types

It is possible to use both type of definitions, ‘Short pattern definition’ with ‘Detailed pattern definition’, in the array of patterns.

.eslintrc.json:

{
  "plugins": [
    "regex"
  ],
  "rules": {
    "regex/invalid": [
      "error", [
        "invalidRegex1",
        "invalidRegex2",
        {
          "regex": "invalidRegex3",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegex1"
          }
        },
        {
          "id": "regexIdN",
          "regex": "invalidRegexN",
          "files": {
            "ignore": "ignoreFilesRegexN"
          }
        }
      ]
    ]
  }
}

Mixing rules

Mixing error levels

Rules names have synonyms:

regex/invalid = regex/invalid-warn = regex/invalid-error = regex/another-invalid = regex/other-invalid.

regex/required = regex/required-warn = regex/required-error = regex/another-required = regex/other-required.

It is possible to set different error level: error, warn and off. For this use a synonym for the regex rule name:

.eslintrc.json:

{
  "plugins": [
    "regex"
  ],
  "rules": {
    "regex/invalid": [
      "error",
      [
        "invalidRegex1",
        "invalidRegexN"
      ]
    ],
    "regex/required": [
      "error",
      [
        "requiredRegex1",
        "requiredRegexN"
      ]
    ],
    "regex/invalid-error": [
      "error", [
        "invalidRegexA1",
        "invalidRegexA2",
        {
          "regex": "invalidRegexA3",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegexA1"
          }
        },
        {
          "id": "regexIdN",
          "regex": "invalidRegexN",
          "files": {
            "ignore": "ignoreFilesRegexAN"
          }
        }
      ],
    ],
    "regex/invalid-warn": [
      "warn", [
        "invalidRegexB1",
        "invalidRegexB2",
        {
          "regex": "invalidRegexB3",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegex1"
          }
        },
        {
          "id": "regexIdN",
          "regex": "invalidRegexBN",
          "files": {
            "ignore": "ignoreFilesRegexN"
          }
        }
      ],
    ],
    "regex/other-invalid": [
      "off", [
        "invalidRegexC1",
        "invalidRegexC2",
        {
          "regex": "invalidRegexB3",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegexC1"
          }
        },
        {
          "id": "regexIdN",
          "regex": "invalidRegexBN",
          "files": {
            "ignore": "ignoreFilesRegexCN"
          }
        }
      ],
    ],
    "regex/required-warn": [
      "warn",
      [
        "requiredRegexA1",
        "requiredRegexAN"
      ]
    ]
  }
}
Custom set of regex rules

Creating and Using a Custom Set of regex rules requires using js files.

Named Regex Rules approach

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).

Mixed Rules

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.

addRegexRuleName must be used to add the custom regex rule name to the set of eslint-plugin-regex rules.

const { addRegexRuleName } = require('eslint-plugin-regex')

addRegexRuleName('*invalid*')
addRegexRuleName('*required*')
Error: Cannot read config file: /path/to/.eslintrc.js
Error: "SomeRuleName" already defined as eslint-plugin-regex rule name

Local Custom Regex rules

Create a local .eslintrc.js:

1 . Add rule name using addRegexRuleName.
2 . Define eslint-plugin-regex custom regex rule.

const { addRegexRuleName } = require('eslint-plugin-regex')

addRegexRuleName('invalid-custom-890')

module.exports = {
  plugins: [ 'regex' ],
  rules: {
    'regex/invalid-custom-890': [
      'error', [
        {
          regex: 'invalidRegexBN',
          files: {
            ignore: 'ignoreFilesRegexCN'
          }
        }
      ]
    ]
  }
}

Custom Regex rules package

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.

Custom package index.js:

const { addRegexRuleName } = require('eslint-plugin-regex')

addRegexRuleName('invalid-custom-890')

module.exports = {
  configs: {
    'someRegexRule1': {
      plugins: [ 'regex' ],
      rules: {
        'regex/invalid-custom-890': [
          'error', [
            {
              regex: 'invalidRegexBN',
              files: {
                ignore: 'ignoreFilesRegexCN'
              }
            }
          ]
        ]
      }
    }
  }
}

An online example can be checked at eslint-plugin-base-style-config.
For more information on how to create a custom ESLint package check ESLint official documentation: Working with Plugins

then use it,

Some project .eslintrc.json:

  { "extends": [ "plugin:the-eslint-plugin/someRegexRule1",

to change the default error level set by the package:

  {
    "extends": [ "plugin:the-eslint-plugin/someRegexRule1" ],
    "rules": {
      "regex/invalid-custom-890": "warn"

mixing with other regex rules:

{
  "extends": [ "plugin:the-eslint-plugin/someRegexRule1" ],
  "rules": {
    "regex/invalid-custom-890": "warn",
    "regex/required": [
      "error",
      [
        "requiredRegex1",
        "requiredRegexN"
      ]
    ],
    "regex/invalid-error": [
      "error", [
        "invalidRegexA1",
        "invalidRegexA2",
        {
          "regex": "invalidRegexA3",
          "message": "errorMessage1",
          "files": {
            "inspect": "inspectFilesRegexA1"
          }
        },
        {
          "id": "regexIdN",
          "regex": "invalidRegexN",
          "files": {
            "ignore": "ignoreFilesRegexAN"
          }
        }
      ],
    ],

Advantages

const { addRegexRuleName } = require('eslint-plugin-regex')

addRegexRuleName('required-custom-896')

then, if an error happens, the output will be something similar to:

/path/to/some.js
  1:1  error  Required regular expression /requiredRegex/gm not found in file  regex/required-custom-896

instead of

/path/to/some.js
  1:1  error  Required regular expression /requiredRegex/gm not found in file  regex/required
Import/Export approach

Create a custom npm package using either with json or js files and add the custom regex rules.

Custom package index.js:

with complete rule definition:

module.exports = {
  regex: 'invalidRegexBN',
  files: {
    ignore: 'ignoreFilesRegexCN'
  }
}

or

module.exports = {
  regex: 'invalidRegexBN',
}

or with only regex definition:

module.exports = 'invalidRegexBN'

or with multiple complete rule definition:

module.exports = {
 ruleName1: {
    regex: 'invalidRegex1',
    files: {
      ignore: 'ignoreFilesRegex1'
    }
  },
  ruleNameN: {
    regex: 'invalidRegexN',
    files: {
      ignore: 'ignoreFilesRegexN'
    }
  }
}

or

module.exports = {
  ruleName1: {
    regex: 'invalidRegex1',
  },
  ruleNameN: {
    regex: 'invalidRegexN',
  }
}

or with multiple only regex definition:

module.exports = {
    ruleName1: 'invalidRegex1',
    ruleNameN: 'invalidRegexN'
}

or using json files:

{
  "regex": "invalidRegexBN",
  "files": {
    "ignore": "ignoreFilesRegexCN"
  }
}

or

{
  "regex": "invalidRegexBN",
}

or

{
 "ruleName1": {
    "regex": "invalidRegex1",
    "files": {
      "ignore": "ignoreFilesRegex1"
    }
  },
  "ruleNameN": {
    "regex": "invalidRegexN",
    "files": {
      "ignore": "ignoreFilesRegexN"
    }
  }
}

or

{
  "ruleName1": {
    "regex": "invalidRegex1",
  },
  "ruleNameN": {
    "regex": "invalidRegexN",
  }
}

or

{
    "ruleName1": "invalidRegex1",
    "ruleNameN": "invalidRegexN"
}

Different approaches can be defined, these are only a glance. For more information on how to create a custom npm package check Contributing packages to the registry

Then use the custom package:

Some project .eslintrc.js:

  import * as SomeESLintSetOfRegexRulesPackage1 from 'the-custom-package1'
  import * as SomeESLintSetOfRegexRulesPackage2 from 'the-custom-package2'

  module.exports = {
    plugins: ["regex"],
    rules: {
      "regex/invalid": [
        'error', [
          SomeESLintSetOfRegexRulesPackage1.ruleName1,
          SomeESLintSetOfRegexRulesPackage1.ruleNameN,
          SomeESLintSetOfRegexRulesPackage2.ruleName1,
          SomeESLintSetOfRegexRulesPackage2.ruleNameN
        ]
      ],

or using synonyms to mix error levels:

  import * as SomeESLintSetOfRegexRulesPackage1 from 'the-custom-package1'
  import * as SomeESLintSetOfRegexRulesPackage2 from 'the-custom-package2'

  module.exports = {
    plugins: ["regex"],
    rules: {
      'regex/invalid-error': [
        'error', [
          SomeESLintSetOfRegexRulesPackage1.ruleNameN,
          SomeESLintSetOfRegexRulesPackage2.ruleName1
        ]
      ],
      'regex/invalid-warn': [
        'warn', [
          SomeESLintSetOfRegexRulesPackage1.ruleName1,
          SomeESLintSetOfRegexRulesPackage2.ruleNameN
        ]
      ]

regex/invalid vs regex/required

Both rule were design with binary approach:

Array of patterns represent different logical operation for each rule:

Examples

Check:


Prerequisites


Evolution

CHANGELOG.md: contains the information about changes in each version, chronologically ordered (Keep a Changelog).

Extending/Developing

Developing

Contributing

License

MIT License


Remember

Additional words

Don’t forget:

At life:

At work: