Rust-native SonarQube-compatible static analyzer for Python, JavaScript/TypeScript,
C#, Go, Java, Ruby, and Rust. It combines a frozen Sonar-parity catalog with a
separate, provenance-rich native catalog and emits text, JSON, SonarQube Generic
Issue Import JSON, or GitLab Code Quality JSON. SARIF 2.1.0 output is reserved
for the isolated github-code-quality profile, which requires --format sarif.
| Crate | Purpose |
|---|---|
hoonarqube |
Public facade crate: re-exports analyze, Language, AnalyzerOptions, catalog, IR |
hoonarqube-core |
Language dispatch, per-file analysis, project measurements, and duplicate-block detection |
hoonarqube-catalog |
Frozen Sonar catalog plus separate native metadata/provenance catalog and cumulative profiles |
hoonarqube-ir |
Findings, execution/data-flow locations, and fix IR: Issue, IssueFlow, Fix, reports and metrics |
hoonarqube-python |
Python analyzer (ruff parser) |
hoonarqube-jsts |
JavaScript/TypeScript/JSX/TSX analyzer (oxc) |
hoonarqube-csharp |
C# analyzer (tree-sitter-c-sharp) |
hoonarqube-go |
Go analyzer (tree-sitter-go) |
hoonarqube-java |
Java frontend, local control-flow facts, and GitHub Code Quality checks (tree-sitter-java) |
hoonarqube-ruby |
Ruby frontend, local data-flow facts, and GitHub Code Quality checks (tree-sitter-ruby) |
hoonarqube-rust |
Rust analyzer (tree-sitter-rust with Clippy-compatible contracts) |
hoonarqube-dataflow |
Generic intra-procedural engine: CFG builder, worklist solvers, dominators; consumed by Go's native decompression-flow rule |
hoonarqube-cli |
analyze (text / JSON / SARIF / SonarQube generic-issue / GitLab Code Quality), fix, plus rules/snapshot catalog queries |
hoonarqube-service |
Optional authenticated SQLite-backed analysis/history service, dashboard, and JSON API (hoonarqube-service binary) |
hoonarqube-bench |
Multi-language throughput benchmark over seeded synthetic fixtures |
xtask |
Catalog audit + implemented-rule coverage reporting |
All workspace packages are source-only and inherit publish = false; GitHub
source releases do not imply crates.io publication.
The Python and JS/TS analyzers share a per-rule layout; C#, Go, Java, Ruby, and Rust use tolerant tree-sitter traversals and language-specific semantic helpers:
src/
├── lib.rs # public API only: language enum, AnalyzerOptions, analyze() orchestration
├── context.rs # per-file analysis context handed to rules
├── support/ # shared helpers: positions, issue constructors, scanners
├── engine/ # shared machinery: scope/symbol models, regex pattern parsers
├── rules/
│ ├── mod.rs # explicit registry dispatching to each rule module
│ └── <rule>.rs # rule implementation + #[cfg(test)] mod tests co-located
│ # (JS/TS groups related rules into family directories
│ # with a per-family walker; Python keeps one file per rule)
└── tests.rs / tests/ # cross-rule integration tests only
Invariants:
- Sonar rule keys remain
<repository>:<key>and resolve only through the frozen catalog. Native keys usehoonarqube-<language>:<key>and resolve only through the separate native catalog; analyzers never duplicate either metadata source. - Per-file rule analysis remains tolerant: partial syntax trees can produce findings. Project analysis separately reports parser/read failures as incomplete and exits nonzero; it never presents an incomplete duplication scan as zero duplication.
- Positions follow the SonarQube convention (1-based line, 0-based column); issues are sorted.
- Flow-aware findings can carry ordered
IssueFlowlocations. Generic Issue Import output exports non-primary flow steps assecondaryLocationsbecause that schema has no code-flow group. - Project reports normalize physical, code-bearing, and comment-only line counts across all supported frontends. Test measurements remain separate from source aggregates.
Audited by cargo run -p xtask -- catalog coverage [--lang <id>] [--strict] [--allow-infra]
against the frozen catalog:
| Language | Implemented | Directly tested | Untested | Infra gaps | Total | Tested coverage | | JavaScript | 445 | 445 | 0 | 0 | 445 | 100.0% | | TypeScript | 455 | 455 | 0 | 0 | 455 | 100.0% | | Python | 353 | 353 | 0 | 0 | 353 | 100.0% | | C# | 468 | 468 | 0 | 0 | 468 | 100.0% | | Go | 36 | 36 | 0 | 0 | 36 | 100.0% | | Rust | 85 | 85 | 0 | 0 | 85 | 100.0% | | Java | 0 | 0 | 0 | 733 | 733 | surface-only | | Ruby | 0 | 0 | 0 | 42 | 42 | surface-only |
Java and Ruby are explicitly versioned Sonar catalog surfaces, not implementations.
Their rule rows were derived from one verified Community Build 26.8.0.126808
capture (xtask catalog import-selected, MQR mode, loopback instance) with full
receipt provenance recorded in catalog/snapshot.toml; every key is classified
as a documented skip in catalog/infra-boundaries.json until per-rule semantic
review lands implementations. The pinned complete-source qualifications — Gson
8b4b5505 (86 files) and Rake 8b4e8eb (44 files) — run the sonar-parity
profile to completion with zero findings; the recorded server-side reference
inventories (182 rows / 43 groups and 5 rows / 5 groups) remain unverified
observations and establish no parity claim.
Already-frozen languages can gain additional rules through
xtask catalog supplement: a verified raw Community MQR capture whose
languages exactly match the request is applied adds-only to the requested
frozen languages — every --key must be absent from the frozen rules, one
supplement per capture digest is recorded per language receipt (in the same
provenance shape as import-selected), pre-existing rows stay byte-stable,
and the closure audit must pass afterwards. Supplemented keys are implemented
detectors, so they are never listed in catalog/infra-boundaries.json.
The following areas depend on configured compiler/project context or explicit parser-fidelity contracts. Their implementations are directly tested, but syntax-only matching does not establish complete framework or analyzer parity:
- TypeScript-checker semantic symbol and dependency metadata —
javascript:S1874,typescript:S1874. - Cross-file module resolution —
javascript:S6627,typescript:S4328,typescript:S6627. - TypeScript-checker-grade type semantics —
typescript:S4325,S6606. - Roslyn-grade type lattice / inheritance coupling graphs —
csharpsquid:S110,S1200,S1944,S3242,S3246,S4047. - Blazor compilation and semantic invocation binding —
csharpsquid:S6802. - Third-party GraphQL symbol resolution and inheritance semantics —
python:S6786. - ASI reconstruction from a tolerant parse —
javascript:S1438,typescript:S1438.
All 1,842 catalog implementations have direct repository test evidence, and the strict implementation-coverage audit passes; the 775 Java and Ruby surface rows are catalogued with documented ownership but deliberately unimplemented. This is implementation coverage, not SonarQube equivalence: compiler prerequisites, reference-sensor availability, finding identity, and remaining comparison failures stay separate. See PARITY.md for the exact oracle contract and current failures.
The current full-corpus evidence
binds native and fresh reference captures to commit
14b389bc34af2835c56ccf050dde48989013fece. It retains all 1,853 rule rows,
including 300 exact C# contracts and every non-pass. The complete C# replay
covers 920 source files; all 13,899 reference findings, including 2,695
project-level findings, are preserved.
The other five full corpora intentionally retain malformed-source controls.
Their native exit 2 and incomplete scope are not converted into clean or
exact-parity results. Go's 135 emitted finding identities match the reference
multiset, but that does not override the incomplete full-project status.
Five Rust upstream boundaries were rechecked and remain explicit. Historical
baseline counts and detailed current limitations are documented in
PARITY.md; complete SonarQube parity is not claimed.
- The configured C# replay at the recorded source commit establishes exactly
21 canonical target findings in both the native probe and actual CLI
(
reference=21,native=21,CLI=21). All other context findings remain retained outside that target-only claim. See the portable C# reference manifest. - The committed JavaScript/TypeScript replay covers all six issue36 cases and 30 exact target findings, including one whitelist finding. The same binary passes the recorded S4325/S6606 and JavaScript/TypeScript S1438 comparisons. GraphQL retains 18 exact controls plus seven explicitly unverified extras. See the portable JS/TS/Python manifest.
- The parsers accept valid JSX
IdentifierNameelement, attribute, member, and namespace names, plus valid C# declarations using contextualasync/await(includingstatic/asynclocal functions).S2306remains declaration-only: validasync/awaitreferences are not reported. - Malformed JavaScript/TypeScript/JSX/C# input, and TypeScript-only JSX syntax
supplied through a
.jsxinput, remain fail-closed: project analysis exits2, marks the report incomplete, and does not expose duplication. - The four-language quickfix qualification
records 275 applications: 181 verified applications, 26 safety refusals,
63 expected no-action cases, four native action refusals, and one explicit
JavaScript reference difference. There are no failed or unavailable
applications. All 54 C# manifest rows are covered; original
S3005/S3447refusal controls and successful used-member controls remain distinct. Refusals and differing edits are not counted as equivalent fixes. - The current security matrix
records 840 complete native scans. Two declared positive controls
(
csharpsquid:S4347andS5773) remain unmatched and explicitly unverified; completion is not correctness. All 17 Enterprise keys remain unverified without licensed reference execution. Exact detector evidence, target presence, and human-review state are separate contracts. - These are source-commit qualification records, not a blanket equivalence claim for SonarQube, Sonar IDE, or a separately built release binary.
Reference captures and their recorded identities are not rewritten, and pending or blocked drafts are not presented as final release evidence.
Forty-seven rules are independently implemented from published CodeQL, gosec, Staticcheck, ESLint, Ruff, .NET analyzer, and Clippy behavior. No third-party rule source is embedded. Every native record declares original tool/rule ID, source URL, upstream license, expected precision, implementation capability, impacts, and minimum profile. This catalog stays structurally separate from captured Sonar facts.
| Language | Native rules | Sources |
|---|---|---|
| Go | 29 | gosec G110/G112/G114/G116/G117/G124/G301/G302/G303/G305/G306/G307/G401/G402/G403/G405/G406; Staticcheck SA1004/SA1012/SA2000/SA2001/SA2003/SA4006/SA4008/SA4010/SA5000/SA5001/SA5003/SA6000 |
| Python | 3 | CodeQL py/side-effect-in-assert, py/file-not-closed; Ruff S113 |
| JavaScript | 4 | CodeQL skipped splice iteration and piped-stream errors; ESLint Promise executor rules |
| TypeScript | 4 | Same CodeQL and ESLint behaviors with a distinct TypeScript namespace |
| C# | 3 | .NET CA2022/CA2024/CA2026 |
| Rust | 4 | Clippy async-guard, readonly-permission, and open-option rules |
Profiles are cumulative:
sonar-parity— default compatibility contract; disables all native rules. Six Sonar rules absent from the reporter-exported SonarQube 2025.4.4 default "Sonar way" profiles (javascript/typescript S1441 and S1537, python S1720 and S6542, csharpsquid S3216 and S4261) stay inactive; see PARITY.md for the bounded membership contract.recommended— 37 high-value, conservative native rules; every Sonar detector stays active.extended— 46 rules, including broader local-flow checks; every Sonar detector stays active.strict— all 47 rules; additionally enforces explicit0600file creation instead ofos.Create's umask-dependent0666mode. Every Sonar detector stays active.
The shared CFG engine now provides deterministic taint facts; Go G110 is its
first taint-fact consumer and emits ordered source-to-sink locations. Rules needing
unavailable type, SSA, or interprocedural proof stay absent instead of being
approximated with broad text matching. Native results are not claims of
CodeQL/gosec/Staticcheck/ESLint/Ruff/Roslyn/Clippy implementation parity.
sonar-parity is the default compatibility profile, not a blanket behavioral
parity promise. Native project metrics/duplication, native rules, compiler-backed
semantic facts, and IDE-style suggestions are separate features; their
availability does not turn them into SonarQube-equivalent metrics, rules, or IDE
behavior. Parity claims are limited to the contracts recorded in
PARITY.md.
Every analyze run under --profile sonar-parity attaches a parity block
to the versioned JSON report (--format json) and to the Generic Issue
Import document (--format sonar). The block records the frozen catalog's
capture provenance (reference.sonarqube_version, captured_at_utc,
capture_sha256), the effective active-rule set for the analyzed languages
with each rule's catalog parameters and fidelity classification
(community-base or enterprise-unverified), the normalized analysis scope
(roots, --test-include/--exclude/--generated-include/--vendor-include
patterns, and per-classification file counts), and whether required semantic
contexts were supplied (semantic_context.typescript/csharp are
supplied, missing, or not_applicable).
parity.completeness is one of reference_parity_verified,
complete_native_analysis, or incomplete. A run is incomplete when the
analysis itself is incomplete, when emitted rule keys escape the recorded
active set (parity.rule_set_violations), or when a requested reference
comparison diverged. Missing semantic context is recorded explicitly in
semantic_context — it is never hidden — but does not by itself change
completeness. An incomplete parity result is reported explicitly; it is
never presented as verified parity.
--parity-reference PATH (requires --profile sonar-parity) compares the
run's findings against a pinned Generic Issue Import report — the same
document --format sonar emits — by rule id, file path, and text range. A
match sets parity.comparison.status to matched and, when every
prerequisite holds, completeness to reference_parity_verified. A
divergence lists each differing identity in parity.comparison.divergences,
prints diagnostics to stderr, and exits non-zero. Other profiles are
unchanged: they emit no parity block and require no reference.
The source-by-source adoption and deferral record is maintained in
RULE_RESEARCH.md.
cargo run -p hoonarqube-cli -- analyze <paths...> # text report
cargo run -p hoonarqube-cli -- analyze --profile recommended <paths>
cargo run -p hoonarqube-cli -- analyze --profile extended <paths>
cargo run -p hoonarqube-cli -- analyze --format sonar <paths> # Generic Issue Import JSON
cargo run -p hoonarqube-cli -- analyze --format gitlab-codequality <paths> # GitLab Code Quality JSON
cargo run -p hoonarqube-cli -- rules native # native provenance catalog
cargo run -p hoonarqube-cli -- rules native --profile recommended --lang go
cargo run -p hoonarqube-cli -- rules info hoonarqube-go:G110
cargo run -p hoonarqube-cli -- fix <paths> # dry-run automatic fixes
cargo run -p hoonarqube-cli -- fix --diff <paths> # preview unified diff
cargo run -p hoonarqube-cli -- fix --apply <paths> # write and verify
cargo run -p hoonarqube-bench -- --iterations N # throughput table
cargo run -p xtask -- catalog coverage # parity auditrules list [--lang <id>] prints the frozen catalog in canonical order and
rules search <term> performs a case-insensitive substring search over keys,
sys_tags, and tags. Rule-tuning flags on analyze and fix include
--go-header-format (analyze only), --csharp-header-format, and
--python-require-type-hints; each expects a literal header/format value or
enables the stricter Python type-hint requirement, and an empty header value
keeps the catalog-default disabled behavior.
See PERFORMANCE.md for measured runtime and memory changes, release footprint, and repeatable CLI benchmarks with exact output comparison.
analyze <paths> is source-only by default: supported source inputs are
classified as Source, no test/generated/vendor filename heuristics are
inferred, and no coverage, baseline, or quality-gate context is loaded. For
JavaScript/TypeScript sources, analyze additionally auto-discovers each
file's nearest ancestor tsconfig.json and loads a best-effort compiler
context, so compiler-backed rules (S6606, S4328, S4325, S1874, S4782) fire on
ordinary project scans; the isolated github-code-quality profile and fix
stay context-free. Native per-file findings and project measurements still run
for supported languages.
Project/compiler contexts use these existing flags:
--typescript-project PATHloadsPATHwhen it is atsconfig.jsonfile, orPATH/tsconfig.jsonwhenPATHis a directory.--typescript-module PATHoptionally supplies an explicit TypeScript package/compiler location; it is only valid with--typescript-project.- TypeScript semantic analysis requires an installed Node.js
nodeexecutable and runs a helper that resolves a project-local TypeScript package; it never searches a global compiler or downloads one. The loaded compiler must be exactly the pinned 6.0.3 release; any other version is rejected with a single compatibility diagnostic. A project may keep a different TypeScript dependency of its own (for example 7.x): point the helper at a separate supported compiler with--typescript-module PATHor theHOONARQUBE_TYPESCRIPT_PACKAGEenvironment variable instead of changing the analyzed project. --typescript-dependency-whitelist PACKAGEsupplies a repeatable S4328 allowlist entry as a package name or scope. It requires--typescript-project; when omitted, the whitelist remains empty.--csharp-project PATHselects a C# project or solution. Pair it with--allow-project-buildto explicitly trust project evaluation and the boundeddotnet build --no-restoreused for project references and Razor generated sources. An installed .NET SDK capable of the bundlednet10.0helper is required;HOONARQUBE_DOTNETcan select its executable. Without this flag the project is not executed and compiler-backed C# facts are unavailable, while native analysis remains.--csharp-context-source PATHsupplies an additional compiler-only snapshot from a regular UTF-8 file (not a symlink). Repeat it for referenced project documents outside the positional analysis inputs; it requires--csharp-project. These files do not become analysis roots, findings, measurements, duplication inputs, or fix targets. Their paths and contents participate in semantic/cache identity; missing or invalid snapshots remain incomplete diagnostics.--csharp-timeout-ms MSoptionally changes the C# helper deadline from its default 30,000 ms. It requires--csharp-projectand accepts only a positive finiteu64millisecond value; for example,180000allows a larger trusted workspace without introducing retries or an unbounded wait.- TypeScript config resolution delegates to the pinned compiler: relative and
package-based
extends, directory or file-form projectreferences, andincludeglobs are resolved against the project root plus supplied source snapshots. Config files and package manifests actually read are recorded as digest-bearing context dependencies. - Each invocation validates the root
tsconfig.jsonbytes against its captured digest before helper analysis and caches that validated root config locally for the invocation. A later on-disk mutation cannot change its options; missing or invalid config/reference input remains an incomplete diagnostic. --csharp-s110-max Noverrides S110's maximum parent-type depth (default 5). It requires--csharp-project.--csharp-s110-filtered-class PATTERNsupplies a repeatable S110 wildcard filter for class names. It requires--csharp-project; when omitted, the filter list remains empty.--csharp-s1200-max Nsets S1200's maximum dependency count and explicitly enables that rule (default disabled, threshold 30). It requires--csharp-project.--python-project PATHsupplies a project root/module namespace for the source-snapshot cross-file rules; it does not execute Python code. Forpython:S6554, include the model and its imported base-class sources in the scan to recognize inherited__str__implementations. File-only scans resolve local inheritance; unknown external bases do not establish an inherited method. Django's defaultModel.__str__does not satisfy this rule, and abstract models remain exempt.
These options apply to both analyze and fix. Missing configuration, runtimes,
references, or compiler facts remain diagnostics and never become fabricated
semantic findings. The isolated github-code-quality profile cannot be combined
with compiler-backed project contexts.
Standalone release binaries embed the TypeScript CJS helper and the C# helper
project/program, QuickFixPlanner, and RazorSourceFacts, then materialize those
sources at runtime. A Hoonarqube checkout is not needed. The helpers do not bundle
external runtimes or packages: TypeScript still needs Node.js and the project-local
TypeScript 6.0.3 package, while C# still needs a .NET SDK capable of the
bundled net10.0 helper. Keep --allow-project-build as an explicit trust
boundary and use it only with trusted project inputs.
Repeated scans can opt in to a local per-file cache:
cargo run --release -p hoonarqube-cli -- analyze --cache-dir .cache/hoonarqube srcUnchanged files reuse successful findings and parsed source facts. Every run still reads and hashes current source contents, discovers added/deleted files, applies current scope settings, and recomputes full-project metrics and cross-file duplication. This is not a partial Git-diff scan: unchanged files remain represented in all results.
Cache keys include the executable's SHA-256, effective analyzer options (including profile), working directory, exact file path and source bytes. Changing the executable invalidates reuse, including development builds with the same version. Cold runs pay cache-writing overhead; warm benefits depend on parsing/rule costs relative to file I/O, deserialization, and duplication. Use a release build for performance-sensitive pipelines.
Entries live in an owned hidden .hoonarqube-cache-v1 child beneath the supplied
directory; --cache-dir . does not exclude your source tree. The ordinary
directory walker skips hidden cache artifacts. Failed analyses are not cached.
Corrupt, oversized, incompatible, or inaccessible entries fall back to fresh
analysis without changing report completeness. Writes are best-effort and
atomic. Omit the flag to disable caching; fix never uses it.
Treat cache storage as trusted local state, not an authenticated report source.
Checksums detect corruption, not deliberate cache forgery. Never restore
untrusted cache archives into a privileged pipeline. See the Actions cache
example for
restore-only pull requests and protected-branch saves. Old entries are not
automatically pruned; remove the owned .hoonarqube-cache-v1 child when you
want to reclaim space or force a cold run.
analyze measures Python, JavaScript/JSX, TypeScript/TSX, C#, Go, Java, Rust,
and Ruby. Measurement support is independent of each language's rule-catalog
coverage. It detects repeated blocks within a file and across files of the
same language; JavaScript and TypeScript are separate matching domains.
cargo run -p hoonarqube-cli -- analyze --format json \
--test-include '**/tests/**' \
--generated-include '**/generated/**' \
--vendor-include '**/vendor/**' \
--exclude '**/fixtures/**' \
--duplication-exclude '**/*.min.js' \
src testsEach glob option is repeatable and accepts one complete, quoted glob.
Brace alternatives such as --exclude '**/*.{js,ts}' are supported. Paths
inside the current working directory are matched relative to that directory;
paths outside it are matched as absolute paths. Existing ignore-file and
hidden-entry walking rules still apply. No test/generated/vendor filename
heuristics are enabled implicitly.
The resulting default project classification is source-only; opt into other
scopes with the explicit include/exclude flags above.
Classification precedence is excluded → vendor → generated → test → source.
Source files contribute to project size and duplication. Tests retain their
findings and individual measurements, but do not contribute to those source
aggregates. Generated, vendor, and excluded scopes are not analyzed.
--duplication-exclude removes a source file only from duplication, preserving
its findings and size measurements.
Excluded directory roots remain visible as scope entries with no measurements;
their contents are not enumerated. Recursive scope globs ending in /**
support subtree pruning; other globs still apply to matching paths.
The JSON report has schema_version: 1 and retains the existing files
array for findings. Its project object contains:
roots, scope inventory infiles,complete, andwarnings.metrics: source file count, physicallines,code_lines, and comment-onlycomment_lines, accumulated with 64-bit counters.duplications: clone groups with every occurrence's path, inclusive 1-based line range, and half-open UTF-8start_byte/end_byteoffsets. Byte offsets distinguish separate blocks on the same physical line.duplication: duplicated line/block/file counts andduplicated_lines_density, also available for eligible individual files.
Duplicated lines are the union of matching line ranges within each file;
overlapping groups cannot count a line twice. Blocks count distinct source
byte spans, not groups. Project density uses the total physical lines of
duplication-eligible source files, rather than averaging file percentages.
An empty denominator produces null, not a fabricated percentage.
Default detection thresholds are 100 normalized syntax tokens across
10 physical lines, or 10 statement units for Java, irrespective of
line count. Override them with --duplication-min-tokens,
--duplication-min-lines, and --duplication-min-statements; all must be
positive. Comments and layout are ignored, plain string contents are
normalized, and identifiers, operators, numeric literals, and embedded
interpolation expressions remain significant. Structural markers retain
layout-sensitive boundaries. Java uses separate direct-statement streams
for nested blocks, with control/declaration signatures and nonmatching
boundaries between streams.
These are native measurement semantics, not an established SonarQube metric-equivalence claim. In particular, syntax-token accounting and Java statement selection require separate oracle evidence. See PARITY.md.
Failed reads/parses, unsupported explicit inputs, or exhausted resource budgets
make project.complete false and duplication unavailable (null), while
preserving available findings and scope diagnostics. Recognized CSS, HTML, and
Docker files found during a directory walk are instead retained in the JSON
inventory as classification: "excluded" and status: "unsupported" entries
with no metrics, so they leave metrics unchanged and do not make the project
incomplete (complete: true, exit 0). Passing one directly as a file (without
an explicit excluded/generated/vendor classification) retains it as
Source/Unsupported, makes complete: false, and exits 2. The CLI
still emits the report. Facts collection limits individual inputs to 16 MiB.
The default project matcher accepts up to 2,000,000 normalized units and
1,000,000 candidate pairs, with an additional bounded comparison budget. Limit
failures are explicit, never silently truncated results.
Text output summarizes source/test measurements, scope, completeness, and matching locations. Detailed scope inventory is in JSON. SonarQube Generic Issue Import, SARIF, and GitLab Code Quality remain issue-only formats: they do not transport these project measures or create artificial duplication issues.
Assessment is optional native JSON data. --assessment records versioned
provenance and source-derived finding identities; the other assessment flags
are explicit inputs:
--coverage-lcov PATHand--coverage-opencover PATHare repeatable. They import LCOV or OpenCover XML against the exact analyzed source snapshots. Inputs must be regular, non-symlink, UTF-8 files within the 64 MiB bounded assessment-input limit.--baseline PATHcompares against the exact prior nativeAnalysisReportJSON (schema 1) supplied at that path. Its assessment context must match the analyzer, catalog, effective options, and scope; this is a pinned-reference comparison, not Git-history or merge-base discovery.--write-baseline PATHatomically writes the current native report with its assessment artifact to that path. Use a complete result as the subsequent pinned reference.--quality-gate PATHreads a versioned JSON gate configuration. The accepted shape is{"schema_version":1,"conditions":[...]}; conditions usescope: "overall"or"new_code",metric,operator(lt,lte,eq,gte, orgt), and a finite non-negativethreshold. Supported metrics arefiles,lines,code_lines,comment_lines,issues,duplicated_lines,duplicated_blocks,duplicated_files,duplicated_lines_density,line_coverage, andbranch_coverage.new_codedoes not supportcode_linesorcomment_lines, and anynew_codecondition requires--baseline.
Coverage, baseline, and gate results are versioned under the native
assessment object. Missing, invalid, or incomplete assessment data, an
unavailable gate, or an incomplete native project yields exit 2; a
configured gate that evaluates to fail yields exit 1. The report is still
emitted when its requested output format can be rendered. Native JSON (--format json or global --json) carries the optional assessment; text includes a
summary, while SonarQube Generic Issue Import, SARIF, and GitLab Code Quality
remain issue-only exports. These contracts are native assessment behavior, not
SonarQube coverage, baseline, or quality-gate parity.
The reference-only
catalog/reference/issue-52-language-inventory.json
records SonarQube Community server 26.8.0.126808 observations. Counts are
all / active reference rules, not shipped detector counts:
| Reference language | Server key | All | Active | Hoonarqube status |
|---|---|---|---|---|
| CSS | css |
43 | 40 | planned_not_implemented |
| HTML | web |
104 | 61 | planned_not_implemented |
| Docker | docker |
28 | 25 | planned_not_implemented |
All three languages are absent from the frozen local rule catalog, native language dispatch, and current eight-language measurement dispatch. The reference counts are server/profile observations only; all listed rule work is planned, not implemented locally.
When a directory is analyzed, recognized CSS, HTML, and Docker paths are kept
as Excluded/Unsupported inventory entries with reason language is unsupported and no metrics. Recognition is case-insensitive for CSS
(.css, .less, .scss, .sass), HTML (.html, .xhtml, .cshtml,
.vbhtml, .aspx, .ascx, .rhtml, .erb, .shtm, .shtml, .cmp,
.twig, .htm), and Docker (Dockerfile or any .dockerfile basename).
The ordinary supported-source metrics and cross-file CPD remain unchanged,
project.complete stays true, and the exit code stays 0. Passing one of those
files explicitly (without an explicit exclusion or another inventory
classification) keeps it as Source/Unsupported, makes the project
incomplete, and exits 2.
catalog/github-code-quality.json is the authoritative metadata catalog for
GitHub Code Quality: it contains 382 definitions captured from CodeQL. A
definition is not an implementation claim. The github-code-quality profile
intentionally runs only Hoonarqube's conservative, high-confidence implemented
subset across C#, Go, Java, JavaScript/TypeScript, Python, and Ruby. The
remaining definitions are not silently approximated, so this action must never
be described as implementing all 382 queries or as full CodeQL behavioral
parity.
The executable registry currently covers 56 of 382 definitions: C# 13/69, Go 5/22, Java 15/89, JavaScript/TypeScript 15/98, Python 5/101, and Ruby 3/3. Audit the registry and print every missing ID with:
cargo run --locked -q -p xtask -- catalog github-coverageAdd --require-full when a release is intended to claim complete parity; it
currently fails closed because 326 definitions remain unimplemented.
Rust is deliberately excluded from this profile. Rust files produce no GitHub Code Quality findings; use the regular Sonar-compatible profile when Rust analysis is required. Hoonarqube runs its own detectors and does not install, invoke, or require the CodeQL CLI. The catalog preserves CodeQL query metadata, but metadata presence is not detector coverage.
The CLI emits the SARIF 2.1.0 contract directly:
cargo run --locked -q -p hoonarqube-cli -- analyze \
--profile github-code-quality --format sarif -- srcThe SARIF driver is Hoonarqube. Query categories are Maintainability and
Reliability. Query severities map to SARIF levels as follows: Error to
error, Warning to warning, and Recommendation or Info to note.
Hoonarqube converts its internal 0-based columns to SARIF's 1-based columns
and declares columnKind: unicodeCodePoints so non-BMP characters keep correct
source locations. Artifact paths are percent-encoded relative URI references,
including filenames containing colons. Flow evidence is retained as
relatedLocations and codeFlows. partialFingerprints carry a stable
primaryLocationLineHash content fingerprint; coordinate-dependent
fingerprints are intentionally omitted.
actions/code-quality installs through the verified setup action, validates
the SARIF document, and exposes report, result-count, and
blocking-findings outputs. Upload is opt-in. The fail-on input accepts
none (default), findings, note, warning, or error; when enabled, a
validated report is uploaded before the threshold gate fails the job. GitHub's
upload-sarif action publishes third-party results to code scanning; it does
not inject them into GitHub's native Code Quality dashboard. The existing
actions/analyze action remains for SonarQube Generic Issue Import JSON.
Copy-paste workflow example:
name: Code quality
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
security-events: write
jobs:
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: hoonarqube
uses: openhoo/hoonarqube/actions/code-quality@6c274a2f157364c2b7f5e8756f6204663937c7e1 # pin to the consuming commit
with:
paths: |
src
crates
output: .reports/hoonarqube-code-quality.sarif
upload: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
- run: echo "Hoonarqube reported ${{ steps.hoonarqube.outputs.result-count }} finding(s)"Uploading to code scanning requires security-events: write; contents: read
is sufficient for checkout. GitHub downgrades the token for pull requests from
forks, so that permission is unavailable there. The condition above uploads
pushes and same-repository pull requests only; fork pull requests still get a
local validated report, but cannot upload it. Keep upload disabled for
untrusted contexts and do not grant write permissions to forked code.
The github-code-quality profile is quality-only by contract. It emits
Maintainability and Reliability findings from file-local, high-confidence
checks and contains no security analysis. CodeQL security queries observed in
downstream comparisons are documented non-coverage, not missing quality
detectors:
js/bad-code-sanitization(CWE-094 path query, #148) andjs/bad-tag-filter(CWE-116 HTML comment/tag-filter query, #149) require interprocedural sanitizer-to-code-construction taint semantics and HTML tag-filter modeling that the quality-only contract intentionally does not build. The recorded reference runs stay as recorded: the pinned Zod whole-project run exits2(incomplete, never re-labeled as a clean negative) and the pinned Markdown-It run analyzes all 57 selected files with zero findings. The native Sonar-catalog rule S5852 targets nested unbounded quantifiers (ReDoS) and is not a tag-filter equivalent; the two remain separate, and no exploit or upstream-defect claim is made.rb/polynomial-redos(#175) is a version-sensitive regex-backtracking query reported at Rake'sapplication.rb:815. The pinned counter-control runtime, Ruby 3.4.10, reportsRegexp.linear_time?truefor the cited pattern/([^:]+):/, so this is not a demonstrated defect on that runtime; older engines permitted by the Rake gemspec (Ruby >= 2.3) remain explicitly unverified, and no timing or exploit reproduction is claimed.
None of the three query IDs is a row in catalog/github-code-quality.json or
a key in any GITHUB_QUALITY_RULE_IDS executable registry; their absence is
this documented boundary, not an audit gap. If security coverage is ever
built, it will live in a separate profile and registry with its own
semantics, fixtures, qualification gates, and supported-runtime contract. It
must not broaden the quality-only profile implicitly.
fix combines quick fixes attached to catalog findings with a safe mechanical
repair for missing final newlines. It never writes by default. Use --diff to
inspect the projected rewrite and --apply to write it. --rule <prefix>
limits finding-backed fixes (repeatable or comma-separated); the final-newline
repair remains enabled. Generic trailing-space and leading-tab rewrites are
intentionally excluded because that whitespace can be data inside multiline or
raw string literals.
Use --suggestion RULE=ACTION_ID (repeatable) to select one finding alternative
explicitly; when supplied, only those selected suggestions are planned, not
automatic fixes. Compiler-backed suggestions require the corresponding complete
project context above. These native/IDE-style actions are not a SonarQube quick-fix
parity claim.
Each multi-edit rule fix is atomic. If fixes overlap, deterministic earlier fixes win and complete later fixes are skipped and reported. Apply mode rejects a file changed since planning, then analyzes projected content before writing: every rule fix must work independently, targeted rule counts must decrease by the number of applied fixes, and no rule count may increase, including after a mechanical-only rewrite. Failed verification returns a nonzero exit status and leaves the file untouched. File content is checked again immediately before the write. Apply mode also rejects symlinked files and directories and rechecks each path before writing. Analysis remains read-only and may inspect symlinked source files, but never follows symlinked directories.
Global --json keeps stdout as one JSON document, including requested diffs as
per-file diff fields instead of mixing human text into machine output. All
54 cataloged C# keys, 57 of 58 Python keys, and all 28 JavaScript + 32
TypeScript keys ship finding-backed fixes or selectable suggestions today;
python:S5806 remains the only documented gap. See
QUICKFIX.md for the parity inventory and bounded proof.
The CLI emits GitLab's required single-array report with:
descriptionfrom the finding message andcheck_namefrom its rule key.- A stable SHA-256
fingerprintover length-delimited normalized primary path, rule key, message, and primary range; nested flow/fix metadata is excluded. - Lowercase
severity(info,minor,major,critical, orblocker). - A raw repository-relative POSIX
location.pathwithout a./prefix and inclusive positivelocation.lines.begin/endvalues. File-level findings use line 1 as their conventional anchor.
Ordinary colon filename components are preserved. Drive- and URI-like prefixes, backslashes, control characters, outside-checkout paths, non-UTF-8 paths, and invalid non-file ranges fail closed.
Use --format gitlab-codequality with the default sonar-parity profile or a
cumulative native profile. It is intentionally separate from the isolated
github-code-quality SARIF profile. Empty findings emit []; invalid
non-file ranges, outside-checkout paths, and non-UTF-8 paths fail closed.
The report carries findings only, not project metrics or completeness. A
complete scan with findings exits 0; an incomplete scan still emits its valid
report and exits 2, while report/serialization failures exit 1.
GitLab consumes the report from a CI job's codequality report artifact. Use a
Linux x86_64 runner with a release binary installed and verified using the
repository's release installer checks:
stages: [quality]
gitlab-code-quality:
stage: quality
script:
- hoonarqube --version
- set +e
- hoonarqube analyze --format gitlab-codequality -- src tests > gl-code-quality-report.json
- status=$?
- set -e
- test -s gl-code-quality-report.json
- exit "$status"
artifacts:
when: always
reports:
codequality: gl-code-quality-report.jsonThe explicit status capture keeps an operational/incomplete exit 2 distinct
from a policy failure chosen by the consuming job; artifacts: when: always
keeps the report available for review.
hoonarqube-service stores already-produced native AnalysisReport JSON; it
does not analyze source itself. It opens a persistent SQLite database and serves
an optional dashboard plus a JSON API.
The service binary has three environment variables:
HOONARQUBE_SERVICE_DBselects the SQLite file; it defaults tohoonarqube-service.sqlite3.HOONARQUBE_SERVICE_CREDENTIALSis required and contains a JSON array of unique credentials. Each entry hasuser_idandtoken, with an optionalprojectsmap (projecttoreader,reviewer, oradmin) and optionalglobal_admin: true. Tokens are static startup configuration; keep them outside source control.HOONARQUBE_SERVICE_BINDselects the listen address; it defaults to127.0.0.1:8080.
For a local standalone deployment, keep the database and credential file private. Populate the credential file with a newly generated token using the JSON shape above; the launch below passes its contents through the existing credential variable without placing a reusable token in the command:
install -d -m 0700 "$HOME/.config/hoonarqube" "$HOME/.local/share/hoonarqube"
credentials_file="$HOME/.config/hoonarqube/service-credentials.json"
test -s "$credentials_file"
chmod 600 "$credentials_file"
exec env \
HOONARQUBE_SERVICE_DB="$HOME/.local/share/hoonarqube/service.sqlite3" \
HOONARQUBE_SERVICE_BIND="127.0.0.1:8080" \
HOONARQUBE_SERVICE_CREDENTIALS="$(<"$credentials_file")" \
hoonarqube-serviceThe SQLite file and its WAL state persist under
$HOME/.local/share/hoonarqube; open http://127.0.0.1:8080/ and provide the
configured bearer token when prompted.
The dashboard is served at / (also /index.html) and keeps the entered
bearer token only in the current tab. All /api/v1 endpoints require
Authorization: Bearer .... The UI can list visible projects and branches,
browse immutable analysis history/details/findings, and create/read finding
reviews. Ingestion and administrative operations remain API-only:
- Read endpoints are
GET /api/v1/projects,GET /api/v1/projects/{project}/branches,GET /api/v1/projects/{project}/analyses,GET /api/v1/projects/{project}/analyses/{analysis_id},GET /api/v1/projects/{project}/analyses/{analysis_id}/findings,GET /api/v1/projects/{project}/reviews, andGET /api/v1/projects/{project}/reviews/{review_id}/history. POST /api/v1/projects/{project}/analysesingests a request withschema_version: 1,branch,commit,analyzed_at, and the nativereport; re-ingesting the same project/branch/commit is idempotent.POST /api/v1/projects/{project}/reviewsrecords a versioned finding or hotspot review with an audit reason.GET /api/v1/projects/{project}/exportandPOST /api/v1/projects/{project}/restoreprovide the backup boundary.POST /api/v1/projects/{project}/retentionandDELETE /api/v1/projects/{project}/analyses/{analysis_id}?reason=...orDELETE /api/v1/projects/{project}?reason=...are administrative and retain deletion/audit records.
reader may read project data, reviewer may also mutate review state, and
admin may ingest, delete, export, restore, and apply retention. A
global_admin bypasses per-project mappings. There is no login or external
identity-provider flow: credentials are static bearer tokens, project-scoped
unless global admin, and every API request is authenticated. The SQLite store
uses WAL and foreign keys; the service defaults to a loopback plain-HTTP
listener, so TLS or a wider network boundary must be provided by the
deployment.
The standalone binary enforces bounded request/report sizes (16 MiB request bodies, 100,000 report files, and 500,000 issues). These limits, static credentials, and the dashboard's read/review-only surface are intentional service boundaries, not SonarQube server parity.
The release workflow publishes an optimized Linux archive containing both
executables, hoonarqube (CLI) and hoonarqube-service (optional persistent
service/dashboard), plus the SPDX SBOM, sorted SHA-256 checksums, keyless
Sigstore bundles, and GitHub artifact attestations from the immutable release
tag.
The published x86_64-unknown-linux-gnu binaries are built on Ubuntu 22.04 and
require glibc 2.35 or newer (Debian 12 bookworm, Ubuntu 22.04, and later). The
release workflow smoke-tests both executables inside a Debian bookworm
container before upload.
Download the Linux x86_64 archive and its matching assets from the official
GitHub release page. The archive
contains both hoonarqube and hoonarqube-service; do not extract or install it
until its checksum and keyless signatures verify. This mirrors the repository's
release installer checks. With cosign installed from
a trusted source, run:
set -euo pipefail
command -v cosign >/dev/null 2>&1 || {
echo "cosign is required; install it from a trusted source." >&2
exit 1
}
read -r -p 'Release version without the leading v: ' VERSION
test -n "$VERSION"
STEM="hoonarqube-${VERSION}-x86_64-unknown-linux-gnu"
ARCHIVE="${STEM}.tar.gz"
CHECKSUMS="SHA256SUMS"
BASE_URL="https://github.com/openhoo/hoonarqube/releases/download/v${VERSION}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
cd "$WORK_DIR"
curl --fail --location --silent --show-error --retry 3 --connect-timeout 30 \
--output "$ARCHIVE" "$BASE_URL/$ARCHIVE"
curl --fail --location --silent --show-error --retry 3 --connect-timeout 30 \
--output "$CHECKSUMS" "$BASE_URL/$CHECKSUMS"
curl --fail --location --silent --show-error --retry 3 --connect-timeout 30 \
--output "${ARCHIVE}.sigstore.json" "$BASE_URL/${ARCHIVE}.sigstore.json"
curl --fail --location --silent --show-error --retry 3 --connect-timeout 30 \
--output "${CHECKSUMS}.sigstore.json" "$BASE_URL/${CHECKSUMS}.sigstore.json"
SIGNATURE_IDENTITY="https://github.com/openhoo/hoonarqube/.github/workflows/release.yml@refs/heads/main"
SIGNATURE_ISSUER="https://token.actions.githubusercontent.com"
cosign verify-blob "$ARCHIVE" --bundle "${ARCHIVE}.sigstore.json" \
--certificate-identity "$SIGNATURE_IDENTITY" \
--certificate-oidc-issuer "$SIGNATURE_ISSUER"
cosign verify-blob "$CHECKSUMS" --bundle "${CHECKSUMS}.sigstore.json" \
--certificate-identity "$SIGNATURE_IDENTITY" \
--certificate-oidc-issuer "$SIGNATURE_ISSUER"
sha256sum --ignore-missing --check "$CHECKSUMS"
tar -xzf "$ARCHIVE"
install -d "$HOME/.local/bin"
install -m 0755 "$STEM/hoonarqube" "$HOME/.local/bin/hoonarqube"
install -m 0755 "$STEM/hoonarqube-service" "$HOME/.local/bin/hoonarqube-service"
export PATH="$HOME/.local/bin:$PATH"
"$HOME/.local/bin/hoonarqube" --versionThe release page is authoritative for the version and asset names; keep the downloaded archive, checksum manifest, and Sigstore bundles from the same release.
cargo test --locked --workspace --all-targets --all-features # full suite, including benches/examples
cargo run --locked -q -p xtask -- catalog coverage --strict --allow-infra
python3 -m unittest discover -s tools/oracle -p 'test_*.py' -v
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
RUSTDOCFLAGS='-D warnings' cargo doc --locked --workspace --all-features --no-deps
cargo fmt --all --check
cargo deny check
ruff check tools/oracle --exclude tools/oracle/fixtures --extend-select C90,PLR0911,PLR0912,PLR0915,PERF,SIM,B
ruff format --check tools/oracle --exclude tools/oracle/fixturesConventions: one rule per file under rules/, its tests co-located in the same file; shared logic
in support/engine; explicit registries in rules/mod.rs; no lint suppressions.