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

semantics

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: 11 Imported by: 0

Documentation

Overview

Package semantics extracts deterministic structural facts from raw source bytes (syntax validity, imports, branching metrics, constructor-like patterns, mutates_input) using Tree-sitter grammars.

This package never imports pkg/githubingest, go-github, or ghinstallation: consumers that only analyze raw source bytes never need to build or vendor a GitHub client. GitHub App-authenticated file fetching, if needed, lives in the separate, optional pkg/githubingest package.

Engine: parsing is pure Go, via github.com/odvcencio/gotreesitter (pkg/semantics/internal/engine/gotreesitter.go). No CGO or C toolchain is involved at build or run time.

The "mutates_input" finding is a syntax-based, conservative first-slice detector: it flags caller-visible writes through a function/method's own parameters without any whole-program alias analysis or type inference beyond that parameter's own syntactic declaration. Go detects assignment and update writes rooted at pointer/map/slice parameters. TS/TSX fires on the same underlying idea — a parameter mutated in place is a hidden side effect on the caller's value — but each language's detection is necessarily different: Go parameter types are explicit in the source (pointer_type/map_type/slice_type), so mutableParamTypes reads them directly (go_mutates_input_params.go), while TS/TSX has no required type annotations, so tsParamScope instead tracks which identifiers are bound to (non- destructured, non-rest, non-defaulted) parameters and matches property/ index assignments, update expressions, deletes, and a fixed list of known mutating collection methods (including bracket notation such as arr["push"]) (ts_scope.go). Neither detector tracks aliases assigned to local variables or follows values across function calls.

The "toctou_check_then_act" finding (CWE-367) flags a filesystem existence/state check whose success path later acts on the identical path expression — a non-atomic check-then-act window a concurrent process can race, adapted from a Semgrep TOCTOU rule referenced by GitHub issue #162. TS/TSX (ts_toctou.go) detects a bare existsSync/fs.existsSync call as an if/while condition guarding a same-path act call (readFileSync, writeFileSync, appendFileSync, unlinkSync, rmSync, bare or fs. form); Go (go_toctou.go) detects an os.Stat/os.Lstat call bound by the if statement's own initializer (if _, err := os.Stat(p); err == nil) whose error result is gated by a direct err == nil comparison guarding a same-path act call (os.Open, os.OpenFile, os.Remove, os.RemoveAll, os.ReadFile); a Stat/Lstat call in a preceding statement is never considered. Both detectors match path identity by Tree-sitter source-text equality only — no alias analysis and no path.join/path.resolve reconstruction — so several conservative forms are known, intentional non-detections rather than bugs: a bare existsSync with no following act in the guarded body; an existsSync combined via && or ||, a ternary, or a for-loop condition; a negated if whose act sits only in the else branch (TS/TSX); Go's inverse err != nil early-return form; a Go sentinel gate such as errors.Is(err, fs.ErrNotExist) or os.IsNotExist(err) used as the sole success-path check instead of a direct nil comparison; a Go Stat/Lstat call whose results are discarded rather than bound and gated; and a Go act call reached only through a walk callback with no Stat/Lstat gate of its own. Async-only pairs (e.g. fs.promises.access followed by an awaited read) are out of scope for v1 in both languages.

Concurrency: an *Analyzer holds no backend-held resources between calls — AnalyzeBytes creates and closes its own Parser, Tree, Query, and QueryCursor per call — so a single *Analyzer is safe for concurrent use by multiple goroutines.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyContent indicates the input source had no bytes.
	ErrEmptyContent = errors.New("semantics: empty content")
	// ErrUnsupportedLanguage indicates the requested Language is not
	// supported in v1.
	ErrUnsupportedLanguage = errors.New("semantics: unsupported language")
	// ErrSyntax indicates the parse tree contained one or more syntax
	// errors. Wrapped by *SyntaxError, which carries the specific issues.
	ErrSyntax = errors.New("semantics: syntax error")
	// ErrFileTooLarge indicates the input source exceeded the configured
	// size limit.
	ErrFileTooLarge = errors.New("semantics: file too large")
	// ErrBinaryContent indicates the input source appears to be binary
	// rather than text.
	ErrBinaryContent = errors.New("semantics: binary content")
	// ErrParseFailure indicates Tree-sitter failed to produce a parse tree
	// at all.
	ErrParseFailure = errors.New("semantics: parse failure")
)

Sentinel errors returned by AnalyzeBytes and related entry points.

Functions

This section is empty.

Types

type Analyzer

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

Analyzer runs the parse -> syntax-check -> extract pipeline against raw source bytes. It holds no C-backed resources between calls -- Parser, Tree, Query, and QueryCursor are all created fresh inside AnalyzeBytes -- so a single *Analyzer is safe for concurrent use and has no Close method.

func NewAnalyzer

func NewAnalyzer(opts AnalyzerOptions) (*Analyzer, error)

NewAnalyzer constructs an Analyzer from opts, validating that every requested language is recognized and that MaxFileBytes is non-negative.

func (*Analyzer) AnalyzeBytes

func (a *Analyzer) AnalyzeBytes(ctx context.Context, in FileInput) (*Result, error)

AnalyzeBytes runs the full parse -> syntax-check -> extract pipeline against in.Content. On a clean parse it returns a Result with ParseStatus "ok" and a nil error. On a parse tree containing ERROR/MISSING nodes, it returns a partial Result (ParseStatus "syntax_errors", SyntaxErrors populated, Imports/Metrics/Findings zero-valued) alongside a non-nil error satisfying errors.Is(err, ErrSyntax). Any precondition failure (bad context, empty content, unsupported language, oversized content, binary content) or parse failure returns (nil, err).

Example

ExampleAnalyzer_AnalyzeBytes shows analyzing valid Go source and reading the resulting structural findings.

package main

import (
	"context"
	"fmt"

	"github.com/lousy-agents/coach/pkg/semantics"
)

func main() {
	analyzer, err := semantics.NewAnalyzer(semantics.AnalyzerOptions{})
	if err != nil {
		fmt.Println(err)
		return
	}

	source := []byte(`package greet

func NewGreeter() *Greeter {
	return &Greeter{}
}

type Greeter struct{}
`)

	result, err := analyzer.AnalyzeBytes(context.Background(), semantics.FileInput{
		Path:     "greeter.go",
		Language: semantics.LanguageGo,
		Content:  source,
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(result.ParseStatus)
	for _, f := range result.Findings {
		fmt.Println(f.Kind, f.Name)
	}
}
Output:
ok
constructor_func NewGreeter
pointer_return NewGreeter
Example (SyntaxError)

ExampleAnalyzer_AnalyzeBytes_syntaxError shows the error-matching pattern for source containing syntax errors: AnalyzeBytes returns both a partial *Result and an error satisfying errors.Is(err, semantics.ErrSyntax), from which a *semantics.SyntaxError can be recovered via errors.As.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/lousy-agents/coach/pkg/semantics"
)

func main() {
	analyzer, err := semantics.NewAnalyzer(semantics.AnalyzerOptions{})
	if err != nil {
		fmt.Println(err)
		return
	}

	result, err := analyzer.AnalyzeBytes(context.Background(), semantics.FileInput{
		Language: semantics.LanguageGo,
		Content:  []byte("package main\nfunc {"),
	})

	fmt.Println(result.ParseStatus)
	fmt.Println(errors.Is(err, semantics.ErrSyntax))

	var syntaxErr *semantics.SyntaxError
	if errors.As(err, &syntaxErr) {
		fmt.Println(len(syntaxErr.Issues) == len(result.SyntaxErrors))
	}
}
Output:
syntax_errors
true
true
Example (TypeScript)

ExampleAnalyzer_AnalyzeBytes_typeScript shows analyzing valid TypeScript source, producing exactly one deterministic tight_coupling finding (AC-R6.3).

package main

import (
	"context"
	"fmt"

	"github.com/lousy-agents/coach/pkg/semantics"
)

func main() {
	analyzer, err := semantics.NewAnalyzer(semantics.AnalyzerOptions{})
	if err != nil {
		fmt.Println(err)
		return
	}

	source := []byte(`import { HttpClient } from "./http";

class Greeter {
	constructor() {
		this.client = new HttpClient();
	}
}
`)

	result, err := analyzer.AnalyzeBytes(context.Background(), semantics.FileInput{
		Path:     "greeter.ts",
		Language: semantics.LanguageTypeScript,
		Content:  source,
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(result.ParseStatus)
	for _, f := range result.Findings {
		fmt.Println(f.Kind, f.Name)
	}
}
Output:
ok
tight_coupling HttpClient

type AnalyzerOptions

type AnalyzerOptions struct {
	// Languages restricts AnalyzeBytes to this set of grammars. Empty means
	// "all supported" (LanguageGo, LanguageTypeScript, and LanguageTSX).
	// Any entry that is not a recognized Language makes NewAnalyzer return
	// an error.
	Languages []Language
	// MaxFileBytes caps the size of content AnalyzeBytes will parse. 0 uses
	// the package default (2 MiB); negative values make NewAnalyzer return
	// an error.
	MaxFileBytes int
}

type FileInput

type FileInput struct {
	Path     string
	Language Language
	Content  []byte
}

FileInput is one file to analyze. Path is opaque metadata: it is echoed into the returned Result verbatim (empty is allowed) but never opened.

type Finding

type Finding struct {
	Kind           string   `json:"kind"` // "constructor_func" | "pointer_return" | "mutates_input" | "toctou_check_then_act" (Go); "tight_coupling" | "mutates_input" | "toctou_check_then_act" (TS/TSX)
	Name           string   `json:"name"`
	Location       Location `json:"location"`
	Confidence     string   `json:"confidence,omitempty"`
	Evidence       string   `json:"evidence,omitempty"`
	Recommendation string   `json:"recommendation,omitempty"`
	SuggestedSkill string   `json:"suggested_skill,omitempty"`
}

Finding describes one detected pattern of interest, such as a constructor-like function. Confidence, Evidence, Recommendation, and SuggestedSkill are optional coaching metadata used by findings like "mutates_input"; they're omitted (via omitempty) for findings that don't set them (e.g. "constructor_func", "pointer_return", "tight_coupling").

type FunctionCognitiveComplexity added in v0.2.0

type FunctionCognitiveComplexity struct {
	Name     string   `json:"name"`
	Kind     string   `json:"kind"`
	Location Location `json:"location"`
	Score    int      `json:"score"`
}

FunctionCognitiveComplexity is the Cognitive Complexity score for one scored function body (declaration, method, func lit, or arrow).

type ImportFeature

type ImportFeature struct {
	Path     string   `json:"path"`
	Alias    string   `json:"alias,omitempty"` // alias ident, ".", or "_"
	Location Location `json:"location"`
}

ImportFeature describes one import declaration.

type Language

type Language string

Language identifies the source language a Result was produced from.

const (
	LanguageGo         Language = "go"
	LanguageTypeScript Language = "typescript"
	LanguageTSX        Language = "tsx"
)

Supported Language values.

func LanguageForExtension

func LanguageForExtension(ext string) (Language, bool)

LanguageForExtension maps a file extension (including the leading dot, as returned by filepath.Ext, e.g. ".go") to the Language it corresponds to. Matching is case-insensitive. It returns ("", false) for any extension with no known mapping. This is an additive convenience for callers that want to select a Language from a file path; AnalyzeBytes itself never calls it and continues to route purely on the caller-supplied FileInput.Language.

type Location

type Location struct {
	StartByte uint `json:"start_byte"`
	EndByte   uint `json:"end_byte"`
	StartRow  uint `json:"start_row"`
	StartCol  uint `json:"start_col"`
	EndRow    uint `json:"end_row"`
	EndCol    uint `json:"end_col"`
}

Location is a 0-based byte/row/col span as Tree-sitter reports it.

type ParseStatus

type ParseStatus string

ParseStatus summarizes whether Tree-sitter found any syntax errors while parsing the source. The only values that exist in v1 are "ok" and "syntax_errors".

type ReactComponentFacts added in v0.2.1

type ReactComponentFacts struct {
	Name                   string                       `json:"name"`
	Location               Location                     `json:"location"`
	ClientKind             string                       `json:"client_kind"`
	UseState               []ReactUseStateBinding       `json:"use_state,omitempty"`
	CoordinatedTransitions []ReactCoordinatedTransition `json:"coordinated_transitions,omitempty"`
	WorkspaceBranches      []ReactWorkspaceBranch       `json:"workspace_branches,omitempty"`
	ImperativeUI           []ReactImperativeUICall      `json:"imperative_ui,omitempty"`
	SharedPanelDeps        []ReactSharedPanelDep        `json:"shared_panel_deps,omitempty"`
}

ReactComponentFacts describes one detected React component's state and coordination shape, used by the react_component_orchestration_density codesignal rule. AnalyzeBytes populates Name, Location, ClientKind, UseState, CoordinatedTransitions, WorkspaceBranches, ImperativeUI, and SharedPanelDeps for TypeScript/TSX.

type ReactCoordinatedTransition added in v0.2.1

type ReactCoordinatedTransition struct {
	Name            string   `json:"name"`
	Kind            string   `json:"kind"`
	Location        Location `json:"location"`
	UpdatedBindings []string `json:"updated_bindings"`
}

ReactCoordinatedTransition is a callback (e.g. a useEffect body or event handler) that updates more than one state binding together.

type ReactImperativeUICall added in v0.2.1

type ReactImperativeUICall struct {
	API      string   `json:"api"`
	Location Location `json:"location"`
}

ReactImperativeUICall is a call to an imperative DOM/UI API (e.g. document.getElementById, .focus()) inside a component body.

type ReactSharedPanelDep added in v0.2.1

type ReactSharedPanelDep struct {
	Name   string   `json:"name"`
	Panels []string `json:"panels"`
}

ReactSharedPanelDep is a state binding passed as a prop to more than one distinct child panel component.

type ReactUseStateBinding added in v0.2.1

type ReactUseStateBinding struct {
	Binding  string   `json:"binding"`
	Setter   string   `json:"setter"`
	Location Location `json:"location"`
}

ReactUseStateBinding is one useState() call's binding/setter pair.

type ReactWorkspaceBranch added in v0.2.1

type ReactWorkspaceBranch struct {
	Label    string   `json:"label"`
	Location Location `json:"location"`
}

ReactWorkspaceBranch is one branch of a multi-way conditional that renders a distinct panel/view.

type Result

type Result struct {
	Path                string                        `json:"path"`
	Language            Language                      `json:"language"`
	ParseStatus         ParseStatus                   `json:"parse_status"`
	SyntaxErrors        []SyntaxIssue                 `json:"syntax_errors,omitempty"`
	Imports             []ImportFeature               `json:"imports,omitempty"`
	Metrics             StructuralMetrics             `json:"metrics"`
	Findings            []Finding                     `json:"findings,omitempty"`
	CognitiveComplexity []FunctionCognitiveComplexity `json:"cognitive_complexity,omitempty"`
	// ReactComponents holds per-component React orchestration facts.
	// AnalyzeBytes populates this field for LanguageTypeScript and
	// LanguageTSX only; it stays nil for Go. Entries are ordered by
	// location.start_byte ascending, then by name.
	ReactComponents []ReactComponentFacts `json:"react_components,omitempty"`
}

Result is the top-level output of analyzing one source file.

type StructuralMetrics

type StructuralMetrics struct {
	Ifs                    int `json:"ifs"`
	Fors                   int `json:"fors"`
	ExprSwitches           int `json:"expr_switches"`
	TypeSwitches           int `json:"type_switches"`
	Selects                int `json:"selects"`
	Functions              int `json:"functions"`
	Methods                int `json:"methods"`
	MaxNestingDepth        int `json:"max_nesting_depth"`
	MaxCognitiveComplexity int `json:"max_cognitive_complexity"`
	SumCognitiveComplexity int `json:"sum_cognitive_complexity"`
}

StructuralMetrics counts branching/declaration constructs across a file. TypeSwitches and Selects have no TypeScript/TSX analog and are always 0 for those languages (Go-only fields).

type SyntaxError

type SyntaxError struct {
	Issues []SyntaxIssue
}

SyntaxError reports the syntax issues found while parsing a file. It wraps ErrSyntax so callers can use errors.Is(err, ErrSyntax) without needing to know the concrete type, or errors.As to recover the specific Issues.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

Error summarizes the number of syntax issues found.

func (*SyntaxError) Is

func (e *SyntaxError) Is(target error) bool

Is reports whether target is ErrSyntax, so errors.Is(err, ErrSyntax) matches any *SyntaxError.

type SyntaxIssue

type SyntaxIssue struct {
	Kind     string   `json:"kind"` // "error" | "missing"
	Location Location `json:"location"`
}

SyntaxIssue describes one syntax error or missing-node location found while parsing.

Source Files

Directories

Path Synopsis
internal
engine
Package engine defines the narrow tree-sitter surface pkg/semantics relies on.
Package engine defines the narrow tree-sitter surface pkg/semantics relies on.

Jump to

Keyboard shortcuts

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