astutil

package
v1.61.2 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

Documentation

Overview

Package astutil provides shared AST walking utilities for ELPS lisp values.

These helpers are used by both the lint and analysis packages for traversing parsed ELPS expressions.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ArgCount

func ArgCount(sexpr *lisp.LVal) int

ArgCount returns the number of arguments in an s-expression (excluding the head). A nil sexpr yields 0, so it is safe to call on the nil parent Walk passes for top-level expressions.

func CollectFormals

func CollectFormals(formals *lisp.LVal, defs map[string]bool)

CollectFormals extracts symbol names from a formals list, skipping &rest, &optional, and &key markers.

func HeadSymbol

func HeadSymbol(sexpr *lisp.LVal) string

HeadSymbol returns the symbol name at the head of an s-expression, or "". A nil sexpr yields "", so it is safe to call on the nil parent Walk passes for top-level expressions.

func PackageNameArg added in v1.39.0

func PackageNameArg(arg *lisp.LVal) string

PackageNameArg extracts a package name from a use-package or in-package argument. Handles quoted symbols ('testing), bare symbols (testing), and strings ("testing").

func SourceLoc added in v1.52.0

func SourceLoc(v *lisp.LVal) *token.Location

SourceLoc returns v's source location as a pointer, or nil when v is nil or carries no location. The pointer refers to a private copy — mutating it never affects v or any other LVal (lisp.LVal exposes locations by value only; see issue #362).

func SourceOf

func SourceOf(v *lisp.LVal) *lisp.LVal

SourceOf returns the best source location for a node. Prefers the node's own source, falls back to first child's source. Returns nil for a nil node, so it is safe to call on the nil parent Walk passes for top-level expressions.

func SymbolLoc added in v1.61.0

func SymbolLoc(v *lisp.LVal) *token.Location

SymbolLoc returns the location of the NAME a node is written with, which is not always the node's own span.

Two shapes carry a name inside a wider form. The first is a quoted symbol:

rdparser gives the whole 'x form a single node: lisp.Quote copies the symbol and sets its quoted flag rather than wrapping it, so no node stands for the quote, and applyPrefixLocation then moves the surviving node's start back onto the ' so that the form reports the position a reader would point at. That start is right for the FORM and wrong for the NAME, and a consumer that wants to point at, highlight, or REPLACE the identifier needs the latter: textDocument/rename built its edit ranges from the form span and so replaced the quote along with the name, turning (set 'x 1) into (set new 1) -- a different program, applied to the user's file unread (elps#577).

The end of the span is the name's end already (ParseQuote inherits it from the operand), so the name is recovered by measuring len(v.Str) BACK from it rather than by counting ' characters forward. That is exact whatever sits in the gap -- "' x", a newline, a preserved comment -- and it is a no-op on an unquoted symbol, whose span is its name.

It never widens a span and never moves one it cannot account for: a node whose recorded end is missing, or whose name does not fit inside its own span, is returned untouched.

This is NOT elps#463. That was a WIDTH in the wrong unit (token.TokenEnd counted EndCol one per rune onto a byte-valued Col); this is a start that is one reader-prefix too far left, and it is wrong by the same byte for "'x" as for "'é".

The second shape is a STRING LITERAL used as a name, which some def-like forms take: (s:deftype "myint" ...) binds a global called myint, and the node analysis records for it is the literal. Its span covers the quotes, so a rename built from it replaced them too and produced (s:deftype NEW ...) -- a bare symbol where the form requires a string. Here the name is the literal's INTERIOR, so both ends move in by one delimiter.

A string is only handled when its raw span is exactly the decoded value plus two delimiter bytes on one line. Anything else -- an escape, a raw-string form, a line break inside -- means the interior is not recoverable by arithmetic on the length, and the span is returned untouched rather than guessed at.

func UserDefined

func UserDefined(exprs []*lisp.LVal) map[string]bool

UserDefined returns the set of names defined or bound in the source that shadow builtins. This includes:

  • Function/macro names from defun/defmacro
  • Parameter names from defun/defmacro/lambda formals lists

The result is file-global (not scope-aware), which is conservative: it may suppress a valid finding but will never produce a false positive.

func Walk

func Walk(exprs []*lisp.LVal, fn func(node *lisp.LVal, parent *lisp.LVal, depth int))

Walk calls fn for every node in the tree, depth-first. parent is nil for top-level expressions.

func WalkSExprs

func WalkSExprs(exprs []*lisp.LVal, fn func(sexpr *lisp.LVal, depth int))

WalkSExprs calls fn for every unquoted s-expression (potential function call or special form) in the tree.

Types

This section is empty.

Jump to

Keyboard shortcuts

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