Skip to content
43 changes: 43 additions & 0 deletions .changeset/fec-738-no-direct-currency-formatting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
'@commercetools-frontend/eslint-config-mc-app': minor
---

Add bundled `no-direct-currency-formatting` rule via the `@commercetools-frontend/eslint-config-mc-app/rules` inline plugin.

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`.

Use a shared currency formatting wrapper instead, and allowlist that wrapper path if needed.

## How to update

Enable the bundled rule in your project config:

```js
// eslint.config.js
import mcAppConfig from '@commercetools-frontend/eslint-config-mc-app';

export default [
...mcAppConfig,
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
'@commercetools-frontend/eslint-config-mc-app/rules/no-direct-currency-formatting':
[
'error',
{
allowedWrapperPaths: [
'src/utils/money.js', // path to your shared wrapper implementation
],
},
],
},
},
];
```

If you need to customize the wrapper allowlist, pass `allowedWrapperPaths` as shown above.

## Why

Direct currency formatting is hard to standardize across applications and can drift in behavior over time.
Enforcing a shared wrapper keeps formatting logic consistent, testable, and centrally maintainable.
78 changes: 57 additions & 21 deletions jest.test.config.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,65 @@
process.env.ENABLE_NEW_JSX_TRANSFORM = 'true';

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

module.exports = {
preset: '@commercetools-frontend/jest-preset-mc-app/typescript',
moduleDirectories: [
'application-templates/',
'custom-views-templates/',
'packages/',
'playground/',
'node_modules/',
projects: [
// Main application test suite — jsdom environment.
{
displayName: 'test',
preset: '@commercetools-frontend/jest-preset-mc-app/typescript',
moduleDirectories: [
'application-templates/',
'custom-views-templates/',
'packages/',
'playground/',
'node_modules/',
],
modulePathIgnorePatterns: [
'.cache',
'build',
'dist',
'public/',
'examples',
'packages-backend/',
],
testPathIgnorePatterns: [
'/node_modules/',
// Excluded here because these tests need the node environment (see below).
'packages/eslint-config-mc-app/rules/',
],
transformIgnorePatterns: [
// Transpile also our local packages as they are only symlinked.
'node_modules/(?!(@commercetools-[frontend|backend]+)/)',
],
testEnvironment: 'jsdom',
},
// Custom ESLint rule tests — node environment, no preset/setup files.
// `transform: {}` disables Babel so the MC app preset's babel-plugin-istanbul
// doesn't conflict with Jest's own coverage instrumentation.
{
displayName: 'eslint-rules',
testEnvironment: 'node',
testMatch: ['<rootDir>/packages/eslint-config-mc-app/rules/**/*.spec.js'],
transform: {},
},
],
modulePathIgnorePatterns: [
'.cache',
'build',
'dist',
'public/',
'examples',
'packages-backend/',
],
transformIgnorePatterns: [
// Transpile also our local packages as they are only symlinked.
'node_modules/(?!(@commercetools-[frontend|backend]+)/)',
],
testEnvironment: 'jsdom',
};
8 changes: 8 additions & 0 deletions packages/eslint-config-mc-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ const { statusCode, allSupportedExtensions } = require('./helpers/eslint');
const hasJsxRuntime = require('./helpers/has-jsx-runtime');
const { craRules } = require('./helpers/rules-presets');

// Bundled custom rules
const noDirectCurrencyFormattingRule = require('./rules/no-direct-currency-formatting');

/**
* ESLint flat config format for @commercetools-frontend/eslint-config-mc-app
* @type {import("eslint").Linter.FlatConfig[]}
Expand Down Expand Up @@ -124,6 +127,11 @@ module.exports = [
'jsx-a11y': jsxA11yPlugin,
prettier: prettierPlugin,
cypress: cypressPlugin,
'@commercetools-frontend/eslint-config-mc-app/rules': {
rules: {
'no-direct-currency-formatting': noDirectCurrencyFormattingRule,
},
},
},
settings: {
'import/resolver': {
Expand Down
Loading
Loading