Skip to content

Add regexp/grapheme-string-literal rule #646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chatty-walls-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-regexp": minor
---

Add `regexp/grapheme-string-literal` rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ The `plugin:regexp/all` config enables all rules. It's meant for testing, not fo
| :----------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | :- | :- | :- | :- |
| [confusing-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/confusing-quantifier.html) | disallow confusing quantifiers | | ✅ | | |
| [control-character-escape](https://ota-meshi.github.io/eslint-plugin-regexp/rules/control-character-escape.html) | enforce consistent escaping of control characters | ✅ | | 🔧 | |
| [grapheme-string-literal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/grapheme-string-literal.html) | enforce single grapheme in string literal | | | | |
| [negation](https://ota-meshi.github.io/eslint-plugin-regexp/rules/negation.html) | enforce use of escapes on negation | ✅ | | 🔧 | |
| [no-dupe-characters-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-dupe-characters-character-class.html) | disallow duplicate characters in the RegExp character class | ✅ | | 🔧 | |
| [no-empty-string-literal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-string-literal.html) | disallow empty string literals in character classes | ✅ | | | |
Expand Down
55 changes: 55 additions & 0 deletions docs/rules/grapheme-string-literal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "regexp/grapheme-string-literal"
description: "enforce single grapheme in string literal"
---
# regexp/grapheme-string-literal

<!-- end auto-generated rule header -->

> enforce single grapheme in string literal

## :book: Rule Details

This rule is aimed to clarify the difference between using a string literal and normal disjunctions by not using string literals for purposes other than expressing a single grapheme.

<eslint-code-block>

```js
/* eslint regexp/grapheme-string-literal: "error" */

/* ✓ GOOD */
var foo = /[\p{RGI_Emoji}--\q{🇦🇨|🇦🇩|🇦🇪|🇦🇫|🇦🇬|🇦🇮|🇦🇱|🇦🇲|🇦🇴|🇦🇶|🇦🇷|🇦🇸|🇦🇹|🇦🇺|🇦🇼|🇦🇽|🇦🇿|🇧🇦|🇧🇧|🇧🇩|🇧🇪|🇧🇫|🇧🇬|🇧🇭|🇧🇮|🇧🇯}]/v
var foo = /[\q{a|b|c}]/v

/* ✗ BAD */
var foo = /[\q{abc|def}]/v
```

</eslint-code-block>

This rule does not report empty string literals. Use [regexp/no-empty-string-literal] and [regexp/no-empty-alternative] if you want to check them.

## :wrench: Options

Nothing.

## :couple: Related rules

- [regexp/no-empty-string-literal]
- [regexp/no-empty-alternative]
- [regexp/no-useless-string-literal]

[regexp/no-empty-string-literal]: ./no-empty-string-literal.md
[regexp/no-empty-alternative]: ./no-empty-alternative.md
[regexp/no-useless-string-literal]: ./no-useless-string-literal.md

## :rocket: Version

:exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/grapheme-string-literal.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/grapheme-string-literal.ts)
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ sidebarDepth: 0
| :-------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | :- | :- | :- | :- |
| [confusing-quantifier](confusing-quantifier.md) | disallow confusing quantifiers | | ✅ | | |
| [control-character-escape](control-character-escape.md) | enforce consistent escaping of control characters | ✅ | | 🔧 | |
| [grapheme-string-literal](grapheme-string-literal.md) | enforce single grapheme in string literal | | | | |
| [negation](negation.md) | enforce use of escapes on negation | ✅ | | 🔧 | |
| [no-dupe-characters-character-class](no-dupe-characters-character-class.md) | disallow duplicate characters in the RegExp character class | ✅ | | 🔧 | |
| [no-empty-string-literal](no-empty-string-literal.md) | disallow empty string literals in character classes | ✅ | | | |
Expand Down
49 changes: 49 additions & 0 deletions lib/rules/grapheme-string-literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { RegExpVisitor } from "@eslint-community/regexpp/visitor"
import type { RegExpContext } from "../utils"
import { createRule, defineRegexpVisitor } from "../utils"

const segmenter = new Intl.Segmenter()

export default createRule("grapheme-string-literal", {
meta: {
docs: {
description: "enforce single grapheme in string literal",
category: "Best Practices",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether this should be a best practice. The practice this rule enforces is good, because it brings regexes into a canonical form (e.g. don't use [\q{foo|bar}], use (?:foo|bar)), but that's more of a stylistic thing IMO.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you 👍 I will categorize it under "Stylistic Issues".

recommended: false,
},
schema: [],
messages: {
useSingleGrapheme: "Use single grapheme in string literal.",
},
type: "suggestion",
},
create(context) {
function createVisitor(
regexpContext: RegExpContext,
): RegExpVisitor.Handlers {
const { node, getRegexpLocation } = regexpContext

return {
onStringAlternativeEnter(saNode) {
if (saNode.elements.length <= 1) return
const string = String.fromCodePoint(
...saNode.elements.map((element) => element.value),
)

const segments = [...segmenter.segment(string)]
if (segments.length > 1) {
context.report({
node,
loc: getRegexpLocation(saNode),
messageId: "useSingleGrapheme",
})
}
},
}
}

return defineRegexpVisitor(context, {
createVisitor,
})
},
})
2 changes: 2 additions & 0 deletions lib/utils/rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RuleModule } from "../types"
import confusingQuantifier from "../rules/confusing-quantifier"
import controlCharacterEscape from "../rules/control-character-escape"
import graphemeStringLiteral from "../rules/grapheme-string-literal"
import hexadecimalEscape from "../rules/hexadecimal-escape"
import letterCase from "../rules/letter-case"
import matchAny from "../rules/match-any"
Expand Down Expand Up @@ -88,6 +89,7 @@ import useIgnoreCase from "../rules/use-ignore-case"
export const rules = [
confusingQuantifier,
controlCharacterEscape,
graphemeStringLiteral,
hexadecimalEscape,
letterCase,
matchAny,
Expand Down
Loading