@noctcore/eslint-plugin-architecture 0.1.0 → 0.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.
@@ -0,0 +1,48 @@
1
+ # `noctcore-architecture/colocated-test-required`
2
+
3
+ > A source file matching an `include` glob must have a colocated `*.test.*` / `*.spec.*` sibling on disk. Off until `include` is configured.
4
+
5
+ ## Why
6
+
7
+ Some files are risky enough that shipping them untested should be a lint error, not a code-review
8
+ afterthought — hooks, services, reducers, money math. This rule lets you name those globs and then
9
+ requires each matching file to carry its test right next to it, so the test travels with the code and
10
+ is obvious when missing.
11
+
12
+ ## What it flags
13
+
14
+ For a file matching `include`, the rule reads the file's directory and looks for a sibling whose name
15
+ is `<stem>.test.<ext>` or `<stem>.spec.<ext>` (any extension). If none exists, it reports.
16
+
17
+ ```
18
+ src/hooks/useCart.ts ← include: ['**/use*.ts']
19
+ src/hooks/useCart.test.ts ✓ colocated test present
20
+ src/hooks/useWishlist.ts ✗ no useWishlist.test.* / .spec.* sibling
21
+ ```
22
+
23
+ A test file that itself matches `include` is never asked to test itself. Directory listings are
24
+ cached for the lint run, so a folder full of gated files is read once.
25
+
26
+ ## Options
27
+
28
+ | Option | Type | Default | Meaning |
29
+ | --- | --- | --- | --- |
30
+ | `include` | `string[]` | `[]` | Globs of source files that must have a colocated test. **Empty = the rule is off.** |
31
+ | `ignore` | `string[]` | `[]` | Globs of files to exempt even when they match `include`. |
32
+
33
+ ```js
34
+ 'noctcore-architecture/colocated-test-required': ['error', {
35
+ include: ['**/use*.ts', '**/*.service.ts', '**/*.reducer.ts'],
36
+ ignore: ['**/*.d.ts'],
37
+ }]
38
+ ```
39
+
40
+ ## In `recommended`
41
+
42
+ Ships **`'off'`**. There is no universal "everything needs a colocated test" default, so the
43
+ `recommended` preset cannot safely enable it — turn it on with your own `include` globs.
44
+
45
+ ## When not to use it
46
+
47
+ If your tests live in a separate `__tests__` tree or a top-level `test/` directory rather than beside
48
+ the source, this colocation check does not model your layout. Leave it off.
@@ -0,0 +1,56 @@
1
+ # `noctcore-architecture/filename-matches-export`
2
+
3
+ > A file's basename must match its primary export (a default export, or the sole named export).
4
+
5
+ ## Why
6
+
7
+ When a module has one clear public export, the filename should announce it. A `helpers.ts` that
8
+ exports a single `formatDate` hides its contents from anyone scanning the file tree, and a rename of
9
+ one without the other leaves the two permanently out of sync. Keeping them aligned makes the tree
10
+ self-describing.
11
+
12
+ ## What it flags
13
+
14
+ The rule first resolves the file's **primary export**:
15
+
16
+ 1. If the file has a default export with a name (`export default function Foo`, `export default
17
+ class Foo`, `export default Foo`), that name is the primary export. An anonymous default
18
+ (`export default () => …`) has no identifier to compare, so the file is skipped.
19
+ 2. Otherwise, if the file has exactly one local named export, that is the primary export.
20
+ 3. Otherwise (no primary, or several named exports) the file is skipped.
21
+
22
+ The basename and the identifier are compared **case- and separator-insensitively**, so naming
23
+ conventions never collide:
24
+
25
+ ```ts
26
+ // TaskCard.tsx → export default function TaskCard() {} ✓
27
+ // use-thing.ts → export const useThing = () => {} ✓ (kebab ↔ camel)
28
+ // helpers.ts → export const formatDate = () => {} ✗ (genuine mismatch)
29
+ ```
30
+
31
+ `index` files and files matching an `ignore` glob are always skipped.
32
+
33
+ ## Suggestion, not autofix
34
+
35
+ A mismatch offers a **suggestion** to rename the export to the filename (the code-side resolution —
36
+ the other is renaming the file). It is never an autofix: renaming a public identifier is a decision a
37
+ human should confirm. When the basename is not a valid identifier (e.g. `2fa.ts`), the mismatch is
38
+ reported without a suggestion.
39
+
40
+ ## Options
41
+
42
+ | Option | Type | Default | Meaning |
43
+ | --- | --- | --- | --- |
44
+ | `ignore` | `string[]` | `[]` | Globs (supporting `**`, `*`, `?`) of files to skip. |
45
+
46
+ ```js
47
+ 'noctcore-architecture/filename-matches-export': ['error', {
48
+ ignore: ['**/*.stories.tsx', '**/route.ts'],
49
+ }]
50
+ ```
51
+
52
+ ## When not to use it
53
+
54
+ If your files routinely export several unrelated symbols, or you use fixed conventional filenames
55
+ (`route.ts`, `handler.ts`) that will never match their export, add them to `ignore` or leave the rule
56
+ off.
@@ -0,0 +1,52 @@
1
+ # `noctcore-architecture/max-import-depth`
2
+
3
+ > A relative import may not climb more than `max` parent levels (default 3). Autofixed to a path alias when one is configured.
4
+
5
+ ## Why
6
+
7
+ A relative import that climbs several directories (`../../../../shared/log`) is brittle and hard to
8
+ read: it couples the file to its exact position in the tree, so moving either end breaks the path. A
9
+ path alias (`@/shared/log`) is stable and self-locating. This rule caps how far a relative import may
10
+ reach before it must become an alias.
11
+
12
+ ## What it flags
13
+
14
+ Any relative specifier whose leading `..` run exceeds `max` is reported, on every source-carrying
15
+ construct: `import`, `import()`, `export … from`, and `export * from`.
16
+
17
+ ```ts
18
+ // max: 3 (default)
19
+ import a from '../../../shared'; // ✓ exactly at the limit
20
+ import b from '../../../../shared/log'; // ✗ climbs 4 levels
21
+ ```
22
+
23
+ ## Autofix
24
+
25
+ When `alias` maps a directory-anchor segment to an alias prefix, a too-deep import that resolves
26
+ *through* that anchor is autofixed:
27
+
28
+ ```js
29
+ 'noctcore-architecture/max-import-depth': ['error', {
30
+ alias: { src: '@' },
31
+ }]
32
+ ```
33
+
34
+ ```ts
35
+ // from src/a/b/c/d/deep.ts
36
+ import x from '../../../../shared/log'; // → import x from '@/shared/log';
37
+ ```
38
+
39
+ With no matching alias anchor on the resolved path, the violation is **reported without a fix** —
40
+ there is nothing safe to rewrite it to.
41
+
42
+ ## Options
43
+
44
+ | Option | Type | Default | Meaning |
45
+ | --- | --- | --- | --- |
46
+ | `max` | `integer` | `3` | Maximum number of `..` parent hops a relative import may take. |
47
+ | `alias` | `Record<string, string>` | `{}` | Map of directory-anchor segment → alias prefix used to autofix, e.g. `{ src: '@' }`. |
48
+
49
+ ## When not to use it
50
+
51
+ If you do not use path aliases and genuinely prefer deep relative imports, raise `max` or leave the
52
+ rule off.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noctcore/eslint-plugin-architecture",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-agnostic folder-per-component and feature-boundary architecture ESLint rules.",
5
5
  "license": "MIT",
6
6
  "type": "module",