Help us shape the future of Go! Take the 2026 Go Developer Survey

projectmodel

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: BSD-2-Clause Imports: 23 Imported by: 0

Documentation

Overview

Package projectmodel defines deterministic, offline, whole-repository structural facts about a codebase. It may depend on pkg/semantics for source parsing, but it never imports pkg/codesignal or any GitHub-related package, so a consumer that only needs raw project facts never pulls in analysis policy or a GitHub client.

Index

Constants

View Source
const (
	DiagBackendUnavailable                            = domain.DiagBackendUnavailable
	DiagRootScopeIncomplete                           = domain.DiagRootScopeIncomplete
	ReachabilityAlgorithm                             = domain.ReachabilityAlgorithm
	TSReachabilityAlgorithm                           = domain.TSReachabilityAlgorithm
	KindPossibleCallReachability                      = domain.KindPossibleCallReachability
	DiagReachabilityBudgetExceeded                    = domain.DiagReachabilityBudgetExceeded
	DiagReachabilitySourceLoadFailed                  = domain.DiagReachabilitySourceLoadFailed
	LayerBypassAlgorithm                              = domain.LayerBypassAlgorithm
	TSLayerBypassAlgorithm                            = domain.TSLayerBypassAlgorithm
	DiagLayerBypassBudgetExceeded                     = domain.DiagLayerBypassBudgetExceeded
	DiagLayerBypassSourceLoadFailed                   = domain.DiagLayerBypassSourceLoadFailed
	DiagLayerBypassAmbiguousLayer                     = domain.DiagLayerBypassAmbiguousLayer
	InclusionRuleTSConfigIncludesNoTestClassification = domain.InclusionRuleTSConfigIncludesNoTestClassification
)
View Source
const (
	// DiagCallUnresolvedInterface marks an interface-method call site: the
	// concrete receiver is unknown statically, so the site contributes no
	// CallFact.
	DiagCallUnresolvedInterface = "project_call_unresolved_interface"
	// DiagCallUnresolvedFunctionValue marks a call through a func-typed
	// variable, field, or parameter with no statically known callee.
	DiagCallUnresolvedFunctionValue = "project_call_unresolved_function_value"
	// DiagCallUnresolvedReflection marks a call dispatched through
	// reflect.Value.Call/CallSlice: SSA resolves the callee to that
	// reflect method itself, not to the reflected target, so the site
	// contributes no CallFact.
	DiagCallUnresolvedReflection = "project_call_unresolved_reflection"
	// DiagCallUnresolvedFrameworkRegistration marks a function/handler
	// value passed to a well-known HTTP registration call (net/http's
	// Handle/HandleFunc or (*http.ServeMux).Handle/HandleFunc) rather than
	// invoked directly: the framework invokes it later, at a call site
	// static analysis cannot see.
	DiagCallUnresolvedFrameworkRegistration = "project_call_unresolved_framework_registration"
	// DiagCallUnresolvedSyntheticWrapper marks a call site whose resolved
	// callee is itself a synthetic wrapper function (fn.Pkg == nil: a
	// bound-method value or a promoted/embedded-method thunk) whose real
	// target is itself local to the snapshot. sortedLocalFunctions excludes
	// the wrapper from the walk, so its own outgoing call is never
	// processed; returning a CallFact into it would dead-end the graph
	// silently, so this diagnostic makes that dead end visible instead. A
	// wrapper whose real target lies outside the snapshot was never going
	// to be walked either way and is not classified here -- it still
	// contributes an ordinary CallFact. A generic instantiation (fn.Pkg ==
	// nil, fn.Origin() != nil) is not classified here either, even when its
	// target is local: sortedLocalFunctions walks fn.Origin() directly (it
	// has its own Pkg and Blocks), so classifyCallSite routes the CallFact
	// to fn.Origin() instead of losing the edge.
	DiagCallUnresolvedSyntheticWrapper = "project_call_unresolved_synthetic_wrapper"
	// DiagCallGraphBudgetExceeded marks a call-graph build that stopped
	// before completion because a budget or context deadline was reached.
	DiagCallGraphBudgetExceeded = "project_callgraph_budget_exceeded"
	// DiagCallGraphBuildFailed marks a root whose Go sources could not be
	// loaded/type-checked, so no call-graph facts could be produced for it.
	DiagCallGraphBuildFailed = "project_callgraph_build_failed"
)

Stable call-site diagnostic codes for CallGraphResult.Coverage.Diagnostics[i].Code.

View Source
const (
	DiagFileSyntaxError    = "project_file_syntax_error"
	DiagFileUnavailable    = "project_file_unavailable"
	DiagFileBudgetExceeded = "project_file_budget_exceeded"
)

Stable file-level diagnostic codes for Model.Coverage.Diagnostics[i].Code, distinct from and never overlapping with the DiagRoot* vocabulary (which describes workspace/module root discovery, not individual files).

View Source
const (
	DiagRootUnavailable     = "project_root_unavailable"
	DiagRootOutsideSnapshot = "project_root_outside_snapshot"
	DiagRootInvalid         = "project_root_invalid"
	DiagRootDuplicate       = "project_root_duplicate"
	DiagRootAmbiguous       = "project_root_ambiguous"
	DiagRootIncomplete      = "project_root_incomplete"
)

Stable root-discovery diagnostic codes for RootDiscoveryResult.Coverage.Diagnostics[i].Code.

View Source
const (
	DiagTSRootUnavailable = "ts_project_root_unavailable"
	DiagTSRootIncomplete  = "ts_project_root_incomplete"
)

Stable TS root-discovery diagnostic codes for TSRootDiscoveryResult.Coverage.Diagnostics[i].Code.

View Source
const CallGraphAlgorithm = "go-callgraph-static@1+golang.org/x/tools@v0.52.0"

CallGraphAlgorithm identifies the pinned static-analysis backend BuildGoCallGraph uses to resolve direct calls, so downstream consumers can attribute coverage/precision claims to the exact method and x/tools version that produced a CallGraphResult's facts. Direct-call resolution uses the same StaticCallee()/IsInvoke() definition golang.org/x/tools/go/callgraph/static.CallGraph uses; that package is also invoked directly, and its node count is recorded in Coverage.Counts["callgraph_static_nodes"] as a cross-check. The version suffix is pinned to go.mod's golang.org/x/tools requirement; see TestCallGraphAlgorithmVersionMatchesGoMod, which fails if they drift.

View Source
const LayerBypassConfidenceHigh = domain.LayerBypassConfidenceHigh
View Source
const ReachabilityConfidenceResolvedDirect = domain.ReachabilityConfidenceResolvedDirect
View Source
const SchemaVersion = domain.SchemaVersion

Variables

View Source
var ReachabilitySinkPatterns = []string{
	"(*database/sql.DB).Exec",
	"(*database/sql.DB).ExecContext",
	"(*database/sql.DB).Query",
	"(*database/sql.DB).QueryContext",
}

ReachabilitySinkPatterns is the pinned, deterministic registry of database-access-shaped callees possible-call-reachability treats as sinks. This is registry policy, not raw ProjectModel data: it is not derived from a snapshot, and extending it is a deliberate versioning decision (bump ReachabilityAlgorithm alongside any change here). IDs use ssa.Function.RelString(nil) form, matching CallFact.To.

Functions

func EffectiveGoBudgets

func EffectiveGoBudgets(b GoBudgets) map[string]int

EffectiveGoBudgets renders b as the frozen budgets map vocabulary shared by RootDiscoveryResult.Coverage.Budgets, Model.Coverage.Budgets, CallGraphResult.Coverage.Budgets, and (with an added search_nodes key) ReachabilityResult.Coverage.Budgets. It is exported so a caller that needs to report this vocabulary before calling DiscoverGoRoots/BuildGoModel (e.g. a zero-value coverage for a pre-discovery failure) can reuse it instead of duplicating the key set.

stderr_bytes always reports 0: the call-graph/reachability path (BuildGoCallGraph, and BuildGoReachability through it) does shell out via go/packages (which invokes the Go toolchain) but does not capture stderr, so the key stays 0; it is reserved so a future backend that does capture stderr can report it without changing the vocabulary.

Types

type BypassLayer

type BypassLayer = domain.BypassLayer

type CallFact

type CallFact = domain.CallFact

type CallGraphOptions

type CallGraphOptions struct {
	Roots   []string
	Budgets GoBudgets
}

CallGraphOptions bounds one BuildGoCallGraph call. Roots optionally scopes the build to specific repository-relative module roots, mirroring GoBuildOptions.Roots; when empty, BuildGoCallGraph builds every module root discovered under snapshot. Budgets.WallTime, MaxGraphNodes, and MaxGraphEdges are enforced here (see GoBudgets); MaxWorkingSetBytes remains reserved.

type CallGraphResult

type CallGraphResult struct {
	// CallFacts holds one entry per direct, statically resolved call edge
	// from a function in one of the snapshot's own packages. From/To are
	// ssa.Function.RelString(nil) identities ("pkgpath.Func" or
	// "(*pkgpath.Type).Method"): stable, deterministic, and independent of
	// the caller's absolute filesystem layout. Sorted by From then To.
	CallFacts []CallFact
	// Algorithm is CallGraphAlgorithm, echoed onto the result so callers do
	// not need to import projectmodel's const block to read it back.
	Algorithm string
	Coverage  Coverage
}

CallGraphResult is the bounded, versioned set of possible-call-reachability facts BuildGoCallGraph produces for a Snapshot. It is intentionally separate from Model: this is raw call-reachability evidence, not a dataflow proof or a Signal, and later work (source/sink registry, path traversal) is layered on top of it, not inside it.

func BuildGoCallGraph

func BuildGoCallGraph(ctx context.Context, snapshot fs.FS, opts CallGraphOptions) (CallGraphResult, error)

BuildGoCallGraph builds bounded, deterministic Go call-graph facts for every module root under snapshot (or opts.Roots, if set) -- and only snapshot. It loads and type-checks the snapshot's Go sources via golang.org/x/tools/go/packages, which invokes the local Go toolchain against the materialized snapshot; that is expected (unlike root discovery, this build has no toolchain-avoidance constraint).

Every ssa.CallInstruction reachable from the snapshot's own packages is classified: a direct, statically resolved call contributes a CallFact; interface dispatch, an unresolved function-value call, a reflect.Value.Call/CallSlice dispatch, a function/handler value passed to a recognized HTTP registration call, and a call into a synthetic bound-method-value or promoted-method thunk whose real target is itself local to the snapshot each contribute no CallFact but do contribute an explicit coverage diagnostic/count, so unresolved call sites stay visible rather than silently dropped; the synthetic-wrapper case is also the only one of these that marks Coverage.Complete false, since it means a real local-to-local call edge was structurally present but never walked.

BuildGoCallGraph never returns a non-nil error for a per-root load failure or for budget/context exhaustion -- those are reported through Coverage.Diagnostics/Coverage.Complete instead, matching BuildGoModel's fail-open-with-diagnostics contract. It returns a non-nil error only if the snapshot itself cannot be materialized to build against.

type Coverage

type Coverage = domain.Coverage

type Diagnostic

type Diagnostic = domain.Diagnostic

type File

type File = domain.File

type GoBudgets

type GoBudgets struct {
	WallTime      time.Duration
	MaxInputFiles int
	MaxInputBytes int64
	MaxGraphNodes int
	MaxGraphEdges int
	// MaxWorkingSetBytes is reserved; not enforced yet.
	MaxWorkingSetBytes int64
}

GoBudgets bounds one Go discovery/build call. A zero field means unbounded for that dimension -- there is no implicit default ceiling; callers that want a safe limit must set it explicitly. MaxInputFiles and MaxInputBytes are enforced by discoverGoProject (the go.work/go.mod walk) and BuildGoModel's source-file read/analyze phase. WallTime, MaxGraphNodes, and MaxGraphEdges are enforced by BuildGoCallGraph (wall clock via context deadline; nodes/edges by bounding its local-function call-site walk). MaxWorkingSetBytes remains reserved for future enforcement: it is echoed back in Coverage.Budgets but has no truncating effect anywhere yet.

type GoBuildOptions

type GoBuildOptions struct {
	Roots   []string
	Budgets GoBudgets
}

GoBuildOptions bounds one BuildGoModel call. Roots optionally scopes discovery to specific repository-relative workspace/module roots; when empty, BuildGoModel auto-discovers every go.work/go.mod root under snapshot.

type ImportEdge

type ImportEdge = domain.ImportEdge

type LayerBypassConfidence

type LayerBypassConfidence = domain.LayerBypassConfidence

type LayerBypassOptions

type LayerBypassOptions struct {
	RequiredLayer  BypassLayer
	Roots          []string
	Budgets        GoBudgets
	MaxSearchNodes int
}

LayerBypassOptions bounds one BuildGoLayerBypass call. Roots and Budgets are forwarded to the underlying BuildGoCallGraph call. MaxSearchNodes additionally bounds the total number of call-graph nodes visited across every source's BFS traversal combined; zero means unbounded.

type LayerBypassResult

type LayerBypassResult = domain.LayerBypassResult

func BuildGoLayerBypass

func BuildGoLayerBypass(ctx context.Context, snapshot fs.FS, opts LayerBypassOptions) (LayerBypassResult, error)

BuildGoLayerBypass finds layer-bypass witnesses: a source/sink pair (the same handler-shaped source and pinned database-sink registry BuildGoReachability uses) with a statically resolved CallFact path that survives deleting every call-graph node whose owning package directory falls under opts.RequiredLayer's prefixes. A surviving path is, by construction, a route from source to sink that never passes through the required layer -- the witness IS the proof, and it is emitted whenever step finds one, regardless of whether a separate compliant path (one that does pass through the required layer) also exists elsewhere in the graph.

A witness is only ever emitted at LayerBypassConfidenceHigh, and only when opts.RequiredLayer is unambiguous (its Prefixes are non-empty and match at least one local package in the snapshot -- see DiagLayerBypassAmbiguousLayer) and the underlying call graph, source identification, and node-classification walk all completed within budget for the pair involved. Any of those conditions failing suppresses the witness for that pair entirely rather than downgrading its confidence; see LayerBypassResult.Coverage for what was and was not evaluated.

BuildGoLayerBypass never returns a non-nil error for a per-root load failure or budget/context exhaustion; those are reported through Coverage.Diagnostics/Coverage.Complete, matching BuildGoReachability's fail-open-with-diagnostics contract.

func BuildTypeScriptLayerBypass deprecated

func BuildTypeScriptLayerBypass(ctx context.Context, snapshot fs.FS, meta SnapshotMeta, opts TSSidecarOptions, requiredLayer BypassLayer) (LayerBypassResult, error)

Deprecated: call BuildTypeScriptModelViaSidecar once and pass the Model to BuildTypeScriptLayerBypassFromModel / BuildTypeScriptReachabilityFromModel instead, so multiple derivations share one sidecar round trip.

func BuildTypeScriptLayerBypassFromModel added in v0.5.0

func BuildTypeScriptLayerBypassFromModel(budgetCtx context.Context, model Model, requiredLayer BypassLayer) LayerBypassResult

BuildTypeScriptLayerBypassFromModel's per-pair witness evaluation is never gated on model.Coverage.Complete or any reachability-gap diagnostic: an unrelated source's own routine gap (e.g. a handler delegating one hop into a helper/service function) must never suppress a different, already-fully-resolved witness. LayerBypassResult.Coverage.Complete is instead computed independently below, so incompleteness is still reported honestly at the result level even though it never suppresses a witness.

That result-level Coverage.Complete still conflates two distinct causes under one diagnostic code (DiagLayerBypassAmbiguousLayer covers both tsLayerBypassRequiredLayerNodes' genuine ambiguous-layer search failure and tsLayerBypassSearchFromSource's single-witness unclassified-node case; see tsLayerBypassDiagnostics). A caller that treats this LayerBypassResult as the customer-facing verdict must re-derive completeness per findable witness the way internal/codesignalcli/ts_project_revision.go's tsBypassCoverageForFold does, rather than trusting this field directly.

type LayerBypassStep

type LayerBypassStep = domain.LayerBypassStep

type LayerBypassWitness

type LayerBypassWitness = domain.LayerBypassWitness

type Model

type Model = domain.Model

func BuildGoModel

func BuildGoModel(snapshot fs.FS, meta SnapshotMeta, opts GoBuildOptions) (Model, error)

BuildGoModel builds a Model's raw Go workspace/module/package/file/import facts by reading snapshot -- and only snapshot. A file with syntax errors keeps its File entry (so callers still see it exists) but contributes no import edges; a DiagFileSyntaxError diagnostic is recorded instead of failing the whole build. BuildGoModel never returns a non-nil error for per-file or per-root problems -- only for failures in the semantics analyzer itself that indicate a programming error (e.g. misconfigured AnalyzerOptions).

func BuildTypeScriptModelViaSidecar

func BuildTypeScriptModelViaSidecar(ctx context.Context, snapshot fs.FS, meta SnapshotMeta, opts TSSidecarOptions) (Model, error)

type Module

type Module = domain.Module

type Package

type Package = domain.Package

type ProjectScope added in v0.5.0

type ProjectScope = domain.ProjectScope

func ProjectScopeFromModel added in v0.5.0

func ProjectScopeFromModel(model Model, policy ProjectScopePolicy) (ProjectScope, error)

type ProjectScopePolicy added in v0.5.0

type ProjectScopePolicy struct {
	Roots  []string
	Layers []ProjectScopePolicyLayer
}

ProjectScopePolicy is not pkg/codesignal.LayerPolicy. This package is an adapter and must not import the use-case package. Keep this type's Layers field shape in sync with ArchitectureLayer by hand if that one changes.

type ProjectScopePolicyLayer added in v0.5.0

type ProjectScopePolicyLayer struct {
	Name     string
	Prefixes []string
}

type ProjectScopeRoot added in v0.5.0

type ProjectScopeRoot = domain.ProjectScopeRoot

type ReachabilityConfidence

type ReachabilityConfidence = domain.ReachabilityConfidence

type ReachabilityFact

type ReachabilityFact = domain.ReachabilityFact

type ReachabilityOptions

type ReachabilityOptions struct {
	Roots          []string
	Budgets        GoBudgets
	MaxSearchNodes int
}

ReachabilityOptions bounds one BuildGoReachability call. Roots and Budgets are forwarded to the underlying BuildGoCallGraph call. MaxSearchNodes additionally bounds the total number of call-graph nodes visited across every source's BFS traversal combined; zero means unbounded.

type ReachabilityResult

type ReachabilityResult = domain.ReachabilityResult

func BuildGoReachability

func BuildGoReachability(ctx context.Context, snapshot fs.FS, opts ReachabilityOptions) (ReachabilityResult, error)

BuildGoReachability finds possible-call-reachability facts between BuildGoCallGraph's direct CallFacts edges and the built-in source/sink registry: a source is any local function whose signature is identical to net/http.HandlerFunc's func(http.ResponseWriter, *http.Request) (the same shape evidenced by BuildGoCallGraph's framework-registration diagnostics, but resolved independently here since CallFact's From/To strings carry no signature information); a sink is any CallFact target matching ReachabilitySinkPatterns.

For every identified source, a single deterministic BFS over the call-graph's adjacency (sorted per node, so tied shortest paths always resolve the same way) finds the shortest path to every reachable sink. A source/sink pair with no path within the traversal contributes no ReachabilityFact -- see ReachabilityFact's doc comment: that is never a "safe" claim, only "not found within Coverage".

BuildGoReachability never returns a non-nil error for a per-root load failure or budget/context exhaustion; those are reported through Coverage.Diagnostics/Coverage.Complete, matching BuildGoCallGraph's fail-open-with-diagnostics contract.

Coverage.Counts["memory_bytes"] is a coarse proxy, not a measurement of this call's own footprint: it is the delta of runtime.MemStats.TotalAlloc (a process-wide, monotonically increasing cumulative-allocation counter) taken before and after the call, so it includes GC'd churn and any concurrent allocation elsewhere in the process during the call. Treat it as an upper bound, not a peak or exclusive figure.

func BuildTypeScriptReachability deprecated

func BuildTypeScriptReachability(ctx context.Context, snapshot fs.FS, meta SnapshotMeta, opts TSSidecarOptions) (ReachabilityResult, error)

Deprecated: call BuildTypeScriptModelViaSidecar once and pass the Model to BuildTypeScriptReachabilityFromModel / BuildTypeScriptLayerBypassFromModel instead, so multiple derivations share one sidecar round trip.

func BuildTypeScriptReachabilityFromModel added in v0.5.0

func BuildTypeScriptReachabilityFromModel(model Model) ReachabilityResult

type ReachabilityStep

type ReachabilityStep = domain.ReachabilityStep

type RootDiscoveryResult

type RootDiscoveryResult struct {
	Roots    []string
	Complete bool
	Coverage Coverage
}

RootDiscoveryResult is the public, candidate-only-consumer discovery result (issue #210, consumed later by issue #220's initializer).

func DiscoverGoRoots

func DiscoverGoRoots(snapshot fs.FS, budgets GoBudgets) (RootDiscoveryResult, error)

DiscoverGoRoots walks snapshot for go.work/go.mod files and returns every distinct workspace/module root. A directory with a go.mod is always a root; a directory with a go.work is a root only if at least one of its use directives resolves onto a module root (an empty or fully-escaping workspace contributes nothing on its own). The walk never descends into testdata/, vendor/, or dot-prefixed directories, matching the go tool's own convention, so a go.mod/go.work nested inside one of those is never reported as a root. It never returns a non-nil error: unreadable snapshots and budget truncation are reported through Coverage.Diagnostics/RootDiscoveryResult.Complete instead.

type RootScope added in v0.5.0

type RootScope = domain.RootScope

type Snapshot

type Snapshot = domain.Snapshot

type SnapshotMeta

type SnapshotMeta struct {
	Revision           string
	TreeID             string
	ConfigDigest       string
	BackendDigest      string
	BuildContextDigest string
	Repository         string
}

SnapshotMeta carries revision/config/backend identities the caller already resolved (e.g. from Git) needed to populate Model.Snapshot. BuildGoModel does not compute these itself.

type TSRootDiscoveryResult added in v0.5.0

type TSRootDiscoveryResult struct {
	Roots      []string
	Candidates []string
	Complete   bool
	Coverage   Coverage
}

TSRootDiscoveryResult is DiscoverTSRoots' result. Roots names every directory holding a tsconfig.json (the same exact-basename match tsWorkspaceFactsFromCollected uses to build Model.Workspaces); Candidates names every directory holding a package.json but no tsconfig.json of its own -- a place a guided policy-authoring flow may offer to configure, not a project Coach has confirmed. Both are flat, deduplicated, sorted directory paths and nothing else: DiscoverTSRoots never groups, orders, or names them by architectural role, so this type has no field capable of expressing a layer boundary -- do not add a grouping/labeling field here.

func DiscoverTSRoots added in v0.5.0

func DiscoverTSRoots(snapshot fs.FS, budgets GoBudgets) (TSRootDiscoveryResult, error)

DiscoverTSRoots walks snapshot for tsconfig.json/package.json files and reports the directories that contain them. It requires no TypeScript compiler and never shells out to one: this is a pure fs.FS walk, mirroring DiscoverGoRoots' no-compiler contract for Go. It never descends into node_modules/ or dot-prefixed directories. It never returns a non-nil error: unreadable snapshots and budget truncation are reported through Coverage.Diagnostics/TSRootDiscoveryResult.Complete instead.

type TSSidecarOptions

type TSSidecarOptions struct {
	BinaryPath string
	Path       string
	Dir        string
	Roots      []string
	Args       []string
	Timeout    time.Duration
	Budgets    GoBudgets
}

type Workspace

type Workspace = domain.Workspace

Directories

Path Synopsis
internal
ssaload
Package ssaload is pkg/projectmodel's driven adapter for building Go SSA programs: it writes an fs.FS snapshot to a temporary directory and loads it through golang.org/x/tools/go/packages, which runs the local Go toolchain against those files.
Package ssaload is pkg/projectmodel's driven adapter for building Go SSA programs: it writes an fs.FS snapshot to a temporary directory and loads it through golang.org/x/tools/go/packages, which runs the local Go toolchain against those files.
tssidecar
Package tssidecar runs the pinned Node/TypeScript project sidecar as a child process for one internal/projectbridge request.
Package tssidecar runs the pinned Node/TypeScript project sidecar as a child process for one internal/projectbridge request.

Jump to

Keyboard shortcuts

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