builtin.tests.pub-fn-untested
| Field | Value |
|---|---|
| id | builtin.tests.pub-fn-untested |
| severity | info |
| category | tests |
| scope | file |
| languages | rust, javascript, typescript, tsx |
| evaluator | builtin / pub_fn_untested |
Flags public-API definitions whose name does not appear in any test
body — checked first in the source file itself, then in a small set of
conventional peer test files sitting next to (or near) the source.
Severity is info: a signal to help focus attention, not a
verdict-breaker.
The rule prefers tests in a peer file — that is the more common
layout across Rust crates and JS/TS projects — but an in-file
#[cfg(test)] mod (Rust) or in-source Vitest block (JS/TS) is also
accepted. Both shapes silence the finding.
Where the rule looks for tests
Section titled “Where the rule looks for tests”For source file <dir>/<stem>.rs, the rule considers:
- In-file
#[test],#[tokio::test], and any…::test-attributed function bodies (including helpers inside a#[cfg(test)] mod— those bodies are pulled into the haystack too). <dir>/<stem>_tests.rs— the sibling-file convention used by this workspace and many crates that want tests next to the code without inflating the source file’s line count.<dir>/tests/<stem>.rs— atests/directory next to the source.<crate-root>/tests/<stem>.rs— Cargo’s integration-test directory at the crate root (found by walking up to the parent ofsrc/).
JavaScript / TypeScript / TSX
Section titled “JavaScript / TypeScript / TSX”For source file <dir>/<stem>.<ext>, the rule considers:
- In-file
describe/it/test/suite/context/fit/fdescribecallback bodies — including chained forms likeit.skip(…),describe.only(…), andtest.each(table)(name, fn). This covers Jest, Vitest, Mocha, and Jasmine in-source patterns. - Sibling
<dir>/<stem>.test.<ext>or<dir>/<stem>.spec.<ext>(acrossts,tsx,js,jsx,mjs,cjs). - The same two shapes nested under a
__tests__/directory beside the source.
Matching is a whole-word identifier match against the file’s text. The peer-file haystack is the raw file contents — no parsing — which keeps the check cheap and language-agnostic.
Per-language definitions
Section titled “Per-language definitions”- Only fully-public
pub fn.pub(crate),pub(super), andpub(in …)are excluded — they’re internal API. - Functions inside a module annotated with
#[cfg(test)]are excluded — they’re test helpers, not public surface.
JavaScript / TypeScript / TSX
Section titled “JavaScript / TypeScript / TSX”- Only declarations introduced with the
exportkeyword are considered public. Bare top-levelfunction fandconst f = …are module-private and skipped (analogous to Rust withoutpub). - Covered shapes:
export function f,export async function f,export function* f,export const f = (…) => …,export const f = function (…) {…},export class C, andexport default function f(with a name). Anonymous default exports (export default () => …) and re-export lists (export { f } from '…') are skipped. - Type-only exports (
export type,export interface) are skipped.
Fixing a finding
Section titled “Fixing a finding”- Preferred: add a test in a peer file.
- Rust: create
<stem>_tests.rsnext to the source withuse super::*;and a#[test]per public function. The finding’s suggested patch creates this file for you. - JS/TS: create
<stem>.test.<ext>next to the source and import the function from./<stem>. The finding’s suggested patch creates this file for you (importing fromvitest).
- Rust: create
- Also accepted: a test in the same file.
- Rust: a
#[cfg(test)] mod testsat the bottom of the file with a#[test]per public function. - JS/TS: an in-source
describe/itblock (Vitest’s in-source testing makes this ergonomic).
- Rust: a
- Reduce visibility. If the function isn’t actually part of the
public API, drop the
exportkeyword (JS/TS) or downgrade topub(crate)(Rust). - Suppress for a specific path. Add an
overrides: [builtin.tests.pub-fn-untested]in a repo-local rule, or setenabled: falsefor projects where the convention doesn’t apply.
Limitations
Section titled “Limitations”- Peer-file detection is path-conventional, not import-aware: a test
file at an unconventional location (e.g.
spec/foo.tsnext tosrc/foo.ts) will not be discovered. - The rule does not understand re-exports — a function tested in a different file (one not on the peer-path list) still triggers a finding here.
- Renaming a function but not the test that calls it will mask the finding until the test is rewritten.
- Macros that generate tests (Rust’s
parameterized!, JS’s custom helper wrappers) are seen as whatever text the invocation contains, which may or may not mention the public function by name. - CommonJS exports (
module.exports.foo = …) and JSexport { foo }re-export lists are not detected — switch to ES-moduleexport function ffor the rule to apply.
See also
Section titled “See also”- Authoring rules → override a built-in — how to disable this rule per-project.