Skip to content

Commit 21dc372

Browse files
NephtysFabien Bézaguutarwyn
authored
Add rule no-imported-number-format-library (GCI535) (#84)
Co-authored-by: Fabien Bézagu <fbezagu@ippon.fr> Co-authored-by: utarwyn <utarwyn@users.noreply.github.com>
1 parent 94679de commit 21dc372

9 files changed

Lines changed: 302 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- [#84](https://github.com/green-code-initiative/creedengo-javascript/pull/84) Add rule GCI535 "No imported number format library"
13+
1014
## [3.1.0] - 2026-05-10
1115

1216
### Added

‎eslint-plugin/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export default [
9090
| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ |
9191
| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ |
9292
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ |
93+
| [no-imported-number-format-library](docs/rules/no-imported-number-format-library.md) | You should not format number with an external library | ✅ |
9394
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ |
9495
| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ |
9596
| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# @creedengo/no-imported-number-format-library
2+
3+
📝 You should not format number with an external library.
4+
5+
⚠️ This rule _warns_ in the ✅ `recommended` config.
6+
7+
<!-- end auto-generated rule header -->
8+
9+
## Why is this an issue?
10+
11+
Importing an external library for lightweight operations increases overall size of the program.
12+
Using native methods instead reduces the amount of memory and storage to run and store the application.
13+
This is especially critical in environments with limited resources, such as on mobile devices or in web applications
14+
where bandwidth and download times matter.
15+
16+
Smaller programs generally have better runtime performance.
17+
Reducing the number of unnecessary modules minimizes the amount of code that needs to be interpreted or compiled,
18+
leading to faster execution and improved overall performance.
19+
20+
Depending on less external dependencies also increases the maintainability and security of your program.
21+
22+
## Examples
23+
24+
**Example with the [numbro](https://numbrojs.com/) library, when you use
25+
`format` method.**
26+
27+
```js
28+
// Example with numbro (not compliant)
29+
import numbro from "numbro";
30+
31+
numbro.setLanguage("en-GB");
32+
var string = numbro(1000).format({
33+
thousandSeparated: true,
34+
}); // '1,000'
35+
36+
// Example with numerable (not compliant)
37+
import { format } from "numerable";
38+
format(1000, "0,0");
39+
40+
// Example with Intl (compliant)
41+
new Intl.NumberFormat("en-GB").format(1000); // '1,000'
42+
```
43+
44+
## Limitations
45+
46+
As for now, only two libraries are handled by this rule :
47+
48+
- [numbro](https://www.npmjs.com/package/numbro)
49+
- [numerable](https://www.npmjs.com/package/numerable)
50+
51+
Some candidates for the future developments are :
52+
53+
- [javascript-number-formatter](https://www.npmjs.com/package/number-format.js)
54+
- [numeraljs](https://www.npmjs.com/package/numeral)
55+
- [formatjs](https://www.npmjs.com/package/@formatjs/intl)
56+
57+
It’s more likely this rule won’t ever be exhaustive.
58+
59+
## Resources
60+
61+
### Documentation
62+
63+
- [Mozilla Web Technology for Developers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
3+
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
"use strict";
20+
21+
/** @type {import("eslint").Rule.RuleModule} */
22+
module.exports = {
23+
meta: {
24+
type: "suggestion",
25+
docs: {
26+
description: "You should not format number with an external library",
27+
category: "eco-design",
28+
recommended: "warn",
29+
},
30+
messages: {
31+
ShouldNotUseImportedNumberFormatLibrary:
32+
"You should not format number with an external library",
33+
},
34+
schema: [],
35+
},
36+
37+
create: function (context) {
38+
let variablesNumbro = [];
39+
40+
const errorReport = (node) => ({
41+
node,
42+
messageId: "ShouldNotUseImportedNumberFormatLibrary",
43+
});
44+
45+
return {
46+
VariableDeclarator(node) {
47+
if (
48+
node.init?.callee?.name === "numbro" &&
49+
node.id.type === "Identifier"
50+
) {
51+
variablesNumbro.push(node.id.name);
52+
}
53+
},
54+
CallExpression(node) {
55+
const formatIsCalledOnANumbroTypeVariable =
56+
node.callee.type === "MemberExpression" &&
57+
variablesNumbro.includes(node.callee.object.name) &&
58+
node.callee.property.name === "format";
59+
if (formatIsCalledOnANumbroTypeVariable) {
60+
context.report(errorReport(node.callee.property));
61+
}
62+
let formatIsCalledOnNumbroInstance =
63+
node.parent.type === "MemberExpression" &&
64+
node.callee.name === "numbro" &&
65+
node.parent.property.name === "format";
66+
if (formatIsCalledOnNumbroInstance) {
67+
context.report(errorReport(node.parent.property));
68+
}
69+
},
70+
ImportDeclaration(node) {
71+
const importedLibraryName = node.source.value;
72+
if (importedLibraryName === "numerable") {
73+
const formatSpecifier = node.specifiers.find(
74+
(specifier) =>
75+
specifier.type === "ImportSpecifier" &&
76+
specifier.imported.name === "format",
77+
);
78+
if (formatSpecifier) {
79+
context.report(errorReport(formatSpecifier));
80+
}
81+
}
82+
},
83+
};
84+
},
85+
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
3+
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
"use strict";
20+
21+
//------------------------------------------------------------------------------
22+
// Requirements
23+
//------------------------------------------------------------------------------
24+
25+
const rule = require("../../../lib/rules/no-imported-number-format-library");
26+
const { RuleTester } = require("eslint");
27+
const { describe, it } = require("node:test");
28+
29+
//------------------------------------------------------------------------------
30+
// Tests
31+
//------------------------------------------------------------------------------
32+
33+
const ruleTester = new RuleTester({
34+
languageOptions: {
35+
ecmaVersion: 6,
36+
sourceType: "module",
37+
},
38+
});
39+
40+
const expectedIdentifierError = {
41+
messageId: "ShouldNotUseImportedNumberFormatLibrary",
42+
};
43+
const expectedImportError = {
44+
messageId: "ShouldNotUseImportedNumberFormatLibrary",
45+
};
46+
47+
const tests = {
48+
valid: [
49+
"new Intl.NumberFormat().format(1000);",
50+
"numbro(1000).add(5);",
51+
`
52+
const number = numbro(1000);
53+
const number2 = numbro(2000);
54+
number2.add(1000);
55+
`,
56+
"import { parse } from 'numerable';",
57+
"import { format } from 'date-fns';",
58+
"import mysql from 'mysql2';",
59+
],
60+
invalid: [
61+
{
62+
code: "numbro(1000).format({thousandSeparated: true});",
63+
errors: [expectedIdentifierError],
64+
},
65+
{
66+
code: `
67+
const number = numbro(1000);
68+
number.format({thousandSeparated: true});
69+
`,
70+
errors: [expectedIdentifierError],
71+
},
72+
{
73+
code: `
74+
const number = numbro(1000);
75+
const number2 = numbro(2000);
76+
number.format({thousandSeparated: true});
77+
`,
78+
errors: [expectedIdentifierError],
79+
},
80+
{
81+
code: "import { format } from 'numerable';",
82+
errors: [expectedImportError],
83+
},
84+
{
85+
code: "import { format as myFormat} from 'numerable';",
86+
errors: [expectedImportError],
87+
},
88+
],
89+
};
90+
91+
describe("no-imported-number-format-library", () => {
92+
it("no-imported-number-format-library", () => {
93+
ruleTester.run("no-imported-number-format-library", rule, tests);
94+
});
95+
});

‎sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static List<Class<? extends EslintHook>> getAllHooks() {
4242
LimitDbQueryResult.class,
4343
NoEmptyImageSrcAttribute.class,
4444
NoImportAllFromLibrary.class,
45+
NoImportedNumberFormatLibrary.class,
4546
NoMultipleAccessDomElement.class,
4647
NoMultipleStyleChanges.class,
4748
NoTorch.class,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
3+
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package org.greencodeinitiative.creedengo.javascript.checks;
19+
20+
import org.sonar.check.Rule;
21+
import org.sonar.plugins.javascript.api.EslintHook;
22+
import org.sonar.plugins.javascript.api.JavaScriptRule;
23+
import org.sonar.plugins.javascript.api.TypeScriptRule;
24+
25+
@JavaScriptRule
26+
@TypeScriptRule
27+
@Rule(key = NoImportedNumberFormatLibrary.RULE_KEY)
28+
public class NoImportedNumberFormatLibrary implements EslintHook {
29+
30+
public static final String RULE_KEY = "GCI535";
31+
32+
@Override
33+
public String eslintKey() {
34+
return "@creedengo/no-imported-number-format-library";
35+
}
36+
37+
}

‎sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"GCI36",
1414
"GCI505",
1515
"GCI523",
16-
"GCI530"
16+
"GCI530",
17+
"GCI535"
1718
]
1819
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import numbro from "numbro";
2+
import { parse, format } from "numerable"; // Non-compliant: usage of external library to format number
3+
4+
numbro(1000).format({thousandSeparated: true}); // Non-compliant: usage of external library to format number
5+
6+
let variable = numbro(1000);
7+
variable.format({thousandSeparated: true}); // Non-compliant: usage of external library to format number
8+
9+
numbro(2000).add(1000); // Compliant
10+
11+
new Intl.NumberFormat().format(1000); // Compliant
12+
13+
format(28);
14+
parse("29"); // Compliant

0 commit comments

Comments
 (0)