-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwalk.go
More file actions
144 lines (133 loc) · 4.53 KB
/
walk.go
File metadata and controls
144 lines (133 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package parser
import (
"strings"
sitter "github.com/smacker/go-tree-sitter"
)
// Node is a tree-sitter parse-tree node. Re-exported as a type alias so
// callers can write `parser.Node` without an extra import of the tree-sitter
// SDK. The underlying type is `sitter.Node`, so all its methods (Type,
// ChildByFieldName, StartPoint, ...) are available.
type Node = sitter.Node
// Tree-sitter Node.StartPoint().Row returns uint32; callers wanting an int
// line number should do `int(n.StartPoint().Row) + 1`.
// Walk does a pre-order DFS over n (inclusive). The visitor returns true to
// recurse into the current node's children, false to skip them. Walking stops
// when the visitor returns false at the root or when all descendants have
// been visited. nil-safe.
//
// Implementation uses tree-sitter's TreeCursor for iterative traversal.
// Compared to the previous recursive `n.Child(i)` form, the cursor avoids
// Go-level recursion frames per descent and matches the canonical
// tree-sitter walking idiom. Note: each visited node still flows through
// smacker's per-Tree node cache (allocates one *Node on first visit per
// node), so the allocation count is roughly the same as the recursive form
// — the win is in stack discipline and code clarity, not GC pressure.
func Walk(n *Node, visit func(*Node) bool) {
if n == nil || visit == nil {
return
}
cur := sitter.NewTreeCursor(n)
defer cur.Close()
// Visit root.
descend := visit(cur.CurrentNode())
for {
if descend && cur.GoToFirstChild() {
descend = visit(cur.CurrentNode())
continue
}
// No children to descend into; advance to next sibling, climbing
// out of the subtree as necessary. The loop terminates when
// GoToParent returns false (we have climbed back above the
// original root).
for {
if cur.GoToNextSibling() {
descend = visit(cur.CurrentNode())
break
}
if !cur.GoToParent() {
return
}
// After climbing to parent, the parent itself was already
// visited when we descended into it — do NOT re-visit. Continue
// trying GoToNextSibling at the parent's level.
}
}
}
// ChildFieldText returns the source text of the named field of n, or "" if n
// has no such field. Convenience wrapper around ChildByFieldName + node text
// extraction; the caller passes the source string (not bytes) because most
// extractors hold their content as a string already.
func ChildFieldText(n *Node, field, source string) string {
if n == nil || field == "" {
return ""
}
c := n.ChildByFieldName(field)
if c == nil {
return ""
}
start, end := int(c.StartByte()), int(c.EndByte())
if start < 0 || end > len(source) || start >= end {
return ""
}
return source[start:end]
}
// NodeTextFromString is the string-source equivalent of NodeText. Returns ""
// if n is nil or its byte range is outside source.
func NodeTextFromString(n *Node, source string) string {
if n == nil {
return ""
}
start, end := int(n.StartByte()), int(n.EndByte())
if start < 0 || end > len(source) || start >= end {
return ""
}
return source[start:end]
}
// ParseByName routes a string language key ("java", "python", "typescript",
// "go") to the typed Parse(Language, ...) call. Returns (nil, error) for
// unknown keys. The string-keyed entry point exists for the intelligence
// extractors, which receive their language as a string off DetectLanguage.
func ParseByName(lang string, source []byte) (*Tree, error) {
l, err := languageFromName(lang)
if err != nil {
return nil, err
}
return Parse(l, source)
}
func languageFromName(lang string) (Language, error) {
// Adding new languages is just an extra case here plus an entry in
// tsLanguage() and LanguageFromExtension().
switch strings.ToLower(strings.TrimSpace(lang)) {
case "java":
return LanguageJava, nil
case "python", "py":
return LanguagePython, nil
case "typescript", "ts", "tsx", "javascript", "js":
return LanguageTypeScript, nil
case "go", "golang":
return LanguageGo, nil
case "yaml", "yml":
return LanguageYaml, nil
case "json":
return LanguageJSON, nil
case "toml":
return LanguageTOML, nil
case "ini", "cfg":
return LanguageINI, nil
case "properties":
return LanguageProperties, nil
case "sql":
return LanguageSQL, nil
case "batch", "bat", "cmd":
return LanguageBatch, nil
case "vue":
return LanguageVue, nil
case "svelte":
return LanguageSvelte, nil
}
return LanguageUnknown, errUnsupportedLanguageName{name: lang}
}
type errUnsupportedLanguageName struct{ name string }
func (e errUnsupportedLanguageName) Error() string {
return "unsupported language name: " + e.name
}