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
96 lines
3.2 KiB
Go
96 lines
3.2 KiB
Go
package languages
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/parser"
|
|
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
|
)
|
|
|
|
// emitScalaAnnotationEdges scans the direct children of a class /
|
|
// object / trait / function / var-definition for `annotation` nodes
|
|
// and emits one EdgeAnnotated per annotation onto the synthetic
|
|
// `annotation::scala::<name>` node. Examples:
|
|
//
|
|
// @deprecated("use newF", "1.5") → annotation::scala::deprecated
|
|
// @inline → annotation::scala::inline
|
|
// @volatile → annotation::scala::volatile
|
|
// @tailrec → annotation::scala::tailrec
|
|
// @main → annotation::scala::main
|
|
//
|
|
// The Scala tree-sitter grammar places annotations as direct children
|
|
// of the annotated definition node, so we only scan the immediate
|
|
// child set — recursive walks would attribute method annotations to
|
|
// the enclosing class, which is wrong.
|
|
//
|
|
// `seen` deduplicates synthetic annotation nodes within the extraction
|
|
// pass; the graph layer also dedupes by ID so a miss here is wasted
|
|
// work, not a correctness bug.
|
|
func emitScalaAnnotationEdges(
|
|
defNode *sitter.Node, fromID, filePath string, src []byte,
|
|
result *parser.ExtractionResult, seen map[string]bool,
|
|
) {
|
|
if defNode == nil || fromID == "" {
|
|
return
|
|
}
|
|
for i, _nc := 0, int(defNode.NamedChildCount()); i < _nc; i++ {
|
|
c := defNode.NamedChild(i)
|
|
if c == nil || c.Type() != "annotation" {
|
|
continue
|
|
}
|
|
name, args := scalaAnnotationNameAndArgs(c, src)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
line := int(c.StartPoint().Row) + 1
|
|
EmitAnnotationEdge(fromID, "scala", name, args, filePath, line, result, seen)
|
|
}
|
|
}
|
|
|
|
// scalaAnnotationNameAndArgs reads an `annotation` node and returns
|
|
// (name, args). Name comes from the `type_identifier` child; args is
|
|
// the raw inner-parentheses text of an `arguments` child (or "" when
|
|
// the annotation has no argument list). Both return values are
|
|
// trimmed of surrounding whitespace.
|
|
//
|
|
// Multi-segment names like `@a.b.C` (qualified annotation references)
|
|
// are returned as the trailing segment ("C") so `find_usages` on the
|
|
// synthetic annotation node still groups every site that uses the
|
|
// same logical annotation regardless of import alias.
|
|
func scalaAnnotationNameAndArgs(annot *sitter.Node, src []byte) (string, string) {
|
|
if annot == nil {
|
|
return "", ""
|
|
}
|
|
var name, args string
|
|
for i, _nc := 0, int(annot.NamedChildCount()); i < _nc; i++ {
|
|
c := annot.NamedChild(i)
|
|
if c == nil {
|
|
continue
|
|
}
|
|
switch c.Type() {
|
|
case "type_identifier", "identifier":
|
|
if name == "" {
|
|
name = strings.TrimSpace(c.Content(src))
|
|
}
|
|
case "stable_type_identifier", "projected_type":
|
|
// Qualified annotation reference like `@scala.deprecated` —
|
|
// keep only the trailing leaf so the synthetic node
|
|
// matches every alias.
|
|
text := strings.TrimSpace(c.Content(src))
|
|
if idx := strings.LastIndex(text, "."); idx >= 0 {
|
|
text = text[idx+1:]
|
|
}
|
|
if name == "" {
|
|
name = text
|
|
}
|
|
case "arguments":
|
|
text := strings.TrimSpace(c.Content(src))
|
|
if len(text) >= 2 && text[0] == '(' && text[len(text)-1] == ')' {
|
|
text = text[1 : len(text)-1]
|
|
}
|
|
args = strings.TrimSpace(text)
|
|
}
|
|
}
|
|
return name, args
|
|
}
|