Skip to content

Commit

Permalink
fix: wrong suggestion and message in no-misleading-character-class (#…
Browse files Browse the repository at this point in the history
…17571)

* fix: wrong suggest and message in `no-misleading-character-class`

* Apply suggestions from code review

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

---------

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
ota-meshi and mdjermanovic committed Sep 18, 2023
1 parent 20893d4 commit aa1b657
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 15 deletions.
80 changes: 65 additions & 15 deletions lib/rules/no-misleading-character-class.js
Expand Up @@ -13,27 +13,34 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
// Helpers
//------------------------------------------------------------------------------

/**
* @typedef {import('@eslint-community/regexpp').AST.Character} Character
* @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
*/

/**
* Iterate character sequences of a given nodes.
*
* CharacterClassRange syntax can steal a part of character sequence,
* so this function reverts CharacterClassRange syntax and restore the sequence.
* @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
* @returns {IterableIterator<number[]>} The list of character sequences.
* @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
* @returns {IterableIterator<Character[]>} The list of character sequences.
*/
function *iterateCharacterSequence(nodes) {

/** @type {Character[]} */
let seq = [];

for (const node of nodes) {
switch (node.type) {
case "Character":
seq.push(node.value);
seq.push(node);
break;

case "CharacterClassRange":
seq.push(node.min.value);
seq.push(node.min);
yield seq;
seq = [node.max.value];
seq = [node.max];
break;

case "CharacterSet":
Expand All @@ -55,32 +62,74 @@ function *iterateCharacterSequence(nodes) {
}
}


/**
* Checks whether the given character node is a Unicode code point escape or not.
* @param {Character} char the character node to check.
* @returns {boolean} `true` if the character node is a Unicode code point escape.
*/
function isUnicodeCodePointEscape(char) {
return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
}

/**
* Each function returns `true` if it detects that kind of problem.
* @type {Record<string, (chars: Character[]) => boolean>}
*/
const hasCharacterSequence = {
surrogatePairWithoutUFlag(chars) {
return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c));
return chars.some((c, i) => {
if (i === 0) {
return false;
}
const c1 = chars[i - 1];

return (
isSurrogatePair(c1.value, c.value) &&
!isUnicodeCodePointEscape(c1) &&
!isUnicodeCodePointEscape(c)
);
});
},

surrogatePair(chars) {
return chars.some((c, i) => {
if (i === 0) {
return false;
}
const c1 = chars[i - 1];

return (
isSurrogatePair(c1.value, c.value) &&
(
isUnicodeCodePointEscape(c1) ||
isUnicodeCodePointEscape(c)
)
);
});
},

combiningClass(chars) {
return chars.some((c, i) => (
i !== 0 &&
isCombiningCharacter(c) &&
!isCombiningCharacter(chars[i - 1])
isCombiningCharacter(c.value) &&
!isCombiningCharacter(chars[i - 1].value)
));
},

emojiModifier(chars) {
return chars.some((c, i) => (
i !== 0 &&
isEmojiModifier(c) &&
!isEmojiModifier(chars[i - 1])
isEmojiModifier(c.value) &&
!isEmojiModifier(chars[i - 1].value)
));
},

regionalIndicatorSymbol(chars) {
return chars.some((c, i) => (
i !== 0 &&
isRegionalIndicatorSymbol(c) &&
isRegionalIndicatorSymbol(chars[i - 1])
isRegionalIndicatorSymbol(c.value) &&
isRegionalIndicatorSymbol(chars[i - 1].value)
));
},

Expand All @@ -90,9 +139,9 @@ const hasCharacterSequence = {
return chars.some((c, i) => (
i !== 0 &&
i !== lastIndex &&
c === 0x200d &&
chars[i - 1] !== 0x200d &&
chars[i + 1] !== 0x200d
c.value === 0x200d &&
chars[i - 1].value !== 0x200d &&
chars[i + 1].value !== 0x200d
));
}
};
Expand Down Expand Up @@ -120,6 +169,7 @@ module.exports = {

messages: {
surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
surrogatePair: "Unexpected surrogate pair in character class.",
combiningClass: "Unexpected combined character in character class.",
emojiModifier: "Unexpected modified Emoji in character class.",
regionalIndicatorSymbol: "Unexpected national flag in character class.",
Expand Down
28 changes: 28 additions & 0 deletions tests/lib/rules/no-misleading-character-class.js
Expand Up @@ -628,6 +628,34 @@ ruleTester.run("no-misleading-character-class", rule, {
suggestions: null
}]
},
{
code: String.raw`/[\ud83d\u{dc4d}]/u`,
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\u{d83d}\udc4d]/u`,
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\u{d83d}\u{dc4d}]/u`,
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\uD83D\u{DC4d}]/u`,
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},


// ES2024
Expand Down

0 comments on commit aa1b657

Please sign in to comment.