Skip to content

Commit

Permalink
Add regexp/sort-flags rule (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Apr 20, 2021
1 parent b0308cb commit 4db794e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
| [regexp/prefer-t](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-t.html) | enforce using `\t` | :star::wrench: |
| [regexp/prefer-unicode-codepoint-escapes](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-unicode-codepoint-escapes.html) | enforce use of unicode codepoint escapes | :wrench: |
| [regexp/prefer-w](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-w.html) | enforce using `\w` | :star::wrench: |
| [regexp/sort-flags](https://ota-meshi.github.io/eslint-plugin-regexp/rules/sort-flags.html) | require regex flags to be sorted | :wrench: |

<!--RULES_TABLE_END-->
<!--RULES_SECTION_END-->
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
| [regexp/prefer-t](./prefer-t.md) | enforce using `\t` | :star::wrench: |
| [regexp/prefer-unicode-codepoint-escapes](./prefer-unicode-codepoint-escapes.md) | enforce use of unicode codepoint escapes | :wrench: |
| [regexp/prefer-w](./prefer-w.md) | enforce using `\w` | :star::wrench: |
| [regexp/sort-flags](./sort-flags.md) | require regex flags to be sorted | :wrench: |
53 changes: 53 additions & 0 deletions docs/rules/sort-flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "regexp/sort-flags"
description: "require regex flags to be sorted"
---
# regexp/sort-flags

> require regex flags to be sorted
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

The flags of JavaScript regular expressions should be sorted alphabetically
because the flags of the `.flags` property of `RegExp` objects are always
sorted. Not sorting flags in regex literals misleads readers into thinking that
the order may have some purpose which it doesn't.

<eslint-code-block fix>

```js
/* eslint regexp/sort-flags: "error" */

/* ✓ GOOD */
var foo = /abc/
var foo = /abc/iu
var foo = /abc/gimsuy

/* ✗ BAD */
var foo = /abc/mi
var foo = /abc/us
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :heart: Compatibility

This rule was taken from [eslint-plugin-clean-regex].
This rule is compatible with [clean-regex/sort-flags] rule.

[eslint-plugin-clean-regex]: https://github.com/RunDevelopment/eslint-plugin-clean-regex
[clean-regex/sort-flags]: https://github.com/RunDevelopment/eslint-plugin-clean-regex/blob/master/docs/rules/sort-flags.md

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/sort-flags.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/sort-flags.ts)
95 changes: 95 additions & 0 deletions lib/rules/sort-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Literal } from "estree"
import type { RegExpVisitor } from "regexpp/visitor"
import type { RegExpContext } from "../utils"
import { createRule, defineRegexpVisitor } from "../utils"

export default createRule("sort-flags", {
meta: {
docs: {
description: "require regex flags to be sorted",
// TODO Switch to recommended in the major version.
// recommended: true,
recommended: false,
},
fixable: "code",
schema: [],
messages: {
sortFlags:
"The flags '{{flags}}' should in the order '{{sortedFlags}}'.",
},
type: "suggestion", // "problem",
},
create(context) {
/**
* Report
*/
function report(
node: Literal,
flags: string,
sortedFlags: string,
flagsRange: [number, number],
) {
const sourceCode = context.getSourceCode()
context.report({
node,
loc: {
start: sourceCode.getLocFromIndex(flagsRange[0]),
end: sourceCode.getLocFromIndex(flagsRange[1]),
},
messageId: "sortFlags",
data: { flags, sortedFlags },
fix(fixer) {
return fixer.replaceTextRange(flagsRange, sortedFlags)
},
})
}

/**
* Sort regexp flags
*/
function sortFlags(flagsStr: string): string {
return [...flagsStr]
.sort((a, b) => a.codePointAt(0)! - b.codePointAt(0)!)
.join("")
}

/**
* Create visitor
*/
function createVisitor({
regexpNode,
}: RegExpContext): RegExpVisitor.Handlers {
if (regexpNode.type === "Literal") {
const flags = regexpNode.regex.flags
const sortedFlags = sortFlags(flags)
if (flags !== sortedFlags) {
report(regexpNode, flags, sortedFlags, [
regexpNode.range![1] - regexpNode.regex.flags.length,
regexpNode.range![1],
])
}
} else {
const flagsArg = regexpNode.arguments[1]
if (
flagsArg.type === "Literal" &&
typeof flagsArg.value === "string"
) {
const flags = flagsArg.value
const sortedFlags = sortFlags(flags)
if (flags !== sortedFlags) {
report(flagsArg, flags, sortedFlags, [
flagsArg.range![0] + 1,
flagsArg.range![1] - 1,
])
}
}
}

return {} // not visit RegExpNodes
}

return defineRegexpVisitor(context, {
createVisitor,
})
},
})
2 changes: 2 additions & 0 deletions lib/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import preferStarQuantifier from "../rules/prefer-star-quantifier"
import preferT from "../rules/prefer-t"
import preferUnicodeCodepointEscapes from "../rules/prefer-unicode-codepoint-escapes"
import preferW from "../rules/prefer-w"
import sortFlags from "../rules/sort-flags"

export const rules = [
confusingQuantifier,
Expand Down Expand Up @@ -97,4 +98,5 @@ export const rules = [
preferT,
preferUnicodeCodepointEscapes,
preferW,
sortFlags,
] as RuleModule[]
61 changes: 61 additions & 0 deletions tests/lib/rules/sort-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { RuleTester } from "eslint"
import rule from "../../../lib/rules/sort-flags"

const tester = new RuleTester({
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
},
})

tester.run("sort-flags", rule as any, {
valid: [
String.raw`/\w/i`,
String.raw`/\w/im`,
String.raw`/\w/gi`,
String.raw`/\w/gimsuy`,
String.raw`new RegExp("\\w", "i")`,
String.raw`new RegExp("\\w", "gi")`,
String.raw`new RegExp("\\w", "gimsuy")`,
String.raw`new RegExp("\\w", "dgimsuy")`,

// ignore
String.raw`
const flags = "yusimg"
new RegExp("\\w", flags)
`,
],
invalid: [
{
code: String.raw`/\w/yusimg`,
output: String.raw`/\w/gimsuy`,
errors: [
{
message: "The flags 'yusimg' should in the order 'gimsuy'.",
column: 5,
},
],
},
{
code: String.raw`new RegExp("\\w", "yusimg")`,
output: String.raw`new RegExp("\\w", "gimsuy")`,
errors: [
{
message: "The flags 'yusimg' should in the order 'gimsuy'.",
column: 20,
},
],
},
{
code: String.raw`new RegExp("\\w", "yusimgd")`,
output: String.raw`new RegExp("\\w", "dgimsuy")`,
errors: [
{
message:
"The flags 'yusimgd' should in the order 'dgimsuy'.",
column: 20,
},
],
},
],
})

0 comments on commit 4db794e

Please sign in to comment.