Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- [#51](https://github.com/green-code-initiative/creedengo-javascript/pull/51) Add ESLint v9 and flat config support
- [#84](https://github.com/green-code-initiative/creedengo-javascript/pull/84) Add rule GCI535 "No imported number format library"

### Changed

Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ But it can be useful to prepare a test project to check the correct execution of
## Installation

1. Clone the Git repository
2. Run `yarn install` inside **eslint-plugin** directory
3. Synchronize dependencies using Maven inside **sonar-plugin** directory
4. You are good to go! 🚀
1. Inside **eslint-plugin** directory, run `yarn install`
1. Inside **sonar-plugin** directory, synchronize dependencies using Maven with `mvn clean install -DskipTests`
1. Inside root directory, initialize docker with `docker compose up --build -d`
1. You are good to go! 🚀

## Create a rule

Expand Down
1 change: 1 addition & 0 deletions eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ If your project uses a legacy ESLint version, it may use as well the now depreca
| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ |
| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ |
| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ |
| [no-imported-number-format-library](docs/rules/no-imported-number-format-library.md) | You should not format number with an external library | ✅ |
| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ |
| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ |
| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ |
Expand Down
56 changes: 56 additions & 0 deletions eslint-plugin/docs/rules/no-imported-number-format-library.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# You should not format number with an external library (`@creedengo/no-imported-number-format-library`)

⚠️ This rule _warns_ in the following configs: ✅ `flat/recommended`, ✅ `recommended`.

## Why is this an issue?

Importing an external library for lightweight operations increases overall size of the program.
Using native methods instead reduces the amount of memory and storage to run and store the application.
This is especially critical in environments with limited resources, such as on mobile devices or in web applications
where bandwidth and download times matter.

Smaller programs generally have better runtime performance.
Reducing the number of unnecessary modules minimizes the amount of code that needs to be interpreted or compiled,
leading to faster execution and improved overall performance.

Depending on less external dependencies also increases the maintainability and security of your program.

## Examples

**Example with the [numbro](https://numbrojs.com/) library, when you use
`format` method.**

```js
// Example with numbro (not compliant)
import numbro from "numbro";

numbro.setLanguage('en-GB');
var string = numbro(1000).format({
thousandSeparated: true,
}); // '1,000'

// Example with numerable (not compliant)
import { format } from "numerable";
format(1000, '0,0');

// Example with Intl (compliant)
new Intl.NumberFormat("en-GB").format(1000); // '1,000'
```

## Limitations
As for now, only two libraries are handled by this rule :
- [numbro](https://numbrojs.com/)
- [numerable](https://numerablejs.com/lander)

Some candidates for the future developments are :
- [javascript-number-formatter](https://github.com/Mottie/javascript-number-formatter)
- [numeraljs](https://www.npmjs.com/package/numerable)
- [formatjs](https://formatjs.github.io/)

It’s more likely this rule won’t ever be exhaustive.

## Resources

### Documentation

- [Mozilla Web Technology for Developers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) -
78 changes: 78 additions & 0 deletions eslint-plugin/lib/rules/no-imported-number-format-library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "You should not format number with an external library",
category: "eco-design",
recommended: "warn",
},
messages: {
ShouldNotUseImportedNumberFormatLibrary:
"You should not format number with an external library",
},
schema: [],
},

create: function (context) {
let variablesNumbro = [];

const errorReport = (node) => ({
node,
messageId: "ShouldNotUseImportedNumberFormatLibrary",
});

return {
VariableDeclarator(node) {
if (node.init.callee?.name === "numbro") {
variablesNumbro.push(node.id.name);
}
},
CallExpression(node) {
const formatIsCalledOnANumbroTypeVariable =
node.callee.type === "MemberExpression" &&
variablesNumbro.includes(node.callee.object.name) &&
node.callee.property.name === "format";
if (formatIsCalledOnANumbroTypeVariable) {
context.report(errorReport(node.callee.property));
}
let formatIsCalledOnNumbroInstance =
node.parent.type === "MemberExpression" &&
node.callee.name === "numbro" &&
node.parent.property.name === "format";
if (formatIsCalledOnNumbroInstance) {
context.report(errorReport(node.parent.property));
}
},
ImportDeclaration(node) {
const importedLibraryName = node.source.value;
if (importedLibraryName === 'numerable') {
const formatSpecifier = node.specifiers.find(specifier => specifier.type === 'ImportSpecifier' && specifier.imported.name === 'format');
if (formatSpecifier) {
context.report(errorReport(formatSpecifier));
}
}
}
};
},
};
1 change: 1 addition & 0 deletions eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"lint:fix": "eslint . --fix",
"pack:sonar": "npm pkg set main=\"./dist/rules.js\" && mkdirp dist/pack && yarn pack -o dist/pack/creedengo-eslint-plugin.tgz && npm pkg set main=\"./lib/standalone.js\"",
"test": "mocha tests --recursive",
"test:watch": "mocha tests --watch --recursive",
"test:cov": "nyc --reporter=lcov --reporter=text mocha tests --recursive",
"update:eslint-docs": "eslint-doc-generator"
},
Expand Down
88 changes: 88 additions & 0 deletions eslint-plugin/tests/lib/rules/no-imported-number-format-library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-imported-number-format-library");
const RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
},
});
const expectedIdentifierError = {
messageId: "ShouldNotUseImportedNumberFormatLibrary",
type: "Identifier",
};
const expectedImportError = {
messageId: "ShouldNotUseImportedNumberFormatLibrary",
type: "ImportSpecifier",
};
ruleTester.run("no-imported-number-format-library", rule, {
valid: [
"new Intl.NumberFormat().format(1000);",
"numbro(1000).add(5);",
`
const number = numbro(1000);
const number2 = numbro(2000);
number2.add(1000);
`,
"import { parse } from 'numerable';",
"import { format } from 'date-fns';",
"import mysql from 'mysql2';"
],
invalid: [
{
code: "numbro(1000).format({thousandSeparated: true});",
errors: [expectedIdentifierError],
},
{
code: `
const number = numbro(1000);
number.format({thousandSeparated: true});
`,
errors: [expectedIdentifierError],
},
{
code: `
const number = numbro(1000);
const number2 = numbro(2000);
number.format({thousandSeparated: true});
`,
errors: [expectedIdentifierError],
},
{
code: "import { format } from 'numerable';",
errors: [expectedImportError],
},
{
code: "import { format as myFormat} from 'numerable';",
errors: [expectedImportError],
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static List<Class<? extends JavaScriptCheck>> getAllChecks() {
LimitDbQueryResult.class,
NoEmptyImageSrcAttribute.class,
NoImportAllFromLibrary.class,
NoImportedNumberFormatLibrary.class,
NoMultipleAccessDomElement.class,
NoMultipleStyleChanges.class,
NoTorch.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.javascript.checks;

import org.greencodeinitiative.creedengo.javascript.DeprecatedEcoCodeRule;
import org.sonar.check.Rule;
import org.sonar.plugins.javascript.api.EslintBasedCheck;
import org.sonar.plugins.javascript.api.JavaScriptRule;
import org.sonar.plugins.javascript.api.TypeScriptRule;

@JavaScriptRule
@TypeScriptRule
@Rule(key = NoImportedNumberFormatLibrary.RULE_KEY)
public class NoImportedNumberFormatLibrary implements EslintBasedCheck {

public static final String RULE_KEY = "GCI535";

@Override
public String eslintKey() {
return "@creedengo/no-imported-number-format-library";
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "Creedengo",
"ruleKeys": [
"GCI535",
"GCI9",
"GCI11",
"GCI12",
Expand Down
2 changes: 1 addition & 1 deletion test-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Use the following Shell script which will do the job for you:
Or you can manually run these commands:

- Install dependencies: `yarn install`
- Start Sonar Scanner: `yarn sonar -Dsonar.token=MY_SONAR_TOKEN`
- Start Sonar Scanner: `yarn sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.token=MY_SONAR_TOKEN`

### 3. Check errors

Expand Down
14 changes: 14 additions & 0 deletions test-project/src/no-imported-number-format-library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import numbro from "numbro";
import { parse, format } from "numerable"; // Non-compliant: usage of external library to format number

numbro(1000).format({thousandSeparated: true}); // Non-compliant: usage of external library to format number

let variable = numbro(1000);
variable.format({thousandSeparated: true}); // Non-compliant: usage of external library to format number

numbro(2000).add(1000); // Compliant

new Intl.NumberFormat().format(1000); // Compliant

format(28);
parse("29"); // Compliant
2 changes: 1 addition & 1 deletion test-project/tool_send_to_sonar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
yarn install

# sending to Sonar phase
yarn sonar -Dsonar.host.url=http://0.0.0.0:9000 -Dsonar.token=$1
yarn sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.token=$1
Loading