analysis

package
v1.46.2 Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2026 License: BSD-3-Clause Imports: 13 Imported by: 0

Documentation

Overview

Package analysis provides scope-aware semantic analysis for ELPS lisp source.

The analyzer builds a scope tree from parsed expressions, resolves symbol references, and identifies unresolved symbols. It is designed to be used by lint analyzers for semantic checks like undefined-symbol and unused-variable.

Index

Constants

View Source
const (
	// DefaultMaxWorkspaceFiles limits the number of .lisp files scanned.
	DefaultMaxWorkspaceFiles = 5000
	// DefaultMaxFileBytes limits individual file size (5 MB).
	DefaultMaxFileBytes = 5 * 1024 * 1024
)

Default limits for workspace scanning.

Variables

This section is empty.

Functions

func ExtractPackageExports

func ExtractPackageExports(reg *lisp.PackageRegistry) map[string][]ExternalSymbol

ExtractPackageExports creates a map of package name to exported symbols from a loaded runtime's package registry. This is used to resolve use-package imports for packages defined in Go (stdlib) that cannot be found by workspace scanning.

func MatchesExclude added in v1.40.0

func MatchesExclude(path string, patterns []string) bool

MatchesExclude returns true if the given path matches any of the exclude patterns. Each pattern is matched against the full path and each path component (directories + filename), using filepath.Match semantics.

func ScanWorkspaceAll added in v1.39.0

func ScanWorkspaceAll(root string) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, allDefs []ExternalSymbol, err error)

ScanWorkspaceAll combines ScanWorkspaceFull and ScanWorkspaceDefinitions into a single pass: each file is parsed once and all three results are extracted from the same AST.

func ScanWorkspaceAllWithConfig added in v1.40.0

func ScanWorkspaceAllWithConfig(root string, scanCfg *ScanConfig) (globals []ExternalSymbol, pkgs map[string][]ExternalSymbol, allDefs []ExternalSymbol, truncated bool, err error)

ScanWorkspaceAllWithConfig is like ScanWorkspaceAll but accepts a ScanConfig for configurable limits. It also returns whether the file list was truncated due to hitting the MaxFiles limit.

Delegates to PrescanWorkspace internally to avoid duplicating the concurrent worker pool logic.

func ScanWorkspacePackages

func ScanWorkspacePackages(root string) (map[string][]ExternalSymbol, error)

ScanWorkspacePackages is like ScanWorkspace but returns a map of package name to exported symbols, suitable for use as Config.PackageExports.

func ScanWorkspaceRefs added in v1.31.0

func ScanWorkspaceRefs(root string, cfg *Config, scanCfg *ScanConfig) map[string][]FileReference

ScanWorkspaceRefs walks the workspace directory tree, performs full analysis on each .lisp file, and extracts cross-file references. The cfg should have ExtraGlobals and PackageExports populated from a prior ScanWorkspaceFull call. Parsing is done concurrently. The optional scanCfg controls file collection limits and excludes.

Returns a map from SymbolKey.String() to FileReference slices.

func ShouldSkipDir added in v1.32.0

func ShouldSkipDir(name string) bool

ShouldSkipDir returns true for directories that should not be walked. It skips hidden directories (e.g. .git, .vscode), underscore-prefixed directories (e.g. _archive, _old), and common dependency/build output directories — but not "." or ".." which represent the current/parent directory.

func SortDefinitions added in v1.40.0

func SortDefinitions(syms []ExternalSymbol)

SortDefinitions sorts external symbols in-place by deterministic priority: lexicographically smallest Source.File, then earliest Source.Line, then earliest Source.Col. Symbols with nil Source are pushed to the end.

Types

type Config

type Config struct {
	// ExtraGlobals are symbols from other files (e.g. workspace scanning).
	ExtraGlobals []ExternalSymbol

	// PackageExports maps package names to their exported symbols.
	// Used to resolve use-package imports from stdlib and workspace packages.
	PackageExports map[string][]ExternalSymbol

	// PackageSymbols maps package names to ALL symbols (exported and
	// non-exported). Used by resolveQualifiedSymbol as a fallback when
	// a qualified reference (pkg:sym) targets a non-exported symbol.
	// ELPS runtime allows qualified access to any symbol in a package,
	// not just exported ones.
	PackageSymbols map[string][]ExternalSymbol

	// DefForms declares additional definition-form heads for embedding programs.
	// Head is matched exactly against the form head symbol. FormalsIndex must
	// point at the parameter list cell. If BindsName is true, NameIndex points
	// at the defined symbol cell and the analyzer registers it using NameKind.
	DefForms []DefFormSpec

	// PackageImports maps package name to imported package names collected
	// from cross-file use-package declarations during workspace prescan.
	// Per-file analysis applies these imports so symbols from packages
	// imported in other files (e.g. main.lisp) are available.
	PackageImports map[string][]string

	// DefaultPackage overrides the default "user" package for bare files
	// (no in-package declaration). Derived from main.lisp's in-package.
	DefaultPackage string

	// WorkspaceRefs maps SymbolKey.String() to cross-file references.
	// When set, analyzers can check whether a symbol is referenced from
	// other files in the workspace.
	WorkspaceRefs map[string][]FileReference

	// Filename is the source file being analyzed.
	Filename string
}

Config controls the behavior of the analyzer.

type DefFormSpec added in v1.38.0

type DefFormSpec struct {
	Head         string
	FormalsIndex int
	BindsName    bool
	NameIndex    int
	NameKind     SymbolKind
}

DefFormSpec describes a custom definition-like form.

type ExternalSymbol

type ExternalSymbol struct {
	Name      string
	Kind      SymbolKind
	Package   string
	Signature *Signature
	Source    *token.Location
	DocString string
}

ExternalSymbol represents a symbol defined in another file.

func ExtractFileDefinitions added in v1.40.0

func ExtractFileDefinitions(source []byte, filename string) []ExternalSymbol

ExtractFileDefinitions parses source and returns all top-level definitions from a single file. This is the public counterpart of extractDefinitions, suitable for incremental workspace index updates.

func FindExternalSymbol added in v1.46.2

func FindExternalSymbol(pkgMap map[string][]ExternalSymbol, pkgName, symName string) *ExternalSymbol

FindExternalSymbol looks up a symbol by name in a package-to-symbols map.

func PreferredDefinition added in v1.40.0

func PreferredDefinition(syms []ExternalSymbol) *ExternalSymbol

PreferredDefinition returns the preferred definition from a set of symbols with the same name. The winner is chosen by: lexicographically smallest Source.File, then earliest Source.Line, then earliest Source.Col. Symbols with nil Source are ranked last. Returns nil if the slice is empty. Note: sorts the input slice in-place as a side effect.

func ScanWorkspace

func ScanWorkspace(root string) ([]ExternalSymbol, error)

ScanWorkspace walks a directory tree, parsing all .lisp files and extracting exported top-level definitions. The result can be used as Config.ExtraGlobals for cross-file symbol resolution.

Files that fail to parse are silently skipped (fault tolerant).

func ScanWorkspaceDefinitions added in v1.39.0

func ScanWorkspaceDefinitions(root string) ([]ExternalSymbol, error)

ScanWorkspaceDefinitions walks a directory tree and returns all top-level definitions, including non-exported symbols. The result is intended for workspace symbol search, not cross-file resolution.

func ScanWorkspaceFull added in v1.27.0

func ScanWorkspaceFull(root string) ([]ExternalSymbol, map[string][]ExternalSymbol, error)

ScanWorkspaceFull walks a directory tree in a single pass, parsing all .lisp files and extracting both global symbols and package exports. It skips directories matched by ShouldSkipDir (hidden, underscore-prefixed, node_modules, vendor, build). Stops collecting after maxWorkspaceFiles. Parsing is done concurrently using a bounded worker pool.

Files that fail to parse are silently skipped (fault tolerant).

type FileReference added in v1.31.0

type FileReference struct {
	SymbolKey       SymbolKey
	Source          *token.Location
	File            string          // absolute path
	Enclosing       string          // name of enclosing function ("" if top-level)
	EnclosingSource *token.Location // definition site of enclosing function
	EnclosingKind   SymbolKind      // kind of enclosing function
}

FileReference represents a cross-file reference to a symbol.

func ExtractFileRefs added in v1.31.0

func ExtractFileRefs(result *Result, filePath string) []FileReference

ExtractFileRefs extracts cross-file-trackable references from an analysis result. Only references to global-scope, non-builtin symbols are included (builtins, special ops, parameters, and locals are skipped).

type Reference

type Reference struct {
	Symbol *Symbol
	Source *token.Location
	Node   *lisp.LVal
}

Reference records a resolved symbol usage.

type Result

type Result struct {
	RootScope  *Scope
	Symbols    []*Symbol
	References []*Reference
	Unresolved []*UnresolvedRef

	// ExtraGlobals are the external symbols that were provided via Config.
	// Stored here so downstream consumers (e.g. lint analyzers) can check
	// for cross-file duplicates without relying on scope lookups that may
	// have been overwritten by local definitions.
	ExtraGlobals []ExternalSymbol

	// WorkspaceRefs maps SymbolKey.String() to cross-file references.
	// Copied from Config. Used by lint analyzers to check whether a symbol
	// is referenced from other workspace files (e.g. unused-function check).
	WorkspaceRefs map[string][]FileReference
}

Result holds the output of semantic analysis.

func Analyze

func Analyze(exprs []*lisp.LVal, cfg *Config) *Result

Analyze performs semantic analysis on a set of parsed expressions. It builds a scope tree, resolves references, and collects unresolved symbols.

func AnalyzeFile added in v1.31.0

func AnalyzeFile(source []byte, filename string, cfg *Config) *Result

AnalyzeFile parses and performs full semantic analysis on a single file. Returns nil if the file fails to parse.

type ScanConfig added in v1.40.0

type ScanConfig struct {
	// MaxFiles is the maximum number of .lisp files to collect.
	// 0 means use DefaultMaxWorkspaceFiles.
	MaxFiles int
	// MaxFileBytes is the maximum size in bytes for a single file.
	// Files exceeding this are skipped. 0 means use DefaultMaxFileBytes.
	MaxFileBytes int64
	// Excludes are glob patterns for files to skip during collection.
	// Patterns are matched against the full path, base name, and each
	// directory component using filepath.Match semantics.
	Excludes []string
	// IncludeDirs are directory names that override ShouldSkipDir.
	// If a directory name matches any entry, it will be walked even if
	// ShouldSkipDir would normally skip it (e.g. "_examples").
	IncludeDirs []string
}

ScanConfig controls workspace file collection limits.

type Scope

type Scope struct {
	Kind           ScopeKind
	Parent         *Scope
	Children       []*Scope
	Symbols        map[string]*Symbol
	PackageSymbols map[string]*Symbol
	PackageImports map[string]map[string]*Symbol

	Node *lisp.LVal // the AST node that introduced this scope
	// contains filtered or unexported fields
}

Scope represents a lexical scope in the source.

func NewScope

func NewScope(kind ScopeKind, parent *Scope, node *lisp.LVal) *Scope

NewScope creates a new scope of the given kind with the given parent.

func ScopeAtPosition added in v1.31.0

func ScopeAtPosition(root *Scope, line, col int) *Scope

ScopeAtPosition returns the innermost scope that contains the given 1-based ELPS line and column. It walks the scope tree depth-first.

func (*Scope) Define

func (s *Scope) Define(sym *Symbol)

Define adds a symbol to this scope.

func (*Scope) DefineImported added in v1.39.0

func (s *Scope) DefineImported(sym *Symbol, pkg string)

DefineImported adds a symbol that should be visible both by package-qualified name and as an unqualified import in the current scope.

func (*Scope) DefineQualifiedOnly added in v1.39.0

func (s *Scope) DefineQualifiedOnly(sym *Symbol)

DefineQualifiedOnly adds a package-qualified symbol without adding it to the bare-name Symbols map. The symbol is still reachable through Lookup/LookupLocal via the bareNameIndex for package-agnostic callers (e.g., minifier, lint arity checker).

func (*Scope) Lookup

func (s *Scope) Lookup(name string) *Symbol

Lookup resolves a symbol by walking the parent chain. Returns nil if the symbol is not found.

func (*Scope) LookupAllLocal added in v1.39.0

func (s *Scope) LookupAllLocal(name string) []*Symbol

LookupAllLocal returns all symbols matching a bare name in this scope, across Symbols and all PackageSymbols entries. Used by call hierarchy to find all package variants of a function name.

func (*Scope) LookupInPackage added in v1.39.0

func (s *Scope) LookupInPackage(name, pkg string) *Symbol

LookupInPackage resolves a symbol by preferring a package-qualified match in the current scope chain before falling back to a bare-name lookup. The bare-name fallback into Symbols is intentional: builtins and user-package symbols are registered with Package == "" and must be reachable when no qualified entry matches.

Unlike Lookup, LookupInPackage does not consult bareNameIndex — a symbol registered only via DefineQualifiedOnly is invisible to LookupInPackage unless the correct package is specified.

func (*Scope) LookupLocal

func (s *Scope) LookupLocal(name string) *Symbol

LookupLocal resolves a symbol only in this scope (not parents).

func (*Scope) LookupLocalInPackage added in v1.39.0

func (s *Scope) LookupLocalInPackage(name, pkg string) *Symbol

LookupLocalInPackage resolves a symbol in the current scope, preferring the package-qualified key when a package is provided.

The bare-name fallback into Symbols is restricted to the default user package. This prevents builtins (Package == "") from blocking package-local definitions like (in-package 'foo) (defun set ...). Unlike LookupInPackage, this method does not consult bareNameIndex.

func (*Scope) LookupLocalVisible added in v1.39.0

func (s *Scope) LookupLocalVisible(name, pkg string) *Symbol

LookupLocalVisible resolves a symbol as it would be seen unqualified in the current scope: first the active package, then imported/bare symbols.

type ScopeKind

type ScopeKind int

ScopeKind classifies the kind of scope.

const (
	ScopeGlobal   ScopeKind = iota // file/module level
	ScopeFunction                  // defun/defmacro body
	ScopeLambda                    // lambda body
	ScopeLet                       // let/let* body
	ScopeFlet                      // flet/labels body
	ScopeDotimes                   // dotimes body
)

func (ScopeKind) String

func (k ScopeKind) String() string

type Signature

type Signature struct {
	Params []lisp.ParamInfo
}

Signature describes the parameter signature of a callable symbol.

func (*Signature) MaxArity

func (sig *Signature) MaxArity() int

MaxArity returns the maximum number of arguments accepted. Returns -1 for variadic functions (those with &rest or &key params).

func (*Signature) MinArity

func (sig *Signature) MinArity() int

MinArity returns the minimum number of arguments required.

type Symbol

type Symbol struct {
	Name       string
	Package    string
	Kind       SymbolKind
	Source     *token.Location // nil for builtins
	Node       *lisp.LVal
	Scope      *Scope
	Signature  *Signature // non-nil for callables
	DocString  string
	References int
	Exported   bool
	External   bool // true for workspace-scanned or package-imported symbols
}

Symbol represents a defined name in a scope.

func FindEnclosingFunction added in v1.31.0

func FindEnclosingFunction(root *Scope, line, col int) *Symbol

FindEnclosingFunction finds the function symbol that contains the given 1-based position by walking the scope tree.

type SymbolKey added in v1.31.0

type SymbolKey struct {
	Package string
	Name    string
	Kind    SymbolKind
}

SymbolKey identifies a symbol across files by name and kind.

func SymbolKeyFromNameKind added in v1.31.0

func SymbolKeyFromNameKind(name string, kind string) SymbolKey

SymbolKeyFromNameKind creates a SymbolKey from raw name and kind strings. The kind string should match SymbolKind.String() output.

func SymbolToKey added in v1.31.0

func SymbolToKey(sym *Symbol) SymbolKey

SymbolToKey derives a SymbolKey from an analysis Symbol.

func (SymbolKey) String added in v1.31.0

func (k SymbolKey) String() string

String returns a lookup key for use in maps.

type SymbolKind

type SymbolKind int

SymbolKind classifies a symbol definition.

const (
	SymVariable  SymbolKind = iota // set, let binding
	SymFunction                    // defun, flet/labels binding
	SymMacro                       // defmacro
	SymParameter                   // function/lambda parameter
	SymSpecialOp                   // special operator (if, cond, etc.)
	SymBuiltin                     // builtin function
	SymType                        // deftype
)

func (SymbolKind) String

func (k SymbolKind) String() string

type UnresolvedRef

type UnresolvedRef struct {
	Name   string
	Source *token.Location
	Node   *lisp.LVal
	// InsideMacroCall is true when the unresolved reference appears inside
	// a user-defined macro call body. Macros may introduce bindings at
	// expansion time that are invisible to static analysis.
	InsideMacroCall bool
}

UnresolvedRef records a symbol usage that could not be resolved.

type WorkspacePrescan added in v1.40.0

type WorkspacePrescan struct {
	// Files is the list of .lisp file paths that were scanned.
	Files []string
	// Truncated is true if the file limit was reached during collection.
	Truncated bool
	// ExportedGlobals are exported top-level definitions only.
	// Used by ScanWorkspaceAllWithConfig for backwards compatibility.
	ExportedGlobals []ExternalSymbol
	// PkgExports maps package name to exported symbols.
	PkgExports map[string][]ExternalSymbol
	// AllDefs are all definitions (exported and non-exported).
	// Typically used as Config.ExtraGlobals for cross-file resolution.
	AllDefs []ExternalSymbol
	// PkgAllSymbols maps package name to ALL symbols (exported and
	// non-exported). Used for qualified symbol resolution since ELPS
	// runtime allows pkg:sym access to any symbol in a package.
	PkgAllSymbols map[string][]ExternalSymbol
	// DefForms are DefFormSpecs derived from defmacro definitions whose
	// names start with "def". These can be injected into Config.DefForms
	// for per-file analysis.
	DefForms []DefFormSpec
	// PackageImports maps package name to the list of packages imported
	// via use-package across all workspace files. This enables per-file
	// analysis to resolve symbols from cross-file use-package declarations.
	PackageImports map[string][]string
	// DefaultPackage is the package declared in main.lisp (if present).
	// Used as the default for bare files (no in-package) so that cross-file
	// resolution works in projects where files inherit the package from
	// load order.
	DefaultPackage string
}

WorkspacePrescan holds the results of a workspace prescan: file definitions, package exports, and DefFormSpecs extracted from defmacro definitions.

func PrescanWorkspace added in v1.40.0

func PrescanWorkspace(root string, scanCfg *ScanConfig) (*WorkspacePrescan, error)

PrescanWorkspace performs a single-pass workspace scan that collects file definitions and extracts DefFormSpecs from def*-prefixed macros. The result provides everything needed to build a Config for per-file analysis, including macro-derived definition forms.

Directories

Path Synopsis
Package perf provides call-graph-based performance analysis for ELPS source files.
Package perf provides call-graph-based performance analysis for ELPS source files.

Jump to

Keyboard shortcuts

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