{"_id":"@boring-stack-pkg/eslint-plugin-react-component-architecture","_rev":"3-b9ab56a445ac34be73386d4a8279c692","name":"@boring-stack-pkg/eslint-plugin-react-component-architecture","dist-tags":{"latest":"0.3.1"},"versions":{"0.2.1":{"name":"@boring-stack-pkg/eslint-plugin-react-component-architecture","version":"0.2.1","keywords":["eslint","eslintplugin","typescript","react","component","architecture"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-react-component-architecture@0.2.1","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/AI-Starter-Templates/eslint-plugins#readme","bugs":{"url":"https://github.com/AI-Starter-Templates/eslint-plugins/issues"},"dist":{"shasum":"813868537c0769428b000367a9c23eb4da9bd317","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-react-component-architecture/-/eslint-plugin-react-component-architecture-0.2.1.tgz","fileCount":26,"integrity":"sha512-xBg28rIZ8Z5vS1eWoB8OUQAPX4hbDR2eyaleqUGzJsFra5OMO83TTlKKCcT+wzi3kq0MJ64iTs/Px9BRxR5/eA==","signatures":[{"sig":"MEUCIQCSvvnL6z6YlAQKcIdJrQm68HsraJBc9TbniYGDT5Uw3gIgA7kJh6LBWsji4OvRGLxKStVWF34WsDalOlCnanJ6eF0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@boring-stack-pkg%2feslint-plugin-react-component-architecture@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":136176},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-react-component-architecture-0.2.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/dea23188efe61073127f286d5da762b4/boring-stack-pkg-eslint-plugin-react-component-architecture-0.2.1.tgz","_integrity":"sha512-xBg28rIZ8Z5vS1eWoB8OUQAPX4hbDR2eyaleqUGzJsFra5OMO83TTlKKCcT+wzi3kq0MJ64iTs/Px9BRxR5/eA==","repository":{"url":"git+https://github.com/AI-Starter-Templates/eslint-plugins.git","type":"git","directory":"eslint-plugin-react-component-architecture"},"_npmVersion":"10.9.7","description":"ESLint plugin enforcing React component architecture conventions.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.2","dependencies":{"yaml":"2.4.1","@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-react-component-architecture_0.2.1_1779219600231_0.840721500100285","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@boring-stack-pkg/eslint-plugin-react-component-architecture","version":"0.3.0","keywords":["eslint","eslintplugin","typescript","react","component","architecture"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-react-component-architecture@0.3.0","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"85361a68c8843b30cb65ca3e9e37c7f79eb5805a","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-react-component-architecture/-/eslint-plugin-react-component-architecture-0.3.0.tgz","fileCount":26,"integrity":"sha512-DZJnqa97Fyzge7uhMDwpXaz8YWvzobhcR8d3VcaU4VBcUaprJBlW3TkSDP5gWhfVdPrrLTpOzdkdTYaz8Ks/Yw==","signatures":[{"sig":"MEQCIDh1O1c4s4XF7S0ZRVvEeYLjPWgtp7n/R2skIa/X7dNXAiB6vl+Y+AL+tKBaHNX40EJ5faW3xo3aJMdaGnBWLWbj3g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":136095},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-react-component-architecture-0.3.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/90ef5dd4ce0f635978575aad7b10f045/boring-stack-pkg-eslint-plugin-react-component-architecture-0.3.0.tgz","_integrity":"sha512-DZJnqa97Fyzge7uhMDwpXaz8YWvzobhcR8d3VcaU4VBcUaprJBlW3TkSDP5gWhfVdPrrLTpOzdkdTYaz8Ks/Yw==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-react-component-architecture"},"_npmVersion":"11.15.0","description":"ESLint plugin enforcing React component architecture conventions.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"yaml":"2.4.1","@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"^8.57.0 || ^9.0.0 || ^10.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-react-component-architecture_0.3.0_1779826112072_0.2028875943174353","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"_id":"@boring-stack-pkg/eslint-plugin-react-component-architecture@0.3.1","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"ba8fca9e8c63a2e9cfd1f8fdb423fb00a4fe12a9","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-react-component-architecture/-/eslint-plugin-react-component-architecture-0.3.1.tgz","fileCount":26,"integrity":"sha512-DJKVeKE919DKvMN/N6POE9/I0Lw29SD9j9DW88KXKWlI8cAOA95EHg/W7St+nOqZJGYd8uxJVBfzuEGdHnbgPg==","signatures":[{"sig":"MEUCIQDIMjFQo/B14D229rkrV4fG5zN4EbBs0uFygNuBnWWQSwIgcjttO6pAlpGrW3+daYArQYKof2Gb9peOC3qRk94XsqI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFdJCVAX3ft9tbpMdhLYcSV8ENFFQKQ11indMW1VEbM6AiEAiRldfye3OoQcXPEaP0RB3E3hgTl0qfK0TD2fHzM90Gs="}],"unpackedSize":137395},"main":"./dist/index.cjs","name":"@boring-stack-pkg/eslint-plugin-react-component-architecture","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-react-component-architecture-0.3.1.tgz","types":"./dist/index.d.ts","author":"","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"license":"MIT","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"0.3.1","_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","react","component","architecture"],"_resolved":"/tmp/7b3571adc10c5cccd5130593a64f7ae1/boring-stack-pkg-eslint-plugin-react-component-architecture-0.3.1.tgz","_integrity":"sha512-DJKVeKE919DKvMN/N6POE9/I0Lw29SD9j9DW88KXKWlI8cAOA95EHg/W7St+nOqZJGYd8uxJVBfzuEGdHnbgPg==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-react-component-architecture"},"_npmVersion":"11.19.1","description":"ESLint plugin enforcing React component architecture conventions.","directories":{},"maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"yaml":"2.4.1","@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"^8.57.0 || ^9.0.0 || ^10.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-react-component-architecture_0.3.1_1789409006744_0.6317197187430739"}}},"time":{"created":"2026-05-19T19:39:59.989Z","modified":"2026-09-14T18:03:28.081Z","0.2.1":"2026-05-19T19:40:00.367Z","0.3.0":"2026-05-26T20:08:32.240Z","0.3.1":"2026-09-14T18:03:26.844Z"},"bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"license":"MIT","homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","react","component","architecture"],"repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-react-component-architecture"},"description":"ESLint plugin enforcing React component architecture conventions.","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"readme":"# eslint-plugin-react-component-architecture\n\nESLint plugin enforcing React component architecture conventions from [AGENTS.md](https://github.com/DreamData-AI/Programmer-Network-Frontend/blob/main/AGENTS.md).\n\n## Why\n\nReact components benefit from enforcing a strict folder structure and separation of concerns. This plugin ensures that:\n\n- Components are properly structured with hooks, types, tests, and stories as siblings\n- State management is isolated in custom hooks, not in component bodies\n- JSX templates stay minimal and clean (no inline functions or complex logic)\n- Props describe visual state only, not business logic\n- TypeScript interfaces follow naming conventions\n- Imports use consistent patterns (e.g., named imports for React)\n- Dark mode classes are removed (light mode only)\n\n## Install\n\n```sh\npnpm add -D @boring-stack-pkg/eslint-plugin-react-component-architecture @typescript-eslint/parser\n```\n\n## Usage (flat config)\n\n```js\n// eslint.config.mjs\nimport tsParser from \"@typescript-eslint/parser\";\nimport reactComponentArchitecture from \"@boring-stack-pkg/eslint-plugin-react-component-architecture\";\n\nexport default [\n  {\n    files: [\"**/*.{ts,tsx}\"],\n    languageOptions: {\n      parser: tsParser,\n      parserOptions: { ecmaVersion: \"latest\", sourceType: \"module\", ecmaFeatures: { jsx: true } },\n    },\n    plugins: { \"react-component-architecture\": reactComponentArchitecture },\n    rules: reactComponentArchitecture.configs.recommended.rules,\n  },\n];\n```\n\nThe recommended preset enables all 15 rules. Most rules are set to `\"error\"`. Heuristic rules (`no-state-in-component-body`, `no-jsx-computation`, `props-must-be-visual`) default to `\"warn\"`.\n\n## Rules\n\n| Rule | Type | Description |\n|------|------|-------------|\n| [`component-folder-structure`](docs/rules/component-folder-structure.md) | Problem | Enforce required sibling files in component folders |\n| [`index-must-reexport-default`](docs/rules/index-must-reexport-default.md) | Problem | index.ts must re-export component default export |\n| [`no-state-in-component-body`](docs/rules/no-state-in-component-body.md) | Suggestion | Move state hooks to custom hooks (.hooks.ts) |\n| [`no-inline-jsx-functions`](docs/rules/no-inline-jsx-functions.md) | Suggestion | Use function references instead of inline functions for handlers |\n| [`no-jsx-computation`](docs/rules/no-jsx-computation.md) | Suggestion | Extract complex JSX computations into hooks |\n| [`classnames-required`](docs/rules/classnames-required.md) | Suggestion | Use classNames for dynamic className values |\n| [`classnames-import-name`](docs/rules/classnames-import-name.md) | Suggestion | Import classnames with correct name |\n| [`no-dark-mode-classes`](docs/rules/no-dark-mode-classes.md) | Suggestion | Remove dark: Tailwind classes (light mode only) |\n| [`interface-prefix-i`](docs/rules/interface-prefix-i.md) | Suggestion | Interfaces should be prefixed with 'I' |\n| [`forwardref-display-name`](docs/rules/forwardref-display-name.md) | Problem | forwardRef components must have displayName |\n| [`stories-require-default-export`](docs/rules/stories-require-default-export.md) | Problem | Story files must export Default |\n| [`props-must-be-visual`](docs/rules/props-must-be-visual.md) | Suggestion | Props should be visual, not business logic |\n| [`react-import-named`](docs/rules/react-import-named.md) | Suggestion | Use named imports from React |\n| [`package-json-exact-deps`](docs/rules/package-json-exact-deps.md) | Problem | Enforce exact dependency versions |\n| [`github-actions-permissions`](docs/rules/github-actions-permissions.md) | Problem | Enforce permissions and pinned action refs |\n\n## Examples\n\n### no-state-in-component-body\n\n```tsx\n// ❌ State in component body\nfunction Counter() {\n  const [count, setCount] = useState(0);\n  return <div>{count}</div>;\n}\n\n// ✅ State in custom hook\nfunction useCounter() {\n  const [count, setCount] = useState(0);\n  return { count, setCount };\n}\n\nfunction Counter() {\n  const { count } = useCounter();\n  return <div>{count}</div>;\n}\n```\n\n### no-inline-jsx-functions\n\n```tsx\n// ❌ Inline function\nfunction Button() {\n  return <button onClick={() => alert('clicked')}>Click</button>;\n}\n\n// ✅ Named function reference\nfunction Button() {\n  const handleClick = () => alert('clicked');\n  return <button onClick={handleClick}>Click</button>;\n}\n```\n\n### classnames-required\n\n```tsx\n// ❌ Ternary in className\n<button className={isActive ? 'active' : 'inactive'}>Click</button>\n\n// ✅ Use classNames utility\nimport classNames from 'classnames';\n<button className={classNames({ active: isActive })}>Click</button>\n```\n\n## Component Folder Structure\n\nEach component should follow this structure:\n\n```\nComponentName/\n├── ComponentName.tsx              // Main component\n├── ComponentName.types.ts         // TypeScript interfaces\n├── ComponentName.hooks.ts         // Custom hooks (if needed)\n├── ComponentName.stories.tsx      // Storybook stories\n├── ComponentName.test.ts          // Tests\n├── ComponentName.utils.ts         // Utilities (optional)\n├── ComponentName.constants.ts     // Constants (optional)\n└── index.ts                       // Exports\n```\n\nThe `index.ts` must contain:\n\n```ts\nexport { default as ComponentName } from \"./ComponentName\";\nexport * from \"./ComponentName.types\";\n```\n\n## Notes\n\n- **Rules 14 & 15**: `package-json-exact-deps` and `github-actions-permissions` perform file-content parsing and are best used with appropriate parsers configured in your ESLint setup.\n- All rules support configuration options. See individual rule docs for details.\n","readmeFilename":"README.md"}