unguard 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-C4KGqeYw.js","names":[],"sources":["../src/scan/cache.ts","../src/scan/config.ts","../src/scan/discover.ts","../src/scan/baseline.ts","../src/scan/policy.ts","../src/engine.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Diagnostic, Rule } from \"../rules/types.ts\";\nimport type { Severity } from \"./types.ts\";\n\nconst CACHE_VERSION = 1;\nconst CACHE_FILENAME = \"scan-cache.json\";\n\nexport interface ScanCacheEntry {\n version: number;\n unguardVersion: string;\n /** Hash of active rules + ignore globs + paths + severity policy. Mismatch -> cache miss. */\n scanKey: string;\n /** `<size>:<mtimeNs>@<contentHash>` per file. Stat fast-path skips hashing when stat matches. */\n fileHashes: Record<string, string>;\n diagnostics: Diagnostic[];\n fileCount: number;\n}\n\nexport interface ScanKeyInput {\n unguardVersion: string;\n rules: readonly Rule[];\n paths: readonly string[];\n ignore: readonly string[];\n strict: boolean;\n failOn: string;\n showSeverities: ReadonlySet<Severity> | null;\n}\n\nexport interface CacheCheckContext {\n unguardVersion: string;\n scanKey: string;\n}\n\n/** Walk up from `startDir` to the nearest `node_modules`, returning its `.cache/unguard/v<N>` subdir. */\nexport function resolveCacheDir(startDir: string): string | null {\n let dir = startDir;\n for (;;) {\n const nm = join(dir, \"node_modules\");\n if (existsSync(nm)) return join(nm, \".cache\", \"unguard\", `v${CACHE_VERSION}`);\n const parent = join(dir, \"..\");\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport function computeScanKey(input: ScanKeyInput): string {\n const ruleSig = [...input.rules]\n .map((r) => `${r.id}=${r.severity}`)\n .sort()\n .join(\",\");\n const payload = JSON.stringify({\n unguardVersion: input.unguardVersion,\n ruleSig,\n paths: [...input.paths].sort(),\n ignore: [...input.ignore].sort(),\n strict: input.strict,\n failOn: input.failOn,\n showSeverities: input.showSeverities ? [...input.showSeverities].sort() : null,\n });\n return createHash(\"sha256\").update(payload).digest(\"hex\").slice(0, 16);\n}\n\nexport function hashFile(path: string, prevFingerprint: string | undefined): string {\n const s = statSync(path, { bigint: true });\n const statTag = `${s.size}:${s.mtimeNs.toString()}`;\n if (prevFingerprint !== undefined) {\n const prevStat = prevFingerprint.split(\"@\", 1)[0];\n if (prevStat === statTag) return prevFingerprint;\n }\n const buf = readFileSync(path);\n const contentHash = createHash(\"sha1\").update(buf).digest(\"hex\").slice(0, 16);\n return `${statTag}@${contentHash}`;\n}\n\nexport function hashFiles(paths: readonly string[], prev: Record<string, string> | null): Record<string, string> {\n const result: Record<string, string> = {};\n for (const path of paths) {\n result[path] = hashFile(path, prev?.[path]);\n }\n return result;\n}\n\nexport function readScanCache(dir: string): ScanCacheEntry | null {\n const path = join(dir, CACHE_FILENAME);\n if (!existsSync(path)) return null;\n\n const text = trySync(() => readFileSync(path, \"utf8\"), \"read failed\");\n if (text === null) return null;\n\n const raw = trySync(() => JSON.parse(text) as unknown, \"parse failed\");\n if (raw === null) return null;\n\n return validateScanCache(raw);\n}\n\nexport function writeScanCache(dir: string, entry: ScanCacheEntry): void {\n trySync(() => {\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, CACHE_FILENAME), JSON.stringify(entry));\n return true;\n }, \"write failed\");\n}\n\n/** Cache hit iff scan config matches and every file's content hash matches. Ignores stat. */\nexport function cacheCovers(\n cached: ScanCacheEntry,\n context: CacheCheckContext,\n currentHashes: Record<string, string>,\n): boolean {\n if (cached.unguardVersion !== context.unguardVersion) return false;\n if (cached.scanKey !== context.scanKey) return false;\n\n const cachedFiles = Object.keys(cached.fileHashes);\n const currentFiles = Object.keys(currentHashes);\n if (cachedFiles.length !== currentFiles.length) return false;\n\n for (const file of currentFiles) {\n const prev = cached.fileHashes[file];\n const cur = currentHashes[file];\n if (prev === undefined || cur === undefined) return false;\n if (contentHashOf(prev) !== contentHashOf(cur)) return false;\n }\n return true;\n}\n\nfunction contentHashOf(fingerprint: string): string {\n const idx = fingerprint.indexOf(\"@\");\n return idx >= 0 ? fingerprint.slice(idx + 1) : fingerprint;\n}\n\nfunction validateScanCache(raw: unknown): ScanCacheEntry | null {\n if (typeof raw !== \"object\" || raw === null) return null;\n const entry = raw as Record<string, unknown>;\n if (entry.version !== CACHE_VERSION) return null;\n if (typeof entry.scanKey !== \"string\") return null;\n if (typeof entry.unguardVersion !== \"string\") return null;\n if (typeof entry.fileCount !== \"number\") return null;\n\n const fileHashes = validateFileHashes(entry.fileHashes);\n if (fileHashes === null) return null;\n\n const diagnostics = validateDiagnostics(entry.diagnostics);\n if (diagnostics === null) return null;\n\n return {\n version: entry.version,\n unguardVersion: entry.unguardVersion,\n scanKey: entry.scanKey,\n fileHashes,\n diagnostics,\n fileCount: entry.fileCount,\n };\n}\n\nfunction validateFileHashes(raw: unknown): Record<string, string> | null {\n if (typeof raw !== \"object\" || raw === null) return null;\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (typeof value !== \"string\") return null;\n result[key] = value;\n }\n return result;\n}\n\nfunction validateDiagnostics(raw: unknown): Diagnostic[] | null {\n if (!Array.isArray(raw)) return null;\n const list: Diagnostic[] = [];\n for (const item of raw) {\n const diag = validateDiagnostic(item);\n if (diag === null) return null;\n list.push(diag);\n }\n return list;\n}\n\nfunction validateDiagnostic(item: unknown): Diagnostic | null {\n if (typeof item !== \"object\" || item === null) return null;\n if (!(\"file\" in item) || typeof item.file !== \"string\") return null;\n if (!(\"ruleId\" in item) || typeof item.ruleId !== \"string\") return null;\n if (!(\"line\" in item) || typeof item.line !== \"number\") return null;\n if (!(\"column\" in item) || typeof item.column !== \"number\") return null;\n if (!(\"severity\" in item)) return null;\n if (item.severity !== \"error\" && item.severity !== \"warning\" && item.severity !== \"info\") return null;\n if (!(\"message\" in item) || typeof item.message !== \"string\") return null;\n const annotation = \"annotation\" in item ? item.annotation : undefined;\n if (annotation !== undefined && typeof annotation !== \"string\") return null;\n return {\n file: item.file,\n ruleId: item.ruleId,\n line: item.line,\n column: item.column,\n severity: item.severity,\n message: item.message,\n annotation,\n };\n}\n\nfunction trySync<T>(fn: () => T, label: string): T | null {\n try {\n return fn();\n } catch (err) {\n return debugLog(label, err);\n }\n}\n\nfunction debugLog(label: string, err: unknown): null {\n if (process.env.UNGUARD_DEBUG !== undefined) {\n console.error(`[unguard] cache ${label}:`, err);\n }\n return null;\n}\n","import type {\n FailOn,\n ResolvedRuleOverride,\n ResolvedScanConfig,\n RuleOverride,\n RulePolicy,\n RulePolicyEntry,\n RulePolicySeverity,\n ScanOptions,\n Severity,\n} from \"./types.ts\";\n\nconst BUILTIN_IGNORE = [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/.git/**\",\n \"**/*.d.ts\",\n \"**/*.d.cts\",\n \"**/*.d.mts\",\n];\n\nconst GENERATED_IGNORE = [\n \"**/*.gen.*\",\n \"**/*.generated.*\",\n];\n\nconst DEFAULT_FAIL_ON: FailOn = \"info\";\n\nexport function resolveScanConfig(options: ScanOptions): ResolvedScanConfig {\n const paths = options.paths.length > 0 ? options.paths : [\".\"];\n const ignore = [...BUILTIN_IGNORE, ...GENERATED_IGNORE, ...(options.ignore ?? [])];\n return {\n paths,\n mode: options.mode ?? \"scan\",\n strict: options.strict ?? false,\n rules: options.rules ? [...options.rules] : null,\n ignore,\n rulePolicy: toRulePolicyEntries(options.rulePolicy),\n overrides: toResolvedOverrides(options.overrides),\n showSeverities: normalizeSeveritySet(options.showSeverities),\n failOn: options.failOn ?? DEFAULT_FAIL_ON,\n useGitIgnore: options.useGitIgnore ?? true,\n concurrency: options.concurrency,\n cache: options.cache ?? true,\n baseline: options.baseline ?? null,\n };\n}\n\nfunction toResolvedOverrides(overrides: RuleOverride[] | undefined): ResolvedRuleOverride[] {\n if (overrides === undefined) return [];\n return overrides.map((entry) => ({\n files: [...entry.files],\n rulePolicy: toRulePolicyEntries(entry.rules),\n }));\n}\n\nexport function toRulePolicyEntries(policy: RulePolicy | undefined): RulePolicyEntry[] {\n if (policy === undefined) return [];\n if (Array.isArray(policy)) return [...policy];\n\n const entries: RulePolicyEntry[] = [];\n for (const [selector, severity] of Object.entries(policy)) {\n entries.push({ selector, severity });\n }\n return entries;\n}\n\nfunction normalizeSeveritySet(levels: Severity[] | undefined): Set<Severity> | null {\n if (levels === undefined || levels.length === 0) return null;\n return new Set(levels);\n}\n\nexport function isRulePolicySeverity(value: string): value is RulePolicySeverity {\n return value === \"off\" || value === \"info\" || value === \"warning\" || value === \"error\";\n}\n\nexport function isSeverity(value: string): value is Severity {\n return value === \"info\" || value === \"warning\" || value === \"error\";\n}\n\nexport function isFailOn(value: string): value is FailOn {\n return value === \"none\" || isSeverity(value);\n}\n","import fg from \"fast-glob\";\nimport ignore from \"ignore\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { relative, resolve, sep } from \"node:path\";\nimport type { ResolvedScanConfig } from \"./types.ts\";\n\nexport async function discoverFiles(config: ResolvedScanConfig): Promise<string[]> {\n const globs = expandGlobs(config.paths);\n const discoveredFiles = await fg(globs, {\n ignore: config.ignore,\n absolute: true,\n });\n if (!config.useGitIgnore) return discoveredFiles;\n return applyGitIgnore(discoveredFiles);\n}\n\nfunction expandGlobs(paths: string[]): string[] {\n return paths.map((p) => {\n if (p === \".\") return \"./**/*.{ts,cts,mts,tsx}\";\n if (p.endsWith(\"/\")) return `${p}**/*.{ts,cts,mts,tsx}`;\n if (!p.includes(\"*\") && !p.endsWith(\".ts\") && !p.endsWith(\".tsx\") && !p.endsWith(\".cts\") && !p.endsWith(\".mts\")) {\n return `${p}/**/*.{ts,cts,mts,tsx}`;\n }\n return p;\n });\n}\n\nfunction applyGitIgnore(files: string[]): string[] {\n const gitIgnorePath = resolve(process.cwd(), \".gitignore\");\n if (!existsSync(gitIgnorePath)) return files;\n\n const matcher = ignore().add(readFileSync(gitIgnorePath, \"utf8\"));\n return files.filter((file) => {\n const rel = relative(process.cwd(), file);\n if (rel.startsWith(\"..\")) return true;\n const normalized = rel.split(sep).join(\"/\");\n return !matcher.ignores(normalized);\n });\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { relative, resolve } from \"node:path\";\nimport type { Diagnostic } from \"../rules/types.ts\";\n\nexport const BASELINE_FILENAME = \"unguard.baseline.json\";\n\n/**\n * Ratchet file: known-issue counts per (file, rule). A scan suppresses a\n * group while its count stays at or below the recorded number; the moment it\n * exceeds the baseline, the whole group surfaces — line numbers shift too\n * easily to pin individual diagnostics, counts only ratchet downward.\n */\nexport interface BaselineData {\n version: 1;\n rules: Record<string, Record<string, number>>;\n}\n\nexport function buildBaseline(diagnostics: Diagnostic[], cwd: string): BaselineData {\n const rules: Record<string, Record<string, number>> = {};\n for (const diagnostic of diagnostics) {\n const file = relative(cwd, diagnostic.file);\n rules[file] ??= {};\n const forFile = rules[file];\n forFile[diagnostic.ruleId] = (forFile[diagnostic.ruleId] ?? 0) + 1;\n }\n return { version: 1, rules };\n}\n\nexport function writeBaseline(baseline: BaselineData, cwd: string): string {\n const path = resolve(cwd, BASELINE_FILENAME);\n writeFileSync(path, `${JSON.stringify(baseline, null, 2)}\\n`, \"utf8\");\n return path;\n}\n\nexport function loadBaseline(cwd: string): BaselineData | null {\n const path = resolve(cwd, BASELINE_FILENAME);\n if (!existsSync(path)) return null;\n const parsed: unknown = JSON.parse(readFileSync(path, \"utf8\"));\n if (!isBaselineData(parsed)) {\n throw new Error(`Invalid baseline in ${BASELINE_FILENAME}: expected {\"version\": 1, \"rules\": {<file>: {<ruleId>: count}}}.`);\n }\n return parsed;\n}\n\nfunction isBaselineData(value: unknown): value is BaselineData {\n if (typeof value !== \"object\" || value === null) return false;\n if (!(\"version\" in value) || value.version !== 1) return false;\n if (!(\"rules\" in value) || typeof value.rules !== \"object\" || value.rules === null) return false;\n return Object.values(value.rules).every(\n (forFile) =>\n typeof forFile === \"object\" &&\n forFile !== null &&\n Object.values(forFile).every((count) => typeof count === \"number\"),\n );\n}\n\n/** Drop diagnostic groups still within their baselined count. */\nexport function applyBaseline(diagnostics: Diagnostic[], baseline: BaselineData, cwd: string): Diagnostic[] {\n const groups = new Map<string, Diagnostic[]>();\n for (const diagnostic of diagnostics) {\n const key = `${relative(cwd, diagnostic.file)}\\0${diagnostic.ruleId}`;\n let list = groups.get(key);\n if (list === undefined) {\n list = [];\n groups.set(key, list);\n }\n list.push(diagnostic);\n }\n\n const kept: Diagnostic[] = [];\n for (const [key, group] of groups) {\n const [file, ruleId] = key.split(\"\\0\") as [string, string];\n const allowed = baseline.rules[file]?.[ruleId] ?? 0;\n if (group.length <= allowed) continue;\n kept.push(...group);\n }\n return kept;\n}\n","import { relative } from \"node:path\";\nimport ignore from \"ignore\";\nimport type { Diagnostic, Rule } from \"../rules/types.ts\";\nimport { getRuleMetadata } from \"../rules/index.ts\";\nimport { applyBaseline } from \"./baseline.ts\";\nimport type {\n ResolvedRuleOverride,\n ResolvedScanConfig,\n RuleDescriptor,\n RulePolicySeverity,\n ScanExecutionResult,\n ScanResult,\n Severity,\n} from \"./types.ts\";\n\nexport function buildRuleDescriptors(rules: Rule[]): RuleDescriptor[] {\n return rules.map((rule) => {\n const metadata = getRuleMetadata(rule.id);\n return {\n rule,\n category: metadata.category,\n tags: metadata.tags,\n confidence: metadata.confidence,\n };\n });\n}\n\nexport function resolveActiveRules(\n descriptors: RuleDescriptor[],\n config: ResolvedScanConfig,\n): Rule[] {\n const selectedRules = config.rules ? new Set(config.rules) : null;\n const wantedConfidence = config.mode === \"audit\" ? \"heuristic\" : \"proven\";\n const active: Rule[] = [];\n\n for (const descriptor of descriptors) {\n if (selectedRules) {\n // An explicit rule filter names what to run; the mode split is moot.\n if (!selectedRules.has(descriptor.rule.id)) continue;\n } else if (descriptor.confidence !== wantedConfidence) {\n continue;\n }\n const resolvedSeverity = resolveRuleSeverity(descriptor, config);\n if (resolvedSeverity === \"off\") continue;\n if (descriptor.rule.severity === resolvedSeverity) {\n active.push(descriptor.rule);\n continue;\n }\n active.push({ ...descriptor.rule, severity: resolvedSeverity });\n }\n\n return active;\n}\n\nfunction resolveRuleSeverity(descriptor: RuleDescriptor, config: ResolvedScanConfig): RulePolicySeverity {\n let severity: RulePolicySeverity = descriptor.rule.severity;\n for (const entry of config.rulePolicy) {\n if (!matchesSelector(descriptor, entry.selector)) continue;\n severity = entry.severity;\n }\n\n if (severity === \"off\") return \"off\";\n if (config.strict) return \"error\";\n return severity;\n}\n\nfunction matchesSelector(\n descriptor: Pick<RuleDescriptor, \"category\" | \"tags\" | \"confidence\"> & { rule: { id: string } },\n selector: string,\n): boolean {\n if (selector.startsWith(\"category:\")) {\n return descriptor.category === selector.slice(\"category:\".length);\n }\n if (selector.startsWith(\"tag:\")) {\n return descriptor.tags.includes(selector.slice(\"tag:\".length));\n }\n if (selector.startsWith(\"confidence:\")) {\n return descriptor.confidence === selector.slice(\"confidence:\".length);\n }\n if (selector === descriptor.rule.id) return true;\n if (!selector.includes(\"*\")) return false;\n const regex = new RegExp(`^${escapeRegex(selector).replaceAll(\"\\\\*\", \".*\")}$`);\n return regex.test(descriptor.rule.id);\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function finalizeScanResult(\n diagnostics: Diagnostic[],\n fileCount: number,\n config: ResolvedScanConfig,\n): ScanExecutionResult {\n // `diagnostics` stays raw (and cached raw); overrides and the baseline\n // ratchet only shape what is reported and what fails the run.\n const afterOverrides = applyOverrides(diagnostics, config.overrides, process.cwd());\n const effective = config.baseline\n ? applyBaseline(afterOverrides, config.baseline, process.cwd())\n : afterOverrides;\n const visibleDiagnostics = config.showSeverities\n ? effective.filter((d) => config.showSeverities?.has(d.severity))\n : effective;\n\n return {\n diagnostics,\n visibleDiagnostics,\n fileCount,\n exitCode: computeExitCode(effective, config.failOn),\n };\n}\n\n/**\n * Re-resolve severities per file: each override whose globs (gitignore\n * syntax, matched against the cwd-relative path) cover the diagnostic's file\n * applies its rule policy on top of the already-resolved severity. `off`\n * drops the diagnostic.\n */\nexport function applyOverrides(\n diagnostics: Diagnostic[],\n overrides: ResolvedRuleOverride[],\n cwd: string,\n): Diagnostic[] {\n if (overrides.length === 0) return diagnostics;\n const matchers = overrides.map((entry) => ({\n matcher: ignore().add(entry.files),\n rulePolicy: entry.rulePolicy,\n }));\n\n const result: Diagnostic[] = [];\n for (const diagnostic of diagnostics) {\n const relPath = relative(cwd, diagnostic.file).replaceAll(\"\\\\\", \"/\");\n const metadata = getRuleMetadata(diagnostic.ruleId);\n const descriptor = {\n rule: { id: diagnostic.ruleId },\n category: metadata.category,\n tags: metadata.tags,\n confidence: metadata.confidence,\n };\n\n let severity: RulePolicySeverity = diagnostic.severity;\n for (const { matcher, rulePolicy } of matchers) {\n if (relPath.startsWith(\"..\") || !matcher.ignores(relPath)) continue;\n for (const entry of rulePolicy) {\n if (!matchesSelector(descriptor, entry.selector)) continue;\n severity = entry.severity;\n }\n }\n\n if (severity === \"off\") continue;\n result.push(severity === diagnostic.severity ? diagnostic : { ...diagnostic, severity });\n }\n return result;\n}\n\nexport function toScanResult(execution: ScanExecutionResult): ScanResult {\n return {\n diagnostics: execution.diagnostics,\n fileCount: execution.fileCount,\n };\n}\n\nexport function computeExitCode(diagnostics: Diagnostic[], failOn: \"none\" | Severity): number {\n if (failOn === \"none\") return 0;\n\n const hasError = diagnostics.some((d) => d.severity === \"error\");\n const hasWarning = diagnostics.some((d) => d.severity === \"warning\");\n const hasInfo = diagnostics.some((d) => d.severity === \"info\");\n\n if (failOn === \"error\") return hasError ? 2 : 0;\n if (failOn === \"warning\") {\n if (hasError) return 2;\n return hasWarning ? 1 : 0;\n }\n\n if (hasError) return 2;\n if (hasWarning || hasInfo) return 1;\n return 0;\n}\n","import { availableParallelism } from \"node:os\";\nimport { createRequire } from \"node:module\";\nimport { allRules } from \"./rules/index.ts\";\nimport { analyzeFiles } from \"./scan/analyze.ts\";\nimport { cacheCovers, computeScanKey, hashFiles, readScanCache, resolveCacheDir, writeScanCache } from \"./scan/cache.ts\";\nimport { resolveScanConfig } from \"./scan/config.ts\";\nimport { discoverFiles } from \"./scan/discover.ts\";\nimport { buildRuleDescriptors, finalizeScanResult, resolveActiveRules, toScanResult } from \"./scan/policy.ts\";\nimport type { ScanExecutionResult, ScanOptions, ScanResult } from \"./scan/types.ts\";\n\nexport type {\n FailOn,\n RulePolicy,\n RulePolicyEntry,\n RulePolicySeverity,\n ScanExecutionResult,\n ScanOptions,\n ScanResult,\n Severity,\n} from \"./scan/types.ts\";\n\nconst UNGUARD_VERSION: string = createRequire(import.meta.url)(\"../package.json\").version;\n\nexport async function executeScan(options: ScanOptions): Promise<ScanExecutionResult> {\n const config = resolveScanConfig(options);\n const files = await discoverFiles(config);\n const descriptors = buildRuleDescriptors(allRules);\n const activeRules = resolveActiveRules(descriptors, config);\n\n const cacheDir = config.cache ? resolveCacheDir(process.cwd()) : null;\n if (cacheDir !== null) {\n const scanKey = computeScanKey({\n unguardVersion: UNGUARD_VERSION,\n rules: activeRules,\n paths: config.paths,\n ignore: config.ignore,\n strict: config.strict,\n failOn: config.failOn,\n showSeverities: config.showSeverities,\n });\n const cached = readScanCache(cacheDir);\n const sortedFiles = [...files].sort();\n const currentHashes = hashFiles(sortedFiles, cached?.fileHashes ?? null);\n\n if (cached !== null && cacheCovers(cached, { unguardVersion: UNGUARD_VERSION, scanKey }, currentHashes)) {\n return finalizeScanResult(cached.diagnostics, cached.fileCount, config);\n }\n\n const concurrency = resolveDefaultConcurrency(config.concurrency);\n const diagnostics = await analyzeFiles(files, activeRules, { concurrency });\n writeScanCache(cacheDir, {\n version: 1,\n unguardVersion: UNGUARD_VERSION,\n scanKey,\n fileHashes: currentHashes,\n diagnostics,\n fileCount: files.length,\n });\n return finalizeScanResult(diagnostics, files.length, config);\n }\n\n const concurrency = resolveDefaultConcurrency(config.concurrency);\n const diagnostics = await analyzeFiles(files, activeRules, { concurrency });\n return finalizeScanResult(diagnostics, files.length, config);\n}\n\nfunction resolveDefaultConcurrency(requested: number | undefined): number {\n if (requested !== undefined) {\n if (!Number.isFinite(requested) || requested < 1) return 1;\n return Math.floor(requested);\n }\n // Each worker holds its own ts.Program; cap at half the CPUs to avoid memory blow-up on shared CI.\n const cores = availableParallelism();\n return Math.min(8, Math.max(1, Math.ceil(cores / 2)));\n}\n\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\n const execution = await executeScan(options);\n return toScanResult(execution);\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,80 +1,84 @@
1
- import * as ts from 'typescript';
2
-
1
+ import * as ts from "typescript";
2
+ //#region src/collect/base-registry.d.ts
3
3
  declare class BaseRegistry<T> {
4
- protected entries: T[];
5
- protected byHash: Map<string, T[]>;
6
- protected addEntry(entry: T, hash: string): void;
7
- getDuplicateGroups(): T[][];
8
- getAll(): T[];
4
+ protected entries: T[];
5
+ protected byHash: Map<string, T[]>;
6
+ protected addEntry(entry: T, hash: string): void;
7
+ getDuplicateGroups(): T[][];
8
+ getAll(): T[];
9
9
  }
10
10
  declare class DualHashRegistry<T> extends BaseRegistry<T> {
11
- private bySecondaryHash;
12
- protected addWithSecondary(entry: T, primaryHash: string, secondaryHash: string): void;
13
- getSecondaryDuplicateGroups(): T[][];
11
+ private bySecondaryHash;
12
+ protected addWithSecondary(entry: T, primaryHash: string, secondaryHash: string): void;
13
+ getSecondaryDuplicateGroups(): T[][];
14
14
  }
15
-
15
+ //#endregion
16
+ //#region src/collect/type-registry.d.ts
16
17
  interface TypeEntry {
17
- name: string;
18
- file: string;
19
- line: number;
20
- column: number;
21
- hash: string;
22
- node: ts.Node;
23
- exported: boolean;
18
+ name: string;
19
+ file: string;
20
+ line: number;
21
+ column: number;
22
+ hash: string;
23
+ node: ts.Node;
24
+ exported: boolean;
24
25
  }
25
26
  declare class TypeRegistry extends BaseRegistry<TypeEntry> {
26
- add(name: string, file: string, line: number, column: number, typeNode: ts.Node, sourceFile: ts.SourceFile, exported: boolean): void;
27
- getNameCollisionGroups(): TypeEntry[][];
27
+ add(name: string, file: string, line: number, column: number, typeNode: ts.Node, sourceFile: ts.SourceFile, exported: boolean): void;
28
+ getNameCollisionGroups(): TypeEntry[][];
28
29
  }
29
-
30
+ //#endregion
31
+ //#region src/collect/function-registry.d.ts
30
32
  interface ParamInfo {
31
- name: string;
32
- optional: boolean;
33
- hasDefault: boolean;
34
- typeText: string | null;
33
+ name: string;
34
+ optional: boolean;
35
+ hasDefault: boolean;
36
+ typeText: string | null;
35
37
  }
36
38
  interface FunctionEntry {
37
- name: string;
38
- file: string;
39
- line: number;
40
- column: number;
41
- hash: string;
42
- normalizedHash: string;
43
- params: ParamInfo[];
44
- node: ts.Node;
45
- exported: boolean;
46
- /** True when the declaration carries the `default` modifier (export default) */
47
- exportedAsDefault?: boolean;
48
- symbol?: ts.Symbol;
49
- /** Whitespace-normalized body text length (for trivial-body filtering) */
50
- bodyLength: number;
51
- /** Fully-normalized body text length (strings/numbers/params replaced) */
52
- normalizedBodyLength: number;
53
- /** Class name for class methods (e.g. "Foo" for "Foo.bar"), undefined for standalone functions */
54
- className?: string;
55
- /** True if the declaring class has an `implements` clause */
56
- implementsInterface?: boolean;
39
+ name: string;
40
+ file: string;
41
+ line: number;
42
+ column: number;
43
+ hash: string;
44
+ normalizedHash: string;
45
+ params: ParamInfo[];
46
+ node: ts.Node;
47
+ exported: boolean;
48
+ /** True when the declaration carries the `default` modifier (export default) */
49
+ exportedAsDefault?: boolean;
50
+ symbol?: ts.Symbol;
51
+ /** Whitespace-normalized body text length (for trivial-body filtering) */
52
+ bodyLength: number;
53
+ /** Fully-normalized body text length (strings/numbers/params replaced) */
54
+ normalizedBodyLength: number;
55
+ /** Class name for class methods (e.g. "Foo" for "Foo.bar"), undefined for standalone functions */
56
+ className?: string;
57
+ /** True if the declaring class has an `implements` clause */
58
+ implementsInterface?: boolean;
57
59
  }
58
60
  declare class FunctionRegistry extends DualHashRegistry<FunctionEntry> {
59
- add(entry: FunctionEntry): void;
60
- getNearDuplicateGroups(): FunctionEntry[][];
61
- getByName(name: string): FunctionEntry[];
62
- getNameCollisionGroups(): FunctionEntry[][];
61
+ add(entry: FunctionEntry): void;
62
+ getNearDuplicateGroups(): FunctionEntry[][];
63
+ getByName(name: string): FunctionEntry[];
64
+ getNameCollisionGroups(): FunctionEntry[][];
63
65
  }
64
-
66
+ //#endregion
67
+ //#region src/collect/constant-registry.d.ts
65
68
  interface ConstantEntry {
66
- name: string;
67
- file: string;
68
- line: number;
69
- column: number;
70
- valueHash: string;
71
- valueText: string;
72
- exported: boolean;
69
+ name: string;
70
+ file: string;
71
+ line: number;
72
+ column: number;
73
+ valueHash: string;
74
+ valueText: string;
75
+ exported: boolean;
73
76
  }
74
77
  declare class ConstantRegistry extends BaseRegistry<ConstantEntry> {
75
- add(entry: ConstantEntry): void;
78
+ add(entry: ConstantEntry): void;
76
79
  }
77
-
80
+ //#endregion
81
+ //#region src/collect/statement-sequence-registry.d.ts
78
82
  /**
79
83
  * Detect maximal duplicated runs of consecutive statements across all collected
80
84
  * blocks. "Maximal" means the run can't be extended in either direction without
@@ -101,108 +105,111 @@ declare class ConstantRegistry extends BaseRegistry<ConstantEntry> {
101
105
  * complete anchor.
102
106
  */
103
107
  interface StatementInBlock {
104
- hash: string;
105
- line: number;
106
- column: number;
107
- endLine: number;
108
- normalizedLength: number;
108
+ hash: string;
109
+ line: number;
110
+ column: number;
111
+ endLine: number;
112
+ normalizedLength: number;
109
113
  }
110
114
  interface MaximalMatchParticipant {
111
- file: string;
112
- line: number;
113
- column: number;
114
- endLine: number;
115
- /** Number of statements in this match (same for every participant). */
116
- statementCount: number;
115
+ file: string;
116
+ line: number;
117
+ column: number;
118
+ endLine: number;
119
+ /** Number of statements in this match (same for every participant). */
120
+ statementCount: number;
117
121
  }
118
122
  interface MaximalMatch {
119
- participants: MaximalMatchParticipant[];
120
- statementCount: number;
121
- /** Sum of per-statement normalized text lengths across the matched run. */
122
- normalizedBodyLength: number;
123
+ participants: MaximalMatchParticipant[];
124
+ statementCount: number;
125
+ /** Sum of per-statement normalized text lengths across the matched run. */
126
+ normalizedBodyLength: number;
123
127
  }
124
128
  declare class StatementSequenceRegistry {
125
- private blocks;
126
- private byHash;
127
- addBlock(file: string, stmts: StatementInBlock[]): void;
128
- getMaximalMatches(minStatementCount: number, minNormalizedBodyLength: number): MaximalMatch[];
129
- private findMatchesFromAnchor;
130
- }
131
-
129
+ private blocks;
130
+ private byHash;
131
+ addBlock(file: string, stmts: StatementInBlock[]): void;
132
+ getMaximalMatches(minStatementCount: number, minNormalizedBodyLength: number): MaximalMatch[];
133
+ private findMatchesFromAnchor;
134
+ }
135
+ //#endregion
136
+ //#region src/collect/inline-type-registry.d.ts
132
137
  interface InlineParamTypeEntry {
133
- file: string;
134
- line: number;
135
- column: number;
136
- hash: string;
137
- typeText: string;
138
- node: ts.Node;
138
+ file: string;
139
+ line: number;
140
+ column: number;
141
+ hash: string;
142
+ typeText: string;
143
+ node: ts.Node;
139
144
  }
140
145
  declare class InlineParamTypeRegistry extends BaseRegistry<InlineParamTypeEntry> {
141
- add(file: string, line: number, column: number, typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): void;
146
+ add(file: string, line: number, column: number, typeNode: ts.TypeLiteralNode, sourceFile: ts.SourceFile): void;
142
147
  }
143
-
148
+ //#endregion
149
+ //#region src/collect/index.d.ts
144
150
  interface ProjectIndex {
145
- types: TypeRegistry;
146
- functions: FunctionRegistry;
147
- constants: ConstantRegistry;
148
- callSites: CallSite[];
149
- imports: ImportEntry[];
150
- files: Map<string, {
151
- source: string;
152
- sourceFile: ts.SourceFile;
153
- }>;
154
- /** Whitespace-normalized file content hash -> list of file paths */
155
- fileHashes: Map<string, string[]>;
156
- statementSequences: StatementSequenceRegistry;
157
- inlineParamTypes: InlineParamTypeRegistry;
151
+ types: TypeRegistry;
152
+ functions: FunctionRegistry;
153
+ constants: ConstantRegistry;
154
+ callSites: CallSite[];
155
+ imports: ImportEntry[];
156
+ files: Map<string, {
157
+ source: string;
158
+ sourceFile: ts.SourceFile;
159
+ }>;
160
+ /** Whitespace-normalized file content hash -> list of file paths */
161
+ fileHashes: Map<string, string[]>;
162
+ statementSequences: StatementSequenceRegistry;
163
+ inlineParamTypes: InlineParamTypeRegistry;
158
164
  }
159
165
  interface CallSite {
160
- calleeName: string;
161
- file: string;
162
- line: number;
163
- argCount: number;
164
- node: ts.CallExpression;
165
- symbol?: ts.Symbol;
166
- resolvedDeclaration?: ts.SignatureDeclaration | ts.JSDocSignature;
166
+ calleeName: string;
167
+ file: string;
168
+ line: number;
169
+ argCount: number;
170
+ node: ts.CallExpression;
171
+ symbol?: ts.Symbol;
172
+ resolvedDeclaration?: ts.SignatureDeclaration | ts.JSDocSignature;
167
173
  }
168
174
  interface ImportEntry {
169
- file: string;
170
- localName: string;
171
- importedName: string;
172
- source: string;
173
- /**
174
- * The module specifier resolved to a project source file via the checker.
175
- * Handles tsconfig path aliases and workspace packages that textual
176
- * resolution cannot. Absent in source-only mode or when the specifier
177
- * resolves outside the program (npm package, declaration file).
178
- */
179
- resolvedFile?: string;
180
- }
181
-
175
+ file: string;
176
+ localName: string;
177
+ importedName: string;
178
+ source: string;
179
+ /**
180
+ * The module specifier resolved to a project source file via the checker.
181
+ * Handles tsconfig path aliases and workspace packages that textual
182
+ * resolution cannot. Absent in source-only mode or when the specifier
183
+ * resolves outside the program (npm package, declaration file).
184
+ */
185
+ resolvedFile?: string;
186
+ }
187
+ //#endregion
188
+ //#region src/rules/types.d.ts
182
189
  interface SemanticServices {
183
- checker: ts.TypeChecker;
184
- typeAtLocation(node: ts.Node): ts.Type;
185
- symbolAtLocation(node: ts.Node): ts.Symbol | undefined;
186
- resolvedSignature(node: ts.CallLikeExpression): ts.Signature | undefined;
187
- typeFromTypeNode(node: ts.TypeNode): ts.Type;
188
- contextualType(node: ts.Expression): ts.Type | undefined;
189
- typeOfSymbolAtLocation(symbol: ts.Symbol, node: ts.Node): ts.Type;
190
- aliasedSymbol(symbol: ts.Symbol): ts.Symbol;
191
- awaitedType(type: ts.Type): ts.Type | undefined;
192
- apparentType(type: ts.Type): ts.Type;
193
- isArrayType(type: ts.Type): boolean;
194
- isTupleType(type: ts.Type): boolean;
195
- isTypeAssignableTo(source: ts.Type, target: ts.Type): boolean;
190
+ checker: ts.TypeChecker;
191
+ typeAtLocation(node: ts.Node): ts.Type;
192
+ symbolAtLocation(node: ts.Node): ts.Symbol | undefined;
193
+ resolvedSignature(node: ts.CallLikeExpression): ts.Signature | undefined;
194
+ typeFromTypeNode(node: ts.TypeNode): ts.Type;
195
+ contextualType(node: ts.Expression): ts.Type | undefined;
196
+ typeOfSymbolAtLocation(symbol: ts.Symbol, node: ts.Node): ts.Type;
197
+ aliasedSymbol(symbol: ts.Symbol): ts.Symbol;
198
+ awaitedType(type: ts.Type): ts.Type | undefined;
199
+ apparentType(type: ts.Type): ts.Type;
200
+ isArrayType(type: ts.Type): boolean;
201
+ isTupleType(type: ts.Type): boolean;
202
+ isTypeAssignableTo(source: ts.Type, target: ts.Type): boolean;
196
203
  }
197
204
  interface Diagnostic {
198
- ruleId: string;
199
- severity: "info" | "warning" | "error";
200
- message: string;
201
- file: string;
202
- line: number;
203
- column: number;
204
- annotation?: string;
205
- fix?: FixEdit;
205
+ ruleId: string;
206
+ severity: "info" | "warning" | "error";
207
+ message: string;
208
+ file: string;
209
+ line: number;
210
+ column: number;
211
+ annotation?: string;
212
+ fix?: FixEdit;
206
213
  }
207
214
  /**
208
215
  * A single text replacement with absolute offsets into the file's source.
@@ -210,65 +217,65 @@ interface Diagnostic {
210
217
  * `--fix` applies these mechanically.
211
218
  */
212
219
  interface FixEdit {
213
- start: number;
214
- end: number;
215
- text: string;
220
+ start: number;
221
+ end: number;
222
+ text: string;
216
223
  }
217
224
  interface TSVisitContext {
218
- report(node: ts.Node, message?: string, fix?: FixEdit): void;
219
- reportAtOffset(offset: number, message?: string): void;
220
- filename: string;
221
- source: string;
222
- sourceFile: ts.SourceFile;
223
- checker: ts.TypeChecker;
224
- semantics: SemanticServices;
225
- compilerOptions: ts.CompilerOptions;
226
- isNullable(node: ts.Node): boolean;
227
- isExternal(node: ts.Node): boolean;
225
+ report(node: ts.Node, message?: string, fix?: FixEdit): void;
226
+ reportAtOffset(offset: number, message?: string): void;
227
+ filename: string;
228
+ source: string;
229
+ sourceFile: ts.SourceFile;
230
+ checker: ts.TypeChecker;
231
+ semantics: SemanticServices;
232
+ compilerOptions: ts.CompilerOptions;
233
+ isNullable(node: ts.Node): boolean;
234
+ isExternal(node: ts.Node): boolean;
228
235
  }
229
236
  interface TSRule {
230
- kind: "ts";
231
- id: string;
232
- severity: "info" | "warning" | "error";
233
- message: string;
234
- /** Restrict visits to these node kinds. Omit only for rules that truly need every node. */
235
- syntaxKinds?: readonly ts.SyntaxKind[];
236
- /** Defaults to true. Set false only when the rule never touches checker/isNullable/isExternal. */
237
- requiresTypeInfo?: boolean;
238
- /**
239
- * Set true when the rule's reasoning collapses without `strictNullChecks`:
240
- * every type reads as non-nullable, inverting "this guard is dead" into
241
- * advice to delete load-bearing guards. Such rules are skipped (with a
242
- * notice) when the analyzed project has strictNullChecks off.
243
- */
244
- requiresStrictNullChecks?: boolean;
245
- visit(node: ts.Node, ctx: TSVisitContext): void;
246
- }
247
-
237
+ kind: "ts";
238
+ id: string;
239
+ severity: "info" | "warning" | "error";
240
+ message: string;
241
+ /** Restrict visits to these node kinds. Omit only for rules that truly need every node. */
242
+ syntaxKinds?: readonly ts.SyntaxKind[];
243
+ /** Defaults to true. Set false only when the rule never touches checker/isNullable/isExternal. */
244
+ requiresTypeInfo?: boolean;
245
+ /**
246
+ * Set true when the rule's reasoning collapses without `strictNullChecks`:
247
+ * every type reads as non-nullable, inverting "this guard is dead" into
248
+ * advice to delete load-bearing guards. Such rules are skipped (with a
249
+ * notice) when the analyzed project has strictNullChecks off.
250
+ */
251
+ requiresStrictNullChecks?: boolean;
252
+ visit(node: ts.Node, ctx: TSVisitContext): void;
253
+ }
248
254
  type ProjectIndexNeed = "files" | "types" | "functions" | "functionSymbols" | "constants" | "callSites" | "callSiteSymbols" | "overloadCallSignatures" | "imports" | "fileHashes" | "statementSequences" | "inlineParamTypes";
249
255
  interface CrossFileRule {
250
- id: string;
251
- severity: "info" | "warning" | "error";
252
- message: string;
253
- requires?: readonly ProjectIndexNeed[];
254
- analyze(project: ProjectIndex, context?: CrossFileAnalysisContext): Diagnostic[];
255
- /**
256
- * Cross-group merge, both hooks or neither. Analysis runs one tsconfig
257
- * group at a time; a rule that would mistake another group's usage for
258
- * absence (e.g. unused-export in a monorepo) implements these instead of
259
- * relying on `analyze` alone. `collectGlobalFacts` runs per group — possibly
260
- * in a worker, so facts must survive structuredClone (no ts.Node/ts.Symbol).
261
- * `finalizeGlobal` runs once on the main thread with every group's facts.
262
- */
263
- collectGlobalFacts?(project: ProjectIndex, context?: CrossFileAnalysisContext): unknown;
264
- finalizeGlobal?(facts: unknown[]): Diagnostic[];
256
+ id: string;
257
+ severity: "info" | "warning" | "error";
258
+ message: string;
259
+ requires?: readonly ProjectIndexNeed[];
260
+ analyze(project: ProjectIndex, context?: CrossFileAnalysisContext): Diagnostic[];
261
+ /**
262
+ * Cross-group merge, both hooks or neither. Analysis runs one tsconfig
263
+ * group at a time; a rule that would mistake another group's usage for
264
+ * absence (e.g. unused-export in a monorepo) implements these instead of
265
+ * relying on `analyze` alone. `collectGlobalFacts` runs per group — possibly
266
+ * in a worker, so facts must survive structuredClone (no ts.Node/ts.Symbol).
267
+ * `finalizeGlobal` runs once on the main thread with every group's facts.
268
+ */
269
+ collectGlobalFacts?(project: ProjectIndex, context?: CrossFileAnalysisContext): unknown;
270
+ finalizeGlobal?(facts: unknown[]): Diagnostic[];
265
271
  }
266
272
  interface CrossFileAnalysisContext {
267
- /** Files explicitly requested by the scan; project context outside this set is non-reportable. */
268
- reportableFiles?: ReadonlySet<string>;
273
+ /** Files explicitly requested by the scan; project context outside this set is non-reportable. */
274
+ reportableFiles?: ReadonlySet<string>;
269
275
  }
270
276
  type Rule = CrossFileRule | TSRule;
271
-
277
+ //#endregion
278
+ //#region src/rules/index.d.ts
272
279
  type RuleCategory = "type-evasion" | "defensive-code" | "error-handling" | "interface-design" | "cross-file" | "imports";
273
280
  /**
274
281
  * The epistemic tier of a rule, orthogonal to severity.
@@ -286,13 +293,14 @@ type RuleCategory = "type-evasion" | "defensive-code" | "error-handling" | "inte
286
293
  */
287
294
  type RuleConfidence = "proven" | "heuristic";
288
295
  interface RuleMetadata {
289
- category: RuleCategory;
290
- tags: string[];
291
- confidence: RuleConfidence;
296
+ category: RuleCategory;
297
+ tags: string[];
298
+ confidence: RuleConfidence;
292
299
  }
293
300
  declare const allRules: Rule[];
294
301
  declare function getRuleMetadata(ruleId: string): RuleMetadata;
295
-
302
+ //#endregion
303
+ //#region src/scan/baseline.d.ts
296
304
  /**
297
305
  * Ratchet file: known-issue counts per (file, rule). A scan suppresses a
298
306
  * group while its count stays at or below the recorded number; the moment it
@@ -300,10 +308,11 @@ declare function getRuleMetadata(ruleId: string): RuleMetadata;
300
308
  * easily to pin individual diagnostics, counts only ratchet downward.
301
309
  */
302
310
  interface BaselineData {
303
- version: 1;
304
- rules: Record<string, Record<string, number>>;
311
+ version: 1;
312
+ rules: Record<string, Record<string, number>>;
305
313
  }
306
-
314
+ //#endregion
315
+ //#region src/scan/types.d.ts
307
316
  type Severity = Diagnostic["severity"];
308
317
  type RulePolicySeverity = Severity | "off";
309
318
  type FailOn = "none" | Severity;
@@ -314,44 +323,45 @@ type FailOn = "none" | Severity;
314
323
  */
315
324
  type ScanMode = "scan" | "audit";
316
325
  interface RulePolicyEntry {
317
- selector: string;
318
- severity: RulePolicySeverity;
326
+ selector: string;
327
+ severity: RulePolicySeverity;
319
328
  }
320
329
  type RulePolicy = Record<string, RulePolicySeverity> | RulePolicyEntry[];
321
330
  /** Per-path rule policy: applies to diagnostics in files matching the globs. */
322
331
  interface RuleOverride {
323
- files: string[];
324
- rules: RulePolicy;
332
+ files: string[];
333
+ rules: RulePolicy;
325
334
  }
326
335
  interface ScanOptions {
327
- paths: string[];
328
- mode?: ScanMode;
329
- strict?: boolean;
330
- rules?: string[];
331
- ignore?: string[];
332
- rulePolicy?: RulePolicy;
333
- /** Path-scoped rule policies, applied after the global policy. */
334
- overrides?: RuleOverride[];
335
- showSeverities?: Severity[];
336
- failOn?: FailOn;
337
- useGitIgnore?: boolean;
338
- /** Worker threads for tsconfig groups. Auto by default; 1 disables. */
339
- concurrency?: number;
340
- /** On-disk diagnostic cache under `node_modules/.cache/unguard/`. Default: true. */
341
- cache?: boolean;
342
- /** Known-issue ratchet: suppress (file, rule) groups within baselined counts. */
343
- baseline?: BaselineData;
336
+ paths: string[];
337
+ mode?: ScanMode;
338
+ strict?: boolean;
339
+ rules?: string[];
340
+ ignore?: string[];
341
+ rulePolicy?: RulePolicy;
342
+ /** Path-scoped rule policies, applied after the global policy. */
343
+ overrides?: RuleOverride[];
344
+ showSeverities?: Severity[];
345
+ failOn?: FailOn;
346
+ useGitIgnore?: boolean;
347
+ /** Worker threads for tsconfig groups. Auto by default; 1 disables. */
348
+ concurrency?: number;
349
+ /** On-disk diagnostic cache under `node_modules/.cache/unguard/`. Default: true. */
350
+ cache?: boolean;
351
+ /** Known-issue ratchet: suppress (file, rule) groups within baselined counts. */
352
+ baseline?: BaselineData;
344
353
  }
345
354
  interface ScanResult {
346
- diagnostics: Diagnostic[];
347
- fileCount: number;
355
+ diagnostics: Diagnostic[];
356
+ fileCount: number;
348
357
  }
349
358
  interface ScanExecutionResult extends ScanResult {
350
- visibleDiagnostics: Diagnostic[];
351
- exitCode: number;
359
+ visibleDiagnostics: Diagnostic[];
360
+ exitCode: number;
352
361
  }
353
-
362
+ //#endregion
363
+ //#region src/engine.d.ts
354
364
  declare function executeScan(options: ScanOptions): Promise<ScanExecutionResult>;
355
365
  declare function scan(options: ScanOptions): Promise<ScanResult>;
356
-
366
+ //#endregion
357
367
  export { type CrossFileRule, type Diagnostic, type Rule, type RuleCategory, type RuleMetadata, type ScanExecutionResult, type ScanOptions, type ScanResult, type TSRule, type TSVisitContext, allRules, executeScan, getRuleMetadata, scan };