Skip to content

Commit bba0bde

Browse files
committed
BREAKING CHANGE: prevents the usage of Intl.NumberFormat for performance purposes
1 parent b99a113 commit bba0bde

File tree

7 files changed

+114
-7
lines changed

7 files changed

+114
-7
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This code should trigger the ESLint rule `avoidIntlNumberFormatRule`
2+
3+
const number = 1234567.89;
4+
5+
// Incorrect usage: This will be flagged by the ESLint rule
6+
const formatter = new Intl.NumberFormat("en-US", {
7+
style: "currency",
8+
currency: "USD",
9+
});
10+
11+
const formattedNumber = formatter.format(number);
12+
13+
// eslint-disable-next-line no-console
14+
console.log(formattedNumber); // Outputs: $1,234,567.89

packages/eslint-plugin/README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,14 @@ This plugin exports some custom rules that you can optionally use in your projec
107107
🧪 Set in the `tests` configuration.\
108108
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
109109

110-
| Name | Description | 💼 | 🔧 |
111-
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--------------------- | :-- |
112-
| [await-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/await-user-event.md) | Enforces awaiting userEvent calls | 🧪 | 🔧 |
113-
| [no-different-displayname](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-different-displayname.md) | Enforce component displayName to match with component name || 🔧 |
114-
| [no-animated-without-native-driver](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-animated-without-native-driver.md) | Disallow the use of `Animated` with `useNativeDriver: false` | ![badge-performance][] | |
115-
| [prefer-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/prefer-user-event.md) | Enforces usage of userEvent over fireEvent in tests. | | 🔧 |
116-
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |
110+
| Name | Description | 💼 | 🔧 |
111+
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :--------------------- | :-- |
112+
| [avoid-intl-number-format](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/avoid-intl-number-format.md) | Disallow the use of `Intl.NumberFormat` due to potential performance issues. | ![badge-performance][] | |
113+
| [await-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/await-user-event.md) | Enforces awaiting userEvent calls | 🧪 | 🔧 |
114+
| [no-different-displayname](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-different-displayname.md) | Enforce component displayName to match with component name || 🔧 |
115+
| [no-animated-without-native-driver](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-animated-without-native-driver.md) | Disallow the use of `Animated` with `useNativeDriver: false` | ![badge-performance][] | |
116+
| [prefer-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/prefer-user-event.md) | Enforces usage of userEvent over fireEvent in tests. | | 🔧 |
117+
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |
117118
| Name                              |
118119

119120
<!-- end auto-generated rules list -->
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Disallow the use of `Intl.NumberFormat` due to potential performance issues (`@bam.tech/avoid-intl-number-format`)
2+
3+
💼 This rule is enabled in the `performance` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Prevents from the using `Intl.NumberFormat` to improve performance.

packages/eslint-plugin/lib/configs/performance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const performanceConfig = defineConfig({
3232
},
3333
],
3434
"@bam.tech/no-animated-without-native-driver": "error",
35+
"@bam.tech/avoid-intl-number-format": "error",
3536
},
3637
overrides: [
3738
{
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Rule } from "eslint";
2+
3+
// Custom Rule: No Intl.NumberFormat Usage
4+
export const avoidIntlNumberFormatRule: Rule.RuleModule = {
5+
meta: {
6+
type: "problem",
7+
docs: {
8+
description:
9+
"Disallow the use of `Intl.NumberFormat` due to potential performance issues.",
10+
category: "Possible Errors",
11+
recommended: true,
12+
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/no-intl-numberformat.md",
13+
},
14+
messages: {
15+
noIntlNumberFormat:
16+
"Avoid using `Intl.NumberFormat` as it can lead to performance issues. Consider using a lightweight formatting alternative or memoizing the formatter instance.",
17+
},
18+
schema: [],
19+
},
20+
21+
create(context) {
22+
return {
23+
NewExpression(node) {
24+
if (
25+
node.callee.type === "MemberExpression" &&
26+
node.callee.object.type === "Identifier" &&
27+
node.callee.object.name === "Intl" &&
28+
node.callee.property.type === "Identifier" &&
29+
node.callee.property.name === "NumberFormat"
30+
) {
31+
context.report({
32+
node,
33+
messageId: "noIntlNumberFormat",
34+
});
35+
}
36+
},
37+
};
38+
},
39+
};

packages/eslint-plugin/lib/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { avoidIntlNumberFormatRule } from "./avoid-intl-number-format";
12
import { awaitUserEventRule } from "./await-user-event";
23
import { noDifferentDisplaynameRule } from "./no-different-displayname";
34
import { noAnimatedWithoutNativeDriverRule } from "./no-animated-without-native-driver";
@@ -10,4 +11,5 @@ export default {
1011
"require-named-effect": requireNamedEffectRule,
1112
"no-different-displayname": noDifferentDisplaynameRule,
1213
"no-animated-without-native-driver": noAnimatedWithoutNativeDriverRule,
14+
"avoid-intl-number-format": avoidIntlNumberFormatRule,
1315
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Save without formatting: [⌘ + K] > [S]
2+
3+
// This should trigger an error breaking eslint-plugin-bam-custom-rules:
4+
// bam-custom-rules/avoid-intl-number-format
5+
6+
import { avoidIntlNumberFormatRule } from "../../../lib/rules/avoid-intl-number-format";
7+
import { RuleTester } from "eslint";
8+
9+
const ruleTester = new RuleTester({
10+
parser: require.resolve("@typescript-eslint/parser"),
11+
});
12+
13+
const valid = [
14+
` const formatCurrency = (number: number) => {
15+
return numeral(number).format('$0,0.00');
16+
};
17+
18+
const number = 1234567.89;
19+
console.log(formatCurrency(number));`,
20+
];
21+
22+
const invalid = [
23+
` const number = 1234567.89;
24+
25+
const formatter = new Intl.NumberFormat('en-US', {
26+
style: 'currency',
27+
currency: 'USD',
28+
});
29+
30+
const formattedNumber = formatter.format(number);
31+
32+
console.log(formattedNumber);`,
33+
];
34+
35+
ruleTester.run("no-animated-without-native-driver", avoidIntlNumberFormatRule, {
36+
valid,
37+
invalid: invalid.map((code) => ({
38+
code,
39+
errors: [
40+
"Avoid using `Intl.NumberFormat` as it can lead to performance issues. Consider using a lightweight formatting alternative or memoizing the formatter instance.",
41+
],
42+
})),
43+
});

0 commit comments

Comments
 (0)