lang

package
v0.0.0-...-527d7fd Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package lang defines the per-language analyzer interfaces that diffguard plugs into. A language implementation registers itself via Register() from an init() function; the diffguard CLI blank-imports each language package it supports so the registration happens at process start.

The types and interfaces declared here are the single source of truth for the data passed between the diff parser, the analyzers, and the language back-ends. Keeping them in one package avoids import cycles (analyzer packages import `lang`; language packages import `lang`; neither imports the other).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Register

func Register(l Language)

Register adds a Language to the global registry under its Name(). It panics on duplicate registration because registrations always happen from init() functions: a duplicate is a programming error in the build graph (two packages registering the same language) and should fail loudly before main() runs.

func RegisterDetector

func RegisterDetector(languageName string, d Detector)

RegisterDetector associates a language name with a custom detection function. Both the detector (if present) and the manifest file (if registered) are consulted during Detect; a language matches if either returns true.

func RegisterManifest

func RegisterManifest(filename, languageName string)

RegisterManifest associates a repo-root filename with a language name. A language implementation typically calls this alongside Register():

func init() {
    lang.Register(&Language{})
    lang.RegisterManifest("go.mod", "go")
}

The detector only fires on files that exist at the repository root, so sub-directory manifests (e.g. nested Cargo.toml for workspaces) don't falsely trigger; languages that need subtree scanning should implement their own detection hook via RegisterDetector.

func UnregisterForTest

func UnregisterForTest(name string)

UnregisterForTest is the exported entry point into unregisterForTest. Production code must never call it; it exists so unit tests can keep the registry clean after injecting a fake Language.

Types

type AnnotationScanner

type AnnotationScanner interface {
	ScanAnnotations(absPath string) (map[int]bool, error)
}

AnnotationScanner returns the set of source lines on which mutation generation should be suppressed, based on in-source annotations.

type ComplexityCalculator

type ComplexityCalculator interface {
	AnalyzeFile(absPath string, fc diff.FileChange) ([]FunctionComplexity, error)
}

ComplexityCalculator computes cognitive complexity per function for a single file's changed regions.

type ComplexityScorer

type ComplexityScorer interface {
	ScoreFile(absPath string, fc diff.FileChange) ([]FunctionComplexity, error)
}

ComplexityScorer is a lightweight complexity score for churn weighting. It may share its implementation with ComplexityCalculator or be a faster, coarser approximation — the churn analyzer only needs a number, not a categorized score.

type DeadCodeDetector

type DeadCodeDetector interface {
	FindDeadCode(repoPath string, fc diff.FileChange) ([]UnusedSymbol, error)
}

DeadCodeDetector finds non-exported symbols declared inside a diff's changed regions that have no references in their analyzable scope. The implementation defines the scope: Go scans the whole package directory (including _test.go files); TypeScript scans the single source file because non-exported symbols there are file-local.

The detector is deliberately conservative: it only considers symbols that CANNOT be referenced from outside their scope, so a "no references" result is a real signal rather than a guess. Exported / public symbols, methods (which may satisfy interfaces), and well-known framework hooks (init, main, TestXxx, etc.) are skipped.

type Detector

type Detector func(repoPath string) bool

Detector is a per-language hook that reports whether the given repo root contains a project of this language. Languages use RegisterDetector when manifest-file matching is too coarse — e.g. "package.json + at least one .ts file" for TypeScript.

type FileFilter

type FileFilter struct {
	// Extensions is the list of source file extensions (including the leading
	// dot) that belong to this language, e.g. [".go"] or [".ts", ".tsx"].
	Extensions []string
	// IsTestFile reports whether the given path is a test file that should be
	// excluded from analysis.
	IsTestFile func(path string) bool
	// DiffGlobs is the list of globs passed to `git diff -- <globs>` to scope
	// the diff output to this language's files.
	DiffGlobs []string
}

FileFilter controls which files the diff parser includes and which it classifies as test files. A language exposes its filter as a plain value struct so callers can read the fields directly — the diff parser uses Extensions/IsTestFile/DiffGlobs during path walks.

func (FileFilter) IncludesSource

func (f FileFilter) IncludesSource(path string) bool

IncludesSource reports whether path is an analyzable source file: the extension matches, the file is not a test file, and it doesn't live under a `testdata/` directory. The testdata rule is a Go convention — `go build` ignores any directory literally named `testdata` — and we apply it cross-language so fixtures shared across analyzers don't get flagged as production violations.

func (FileFilter) MatchesExtension

func (f FileFilter) MatchesExtension(path string) bool

MatchesExtension reports whether path has one of the filter's source extensions. It does not apply the IsTestFile check.

type FileSize

type FileSize struct {
	Path  string
	Lines int
}

FileSize holds size info for a single file.

type FunctionComplexity

type FunctionComplexity struct {
	FunctionInfo
	Complexity int
}

FunctionComplexity holds a complexity score for a single function. It's used by both the complexity analyzer and the churn analyzer (via the ComplexityScorer interface, which may reuse the ComplexityCalculator's implementation or provide a lighter approximation).

type FunctionExtractor

type FunctionExtractor interface {
	ExtractFunctions(absPath string, fc diff.FileChange) ([]FunctionSize, *FileSize, error)
}

FunctionExtractor parses a single file and reports its function sizes plus the overall file size.

type FunctionInfo

type FunctionInfo struct {
	File    string
	Line    int
	EndLine int
	Name    string
}

FunctionInfo identifies a function in a source file. It's embedded by the richer FunctionSize and FunctionComplexity types so analyzers can share one identity struct.

type FunctionSize

type FunctionSize struct {
	FunctionInfo
	Lines int
}

FunctionSize holds size info for a single function.

type ImportResolver

type ImportResolver interface {
	DetectModulePath(repoPath string) (string, error)
	ScanPackageImports(repoPath, pkgDir, modulePath string) map[string]map[string]bool
}

ImportResolver drives the deps analyzer. DetectModulePath returns the project-level identifier used to classify internal vs. external imports; ScanPackageImports returns a per-package adjacency list keyed by the importing package's directory-level identifier.

type Language

type Language interface {
	Name() string
	FileFilter() FileFilter
	ComplexityCalculator() ComplexityCalculator
	FunctionExtractor() FunctionExtractor
	ImportResolver() ImportResolver
	ComplexityScorer() ComplexityScorer
	MutantGenerator() MutantGenerator
	MutantApplier() MutantApplier
	AnnotationScanner() AnnotationScanner
	TestRunner() TestRunner
	DeadCodeDetector() DeadCodeDetector
}

Language is the top-level per-language interface. Every language implementation exposes its sub-components through this one type so the orchestrator can iterate `for _, l := range lang.All()` and read out any capability it needs.

func All

func All() []Language

All returns every registered language, sorted by Name(). Deterministic ordering keeps report sections stable across runs and hosts.

func Detect

func Detect(repoPath string) []Language

Detect scans repoPath for per-language manifest files and custom detectors and returns the languages whose signatures match. The returned slice is sorted by Name() so report ordering stays deterministic across calls.

Only languages that are both (a) registered via Register and (b) match via a manifest or detector are returned. That way, adding a new language to the binary without a matching manifest entry is inert — nothing misfires.

func Get

func Get(name string) (Language, bool)

Get returns the language registered under the given name, or (nil, false) if no such language is registered.

type MutantApplier

type MutantApplier interface {
	ApplyMutation(absPath string, site MutantSite) ([]byte, error)
}

MutantApplier produces the mutated source bytes for a given mutation site. Returning nil signals "skip this mutant" — callers should not treat a nil return as an error.

type MutantGenerator

type MutantGenerator interface {
	GenerateMutants(absPath string, fc diff.FileChange, disabledLines map[int]bool) ([]MutantSite, error)
}

MutantGenerator returns the mutation sites produced for a single file's changed regions, after disabled lines have been filtered out.

type MutantSite

type MutantSite struct {
	File        string
	Line        int
	Description string
	Operator    string
}

MutantSite describes a single potential mutation within changed code.

type TestRunConfig

type TestRunConfig struct {
	// RepoPath is the absolute path to the repository root.
	RepoPath string
	// MutantFile is the absolute path to the file containing the mutated
	// source (usually a temp file). For languages that run tests directly on
	// the original tree this may be the path to the original file after the
	// mutation has been written to it.
	MutantFile string
	// OriginalFile is the absolute path to the original (unmutated) source
	// file. Temp-copy runners use this to restore the original after running
	// the tests.
	OriginalFile string
	// Timeout caps the test run's wall-clock duration.
	Timeout time.Duration
	// TestPattern, if non-empty, is passed to the runner's test filter flag
	// (e.g. `go test -run <pattern>`).
	TestPattern string
	// WorkDir is a writable directory private to this run, available for
	// overlay files, backups, etc.
	WorkDir string
	// Index is a monotonically-increasing identifier for the mutant within
	// the current run. Useful for naming per-mutant temp files without
	// collision.
	Index int
}

TestRunConfig carries the parameters needed to run tests against a single mutant. The set of fields is deliberately broad so temp-copy runners (which need WorkDir and Index to write a scratch copy) and overlay-based runners (which only need the MutantFile, OriginalFile, and RepoPath) can share one shape.

type TestRunner

type TestRunner interface {
	RunTest(cfg TestRunConfig) (killed bool, output string, err error)
}

TestRunner executes the language's test suite against a mutated source tree and reports whether any test failed (the mutant was "killed").

type UnusedSymbol

type UnusedSymbol struct {
	File string
	Line int
	Name string
	// Kind is the language-agnostic declaration kind: "func", "var", "const",
	// "type", "class". Languages reuse the same set so the orchestrator can
	// format a uniform message.
	Kind string
}

UnusedSymbol describes a symbol declared in a diff's changed region that has no references within its analyzable scope. Reported by the dead code detector as a warning — false positives are possible (reflective use, frameworks that wire symbols at runtime) so the human is the final judge.

Directories

Path Synopsis
Package evalharness provides helpers shared by per-language evaluation test suites.
Package evalharness provides helpers shared by per-language evaluation test suites.
Package goanalyzer implements the lang.Language interface for Go.
Package goanalyzer implements the lang.Language interface for Go.
Package rustanalyzer implements the lang.Language interface for Rust.
Package rustanalyzer implements the lang.Language interface for Rust.
Package tsanalyzer implements the lang.Language interface for TypeScript (and .tsx).
Package tsanalyzer implements the lang.Language interface for TypeScript (and .tsx).

Jump to

Keyboard shortcuts

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