hook-o-gnese 0.0.1 → 0.0.6
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/LICENSE +8 -2
- package/README.md +43 -23
- package/dist/THIRD_PARTY_NOTICES.md +39 -0
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/registry-iRG6wil9.mjs.map +1 -1
- package/package.json +19 -5
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Michal Rehout
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -12,4 +12,10 @@ furnished to do so, subject to the following conditions:
|
|
|
12
12
|
The above copyright notice and this permission notice shall be included in all
|
|
13
13
|
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# hook-o-gnese
|
|
2
2
|
|
|
3
|
-
> Score React component complexity from hook usage. Catch fat effects, scattered
|
|
3
|
+
> Score React component complexity from hook usage. Catch fat effects, scattered
|
|
4
|
+
> state, and coupled hooks before they ship.
|
|
4
5
|
|
|
5
6
|
[](https://www.npmjs.com/package/hook-o-gnese)
|
|
6
|
-
[](https://jsr.io/hook-o-gnese)
|
|
7
|
+
[](https://jsr.io/@mrht/hook-o-gnese)
|
|
7
8
|
[](https://github.com/rehoutm/spaghetti-hook-o-gnese/actions/workflows/ci.yml)
|
|
8
9
|
[](LICENSE)
|
|
9
10
|
|
|
10
|
-
Most lint rules check syntax. **hook-o-gnese checks complexity.** It scores how
|
|
11
|
+
Most lint rules check syntax. **hook-o-gnese checks complexity.** It scores how
|
|
12
|
+
dense your React hooks are — useEffect blocks bloated with branches and
|
|
13
|
+
`setState` calls, components with too many `useState`s that should be a
|
|
14
|
+
`useReducer`, effects that read and write the same state (loop bait), and custom
|
|
15
|
+
hooks stacked too deep.
|
|
11
16
|
|
|
12
17
|
```bash
|
|
13
18
|
npx hook-o-gnese ./src
|
|
@@ -26,26 +31,33 @@ src/screens/Settings.tsx
|
|
|
26
31
|
|
|
27
32
|
## Why
|
|
28
33
|
|
|
29
|
-
You've seen the file. 800 lines of component, fifteen `useState` calls, a
|
|
34
|
+
You've seen the file. 800 lines of component, fifteen `useState` calls, a
|
|
35
|
+
`useEffect` whose dependency array reads like a phone book, and a comment that
|
|
36
|
+
says `// TODO: refactor`. By the time anyone notices, it's already in production
|
|
37
|
+
and nobody wants to touch it.
|
|
30
38
|
|
|
31
|
-
`hook-o-gnese` is your early warning system. It measures the smells objectively,
|
|
39
|
+
`hook-o-gnese` is your early warning system. It measures the smells objectively,
|
|
40
|
+
surfaces them in CI, and gives you concrete numbers to argue with in code
|
|
41
|
+
review.
|
|
32
42
|
|
|
33
43
|
## What it catches
|
|
34
44
|
|
|
35
|
-
| Rule
|
|
36
|
-
|
|
37
|
-
| `no-fat-effects`
|
|
38
|
-
| `state-scatter`
|
|
39
|
-
| `hook-coupling`
|
|
40
|
-
| `custom-hook-depth` | Custom hooks calling custom hooks calling custom hooks (type-aware)
|
|
45
|
+
| Rule | Smell | Default |
|
|
46
|
+
| ------------------- | ---------------------------------------------------------------------- | ------------------ |
|
|
47
|
+
| `no-fat-effects` | useEffect blocks dense with branches, setState calls, missing cleanup | warn at score ≥ 10 |
|
|
48
|
+
| `state-scatter` | Components with too many `useState` calls (probably want `useReducer`) | warn at score ≥ 5 |
|
|
49
|
+
| `hook-coupling` | useEffect that reads state it also writes (re-render loop bait) | error |
|
|
50
|
+
| `custom-hook-depth` | Custom hooks calling custom hooks calling custom hooks (type-aware) | warn at depth ≥ 3 |
|
|
41
51
|
|
|
42
|
-
Full scoring formulas in [docs/thresholds.md](docs/thresholds.md). Per-rule
|
|
52
|
+
Full scoring formulas in [docs/thresholds.md](docs/thresholds.md). Per-rule
|
|
53
|
+
reference in [docs/rule-reference.md](docs/rule-reference.md).
|
|
43
54
|
|
|
44
55
|
## Two ways to run
|
|
45
56
|
|
|
46
57
|
### 1. Standalone CLI — recommended for most
|
|
47
58
|
|
|
48
|
-
No linter setup required. Works in any project. Outputs stylish, JSON, SARIF
|
|
59
|
+
No linter setup required. Works in any project. Outputs stylish, JSON, SARIF
|
|
60
|
+
(for GitHub code scanning), or GitHub Actions annotations.
|
|
49
61
|
|
|
50
62
|
```bash
|
|
51
63
|
npx hook-o-gnese ./src
|
|
@@ -89,7 +101,8 @@ npm install -D hook-o-gnese oxlint
|
|
|
89
101
|
}
|
|
90
102
|
```
|
|
91
103
|
|
|
92
|
-
Or import the recommended preset, which bundles tsgolint type-aware rules
|
|
104
|
+
Or import the recommended preset, which bundles tsgolint type-aware rules
|
|
105
|
+
(`no-floating-promises`, `no-misused-promises`):
|
|
93
106
|
|
|
94
107
|
```ts
|
|
95
108
|
import { recommended } from "hook-o-gnese";
|
|
@@ -143,17 +156,19 @@ const diagnostics = await lintFile("Component.tsx", source, {
|
|
|
143
156
|
|
|
144
157
|
Sequential per-file scan, but each file is cheap:
|
|
145
158
|
|
|
146
|
-
| Path
|
|
147
|
-
|
|
148
|
-
| Node CLI (`npx`)
|
|
149
|
-
| Standalone binary (`deno compile`) | ~30ms
|
|
150
|
-
| Type-aware rule (first run)
|
|
159
|
+
| Path | Cold start | Per-file warm |
|
|
160
|
+
| ---------------------------------- | ---------- | --------------- |
|
|
161
|
+
| Node CLI (`npx`) | ~80ms | ~3–5ms |
|
|
162
|
+
| Standalone binary (`deno compile`) | ~30ms | ~3–5ms |
|
|
163
|
+
| Type-aware rule (first run) | +50–150ms | TS Program load |
|
|
151
164
|
|
|
152
|
-
Linear scan of ~200 files/sec on a single core. Rouvy companion app: 470 files
|
|
165
|
+
Linear scan of ~200 files/sec on a single core. Rouvy companion app: 470 files
|
|
166
|
+
in 415ms.
|
|
153
167
|
|
|
154
168
|
## Standalone binary
|
|
155
169
|
|
|
156
|
-
The CLI also ships as a single static binary built with `deno compile` — no
|
|
170
|
+
The CLI also ships as a single static binary built with `deno compile` — no
|
|
171
|
+
Node, no Deno, no install required:
|
|
157
172
|
|
|
158
173
|
```bash
|
|
159
174
|
git clone https://github.com/rehoutm/spaghetti-hook-o-gnese
|
|
@@ -164,8 +179,13 @@ deno task build:bin
|
|
|
164
179
|
|
|
165
180
|
## Honest limitations
|
|
166
181
|
|
|
167
|
-
- **`custom-hook-depth` uses the TypeScript Compiler API**, not tsgolint's Go
|
|
168
|
-
|
|
182
|
+
- **`custom-hook-depth` uses the TypeScript Compiler API**, not tsgolint's Go
|
|
183
|
+
backend. Oxlint's JS plugin API doesn't expose tsgolint type info to custom
|
|
184
|
+
rules, so we lazily build a `ts.Program` for that one rule. ~50–150ms
|
|
185
|
+
first-run cost, then cached.
|
|
186
|
+
- **Sequential file scan.** Worker-thread parallelism is on the v1.5 list.
|
|
187
|
+
Current per-file cost (~3–5ms) means linear scanning is fine through
|
|
188
|
+
~thousands of files.
|
|
169
189
|
- **No daemon mode yet.** Each invocation is a fresh process. Also v1.5.
|
|
170
190
|
|
|
171
191
|
## Compatibility
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This file is auto-generated at build time by `rollup-plugin-license`.
|
|
4
|
+
It lists third-party software whose source code is bundled into the
|
|
5
|
+
distributed `hook-o-gnese` package (i.e. inlined into `dist/`).
|
|
6
|
+
Externalized dependencies (resolved via the consumer's package manager) are
|
|
7
|
+
not listed here — their licenses are delivered alongside their packages.
|
|
8
|
+
|
|
9
|
+
## @oxlint/plugins
|
|
10
|
+
|
|
11
|
+
- Version: 1.63.0
|
|
12
|
+
- License: MIT
|
|
13
|
+
- Homepage: https://oxc.rs/docs/guide/usage/linter/js-plugins
|
|
14
|
+
- Repository: git+https://github.com/oxc-project/oxc.git
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
MIT License
|
|
18
|
+
|
|
19
|
+
Copyright (c) 2024-present VoidZero Inc. & Contributors
|
|
20
|
+
Copyright (c) 2023 Boshen
|
|
21
|
+
|
|
22
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
23
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
24
|
+
in the Software without restriction, including without limitation the rights
|
|
25
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
26
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
27
|
+
furnished to do so, subject to the following conditions:
|
|
28
|
+
|
|
29
|
+
The above copyright notice and this permission notice shall be included in all
|
|
30
|
+
copies or substantial portions of the Software.
|
|
31
|
+
|
|
32
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
34
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
35
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
36
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
37
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
38
|
+
SOFTWARE.
|
|
39
|
+
```
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["jsonFmt"],"sources":["../src/config.ts","../src/formatters/stylish.ts","../src/formatters/json.ts","../src/formatters/sarif.ts","../src/formatters/github.ts","../src/cli-core.ts","../src/cli.node.ts"],"sourcesContent":["import type { EngineConfig, Severity } from \"./engine.ts\";\n\nconst DEFAULT_RULES: Record<string, { severity: Severity; options?: unknown }> = {\n \"hook-o-gnese/no-fat-effects\": { severity: \"warn\" },\n \"hook-o-gnese/state-scatter\": { severity: \"warn\" },\n \"hook-o-gnese/hook-coupling\": { severity: \"error\" },\n \"hook-o-gnese/custom-hook-depth\": { severity: \"warn\", options: { maxDepth: 3 } },\n};\n\nexport const DEFAULT_IGNORE = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/.cache/**\",\n];\n\ninterface FileConfig {\n rules?: Record<string, Severity | [Severity, unknown]>;\n ignore?: string[];\n typeAware?: boolean;\n}\n\nexport type ReadTextFile = (path: string) => Promise<string>;\n\nexport async function loadConfig(\n cwd: string,\n configPath: string | undefined,\n readTextFile: ReadTextFile,\n): Promise<{ engine: EngineConfig; ignore: string[] }> {\n const candidates = configPath\n ? [configPath]\n : [`${cwd.replace(/\\/$/, \"\")}/.hookogneserc.json`];\n\n let fileCfg: FileConfig = {};\n for (const c of candidates) {\n try {\n const text = await readTextFile(c);\n fileCfg = JSON.parse(text);\n break;\n } catch {\n // not found — fine, use defaults\n }\n }\n\n const rules: EngineConfig[\"rules\"] = { ...DEFAULT_RULES };\n if (fileCfg.rules) {\n for (const [id, spec] of Object.entries(fileCfg.rules)) {\n if (Array.isArray(spec)) {\n rules[id] = { severity: spec[0], options: spec[1] };\n } else {\n rules[id] = { severity: spec };\n }\n }\n }\n\n return {\n engine: {\n rules,\n cwd,\n typeAware: fileCfg.typeAware ?? false,\n },\n ignore: fileCfg.ignore ?? DEFAULT_IGNORE,\n };\n}\n\nexport function applyCliRuleOverrides(\n cfg: EngineConfig,\n overrides: Array<{ id: string; severity: Severity }>,\n): EngineConfig {\n const rules = { ...cfg.rules };\n for (const o of overrides) {\n rules[o.id] = { ...(rules[o.id] ?? { severity: \"off\" }), severity: o.severity };\n }\n return { ...cfg, rules };\n}\n","import type { Formatter } from \"./types.ts\";\n\nexport const stylish: Formatter = ({ diagnostics, filesScanned, durationMs }) => {\n if (diagnostics.length === 0) {\n return `✓ no problems found (${filesScanned} files, ${durationMs}ms)\\n`;\n }\n const byFile = new Map<string, typeof diagnostics>();\n for (const d of diagnostics) {\n if (!byFile.has(d.file)) byFile.set(d.file, []);\n byFile.get(d.file)!.push(d);\n }\n const lines: string[] = [];\n for (const [file, ds] of byFile) {\n lines.push(`\\n${file}`);\n for (const d of ds) {\n const sev = d.severity === \"error\" ? \"error\" : \"warn \";\n const loc = `${d.line}:${d.column}`.padEnd(7);\n lines.push(` ${loc} ${sev} ${d.message} ${d.rule}`);\n }\n }\n const errors = diagnostics.filter((d) => d.severity === \"error\").length;\n const warnings = diagnostics.filter((d) => d.severity === \"warn\").length;\n lines.push(\n `\\n${diagnostics.length} problems (${errors} error${errors === 1 ? \"\" : \"s\"}, ${warnings} warning${warnings === 1 ? \"\" : \"s\"}) in ${filesScanned} files, ${durationMs}ms`,\n );\n return lines.join(\"\\n\") + \"\\n\";\n};\n","import type { Formatter } from \"./types.ts\";\n\nexport const json: Formatter = (ctx) => JSON.stringify(ctx, null, 2);\n","import type { Formatter } from \"./types.ts\";\n\nexport const sarif: Formatter = ({ diagnostics }) => {\n const ruleIds = [...new Set(diagnostics.map((d) => d.rule))];\n return JSON.stringify(\n {\n version: \"2.1.0\",\n $schema:\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json\",\n runs: [{\n tool: {\n driver: {\n name: \"hook-o-gnese\",\n informationUri: \"https://github.com/rehoutm/spaghetti-hook-o-gnese\",\n rules: ruleIds.map((id) => ({ id })),\n },\n },\n results: diagnostics.map((d) => ({\n ruleId: d.rule,\n level: d.severity === \"error\" ? \"error\" : \"warning\",\n message: { text: d.message },\n locations: [{\n physicalLocation: {\n artifactLocation: { uri: d.file },\n region: {\n startLine: d.line,\n startColumn: d.column,\n endLine: d.endLine,\n endColumn: d.endColumn,\n },\n },\n }],\n })),\n }],\n },\n null,\n 2,\n );\n};\n","import type { Formatter } from \"./types.ts\";\n\nexport const github: Formatter = ({ diagnostics }) =>\n diagnostics.map((d) => {\n const cmd = d.severity === \"error\" ? \"::error\" : \"::warning\";\n const safe = d.message.replace(/\\r?\\n/g, \" \").replace(/::/g, \":\");\n return `${cmd} file=${d.file},line=${d.line},col=${d.column},title=${d.rule}::${safe}`;\n }).join(\"\\n\") + \"\\n\";\n","import { globby } from \"globby\";\nimport { lintFiles } from \"./engine.ts\";\nimport type { Severity } from \"./engine.ts\";\nimport { applyCliRuleOverrides, DEFAULT_IGNORE, loadConfig } from \"./config.ts\";\nimport { stylish } from \"./formatters/stylish.ts\";\nimport { json as jsonFmt } from \"./formatters/json.ts\";\nimport { sarif } from \"./formatters/sarif.ts\";\nimport { github } from \"./formatters/github.ts\";\nimport type { Formatter } from \"./formatters/types.ts\";\n\nconst FORMATTERS: Record<string, Formatter> = {\n stylish,\n json: jsonFmt,\n sarif,\n github,\n};\n\nexport const HELP = `\nhook-o-gnese — score React hook complexity\n\nUsage:\n hook-o-gnese [options] <paths...>\n\nOptions:\n --format=<fmt> stylish (default) | json | sarif | github\n --config=<path> path to .hookogneserc.json\n --type-aware enable custom-hook-depth (slower, uses TS Compiler API)\n --rule=<id>=<sev> override rule severity (off|warn|error). Repeatable.\n --help, -h show this message\n\nExamples:\n hook-o-gnese ./src\n hook-o-gnese ./src --format=sarif > report.sarif\n hook-o-gnese ./src --type-aware --rule=hook-o-gnese/state-scatter=error\n`.trim();\n\nexport interface CliOptions {\n paths: string[];\n format: string;\n config?: string;\n typeAware: boolean;\n ruleOverrides: Array<{ id: string; severity: Severity }>;\n cwd: string;\n}\n\nexport interface RuntimeIO {\n readTextFile(path: string): Promise<string>;\n writeStdout(s: string): void;\n writeStderr(s: string): void;\n}\n\nexport async function runCli(opts: CliOptions, io: RuntimeIO): Promise<number> {\n if (opts.paths.length === 0) {\n io.writeStderr(\"Error: no paths provided. Use --help for usage.\\n\");\n return 2;\n }\n\n const formatter = FORMATTERS[opts.format];\n if (!formatter) {\n io.writeStderr(`Error: unknown format '${opts.format}'\\n`);\n return 2;\n }\n\n const { engine, ignore } = await loadConfig(opts.cwd, opts.config, io.readTextFile);\n if (opts.typeAware) engine.typeAware = true;\n const finalEngine = applyCliRuleOverrides(engine, opts.ruleOverrides);\n\n const files = await globby(opts.paths, {\n ignore: [...DEFAULT_IGNORE, ...ignore],\n expandDirectories: { extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"] },\n absolute: false,\n });\n\n if (files.length === 0) {\n io.writeStderr(\"Error: no matching files found\\n\");\n return 2;\n }\n\n const start = performance.now();\n const diagnostics = await lintFiles(files, finalEngine, io.readTextFile);\n const durationMs = Math.round(performance.now() - start);\n\n io.writeStdout(formatter({\n diagnostics,\n filesScanned: files.length,\n durationMs,\n }));\n\n if (diagnostics.some((d) => d.severity === \"error\")) return 1;\n return 0;\n}\n","#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { readFile } from \"node:fs/promises\";\nimport { HELP, runCli } from \"./cli-core.ts\";\nimport type { Severity } from \"./engine.ts\";\n\nconst { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n options: {\n help: { type: \"boolean\", short: \"h\" },\n \"type-aware\": { type: \"boolean\" },\n format: { type: \"string\", default: \"stylish\" },\n config: { type: \"string\" },\n rule: { type: \"string\", multiple: true },\n },\n allowPositionals: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst overrides = (values.rule ?? []).map((spec) => {\n const [id, sev] = spec.split(\"=\");\n return { id, severity: sev as Severity };\n});\n\nconst code = await runCli(\n {\n paths: positionals,\n format: values.format as string,\n config: values.config,\n typeAware: !!values[\"type-aware\"],\n ruleOverrides: overrides,\n cwd: process.cwd(),\n },\n {\n readTextFile: (p) => readFile(p, \"utf-8\"),\n writeStdout: (s) => {\n process.stdout.write(s);\n },\n writeStderr: (s) => {\n process.stderr.write(s);\n },\n },\n);\nprocess.exit(code);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["jsonFmt"],"sources":["../src/config.ts","../src/formatters/stylish.ts","../src/formatters/json.ts","../src/formatters/sarif.ts","../src/formatters/github.ts","../src/cli-core.ts","../src/cli.node.ts"],"sourcesContent":["import type { EngineConfig, Severity } from \"./engine.ts\";\n\nconst DEFAULT_RULES: Record<string, { severity: Severity; options?: unknown }> =\n {\n \"hook-o-gnese/no-fat-effects\": { severity: \"warn\" },\n \"hook-o-gnese/state-scatter\": { severity: \"warn\" },\n \"hook-o-gnese/hook-coupling\": { severity: \"error\" },\n \"hook-o-gnese/custom-hook-depth\": {\n severity: \"warn\",\n options: { maxDepth: 3 },\n },\n };\n\nexport const DEFAULT_IGNORE = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n \"**/.next/**\",\n \"**/.cache/**\",\n];\n\ninterface FileConfig {\n rules?: Record<string, Severity | [Severity, unknown]>;\n ignore?: string[];\n typeAware?: boolean;\n}\n\nexport type ReadTextFile = (path: string) => Promise<string>;\n\nexport async function loadConfig(\n cwd: string,\n configPath: string | undefined,\n readTextFile: ReadTextFile,\n): Promise<{ engine: EngineConfig; ignore: string[] }> {\n const candidates = configPath\n ? [configPath]\n : [`${cwd.replace(/\\/$/, \"\")}/.hookogneserc.json`];\n\n let fileCfg: FileConfig = {};\n for (const c of candidates) {\n try {\n const text = await readTextFile(c);\n fileCfg = JSON.parse(text);\n break;\n } catch {\n // not found — fine, use defaults\n }\n }\n\n const rules: EngineConfig[\"rules\"] = { ...DEFAULT_RULES };\n if (fileCfg.rules) {\n for (const [id, spec] of Object.entries(fileCfg.rules)) {\n if (Array.isArray(spec)) {\n rules[id] = { severity: spec[0], options: spec[1] };\n } else {\n rules[id] = { severity: spec };\n }\n }\n }\n\n return {\n engine: {\n rules,\n cwd,\n typeAware: fileCfg.typeAware ?? false,\n },\n ignore: fileCfg.ignore ?? DEFAULT_IGNORE,\n };\n}\n\nexport function applyCliRuleOverrides(\n cfg: EngineConfig,\n overrides: Array<{ id: string; severity: Severity }>,\n): EngineConfig {\n const rules = { ...cfg.rules };\n for (const o of overrides) {\n rules[o.id] = {\n ...(rules[o.id] ?? { severity: \"off\" }),\n severity: o.severity,\n };\n }\n return { ...cfg, rules };\n}\n","import type { Formatter } from \"./types.ts\";\n\nexport const stylish: Formatter = (\n { diagnostics, filesScanned, durationMs },\n) => {\n if (diagnostics.length === 0) {\n return `✓ no problems found (${filesScanned} files, ${durationMs}ms)\\n`;\n }\n const byFile = new Map<string, typeof diagnostics>();\n for (const d of diagnostics) {\n if (!byFile.has(d.file)) byFile.set(d.file, []);\n byFile.get(d.file)!.push(d);\n }\n const lines: string[] = [];\n for (const [file, ds] of byFile) {\n lines.push(`\\n${file}`);\n for (const d of ds) {\n const sev = d.severity === \"error\" ? \"error\" : \"warn \";\n const loc = `${d.line}:${d.column}`.padEnd(7);\n lines.push(` ${loc} ${sev} ${d.message} ${d.rule}`);\n }\n }\n const errors = diagnostics.filter((d) => d.severity === \"error\").length;\n const warnings = diagnostics.filter((d) => d.severity === \"warn\").length;\n lines.push(\n `\\n${diagnostics.length} problems (${errors} error${\n errors === 1 ? \"\" : \"s\"\n }, ${warnings} warning${\n warnings === 1 ? \"\" : \"s\"\n }) in ${filesScanned} files, ${durationMs}ms`,\n );\n return lines.join(\"\\n\") + \"\\n\";\n};\n","import type { Formatter } from \"./types.ts\";\n\nexport const json: Formatter = (ctx) => JSON.stringify(ctx, null, 2);\n","import type { Formatter } from \"./types.ts\";\n\nexport const sarif: Formatter = ({ diagnostics }) => {\n const ruleIds = [...new Set(diagnostics.map((d) => d.rule))];\n return JSON.stringify(\n {\n version: \"2.1.0\",\n $schema:\n \"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json\",\n runs: [{\n tool: {\n driver: {\n name: \"hook-o-gnese\",\n informationUri: \"https://github.com/rehoutm/spaghetti-hook-o-gnese\",\n rules: ruleIds.map((id) => ({ id })),\n },\n },\n results: diagnostics.map((d) => ({\n ruleId: d.rule,\n level: d.severity === \"error\" ? \"error\" : \"warning\",\n message: { text: d.message },\n locations: [{\n physicalLocation: {\n artifactLocation: { uri: d.file },\n region: {\n startLine: d.line,\n startColumn: d.column,\n endLine: d.endLine,\n endColumn: d.endColumn,\n },\n },\n }],\n })),\n }],\n },\n null,\n 2,\n );\n};\n","import type { Formatter } from \"./types.ts\";\n\nexport const github: Formatter = ({ diagnostics }) =>\n diagnostics.map((d) => {\n const cmd = d.severity === \"error\" ? \"::error\" : \"::warning\";\n const safe = d.message.replace(/\\r?\\n/g, \" \").replace(/::/g, \":\");\n return `${cmd} file=${d.file},line=${d.line},col=${d.column},title=${d.rule}::${safe}`;\n }).join(\"\\n\") + \"\\n\";\n","import { globby } from \"globby\";\nimport { lintFiles } from \"./engine.ts\";\nimport type { Severity } from \"./engine.ts\";\nimport { applyCliRuleOverrides, DEFAULT_IGNORE, loadConfig } from \"./config.ts\";\nimport { stylish } from \"./formatters/stylish.ts\";\nimport { json as jsonFmt } from \"./formatters/json.ts\";\nimport { sarif } from \"./formatters/sarif.ts\";\nimport { github } from \"./formatters/github.ts\";\nimport type { Formatter } from \"./formatters/types.ts\";\n\nconst FORMATTERS: Record<string, Formatter> = {\n stylish,\n json: jsonFmt,\n sarif,\n github,\n};\n\nexport const HELP = `\nhook-o-gnese — score React hook complexity\n\nUsage:\n hook-o-gnese [options] <paths...>\n\nOptions:\n --format=<fmt> stylish (default) | json | sarif | github\n --config=<path> path to .hookogneserc.json\n --type-aware enable custom-hook-depth (slower, uses TS Compiler API)\n --rule=<id>=<sev> override rule severity (off|warn|error). Repeatable.\n --help, -h show this message\n\nExamples:\n hook-o-gnese ./src\n hook-o-gnese ./src --format=sarif > report.sarif\n hook-o-gnese ./src --type-aware --rule=hook-o-gnese/state-scatter=error\n`.trim();\n\nexport interface CliOptions {\n paths: string[];\n format: string;\n config?: string;\n typeAware: boolean;\n ruleOverrides: Array<{ id: string; severity: Severity }>;\n cwd: string;\n}\n\nexport interface RuntimeIO {\n readTextFile(path: string): Promise<string>;\n writeStdout(s: string): void;\n writeStderr(s: string): void;\n}\n\nexport async function runCli(opts: CliOptions, io: RuntimeIO): Promise<number> {\n if (opts.paths.length === 0) {\n io.writeStderr(\"Error: no paths provided. Use --help for usage.\\n\");\n return 2;\n }\n\n const formatter = FORMATTERS[opts.format];\n if (!formatter) {\n io.writeStderr(`Error: unknown format '${opts.format}'\\n`);\n return 2;\n }\n\n const { engine, ignore } = await loadConfig(\n opts.cwd,\n opts.config,\n io.readTextFile,\n );\n if (opts.typeAware) engine.typeAware = true;\n const finalEngine = applyCliRuleOverrides(engine, opts.ruleOverrides);\n\n const files = await globby(opts.paths, {\n ignore: [...DEFAULT_IGNORE, ...ignore],\n expandDirectories: { extensions: [\"ts\", \"tsx\", \"js\", \"jsx\"] },\n absolute: false,\n });\n\n if (files.length === 0) {\n io.writeStderr(\"Error: no matching files found\\n\");\n return 2;\n }\n\n const start = performance.now();\n const diagnostics = await lintFiles(files, finalEngine, io.readTextFile);\n const durationMs = Math.round(performance.now() - start);\n\n io.writeStdout(formatter({\n diagnostics,\n filesScanned: files.length,\n durationMs,\n }));\n\n if (diagnostics.some((d) => d.severity === \"error\")) return 1;\n return 0;\n}\n","#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { readFile } from \"node:fs/promises\";\nimport { HELP, runCli } from \"./cli-core.ts\";\nimport type { Severity } from \"./engine.ts\";\n\nconst { values, positionals } = parseArgs({\n args: process.argv.slice(2),\n options: {\n help: { type: \"boolean\", short: \"h\" },\n \"type-aware\": { type: \"boolean\" },\n format: { type: \"string\", default: \"stylish\" },\n config: { type: \"string\" },\n rule: { type: \"string\", multiple: true },\n },\n allowPositionals: true,\n});\n\nif (values.help) {\n console.log(HELP);\n process.exit(0);\n}\n\nconst overrides = (values.rule ?? []).map((spec) => {\n const [id, sev] = spec.split(\"=\");\n return { id, severity: sev as Severity };\n});\n\nconst code = await runCli(\n {\n paths: positionals,\n format: values.format as string,\n config: values.config,\n typeAware: !!values[\"type-aware\"],\n ruleOverrides: overrides,\n cwd: process.cwd(),\n },\n {\n readTextFile: (p) => readFile(p, \"utf-8\"),\n writeStdout: (s) => {\n process.stdout.write(s);\n },\n writeStderr: (s) => {\n process.stderr.write(s);\n },\n },\n);\nprocess.exit(code);\n"],"mappings":"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"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../node_modules/.deno/@oxlint+plugins@1.63.0/node_modules/@oxlint/plugins/index.js","../src/index.ts"],"sourcesContent":["//#region src-js/package/define.ts\n/**\n* Define a plugin.\n*\n* No-op function, just to provide type safety. Input is passed through unchanged.\n*\n* @param plugin - Plugin to define\n* @returns Same plugin as passed in\n*/\nfunction definePlugin(plugin) {\n\treturn plugin;\n}\n/**\n* Define a rule.\n*\n* No-op function, just to provide type safety. Input is passed through unchanged.\n*\n* @param rule - Rule to define\n* @returns Same rule as passed in\n*/\nfunction defineRule(rule) {\n\treturn rule;\n}\n//#endregion\n//#region src-js/package/compat.ts\nconst EMPTY_VISITOR = {};\n/**\n* Convert a plugin which used Oxlint's `createOnce` API to also work with ESLint.\n*\n* If any of the plugin's rules use the Oxlint alternative `createOnce` API,\n* add ESLint-compatible `create` methods to those rules, which delegate to `createOnce`.\n* This makes the plugin compatible with ESLint.\n*\n* The `plugin` object passed in is mutated in-place.\n*\n* @param plugin - Plugin to convert\n* @returns Plugin with all rules having `create` method\n* @throws {Error} If `plugin` is not an object, or `plugin.rules` is not an object\n*/\nfunction eslintCompatPlugin(plugin) {\n\tif (typeof plugin != \"object\" || !plugin) throw Error(\"Plugin must be an object\");\n\tlet { rules } = plugin;\n\tif (typeof rules != \"object\" || !rules) throw Error(\"Plugin must have an object as `rules` property\");\n\tlet afterHooksState = new AfterHooksState();\n\tfor (let ruleName in rules) Object.hasOwn(rules, ruleName) && convertRule(rules[ruleName], afterHooksState);\n\treturn plugin;\n}\n/**\n* Class containing state for tracking if any `after` hooks for a plugin's rules need to be called.\n*\n* # Aims\n*\n* Aims are:\n* 1. `after` hook of each rule runs after all other AST visit functions, and CFG event handlers.\n* 2. `after` hooks for *all* a plugin's rules run after *all* that plugin's rules have completed visiting AST.\n* 3. `after` hooks for *all* a plugin's rules run before *any* of plugin's rules begin linting another file.\n* 4. In the case of an error during AST traversal, `after` hooks are always still run.\n*\n* The above exactly matches the behavior when running a `createOnce` rule in Oxlint.\n*\n* # Why this is important\n*\n* All the complication comes from ensuring `after` hooks run even after an error during AST traversal.\n*\n* In ESLint CLI, an error will crash the process, so it doesn't particularly matter if `after` hooks run or not,\n* but language servers will typically swallow errors, and keep the process running.\n*\n* Rules using `before` and `after` hooks will often rely on both hooks running in a predictable order,\n* to maintain some internal state. For example, they may use `before` and `after` hooks to maintain a per-file\n* cache of data which is shared between rules. The cache use case is why rule (2) above is important.\n*\n* Below is an example of using `before` and `after` hooks to maintain a per-file cache, shared between rules.\n* It relies on all `before` hooks running before any rule starts visiting the AST,\n* and all `after` hooks running after all rules have finished visiting the AST.\n*\n* ```ts\n* let cache: Data | null = null;\n*\n* let numRunningRules = 0;\n*\n* const setupCache = (context) => {\n* if (cache === null) cache = new Data(context);\n* numRunningRules++;\n* };\n*\n* const teardownCache = () => {\n* numRunningRules--;\n* if (numRunningRules === 0) cache = null;\n* };\n*\n* const rule1 = {\n* createOnce(context) {\n* return {\n* before() {\n* setupCache(context);\n* },\n* Identifier(node) {\n* // Use `cache`\n* },\n* after: teardownCache,\n* };\n* },\n* };\n*\n* const rule2 = {\n* // Same as above\n* };\n*\n* const rule3 = {\n* // Same as above\n* };\n* ```\n*\n* If `after` hooks did not always run, the next lint run could get stale state, and malfunction.\n* If `after` hooks ran in the wrong order (e.g. after some `before` hooks for next file),\n* `numRunningRules` would never get to 0, and cache would never be cleared.\n*\n* Note that because all rules run together in a single AST traversal, if a rule from plugin X throws an error,\n* it can disrupt rules from plugin Y. This would make it hard to debug.\n*\n* # Mechanism\n*\n* ## Initialization\n*\n* Rules with an `after` hook register themselves by:\n*\n* 1. Calling `registerResetFunction` to register a function to run `after` hook and clean up internal state.\n* This call adds the reset fn to `resetFunctions`, and adds `AFTER_HOOK_INACTIVE` to `pendingStates`.\n* 2. Adding an `onCodePathEnd` CFG event handler to the visitor which calls `ruleFinished` at end of AST traversal.\n*\n* ## Per-file setup\n*\n* Before linting a file, `create` will call `setupAfterHook` which is created by `createContextAndVisitor`.\n* This registers that the `after` hook for the rule needs to run, by setting `pendingStates[ruleIndex]`\n* to `AFTER_HOOK_PENDING`, and incrementing `pendingCount`.\n*\n* If a cleanup microtask has not been scheduled yet, one is scheduled now (see reason below).\n*\n* ## Normal operation\n*\n* AST traversal for each rule ends with `ruleFinished` hook being called from `onCodePathEnd` CFG event handler.\n* It increments `lintFinishedCount`. If `lintFinishedCount` equals `pendingCount`, all rules have finished linting\n* the file, and `reset` is called, which calls all the pending `after` hooks.\n*\n* ## Error handling\n*\n* If an error is thrown during AST traversal, we ensure that `after` hooks are still run by 2 mechanisms:\n*\n* ### 1. Next microtick\n*\n* Before any rules began linting files, a microtask was scheduled, which runs on next micro-tick.\n* All language servers we're aware of run each lint task in a separate tick, so this microtask will run in next tick\n* after a linting run, before the next lint task starts.\n*\n* If the linting run completed successfully, the microtask does nothing.\n*\n* But if an error was thrown during AST traversal, this will be visible from the state of `pendingCount`.\n* The microtask will run any `after` hooks which need to be run, and reset state to reflect that there are\n* no more pending `after` hooks.\n*\n* ### 2. Fallback: Next lint run\n*\n* Before linting any file, the state of `pendingCount` is checked.\n* If any `after` hooks are still pending, they are run immediately.\n* They're run before the `context` objects in `createOnce` closures are updated to the next file,\n* so they run with access to the old `context` object from the last file.\n*\n* This fallback should not be required, but it's included as \"belt and braces\", to handle if any language server\n* or other environment running ESLint programmatically, does not pause a tick between linting runs.\n*/\nvar AfterHooksState = class {\n\tresetFunctions = [];\n\tpendingStates = [];\n\tpendingCount = 0;\n\tlintFinishedCount = 0;\n\tresetIsScheduled = !1;\n\tsourceCode = null;\n\tresetMicrotask = this.resetMicrotaskImpl.bind(this);\n\t/**\n\t* Register a function to run `after` hook for a rule, and reset state.\n\t* @param reset - Function to run `after` hook and reset state\n\t* @returns Index of rule\n\t*/\n\tregisterResetFunction(reset) {\n\t\tlet { pendingStates } = this, index = pendingStates.length;\n\t\treturn pendingStates.push(0), this.resetFunctions.push(reset), index;\n\t}\n\t/**\n\t* Register that a rule with `after` hook has completed linting a file.\n\t* Called by `onCodePathEnd` CFG event handler which is added to visitor for rules with `after` hooks.\n\t*\n\t* If all rules with an `after` hook which needs to be run have completed linting the file, run all `after` hooks.\n\t*/\n\truleFinished() {\n\t\tthis.lintFinishedCount++, this.lintFinishedCount === this.pendingCount && this.reset(!1);\n\t}\n\t/**\n\t* Call all reset functions where corresponding entry in `pendingStates` is `AFTER_HOOK_PENDING`.\n\t* Should only be called when some `after` hooks are pending.\n\t*\n\t* @param ignoreErrors - `true` to catch and silently ignore any errors which occur in `after` hooks.\n\t* `false` to throw them,\n\t* @throws {unknown} If `ignoreErrors` is `false` and an error occurs in any `after` hooks.\n\t*/\n\treset(ignoreErrors) {\n\t\tthis.pendingCount;\n\t\tlet { resetFunctions, pendingStates } = this, hooksLen = pendingStates.length, hasError = !1, error;\n\t\tfor (let i = 0; i < hooksLen; i++) if (pendingStates[i] !== 0) {\n\t\t\tpendingStates[i] = 0;\n\t\t\ttry {\n\t\t\t\tresetFunctions[i]();\n\t\t\t} catch (e) {\n\t\t\t\thasError === !1 && (hasError = !0, error = e);\n\t\t\t}\n\t\t}\n\t\tif (this.pendingCount = 0, this.lintFinishedCount = 0, this.sourceCode = null, hasError === !0 && ignoreErrors === !1) throw error;\n\t}\n\t/**\n\t* Schedule a microtask to run `reset` functions.\n\t*/\n\tscheduleReset() {\n\t\tqueueMicrotask(this.resetMicrotask), this.resetIsScheduled = !0;\n\t}\n\t/**\n\t* Function which is scheduled as the cleanup microtask.\n\t* `scheduleReset` uses `resetMicrotask` which is this method bound to `this`.\n\t*/\n\tresetMicrotaskImpl() {\n\t\tthis.resetIsScheduled = !1, this.pendingCount !== 0 && this.reset(!0);\n\t}\n};\n/**\n* Convert a rule.\n*\n* The `rule` object passed in is mutated in-place.\n*\n* @param rule - Rule to convert\n* @param afterHooksState - State of `after` hooks\n* @throws {Error} If `rule` is not an object\n*/\nfunction convertRule(rule, afterHooksState) {\n\tif (typeof rule != \"object\" || !rule) throw Error(\"Rule must be an object\");\n\tif (\"create\" in rule) return;\n\tlet context = null, visitor, beforeHook, setupAfterHook;\n\trule.create = (eslintContext) => {\n\t\tcontext === null && ({context, visitor, beforeHook, setupAfterHook} = createContextAndVisitor(rule, afterHooksState));\n\t\tlet eslintFileContext = Object.getPrototypeOf(eslintContext);\n\t\tif (setupAfterHook !== null) {\n\t\t\tlet { sourceCode } = eslintFileContext;\n\t\t\tafterHooksState.sourceCode !== sourceCode && (afterHooksState.sourceCode = sourceCode, afterHooksState.pendingCount !== 0 && afterHooksState.reset(!0));\n\t\t}\n\t\treturn Object.defineProperties(context, {\n\t\t\tid: { value: eslintContext.id },\n\t\t\toptions: { value: eslintContext.options },\n\t\t\treport: { value: eslintContext.report }\n\t\t}), Object.setPrototypeOf(context, eslintFileContext), beforeHook !== null && beforeHook() === !1 ? EMPTY_VISITOR : (setupAfterHook !== null && (setupAfterHook(eslintFileContext.sourceCode.ast), afterHooksState.resetIsScheduled === !1 && afterHooksState.scheduleReset()), visitor);\n\t};\n}\nconst FILE_CONTEXT = Object.freeze({\n\tget filename() {\n\t\tthrow Error(\"Cannot access `context.filename` in `createOnce`\");\n\t},\n\tgetFilename() {\n\t\tthrow Error(\"Cannot call `context.getFilename` in `createOnce`\");\n\t},\n\tget physicalFilename() {\n\t\tthrow Error(\"Cannot access `context.physicalFilename` in `createOnce`\");\n\t},\n\tgetPhysicalFilename() {\n\t\tthrow Error(\"Cannot call `context.getPhysicalFilename` in `createOnce`\");\n\t},\n\tget cwd() {\n\t\tthrow Error(\"Cannot access `context.cwd` in `createOnce`\");\n\t},\n\tgetCwd() {\n\t\tthrow Error(\"Cannot call `context.getCwd` in `createOnce`\");\n\t},\n\tget sourceCode() {\n\t\tthrow Error(\"Cannot access `context.sourceCode` in `createOnce`\");\n\t},\n\tgetSourceCode() {\n\t\tthrow Error(\"Cannot call `context.getSourceCode` in `createOnce`\");\n\t},\n\tget languageOptions() {\n\t\tthrow Error(\"Cannot access `context.languageOptions` in `createOnce`\");\n\t},\n\tget settings() {\n\t\tthrow Error(\"Cannot access `context.settings` in `createOnce`\");\n\t},\n\textend(extension) {\n\t\treturn Object.freeze(Object.assign(Object.create(this), extension));\n\t},\n\tget parserOptions() {\n\t\tthrow Error(\"Cannot access `context.parserOptions` in `createOnce`\");\n\t},\n\tget parserPath() {\n\t\tthrow Error(\"Cannot access `context.parserPath` in `createOnce`\");\n\t}\n});\n/**\n* Call `createOnce` method of rule, and return `Context`, `Visitor`, and `beforeHook` (if any).\n*\n* @param rule - Rule with `createOnce` method\n* @param afterHooksState - State of `after` hooks\n* @returns Object with `context`, `visitor`, and `beforeHook` properties,\n* and `setupAfterHook` function if visitor has an `after` hook\n*/\nfunction createContextAndVisitor(rule, afterHooksState) {\n\tlet { createOnce } = rule;\n\tif (createOnce == null) throw Error(\"Rules must define either a `create` or `createOnce` method\");\n\tif (typeof createOnce != \"function\") throw Error(\"Rule `createOnce` property must be a function\");\n\tlet context = Object.create(FILE_CONTEXT, {\n\t\tid: {\n\t\t\tvalue: null,\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t},\n\t\toptions: {\n\t\t\tvalue: null,\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t},\n\t\treport: {\n\t\t\tvalue() {\n\t\t\t\tthrow Error(\"Cannot report errors in `createOnce`\");\n\t\t\t},\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t}\n\t}), { before: beforeHook, after: afterHook, ...visitor } = createOnce.call(rule, context);\n\tif (beforeHook === void 0) beforeHook = null;\n\telse if (beforeHook !== null && typeof beforeHook != \"function\") throw Error(\"`before` property of visitor must be a function if defined\");\n\tlet setupAfterHook = null;\n\tif (afterHook != null) {\n\t\tif (typeof afterHook != \"function\") throw Error(\"`after` property of visitor must be a function if defined\");\n\t\tlet program = null, ruleIndex = afterHooksState.registerResetFunction(() => {\n\t\t\tprogram = null, afterHook();\n\t\t});\n\t\tsetupAfterHook = (ast) => {\n\t\t\tprogram = ast, afterHooksState.pendingStates[ruleIndex] = 1, afterHooksState.pendingCount++;\n\t\t};\n\t\tlet onCodePathEnd = visitor.onCodePathEnd;\n\t\tvisitor.onCodePathEnd = onCodePathEnd == null ? function(_codePath, node) {\n\t\t\tnode === program && afterHooksState.ruleFinished();\n\t\t} : function(codePath, node) {\n\t\t\tonCodePathEnd.call(this, codePath, node), node === program && afterHooksState.ruleFinished();\n\t\t};\n\t}\n\treturn {\n\t\tcontext,\n\t\tvisitor,\n\t\tbeforeHook,\n\t\tsetupAfterHook\n\t};\n}\n//#endregion\nexport { definePlugin, defineRule, eslintCompatPlugin };\n","import { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\nimport { ALL_RULES } from \"./rules/registry.ts\";\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"hook-o-gnese\" },\n rules: ALL_RULES,\n});\n\nexport const recommended = {\n jsPlugins: [\"./node_modules/hook-o-gnese/dist/index.mjs\"],\n options: { typeAware: true, typeCheck: true },\n rules: {\n \"hook-o-gnese/no-fat-effects\": \"warn\",\n \"hook-o-gnese/state-scatter\": \"warn\",\n \"hook-o-gnese/hook-coupling\": \"error\",\n \"hook-o-gnese/custom-hook-depth\": [\"warn\", { maxDepth: 3 }],\n \"typescript/no-floating-promises\": \"error\",\n \"typescript/no-misused-promises\": \"error\",\n },\n};\n\nexport default plugin;\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../node_modules/.deno/@oxlint+plugins@1.63.0/node_modules/@oxlint/plugins/index.js","../src/index.ts"],"sourcesContent":["//#region src-js/package/define.ts\n/**\n* Define a plugin.\n*\n* No-op function, just to provide type safety. Input is passed through unchanged.\n*\n* @param plugin - Plugin to define\n* @returns Same plugin as passed in\n*/\nfunction definePlugin(plugin) {\n\treturn plugin;\n}\n/**\n* Define a rule.\n*\n* No-op function, just to provide type safety. Input is passed through unchanged.\n*\n* @param rule - Rule to define\n* @returns Same rule as passed in\n*/\nfunction defineRule(rule) {\n\treturn rule;\n}\n//#endregion\n//#region src-js/package/compat.ts\nconst EMPTY_VISITOR = {};\n/**\n* Convert a plugin which used Oxlint's `createOnce` API to also work with ESLint.\n*\n* If any of the plugin's rules use the Oxlint alternative `createOnce` API,\n* add ESLint-compatible `create` methods to those rules, which delegate to `createOnce`.\n* This makes the plugin compatible with ESLint.\n*\n* The `plugin` object passed in is mutated in-place.\n*\n* @param plugin - Plugin to convert\n* @returns Plugin with all rules having `create` method\n* @throws {Error} If `plugin` is not an object, or `plugin.rules` is not an object\n*/\nfunction eslintCompatPlugin(plugin) {\n\tif (typeof plugin != \"object\" || !plugin) throw Error(\"Plugin must be an object\");\n\tlet { rules } = plugin;\n\tif (typeof rules != \"object\" || !rules) throw Error(\"Plugin must have an object as `rules` property\");\n\tlet afterHooksState = new AfterHooksState();\n\tfor (let ruleName in rules) Object.hasOwn(rules, ruleName) && convertRule(rules[ruleName], afterHooksState);\n\treturn plugin;\n}\n/**\n* Class containing state for tracking if any `after` hooks for a plugin's rules need to be called.\n*\n* # Aims\n*\n* Aims are:\n* 1. `after` hook of each rule runs after all other AST visit functions, and CFG event handlers.\n* 2. `after` hooks for *all* a plugin's rules run after *all* that plugin's rules have completed visiting AST.\n* 3. `after` hooks for *all* a plugin's rules run before *any* of plugin's rules begin linting another file.\n* 4. In the case of an error during AST traversal, `after` hooks are always still run.\n*\n* The above exactly matches the behavior when running a `createOnce` rule in Oxlint.\n*\n* # Why this is important\n*\n* All the complication comes from ensuring `after` hooks run even after an error during AST traversal.\n*\n* In ESLint CLI, an error will crash the process, so it doesn't particularly matter if `after` hooks run or not,\n* but language servers will typically swallow errors, and keep the process running.\n*\n* Rules using `before` and `after` hooks will often rely on both hooks running in a predictable order,\n* to maintain some internal state. For example, they may use `before` and `after` hooks to maintain a per-file\n* cache of data which is shared between rules. The cache use case is why rule (2) above is important.\n*\n* Below is an example of using `before` and `after` hooks to maintain a per-file cache, shared between rules.\n* It relies on all `before` hooks running before any rule starts visiting the AST,\n* and all `after` hooks running after all rules have finished visiting the AST.\n*\n* ```ts\n* let cache: Data | null = null;\n*\n* let numRunningRules = 0;\n*\n* const setupCache = (context) => {\n* if (cache === null) cache = new Data(context);\n* numRunningRules++;\n* };\n*\n* const teardownCache = () => {\n* numRunningRules--;\n* if (numRunningRules === 0) cache = null;\n* };\n*\n* const rule1 = {\n* createOnce(context) {\n* return {\n* before() {\n* setupCache(context);\n* },\n* Identifier(node) {\n* // Use `cache`\n* },\n* after: teardownCache,\n* };\n* },\n* };\n*\n* const rule2 = {\n* // Same as above\n* };\n*\n* const rule3 = {\n* // Same as above\n* };\n* ```\n*\n* If `after` hooks did not always run, the next lint run could get stale state, and malfunction.\n* If `after` hooks ran in the wrong order (e.g. after some `before` hooks for next file),\n* `numRunningRules` would never get to 0, and cache would never be cleared.\n*\n* Note that because all rules run together in a single AST traversal, if a rule from plugin X throws an error,\n* it can disrupt rules from plugin Y. This would make it hard to debug.\n*\n* # Mechanism\n*\n* ## Initialization\n*\n* Rules with an `after` hook register themselves by:\n*\n* 1. Calling `registerResetFunction` to register a function to run `after` hook and clean up internal state.\n* This call adds the reset fn to `resetFunctions`, and adds `AFTER_HOOK_INACTIVE` to `pendingStates`.\n* 2. Adding an `onCodePathEnd` CFG event handler to the visitor which calls `ruleFinished` at end of AST traversal.\n*\n* ## Per-file setup\n*\n* Before linting a file, `create` will call `setupAfterHook` which is created by `createContextAndVisitor`.\n* This registers that the `after` hook for the rule needs to run, by setting `pendingStates[ruleIndex]`\n* to `AFTER_HOOK_PENDING`, and incrementing `pendingCount`.\n*\n* If a cleanup microtask has not been scheduled yet, one is scheduled now (see reason below).\n*\n* ## Normal operation\n*\n* AST traversal for each rule ends with `ruleFinished` hook being called from `onCodePathEnd` CFG event handler.\n* It increments `lintFinishedCount`. If `lintFinishedCount` equals `pendingCount`, all rules have finished linting\n* the file, and `reset` is called, which calls all the pending `after` hooks.\n*\n* ## Error handling\n*\n* If an error is thrown during AST traversal, we ensure that `after` hooks are still run by 2 mechanisms:\n*\n* ### 1. Next microtick\n*\n* Before any rules began linting files, a microtask was scheduled, which runs on next micro-tick.\n* All language servers we're aware of run each lint task in a separate tick, so this microtask will run in next tick\n* after a linting run, before the next lint task starts.\n*\n* If the linting run completed successfully, the microtask does nothing.\n*\n* But if an error was thrown during AST traversal, this will be visible from the state of `pendingCount`.\n* The microtask will run any `after` hooks which need to be run, and reset state to reflect that there are\n* no more pending `after` hooks.\n*\n* ### 2. Fallback: Next lint run\n*\n* Before linting any file, the state of `pendingCount` is checked.\n* If any `after` hooks are still pending, they are run immediately.\n* They're run before the `context` objects in `createOnce` closures are updated to the next file,\n* so they run with access to the old `context` object from the last file.\n*\n* This fallback should not be required, but it's included as \"belt and braces\", to handle if any language server\n* or other environment running ESLint programmatically, does not pause a tick between linting runs.\n*/\nvar AfterHooksState = class {\n\tresetFunctions = [];\n\tpendingStates = [];\n\tpendingCount = 0;\n\tlintFinishedCount = 0;\n\tresetIsScheduled = !1;\n\tsourceCode = null;\n\tresetMicrotask = this.resetMicrotaskImpl.bind(this);\n\t/**\n\t* Register a function to run `after` hook for a rule, and reset state.\n\t* @param reset - Function to run `after` hook and reset state\n\t* @returns Index of rule\n\t*/\n\tregisterResetFunction(reset) {\n\t\tlet { pendingStates } = this, index = pendingStates.length;\n\t\treturn pendingStates.push(0), this.resetFunctions.push(reset), index;\n\t}\n\t/**\n\t* Register that a rule with `after` hook has completed linting a file.\n\t* Called by `onCodePathEnd` CFG event handler which is added to visitor for rules with `after` hooks.\n\t*\n\t* If all rules with an `after` hook which needs to be run have completed linting the file, run all `after` hooks.\n\t*/\n\truleFinished() {\n\t\tthis.lintFinishedCount++, this.lintFinishedCount === this.pendingCount && this.reset(!1);\n\t}\n\t/**\n\t* Call all reset functions where corresponding entry in `pendingStates` is `AFTER_HOOK_PENDING`.\n\t* Should only be called when some `after` hooks are pending.\n\t*\n\t* @param ignoreErrors - `true` to catch and silently ignore any errors which occur in `after` hooks.\n\t* `false` to throw them,\n\t* @throws {unknown} If `ignoreErrors` is `false` and an error occurs in any `after` hooks.\n\t*/\n\treset(ignoreErrors) {\n\t\tthis.pendingCount;\n\t\tlet { resetFunctions, pendingStates } = this, hooksLen = pendingStates.length, hasError = !1, error;\n\t\tfor (let i = 0; i < hooksLen; i++) if (pendingStates[i] !== 0) {\n\t\t\tpendingStates[i] = 0;\n\t\t\ttry {\n\t\t\t\tresetFunctions[i]();\n\t\t\t} catch (e) {\n\t\t\t\thasError === !1 && (hasError = !0, error = e);\n\t\t\t}\n\t\t}\n\t\tif (this.pendingCount = 0, this.lintFinishedCount = 0, this.sourceCode = null, hasError === !0 && ignoreErrors === !1) throw error;\n\t}\n\t/**\n\t* Schedule a microtask to run `reset` functions.\n\t*/\n\tscheduleReset() {\n\t\tqueueMicrotask(this.resetMicrotask), this.resetIsScheduled = !0;\n\t}\n\t/**\n\t* Function which is scheduled as the cleanup microtask.\n\t* `scheduleReset` uses `resetMicrotask` which is this method bound to `this`.\n\t*/\n\tresetMicrotaskImpl() {\n\t\tthis.resetIsScheduled = !1, this.pendingCount !== 0 && this.reset(!0);\n\t}\n};\n/**\n* Convert a rule.\n*\n* The `rule` object passed in is mutated in-place.\n*\n* @param rule - Rule to convert\n* @param afterHooksState - State of `after` hooks\n* @throws {Error} If `rule` is not an object\n*/\nfunction convertRule(rule, afterHooksState) {\n\tif (typeof rule != \"object\" || !rule) throw Error(\"Rule must be an object\");\n\tif (\"create\" in rule) return;\n\tlet context = null, visitor, beforeHook, setupAfterHook;\n\trule.create = (eslintContext) => {\n\t\tcontext === null && ({context, visitor, beforeHook, setupAfterHook} = createContextAndVisitor(rule, afterHooksState));\n\t\tlet eslintFileContext = Object.getPrototypeOf(eslintContext);\n\t\tif (setupAfterHook !== null) {\n\t\t\tlet { sourceCode } = eslintFileContext;\n\t\t\tafterHooksState.sourceCode !== sourceCode && (afterHooksState.sourceCode = sourceCode, afterHooksState.pendingCount !== 0 && afterHooksState.reset(!0));\n\t\t}\n\t\treturn Object.defineProperties(context, {\n\t\t\tid: { value: eslintContext.id },\n\t\t\toptions: { value: eslintContext.options },\n\t\t\treport: { value: eslintContext.report }\n\t\t}), Object.setPrototypeOf(context, eslintFileContext), beforeHook !== null && beforeHook() === !1 ? EMPTY_VISITOR : (setupAfterHook !== null && (setupAfterHook(eslintFileContext.sourceCode.ast), afterHooksState.resetIsScheduled === !1 && afterHooksState.scheduleReset()), visitor);\n\t};\n}\nconst FILE_CONTEXT = Object.freeze({\n\tget filename() {\n\t\tthrow Error(\"Cannot access `context.filename` in `createOnce`\");\n\t},\n\tgetFilename() {\n\t\tthrow Error(\"Cannot call `context.getFilename` in `createOnce`\");\n\t},\n\tget physicalFilename() {\n\t\tthrow Error(\"Cannot access `context.physicalFilename` in `createOnce`\");\n\t},\n\tgetPhysicalFilename() {\n\t\tthrow Error(\"Cannot call `context.getPhysicalFilename` in `createOnce`\");\n\t},\n\tget cwd() {\n\t\tthrow Error(\"Cannot access `context.cwd` in `createOnce`\");\n\t},\n\tgetCwd() {\n\t\tthrow Error(\"Cannot call `context.getCwd` in `createOnce`\");\n\t},\n\tget sourceCode() {\n\t\tthrow Error(\"Cannot access `context.sourceCode` in `createOnce`\");\n\t},\n\tgetSourceCode() {\n\t\tthrow Error(\"Cannot call `context.getSourceCode` in `createOnce`\");\n\t},\n\tget languageOptions() {\n\t\tthrow Error(\"Cannot access `context.languageOptions` in `createOnce`\");\n\t},\n\tget settings() {\n\t\tthrow Error(\"Cannot access `context.settings` in `createOnce`\");\n\t},\n\textend(extension) {\n\t\treturn Object.freeze(Object.assign(Object.create(this), extension));\n\t},\n\tget parserOptions() {\n\t\tthrow Error(\"Cannot access `context.parserOptions` in `createOnce`\");\n\t},\n\tget parserPath() {\n\t\tthrow Error(\"Cannot access `context.parserPath` in `createOnce`\");\n\t}\n});\n/**\n* Call `createOnce` method of rule, and return `Context`, `Visitor`, and `beforeHook` (if any).\n*\n* @param rule - Rule with `createOnce` method\n* @param afterHooksState - State of `after` hooks\n* @returns Object with `context`, `visitor`, and `beforeHook` properties,\n* and `setupAfterHook` function if visitor has an `after` hook\n*/\nfunction createContextAndVisitor(rule, afterHooksState) {\n\tlet { createOnce } = rule;\n\tif (createOnce == null) throw Error(\"Rules must define either a `create` or `createOnce` method\");\n\tif (typeof createOnce != \"function\") throw Error(\"Rule `createOnce` property must be a function\");\n\tlet context = Object.create(FILE_CONTEXT, {\n\t\tid: {\n\t\t\tvalue: null,\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t},\n\t\toptions: {\n\t\t\tvalue: null,\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t},\n\t\treport: {\n\t\t\tvalue() {\n\t\t\t\tthrow Error(\"Cannot report errors in `createOnce`\");\n\t\t\t},\n\t\t\tenumerable: !0,\n\t\t\tconfigurable: !0\n\t\t}\n\t}), { before: beforeHook, after: afterHook, ...visitor } = createOnce.call(rule, context);\n\tif (beforeHook === void 0) beforeHook = null;\n\telse if (beforeHook !== null && typeof beforeHook != \"function\") throw Error(\"`before` property of visitor must be a function if defined\");\n\tlet setupAfterHook = null;\n\tif (afterHook != null) {\n\t\tif (typeof afterHook != \"function\") throw Error(\"`after` property of visitor must be a function if defined\");\n\t\tlet program = null, ruleIndex = afterHooksState.registerResetFunction(() => {\n\t\t\tprogram = null, afterHook();\n\t\t});\n\t\tsetupAfterHook = (ast) => {\n\t\t\tprogram = ast, afterHooksState.pendingStates[ruleIndex] = 1, afterHooksState.pendingCount++;\n\t\t};\n\t\tlet onCodePathEnd = visitor.onCodePathEnd;\n\t\tvisitor.onCodePathEnd = onCodePathEnd == null ? function(_codePath, node) {\n\t\t\tnode === program && afterHooksState.ruleFinished();\n\t\t} : function(codePath, node) {\n\t\t\tonCodePathEnd.call(this, codePath, node), node === program && afterHooksState.ruleFinished();\n\t\t};\n\t}\n\treturn {\n\t\tcontext,\n\t\tvisitor,\n\t\tbeforeHook,\n\t\tsetupAfterHook\n\t};\n}\n//#endregion\nexport { definePlugin, defineRule, eslintCompatPlugin };\n","import { eslintCompatPlugin, type Plugin } from \"@oxlint/plugins\";\nimport { ALL_RULES } from \"./rules/registry.ts\";\n\nconst plugin: Plugin = eslintCompatPlugin({\n meta: { name: \"hook-o-gnese\" },\n rules: ALL_RULES,\n});\n\nexport const recommended = {\n jsPlugins: [\"./node_modules/hook-o-gnese/dist/index.mjs\"],\n options: { typeAware: true, typeCheck: true },\n rules: {\n \"hook-o-gnese/no-fat-effects\": \"warn\",\n \"hook-o-gnese/state-scatter\": \"warn\",\n \"hook-o-gnese/hook-coupling\": \"error\",\n \"hook-o-gnese/custom-hook-depth\": [\"warn\", { maxDepth: 3 }],\n \"typescript/no-floating-promises\": \"error\",\n \"typescript/no-misused-promises\": \"error\",\n },\n};\n\nexport default plugin;\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-iRG6wil9.mjs","names":[],"sources":["../src/ast-helpers.ts","../src/scoring/effect-score.ts","../src/scoring/thresholds.ts","../src/rules/no-fat-effects.ts","../src/scoring/state-score.ts","../src/rules/state-scatter.ts","../src/scoring/coupling-score.ts","../src/rules/hook-coupling.ts","../src/ts-program.ts","../src/rules/custom-hook-depth.ts","../src/rules/registry.ts"],"sourcesContent":["type Node = { type: string; [k: string]: unknown };\n\nconst HOOK_RE = /^use[A-Z]/;\n\nexport function getHookName(node: Node): string | null {\n if (node.type !== \"CallExpression\") return null;\n const callee = (node as any).callee as Node;\n if (callee?.type !== \"Identifier\") return null;\n const name = (callee as any).name as string;\n return HOOK_RE.test(name) ? name : null;\n}\n\nexport function isHookCall(node: Node, expected: string): boolean {\n return getHookName(node) === expected;\n}\n\nexport function isReactComponent(node: Node): boolean {\n if (node.type === \"FunctionDeclaration\") {\n const name = (node as any).id?.name as string | undefined;\n if (!name || !/^[A-Z]/.test(name)) return false;\n return findReturnsJSX(node);\n }\n if (node.type === \"VariableDeclaration\") {\n const decl = (node as any).declarations?.[0];\n const name = decl?.id?.name as string | undefined;\n const init = decl?.init as Node | undefined;\n if (!name || !/^[A-Z]/.test(name) || !init) return false;\n if (\n init.type === \"ArrowFunctionExpression\" ||\n init.type === \"FunctionExpression\"\n ) {\n return findReturnsJSX(init);\n }\n }\n return false;\n}\n\nexport function findReturnsJSX(node: Node): boolean {\n let found = false;\n walk(node, (n) => {\n if (\n n.type === \"JSXElement\" ||\n n.type === \"JSXFragment\" ||\n n.type === \"JSXSelfClosingElement\"\n ) {\n found = true;\n return false;\n }\n return true;\n });\n return found;\n}\n\nexport function walk(\n node: Node,\n visit: (n: Node) => boolean | void,\n seen: WeakSet<Node> = new WeakSet(),\n): void {\n if (seen.has(node)) return;\n seen.add(node);\n const cont = visit(node);\n if (cont === false) return;\n for (const key in node) {\n if (key === \"parent\") continue;\n const val = (node as any)[key];\n if (Array.isArray(val)) {\n for (const child of val) {\n if (child && typeof child === \"object\" && \"type\" in child) {\n walk(child as Node, visit, seen);\n }\n }\n } else if (val && typeof val === \"object\" && \"type\" in val) {\n walk(val as Node, visit, seen);\n }\n }\n}\n","import { walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface EffectScore {\n deps: number;\n branches: number;\n setStateCount: number;\n nestedEffects: number;\n hasCleanup: boolean;\n hasSubscriptionLike: boolean;\n total: number;\n}\n\nconst SET_STATE_RE = /^set[A-Z]/;\nconst BRANCH_TYPES = new Set([\n \"IfStatement\",\n \"ConditionalExpression\",\n \"SwitchCase\",\n \"LogicalExpression\",\n]);\n\nexport function scoreEffect(node: Node): EffectScore {\n const args = (node as any).arguments as Node[];\n const fn = args?.[0] as Node | undefined;\n const depsArr = args?.[1] as any;\n\n const deps = Array.isArray(depsArr?.elements) ? depsArr.elements.length : 0;\n let branches = 0;\n let setStateCount = 0;\n let nestedEffects = 0;\n let hasCleanup = false;\n let hasSubscriptionLike = false;\n\n if (fn) {\n const body = (fn as any).body as Node;\n if (body?.type === \"BlockStatement\") {\n for (const stmt of (body as any).body as Node[]) {\n if (stmt.type === \"ReturnStatement\") {\n const arg = (stmt as any).argument as Node | undefined;\n if (\n arg &&\n (arg.type === \"ArrowFunctionExpression\" ||\n arg.type === \"FunctionExpression\")\n ) hasCleanup = true;\n }\n }\n }\n\n walk(fn, (n) => {\n if (BRANCH_TYPES.has(n.type)) branches++;\n if (n.type === \"CallExpression\") {\n const callee = (n as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const name = (callee as any).name as string;\n if (SET_STATE_RE.test(name)) setStateCount++;\n if (name === \"useEffect\" && n !== node) nestedEffects++;\n if (\n name === \"addEventListener\" ||\n name === \"subscribe\" ||\n name === \"setInterval\" ||\n name === \"setTimeout\"\n ) hasSubscriptionLike = true;\n }\n if (callee?.type === \"MemberExpression\") {\n const prop = (callee as any).property as Node;\n if (prop?.type === \"Identifier\") {\n const name = (prop as any).name as string;\n if (\n name === \"addEventListener\" ||\n name === \"subscribe\" ||\n name === \"on\"\n ) hasSubscriptionLike = true;\n }\n }\n }\n return true;\n });\n }\n\n const cleanupPenalty = hasSubscriptionLike && !hasCleanup ? 3 : 0;\n const total = deps + branches * 2 + setStateCount * 1.5 +\n nestedEffects * 5 + cleanupPenalty;\n\n return {\n deps,\n branches,\n setStateCount,\n nestedEffects,\n hasCleanup,\n hasSubscriptionLike,\n total,\n };\n}\n","export interface Thresholds {\n fatEffect: { warn: number; error: number };\n stateScatter: { warn: number; error: number };\n hookCoupling: { warn: number; error: number };\n customHookDepth: { warn: number; error: number };\n}\n\nexport const DEFAULT_THRESHOLDS: Thresholds = {\n fatEffect: { warn: 10, error: 20 },\n stateScatter: { warn: 5, error: 8 },\n hookCoupling: { warn: 3, error: 6 },\n customHookDepth: { warn: 3, error: 5 },\n};\n","import { scoreEffect } from \"../scoring/effect-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isHookCall } from \"../ast-helpers.ts\";\n\ninterface Options { threshold?: number; errorThreshold?: number }\n\nexport interface RuleContext {\n options: unknown[];\n filename?: string;\n cwd?: string;\n report: (d: {\n message: string;\n node: unknown;\n severity?: \"warn\" | \"error\";\n }) => void;\n}\n\nexport const noFatEffects = {\n meta: {\n type: \"suggestion\" as const,\n docs: { description: \"Flag dense useEffect blocks\" },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.fatEffect.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.fatEffect.error;\n return {\n CallExpression(node: any) {\n if (!isHookCall(node, \"useEffect\")) return;\n const score = scoreEffect(node);\n if (score.total >= threshold) {\n const breakdown = `deps=${score.deps} branches=${score.branches} ` +\n `setStates=${score.setStateCount} nested=${score.nestedEffects}` +\n (score.hasSubscriptionLike && !score.hasCleanup\n ? \" missing-cleanup\"\n : \"\");\n const severity = score.total >= errorThreshold ? \"error\" : \"warn\";\n context.report({\n message:\n `useEffect entropy ${score.total.toFixed(1)} ≥ ${threshold} (${breakdown})`,\n node,\n severity,\n });\n }\n },\n };\n },\n};\n","import { isHookCall, walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface StateScore {\n useStateCount: number;\n correlatedSetters: number;\n total: number;\n}\n\nexport function scoreComponentState(componentNode: Node): StateScore {\n const setterNames = new Set<string>();\n let useStateCount = 0;\n\n // First pass: collect useState setters\n walk(componentNode, (n) => {\n if (!n) return true;\n if (n.type === \"VariableDeclarator\") {\n const init = (n as any).init as Node | undefined;\n const id = (n as any).id as Node | undefined;\n if (\n init?.type === \"CallExpression\" &&\n isHookCall(init, \"useState\") &&\n id?.type === \"ArrayPattern\"\n ) {\n useStateCount++;\n const els = (id as any).elements as Node[];\n const setter = els?.[1];\n if (setter?.type === \"Identifier\") {\n setterNames.add((setter as any).name as string);\n }\n }\n }\n return true;\n });\n\n // Second pass: count correlated setters\n let correlatedSetters = 0;\n walk(componentNode, (n) => {\n if (!n) return true;\n if (\n n.type === \"FunctionDeclaration\" ||\n n.type === \"FunctionExpression\" ||\n n.type === \"ArrowFunctionExpression\"\n ) {\n const calledSetters = new Set<string>();\n walk(n, (m) => {\n if (!m) return true;\n if (m.type === \"CallExpression\") {\n const callee = (m as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const name = (callee as any).name as string;\n if (setterNames.has(name)) calledSetters.add(name);\n }\n }\n return true;\n });\n if (calledSetters.size >= 2) correlatedSetters += calledSetters.size;\n }\n return true;\n });\n\n const total = useStateCount + correlatedSetters * 0.5;\n return { useStateCount, correlatedSetters, total };\n}\n","import { scoreComponentState } from \"../scoring/state-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isReactComponent } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options { threshold?: number; errorThreshold?: number }\n\nexport const stateScatter = {\n meta: {\n type: \"suggestion\" as const,\n docs: { description: \"Flag components with too many useState calls\" },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.stateScatter.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.stateScatter.error;\n function check(node: any) {\n if (!isReactComponent(node)) return;\n const s = scoreComponentState(node);\n if (s.total >= threshold) {\n const severity = s.total >= errorThreshold ? \"error\" : \"warn\";\n context.report({\n message:\n `state scatter ${s.total} ≥ ${threshold} (useStates=${s.useStateCount}, correlated setters=${s.correlatedSetters}). Consider useReducer.`,\n node,\n severity,\n });\n }\n }\n return {\n FunctionDeclaration: check,\n VariableDeclaration: check,\n };\n },\n};\n","import { isHookCall, walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface CouplingScore {\n total: number;\n readWriteSame: Array<{ state: string; effect: Node }>;\n}\n\nexport function scoreCoupling(componentNode: Node): CouplingScore {\n const stateBySetter = new Map<string, string>();\n walk(componentNode, (n) => {\n if (n.type === \"VariableDeclarator\") {\n const init = (n as any).init as Node | undefined;\n const id = (n as any).id as Node | undefined;\n if (\n init?.type === \"CallExpression\" &&\n isHookCall(init, \"useState\") &&\n id?.type === \"ArrayPattern\"\n ) {\n const els = (id as any).elements as Node[];\n const stateId = els?.[0];\n const setterId = els?.[1];\n if (stateId?.type === \"Identifier\" && setterId?.type === \"Identifier\") {\n stateBySetter.set(\n (setterId as any).name as string,\n (stateId as any).name as string,\n );\n }\n }\n }\n return true;\n });\n\n const readWriteSame: Array<{ state: string; effect: Node }> = [];\n let total = 0;\n\n walk(componentNode, (n) => {\n if (n.type === \"CallExpression\" && isHookCall(n, \"useEffect\")) {\n const effectFn = ((n as any).arguments as Node[])?.[0];\n if (!effectFn) return true;\n\n const stateRefs = new Set<string>();\n const stateWrites = new Set<string>();\n const stateNames = new Set(stateBySetter.values());\n\n walk(effectFn, (m) => {\n if (m.type === \"Identifier\") {\n const name = (m as any).name as string;\n if (stateNames.has(name)) stateRefs.add(name);\n }\n if (m.type === \"CallExpression\") {\n const callee = (m as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const setter = (callee as any).name as string;\n const stateName = stateBySetter.get(setter);\n if (stateName) stateWrites.add(stateName);\n }\n }\n return true;\n });\n\n for (const written of stateWrites) {\n if (stateRefs.has(written)) {\n readWriteSame.push({ state: written, effect: n });\n total += 3;\n }\n }\n }\n return true;\n });\n\n return { total, readWriteSame };\n}\n","import { scoreCoupling } from \"../scoring/coupling-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isReactComponent } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options { threshold?: number; errorThreshold?: number }\n\nexport const hookCoupling = {\n meta: {\n type: \"problem\" as const,\n docs: {\n description: \"Flag effects that read state they also write (loop bait)\",\n },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.hookCoupling.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.hookCoupling.error;\n function check(node: any) {\n if (!isReactComponent(node)) return;\n const s = scoreCoupling(node);\n if (s.total < threshold) return;\n const severity = s.total >= errorThreshold ? \"error\" : \"warn\";\n for (const v of s.readWriteSame) {\n context.report({\n message:\n `useEffect reads + writes same state '${v.state}' (loop risk)`,\n node: v.effect,\n severity,\n });\n }\n }\n return { FunctionDeclaration: check, VariableDeclaration: check };\n },\n};\n","import ts from \"typescript\";\n\nexport class TsProgramCache {\n private program: ts.Program | null = null;\n private rootDir: string;\n\n constructor(rootDir: string) {\n this.rootDir = rootDir;\n }\n\n private getProgram(): ts.Program {\n if (this.program) return this.program;\n const configPath = ts.findConfigFile(\n this.rootDir,\n ts.sys.fileExists,\n \"tsconfig.json\",\n );\n let compilerOptions: ts.CompilerOptions = {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n jsx: ts.JsxEmit.Preserve,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n allowJs: true,\n noEmit: true,\n skipLibCheck: true,\n strict: false,\n };\n let fileNames: string[] = [];\n if (configPath) {\n const cfg = ts.readConfigFile(configPath, ts.sys.readFile);\n const configDir = configPath.slice(0, configPath.lastIndexOf(\"/\"));\n const parsed = ts.parseJsonConfigFileContent(\n cfg.config,\n ts.sys,\n configDir,\n );\n compilerOptions = { ...compilerOptions, ...parsed.options };\n fileNames = parsed.fileNames;\n }\n this.program = ts.createProgram(fileNames, compilerOptions);\n return this.program;\n }\n\n resolveIdentifierDeclaration(\n filePath: string,\n identifier: string,\n ): ts.Declaration | null {\n const program = this.getProgram();\n const checker = program.getTypeChecker();\n const absolute = filePath.startsWith(\"/\")\n ? filePath\n : `${this.rootDir.replace(/\\/$/, \"\")}/${filePath}`;\n const sourceFile = program.getSourceFile(absolute) ??\n program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n let target: ts.Node | null = null;\n function find(node: ts.Node) {\n if (target) return;\n if (ts.isIdentifier(node) && node.text === identifier) {\n target = node;\n return;\n }\n ts.forEachChild(node, find);\n }\n find(sourceFile);\n if (!target) return null;\n\n const symbol = checker.getSymbolAtLocation(target);\n if (!symbol) return null;\n const aliased = symbol.flags & ts.SymbolFlags.Alias\n ? checker.getAliasedSymbol(symbol)\n : symbol;\n return aliased.declarations?.[0] ?? null;\n }\n\n countTransitiveHookCalls(\n decl: ts.Declaration,\n depth = 0,\n seen = new Set<ts.Declaration>(),\n ): number {\n if (depth > 10 || seen.has(decl)) return depth;\n seen.add(decl);\n const program = this.getProgram();\n const checker = program.getTypeChecker();\n let maxDepth = depth;\n\n const visit = (node: ts.Node) => {\n if (\n ts.isCallExpression(node) &&\n ts.isIdentifier(node.expression) &&\n /^use[A-Z]/.test(node.expression.text)\n ) {\n const sym = checker.getSymbolAtLocation(node.expression);\n if (sym) {\n const aliased = sym.flags & ts.SymbolFlags.Alias\n ? checker.getAliasedSymbol(sym)\n : sym;\n const innerDecl = aliased.declarations?.[0];\n if (innerDecl) {\n const sf = innerDecl.getSourceFile();\n if (\n sf.fileName.includes(\"node_modules/@types/react\") ||\n sf.fileName.includes(\"node_modules/react/\")\n ) {\n maxDepth = Math.max(maxDepth, depth + 1);\n } else {\n const childDepth = this.countTransitiveHookCalls(\n innerDecl,\n depth + 1,\n seen,\n );\n maxDepth = Math.max(maxDepth, childDepth);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(decl);\n return maxDepth;\n }\n}\n","import { TsProgramCache } from \"../ts-program.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { getHookName } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options { maxDepth?: number; errorMaxDepth?: number }\n\nconst REACT_HOOKS = new Set([\n \"useState\", \"useEffect\", \"useLayoutEffect\", \"useMemo\", \"useCallback\",\n \"useReducer\", \"useContext\", \"useRef\", \"useImperativeHandle\",\n \"useDebugValue\", \"useId\", \"useTransition\", \"useDeferredValue\",\n \"useSyncExternalStore\", \"useInsertionEffect\",\n]);\n\nlet sharedCache: TsProgramCache | null = null;\n\nexport const customHookDepth = {\n meta: {\n type: \"suggestion\" as const,\n docs: {\n description:\n \"Flag custom hooks whose transitive nesting exceeds maxDepth (type-aware).\",\n },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const maxDepth = opts.maxDepth ?? DEFAULT_THRESHOLDS.customHookDepth.warn;\n const errorMaxDepth = opts.errorMaxDepth ??\n DEFAULT_THRESHOLDS.customHookDepth.error;\n const g = globalThis as { Deno?: { cwd(): string }; process?: { cwd(): string } };\n const cwd = context.cwd ?? g.process?.cwd() ?? g.Deno?.cwd() ?? \".\";\n sharedCache ??= new TsProgramCache(cwd);\n const cache = sharedCache;\n const filename = context.filename;\n\n return {\n CallExpression(node: any) {\n const name = getHookName(node);\n if (!name || REACT_HOOKS.has(name)) return;\n if (!filename) return;\n const decl = cache.resolveIdentifierDeclaration(filename, name);\n if (!decl) return;\n const depth = cache.countTransitiveHookCalls(decl);\n if (depth >= maxDepth) {\n const severity = depth >= errorMaxDepth ? \"error\" : \"warn\";\n context.report({\n message:\n `custom hook '${name}' transitive depth ${depth} ≥ ${maxDepth}`,\n node,\n severity,\n });\n }\n },\n };\n },\n};\n","import type { Rule } from \"@oxlint/plugins\";\nimport { noFatEffects } from \"./no-fat-effects.ts\";\nimport { stateScatter } from \"./state-scatter.ts\";\nimport { hookCoupling } from \"./hook-coupling.ts\";\nimport { customHookDepth } from \"./custom-hook-depth.ts\";\n\n// Rules use our internal ESLint-compatible RuleContext shape; eslintCompatPlugin\n// adapts them to oxlint's stricter Rule contract at runtime.\nexport const ALL_RULES: Record<string, Rule> = {\n \"no-fat-effects\": noFatEffects as unknown as Rule,\n \"state-scatter\": stateScatter as unknown as Rule,\n \"hook-coupling\": hookCoupling as unknown as Rule,\n \"custom-hook-depth\": customHookDepth as unknown as Rule,\n};\n\nexport type RuleId =\n | \"no-fat-effects\"\n | \"state-scatter\"\n | \"hook-coupling\"\n | \"custom-hook-depth\";\n"],"mappings":";;AAEA,MAAM,UAAU;AAEhB,SAAgB,YAAY,MAA2B;AACrD,KAAI,KAAK,SAAS,iBAAkB,QAAO;CAC3C,MAAM,SAAU,KAAa;AAC7B,KAAI,QAAQ,SAAS,aAAc,QAAO;CAC1C,MAAM,OAAQ,OAAe;AAC7B,QAAO,QAAQ,KAAK,KAAK,GAAG,OAAO;;AAGrC,SAAgB,WAAW,MAAY,UAA2B;AAChE,QAAO,YAAY,KAAK,KAAK;;AAG/B,SAAgB,iBAAiB,MAAqB;AACpD,KAAI,KAAK,SAAS,uBAAuB;EACvC,MAAM,OAAQ,KAAa,IAAI;AAC/B,MAAI,CAAC,QAAQ,CAAC,SAAS,KAAK,KAAK,CAAE,QAAO;AAC1C,SAAO,eAAe,KAAK;;AAE7B,KAAI,KAAK,SAAS,uBAAuB;EACvC,MAAM,OAAQ,KAAa,eAAe;EAC1C,MAAM,OAAO,MAAM,IAAI;EACvB,MAAM,OAAO,MAAM;AACnB,MAAI,CAAC,QAAQ,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,KAAM,QAAO;AACnD,MACE,KAAK,SAAS,6BACd,KAAK,SAAS,qBAEd,QAAO,eAAe,KAAK;;AAG/B,QAAO;;AAGT,SAAgB,eAAe,MAAqB;CAClD,IAAI,QAAQ;AACZ,MAAK,OAAO,MAAM;AAChB,MACE,EAAE,SAAS,gBACX,EAAE,SAAS,iBACX,EAAE,SAAS,yBACX;AACA,WAAQ;AACR,UAAO;;AAET,SAAO;GACP;AACF,QAAO;;AAGT,SAAgB,KACd,MACA,OACA,uBAAsB,IAAI,SAAS,EAC7B;AACN,KAAI,KAAK,IAAI,KAAK,CAAE;AACpB,MAAK,IAAI,KAAK;AAEd,KADa,MAAM,KACX,KAAK,MAAO;AACpB,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,QAAQ,SAAU;EACtB,MAAM,MAAO,KAAa;AAC1B,MAAI,MAAM,QAAQ,IAAI;QACf,MAAM,SAAS,IAClB,KAAI,SAAS,OAAO,UAAU,YAAY,UAAU,MAClD,MAAK,OAAe,OAAO,KAAK;aAG3B,OAAO,OAAO,QAAQ,YAAY,UAAU,IACrD,MAAK,KAAa,OAAO,KAAK;;;;;AC1DpC,MAAM,eAAe;AACrB,MAAM,eAAe,IAAI,IAAI;CAC3B;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,YAAY,MAAyB;CACnD,MAAM,OAAQ,KAAa;CAC3B,MAAM,KAAK,OAAO;CAClB,MAAM,UAAU,OAAO;CAEvB,MAAM,OAAO,MAAM,QAAQ,SAAS,SAAS,GAAG,QAAQ,SAAS,SAAS;CAC1E,IAAI,WAAW;CACf,IAAI,gBAAgB;CACpB,IAAI,gBAAgB;CACpB,IAAI,aAAa;CACjB,IAAI,sBAAsB;AAE1B,KAAI,IAAI;EACN,MAAM,OAAQ,GAAW;AACzB,MAAI,MAAM,SAAS;QACZ,MAAM,QAAS,KAAa,KAC/B,KAAI,KAAK,SAAS,mBAAmB;IACnC,MAAM,MAAO,KAAa;AAC1B,QACE,QACC,IAAI,SAAS,6BACZ,IAAI,SAAS,sBACf,cAAa;;;AAKrB,OAAK,KAAK,MAAM;AACd,OAAI,aAAa,IAAI,EAAE,KAAK,CAAE;AAC9B,OAAI,EAAE,SAAS,kBAAkB;IAC/B,MAAM,SAAU,EAAU;AAC1B,QAAI,QAAQ,SAAS,cAAc;KACjC,MAAM,OAAQ,OAAe;AAC7B,SAAI,aAAa,KAAK,KAAK,CAAE;AAC7B,SAAI,SAAS,eAAe,MAAM,KAAM;AACxC,SACE,SAAS,sBACT,SAAS,eACT,SAAS,iBACT,SAAS,aACT,uBAAsB;;AAE1B,QAAI,QAAQ,SAAS,oBAAoB;KACvC,MAAM,OAAQ,OAAe;AAC7B,SAAI,MAAM,SAAS,cAAc;MAC/B,MAAM,OAAQ,KAAa;AAC3B,UACE,SAAS,sBACT,SAAS,eACT,SAAS,KACT,uBAAsB;;;;AAI9B,UAAO;IACP;;CAGJ,MAAM,iBAAiB,uBAAuB,CAAC,aAAa,IAAI;CAChE,MAAM,QAAQ,OAAO,WAAW,IAAI,gBAAgB,MAClD,gBAAgB,IAAI;AAEtB,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;;;ACrFH,MAAa,qBAAiC;CAC5C,WAAW;EAAE,MAAM;EAAI,OAAO;EAAI;CAClC,cAAc;EAAE,MAAM;EAAG,OAAO;EAAG;CACnC,cAAc;EAAE,MAAM;EAAG,OAAO;EAAG;CACnC,iBAAiB;EAAE,MAAM;EAAG,OAAO;EAAG;CACvC;;;ACKD,MAAa,eAAe;CAC1B,MAAM;EACJ,MAAM;EACN,MAAM,EAAE,aAAa,+BAA+B;EACrD;CACD,OAAO,SAAsB;EAC3B,MAAM,OAAQ,QAAQ,QAAQ,MAA8B,EAAE;EAC9D,MAAM,YAAY,KAAK,aAAa,mBAAmB,UAAU;EACjE,MAAM,iBAAiB,KAAK,kBAC1B,mBAAmB,UAAU;AAC/B,SAAO,EACL,eAAe,MAAW;AACxB,OAAI,CAAC,WAAW,MAAM,YAAY,CAAE;GACpC,MAAM,QAAQ,YAAY,KAAK;AAC/B,OAAI,MAAM,SAAS,WAAW;IAC5B,MAAM,YAAY,QAAQ,MAAM,KAAK,YAAY,MAAM,SAAS,aACjD,MAAM,cAAc,UAAU,MAAM,mBAChD,MAAM,uBAAuB,CAAC,MAAM,aACjC,qBACA;IACN,MAAM,WAAW,MAAM,SAAS,iBAAiB,UAAU;AAC3D,YAAQ,OAAO;KACb,SACE,qBAAqB,MAAM,MAAM,QAAQ,EAAE,CAAC,KAAK,UAAU,IAAI,UAAU;KAC3E;KACA;KACD,CAAC;;KAGP;;CAEJ;;;ACtCD,SAAgB,oBAAoB,eAAiC;CACnE,MAAM,8BAAc,IAAI,KAAa;CACrC,IAAI,gBAAgB;AAGpB,MAAK,gBAAgB,MAAM;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,EAAE,SAAS,sBAAsB;GACnC,MAAM,OAAQ,EAAU;GACxB,MAAM,KAAM,EAAU;AACtB,OACE,MAAM,SAAS,oBACf,WAAW,MAAM,WAAW,IAC5B,IAAI,SAAS,gBACb;AACA;IAEA,MAAM,SADO,GAAW,WACH;AACrB,QAAI,QAAQ,SAAS,aACnB,aAAY,IAAK,OAAe,KAAe;;;AAIrD,SAAO;GACP;CAGF,IAAI,oBAAoB;AACxB,MAAK,gBAAgB,MAAM;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,MACE,EAAE,SAAS,yBACX,EAAE,SAAS,wBACX,EAAE,SAAS,2BACX;GACA,MAAM,gCAAgB,IAAI,KAAa;AACvC,QAAK,IAAI,MAAM;AACb,QAAI,CAAC,EAAG,QAAO;AACf,QAAI,EAAE,SAAS,kBAAkB;KAC/B,MAAM,SAAU,EAAU;AAC1B,SAAI,QAAQ,SAAS,cAAc;MACjC,MAAM,OAAQ,OAAe;AAC7B,UAAI,YAAY,IAAI,KAAK,CAAE,eAAc,IAAI,KAAK;;;AAGtD,WAAO;KACP;AACF,OAAI,cAAc,QAAQ,EAAG,sBAAqB,cAAc;;AAElE,SAAO;GACP;CAEF,MAAM,QAAQ,gBAAgB,oBAAoB;AAClD,QAAO;EAAE;EAAe;EAAmB;EAAO;;;;ACxDpD,MAAa,eAAe;CAC1B,MAAM;EACJ,MAAM;EACN,MAAM,EAAE,aAAa,gDAAgD;EACtE;CACD,OAAO,SAAsB;EAC3B,MAAM,OAAQ,QAAQ,QAAQ,MAA8B,EAAE;EAC9D,MAAM,YAAY,KAAK,aAAa,mBAAmB,aAAa;EACpE,MAAM,iBAAiB,KAAK,kBAC1B,mBAAmB,aAAa;EAClC,SAAS,MAAM,MAAW;AACxB,OAAI,CAAC,iBAAiB,KAAK,CAAE;GAC7B,MAAM,IAAI,oBAAoB,KAAK;AACnC,OAAI,EAAE,SAAS,WAAW;IACxB,MAAM,WAAW,EAAE,SAAS,iBAAiB,UAAU;AACvD,YAAQ,OAAO;KACb,SACE,iBAAiB,EAAE,MAAM,KAAK,UAAU,cAAc,EAAE,cAAc,uBAAuB,EAAE,kBAAkB;KACnH;KACA;KACD,CAAC;;;AAGN,SAAO;GACL,qBAAqB;GACrB,qBAAqB;GACtB;;CAEJ;;;AC1BD,SAAgB,cAAc,eAAoC;CAChE,MAAM,gCAAgB,IAAI,KAAqB;AAC/C,MAAK,gBAAgB,MAAM;AACzB,MAAI,EAAE,SAAS,sBAAsB;GACnC,MAAM,OAAQ,EAAU;GACxB,MAAM,KAAM,EAAU;AACtB,OACE,MAAM,SAAS,oBACf,WAAW,MAAM,WAAW,IAC5B,IAAI,SAAS,gBACb;IACA,MAAM,MAAO,GAAW;IACxB,MAAM,UAAU,MAAM;IACtB,MAAM,WAAW,MAAM;AACvB,QAAI,SAAS,SAAS,gBAAgB,UAAU,SAAS,aACvD,eAAc,IACX,SAAiB,MACjB,QAAgB,KAClB;;;AAIP,SAAO;GACP;CAEF,MAAM,gBAAwD,EAAE;CAChE,IAAI,QAAQ;AAEZ,MAAK,gBAAgB,MAAM;AACzB,MAAI,EAAE,SAAS,oBAAoB,WAAW,GAAG,YAAY,EAAE;GAC7D,MAAM,WAAa,EAAU,YAAuB;AACpD,OAAI,CAAC,SAAU,QAAO;GAEtB,MAAM,4BAAY,IAAI,KAAa;GACnC,MAAM,8BAAc,IAAI,KAAa;GACrC,MAAM,aAAa,IAAI,IAAI,cAAc,QAAQ,CAAC;AAElD,QAAK,WAAW,MAAM;AACpB,QAAI,EAAE,SAAS,cAAc;KAC3B,MAAM,OAAQ,EAAU;AACxB,SAAI,WAAW,IAAI,KAAK,CAAE,WAAU,IAAI,KAAK;;AAE/C,QAAI,EAAE,SAAS,kBAAkB;KAC/B,MAAM,SAAU,EAAU;AAC1B,SAAI,QAAQ,SAAS,cAAc;MACjC,MAAM,SAAU,OAAe;MAC/B,MAAM,YAAY,cAAc,IAAI,OAAO;AAC3C,UAAI,UAAW,aAAY,IAAI,UAAU;;;AAG7C,WAAO;KACP;AAEF,QAAK,MAAM,WAAW,YACpB,KAAI,UAAU,IAAI,QAAQ,EAAE;AAC1B,kBAAc,KAAK;KAAE,OAAO;KAAS,QAAQ;KAAG,CAAC;AACjD,aAAS;;;AAIf,SAAO;GACP;AAEF,QAAO;EAAE;EAAO;EAAe;;;;ACjEjC,MAAa,eAAe;CAC1B,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,4DACd;EACF;CACD,OAAO,SAAsB;EAC3B,MAAM,OAAQ,QAAQ,QAAQ,MAA8B,EAAE;EAC9D,MAAM,YAAY,KAAK,aAAa,mBAAmB,aAAa;EACpE,MAAM,iBAAiB,KAAK,kBAC1B,mBAAmB,aAAa;EAClC,SAAS,MAAM,MAAW;AACxB,OAAI,CAAC,iBAAiB,KAAK,CAAE;GAC7B,MAAM,IAAI,cAAc,KAAK;AAC7B,OAAI,EAAE,QAAQ,UAAW;GACzB,MAAM,WAAW,EAAE,SAAS,iBAAiB,UAAU;AACvD,QAAK,MAAM,KAAK,EAAE,cAChB,SAAQ,OAAO;IACb,SACE,wCAAwC,EAAE,MAAM;IAClD,MAAM,EAAE;IACR;IACD,CAAC;;AAGN,SAAO;GAAE,qBAAqB;GAAO,qBAAqB;GAAO;;CAEpE;;;ACjCD,IAAa,iBAAb,MAA4B;CAC1B,UAAqC;CACrC;CAEA,YAAY,SAAiB;AAC3B,OAAK,UAAU;;CAGjB,aAAiC;AAC/B,MAAI,KAAK,QAAS,QAAO,KAAK;EAC9B,MAAM,aAAa,GAAG,eACpB,KAAK,SACL,GAAG,IAAI,YACP,gBACD;EACD,IAAI,kBAAsC;GACxC,QAAQ,GAAG,aAAa;GACxB,QAAQ,GAAG,WAAW;GACtB,KAAK,GAAG,QAAQ;GAChB,kBAAkB,GAAG,qBAAqB;GAC1C,SAAS;GACT,QAAQ;GACR,cAAc;GACd,QAAQ;GACT;EACD,IAAI,YAAsB,EAAE;AAC5B,MAAI,YAAY;GACd,MAAM,MAAM,GAAG,eAAe,YAAY,GAAG,IAAI,SAAS;GAC1D,MAAM,YAAY,WAAW,MAAM,GAAG,WAAW,YAAY,IAAI,CAAC;GAClE,MAAM,SAAS,GAAG,2BAChB,IAAI,QACJ,GAAG,KACH,UACD;AACD,qBAAkB;IAAE,GAAG;IAAiB,GAAG,OAAO;IAAS;AAC3D,eAAY,OAAO;;AAErB,OAAK,UAAU,GAAG,cAAc,WAAW,gBAAgB;AAC3D,SAAO,KAAK;;CAGd,6BACE,UACA,YACuB;EACvB,MAAM,UAAU,KAAK,YAAY;EACjC,MAAM,UAAU,QAAQ,gBAAgB;EACxC,MAAM,WAAW,SAAS,WAAW,IAAI,GACrC,WACA,GAAG,KAAK,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAAG;EAC1C,MAAM,aAAa,QAAQ,cAAc,SAAS,IAChD,QAAQ,cAAc,SAAS;AACjC,MAAI,CAAC,WAAY,QAAO;EAExB,IAAI,SAAyB;EAC7B,SAAS,KAAK,MAAe;AAC3B,OAAI,OAAQ;AACZ,OAAI,GAAG,aAAa,KAAK,IAAI,KAAK,SAAS,YAAY;AACrD,aAAS;AACT;;AAEF,MAAG,aAAa,MAAM,KAAK;;AAE7B,OAAK,WAAW;AAChB,MAAI,CAAC,OAAQ,QAAO;EAEpB,MAAM,SAAS,QAAQ,oBAAoB,OAAO;AAClD,MAAI,CAAC,OAAQ,QAAO;AAIpB,UAHgB,OAAO,QAAQ,GAAG,YAAY,QAC1C,QAAQ,iBAAiB,OAAO,GAChC,QACW,eAAe,MAAM;;CAGtC,yBACE,MACA,QAAQ,GACR,uBAAO,IAAI,KAAqB,EACxB;AACR,MAAI,QAAQ,MAAM,KAAK,IAAI,KAAK,CAAE,QAAO;AACzC,OAAK,IAAI,KAAK;EAEd,MAAM,UADU,KAAK,YACE,CAAC,gBAAgB;EACxC,IAAI,WAAW;EAEf,MAAM,SAAS,SAAkB;AAC/B,OACE,GAAG,iBAAiB,KAAK,IACzB,GAAG,aAAa,KAAK,WAAW,IAChC,YAAY,KAAK,KAAK,WAAW,KAAK,EACtC;IACA,MAAM,MAAM,QAAQ,oBAAoB,KAAK,WAAW;AACxD,QAAI,KAAK;KAIP,MAAM,aAHU,IAAI,QAAQ,GAAG,YAAY,QACvC,QAAQ,iBAAiB,IAAI,GAC7B,KACsB,eAAe;AACzC,SAAI,WAAW;MACb,MAAM,KAAK,UAAU,eAAe;AACpC,UACE,GAAG,SAAS,SAAS,4BAA4B,IACjD,GAAG,SAAS,SAAS,sBAAsB,CAE3C,YAAW,KAAK,IAAI,UAAU,QAAQ,EAAE;WACnC;OACL,MAAM,aAAa,KAAK,yBACtB,WACA,QAAQ,GACR,KACD;AACD,kBAAW,KAAK,IAAI,UAAU,WAAW;;;;;AAKjD,MAAG,aAAa,MAAM,MAAM;;AAG9B,QAAM,KAAK;AACX,SAAO;;;;;AClHX,MAAM,cAAc,IAAI,IAAI;CAC1B;CAAY;CAAa;CAAmB;CAAW;CACvD;CAAc;CAAc;CAAU;CACtC;CAAiB;CAAS;CAAiB;CAC3C;CAAwB;CACzB,CAAC;AAEF,IAAI,cAAqC;;;ACNzC,MAAa,YAAkC;CAC7C,kBAAkB;CAClB,iBAAiB;CACjB,iBAAiB;CACjB,qBAAqB;EDKrB,MAAM;GACJ,MAAM;GACN,MAAM,EACJ,aACE,6EACH;GACF;EACD,OAAO,SAAsB;GAC3B,MAAM,OAAQ,QAAQ,QAAQ,MAA8B,EAAE;GAC9D,MAAM,WAAW,KAAK,YAAY,mBAAmB,gBAAgB;GACrE,MAAM,gBAAgB,KAAK,iBACzB,mBAAmB,gBAAgB;GACrC,MAAM,IAAI;GACV,MAAM,MAAM,QAAQ,OAAO,EAAE,SAAS,KAAK,IAAI,EAAE,MAAM,KAAK,IAAI;AAChE,mBAAgB,IAAI,eAAe,IAAI;GACvC,MAAM,QAAQ;GACd,MAAM,WAAW,QAAQ;AAEzB,UAAO,EACL,eAAe,MAAW;IACxB,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAI,CAAC,QAAQ,YAAY,IAAI,KAAK,CAAE;AACpC,QAAI,CAAC,SAAU;IACf,MAAM,OAAO,MAAM,6BAA6B,UAAU,KAAK;AAC/D,QAAI,CAAC,KAAM;IACX,MAAM,QAAQ,MAAM,yBAAyB,KAAK;AAClD,QAAI,SAAS,UAAU;KACrB,MAAM,WAAW,SAAS,gBAAgB,UAAU;AACpD,aAAQ,OAAO;MACb,SACE,gBAAgB,KAAK,qBAAqB,MAAM,KAAK;MACvD;MACA;MACD,CAAC;;MAGP;;ECzCkB;CACtB"}
|
|
1
|
+
{"version":3,"file":"registry-iRG6wil9.mjs","names":[],"sources":["../src/ast-helpers.ts","../src/scoring/effect-score.ts","../src/scoring/thresholds.ts","../src/rules/no-fat-effects.ts","../src/scoring/state-score.ts","../src/rules/state-scatter.ts","../src/scoring/coupling-score.ts","../src/rules/hook-coupling.ts","../src/ts-program.ts","../src/rules/custom-hook-depth.ts","../src/rules/registry.ts"],"sourcesContent":["type Node = { type: string; [k: string]: unknown };\n\nconst HOOK_RE = /^use[A-Z]/;\n\nexport function getHookName(node: Node): string | null {\n if (node.type !== \"CallExpression\") return null;\n const callee = (node as any).callee as Node;\n if (callee?.type !== \"Identifier\") return null;\n const name = (callee as any).name as string;\n return HOOK_RE.test(name) ? name : null;\n}\n\nexport function isHookCall(node: Node, expected: string): boolean {\n return getHookName(node) === expected;\n}\n\nexport function isReactComponent(node: Node): boolean {\n if (node.type === \"FunctionDeclaration\") {\n const name = (node as any).id?.name as string | undefined;\n if (!name || !/^[A-Z]/.test(name)) return false;\n return findReturnsJSX(node);\n }\n if (node.type === \"VariableDeclaration\") {\n const decl = (node as any).declarations?.[0];\n const name = decl?.id?.name as string | undefined;\n const init = decl?.init as Node | undefined;\n if (!name || !/^[A-Z]/.test(name) || !init) return false;\n if (\n init.type === \"ArrowFunctionExpression\" ||\n init.type === \"FunctionExpression\"\n ) {\n return findReturnsJSX(init);\n }\n }\n return false;\n}\n\nexport function findReturnsJSX(node: Node): boolean {\n let found = false;\n walk(node, (n) => {\n if (\n n.type === \"JSXElement\" ||\n n.type === \"JSXFragment\" ||\n n.type === \"JSXSelfClosingElement\"\n ) {\n found = true;\n return false;\n }\n return true;\n });\n return found;\n}\n\nexport function walk(\n node: Node,\n visit: (n: Node) => boolean | void,\n seen: WeakSet<Node> = new WeakSet(),\n): void {\n if (seen.has(node)) return;\n seen.add(node);\n const cont = visit(node);\n if (cont === false) return;\n for (const key in node) {\n if (key === \"parent\") continue;\n const val = (node as any)[key];\n if (Array.isArray(val)) {\n for (const child of val) {\n if (child && typeof child === \"object\" && \"type\" in child) {\n walk(child as Node, visit, seen);\n }\n }\n } else if (val && typeof val === \"object\" && \"type\" in val) {\n walk(val as Node, visit, seen);\n }\n }\n}\n","import { walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface EffectScore {\n deps: number;\n branches: number;\n setStateCount: number;\n nestedEffects: number;\n hasCleanup: boolean;\n hasSubscriptionLike: boolean;\n total: number;\n}\n\nconst SET_STATE_RE = /^set[A-Z]/;\nconst BRANCH_TYPES = new Set([\n \"IfStatement\",\n \"ConditionalExpression\",\n \"SwitchCase\",\n \"LogicalExpression\",\n]);\n\nexport function scoreEffect(node: Node): EffectScore {\n const args = (node as any).arguments as Node[];\n const fn = args?.[0] as Node | undefined;\n const depsArr = args?.[1] as any;\n\n const deps = Array.isArray(depsArr?.elements) ? depsArr.elements.length : 0;\n let branches = 0;\n let setStateCount = 0;\n let nestedEffects = 0;\n let hasCleanup = false;\n let hasSubscriptionLike = false;\n\n if (fn) {\n const body = (fn as any).body as Node;\n if (body?.type === \"BlockStatement\") {\n for (const stmt of (body as any).body as Node[]) {\n if (stmt.type === \"ReturnStatement\") {\n const arg = (stmt as any).argument as Node | undefined;\n if (\n arg &&\n (arg.type === \"ArrowFunctionExpression\" ||\n arg.type === \"FunctionExpression\")\n ) hasCleanup = true;\n }\n }\n }\n\n walk(fn, (n) => {\n if (BRANCH_TYPES.has(n.type)) branches++;\n if (n.type === \"CallExpression\") {\n const callee = (n as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const name = (callee as any).name as string;\n if (SET_STATE_RE.test(name)) setStateCount++;\n if (name === \"useEffect\" && n !== node) nestedEffects++;\n if (\n name === \"addEventListener\" ||\n name === \"subscribe\" ||\n name === \"setInterval\" ||\n name === \"setTimeout\"\n ) hasSubscriptionLike = true;\n }\n if (callee?.type === \"MemberExpression\") {\n const prop = (callee as any).property as Node;\n if (prop?.type === \"Identifier\") {\n const name = (prop as any).name as string;\n if (\n name === \"addEventListener\" ||\n name === \"subscribe\" ||\n name === \"on\"\n ) hasSubscriptionLike = true;\n }\n }\n }\n return true;\n });\n }\n\n const cleanupPenalty = hasSubscriptionLike && !hasCleanup ? 3 : 0;\n const total = deps + branches * 2 + setStateCount * 1.5 +\n nestedEffects * 5 + cleanupPenalty;\n\n return {\n deps,\n branches,\n setStateCount,\n nestedEffects,\n hasCleanup,\n hasSubscriptionLike,\n total,\n };\n}\n","export interface Thresholds {\n fatEffect: { warn: number; error: number };\n stateScatter: { warn: number; error: number };\n hookCoupling: { warn: number; error: number };\n customHookDepth: { warn: number; error: number };\n}\n\nexport const DEFAULT_THRESHOLDS: Thresholds = {\n fatEffect: { warn: 10, error: 20 },\n stateScatter: { warn: 5, error: 8 },\n hookCoupling: { warn: 3, error: 6 },\n customHookDepth: { warn: 3, error: 5 },\n};\n","import { scoreEffect } from \"../scoring/effect-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isHookCall } from \"../ast-helpers.ts\";\n\ninterface Options {\n threshold?: number;\n errorThreshold?: number;\n}\n\nexport interface RuleContext {\n options: unknown[];\n filename?: string;\n cwd?: string;\n report: (d: {\n message: string;\n node: unknown;\n severity?: \"warn\" | \"error\";\n }) => void;\n}\n\nexport const noFatEffects = {\n meta: {\n type: \"suggestion\" as const,\n docs: { description: \"Flag dense useEffect blocks\" },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.fatEffect.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.fatEffect.error;\n return {\n CallExpression(node: any) {\n if (!isHookCall(node, \"useEffect\")) return;\n const score = scoreEffect(node);\n if (score.total >= threshold) {\n const breakdown = `deps=${score.deps} branches=${score.branches} ` +\n `setStates=${score.setStateCount} nested=${score.nestedEffects}` +\n (score.hasSubscriptionLike && !score.hasCleanup\n ? \" missing-cleanup\"\n : \"\");\n const severity = score.total >= errorThreshold ? \"error\" : \"warn\";\n context.report({\n message: `useEffect entropy ${\n score.total.toFixed(1)\n } ≥ ${threshold} (${breakdown})`,\n node,\n severity,\n });\n }\n },\n };\n },\n};\n","import { isHookCall, walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface StateScore {\n useStateCount: number;\n correlatedSetters: number;\n total: number;\n}\n\nexport function scoreComponentState(componentNode: Node): StateScore {\n const setterNames = new Set<string>();\n let useStateCount = 0;\n\n // First pass: collect useState setters\n walk(componentNode, (n) => {\n if (!n) return true;\n if (n.type === \"VariableDeclarator\") {\n const init = (n as any).init as Node | undefined;\n const id = (n as any).id as Node | undefined;\n if (\n init?.type === \"CallExpression\" &&\n isHookCall(init, \"useState\") &&\n id?.type === \"ArrayPattern\"\n ) {\n useStateCount++;\n const els = (id as any).elements as Node[];\n const setter = els?.[1];\n if (setter?.type === \"Identifier\") {\n setterNames.add((setter as any).name as string);\n }\n }\n }\n return true;\n });\n\n // Second pass: count correlated setters\n let correlatedSetters = 0;\n walk(componentNode, (n) => {\n if (!n) return true;\n if (\n n.type === \"FunctionDeclaration\" ||\n n.type === \"FunctionExpression\" ||\n n.type === \"ArrowFunctionExpression\"\n ) {\n const calledSetters = new Set<string>();\n walk(n, (m) => {\n if (!m) return true;\n if (m.type === \"CallExpression\") {\n const callee = (m as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const name = (callee as any).name as string;\n if (setterNames.has(name)) calledSetters.add(name);\n }\n }\n return true;\n });\n if (calledSetters.size >= 2) correlatedSetters += calledSetters.size;\n }\n return true;\n });\n\n const total = useStateCount + correlatedSetters * 0.5;\n return { useStateCount, correlatedSetters, total };\n}\n","import { scoreComponentState } from \"../scoring/state-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isReactComponent } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options {\n threshold?: number;\n errorThreshold?: number;\n}\n\nexport const stateScatter = {\n meta: {\n type: \"suggestion\" as const,\n docs: { description: \"Flag components with too many useState calls\" },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.stateScatter.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.stateScatter.error;\n function check(node: any) {\n if (!isReactComponent(node)) return;\n const s = scoreComponentState(node);\n if (s.total >= threshold) {\n const severity = s.total >= errorThreshold ? \"error\" : \"warn\";\n context.report({\n message:\n `state scatter ${s.total} ≥ ${threshold} (useStates=${s.useStateCount}, correlated setters=${s.correlatedSetters}). Consider useReducer.`,\n node,\n severity,\n });\n }\n }\n return {\n FunctionDeclaration: check,\n VariableDeclaration: check,\n };\n },\n};\n","import { isHookCall, walk } from \"../ast-helpers.ts\";\n\ntype Node = { type: string; [k: string]: unknown };\n\nexport interface CouplingScore {\n total: number;\n readWriteSame: Array<{ state: string; effect: Node }>;\n}\n\nexport function scoreCoupling(componentNode: Node): CouplingScore {\n const stateBySetter = new Map<string, string>();\n walk(componentNode, (n) => {\n if (n.type === \"VariableDeclarator\") {\n const init = (n as any).init as Node | undefined;\n const id = (n as any).id as Node | undefined;\n if (\n init?.type === \"CallExpression\" &&\n isHookCall(init, \"useState\") &&\n id?.type === \"ArrayPattern\"\n ) {\n const els = (id as any).elements as Node[];\n const stateId = els?.[0];\n const setterId = els?.[1];\n if (stateId?.type === \"Identifier\" && setterId?.type === \"Identifier\") {\n stateBySetter.set(\n (setterId as any).name as string,\n (stateId as any).name as string,\n );\n }\n }\n }\n return true;\n });\n\n const readWriteSame: Array<{ state: string; effect: Node }> = [];\n let total = 0;\n\n walk(componentNode, (n) => {\n if (n.type === \"CallExpression\" && isHookCall(n, \"useEffect\")) {\n const effectFn = ((n as any).arguments as Node[])?.[0];\n if (!effectFn) return true;\n\n const stateRefs = new Set<string>();\n const stateWrites = new Set<string>();\n const stateNames = new Set(stateBySetter.values());\n\n walk(effectFn, (m) => {\n if (m.type === \"Identifier\") {\n const name = (m as any).name as string;\n if (stateNames.has(name)) stateRefs.add(name);\n }\n if (m.type === \"CallExpression\") {\n const callee = (m as any).callee as Node;\n if (callee?.type === \"Identifier\") {\n const setter = (callee as any).name as string;\n const stateName = stateBySetter.get(setter);\n if (stateName) stateWrites.add(stateName);\n }\n }\n return true;\n });\n\n for (const written of stateWrites) {\n if (stateRefs.has(written)) {\n readWriteSame.push({ state: written, effect: n });\n total += 3;\n }\n }\n }\n return true;\n });\n\n return { total, readWriteSame };\n}\n","import { scoreCoupling } from \"../scoring/coupling-score.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { isReactComponent } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options {\n threshold?: number;\n errorThreshold?: number;\n}\n\nexport const hookCoupling = {\n meta: {\n type: \"problem\" as const,\n docs: {\n description: \"Flag effects that read state they also write (loop bait)\",\n },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const threshold = opts.threshold ?? DEFAULT_THRESHOLDS.hookCoupling.warn;\n const errorThreshold = opts.errorThreshold ??\n DEFAULT_THRESHOLDS.hookCoupling.error;\n function check(node: any) {\n if (!isReactComponent(node)) return;\n const s = scoreCoupling(node);\n if (s.total < threshold) return;\n const severity = s.total >= errorThreshold ? \"error\" : \"warn\";\n for (const v of s.readWriteSame) {\n context.report({\n message:\n `useEffect reads + writes same state '${v.state}' (loop risk)`,\n node: v.effect,\n severity,\n });\n }\n }\n return { FunctionDeclaration: check, VariableDeclaration: check };\n },\n};\n","import ts from \"typescript\";\n\nexport class TsProgramCache {\n private program: ts.Program | null = null;\n private rootDir: string;\n\n constructor(rootDir: string) {\n this.rootDir = rootDir;\n }\n\n private getProgram(): ts.Program {\n if (this.program) return this.program;\n const configPath = ts.findConfigFile(\n this.rootDir,\n ts.sys.fileExists,\n \"tsconfig.json\",\n );\n let compilerOptions: ts.CompilerOptions = {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n jsx: ts.JsxEmit.Preserve,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n allowJs: true,\n noEmit: true,\n skipLibCheck: true,\n strict: false,\n };\n let fileNames: string[] = [];\n if (configPath) {\n const cfg = ts.readConfigFile(configPath, ts.sys.readFile);\n const configDir = configPath.slice(0, configPath.lastIndexOf(\"/\"));\n const parsed = ts.parseJsonConfigFileContent(\n cfg.config,\n ts.sys,\n configDir,\n );\n compilerOptions = { ...compilerOptions, ...parsed.options };\n fileNames = parsed.fileNames;\n }\n this.program = ts.createProgram(fileNames, compilerOptions);\n return this.program;\n }\n\n resolveIdentifierDeclaration(\n filePath: string,\n identifier: string,\n ): ts.Declaration | null {\n const program = this.getProgram();\n const checker = program.getTypeChecker();\n const absolute = filePath.startsWith(\"/\")\n ? filePath\n : `${this.rootDir.replace(/\\/$/, \"\")}/${filePath}`;\n const sourceFile = program.getSourceFile(absolute) ??\n program.getSourceFile(filePath);\n if (!sourceFile) return null;\n\n let target: ts.Node | null = null;\n function find(node: ts.Node) {\n if (target) return;\n if (ts.isIdentifier(node) && node.text === identifier) {\n target = node;\n return;\n }\n ts.forEachChild(node, find);\n }\n find(sourceFile);\n if (!target) return null;\n\n const symbol = checker.getSymbolAtLocation(target);\n if (!symbol) return null;\n const aliased = symbol.flags & ts.SymbolFlags.Alias\n ? checker.getAliasedSymbol(symbol)\n : symbol;\n return aliased.declarations?.[0] ?? null;\n }\n\n countTransitiveHookCalls(\n decl: ts.Declaration,\n depth = 0,\n seen = new Set<ts.Declaration>(),\n ): number {\n if (depth > 10 || seen.has(decl)) return depth;\n seen.add(decl);\n const program = this.getProgram();\n const checker = program.getTypeChecker();\n let maxDepth = depth;\n\n const visit = (node: ts.Node) => {\n if (\n ts.isCallExpression(node) &&\n ts.isIdentifier(node.expression) &&\n /^use[A-Z]/.test(node.expression.text)\n ) {\n const sym = checker.getSymbolAtLocation(node.expression);\n if (sym) {\n const aliased = sym.flags & ts.SymbolFlags.Alias\n ? checker.getAliasedSymbol(sym)\n : sym;\n const innerDecl = aliased.declarations?.[0];\n if (innerDecl) {\n const sf = innerDecl.getSourceFile();\n if (\n sf.fileName.includes(\"node_modules/@types/react\") ||\n sf.fileName.includes(\"node_modules/react/\")\n ) {\n maxDepth = Math.max(maxDepth, depth + 1);\n } else {\n const childDepth = this.countTransitiveHookCalls(\n innerDecl,\n depth + 1,\n seen,\n );\n maxDepth = Math.max(maxDepth, childDepth);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(decl);\n return maxDepth;\n }\n}\n","import { TsProgramCache } from \"../ts-program.ts\";\nimport { DEFAULT_THRESHOLDS } from \"../scoring/thresholds.ts\";\nimport { getHookName } from \"../ast-helpers.ts\";\nimport type { RuleContext } from \"./no-fat-effects.ts\";\n\ninterface Options {\n maxDepth?: number;\n errorMaxDepth?: number;\n}\n\nconst REACT_HOOKS = new Set([\n \"useState\",\n \"useEffect\",\n \"useLayoutEffect\",\n \"useMemo\",\n \"useCallback\",\n \"useReducer\",\n \"useContext\",\n \"useRef\",\n \"useImperativeHandle\",\n \"useDebugValue\",\n \"useId\",\n \"useTransition\",\n \"useDeferredValue\",\n \"useSyncExternalStore\",\n \"useInsertionEffect\",\n]);\n\nlet sharedCache: TsProgramCache | null = null;\n\nexport const customHookDepth = {\n meta: {\n type: \"suggestion\" as const,\n docs: {\n description:\n \"Flag custom hooks whose transitive nesting exceeds maxDepth (type-aware).\",\n },\n },\n create(context: RuleContext) {\n const opts = (context.options[0] as Options | undefined) ?? {};\n const maxDepth = opts.maxDepth ?? DEFAULT_THRESHOLDS.customHookDepth.warn;\n const errorMaxDepth = opts.errorMaxDepth ??\n DEFAULT_THRESHOLDS.customHookDepth.error;\n const g = globalThis as {\n Deno?: { cwd(): string };\n process?: { cwd(): string };\n };\n const cwd = context.cwd ?? g.process?.cwd() ?? g.Deno?.cwd() ?? \".\";\n sharedCache ??= new TsProgramCache(cwd);\n const cache = sharedCache;\n const filename = context.filename;\n\n return {\n CallExpression(node: any) {\n const name = getHookName(node);\n if (!name || REACT_HOOKS.has(name)) return;\n if (!filename) return;\n const decl = cache.resolveIdentifierDeclaration(filename, name);\n if (!decl) return;\n const depth = cache.countTransitiveHookCalls(decl);\n if (depth >= maxDepth) {\n const severity = depth >= errorMaxDepth ? \"error\" : \"warn\";\n context.report({\n message:\n `custom hook '${name}' transitive depth ${depth} ≥ ${maxDepth}`,\n node,\n severity,\n });\n }\n },\n };\n },\n};\n","import type { Rule } from \"@oxlint/plugins\";\nimport { noFatEffects } from \"./no-fat-effects.ts\";\nimport { stateScatter } from \"./state-scatter.ts\";\nimport { hookCoupling } from \"./hook-coupling.ts\";\nimport { customHookDepth } from \"./custom-hook-depth.ts\";\n\n// Rules use our internal ESLint-compatible RuleContext shape; eslintCompatPlugin\n// adapts them to oxlint's stricter Rule contract at runtime.\nexport const ALL_RULES: Record<string, Rule> = {\n \"no-fat-effects\": noFatEffects as unknown as Rule,\n \"state-scatter\": stateScatter as unknown as Rule,\n \"hook-coupling\": hookCoupling as unknown as Rule,\n \"custom-hook-depth\": customHookDepth as unknown as Rule,\n};\n\nexport type RuleId =\n | \"no-fat-effects\"\n | \"state-scatter\"\n | \"hook-coupling\"\n | \"custom-hook-depth\";\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hook-o-gnese",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Score React hook complexity. Runs as oxlint plugin or standalone CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.mts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"hook-o-gnese": "
|
|
9
|
+
"hook-o-gnese": "dist/cli.mjs"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
@@ -18,8 +18,20 @@
|
|
|
18
18
|
"types": "./dist/engine.d.mts"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
22
|
-
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"oxlint",
|
|
28
|
+
"oxlint-plugin",
|
|
29
|
+
"react",
|
|
30
|
+
"hooks",
|
|
31
|
+
"complexity",
|
|
32
|
+
"lint",
|
|
33
|
+
"cli"
|
|
34
|
+
],
|
|
23
35
|
"license": "MIT",
|
|
24
36
|
"author": "Michal Rehout",
|
|
25
37
|
"repository": {
|
|
@@ -38,7 +50,9 @@
|
|
|
38
50
|
"typescript": ">=6.0.0"
|
|
39
51
|
},
|
|
40
52
|
"peerDependenciesMeta": {
|
|
41
|
-
"oxlint": {
|
|
53
|
+
"oxlint": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
42
56
|
},
|
|
43
57
|
"dependencies": {
|
|
44
58
|
"globby": "^16.2.0",
|