Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

852 lines
26 KiB
Go

// Package elide compresses source code by replacing every function or
// method body with a one-line stub while preserving signatures,
// imports, top-level declarations, comments, and structure.
//
// Tree-sitter parses the source with the appropriate grammar for the
// language, then the walker collects the byte ranges of every
// function/method body and rebuilds the buffer with each range
// replaced by a per-language stub:
//
// - Brace languages (Go, TS, JS, Java, C, C++, C#, Rust, PHP, Bash,
// Kotlin, Scala): "{ /* N lines elided */ }"
// - Indent languages (Python): "... # N lines elided"
// - Ruby: "# N lines elided"
//
// The package is intentionally fail-soft: an unsupported language, a
// missing grammar binding, or a tree-sitter parse failure all return
// the original source unchanged with a sentinel error. Callers (the
// compress_bodies path of get_symbol_source / get_editing_context /
// read_file) fall back to the raw source in that case.
package elide
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"
sitter "github.com/zzet/gortex/internal/parser/tsitter"
bashlang "github.com/zzet/gortex/internal/parser/tsitter/bash"
clang "github.com/zzet/gortex/internal/parser/tsitter/c"
cpplang "github.com/zzet/gortex/internal/parser/tsitter/cpp"
csharplang "github.com/zzet/gortex/internal/parser/tsitter/csharp"
elixirlang "github.com/zzet/gortex/internal/parser/tsitter/elixir"
golang "github.com/zzet/gortex/internal/parser/tsitter/golang"
javalang "github.com/zzet/gortex/internal/parser/tsitter/java"
jslang "github.com/zzet/gortex/internal/parser/tsitter/javascript"
kotlinlang "github.com/zzet/gortex/internal/parser/tsitter/kotlin"
phplang "github.com/zzet/gortex/internal/parser/tsitter/php"
pylang "github.com/zzet/gortex/internal/parser/tsitter/python"
rubylang "github.com/zzet/gortex/internal/parser/tsitter/ruby"
rustlang "github.com/zzet/gortex/internal/parser/tsitter/rust"
scalalang "github.com/zzet/gortex/internal/parser/tsitter/scala"
tsxlang "github.com/zzet/gortex/internal/parser/tsitter/tsx"
tslang "github.com/zzet/gortex/internal/parser/tsitter/typescript"
)
const parseTimeout = 5 * time.Second
var (
// ErrUnsupportedLang is returned when the language is unknown or no
// grammar binding is wired in. The original source is returned
// alongside the error.
ErrUnsupportedLang = errors.New("elide: unsupported language")
// ErrParse is returned when tree-sitter could not parse the source.
// The original source is returned alongside the error.
ErrParse = errors.New("elide: parse failed")
)
// Decl describes one function or method declaration the elider is
// about to compress. A Keep predicate (see Options) inspects it to
// decide whether that body is left verbatim or replaced by a stub.
type Decl struct {
// Name is the best-effort declaration name. It is "" when the
// grammar gives no easy name handle (anonymous functions, some
// Kotlin/Elixir shapes) — such decls are matchable only by line
// range, never by name.
Name string
// Kind is the tree-sitter node type of the declaration.
Kind string
// StartRow and EndRow are the 0-based row span of the whole
// declaration node (signature through closing brace).
StartRow int
EndRow int
}
// Fidelity is the per-declaration verdict the elider acts on: keep the
// whole declaration verbatim, compress its body to a stub, or omit the
// declaration entirely behind a one-line marker.
type Fidelity int
const (
// FidelityCompress replaces the declaration's body with a stub —
// the default, identical to the legacy compress behaviour.
FidelityCompress Fidelity = iota
// FidelityFull leaves the whole declaration (signature + body)
// verbatim. Equivalent to a Keep predicate returning true.
FidelityFull
// FidelityOmit removes the whole declaration — signature and body
// both — and leaves a single-line `// <name> omitted` marker in
// its place.
FidelityOmit
)
// Options tunes CompressWith. The zero value elides every body, which
// is exactly what the bare Compress entry point does.
type Options struct {
// Keep, when non-nil, is consulted once per elidable declaration.
// Returning true leaves that declaration's body verbatim; every
// other body is still stubbed. A nil Keep elides everything.
//
// Keep is retained for back-compat and composes with Decide: a
// Keep that returns true forces FidelityFull regardless of what
// Decide would have returned.
Keep func(Decl) bool
// Decide, when non-nil, returns the per-declaration fidelity
// verdict (full / compress / omit). It generalises the binary
// Keep into a three-way choice. A nil Decide means "compress
// every body" (the legacy behaviour). Keep is layered on top:
// when Keep(d) is true the verdict is forced to FidelityFull.
Decide func(Decl) Fidelity
}
// verdict resolves the effective fidelity for a declaration, folding
// the legacy Keep predicate over the Decide function. Keep wins when
// it fires (back-compat: a kept declaration is always full).
func (o Options) verdict(d Decl) Fidelity {
if o.Keep != nil && o.Keep(d) {
return FidelityFull
}
if o.Decide != nil {
return o.Decide(d)
}
return FidelityCompress
}
// KeepLineRanges builds a Keep predicate that retains any declaration
// whose row span intersects one of the given 1-based inclusive line
// ranges. It is the precise matching path: callers resolve symbol IDs
// to graph line ranges and pass them here. Returns nil (elide all)
// when no ranges are supplied.
func KeepLineRanges(ranges [][2]int) func(Decl) bool {
if len(ranges) == 0 {
return nil
}
cp := make([][2]int, len(ranges))
copy(cp, ranges)
return func(d Decl) bool {
ds, de := d.StartRow+1, d.EndRow+1
for _, r := range cp {
lo, hi := r[0], r[1]
if hi < lo {
lo, hi = hi, lo
}
if ds <= hi && lo <= de {
return true
}
}
return false
}
}
// KeepNames builds a Keep predicate that retains any declaration whose
// extracted name is in the set. Matching is case-sensitive. Returns
// nil (elide all) when the set is effectively empty.
func KeepNames(names []string) func(Decl) bool {
set := make(map[string]struct{}, len(names))
for _, n := range names {
if n != "" {
set[n] = struct{}{}
}
}
if len(set) == 0 {
return nil
}
return func(d Decl) bool {
if d.Name == "" {
return false
}
_, ok := set[d.Name]
return ok
}
}
// KeepAny combines predicates into one that keeps a declaration when
// any constituent predicate keeps it. nil predicates are dropped;
// KeepAny returns nil when nothing usable is left.
func KeepAny(preds ...func(Decl) bool) func(Decl) bool {
var kept []func(Decl) bool
for _, p := range preds {
if p != nil {
kept = append(kept, p)
}
}
if len(kept) == 0 {
return nil
}
return func(d Decl) bool {
for _, p := range kept {
if p(d) {
return true
}
}
return false
}
}
type stubStyle int
const (
stubBrace stubStyle = iota // { /* N lines elided */ }
stubPython // ... # N lines elided
stubRuby // # N lines elided
stubElixir // # N lines elided (between `do` and `end`)
)
// languageSpec describes how to find and elide function bodies in one
// tree-sitter grammar.
type languageSpec struct {
grammarFn func() *sitter.Language
// findBody locates the body node to elide inside a parent
// declaration node. Returns nil when the declaration has no
// elidable body (e.g. an abstract method, an arrow function with
// expression body).
findBody func(node *sitter.Node) *sitter.Node
// parents lists the node kinds that are function/method
// declarations whose body should be elided.
parents map[string]struct{}
style stubStyle
}
func parents(kinds ...string) map[string]struct{} {
m := make(map[string]struct{}, len(kinds))
for _, k := range kinds {
m[k] = struct{}{}
}
return m
}
// fieldFinder builds a findBody that looks up a field by name and
// accepts only bodies whose kind is in the allowlist.
func fieldFinder(field string, bodyKinds ...string) func(*sitter.Node) *sitter.Node {
allow := make(map[string]struct{}, len(bodyKinds))
for _, k := range bodyKinds {
allow[k] = struct{}{}
}
return func(node *sitter.Node) *sitter.Node {
body := node.ChildByFieldName(field)
if body == nil {
return nil
}
if _, ok := allow[body.Type()]; !ok {
return nil
}
return body
}
}
// namedChildFinder builds a findBody that scans named children for
// the first one whose kind is in the allowlist. Used by Ruby's
// `method` (body is the `body_statement` named child, not a field).
func namedChildFinder(bodyKinds ...string) func(*sitter.Node) *sitter.Node {
allow := make(map[string]struct{}, len(bodyKinds))
for _, k := range bodyKinds {
allow[k] = struct{}{}
}
return func(node *sitter.Node) *sitter.Node {
n := int(node.NamedChildCount())
for i := range n {
c := node.NamedChild(i)
if c == nil {
continue
}
if _, ok := allow[c.Type()]; ok {
return c
}
}
return nil
}
}
// kotlinBodyFinder locates the block body of a Kotlin function. The
// tree-sitter-kotlin grammar wraps the body in a `function_body` node
// (no `body` field name). A block-bodied function has function_body's
// source starting with `{`; the `fun foo() = expr` short form starts
// with `=` and is left untouched (already minimal).
func kotlinBodyFinder(node *sitter.Node) *sitter.Node {
n := int(node.NamedChildCount())
for i := range n {
c := node.NamedChild(i)
if c == nil {
continue
}
if c.Type() != "function_body" {
continue
}
// Look at the first non-whitespace byte of the function_body
// substring to detect block- vs expression-bodied. We don't
// have the source bytes in scope here, so peek at the first
// named child instead: a block body has a `statements` child
// or is empty; an expression body has a single expression-kind
// child (binary_expression, simple_identifier, call_expression, …).
if c.NamedChildCount() == 0 {
// Empty block (`fun foo() {}`); the function_body itself
// is `{}` and elision will replace it with the stub.
return c
}
first := c.NamedChild(0)
if first != nil && first.Type() == "statements" {
return c
}
// Expression-bodied — already minimal, skip.
return nil
}
return nil
}
// elixirCallBody handles Elixir's `def name do ... end` shape. Tree-sitter-elixir
// represents a function as a `call` whose first child identifier is
// "def"/"defp"/"defmacro" and whose last argument is a `do_block`. We
// return the `do_block` for elision.
func elixirCallBody(node *sitter.Node) *sitter.Node {
first := node.NamedChild(0)
if first == nil {
return nil
}
if first.Type() != "identifier" {
return nil
}
// Caller would not call this if the node isn't a `call`, but we
// still gate by macro name to avoid eliding arbitrary calls.
// (The src bytes aren't here, so we compare via byte range —
// kept simple: this function is only invoked when the parent
// kind is "call" and the caller has already filtered by the
// macro identifier via a separate hook, so the gate would
// actually live elsewhere. See specs[elixir].findBody below.)
for i := int(node.NamedChildCount()) - 1; i >= 0; i-- {
c := node.NamedChild(i)
if c == nil {
continue
}
if c.Type() == "do_block" {
return c
}
}
return nil
}
// elixirCallFilter is a per-language filter applied on top of the
// parents map: it returns true only when a `call` node looks like a
// `def`-family macro call. Used to avoid eliding arbitrary calls in
// elixir source.
func elixirCallFilter(node *sitter.Node, src []byte) bool {
first := node.NamedChild(0)
if first == nil || first.Type() != "identifier" {
return false
}
name := first.Content(src)
switch name {
case "def", "defp", "defmacro", "defmacrop", "defguard", "defguardp":
return true
}
return false
}
var (
specsOnce sync.Once
specs map[string]*languageSpec
)
func initSpecs() {
specs = map[string]*languageSpec{
"go": {
grammarFn: golang.GetLanguage,
parents: parents("function_declaration", "method_declaration"),
findBody: fieldFinder("body", "block"),
style: stubBrace,
},
"typescript": {
grammarFn: tslang.GetLanguage,
parents: parents(
"function_declaration",
"method_definition",
"generator_function_declaration",
"function_expression",
"arrow_function",
),
// Only elide block-bodied arrow functions; expression-bodied
// arrows are already minimal.
findBody: fieldFinder("body", "statement_block"),
style: stubBrace,
},
"tsx": {
grammarFn: tsxlang.GetLanguage,
parents: parents(
"function_declaration",
"method_definition",
"generator_function_declaration",
"function_expression",
"arrow_function",
),
findBody: fieldFinder("body", "statement_block"),
style: stubBrace,
},
"javascript": {
grammarFn: jslang.GetLanguage,
parents: parents(
"function_declaration",
"method_definition",
"generator_function_declaration",
"function_expression",
"arrow_function",
),
findBody: fieldFinder("body", "statement_block"),
style: stubBrace,
},
"python": {
grammarFn: pylang.GetLanguage,
parents: parents("function_definition"),
findBody: fieldFinder("body", "block"),
style: stubPython,
},
"rust": {
grammarFn: rustlang.GetLanguage,
parents: parents("function_item"),
findBody: fieldFinder("body", "block"),
style: stubBrace,
},
"java": {
grammarFn: javalang.GetLanguage,
parents: parents(
"method_declaration",
"constructor_declaration",
),
findBody: fieldFinder("body", "block", "constructor_body"),
style: stubBrace,
},
"c": {
grammarFn: clang.GetLanguage,
parents: parents("function_definition"),
findBody: fieldFinder("body", "compound_statement"),
style: stubBrace,
},
"cpp": {
grammarFn: cpplang.GetLanguage,
parents: parents("function_definition", "template_function"),
findBody: fieldFinder("body", "compound_statement"),
style: stubBrace,
},
"csharp": {
grammarFn: csharplang.GetLanguage,
parents: parents(
"method_declaration",
"constructor_declaration",
"destructor_declaration",
"operator_declaration",
"local_function_statement",
"conversion_operator_declaration",
),
findBody: fieldFinder("body", "block"),
style: stubBrace,
},
"kotlin": {
grammarFn: kotlinlang.GetLanguage,
parents: parents("function_declaration", "anonymous_function"),
findBody: kotlinBodyFinder,
style: stubBrace,
},
"scala": {
grammarFn: scalalang.GetLanguage,
parents: parents(
"function_definition",
"function_declaration",
),
// tree-sitter-scala wires the body as a plain `block` named
// child (no `body` field). The `def x = expr` short form has
// no block child and is left untouched.
findBody: namedChildFinder("block", "indented_block"),
style: stubBrace,
},
"php": {
grammarFn: phplang.GetLanguage,
parents: parents(
"function_definition",
"method_declaration",
),
findBody: fieldFinder("body", "compound_statement"),
style: stubBrace,
},
"ruby": {
grammarFn: rubylang.GetLanguage,
parents: parents(
"method",
"singleton_method",
),
findBody: namedChildFinder("body_statement"),
style: stubRuby,
},
"bash": {
grammarFn: bashlang.GetLanguage,
parents: parents("function_definition"),
findBody: fieldFinder("body", "compound_statement"),
style: stubBrace,
},
"elixir": {
grammarFn: elixirlang.GetLanguage,
parents: parents("call"),
findBody: elixirCallBody,
style: stubElixir,
},
}
}
func getSpec(lang string) *languageSpec {
specsOnce.Do(initSpecs)
return specs[normalizeLang(lang)]
}
// Languages reports the canonical language codes elide knows how to
// compress. The returned slice is sorted and safe for the caller to
// retain; it is recomputed on every call so test-only manipulations
// don't bleed across goroutines.
func Languages() []string {
specsOnce.Do(initSpecs)
out := make([]string, 0, len(specs))
for k := range specs {
out = append(out, k)
}
sort.Strings(out)
return out
}
func normalizeLang(lang string) string {
switch strings.ToLower(lang) {
case "c++", "cpp", "cxx", "cc":
return "cpp"
case "c#", "csharp", "cs":
return "csharp"
case "js", "javascript", "jsx":
return "javascript"
case "ts", "typescript":
return "typescript"
case "tsx":
return "tsx"
case "py", "python":
return "python"
case "rb", "ruby":
return "ruby"
case "rs", "rust":
return "rust"
case "sh", "bash", "shell":
return "bash"
case "kt", "kotlin":
return "kotlin"
case "ex", "exs", "elixir":
return "elixir"
}
return strings.ToLower(lang)
}
// IsSupported reports whether the language is wired in. The caller
// can use this to decide whether to pass compress_bodies=true upstream
// or skip the round-trip.
func IsSupported(lang string) bool {
return getSpec(lang) != nil
}
// Compress returns a copy of src with every function or method body
// replaced by a per-language stub. The original src and a sentinel
// error are returned when the language is unsupported, when no
// grammar binding is available, or when tree-sitter parsing fails.
//
// Top-level constants, types, fields, imports, and comments are
// preserved verbatim because they have no body to elide; everything
// outside the collected body ranges is copied through byte-for-byte.
func Compress(src []byte, lang string) ([]byte, error) {
return CompressWith(src, lang, Options{})
}
// CompressWith is Compress with caller-supplied Options. When
// opts.Keep is non-nil it is consulted for every elidable
// declaration; returning true leaves that body verbatim while every
// other body is still stubbed. CompressWith(src, lang, Options{}) is
// identical to Compress.
func CompressWith(src []byte, lang string, opts Options) ([]byte, error) {
if len(src) == 0 {
return src, nil
}
spec := getSpec(lang)
if spec == nil {
return src, fmt.Errorf("%w: %q", ErrUnsupportedLang, lang)
}
grammar := spec.grammarFn()
if grammar == nil {
return src, fmt.Errorf("%w: %q (no grammar binding)", ErrUnsupportedLang, lang)
}
parser := sitter.NewParser()
defer parser.Close()
parser.SetLanguage(grammar)
ctx, cancel := context.WithTimeout(context.Background(), parseTimeout)
defer cancel()
tree, err := parser.ParseCtx(ctx, nil, src)
if err != nil {
return src, fmt.Errorf("%w: %v", ErrParse, err)
}
if tree == nil {
return src, ErrParse
}
defer tree.Close()
root := tree.RootNode()
if root == nil {
return src, nil
}
ranges := collectRanges(root, spec, src, opts)
if len(ranges) == 0 {
return src, nil
}
return applyRanges(src, ranges), nil
}
// CompressString is the string-flavoured wrapper around Compress.
func CompressString(src, lang string) (string, error) {
out, err := Compress([]byte(src), lang)
return string(out), err
}
// CompressStringWith is the string-flavoured wrapper around CompressWith.
func CompressStringWith(src, lang string, opts Options) (string, error) {
out, err := CompressWith([]byte(src), lang, opts)
return string(out), err
}
type elideRange struct {
startByte uint32
endByte uint32
stub string
}
// identifierTypes are the tree-sitter node kinds that can carry a bare
// declaration name across the supported grammars.
var identifierTypes = map[string]struct{}{
"identifier": {},
"field_identifier": {},
"type_identifier": {},
"simple_identifier": {},
"property_identifier": {},
"word": {},
}
// declName extracts a best-effort name for a function/method
// declaration node. It returns "" when no name handle is available —
// callers treat that as "match by line range only". Name extraction
// is deliberately fail-soft: a miss never blocks elision.
func declName(node *sitter.Node, src []byte) string {
if node == nil {
return ""
}
if n := node.ChildByFieldName("name"); n != nil {
return n.Content(src)
}
// C / C++: the name lives inside nested declarator wrappers.
if d := node.ChildByFieldName("declarator"); d != nil {
if name := firstIdentifier(d, src, 5); name != "" {
return name
}
}
// Fallback: the first identifier-kinded direct named child.
cnt := int(node.NamedChildCount())
for i := range cnt {
c := node.NamedChild(i)
if c == nil {
continue
}
if _, ok := identifierTypes[c.Type()]; ok {
return c.Content(src)
}
}
return ""
}
// firstIdentifier descends up to depth levels (preferring declarator
// fields) for the first identifier-kinded node.
func firstIdentifier(node *sitter.Node, src []byte, depth int) string {
if node == nil || depth < 0 {
return ""
}
if _, ok := identifierTypes[node.Type()]; ok {
return node.Content(src)
}
if d := node.ChildByFieldName("declarator"); d != nil {
if name := firstIdentifier(d, src, depth-1); name != "" {
return name
}
}
cnt := int(node.NamedChildCount())
for i := range cnt {
if name := firstIdentifier(node.NamedChild(i), src, depth-1); name != "" {
return name
}
}
return ""
}
func collectRanges(root *sitter.Node, spec *languageSpec, src []byte, opts Options) []elideRange {
var out []elideRange
var walk func(node *sitter.Node)
walk = func(node *sitter.Node) {
if node == nil {
return
}
kind := node.Type()
if _, isParent := spec.parents[kind]; isParent {
// Elixir gate: only `call` nodes whose head identifier is
// a def-family macro qualify. Other languages don't need
// a filter — their parent kinds are unambiguous.
eligible := true
if spec.style == stubElixir && kind == "call" {
eligible = elixirCallFilter(node, src)
}
if eligible {
if body := spec.findBody(node); body != nil {
name := declName(node, src)
switch opts.verdict(Decl{
Name: name,
Kind: kind,
StartRow: int(node.StartPoint().Row),
EndRow: int(node.EndPoint().Row),
}) {
case FidelityFull:
// Keep this declaration's whole subtree verbatim —
// no stub, no recursion.
return
case FidelityOmit:
// Drop the whole declaration (signature + body)
// behind a one-line marker. The span is the whole
// node, not just its body. No recursion — the
// invariants (ascending, non-overlapping ranges;
// no descent into a removed subtree) hold.
out = append(out, elideRange{
startByte: node.StartByte(),
endByte: node.EndByte(),
stub: omitMarker(spec.style, name),
})
return
default: // FidelityCompress
stub, lineCount := renderStub(spec.style, body, src)
_ = lineCount
out = append(out, elideRange{
startByte: body.StartByte(),
endByte: body.EndByte(),
stub: stub,
})
return // do not recurse into elided body
}
}
}
}
n := int(node.NamedChildCount())
for i := range n {
walk(node.NamedChild(i))
}
}
walk(root)
return out
}
// omitMarker renders the one-line placeholder that stands in for a
// whole declaration removed under FidelityOmit. The comment prefix
// follows the language's line-comment syntax so the result still
// parses as a comment in the host language.
func omitMarker(style stubStyle, name string) string {
prefix := "//"
switch style {
case stubPython, stubRuby, stubElixir:
prefix = "#"
}
if name == "" {
return prefix + " declaration omitted"
}
return fmt.Sprintf("%s %s omitted", prefix, name)
}
func renderStub(style stubStyle, body *sitter.Node, src []byte) (string, int) {
startRow := int(body.StartPoint().Row)
endRow := int(body.EndPoint().Row)
switch style {
case stubBrace:
// {…} body: lines strictly between the opening and closing brace.
// Single-line body { foo } collapses to 1.
lines := max(endRow-startRow-1, 1)
return fmt.Sprintf("{ /* %d lines elided */ }", lines), lines
case stubPython:
// The block node starts at the first non-whitespace byte of the
// first body statement; the leading indent of that first line
// is already in the source bytes BEFORE block.StartByte, so
// we emit only the ellipsis stub.
lines := max(endRow-startRow+1, 1)
return fmt.Sprintf("... # %d lines elided", lines), lines
case stubRuby:
// body_statement byte range is the inner statements; the
// surrounding `def name`/`end` keywords stay verbatim. The
// caller's original indent precedes body_statement.StartByte.
lines := max(endRow-startRow+1, 1)
return fmt.Sprintf("# %d lines elided", lines), lines
case stubElixir:
// do_block spans `do\n ...\nend`. We collapse the inner
// statements but keep the do/end keywords intact by emitting
// a fresh `do\n # N lines elided\nend` block. The indent of
// the do-block opener is in the source bytes before
// body.StartByte; we line our `do` up flush against it.
lines := max(endRow-startRow-1, 1)
indent := leadingIndent(src, body.StartByte())
return fmt.Sprintf("do\n%s # %d lines elided\n%send", indent, lines, indent), lines
}
return "", 0
}
// leadingIndent returns the run of spaces/tabs immediately preceding
// the byte offset (back to the most recent newline or start of file).
// Used by stubElixir so its emitted `end` lands on the column of the
// original `do` keyword.
func leadingIndent(src []byte, off uint32) string {
if int(off) > len(src) {
off = uint32(len(src))
}
i := int(off) - 1
end := i
for i >= 0 && (src[i] == ' ' || src[i] == '\t') {
i--
}
if end < 0 || i+1 > end {
return ""
}
return string(src[i+1 : end+1])
}
func applyRanges(src []byte, ranges []elideRange) []byte {
sort.Slice(ranges, func(i, j int) bool {
return ranges[i].startByte < ranges[j].startByte
})
var b strings.Builder
b.Grow(len(src))
cursor := uint32(0)
for _, r := range ranges {
if r.startByte < cursor {
continue
}
if int(r.startByte) > len(src) || int(r.endByte) > len(src) || r.endByte < r.startByte {
continue
}
b.Write(src[cursor:r.startByte])
b.WriteString(r.stub)
cursor = r.endByte
}
if int(cursor) < len(src) {
b.Write(src[cursor:])
}
return []byte(b.String())
}