eslint-plugin-nextfriday 3.0.0 → 3.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # eslint-plugin-nextfriday
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#118](https://github.com/next-friday/eslint-plugin-nextfriday/pull/118) [`143eee9`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/143eee9fd0c6aed00e10677a6cad448dbdc9136e) Thanks [@joetakara](https://github.com/joetakara)! - Add `index-export-only` rule. Restricts `index.{js,jsx,ts,tsx}` files to imports, re-exports, and type/interface declarations only — flagging local function/class/variable declarations, inline `export const`/`export function`/`export class`, top-level expressions, and control flow. Type aliases, interfaces, and `export type` are allowed since they have no runtime cost. Included in `base`, `react`, and `nextjs` presets.
8
+
9
+ - [#118](https://github.com/next-friday/eslint-plugin-nextfriday/pull/118) [`143eee9`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/143eee9fd0c6aed00e10677a6cad448dbdc9136e) Thanks [@joetakara](https://github.com/joetakara)! - Add `no-inline-type-import` rule. Disallows inline `type` markers on import specifiers (`import { type Foo }` and `import { value, type Foo }`); auto-fix hoists single inline-type imports to `import type { ... }` and splits mixed value/type imports into two separate statements while preserving aliases, default specifiers, and quote style. Also strips redundant inline markers from existing `import type { ... }` statements. Included in `base`, `react`, and `nextjs` presets.
10
+
11
+ ## 3.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#116](https://github.com/next-friday/eslint-plugin-nextfriday/pull/116) [`64ced55`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/64ced556b4099a40ca2f4750523c7a079d2c81d8) Thanks [@joetakara](https://github.com/joetakara)! - `nextjs` and `nextjs/recommended` presets now also disable `nextfriday/file-kebab-case` (in addition to `nextfriday/jsx-pascal-case`) for files under `app/**`, `src/app/**`, `pages/**`, and `src/pages/**`. The override globs are expanded from `*.{jsx,tsx}` to `*.{js,jsx,ts,tsx}` so `route.ts`, `middleware.ts`, and other framework-named `.ts`/`.js` files in those directories are no longer flagged by either filename rule. Filename conventions in routing directories are owned by the framework, not by this plugin.
16
+
17
+ The `react` and `react/recommended` presets are unchanged — projects using them still enforce `file-kebab-case` on every `.ts`/`.js` and `jsx-pascal-case` on every `.tsx`/`.jsx`.
18
+
19
+ ### Patch Changes
20
+
21
+ - [#116](https://github.com/next-friday/eslint-plugin-nextfriday/pull/116) [`64ced55`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/64ced556b4099a40ca2f4750523c7a079d2c81d8) Thanks [@joetakara](https://github.com/joetakara)! - Documentation quality improvements targeting Context7 scoring gaps:
22
+ - `README.md` — "Per-Directory Configuration" section now explains how flat config resolves rules (config-array order, `files`/`ignores` precedence, why flat replaces `.eslintrc` overrides), adds a preset-tier-per-directory table, and lists common edge cases (glob ordering, top-level vs scoped `ignores`, spreading array-shaped presets, `--print-config` for debugging).
23
+ - `README.md` — "Migration Strategy" section is restructured around six concrete phases: surveying violations with `eslint --format json | jq`, isolating the auto-fix pass into its own PR, adopting the warn-level preset, ratcheting clean directories to `/recommended`, managing exceptions (severity override → directory override → disable comment, in that order of preference), and tracking violation count over time. The "Prioritize rules by impact" table is unchanged.
24
+ - `docs/rules/ENFORCE_CONSTANT_CASE.md` — Configuration section split into install / enable-just-this-rule / enable-with-related-rules / enable-via-preset / scope-to-directory subsections, plus a "Severity-only — no rule options" callout clarifying that the legacy `["error", { ... }]` array form is not accepted because no rule in this plugin has options.
25
+
3
26
  ## 3.0.0
4
27
 
5
28
  ### Major Changes
package/README.md CHANGED
@@ -178,44 +178,115 @@ export default [
178
178
 
179
179
  ### Per-Directory Configuration
180
180
 
181
- ESLint flat config is an array of config objects. Each object's `files` and `ignores` glob patterns scope its rules to a subset of the project. Use this to apply different rule severities to different directories.
181
+ #### How ESLint flat config resolves rules
182
+
183
+ Flat config (the only format this plugin supports) is an **array of config objects** evaluated in order. For every file ESLint lints, it walks the array and merges every object whose `files` glob matches and whose `ignores` glob does not. Later objects override earlier ones rule-by-rule, so the **last matching object wins** for any given rule. Objects with no `files` field apply globally; objects with only `ignores` at the top level remove files from the entire run.
184
+
185
+ This is why per-directory configuration works: you stack a global default first, then layer narrower `files`-scoped objects on top. ESLint 9+ also flattens nested arrays automatically, so spreading a preset that ships as an array (like `nextfriday.configs.nextjs`) works the same as spreading a single object.
186
+
187
+ The legacy `.eslintrc` format used `overrides` and an inheritance tree to express the same thing. Flat config replaces that tree with a flat, deterministic array — easier to reason about, no implicit merging across `extends`, and no `parserOptions` plumbing per directory. The plugin does not ship `.eslintrc` shims, so projects on ESLint 8 or below cannot consume it without upgrading.
188
+
189
+ #### Layering strict and loose presets
190
+
191
+ Apply different rule severities to different directories by stacking config objects:
182
192
 
183
193
  ```js
184
194
  import nextfriday from "eslint-plugin-nextfriday";
185
195
 
186
196
  export default [
187
197
  {
188
- files: ["src/components/**/*.{ts,tsx}"],
189
- ...nextfriday.configs["react/recommended"],
198
+ ignores: ["src/legacy/**", "dist/**", "build/**", "**/*.generated.ts"],
190
199
  },
191
200
 
201
+ nextfriday.configs.react,
202
+
192
203
  {
193
- files: ["src/utils/**/*.ts"],
194
- ...nextfriday.configs.base,
204
+ files: ["src/components/**/*.{ts,tsx}", "src/hooks/**/*.{ts,tsx}"],
205
+ ...nextfriday.configs["react/recommended"],
195
206
  },
196
207
 
197
208
  {
198
- files: ["src/legacy/**/*.{ts,tsx}"],
199
- ignores: ["src/legacy/**/*"],
209
+ files: ["src/utils/**/*.ts", "src/lib/**/*.ts"],
210
+ rules: {
211
+ "nextfriday/require-explicit-return-type": "error",
212
+ "nextfriday/no-relative-imports": "error",
213
+ },
200
214
  },
201
215
 
202
216
  {
203
- files: ["**/*.test.{ts,tsx}"],
217
+ files: ["**/*.{test,spec}.{ts,tsx}"],
204
218
  rules: {
205
219
  "nextfriday/require-explicit-return-type": "off",
206
220
  "nextfriday/no-single-char-variables": "off",
221
+ "nextfriday/no-direct-date": "off",
207
222
  },
208
223
  },
209
224
  ];
210
225
  ```
211
226
 
212
- The first config block applies the strict `react/recommended` preset (errors) to component files. The second applies the looser `base` preset (warnings) to utilities. The third excludes legacy code from linting entirely. The fourth keeps lint enabled for tests but turns off rules that conflict with common test patterns.
227
+ Reading top to bottom:
228
+
229
+ 1. **Top-level `ignores`** removes legacy and build artifacts from the entire run. A config object containing _only_ `ignores` is treated as a global ignore — narrower `ignores` inside a `files`-scoped object only affect that object.
230
+ 2. **`nextfriday.configs.react`** applies as the warn-level baseline to every file ESLint sees (no `files` glob).
231
+ 3. **Component and hook directories** get promoted to `react/recommended` (errors). Because this object comes after the baseline, its severities win.
232
+ 4. **Utility directories** keep the warn-level baseline but selectively promote two correctness rules to `error`. Use this pattern when you don't want the full `/recommended` preset but do want specific rules treated as blocking.
233
+ 5. **Test files** keep most rules but turn off rules that conflict with common test patterns (single-letter loop counters, frozen `Date.now()` mocks, void-returning `it()` callbacks).
234
+
235
+ #### Choosing a preset tier per directory
236
+
237
+ | Code area | Suggested preset | Why |
238
+ | --------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------- |
239
+ | Library / SDK code | `base/recommended` or `react/recommended` | Public surface should be tightest. `error` blocks regressions at PR time. |
240
+ | New product code | `react/recommended` or `nextjs/recommended` | New code starts clean; lock the conventions in immediately. |
241
+ | Mature product code mid-migration | `react` or `nextjs` (warn) | Ship while migrating. Switch to `/recommended` after a clean run. |
242
+ | Tests, scripts, fixtures | preset + targeted overrides | Keep the core lint signal; turn off rules that mismatch test/CLI patterns. |
243
+ | Legacy / vendored / generated | top-level `ignores` | No lint signal at all. Don't waste reviewer attention or CI time. |
244
+
245
+ #### Edge cases and troubleshooting
246
+
247
+ - **Glob precedence is order-dependent, not specificity-based.** A more specific glob later in the array wins; a more specific glob _earlier_ in the array does not. If `files: ["src/**"]` appears after `files: ["src/components/**"]`, the broader rule set wins for components. Put broader configs first.
248
+ - **`files` and `ignores` are anchored to the project root** (the directory containing `eslint.config.{js,mjs,ts}`), not the file's directory. Use `**/` prefixes for matches anywhere in the tree (e.g., `**/*.test.ts`).
249
+ - **Top-level `ignores` ≠ `ignores` inside a config object.** A standalone object `{ ignores: [...] }` removes files from the entire run; an `ignores` field next to `files` and `rules` only narrows that one config object's match.
250
+ - **Spreading a preset replaces, not merges, the `plugins` field.** If you spread `...nextfriday.configs.react` and then a different config later, the later object's `plugins` wins. Re-declare `plugins: { nextfriday }` if you add rules in a later object.
251
+ - **`nextfriday.configs.nextjs` is an array, not a single object.** Spreading it inline with `...nextfriday.configs.nextjs` only spreads array indices, not the inner objects. Use it as an array entry (`nextfriday.configs.nextjs,`) instead, so ESLint 9+ flattens it.
252
+ - **Two presets in one project.** Use scoped `files` rather than two unscoped presets — unscoped presets stack and the later one's rule severities win for every file, which is rarely what you want.
253
+ - **Verifying the resolved config for a file:** `pnpm eslint --print-config path/to/file.tsx` prints the merged config ESLint would actually use. Reach for this when a rule fires (or doesn't) and you can't tell why.
213
254
 
214
255
  ### Migration Strategy
215
256
 
216
- For an existing codebase with many violations, enable rules gradually instead of all at once. Three patterns, in order of how disruptive each is to your team:
257
+ For an existing codebase with many violations, treat the migration as a phased rollout survey, fix, lock in, repeat.
258
+
259
+ #### 1. Survey violations before changing anything
260
+
261
+ Install the plugin as a dev dependency, drop a warn-level preset into `eslint.config.mjs`, and run a read-only lint. Don't `--fix` yet — you want to see the raw shape of the codebase first.
262
+
263
+ ```bash
264
+ pnpm add -D eslint-plugin-nextfriday eslint
265
+ pnpm eslint . --no-fix
266
+ ```
267
+
268
+ Group violations by rule so you can plan the work. Most CI dashboards do this for you, but a one-liner works locally:
269
+
270
+ ```bash
271
+ pnpm eslint . --no-fix --format json | jq -r '.[].messages[].ruleId' | sort | uniq -c | sort -rn
272
+ ```
273
+
274
+ The output tells you which rules account for most of the noise. A rule with 3 violations is a 10-minute fix; a rule with 800 is a multi-week project. Plan accordingly.
275
+
276
+ #### 2. Run the auto-fixers in an isolated PR
277
+
278
+ Roughly a third of this plugin's rules are auto-fixable (the `Fixable ✅` column in the [Rules](#rules) table). Run them in a dedicated commit so the diff is purely mechanical and reviewers don't have to read every line:
279
+
280
+ ```bash
281
+ pnpm eslint . --fix
282
+ git add -u && git commit -m "style(lint): autofix nextfriday rules"
283
+ ```
284
+
285
+ Open this as its own PR. Mixing auto-fix output with hand-written changes in the same diff makes review almost impossible.
286
+
287
+ #### 3. Adopt the warn-level preset
217
288
 
218
- **1. Start with the warn-level preset.** All rules surface as warnings, so the build still passes. Fix issues at your own pace, then switch to `/recommended`.
289
+ After the auto-fix pass, drop in the warn-level preset so the remaining violations surface during local dev and CI without breaking the build:
219
290
 
220
291
  ```js
221
292
  import nextfriday from "eslint-plugin-nextfriday";
@@ -223,7 +294,11 @@ import nextfriday from "eslint-plugin-nextfriday";
223
294
  export default [nextfriday.configs.react];
224
295
  ```
225
296
 
226
- **2. Lock-in a clean directory at a time.** Use `files` to apply `/recommended` (errors) only where the code is already clean, and the warn-level preset everywhere else.
297
+ Warnings don't fail `eslint --max-warnings=0`, so add that flag in CI only when you're ready to block on warnings. Until then, warnings are visible signal without pressure.
298
+
299
+ #### 4. Lock in clean directories one at a time
300
+
301
+ As individual directories or features reach zero violations, promote them to `/recommended` (errors) so regressions block at PR time. Everything else stays on the warn-level preset.
227
302
 
228
303
  ```js
229
304
  import nextfriday from "eslint-plugin-nextfriday";
@@ -238,11 +313,15 @@ export default [
238
313
  ];
239
314
  ```
240
315
 
241
- **3. Disable individual rules until the codebase is ready.** Re-declare specific rules with a lower severity (or `"off"`) after spreading the preset. Useful when one rule produces too much noise to fix at once.
316
+ Repeat per directory. This is how you ratchet without flag-day rewrites.
242
317
 
243
- ```js
244
- import nextfriday from "eslint-plugin-nextfriday";
318
+ #### 5. Manage exceptions explicitly
319
+
320
+ Three ways to carve out an exception, in order of preference. Pick the narrowest one that solves the problem.
245
321
 
322
+ **Per-rule severity override** — turn off a single noisy rule globally until you can fix it at the codebase level:
323
+
324
+ ```js
246
325
  export default [
247
326
  nextfriday.configs["react/recommended"],
248
327
 
@@ -255,14 +334,56 @@ export default [
255
334
  ];
256
335
  ```
257
336
 
258
- Pair these with `ignores` to skip vendored or generated files entirely:
337
+ **Per-directory exception** keep the rule strict everywhere except one stubborn corner:
338
+
339
+ ```js
340
+ export default [
341
+ nextfriday.configs["react/recommended"],
342
+
343
+ {
344
+ files: ["src/legacy/**/*.{ts,tsx}"],
345
+ rules: {
346
+ "nextfriday/no-relative-imports": "off",
347
+ "nextfriday/enforce-camel-case": "off",
348
+ },
349
+ },
350
+ ];
351
+ ```
352
+
353
+ **Per-file or per-line disable comments** — last resort, for genuinely irreducible cases:
354
+
355
+ ```ts
356
+ // eslint-disable-next-line nextfriday/no-direct-date
357
+ const epochAnchor = new Date(0);
358
+
359
+ /* eslint-disable nextfriday/no-emoji */
360
+ export const FLAG_EMOJIS = ["🇹🇭", "🇯🇵", "🇺🇸"];
361
+ /* eslint-enable nextfriday/no-emoji */
362
+ ```
363
+
364
+ Always disable a _named_ rule, never blanket-disable ESLint. A blanket `// eslint-disable` mutes every rule including correctness ones, so legitimate bugs slip through later.
365
+
366
+ **Skip vendored or generated files entirely** with a top-level ignore — these aren't exceptions to manage, they're code you don't lint at all:
259
367
 
260
368
  ```js
261
- {
262
- ignores: ["dist/**", "build/**", "**/*.generated.ts"],
263
- }
369
+ export default [
370
+ {
371
+ ignores: ["dist/**", "build/**", "coverage/**", "**/*.generated.ts"],
372
+ },
373
+
374
+ nextfriday.configs["react/recommended"],
375
+ ];
264
376
  ```
265
377
 
378
+ #### 6. Track progress so the migration actually lands
379
+
380
+ Migrations stall when nobody can see how close you are. Two cheap signals:
381
+
382
+ - **Violation count over time.** Pipe `pnpm eslint . --no-fix --format compact | wc -l` into your CI metrics or a daily Slack post. Trend it weekly. If the number stops dropping, the migration has stalled.
383
+ - **Verify a single file's resolved config.** When a contributor asks "why did this rule fire on my file?", run `pnpm eslint --print-config path/to/file.tsx`. The output shows exactly which severity ESLint resolved for every rule on that file — usually answers the question in seconds.
384
+
385
+ Once a directory hits zero, lock it in (step 4). Once the warn-level count hits zero across the whole repo, switch the global preset to `/recommended` and add `--max-warnings=0` to CI. The migration is done.
386
+
266
387
  #### Prioritize rules by impact
267
388
 
268
389
  When the warn-level preset surfaces hundreds of violations, fix them in this order — high-impact rules catch real bugs, while low-impact rules are style preferences that can wait.
@@ -313,6 +434,7 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
313
434
  | [enforce-sorted-destructuring](docs/rules/ENFORCE_SORTED_DESTRUCTURING.md) | Enforce alphabetical sorting of destructured properties | ✅ |
314
435
  | [no-env-fallback](docs/rules/NO_ENV_FALLBACK.md) | Disallow fallback values for environment variables | ❌ |
315
436
  | [no-inline-default-export](docs/rules/NO_INLINE_DEFAULT_EXPORT.md) | Disallow inline default exports - declare first, then export | ❌ |
437
+ | [index-export-only](docs/rules/INDEX_EXPORT_ONLY.md) | Restrict index files to imports, re-exports, and type declarations | ❌ |
316
438
  | [no-direct-date](docs/rules/NO_DIRECT_DATE.md) | Disallow direct usage of Date constructor and methods | ❌ |
317
439
  | [newline-after-multiline-block](docs/rules/NEWLINE_AFTER_MULTILINE_BLOCK.md) | Require a blank line after multi-line statements | ✅ |
318
440
  | [newline-before-return](docs/rules/NEWLINE_BEFORE_RETURN.md) | Require a blank line before return statements | ✅ |
@@ -328,6 +450,7 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
328
450
  | Rule | Description | Fixable |
329
451
  | -------------------------------------------------------------------- | --------------------------------------------------------- | ------- |
330
452
  | [no-relative-imports](docs/rules/NO_RELATIVE_IMPORTS.md) | Disallow relative imports with ../ - use absolute imports | ❌ |
453
+ | [no-inline-type-import](docs/rules/NO_INLINE_TYPE_IMPORT.md) | Disallow inline 'type' markers - hoist or split imports | ✅ |
331
454
  | [prefer-import-type](docs/rules/PREFER_IMPORT_TYPE.md) | Enforce using 'import type' for type-only imports | ✅ |
332
455
  | [prefer-react-import-types](docs/rules/PREFER_REACT_IMPORT_TYPES.md) | Enforce direct imports from 'react' instead of React.X | ✅ |
333
456
  | [sort-exports](docs/rules/SORT_EXPORTS.md) | Enforce a consistent ordering of export groups | ✅ |
@@ -377,14 +500,16 @@ In practice: turn the high tier on as `"error"` first, leave the medium tier as
377
500
 
378
501
  | Preset | Severity | Base Rules | JSX Rules | Next.js Rules | Total Rules |
379
502
  | -------------------- | -------- | ---------- | --------- | ------------- | ----------- |
380
- | `base` | warn | 40 | 0 | 0 | 40 |
381
- | `base/recommended` | error | 40 | 0 | 0 | 40 |
382
- | `react` | warn | 40 | 15 | 0 | 55 |
383
- | `react/recommended` | error | 40 | 15 | 0 | 55 |
384
- | `nextjs` | warn | 40 | 15 | 1 | 56 |
385
- | `nextjs/recommended` | error | 40 | 15 | 1 | 56 |
503
+ | `base` | warn | 42 | 0 | 0 | 42 |
504
+ | `base/recommended` | error | 42 | 0 | 0 | 42 |
505
+ | `react` | warn | 42 | 16 | 0 | 58 |
506
+ | `react/recommended` | error | 42 | 16 | 0 | 58 |
507
+ | `nextjs` | warn | 42 | 16 | 1 | 59 |
508
+ | `nextjs/recommended` | error | 42 | 16 | 1 | 59 |
509
+
510
+ The `nextjs` and `nextjs/recommended` presets ship as an array of two flat-config objects: the rule set above, plus a routing override that disables `nextfriday/file-kebab-case` and `nextfriday/jsx-pascal-case` for files matching `app/**/*.{js,jsx,ts,tsx}`, `src/app/**/*.{js,jsx,ts,tsx}`, `pages/**/*.{js,jsx,ts,tsx}`, and `src/pages/**/*.{js,jsx,ts,tsx}`. Next.js owns the filenames in those directories (`page.tsx`, `layout.tsx`, `route.ts`, `middleware.ts`, etc.), so the plugin steps out of the way. ESLint 9+ flattens nested config arrays automatically, so spreading the preset works as expected.
386
511
 
387
- ### Base Configuration Rules (40 rules)
512
+ ### Base Configuration Rules (42 rules)
388
513
 
389
514
  Included in `base`, `base/recommended`, and all other presets:
390
515
 
@@ -398,6 +523,7 @@ Included in `base`, `base/recommended`, and all other presets:
398
523
  - `nextfriday/enforce-sorted-destructuring`
399
524
  - `nextfriday/enforce-type-declaration-order`
400
525
  - `nextfriday/file-kebab-case`
526
+ - `nextfriday/index-export-only`
401
527
  - `nextfriday/newline-after-multiline-block`
402
528
  - `nextfriday/newline-before-return`
403
529
  - `nextfriday/no-complex-inline-return`
@@ -407,6 +533,7 @@ Included in `base`, `base/recommended`, and all other presets:
407
533
  - `nextfriday/no-inline-default-export`
408
534
  - `nextfriday/no-inline-nested-object`
409
535
  - `nextfriday/no-inline-return-properties`
536
+ - `nextfriday/no-inline-type-import`
410
537
  - `nextfriday/no-lazy-identifiers`
411
538
  - `nextfriday/no-logic-in-params`
412
539
  - `nextfriday/no-misleading-constant-case`
@@ -58,7 +58,36 @@ function foo() {
58
58
 
59
59
  ## Configuration
60
60
 
61
- This rule pairs with [`no-misleading-constant-case`](./NO_MISLEADING_CONSTANT_CASE.md) so that static globals use `SCREAMING_SNAKE_CASE` while local scopes and dynamic values keep `camelCase`. ESLint 9+ flat config:
61
+ This rule has no options — only severity is configurable (`"error"`, `"warn"`, `"off"`). It pairs with [`no-misleading-constant-case`](./NO_MISLEADING_CONSTANT_CASE.md) so that static globals use `SCREAMING_SNAKE_CASE` while local scopes and dynamic values keep `camelCase`.
62
+
63
+ ### Install
64
+
65
+ ```bash
66
+ pnpm add -D eslint-plugin-nextfriday eslint
67
+ # npm install --save-dev eslint-plugin-nextfriday eslint
68
+ # yarn add --dev eslint-plugin-nextfriday eslint
69
+ ```
70
+
71
+ ### Enable just this rule
72
+
73
+ Use this when you want the rule but not the rest of the preset (e.g., adopting one rule at a time during a migration). The `plugins` field registers the plugin under the `nextfriday` namespace; the `rules` field turns on the rule by name:
74
+
75
+ ```js
76
+ import nextfriday from "eslint-plugin-nextfriday";
77
+
78
+ export default [
79
+ {
80
+ plugins: { nextfriday },
81
+ rules: {
82
+ "nextfriday/enforce-constant-case": "error",
83
+ },
84
+ },
85
+ ];
86
+ ```
87
+
88
+ ### Enable with related rules
89
+
90
+ Constants, locals, and dynamic values each need a different naming convention. Enable the trio together so violations from any direction surface:
62
91
 
63
92
  ```js
64
93
  import nextfriday from "eslint-plugin-nextfriday";
@@ -75,7 +104,9 @@ export default [
75
104
  ];
76
105
  ```
77
106
 
78
- Or via a preset (every preset already enables all three at the configured severity):
107
+ ### Enable via a preset
108
+
109
+ Every preset includes this rule at the preset's severity (`warn` or `error`). This is the simplest setup and the recommended one for most projects:
79
110
 
80
111
  ```js
81
112
  import nextfriday from "eslint-plugin-nextfriday";
@@ -83,7 +114,38 @@ import nextfriday from "eslint-plugin-nextfriday";
83
114
  export default [nextfriday.configs["base/recommended"]];
84
115
  ```
85
116
 
86
- This plugin only supports ESLint 9+ flat config — legacy `.eslintrc` is not supported.
117
+ ### Scope to a directory
118
+
119
+ If you're migrating an existing codebase, scope the rule to a clean directory first and leave the rest of the project on `"warn"` until you've fixed the violations there. ESLint 9+ flat config layers configs in array order; the later object's severity wins for any file matching its `files` glob:
120
+
121
+ ```js
122
+ import nextfriday from "eslint-plugin-nextfriday";
123
+
124
+ export default [
125
+ nextfriday.configs.base,
126
+
127
+ {
128
+ files: ["src/config/**/*.ts", "src/lib/**/*.ts"],
129
+ rules: {
130
+ "nextfriday/enforce-constant-case": "error",
131
+ },
132
+ },
133
+ ];
134
+ ```
135
+
136
+ ### Severity-only — no rule options
137
+
138
+ Each rule in this plugin uses `schema: []` and `defaultOptions: []` (no options). The flat-config value is **only** the severity string — `"error"`, `"warn"`, or `"off"`. The legacy `["error", { ... }]` array form is not accepted because there are no options to pass.
139
+
140
+ ```js
141
+ // Correct
142
+ "nextfriday/enforce-constant-case": "error"
143
+
144
+ // Won't apply — there are no options to override
145
+ "nextfriday/enforce-constant-case": ["error", { allowCamelCase: true }]
146
+ ```
147
+
148
+ This plugin only supports ESLint 9+ flat config — legacy `.eslintrc` is not supported. Projects on ESLint 8 or below cannot consume it without upgrading.
87
149
 
88
150
  ## When Not To Use It
89
151
 
@@ -40,9 +40,15 @@ This rule enforces that all TypeScript (.ts) and JavaScript (.js) files use keba
40
40
  - **Numbers are allowed inside segments.** `file-with-numbers-123.ts` ✓
41
41
  - **Single-word filenames are valid.** `single.ts` ✓ (no hyphens needed)
42
42
 
43
+ ## Scoping the Rule
44
+
45
+ This rule has **no built-in framework detection**. It checks every `.ts`/`.js` file it sees against kebab-case. In Next.js projects, routing directories (`app/`, `pages/`) contain framework-named files (`route.ts`, `middleware.ts`) that already happen to be kebab-case — but if you colocate helpers there with non-kebab names (`useThing.ts`, `MyHelper.ts`), the rule will flag them.
46
+
47
+ The `nextjs` and `nextjs/recommended` presets ship with an override that **automatically disables this rule** for files under `app/**`, `src/app/**`, `pages/**`, and `src/pages/**` (matched against `*.{js,jsx,ts,tsx}`). Filename conventions in those directories are owned by the framework, not by this plugin. The `base` and `react` presets do **not** include this override — they enforce `file-kebab-case` on every `.ts`/`.js` regardless of directory.
48
+
43
49
  ## Disabling the Rule
44
50
 
45
- To opt out for a specific directory or file pattern, add an override in your flat config:
51
+ To opt out for additional directories or file patterns, add an override in your flat config:
46
52
 
47
53
  ```js
48
54
  import nextfriday from "eslint-plugin-nextfriday";
@@ -0,0 +1,88 @@
1
+ # index-export-only
2
+
3
+ Restrict `index` files to imports, re-exports, and type declarations only.
4
+
5
+ ## Rule Details
6
+
7
+ This rule enforces that `index.{js,jsx,ts,tsx}` files act purely as barrel files. Any runtime declaration — functions, classes, variables, top-level expressions, or inline `export const`/`export function`/`export class` — must live in its own module and be re-exported from the index.
8
+
9
+ The rule applies only when the basename of the file is `index`. Files like `index.test.ts`, `index.spec.ts`, or `index.d.ts` are not affected.
10
+
11
+ ### Why?
12
+
13
+ Index files are entry points. Mixing implementation into them obscures where behavior lives, breaks file-based code navigation, and makes the import surface harder to refactor. A barrel file should describe the public API of a directory — nothing more.
14
+
15
+ ## Examples
16
+
17
+ ### Incorrect
18
+
19
+ ```ts
20
+ import { clsx, type ClassValue } from "clsx";
21
+ import { twMerge } from "tailwind-merge";
22
+
23
+ function cn(...inputs: ClassValue[]): string {
24
+ return twMerge(clsx(inputs));
25
+ }
26
+
27
+ export { cn };
28
+ ```
29
+
30
+ ```ts
31
+ export const VERSION = "1.0.0";
32
+
33
+ export function helper() {
34
+ return 1;
35
+ }
36
+
37
+ export class Service {}
38
+ ```
39
+
40
+ ```ts
41
+ console.log("loaded");
42
+ ```
43
+
44
+ ### Correct
45
+
46
+ ```ts
47
+ export { cn } from "./cn";
48
+ export * from "./types";
49
+ export type { Props } from "./props";
50
+ export { default as Button } from "./button";
51
+ ```
52
+
53
+ ```ts
54
+ import button from "./button";
55
+
56
+ export default button;
57
+ ```
58
+
59
+ ```ts
60
+ export type Foo = string;
61
+ export interface Bar {
62
+ id: string;
63
+ }
64
+ ```
65
+
66
+ ## What This Rule Allows
67
+
68
+ - `import` statements (including side-effect imports like `import "./styles.css"`)
69
+ - Specifier-only `export` and `export ... from` re-exports
70
+ - `export *` and `export * as ns` re-exports
71
+ - `export default identifier` where the identifier comes from an import
72
+ - Top-level `type` aliases and `interface` declarations (they have no runtime cost)
73
+ - `export type` and `export interface` declarations
74
+
75
+ ## What This Rule Disallows
76
+
77
+ - `function`, `class`, and `const`/`let`/`var` declarations at the top level
78
+ - Inline `export function`, `export class`, `export const`, `export let`, `export var`
79
+ - `export default` of a function/class/literal/object expression
80
+ - Top-level expression statements and control flow (`console.log(...)`, `if`, `for`, etc.)
81
+
82
+ ## When Not To Use It
83
+
84
+ If your project intentionally mixes implementation and re-exports in index files — for example, a single-file utility library where `index.ts` is the only source file — disable this rule.
85
+
86
+ ## Related Rules
87
+
88
+ - [no-inline-default-export](./NO_INLINE_DEFAULT_EXPORT.md) - Disallow inline default and named exports across all files
@@ -30,7 +30,11 @@ This rule enforces that JSX and TSX files use PascalCase naming convention for t
30
30
 
31
31
  ## Scoping the Rule
32
32
 
33
- This rule has **no built-in framework detection** and no allowlist of "known" filenames. It checks every `.jsx`/`.tsx` it sees. If your project mixes component files with framework routing files that use lowercase names (e.g. Next.js App Router `page.tsx`, `layout.tsx`, `error.tsx`, or Pages Router `_app.tsx`), scope the rule explicitly via ESLint's `files` glob in flat config:
33
+ This rule has **no built-in framework detection** and no allowlist of "known" filenames. It checks every `.jsx`/`.tsx` it sees against PascalCase.
34
+
35
+ The `nextjs` and `nextjs/recommended` presets ship with an override that **automatically disables this rule** for files under `app/**`, `src/app/**`, `pages/**`, and `src/pages/**` — Next.js owns those filenames (`page.tsx`, `layout.tsx`, `error.tsx`, etc.). If you use a `nextjs` preset, you do not need to add a routing override yourself.
36
+
37
+ The `base` and `react` presets do **not** include this override. If you use `react` on a Next.js project (or any project that mixes PascalCase component files with lowercase framework routing files), scope the rule explicitly via ESLint's `files` glob:
34
38
 
35
39
  ```js
36
40
  import nextfriday from "eslint-plugin-nextfriday";
@@ -56,7 +60,7 @@ export default [
56
60
 
57
61
  The first override turns the rule on only inside component directories where PascalCase is the convention. The second override explicitly disables the rule for Next.js routing directories where the framework owns the filename.
58
62
 
59
- The plugin deliberately does not try to detect Next.js, Remix, or other framework conventions automatically — folder structures vary across projects (monorepos, custom `app` locations, hybrid Pages + App Router setups), and a built-in allowlist would inevitably go stale. ESLint's `files` glob is the deterministic way to express the scope you actually want.
63
+ The plugin deliberately does not try to detect Next.js, Remix, or other framework conventions outside of the `nextjs` preset — folder structures vary across projects (monorepos, custom `app` locations, hybrid Pages + App Router setups), and a built-in allowlist would inevitably go stale. ESLint's `files` glob is the deterministic way to express the scope you actually want.
60
64
 
61
65
  ## When Not To Use It
62
66
 
@@ -0,0 +1,86 @@
1
+ # no-inline-type-import
2
+
3
+ Disallow inline `type` markers on import specifiers. Use `import type` or split into a separate type-only import statement.
4
+
5
+ > This rule is auto-fixable using `--fix`.
6
+
7
+ ## Rule Details
8
+
9
+ This rule forbids the inline-`type` form `import { type Foo }` and the mixed form `import { value, type Foo }`. Type-only imports must be expressed at the statement level with `import type { ... }`. When value and type imports come from the same module, the rule splits them into two separate statements.
10
+
11
+ ### Why?
12
+
13
+ Statement-level `import type` makes the runtime cost of every import unambiguous at a glance, simplifies tooling that distinguishes erased imports from real ones (bundlers, type-only emit, transpilers), and removes the need for readers to scan each specifier for an inline keyword.
14
+
15
+ ## Examples
16
+
17
+ ### Incorrect
18
+
19
+ ```ts
20
+ import { type foo } from "bar";
21
+
22
+ import { baz, type moo } from "mee";
23
+
24
+ import Default, { value, type Foo } from "src";
25
+ ```
26
+
27
+ ### Correct
28
+
29
+ ```ts
30
+ import type { foo } from "bar";
31
+
32
+ import { baz } from "mee";
33
+ import type { moo } from "mee";
34
+
35
+ import Default, { value } from "src";
36
+ import type { Foo } from "src";
37
+ ```
38
+
39
+ ## Auto-fixing
40
+
41
+ The rule's `--fix` produces these transformations:
42
+
43
+ ```ts
44
+ // Single inline type → hoisted
45
+ import { type foo } from "bar";
46
+ // becomes
47
+ import type { foo } from "bar";
48
+
49
+ // All inline types → hoisted
50
+ import { type foo, type bar } from "src";
51
+ // becomes
52
+ import type { foo, bar } from "src";
53
+
54
+ // Mixed value + inline type → split
55
+ import { baz, type moo } from "mee";
56
+ // becomes
57
+ import { baz } from "mee";
58
+ import type { moo } from "mee";
59
+
60
+ // Default + inline type → split
61
+ import Default, { type Foo } from "src";
62
+ // becomes
63
+ import Default from "src";
64
+ import type { Foo } from "src";
65
+
66
+ // Aliases are preserved
67
+ import { foo as bar, type baz as qux } from "src";
68
+ // becomes
69
+ import { foo as bar } from "src";
70
+ import type { baz as qux } from "src";
71
+
72
+ // Redundant inline markers inside `import type` are stripped
73
+ import type { foo, type bar } from "src";
74
+ // becomes
75
+ import type { foo, bar } from "src";
76
+ ```
77
+
78
+ After auto-fix, other import-ordering rules (such as `sort-imports`) may re-order the resulting statements according to their own grouping rules.
79
+
80
+ ## When Not To Use It
81
+
82
+ If your codebase intentionally uses the inline `type` form to keep value and type imports adjacent in a single statement, disable this rule.
83
+
84
+ ## Related Rules
85
+
86
+ - [prefer-import-type](./PREFER_IMPORT_TYPE.md) - Hoists imports that are used only as types to `import type`. Complements this rule for usage-based detection.