hook-o-gnese 0.0.7 → 0.0.8

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/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as isTypescriptAvailable } from "./registry-YXM4yhf2.mjs";
3
- import { n as lintFiles } from "./engine-D5X2INMx.mjs";
3
+ import { n as lintFiles } from "./engine-DKI7WYuw.mjs";
4
4
  import { parseArgs } from "node:util";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { globby } from "globby";
@@ -156,8 +156,11 @@ async function runCli(opts, io) {
156
156
  return 2;
157
157
  }
158
158
  const { engine, ignore } = await loadConfig(opts.cwd, opts.config, io.readTextFile);
159
- if (opts.typeAware) if (isTypescriptAvailable(opts.cwd)) engine.typeAware = true;
160
- else io.writeStderr("warning: --type-aware was requested but the 'typescript' package is not installed. Skipping type-aware rules. Install with: npm i -D typescript@>=6\n");
159
+ if (opts.typeAware) engine.typeAware = true;
160
+ if (engine.typeAware && !isTypescriptAvailable(opts.cwd)) {
161
+ io.writeStderr("warning: type-aware rules require the 'typescript' package to be installed in your project. Skipping type-aware rules. Install with: npm i -D typescript@>=6\n");
162
+ engine.typeAware = false;
163
+ }
161
164
  const finalEngine = applyCliRuleOverrides(engine, opts.ruleOverrides);
162
165
  const files = await globby(opts.paths, {
163
166
  ignore: [...DEFAULT_IGNORE, ...ignore],
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 {\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 { isTypescriptAvailable } from \"./ts-program.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 Requires 'typescript' installed in your project.\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) {\n if (isTypescriptAvailable(opts.cwd)) {\n engine.typeAware = true;\n } else {\n io.writeStderr(\n \"warning: --type-aware was requested but the 'typescript' package \" +\n \"is not installed. Skipping type-aware rules. \" +\n \"Install with: npm i -D typescript@>=6\\n\",\n );\n }\n }\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 { isTypescriptAvailable } from \"./ts-program.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 Requires 'typescript' installed in your project.\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 // CLI flag --type-aware force-enables; config-file `typeAware: true` is\n // honored as-is. Either way, downgrade to false if `typescript` cannot be\n // loaded, so the rule never explodes at create() time.\n if (opts.typeAware) engine.typeAware = true;\n if (engine.typeAware && !isTypescriptAvailable(opts.cwd)) {\n io.writeStderr(\n \"warning: type-aware rules require the 'typescript' package to be \" +\n \"installed in your project. Skipping type-aware rules. \" +\n \"Install with: npm i -D typescript@>=6\\n\",\n );\n engine.typeAware = false;\n }\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,6 +1,16 @@
1
1
  import { t as ALL_RULES } from "./registry-YXM4yhf2.mjs";
2
2
  import { parseSync } from "oxc-parser";
3
3
  //#region src/engine.ts
4
+ /**
5
+ * Standalone linting engine for `hook-o-gnese`.
6
+ *
7
+ * Provides a runtime-agnostic pipeline that parses files with `oxc-parser`,
8
+ * runs the package's rules against the resulting AST, and returns
9
+ * {@link Diagnostic} records. Used directly by the CLI and embeddable in any
10
+ * host that can supply a file reader.
11
+ *
12
+ * @module
13
+ */
4
14
  const TYPE_AWARE_RULES = new Set(["hook-o-gnese/custom-hook-depth"]);
5
15
  function ruleNamespace(id) {
6
16
  return id.replace(/^hook-o-gnese\//, "");
@@ -51,6 +61,16 @@ function walkAST(node, handlers) {
51
61
  const exit = handlers[`${node.type}:exit`];
52
62
  if (exit) exit(node);
53
63
  }
64
+ /**
65
+ * Lint a single source file and return any diagnostics produced.
66
+ *
67
+ * Parses `source` with `oxc-parser`, bails out early on non-React files, and
68
+ * dispatches every enabled rule in `config` against the resulting AST.
69
+ *
70
+ * @param filePath Path used for diagnostic reporting and to infer the parser language.
71
+ * @param source The source code to lint.
72
+ * @param config Resolved engine configuration.
73
+ */
54
74
  async function lintFile(filePath, source, config) {
55
75
  const parsed = parseSync(filePath, source, {
56
76
  lang: filePath.endsWith(".tsx") ? "tsx" : filePath.endsWith(".ts") ? "ts" : filePath.endsWith(".jsx") ? "jsx" : "js",
@@ -103,6 +123,13 @@ async function lintFile(filePath, source, config) {
103
123
  }
104
124
  return out;
105
125
  }
126
+ /**
127
+ * Lint a batch of files in parallel and return their combined diagnostics.
128
+ *
129
+ * @param filePaths Paths to lint.
130
+ * @param config Resolved engine configuration shared across files.
131
+ * @param readTextFile File reader supplied by the host (Deno, Node, in-memory).
132
+ */
106
133
  async function lintFiles(filePaths, config, readTextFile) {
107
134
  return (await Promise.all(filePaths.map(async (p) => {
108
135
  return lintFile(p, await readTextFile(p), config);
@@ -111,4 +138,4 @@ async function lintFiles(filePaths, config, readTextFile) {
111
138
  //#endregion
112
139
  export { lintFiles as n, lintFile as t };
113
140
 
114
- //# sourceMappingURL=engine-D5X2INMx.mjs.map
141
+ //# sourceMappingURL=engine-DKI7WYuw.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-DKI7WYuw.mjs","names":[],"sources":["../src/engine.ts"],"sourcesContent":["/**\n * Standalone linting engine for `hook-o-gnese`.\n *\n * Provides a runtime-agnostic pipeline that parses files with `oxc-parser`,\n * runs the package's rules against the resulting AST, and returns\n * {@link Diagnostic} records. Used directly by the CLI and embeddable in any\n * host that can supply a file reader.\n *\n * @module\n */\n\nimport { parseSync } from \"oxc-parser\";\nimport { ALL_RULES } from \"./rules/registry.ts\";\n\n/** Rule severity. `\"off\"` disables a rule entirely. */\nexport type Severity = \"off\" | \"warn\" | \"error\";\n\n/** A single lint finding produced by the engine. */\nexport interface Diagnostic {\n /** Absolute or workspace-relative path of the linted file. */\n file: string;\n /** Fully qualified rule id (e.g. `\"hook-o-gnese/no-fat-effects\"`). */\n rule: string;\n /** Effective severity after rule and config resolution. */\n severity: Exclude<Severity, \"off\">;\n /** Human-readable description of the problem. */\n message: string;\n /** 1-based line where the diagnostic starts. */\n line: number;\n /** 1-based column where the diagnostic starts. */\n column: number;\n /** 1-based line where the diagnostic ends, when known. */\n endLine?: number;\n /** 1-based column where the diagnostic ends, when known. */\n endColumn?: number;\n}\n\n/** Per-rule configuration: severity plus optional rule-specific options. */\nexport interface RuleConfig {\n severity: Severity;\n options?: unknown;\n}\n\n/** Configuration consumed by {@link lintFile} and {@link lintFiles}. */\nexport interface EngineConfig {\n /** Map of rule id to its configuration. */\n rules: Record<string, RuleConfig>;\n /** Working directory used to resolve TypeScript projects and relative paths. */\n cwd: string;\n /** When true, type-aware rules (e.g. `custom-hook-depth`) are run. */\n typeAware: boolean;\n}\n\nconst TYPE_AWARE_RULES = new Set([\"hook-o-gnese/custom-hook-depth\"]);\n\nfunction ruleNamespace(id: string): string {\n return id.replace(/^hook-o-gnese\\//, \"\");\n}\n\nfunction buildLineOffsets(source: string): number[] {\n const offsets = [0];\n for (let i = 0; i < source.length; i++) {\n if (source.charCodeAt(i) === 10) offsets.push(i + 1);\n }\n return offsets;\n}\n\nfunction offsetToLineCol(\n offset: number,\n lineOffsets: number[],\n): { line: number; column: number } {\n let lo = 0;\n let hi = lineOffsets.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n if (lineOffsets[mid] <= offset) lo = mid;\n else hi = mid - 1;\n }\n return { line: lo + 1, column: offset - lineOffsets[lo] + 1 };\n}\n\nfunction getLoc(\n node: any,\n lineOffsets: number[],\n): { line: number; column: number; endLine?: number; endColumn?: number } {\n const startOffset = typeof node?.start === \"number\" ? node.start : undefined;\n const endOffset = typeof node?.end === \"number\" ? node.end : undefined;\n if (startOffset === undefined) {\n return { line: 1, column: 1 };\n }\n const start = offsetToLineCol(startOffset, lineOffsets);\n const end = endOffset !== undefined\n ? offsetToLineCol(endOffset, lineOffsets)\n : undefined;\n return {\n line: start.line,\n column: start.column,\n endLine: end?.line,\n endColumn: end?.column,\n };\n}\n\nfunction walkAST(node: any, handlers: Record<string, any>) {\n if (!node || typeof node !== \"object\") return;\n const enter = handlers[node.type];\n if (enter) enter(node);\n for (const key in node) {\n const v = node[key];\n if (Array.isArray(v)) {\n for (const c of v) walkAST(c, handlers);\n } else if (v && typeof v === \"object\") {\n walkAST(v, handlers);\n }\n }\n const exit = handlers[`${node.type}:exit`];\n if (exit) exit(node);\n}\n\n/**\n * Lint a single source file and return any diagnostics produced.\n *\n * Parses `source` with `oxc-parser`, bails out early on non-React files, and\n * dispatches every enabled rule in `config` against the resulting AST.\n *\n * @param filePath Path used for diagnostic reporting and to infer the parser language.\n * @param source The source code to lint.\n * @param config Resolved engine configuration.\n */\nexport async function lintFile(\n filePath: string,\n source: string,\n config: EngineConfig,\n): Promise<Diagnostic[]> {\n const lang = filePath.endsWith(\".tsx\")\n ? \"tsx\"\n : filePath.endsWith(\".ts\")\n ? \"ts\"\n : filePath.endsWith(\".jsx\")\n ? \"jsx\"\n : \"js\";\n\n const parsed = parseSync(filePath, source, {\n lang,\n sourceType: \"module\",\n });\n\n const lineOffsets = buildLineOffsets(source);\n\n if (parsed.errors?.length) {\n return parsed.errors.map((e: any) => {\n const offset = typeof e.labels?.[0]?.start === \"number\"\n ? e.labels[0].start\n : undefined;\n const loc = offset !== undefined\n ? offsetToLineCol(offset, lineOffsets)\n : { line: 1, column: 1 };\n return {\n file: filePath,\n rule: \"parse-error\",\n severity: \"error\" as const,\n message: e.message ?? \"parse error\",\n line: loc.line,\n column: loc.column,\n };\n });\n }\n\n // Bail early on non-React files\n const imports = parsed.module?.staticImports ?? [];\n const hasReact = imports.some((i: any) =>\n (i.moduleRequest?.value ?? i.source?.value) === \"react\"\n );\n if (!hasReact) return [];\n\n const out: Diagnostic[] = [];\n\n for (const [ruleId, ruleCfg] of Object.entries(config.rules)) {\n if (ruleCfg.severity === \"off\") continue;\n if (!config.typeAware && TYPE_AWARE_RULES.has(ruleId)) continue;\n\n const rule = (ALL_RULES as any)[ruleNamespace(ruleId)];\n if (!rule) continue;\n\n const localDiags: Diagnostic[] = [];\n const context = {\n options: ruleCfg.options ? [ruleCfg.options] : [],\n filename: filePath,\n cwd: config.cwd,\n report(d: { message: string; node: any; severity?: \"warn\" | \"error\" }) {\n const loc = getLoc(d.node, lineOffsets);\n const cfgSev = ruleCfg.severity as \"warn\" | \"error\";\n // Rule-emitted severity only escalates (warn → error); never downgrades.\n const severity = d.severity === \"error\" ? \"error\" : cfgSev;\n localDiags.push({\n file: filePath,\n rule: ruleId,\n severity,\n message: d.message,\n ...loc,\n });\n },\n };\n\n const handlers = rule.create(context);\n walkAST(parsed.program, handlers);\n out.push(...localDiags);\n }\n\n return out;\n}\n\n/** Reads a file's contents as text. Injected so the engine stays runtime-agnostic. */\nexport type ReadTextFile = (path: string) => Promise<string>;\n\n/**\n * Lint a batch of files in parallel and return their combined diagnostics.\n *\n * @param filePaths Paths to lint.\n * @param config Resolved engine configuration shared across files.\n * @param readTextFile File reader supplied by the host (Deno, Node, in-memory).\n */\nexport async function lintFiles(\n filePaths: string[],\n config: EngineConfig,\n readTextFile: ReadTextFile,\n): Promise<Diagnostic[]> {\n const results = await Promise.all(\n filePaths.map(async (p) => {\n const src = await readTextFile(p);\n return lintFile(p, src, config);\n }),\n );\n return results.flat();\n}\n"],"mappings":"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"}
package/dist/engine.d.mts CHANGED
@@ -1,26 +1,69 @@
1
1
  //#region src/engine.d.ts
2
+ /**
3
+ * Standalone linting engine for `hook-o-gnese`.
4
+ *
5
+ * Provides a runtime-agnostic pipeline that parses files with `oxc-parser`,
6
+ * runs the package's rules against the resulting AST, and returns
7
+ * {@link Diagnostic} records. Used directly by the CLI and embeddable in any
8
+ * host that can supply a file reader.
9
+ *
10
+ * @module
11
+ */
12
+ /** Rule severity. `"off"` disables a rule entirely. */
2
13
  type Severity = "off" | "warn" | "error";
14
+ /** A single lint finding produced by the engine. */
3
15
  interface Diagnostic {
16
+ /** Absolute or workspace-relative path of the linted file. */
4
17
  file: string;
18
+ /** Fully qualified rule id (e.g. `"hook-o-gnese/no-fat-effects"`). */
5
19
  rule: string;
20
+ /** Effective severity after rule and config resolution. */
6
21
  severity: Exclude<Severity, "off">;
22
+ /** Human-readable description of the problem. */
7
23
  message: string;
24
+ /** 1-based line where the diagnostic starts. */
8
25
  line: number;
26
+ /** 1-based column where the diagnostic starts. */
9
27
  column: number;
28
+ /** 1-based line where the diagnostic ends, when known. */
10
29
  endLine?: number;
30
+ /** 1-based column where the diagnostic ends, when known. */
11
31
  endColumn?: number;
12
32
  }
33
+ /** Per-rule configuration: severity plus optional rule-specific options. */
13
34
  interface RuleConfig {
14
35
  severity: Severity;
15
36
  options?: unknown;
16
37
  }
38
+ /** Configuration consumed by {@link lintFile} and {@link lintFiles}. */
17
39
  interface EngineConfig {
40
+ /** Map of rule id to its configuration. */
18
41
  rules: Record<string, RuleConfig>;
42
+ /** Working directory used to resolve TypeScript projects and relative paths. */
19
43
  cwd: string;
44
+ /** When true, type-aware rules (e.g. `custom-hook-depth`) are run. */
20
45
  typeAware: boolean;
21
46
  }
47
+ /**
48
+ * Lint a single source file and return any diagnostics produced.
49
+ *
50
+ * Parses `source` with `oxc-parser`, bails out early on non-React files, and
51
+ * dispatches every enabled rule in `config` against the resulting AST.
52
+ *
53
+ * @param filePath Path used for diagnostic reporting and to infer the parser language.
54
+ * @param source The source code to lint.
55
+ * @param config Resolved engine configuration.
56
+ */
22
57
  declare function lintFile(filePath: string, source: string, config: EngineConfig): Promise<Diagnostic[]>;
58
+ /** Reads a file's contents as text. Injected so the engine stays runtime-agnostic. */
23
59
  type ReadTextFile = (path: string) => Promise<string>;
60
+ /**
61
+ * Lint a batch of files in parallel and return their combined diagnostics.
62
+ *
63
+ * @param filePaths Paths to lint.
64
+ * @param config Resolved engine configuration shared across files.
65
+ * @param readTextFile File reader supplied by the host (Deno, Node, in-memory).
66
+ */
24
67
  declare function lintFiles(filePaths: string[], config: EngineConfig, readTextFile: ReadTextFile): Promise<Diagnostic[]>;
25
68
  //#endregion
26
69
  export { Diagnostic, EngineConfig, ReadTextFile, RuleConfig, Severity, lintFile, lintFiles };
package/dist/engine.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as lintFiles, t as lintFile } from "./engine-D5X2INMx.mjs";
1
+ import { n as lintFiles, t as lintFile } from "./engine-DKI7WYuw.mjs";
2
2
  export { lintFile, lintFiles };
package/dist/index.d.mts CHANGED
@@ -3971,6 +3971,13 @@ interface CreateOnceRule {
3971
3971
  //#endregion
3972
3972
  //#region src/index.d.ts
3973
3973
  declare const plugin: Plugin;
3974
+ /**
3975
+ * Recommended oxlint configuration for `hook-o-gnese`.
3976
+ *
3977
+ * Enables every rule the plugin ships with sensible severities, plus the
3978
+ * tsgolint type-aware rules required for full coverage. Spread into your
3979
+ * oxlint config alongside the default plugin export.
3980
+ */
3974
3981
  declare const recommended: {
3975
3982
  jsPlugins: string[];
3976
3983
  options: {
package/dist/index.mjs CHANGED
@@ -332,10 +332,33 @@ function createContextAndVisitor(rule, afterHooksState) {
332
332
  }
333
333
  //#endregion
334
334
  //#region src/index.ts
335
+ /**
336
+ * Oxlint plugin entrypoint for `hook-o-gnese`.
337
+ *
338
+ * Exports the plugin as the default export and a `recommended` config that
339
+ * bundles the package's hook-complexity rules together with the type-aware
340
+ * tsgolint built-ins it relies on.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * // oxlint.config.ts
345
+ * import hookOGnese, { recommended } from "hook-o-gnese";
346
+ * export default { plugins: [hookOGnese], ...recommended };
347
+ * ```
348
+ *
349
+ * @module
350
+ */
335
351
  const plugin = eslintCompatPlugin({
336
352
  meta: { name: "hook-o-gnese" },
337
353
  rules: ALL_RULES
338
354
  });
355
+ /**
356
+ * Recommended oxlint configuration for `hook-o-gnese`.
357
+ *
358
+ * Enables every rule the plugin ships with sensible severities, plus the
359
+ * tsgolint type-aware rules required for full coverage. Spread into your
360
+ * oxlint config alongside the default plugin export.
361
+ */
339
362
  const recommended = {
340
363
  jsPlugins: ["./node_modules/hook-o-gnese/dist/index.mjs"],
341
364
  options: {
@@ -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","/**\n * Oxlint plugin entrypoint for `hook-o-gnese`.\n *\n * Exports the plugin as the default export and a `recommended` config that\n * bundles the package's hook-complexity rules together with the type-aware\n * tsgolint built-ins it relies on.\n *\n * @example\n * ```ts\n * // oxlint.config.ts\n * import hookOGnese, { recommended } from \"hook-o-gnese\";\n * export default { plugins: [hookOGnese], ...recommended };\n * ```\n *\n * @module\n */\n\nimport { 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\n/**\n * Recommended oxlint configuration for `hook-o-gnese`.\n *\n * Enables every rule the plugin ships with sensible severities, plus the\n * tsgolint type-aware rules required for full coverage. Spread into your\n * oxlint config alongside the default plugin export.\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\n/**\n * The `hook-o-gnese` oxlint plugin. Register as a plugin in your oxlint config\n * to enable React hook complexity rules.\n */\nexport default plugin;\n"],"x_google_ignoreList":[0],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hook-o-gnese",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Score React hook complexity. Runs as oxlint plugin or standalone CLI.",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine-D5X2INMx.mjs","names":[],"sources":["../src/engine.ts"],"sourcesContent":["import { parseSync } from \"oxc-parser\";\nimport { ALL_RULES } from \"./rules/registry.ts\";\n\nexport type Severity = \"off\" | \"warn\" | \"error\";\n\nexport interface Diagnostic {\n file: string;\n rule: string;\n severity: Exclude<Severity, \"off\">;\n message: string;\n line: number;\n column: number;\n endLine?: number;\n endColumn?: number;\n}\n\nexport interface RuleConfig {\n severity: Severity;\n options?: unknown;\n}\n\nexport interface EngineConfig {\n rules: Record<string, RuleConfig>;\n cwd: string;\n typeAware: boolean;\n}\n\nconst TYPE_AWARE_RULES = new Set([\"hook-o-gnese/custom-hook-depth\"]);\n\nfunction ruleNamespace(id: string): string {\n return id.replace(/^hook-o-gnese\\//, \"\");\n}\n\nfunction buildLineOffsets(source: string): number[] {\n const offsets = [0];\n for (let i = 0; i < source.length; i++) {\n if (source.charCodeAt(i) === 10) offsets.push(i + 1);\n }\n return offsets;\n}\n\nfunction offsetToLineCol(\n offset: number,\n lineOffsets: number[],\n): { line: number; column: number } {\n let lo = 0;\n let hi = lineOffsets.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1;\n if (lineOffsets[mid] <= offset) lo = mid;\n else hi = mid - 1;\n }\n return { line: lo + 1, column: offset - lineOffsets[lo] + 1 };\n}\n\nfunction getLoc(\n node: any,\n lineOffsets: number[],\n): { line: number; column: number; endLine?: number; endColumn?: number } {\n const startOffset = typeof node?.start === \"number\" ? node.start : undefined;\n const endOffset = typeof node?.end === \"number\" ? node.end : undefined;\n if (startOffset === undefined) {\n return { line: 1, column: 1 };\n }\n const start = offsetToLineCol(startOffset, lineOffsets);\n const end = endOffset !== undefined\n ? offsetToLineCol(endOffset, lineOffsets)\n : undefined;\n return {\n line: start.line,\n column: start.column,\n endLine: end?.line,\n endColumn: end?.column,\n };\n}\n\nfunction walkAST(node: any, handlers: Record<string, any>) {\n if (!node || typeof node !== \"object\") return;\n const enter = handlers[node.type];\n if (enter) enter(node);\n for (const key in node) {\n const v = node[key];\n if (Array.isArray(v)) {\n for (const c of v) walkAST(c, handlers);\n } else if (v && typeof v === \"object\") {\n walkAST(v, handlers);\n }\n }\n const exit = handlers[`${node.type}:exit`];\n if (exit) exit(node);\n}\n\nexport async function lintFile(\n filePath: string,\n source: string,\n config: EngineConfig,\n): Promise<Diagnostic[]> {\n const lang = filePath.endsWith(\".tsx\")\n ? \"tsx\"\n : filePath.endsWith(\".ts\")\n ? \"ts\"\n : filePath.endsWith(\".jsx\")\n ? \"jsx\"\n : \"js\";\n\n const parsed = parseSync(filePath, source, {\n lang,\n sourceType: \"module\",\n });\n\n const lineOffsets = buildLineOffsets(source);\n\n if (parsed.errors?.length) {\n return parsed.errors.map((e: any) => {\n const offset = typeof e.labels?.[0]?.start === \"number\"\n ? e.labels[0].start\n : undefined;\n const loc = offset !== undefined\n ? offsetToLineCol(offset, lineOffsets)\n : { line: 1, column: 1 };\n return {\n file: filePath,\n rule: \"parse-error\",\n severity: \"error\" as const,\n message: e.message ?? \"parse error\",\n line: loc.line,\n column: loc.column,\n };\n });\n }\n\n // Bail early on non-React files\n const imports = parsed.module?.staticImports ?? [];\n const hasReact = imports.some((i: any) =>\n (i.moduleRequest?.value ?? i.source?.value) === \"react\"\n );\n if (!hasReact) return [];\n\n const out: Diagnostic[] = [];\n\n for (const [ruleId, ruleCfg] of Object.entries(config.rules)) {\n if (ruleCfg.severity === \"off\") continue;\n if (!config.typeAware && TYPE_AWARE_RULES.has(ruleId)) continue;\n\n const rule = (ALL_RULES as any)[ruleNamespace(ruleId)];\n if (!rule) continue;\n\n const localDiags: Diagnostic[] = [];\n const context = {\n options: ruleCfg.options ? [ruleCfg.options] : [],\n filename: filePath,\n cwd: config.cwd,\n report(d: { message: string; node: any; severity?: \"warn\" | \"error\" }) {\n const loc = getLoc(d.node, lineOffsets);\n const cfgSev = ruleCfg.severity as \"warn\" | \"error\";\n // Rule-emitted severity only escalates (warn → error); never downgrades.\n const severity = d.severity === \"error\" ? \"error\" : cfgSev;\n localDiags.push({\n file: filePath,\n rule: ruleId,\n severity,\n message: d.message,\n ...loc,\n });\n },\n };\n\n const handlers = rule.create(context);\n walkAST(parsed.program, handlers);\n out.push(...localDiags);\n }\n\n return out;\n}\n\nexport type ReadTextFile = (path: string) => Promise<string>;\n\nexport async function lintFiles(\n filePaths: string[],\n config: EngineConfig,\n readTextFile: ReadTextFile,\n): Promise<Diagnostic[]> {\n const results = await Promise.all(\n filePaths.map(async (p) => {\n const src = await readTextFile(p);\n return lintFile(p, src, config);\n }),\n );\n return results.flat();\n}\n"],"mappings":"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"}