funcusage

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

README

Function Usage

Function Usage is not a standalone CLI tool — it is a lightweight, composable static analysis library designed to map how functions are used across a Go repository and live inside tests.
It provides a clear, type-checked view of direct call relationships between declared functions, allowing one to understand usage patterns and identify structural issues in your codebase.
Function Usage is intentionally a simple, deterministic, and transparent structural analysis tool, not a behavioral one. It relies on Go’s type checker rather than heuristics or heavy call‑graph machinery, giving one predictable and actionable results.

It can help with below:

a. find functions with the highest number of direct calls
b. find functions with the lowest number of direct calls
c. find exported functions not used by direct calls
d. find exported functions that could be unexported
e. optionally include or exclude tests and external functions.

funcusage is not suitable for codebases where behavior is primarily expressed through interface dispatch, higher-order functions, or dynamic call construction. In such cases, direct call counts may significantly underrepresent actual runtime behavior.

How to use

The test files should provide examples on how to use.
A possible workflow could be:

Analyze the repository

The Usage data returned by the analyzer represents an expensive snapshot of the codebase (AST parsing, type checking). All filter and sort operations preserve this original data by returning new slices.
Run the analyzer from the module root as a test.
This way it can run both in CI and also resolve the module path and load all packages, including tests.

Performance Trade-off
  • Analysis Phase: Expensive (seconds, type checking)
  • Query Phase: Cheap (microseconds, slice operations)
  • Design Choice: Accept O(n) copying in where operations to guarantee:
    • Test determinism (no flaky tests from mutations)
    • Safe method chaining
    • Predictable debugging (original data always available)
Scope Control

Configure whether to include:

  • same‑package tests
  • external test packages (mypkg_test)
  • external packages outside the module
Usage Structure

The analyzer returns a Usage slice.
Each FunctionUsage entry contains:

  • canonical function identity
  • short name
  • source position
  • internal and external call counts
  • internal and external test call counts
Chaining Filters

Filters return a new Usage, allowing composition:

untestedExported := usage.
    WhereNotTested().
    WhereExported().
    Limit(10)

Example: find a specific untested exported function:

result := usage.
    WhereNotTested().
    WhereExported().
    WhereNameIs("some function name")

Modes

funcusage provides several mutually exclusive analysis modes. Each mode defines how production code and test code are interpreted during analysis.

ModeDefault

Analyze only production code.

  • Test files are ignored.
  • Test-defined functions are not reported.
  • Calls from tests do not count toward usage.

Use this mode for strict dead‑code detection in production.

ModeIncludeTestsForCoverage

Include calls from test files into production code, but do not report test-defined functions.

  • Test helpers are ignored.
  • Calls from tests increase usage counts for production functions.

Use this mode for a coverage‑like view of how tests exercise production code.

ModeIncludeTestHelpers

Include both production functions and test-defined functions.

  • Test helpers appear in the output.
  • Their usage is counted normally.

Use this mode to analyze or clean up test suites.

ModeOnlyTestHelpers

Analyze only functions defined in test files.

  • Production code is ignored.
  • Only test helpers and their usage are reported.

Use this mode to audit or refactor large test suites.

ModeOnlyInTestFiles

Report production functions, but count only calls originating from test files.

  • Production functions are included.
  • Only test-origin usage counts are considered.

Use this mode to identify production functions used exclusively by tests or over‑exported APIs.

Filters

Filters allow narrowing the analysis result (Usage) based on name, visibility, or usage patterns.
The number of values can be controlled with Limit as last element in the chain.
All filter methods start with Where and return new Usage slices:

WhereNameIs

Return only functions whose name matches exactly passed name.

WhereUnused

Return functions with zero internal and zero external calls.

WhereExported

Return functions whose name starts with an upper‑case letter.

WhereUnexported

Return functions whose name starts with a lower‑case letter.

WhereTestedInternally

Return functions that have internal test calls.

WhereTestedExternally

Return functions that have external test calls.

ExportedUnused

Return exported functions that have no internal or external calls.

ExportedWithNoExternalCalls

Return exported functions that have no external calls.
Useful for identifying candidates that should be unexported.

Generic Where

Allows injecting custom predicate for extensibility.

Sorters / Order by

OrderByTotalCallsDesc

Sort by total calls (internal + external), highest first.
If totals are equal, sort by key.

OrderByTotalCallsAsc

Sort by number of calls, lowest number of calls first.
If counts are equal, sort by key.

OrderByExternalCallsDesc

Sort by external calls, highest number of calls first.

OrderByNameAsc

Sort by function name A → Z.

OrderByNameDesc

Sort by function name Z → A.

MostUsed

Returns the first N highest number of calls functions.

LeastUsed

Returns the first N lowest number of calls functions.

Declared Functions Scope (Intentional Limitation)

funcusage analyzes declared functions and methods (func declarations) only.

It intentionally does not track:

  • function literals or closures
  • anonymous functions assigned to variables
  • dynamically constructed call targets
  • method usage through interfaces.

This is a deliberate design choice.
Declared functions form the stable, addressable API surface of a Go codebase — the part that is exported, refactored, reviewed, and reasoned about at scale.
Including function literals would significantly increase noise while providing little actionable insight for structural analysis.

As a result, funcusage may undercount usage in highly functional or closure-heavy code, but it avoids false positives and preserves deterministic, explainable results.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func TraceExit

func TraceExit()

Types

type Analysis

type Analysis []FunctionAnalysis

func (Analysis) ExportedUnused

func (a Analysis) ExportedUnused() Analysis

func (Analysis) ExportedWithNoExternalCalls

func (a Analysis) ExportedWithNoExternalCalls() Analysis

func (Analysis) GroupedByObject

func (a Analysis) GroupedByObject() AnalysisGroupedByObject

func (Analysis) GroupedByPackage

func (a Analysis) GroupedByPackage() AnalysisGroupedByPackage

func (Analysis) GroupedByPackageAndObject

func (a Analysis) GroupedByPackageAndObject() AnalysisGroupedByPackageAndObject

func (Analysis) IsFunction

func (a Analysis) IsFunction() Analysis

func (Analysis) IsMethod

func (a Analysis) IsMethod() Analysis

func (Analysis) LeastUsed

func (a Analysis) LeastUsed(n int) Analysis

func (Analysis) Limit

func (a Analysis) Limit(n int) Analysis

Limit returns at most n elements from the slice. Should be used as the last element in a chain of operations.

Why returning a slice (not a copy) is safe: 1. All preceding operations (Where*, OrderBy*) return NEW slices 2. Limit operates on a slice that exists ONLY within this chain 3. No other code holds references to this intermediate slice 4. FunctionUsage values are immutable (no reference types)

Example safe usage:

result := usage.
    WhereExported().          // ← new slice
    OrderByTotalCallsDesc().  // ← new slice
    Limit(10)                 // ← slice of the last new slice

Memory efficient: O(1) slice operation, no allocation. Follows Go's slice semantics (like u[:n]).

func (Analysis) MethodLike

func (a Analysis) MethodLike(substr string) Analysis

func (Analysis) MethodOf

func (a Analysis) MethodOf(objectName string) Analysis

func (Analysis) MethodOfPointerReceiver

func (a Analysis) MethodOfPointerReceiver() Analysis

func (Analysis) MethodOfValueReceiver

func (a Analysis) MethodOfValueReceiver() Analysis

func (Analysis) MostUsed

func (a Analysis) MostUsed(n int) Analysis

func (Analysis) OrderByExternalCallsDesc

func (a Analysis) OrderByExternalCallsDesc() Analysis

func (Analysis) OrderByNameAsc

func (a Analysis) OrderByNameAsc() Analysis

func (Analysis) OrderByNameDesc

func (a Analysis) OrderByNameDesc() Analysis

func (Analysis) OrderByTotalCallsAsc

func (a Analysis) OrderByTotalCallsAsc() Analysis

func (Analysis) OrderByTotalCallsDesc

func (a Analysis) OrderByTotalCallsDesc() Analysis

func (Analysis) PrintWith

func (a Analysis) PrintWith(printer *Printer)

func (Analysis) String

func (a Analysis) String() string

func (Analysis) Where

func (a Analysis) Where(predicate func(FunctionAnalysis) bool) Analysis

func (Analysis) WhereExported

func (a Analysis) WhereExported() Analysis

func (Analysis) WhereNameIs

func (a Analysis) WhereNameIs(name string) Analysis

func (Analysis) WhereNotTested

func (a Analysis) WhereNotTested() Analysis

func (Analysis) WhereTestedExternally

func (a Analysis) WhereTestedExternally() Analysis

func (Analysis) WhereTestedInternally

func (a Analysis) WhereTestedInternally() Analysis

func (Analysis) WhereUnexported

func (a Analysis) WhereUnexported() Analysis

func (Analysis) WhereUnused

func (a Analysis) WhereUnused() Analysis

type AnalysisGroupedByObject

type AnalysisGroupedByObject map[NameObject]Analysis

func (AnalysisGroupedByObject) PrintWith

func (a AnalysisGroupedByObject) PrintWith(printer *Printer)

type AnalysisGroupedByPackage

type AnalysisGroupedByPackage map[NamePackage]Analysis

func (AnalysisGroupedByPackage) PrintWith

func (a AnalysisGroupedByPackage) PrintWith(printer *Printer)

type AnalysisGroupedByPackageAndObject

type AnalysisGroupedByPackageAndObject map[NamePackage]map[NameObject]Analysis

func (AnalysisGroupedByPackageAndObject) PrintWith

func (a AnalysisGroupedByPackageAndObject) PrintWith(printer *Printer)

type AnalyzeMode

type AnalyzeMode int

AnalyzeMode defines how test files influence the analysis. Only one mode is active at a time. Each mode represents a distinct perspective on how production code and test code interact.

The modes are intentionally *mutually exclusive* to avoid boolean-flag combinatorics and to keep the API deterministic and predictable.

const (
	// ModeDefault analyzes only production code.
	// Test files are ignored entirely:
	//   - test functions are not reported
	//   - calls from tests do not increase usage counts
	//
	// This mode is ideal for pure dead-code detection in production code.
	ModeDefault AnalyzeMode = iota + 1

	// ModeIncludeTestsForCoverage counts calls from test files *into*
	// production code, but does not report test-defined functions.
	//
	// This mode is useful when you want a coverage-like view of which
	// production functions are exercised by tests, without treating test
	// helpers as part of the API surface.
	ModeIncludeTestsForCoverage

	// ModeIncludeTestHelpers includes functions defined in test files
	// (e.g. *_test.go) in the output, and counts their usage normally.
	//
	// This mode is useful for cleaning up test suites, identifying unused
	// test helpers, and understanding the internal API surface of tests.
	ModeIncludeTestHelpers

	// ModeOnlyTestHelpers analyzes *only* functions defined in test files.
	// Production code is ignored entirely.
	//
	// This mode is ideal for:
	//   - finding unused test helpers
	//   - understanding test-only utility layers
	//   - refactoring large test suites
	ModeOnlyTestHelpers

	// ModeOnlyInTestFiles counts only calls that *originate* from test files.
	// Production functions are still reported, but only with test-origin
	// internal/external counts.
	//
	// This mode is useful for:
	//   - identifying production functions used exclusively by tests
	//   - detecting over-exported APIs that are not used by real consumers
	//   - understanding test-to-prod dependency patterns
	ModeOnlyInTestFiles
)

type Analyzer

type Analyzer struct {
	// contains filtered or unexported fields
}

func NewAnalyzer

func NewAnalyzer(atRoot string) (*Analyzer, error)

func (Analyzer) Analyze

func (a Analyzer) Analyze(inMode AnalyzeMode, includeExternal bool) (Analysis, error)

type FunctionAnalysis

type FunctionAnalysis struct {
	// Key is the canonical identity of the function or method.
	// Example (function): "github.com/me/project/pkg.DoThing"
	// Example (method):   "github.com/me/project/pkg.(*User).Save"
	Key string

	// Name is the short name of the function or method (without package or receiver).
	Name string

	// MethodOf highlights the object name for which the method belongs to.
	// Alias for Object, but only populated for methods.
	MethodOf NameObject

	// Position is the source position of the function declaration.
	Position token.Position

	// InternalCount is the number of calls from within the same package.
	// Does not include InternalTestsCount.
	InternalCount int

	// InternalTestsCount is the number of calls from within the same package tests.
	InternalTestsCount int

	// ExternalCount is the number of calls from other packages.
	// Does not include ExternalTestsCount.
	ExternalCount int

	// ExternalTestsCount is the number of calls from other packages tests.
	ExternalTestsCount int
}

FunctionAnalysis describes how a single function or method is used across the module.

type NameObject

type NameObject string

type NamePackage

type NamePackage string

type Printer

type Printer struct {
	// contains filtered or unexported fields
}

func NewPrinter

func NewPrinter() *Printer

func (*Printer) WithMethodOf

func (p *Printer) WithMethodOf() *Printer

func (*Printer) WithName

func (p *Printer) WithName() *Printer

func (*Printer) WithTotal

func (p *Printer) WithTotal() *Printer

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL