Skip to content
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

fix: wrong suggestion and message in no-misleading-character-class #17571

Merged
merged 2 commits into from Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
32 changes: 32 additions & 0 deletions tests/lib/rules/no-misleading-character-class.js
Expand Up @@ -628,6 +628,38 @@ ruleTester.run("no-misleading-character-class", rule, {
suggestions: null
}]
},
{
code: String.raw`/[\ud83d\u{dc4d}]/u`,
env: { es2020: true },
ota-meshi marked this conversation as resolved.
Show resolved Hide resolved
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\u{d83d}\udc4d]/u`,
env: { es2020: true },
ota-meshi marked this conversation as resolved.
Show resolved Hide resolved
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\u{d83d}\u{dc4d}]/u`,
env: { es2020: true },
ota-meshi marked this conversation as resolved.
Show resolved Hide resolved
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},
{
code: String.raw`/[\uD83D\u{DC4d}]/u`,
env: { es2020: true },
ota-meshi marked this conversation as resolved.
Show resolved Hide resolved
errors: [{
messageId: "surrogatePair",
suggestions: null
}]
},


// ES2024
Expand Down