@zimbra/eslint-config 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,11 +9,13 @@ This package bundles a set of shareable ESLint configs and rule customizations s
9
9
  - [Overview](#overview)
10
10
  - [Quick start](#quick-start)
11
11
  - [Installation](#installation)
12
+ - [Prettier integration (important)](#prettier-integration-important)
13
+ - [When should I integrate this?](#when-should-i-integrate-this)
14
+ - [What problem will it resolve?](#what-problem-will-it-resolve)
12
15
  - [Usage examples](#usage-examples)
13
16
  - [ESLint Flat Config example (eslint.config.mjs)](#eslint-flat-config-example-eslintconfigmjs)
14
17
  - [Exports & configs](#exports--configs)
15
- - [Rule summary](#rule-summary)
16
- - [Custom rules](#custom-rules)
18
+ - [Architecture & rule organization](#architecture--rule-organization)
17
19
  - [Scripts](#scripts)
18
20
  - [Publishing](#publishing)
19
21
  - [Contributing](#contributing)
@@ -101,15 +103,15 @@ Adopting this config helps maintain code quality, reduces time spent configuring
101
103
 
102
104
  ## **ESLint Flat Config example (eslint.config.mjs)**
103
105
 
104
- If your project uses ESLint v9+ with the Flat Config (`eslint.config.mjs` / `eslint.config.cjs`), import the named config blocks exported by this package and spread them into your exported array. Example JS project:
106
+ If your project uses ESLint v9+ with the Flat Config (`eslint.config.mjs` / `eslint.config.cjs`), import the named config blocks exported by this package and add them to your exported array. Example JS project:
105
107
 
106
108
  ```js
107
109
  // eslint.config.mjs
108
110
  import { coreJsConfig, customConfig } from "@zimbra/eslint-config";
109
111
 
110
112
  export default [
111
- ...coreJsConfig,
112
- ...customConfig,
113
+ coreJsConfig,
114
+ customConfig,
113
115
  // Add local overrides or additional blocks here
114
116
  {
115
117
  files: ["**/*.js", "**/*.jsx"],
@@ -120,6 +122,24 @@ export default [
120
122
  ];
121
123
  ```
122
124
 
125
+ Example React project with i18n support:
126
+
127
+ ```js
128
+ // eslint.config.mjs
129
+ import { coreJsConfig, customConfig, reactConfig, preactI18nConfig } from "@zimbra/eslint-config";
130
+
131
+ export default [
132
+ coreJsConfig,
133
+ customConfig,
134
+ reactConfig,
135
+ preactI18nConfig,
136
+ {
137
+ files: ["**/*.jsx"],
138
+ // local React overrides (if needed)
139
+ }
140
+ ];
141
+ ```
142
+
123
143
  TypeScript project using the package TypeScript export:
124
144
 
125
145
  ```js
@@ -128,8 +148,8 @@ import { coreJsConfig } from "@zimbra/eslint-config";
128
148
  import typescriptConfig from "@zimbra/eslint-config/typescript";
129
149
 
130
150
  export default [
131
- ...coreJsConfig,
132
- ...typescriptConfig,
151
+ coreJsConfig,
152
+ typescriptConfig,
133
153
  {
134
154
  files: ["**/*.ts", "**/*.tsx"],
135
155
  // local TypeScript overrides (if needed)
@@ -141,17 +161,40 @@ Notes:
141
161
 
142
162
  - Ensure your project has `type: "module"` in `package.json` or use the `.mjs` extension for the config file so Node treats it as ESM.
143
163
  - Install peer dependencies (`eslint`, `@typescript-eslint/*`, `prettier`) in the consumer project as described in the Installation section.
144
- - The exported config blocks (`coreJsConfig`, `customConfig`, etc.) are arrays of config blocks spreading them preserves ordering and allows local blocks to appear before/after as desired.
164
+ - The exported config blocks (`coreJsConfig`, `customConfig`, etc.) are objects that represent a single ESLint config block add them directly to your config array without spreading.
145
165
 
146
166
  ## **Exports & configs**
147
167
 
148
- - `.` -> `src/index.js` (base config)
149
- - `./typescript` -> `src/typescript.js` (TypeScript-focused config)
150
- - Additional configs are in `src/configs/` (automation, core-js, custom, locale-json, ts-eslint-config)
168
+ ### Main exports from `src/index.js`
169
+
170
+ - **`coreJsConfig`** Base JavaScript config with ESLint recommended rules, import rules, security, and style rules.
171
+ - **`customConfig`** — Custom Zimbra rules (includes `no-direct-memoize` and other custom patterns).
172
+ - **`reactConfig`** — React and React Hooks rules (includes plugin setup and recommended rules).
173
+ - **`preactI18nConfig`** — Preact i18n rules and configuration (requires `ESLINT_INTL_PATH` environment variable or defaults to `src/intl`).
174
+ - **`prettierConfig`** — Prettier integration (formatting rules and conflict resolution).
175
+ - **`automationConfig`** — Automation/TestCafe rules for test files.
176
+ - **`localeJsonConfig`** — i18n JSON validation rules.
177
+
178
+ ### Additional exports
179
+
180
+ - `./typescript` -> `src/typescript.js` (TypeScript-focused config — exports `tsEslintConfig`)
181
+
182
+ ## **Architecture & rule organization**
183
+
184
+ The package follows a modular structure:
185
+
186
+ - **`src/rules/`** — Individual rule modules (react, style, security, import, etc.) that define which ESLint rules are enabled and their severity levels.
187
+ - **`src/configs/`** — Config modules that combine related rules and plugins into focused, reusable blocks. Each config handles a specific concern (e.g., React, i18n, Prettier, automation).
188
+ - **`src/index.js`** — Exports the themed configs for use in consumer projects.
189
+ - **`src/typescript.js`** — TypeScript-specific config export.
190
+
191
+ Rule details and descriptions have moved to [RULES.md](RULES.md). That file contains a complete list of rule modules and plain-language explanations of what each rule enforces or why a rule is disabled. It also reflects the latest updates in this branch, including converting numeric rule severity values (0/1/2) into explicit words (`off`, `warn`, `error`) for `src/rules/style.js` and `src/rules/security.js` rules. See [RULES.md](RULES.md) in the repo root for the authoritative list and examples.
192
+
193
+ ### Custom rules
151
194
 
152
- ## **Rules and custom rules**
195
+ Custom rules are defined in `src/rules/custom-rules/`:
153
196
 
154
- Rule details and descriptions have moved to `RULES.md`. That file contains a complete list of rule modules and plain-language explanations of what each rule enforces or why a rule is disabled. See `RULES.md` in the repo root for the authoritative list and examples.
197
+ - **`no-direct-memoize.js`** Prevents direct wrapping of components in React.memo without considering performance implications. Use memoization only when genuinely needed.
155
198
 
156
199
  ## **Scripts**
157
200
 
package/RULES.md CHANGED
@@ -62,6 +62,20 @@ Source: `src/rules/import.js`
62
62
 
63
63
  ---
64
64
 
65
+ ## **src/rules/security.js**
66
+
67
+ Purpose: Security-focused rules that disallow unsafe JavaScript patterns.
68
+
69
+ Rules and what they do:
70
+
71
+ - `no-eval`: error — disallow `eval()` usage (runtime injection risk).
72
+ - `no-implied-eval`: error — disallow `setTimeout/string` and similar implicit eval patterns.
73
+ - `no-new-func`: error — disallow `new Function(...)` creation (runtime code execution risk).
74
+
75
+ Source: `src/rules/security.js`
76
+
77
+ ---
78
+
65
79
  ## **src/rules/parser.js**
66
80
 
67
81
  Purpose: Central parser configuration used for TypeScript-enabled linting blocks.
@@ -134,11 +148,19 @@ Purpose: Style and code-shape rules that affect common JavaScript patterns.
134
148
 
135
149
  Rules and what they do:
136
150
 
137
- - `no-undef`: offdo not report undefined variables here (TypeScript or other checks may handle this).
138
- - `no-unsafe-optional-chaining`: off allow some optional chaining patterns that would otherwise be flagged.
151
+ - `new-cap`: warnrequire constructor function names to be capitalized, but at warn level.
152
+ - `no-console`: ['warn', { allow: ['warn', 'error'] }] warn on console calls except `console.warn` and `console.error`.
153
+ - `no-shadow-restricted-names`: error — disallow shadowing of restricted names such as `arguments`.
154
+ - `no-shadow`: error — disallow variable shadowing.
155
+ - `eqeqeq`: ['error', 'smart'] — require strict equality except for some safe special cases.
156
+ - `camelcase`: ['warn', { properties: 'never' }] — warn on non-camelcase identifiers while allowing property names.
157
+ - `guard-for-in`: error — require `hasOwnProperty` checks in `for..in` loops.
158
+ - `prefer-rest-params`: error — prefer rest parameters over `arguments`.
159
+ - `no-alert`: error — disallow alert/confirm/prompt usage.
160
+ - `no-unused-vars`: ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, caughtErrors: 'none' }] — report unused variables while allowing common ignore patterns.
139
161
  - `no-empty`: off — allow empty blocks in some cases.
140
162
  - `no-empty-pattern`: off — allow empty destructuring patterns.
141
- - `no-unused-vars`: `['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, caughtErrors: 'none' }]` report unused variables, but ignore some common patterns (e.g., rest siblings).
163
+ - `no-unsafe-optional-chaining`: offallow optional chaining in conditions that would otherwise be invalid.
142
164
 
143
165
  Source: `src/rules/style.js`
144
166
 
package/eslint.config.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { coreJsConfig } from './src/index.js';
2
2
 
3
- export default [...coreJsConfig];
3
+ export default coreJsConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zimbra/eslint-config",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "ESLint configuration for Zimbra javascript projects.",
6
6
  "main": "src/index.js",
@@ -11,7 +11,6 @@
11
11
  "scripts": {
12
12
  "lint": "eslint src --config eslint.config.mjs",
13
13
  "lint:fix": "npm run lint -- --fix",
14
- "prepublishOnly": "npm run lint && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags",
15
14
  "prepare": "husky || true"
16
15
  },
17
16
  "engines": {
@@ -1,16 +1,20 @@
1
+ import { fixupPluginRules } from '@eslint/compat';
2
+
1
3
  import testcafePlugin from 'eslint-plugin-testcafe';
4
+ import pluginMocha from 'eslint-plugin-mocha';
5
+
2
6
  import { automationRules } from '../rules/automation.js';
3
7
 
4
- export default [
5
- {
6
- files: ['test/**/*.{js,jsx,mjs,cjs}', 'tests/**/*.{js,jsx,mjs,cjs}'],
7
- ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'],
8
- plugins: {
9
- testcafePlugin: testcafePlugin
10
- },
11
- rules: {
12
- ...testcafePlugin.configs.recommended.rules,
13
- ...automationRules
14
- }
8
+ export default {
9
+ files: ['**/*.{js,jsx,mjs,cjs}'],
10
+
11
+ plugins: {
12
+ mocha: fixupPluginRules(pluginMocha),
13
+ testcafePlugin: testcafePlugin
14
+ },
15
+
16
+ rules: {
17
+ ...testcafePlugin.configs.recommended.rules,
18
+ ...automationRules
15
19
  }
16
- ];
20
+ };
@@ -1,91 +1,37 @@
1
1
  import eslint from '@eslint/js';
2
- import { fixupPluginRules } from '@eslint/compat';
3
2
  import globals from 'globals';
4
- import path from 'path';
5
3
 
6
- import prettierConfig from 'eslint-config-prettier';
7
- import pluginPrettier from 'eslint-plugin-prettier';
8
- import pluginMocha from 'eslint-plugin-mocha';
9
- import pluginPreactI18n from 'eslint-plugin-preact-i18n';
10
- import pluginReact from 'eslint-plugin-react';
11
- import pluginReactHooks from 'eslint-plugin-react-hooks';
12
4
  import importPlugin from 'eslint-plugin-import';
13
5
 
14
6
  import styleRules from '../rules/style.js';
15
- import reactRules from '../rules/react.js';
16
- import reactHooksRules from '../rules/react-hooks.js';
17
7
  import securityRules from '../rules/security.js';
18
8
  import importRules from '../rules/import.js';
19
- import { i18nRules, LANGUAGE_FILES_RELATIVE, i18nTextComponents } from '../rules/i18n.js';
20
- import prettierRules from '../rules/prettier.js';
21
9
  import parserConfig from '../rules/parser.js';
22
10
 
23
- const intlPath = process.env.ESLINT_INTL_PATH ?? 'src/intl';
24
- const disableIntl = process.env.ESLINT_DISABLE_INTL === 'true';
11
+ export default {
12
+ files: ['**/*.{js,jsx,mjs,cjs}'],
25
13
 
26
- const coreRules = {
27
- ...styleRules,
28
- ...reactRules,
29
- ...reactHooksRules,
30
- ...importRules,
31
- ...prettierRules,
32
- ...securityRules,
33
- ...(disableIntl ? {} : i18nRules)
34
- };
35
-
36
- const languageFilesAbsolute = LANGUAGE_FILES_RELATIVE.map(entry => ({
37
- name: entry.name,
38
- path: path.join(intlPath, entry.filename)
39
- }));
40
-
41
- export default [
42
- {
43
- files: ['**/*.{js,jsx,mjs,cjs}'],
44
-
45
- ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.graphql', '**/*.gql'],
46
-
47
- languageOptions: {
48
- ...parserConfig,
49
- globals: {
50
- ...globals.browser,
51
- ...globals.node
52
- }
53
- },
54
-
55
- plugins: {
56
- react: pluginReact,
57
- 'react-hooks': pluginReactHooks,
58
- mocha: fixupPluginRules(pluginMocha),
59
- prettier: pluginPrettier,
60
- import: importPlugin,
61
- ...(disableIntl
62
- ? {}
63
- : {
64
- // TODO: Upgrade `pluginPreactI18n` to a modern version to ensure compatibility with current tooling and React/Preact best practices
65
- 'preact-i18n': fixupPluginRules(pluginPreactI18n)
66
- })
67
- },
68
-
69
- rules: {
70
- ...eslint.configs.recommended.rules,
71
- ...prettierConfig.rules,
72
- ...pluginReact.configs.recommended.rules,
73
- ...pluginReactHooks.configs.recommended.rules,
74
- ...importPlugin.configs.recommended.rules,
75
- ...coreRules
76
- },
77
-
78
- settings: {
79
- // Requires exactly version 16.0. See: https://github.com/jsx-eslint/eslint-plugin-react/issues/1754
80
- react: { pragma: 'createElement', version: '16.0' },
81
- ...(disableIntl
82
- ? {}
83
- : {
84
- 'preact-i18n': {
85
- languageFiles: languageFilesAbsolute,
86
- textComponents: i18nTextComponents
87
- }
88
- })
14
+ languageOptions: {
15
+ ...parserConfig,
16
+ globals: {
17
+ ...globals.browser,
18
+ ...globals.node
89
19
  }
20
+ },
21
+
22
+ plugins: {
23
+ import: importPlugin
24
+ },
25
+
26
+ rules: {
27
+ ...eslint.configs.recommended.rules,
28
+ ...importPlugin.configs.recommended.rules,
29
+ ...styleRules,
30
+ ...importRules,
31
+ ...securityRules
32
+ },
33
+
34
+ settings: {
35
+ 'import/node-version': '18.20.0'
90
36
  }
91
- ];
37
+ };
@@ -1,19 +1,18 @@
1
1
  import customRules from '../rules/custom-rules/custom-rules.js';
2
2
  import noDirectMemoize from '../rules/custom-rules/no-direct-memoize.js';
3
3
 
4
- export default [
5
- {
6
- files: ['**/*.{js,jsx,mjs,cjs}'],
7
- ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'],
8
- plugins: {
9
- custom: {
10
- rules: {
11
- 'no-direct-memoize': noDirectMemoize
12
- }
4
+ export default {
5
+ files: ['**/*.{js,jsx,mjs,cjs}'],
6
+
7
+ plugins: {
8
+ custom: {
9
+ rules: {
10
+ 'no-direct-memoize': noDirectMemoize
13
11
  }
14
- },
15
- rules: {
16
- ...customRules
17
12
  }
13
+ },
14
+
15
+ rules: {
16
+ ...customRules
18
17
  }
19
- ];
18
+ };
@@ -1,20 +1,20 @@
1
1
  import pluginI18nJson from 'eslint-plugin-i18n-json';
2
2
  import { i18nJsonRules } from '../rules/i18n.js';
3
3
 
4
- export default [
5
- {
6
- files: ['**/*.json'],
7
- ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'],
8
- plugins: {
9
- 'i18n-json': pluginI18nJson
10
- },
11
- processor: {
12
- meta: { name: '.json' },
13
- ...pluginI18nJson.processors['.json']
14
- },
15
- rules: {
16
- ...pluginI18nJson.configs.recommended.rules,
17
- ...i18nJsonRules
18
- }
4
+ export default {
5
+ files: ['**/*.json'],
6
+
7
+ plugins: {
8
+ 'i18n-json': pluginI18nJson
9
+ },
10
+
11
+ processor: {
12
+ meta: { name: '.json' },
13
+ ...pluginI18nJson.processors['.json']
14
+ },
15
+
16
+ rules: {
17
+ ...pluginI18nJson.configs.recommended.rules,
18
+ ...i18nJsonRules
19
19
  }
20
- ];
20
+ };
@@ -0,0 +1,17 @@
1
+ import prettierConfig from 'eslint-config-prettier';
2
+ import pluginPrettier from 'eslint-plugin-prettier';
3
+
4
+ import prettierRules from '../rules/prettier.js';
5
+
6
+ export default {
7
+ files: ['**/*.{js,jsx,mjs,cjs}'],
8
+
9
+ plugins: {
10
+ prettier: pluginPrettier
11
+ },
12
+
13
+ rules: {
14
+ ...prettierConfig.rules,
15
+ ...prettierRules
16
+ }
17
+ };
package/src/index.js CHANGED
@@ -1,6 +1,17 @@
1
- import automationConfig from './configs/automation-config.js';
2
- import localeJsonConfig from './configs/locale-json-config.js';
3
1
  import coreJsConfig from './configs/core-js-config.js';
2
+ import localeJsonConfig from './configs/locale-json-config.js';
3
+ import automationConfig from './configs/automation-config.js';
4
+ import prettierConfig from './configs/prettier-config.js';
4
5
  import customConfig from './configs/custom-config.js';
6
+ import reactConfig from './configs/react-config.js';
7
+ import preactI18nConfig from './configs/preact-i18n-config.js';
5
8
 
6
- export { coreJsConfig, localeJsonConfig, automationConfig, customConfig };
9
+ export {
10
+ coreJsConfig,
11
+ localeJsonConfig,
12
+ automationConfig,
13
+ prettierConfig,
14
+ customConfig,
15
+ reactConfig,
16
+ preactI18nConfig
17
+ };
@@ -1,16 +1,8 @@
1
1
  export const automationRules = {
2
- 'prettier/prettier': 'off',
3
2
  'prefer-const': 'off',
4
- 'require-atomic-updates': 'off',
5
3
  'guard-for-in': 'off',
6
- 'react/jsx-no-useless-fragment': 'off',
7
- 'lines-around-comment': 'off',
4
+ 'prettier/prettier': 'off',
8
5
  'no-unexpected-multiline': 'off',
9
- 'no-spaced-func': 'off',
10
6
  'new-cap': 'off',
11
- 'no-undef-init': 'off',
12
- 'no-shadow': 'off',
13
- 'no-case-declarations': 'off',
14
- 'no-constant-binary-expression': 'off',
15
- semi: ['error', 'always']
7
+ 'no-case-declarations': 'off'
16
8
  };
package/src/rules/i18n.js CHANGED
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import { fileURLToPath } from 'url';
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
3
 
4
4
  const configUrl = import.meta.url;
5
5
  const intlPath = process.env.ESLINT_INTL_PATH ?? 'src/intl';
@@ -1,8 +1,13 @@
1
1
  export const importRules = {
2
2
  'import/no-unresolved': 'off',
3
+ 'import/enforce-node-protocol-usage': ['error', 'always'],
3
4
 
4
5
  // TODO: Enable these rules once the codebase is updated to follow best practices
5
- 'import/no-named-as-default': 'off'
6
+ 'import/no-named-as-default': 'off',
7
+ 'import/extensions': 'off',
8
+ 'import/newline-after-import': 'off',
9
+ 'import/order': 'off',
10
+ 'import/no-extraneous-dependencies': 'off'
6
11
  };
7
12
 
8
13
  export default importRules;
@@ -14,6 +14,9 @@ export const reactRules = {
14
14
  'react/jsx-no-duplicate-props': 'error',
15
15
  'react/jsx-pascal-case': 'error',
16
16
 
17
+ // Security related rules
18
+ 'react/no-danger': 'error',
19
+
17
20
  // TODO: Enable these rules once the codebase is updated to follow best practices
18
21
  'react/react-in-jsx-scope': 'off',
19
22
  'react/jsx-key': 'off',
@@ -1,8 +1,7 @@
1
1
  export const securityRules = {
2
- 'no-eval': 2,
3
- 'no-implied-eval': 2,
4
- 'no-new-func': 2,
5
- 'react/no-danger': 'error'
2
+ 'no-eval': 'error',
3
+ 'no-implied-eval': 'error',
4
+ 'no-new-func': 'error'
6
5
  };
7
6
 
8
7
  export default securityRules;
@@ -1,12 +1,12 @@
1
1
  export const styleRules = {
2
- 'new-cap': 1,
3
- 'no-console': [1, { allow: ['warn', 'error'] }],
4
- 'no-shadow-restricted-names': 2,
2
+ 'new-cap': 'warn',
3
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
4
+ 'no-shadow-restricted-names': 'error',
5
5
  'no-shadow': 'error',
6
6
  eqeqeq: ['error', 'smart'],
7
- camelcase: [1, { properties: 'never' }],
8
- 'guard-for-in': 2,
9
- 'prefer-rest-params': 2,
7
+ camelcase: ['warn', { properties: 'never' }],
8
+ 'guard-for-in': 'error',
9
+ 'prefer-rest-params': 'error',
10
10
  'no-alert': 'error',
11
11
 
12
12
  // TODO: Fully enable this rule once the codebase is updated to follow best practices