Skip to content

Commit 8efed36

Browse files
nima-ctmisama-ct
andauthored
FEC-753: Add shared no-direct-currency formatting rule (#3961)
* feat(es-lint): add no-direct-currency-formatting rule to ESLint config * fix(eslint-config): update rule path for no-direct-currency-formatting in ESLint config * feat(eslint-config): enhance no-direct-currency-formatting rule with updated usage instructions and allowlist customization * feat(eslint-config): extend no-direct-currency-formatting rule to include <FormattedNumber /> from react-intl * chore: add eslint-rules jest project for node-environment rule tests Use Jest projects in jest.test.config.js so custom ESLint rule tests run in a node environment alongside the existing jsdom test suite. * test(eslint-config): add tests for no-direct-currency-formatting rule Covers intl.formatNumber, destructured/aliased variants, Intl.NumberFormat, variable-resolved options, spread elements, and <FormattedNumber /> from react-intl. Includes cases proving the false-positive bug where non-currency <FormattedNumber /> usage (percent, decimal, no style) is incorrectly flagged. * fix: disable Babel transform for eslint-rules jest project The eslint-rules project inherits Babel config resolution which loads babel-plugin-istanbul, conflicting with Jest's own coverage instrumentation. Setting transform: {} avoids this since the rule files are plain CommonJS. * feat(eslint-config): enhance no-direct-currency-formatting rule with JSX attribute handling --------- Co-authored-by: Michael Salzmann <michael.salzmann@commercetools.com>
1 parent ee05b63 commit 8efed36

File tree

5 files changed

+882
-21
lines changed

5 files changed

+882
-21
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@commercetools-frontend/eslint-config-mc-app': minor
3+
---
4+
5+
Add bundled `no-direct-currency-formatting` rule via the `@commercetools-frontend/eslint-config-mc-app/rules` inline plugin.
6+
7+
This rule disallows direct currency formatting through `intl.formatNumber`, `intl.formatCurrency`, `new Intl.NumberFormat` when using a `currency` option or `style: 'currency'`, and `<FormattedNumber />` from `react-intl`.
8+
9+
Use a shared currency formatting wrapper instead, and allowlist that wrapper path if needed.
10+
11+
## How to update
12+
13+
Enable the bundled rule in your project config:
14+
15+
```js
16+
// eslint.config.js
17+
import mcAppConfig from '@commercetools-frontend/eslint-config-mc-app';
18+
19+
export default [
20+
...mcAppConfig,
21+
{
22+
files: ['**/*.{js,jsx,ts,tsx}'],
23+
rules: {
24+
'@commercetools-frontend/eslint-config-mc-app/rules/no-direct-currency-formatting':
25+
[
26+
'error',
27+
{
28+
allowedWrapperPaths: [
29+
'src/utils/money.js', // path to your shared wrapper implementation
30+
],
31+
},
32+
],
33+
},
34+
},
35+
];
36+
```
37+
38+
If you need to customize the wrapper allowlist, pass `allowedWrapperPaths` as shown above.
39+
40+
## Why
41+
42+
Direct currency formatting is hard to standardize across applications and can drift in behavior over time.
43+
Enforcing a shared wrapper keeps formatting logic consistent, testable, and centrally maintainable.

jest.test.config.js

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,65 @@
11
process.env.ENABLE_NEW_JSX_TRANSFORM = 'true';
22

33
/**
4-
* @type {import('@jest/types').Config.ProjectConfig}
4+
* This config uses Jest "projects" to run two test suites in a single
5+
* `pnpm test` invocation, each with its own environment:
6+
*
7+
* - "test" — the main suite (jsdom). Uses the MC app preset which
8+
* sets up window.app, localStorage mocks, etc.
9+
* - "eslint-rules" — custom ESLint rule tests (node). These use ESLint's
10+
* RuleTester which requires `structuredClone` (available
11+
* in Node but not in jsdom) and has no DOM dependencies.
12+
* The MC app preset's setup files also assume jsdom
13+
* (they write to `global.window`), so these tests cannot
14+
* run under the main project.
15+
*
16+
* If you add more custom ESLint rules under
17+
* packages/eslint-config-mc-app/rules/, their *.spec.js files will be
18+
* picked up automatically by the "eslint-rules" project.
19+
*
20+
* @type {import('@jest/types').Config.InitialOptions}
521
*/
622

723
module.exports = {
8-
preset: '@commercetools-frontend/jest-preset-mc-app/typescript',
9-
moduleDirectories: [
10-
'application-templates/',
11-
'custom-views-templates/',
12-
'packages/',
13-
'playground/',
14-
'node_modules/',
24+
projects: [
25+
// Main application test suite — jsdom environment.
26+
{
27+
displayName: 'test',
28+
preset: '@commercetools-frontend/jest-preset-mc-app/typescript',
29+
moduleDirectories: [
30+
'application-templates/',
31+
'custom-views-templates/',
32+
'packages/',
33+
'playground/',
34+
'node_modules/',
35+
],
36+
modulePathIgnorePatterns: [
37+
'.cache',
38+
'build',
39+
'dist',
40+
'public/',
41+
'examples',
42+
'packages-backend/',
43+
],
44+
testPathIgnorePatterns: [
45+
'/node_modules/',
46+
// Excluded here because these tests need the node environment (see below).
47+
'packages/eslint-config-mc-app/rules/',
48+
],
49+
transformIgnorePatterns: [
50+
// Transpile also our local packages as they are only symlinked.
51+
'node_modules/(?!(@commercetools-[frontend|backend]+)/)',
52+
],
53+
testEnvironment: 'jsdom',
54+
},
55+
// Custom ESLint rule tests — node environment, no preset/setup files.
56+
// `transform: {}` disables Babel so the MC app preset's babel-plugin-istanbul
57+
// doesn't conflict with Jest's own coverage instrumentation.
58+
{
59+
displayName: 'eslint-rules',
60+
testEnvironment: 'node',
61+
testMatch: ['<rootDir>/packages/eslint-config-mc-app/rules/**/*.spec.js'],
62+
transform: {},
63+
},
1564
],
16-
modulePathIgnorePatterns: [
17-
'.cache',
18-
'build',
19-
'dist',
20-
'public/',
21-
'examples',
22-
'packages-backend/',
23-
],
24-
transformIgnorePatterns: [
25-
// Transpile also our local packages as they are only symlinked.
26-
'node_modules/(?!(@commercetools-[frontend|backend]+)/)',
27-
],
28-
testEnvironment: 'jsdom',
2965
};

packages/eslint-config-mc-app/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ const { statusCode, allSupportedExtensions } = require('./helpers/eslint');
9090
const hasJsxRuntime = require('./helpers/has-jsx-runtime');
9191
const { craRules } = require('./helpers/rules-presets');
9292

93+
// Bundled custom rules
94+
const noDirectCurrencyFormattingRule = require('./rules/no-direct-currency-formatting');
95+
9396
/**
9497
* ESLint flat config format for @commercetools-frontend/eslint-config-mc-app
9598
* @type {import("eslint").Linter.FlatConfig[]}
@@ -124,6 +127,11 @@ module.exports = [
124127
'jsx-a11y': jsxA11yPlugin,
125128
prettier: prettierPlugin,
126129
cypress: cypressPlugin,
130+
'@commercetools-frontend/eslint-config-mc-app/rules': {
131+
rules: {
132+
'no-direct-currency-formatting': noDirectCurrencyFormattingRule,
133+
},
134+
},
127135
},
128136
settings: {
129137
'import/resolver': {

0 commit comments

Comments
 (0)