chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
"github.com/zzet/gortex/internal/parser"
|
||||
)
|
||||
|
||||
// JSON has no symbol semantics, but we still want CLAUDE.md /
|
||||
// package.json / tsconfig keys to show up in the graph so they can
|
||||
// be referenced and searched. Extract top-level keys of the outermost
|
||||
// object as variable nodes. Nested keys are intentionally skipped.
|
||||
type JSONExtractor struct{}
|
||||
|
||||
func NewJSONExtractor() *JSONExtractor { return &JSONExtractor{} }
|
||||
|
||||
func (e *JSONExtractor) Language() string { return "json" }
|
||||
func (e *JSONExtractor) Extensions() []string { return []string{".json", ".json5", ".jsonc"} }
|
||||
|
||||
func (e *JSONExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
|
||||
lines := strings.Split(string(src), "\n")
|
||||
result := &parser.ExtractionResult{}
|
||||
|
||||
fileNode := &graph.Node{
|
||||
ID: filePath, Kind: graph.KindFile, Name: filePath,
|
||||
FilePath: filePath, StartLine: 1, EndLine: len(lines),
|
||||
Language: "json",
|
||||
}
|
||||
result.Nodes = append(result.Nodes, fileNode)
|
||||
|
||||
seen := make(map[string]bool)
|
||||
add := func(name string, start int) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
id := filePath + "::" + name
|
||||
if seen[id] {
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindVariable, Name: name,
|
||||
FilePath: filePath, StartLine: start, EndLine: start,
|
||||
Language: "json",
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileNode.ID, To: id, Kind: graph.EdgeDefines,
|
||||
FilePath: filePath, Line: start,
|
||||
})
|
||||
}
|
||||
|
||||
// Manual scan: track brace/bracket depth while skipping strings and
|
||||
// comments (JSON5/JSONC). Emit each string key that sits at depth == 1
|
||||
// inside an object (not an array) and is followed by a `:`.
|
||||
//
|
||||
// Depth convention: incremented on `{` / `[`, decremented on `}` / `]`.
|
||||
// A key is "top-level" when the innermost open container is the
|
||||
// outermost `{` — i.e. depth transitions 0 -> 1 on `{`, and we accept
|
||||
// keys at depth == 1 while the innermost container is an object.
|
||||
var stack []byte // tracks nested container chars: '{' or '['
|
||||
line := 1
|
||||
i := 0
|
||||
n := len(src)
|
||||
for i < n {
|
||||
c := src[i]
|
||||
switch {
|
||||
case c == '\n':
|
||||
line++
|
||||
i++
|
||||
case c == '/' && i+1 < n && src[i+1] == '/':
|
||||
// Line comment (JSON5/JSONC).
|
||||
for i < n && src[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
case c == '/' && i+1 < n && src[i+1] == '*':
|
||||
i += 2
|
||||
for i+1 < n && (src[i] != '*' || src[i+1] != '/') {
|
||||
if src[i] == '\n' {
|
||||
line++
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i+1 < n {
|
||||
i += 2
|
||||
}
|
||||
case c == '{' || c == '[':
|
||||
stack = append(stack, c)
|
||||
i++
|
||||
case c == '}' || c == ']':
|
||||
if len(stack) > 0 {
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
i++
|
||||
case c == '"':
|
||||
// String literal — capture the contents.
|
||||
keyStart := i + 1
|
||||
j := i + 1
|
||||
for j < n {
|
||||
if src[j] == '\\' && j+1 < n {
|
||||
if src[j+1] == '\n' {
|
||||
line++
|
||||
}
|
||||
j += 2
|
||||
continue
|
||||
}
|
||||
if src[j] == '"' {
|
||||
break
|
||||
}
|
||||
if src[j] == '\n' {
|
||||
line++
|
||||
}
|
||||
j++
|
||||
}
|
||||
if j >= n {
|
||||
i = n
|
||||
break
|
||||
}
|
||||
key := string(src[keyStart:j])
|
||||
// Move past closing quote.
|
||||
i = j + 1
|
||||
// Look ahead past whitespace/comments for `:` — if present at
|
||||
// depth 1 inside an object, it's a top-level key.
|
||||
k := i
|
||||
for k < n {
|
||||
if src[k] == ' ' || src[k] == '\t' || src[k] == '\r' || src[k] == '\n' {
|
||||
k++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if k < n && src[k] == ':' {
|
||||
// At depth 1 means stack length is exactly 1 and top is '{'.
|
||||
if len(stack) == 1 && stack[0] == '{' {
|
||||
add(key, line)
|
||||
}
|
||||
}
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var _ parser.Extractor = (*JSONExtractor)(nil)
|
||||
Reference in New Issue
Block a user