Skip to content

Commit d85efad

Browse files
authoredApr 26, 2023
perf: don't use grapheme-splitter on ASCII strings in key-spacing rule (#17122)
* perf: don't use `grapheme-splitter` on ASCII strings in key-spacing rule * extract to string-utils * remove extra blank line
1 parent af5fe64 commit d85efad

File tree

4 files changed

+91
-45
lines changed

4 files changed

+91
-45
lines changed
 

‎lib/rules/id-length.js

+2-35
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,8 @@
99
//------------------------------------------------------------------------------
1010
// Requirements
1111
//------------------------------------------------------------------------------
12-
const GraphemeSplitter = require("grapheme-splitter");
1312

14-
//------------------------------------------------------------------------------
15-
// Helpers
16-
//------------------------------------------------------------------------------
17-
18-
/**
19-
* Checks if the string given as argument is ASCII or not.
20-
* @param {string} value A string that you want to know if it is ASCII or not.
21-
* @returns {boolean} `true` if `value` is ASCII string.
22-
*/
23-
function isASCII(value) {
24-
if (typeof value !== "string") {
25-
return false;
26-
}
27-
return /^[\u0020-\u007f]*$/u.test(value);
28-
}
29-
30-
/** @type {GraphemeSplitter | undefined} */
31-
let splitter;
32-
33-
/**
34-
* Gets the length of the string. If the string is not in ASCII, counts graphemes.
35-
* @param {string} value A string that you want to get the length.
36-
* @returns {number} The length of `value`.
37-
*/
38-
function getStringLength(value) {
39-
if (isASCII(value)) {
40-
return value.length;
41-
}
42-
if (!splitter) {
43-
splitter = new GraphemeSplitter();
44-
}
45-
return splitter.countGraphemes(value);
46-
}
13+
const { getGraphemeCount } = require("../shared/string-utils");
4714

4815
//------------------------------------------------------------------------------
4916
// Rule Definition
@@ -169,7 +136,7 @@ module.exports = {
169136
const name = node.name;
170137
const parent = node.parent;
171138

172-
const nameLength = getStringLength(name);
139+
const nameLength = getGraphemeCount(name);
173140

174141
const isShort = nameLength < minLength;
175142
const isLong = nameLength > maxLength;

‎lib/rules/key-spacing.js

+2-8
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@
99
//------------------------------------------------------------------------------
1010

1111
const astUtils = require("./utils/ast-utils");
12-
const GraphemeSplitter = require("grapheme-splitter");
13-
14-
const splitter = new GraphemeSplitter();
15-
16-
//------------------------------------------------------------------------------
17-
// Helpers
18-
//------------------------------------------------------------------------------
12+
const { getGraphemeCount } = require("../shared/string-utils");
1913

2014
/**
2115
* Checks whether a string contains a line terminator as defined in
@@ -523,7 +517,7 @@ module.exports = {
523517
const startToken = sourceCode.getFirstToken(property);
524518
const endToken = getLastTokenBeforeColon(property.key);
525519

526-
return splitter.countGraphemes(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));
520+
return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));
527521
}
528522

529523
/**

‎lib/shared/string-utils.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@
55

66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const GraphemeSplitter = require("grapheme-splitter");
13+
14+
//------------------------------------------------------------------------------
15+
// Helpers
16+
//------------------------------------------------------------------------------
17+
18+
// eslint-disable-next-line no-control-regex -- intentionally including control characters
19+
const ASCII_REGEX = /^[\u0000-\u007f]*$/u;
20+
21+
/** @type {GraphemeSplitter | undefined} */
22+
let splitter;
23+
24+
//------------------------------------------------------------------------------
25+
// Public Interface
26+
//------------------------------------------------------------------------------
27+
828
/**
929
* Converts the first letter of a string to uppercase.
1030
* @param {string} string The string to operate on
@@ -17,6 +37,24 @@ function upperCaseFirst(string) {
1737
return string[0].toUpperCase() + string.slice(1);
1838
}
1939

40+
/**
41+
* Counts graphemes in a given string.
42+
* @param {string} value A string to count graphemes.
43+
* @returns {number} The number of graphemes in `value`.
44+
*/
45+
function getGraphemeCount(value) {
46+
if (ASCII_REGEX.test(value)) {
47+
return value.length;
48+
}
49+
50+
if (!splitter) {
51+
splitter = new GraphemeSplitter();
52+
}
53+
54+
return splitter.countGraphemes(value);
55+
}
56+
2057
module.exports = {
21-
upperCaseFirst
58+
upperCaseFirst,
59+
getGraphemeCount
2260
};

‎tests/lib/shared/string-utils.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,23 @@
1111

1212
const assert = require("chai").assert;
1313

14-
const { upperCaseFirst } = require("../../../lib/shared/string-utils");
14+
const { upperCaseFirst, getGraphemeCount } = require("../../../lib/shared/string-utils");
15+
16+
//------------------------------------------------------------------------------
17+
// Helpers
18+
//------------------------------------------------------------------------------
19+
20+
/**
21+
* Replaces raw control characters with the `\xXX` form.
22+
* @param {string} text The text to process.
23+
* @returns {string} `text` with escaped control characters.
24+
*/
25+
function escapeControlCharacters(text) {
26+
return text.replace(
27+
/[\u0000-\u001F\u007F-\u009F]/gu, // eslint-disable-line no-control-regex -- intentionally including control characters
28+
c => `\\x${c.codePointAt(0).toString(16).padStart(2, "0")}`
29+
);
30+
}
1531

1632
//------------------------------------------------------------------------------
1733
// Tests
@@ -39,3 +55,34 @@ describe("upperCaseFirst", () => {
3955
assert(upperCaseFirst("") === "");
4056
});
4157
});
58+
59+
describe("getGraphemeCount", () => {
60+
/* eslint-disable quote-props -- Make consistent here for readability */
61+
const expectedResults = {
62+
"": 0,
63+
"a": 1,
64+
"ab": 2,
65+
"aa": 2,
66+
"123": 3,
67+
"cccc": 4,
68+
[Array.from({ length: 128 }, (_, i) => String.fromCharCode(i)).join("")]: 128, // all ASCII characters
69+
"👍": 1, // 1 grapheme, 1 code point, 2 code units
70+
"👍👍": 2,
71+
"👍9👍": 3,
72+
"a👍b": 3,
73+
"👶🏽": 1, // 1 grapheme, 2 code points, 4 code units
74+
"👨‍👩‍👦": 1, // 1 grapheme, 5 code points, 8 code units
75+
"👨‍👩‍👦👨‍👩‍👦": 2,
76+
"👨‍👩‍👦a👨‍👩‍👦": 3,
77+
"a👨‍👩‍👦b👨‍👩‍👦c": 5,
78+
"👨‍👩‍👦👍": 2,
79+
"👶🏽👨‍👩‍👦": 2
80+
};
81+
/* eslint-enable quote-props -- Make consistent here for readability */
82+
83+
Object.entries(expectedResults).forEach(([key, value]) => {
84+
it(`should return ${value} for ${escapeControlCharacters(key)}`, () => {
85+
assert.strictEqual(getGraphemeCount(key), value);
86+
});
87+
});
88+
});

0 commit comments

Comments
 (0)
Please sign in to comment.