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 ¶
- Variables
- type Analyzer
- type AnalyzerOptions
- type FileInput
- type Finding
- type FunctionCognitiveComplexity
- type ImportFeature
- type Language
- type Location
- type ParseStatus
- type ReactComponentFacts
- type ReactCoordinatedTransition
- type ReactImperativeUICall
- type ReactSharedPanelDep
- type ReactUseStateBinding
- type ReactWorkspaceBranch
- type Result
- type StructuralMetrics
- type SyntaxError
- type SyntaxIssue
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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"`
}
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
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 {
}
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
¶
- analyzer.go
- analyzer_options.go
- cognitive_complexity.go
- doc.go
- errors.go
- go_calls.go
- go_cognitive_complexity.go
- go_cognitive_complexity_boolean_sequences.go
- go_cognitive_complexity_increments.go
- go_cognitive_complexity_names.go
- go_cognitive_complexity_scorer.go
- go_constructor_funcs.go
- go_expression_lists.go
- go_expressions.go
- go_features.go
- go_identifier_sets.go
- go_imports.go
- go_mutates_input.go
- go_mutates_input_derefs.go
- go_mutates_input_params.go
- go_mutates_input_selectors.go
- go_mutates_input_targets.go
- go_pointer_returns.go
- go_shadowing.go
- go_shadowing_type_switch.go
- go_shadowing_var_declarations.go
- go_toctou.go
- go_toctou_act_calls.go
- go_toctou_stat_gates.go
- language.go
- mutates_input_finding.go
- node_spans.go
- parser.go
- react_components.go
- react_components_candidacy.go
- react_components_default_export.go
- react_components_exports.go
- react_components_module_bindings.go
- react_components_named_exports.go
- react_components_scope.go
- react_components_use_client.go
- react_components_wrappers.go
- react_coordinated_transitions.go
- react_coordinated_transitions_effects.go
- react_coordinated_transitions_handlers.go
- react_coordinated_transitions_kinds.go
- react_coordinated_transitions_setter_calls.go
- react_imperative_ui.go
- react_jsx_attributes.go
- react_jsx_elements.go
- react_local_function_names.go
- react_shared_panel_deps.go
- react_shared_panel_deps_refs.go
- react_use_state.go
- react_use_state_bindings.go
- react_workspace_branches.go
- react_workspace_branches_discriminants.go
- react_workspace_branches_if_chains.go
- react_workspace_branches_labels.go
- react_workspace_branches_tabpanels.go
- react_workspace_branches_ternary_chains.go
- result.go
- syntax_issues.go
- ts_binding_patterns.go
- ts_calls.go
- ts_cognitive_complexity.go
- ts_cognitive_complexity_boolean_sequences.go
- ts_cognitive_complexity_if_chains.go
- ts_cognitive_complexity_increments.go
- ts_cognitive_complexity_names.go
- ts_cognitive_complexity_scorer.go
- ts_cognitive_complexity_targets.go
- ts_expressions.go
- ts_features.go
- ts_imports.go
- ts_mutates_input.go
- ts_mutates_input_method_calls.go
- ts_mutates_input_roots.go
- ts_mutates_input_writes.go
- ts_scope.go
- ts_scope_block_bindings.go
- ts_scope_control_flow_bindings.go
- ts_scope_declarators.go
- ts_scope_function_bindings.go
- ts_scope_parameters.go
- ts_scope_rebound_bindings.go
- ts_scope_statement_bindings.go
- ts_syntax_issues.go
- ts_tight_coupling.go
- ts_toctou.go
- ts_toctou_act_calls.go
- validate.go