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,414 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zzet/gortex/internal/parser"
|
||||
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
||||
)
|
||||
|
||||
// emitSwiftAnnotationEdges scans a declaration node for `modifiers` /
|
||||
// `attribute` children and emits one EdgeAnnotated per attribute onto
|
||||
// the synthetic `annotation::swift::<name>` node. Covers:
|
||||
//
|
||||
// @objc → annotation::swift::objc
|
||||
// @objc(legacyName) → annotation::swift::objc (args="legacyName")
|
||||
// @available(iOS 13.0, *) → annotation::swift::available
|
||||
// @MainActor → annotation::swift::MainActor
|
||||
// @Published → annotation::swift::Published
|
||||
// @State / @Binding / @Environment — SwiftUI property wrappers
|
||||
// @objcMembers, @inlinable, @inline, @dynamicCallable, @propertyWrapper, …
|
||||
//
|
||||
// Property wrappers and actor attributes are dispatch-relevant — they
|
||||
// change how the property is accessed (KVO, observation framework,
|
||||
// main-thread isolation) — so making them queryable via `find_usages`
|
||||
// on the synthetic annotation node lets agents answer "every @Published
|
||||
// property in this module" with one hop.
|
||||
//
|
||||
// The Swift tree-sitter grammar nests attributes under a `modifiers`
|
||||
// child of the declaration, so the scan walks that level. If the
|
||||
// declaration has no modifiers child the function is a silent no-op.
|
||||
func emitSwiftAnnotationEdges(
|
||||
defNode *sitter.Node, fromID, filePath string, src []byte,
|
||||
result *parser.ExtractionResult, seen map[string]bool,
|
||||
) {
|
||||
if defNode == nil || fromID == "" {
|
||||
return
|
||||
}
|
||||
mods := defNode.ChildByFieldName("modifiers")
|
||||
if mods == nil {
|
||||
// Some declarations expose modifiers as a positional named
|
||||
// child rather than via a named field; scan top-level
|
||||
// children for a `modifiers` node as a fallback.
|
||||
for i, _nc := 0, int(defNode.NamedChildCount()); i < _nc; i++ {
|
||||
c := defNode.NamedChild(i)
|
||||
if c != nil && c.Type() == "modifiers" {
|
||||
mods = c
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if mods == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i, _nc := 0, int(mods.NamedChildCount()); i < _nc; i++ {
|
||||
attr := mods.NamedChild(i)
|
||||
if attr == nil || attr.Type() != "attribute" {
|
||||
continue
|
||||
}
|
||||
name, args := swiftAttributeNameAndArgs(attr, src)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
line := int(attr.StartPoint().Row) + 1
|
||||
EmitAnnotationEdge(fromID, "swift", name, args, filePath, line, result, seen)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftAttributeNameAndArgs reads an `attribute` AST node and returns
|
||||
// (name, args). The name comes from the first `user_type` /
|
||||
// `type_identifier` child (Swift's grammar wraps attribute names in a
|
||||
// type position). Arguments come from any remaining named children
|
||||
// joined by ", " — the verbatim form is preserved so route paths and
|
||||
// availability shims stay queryable.
|
||||
//
|
||||
// For qualified attribute names (`@SomeModule.SomeAttr`) the trailing
|
||||
// segment is returned so the synthetic annotation node groups every
|
||||
// equivalent use regardless of import alias.
|
||||
func swiftAttributeNameAndArgs(attr *sitter.Node, src []byte) (string, string) {
|
||||
if attr == nil {
|
||||
return "", ""
|
||||
}
|
||||
var name string
|
||||
var argParts []string
|
||||
for i, _nc := 0, int(attr.NamedChildCount()); i < _nc; i++ {
|
||||
c := attr.NamedChild(i)
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
switch c.Type() {
|
||||
case "user_type":
|
||||
if name == "" {
|
||||
name = swiftUserTypeName(c, src)
|
||||
}
|
||||
case "type_identifier", "simple_identifier", "identifier":
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(c.Content(src))
|
||||
} else {
|
||||
argParts = append(argParts, strings.TrimSpace(c.Content(src)))
|
||||
}
|
||||
default:
|
||||
argParts = append(argParts, strings.TrimSpace(c.Content(src)))
|
||||
}
|
||||
}
|
||||
args := strings.Join(argParts, ", ")
|
||||
return name, args
|
||||
}
|
||||
|
||||
// swiftModifiers returns the `modifiers` child of a declaration node,
|
||||
// trying the named field first and falling back to a positional scan —
|
||||
// the two shapes Swift's tree-sitter grammar uses across versions.
|
||||
func swiftModifiers(defNode *sitter.Node) *sitter.Node {
|
||||
if defNode == nil {
|
||||
return nil
|
||||
}
|
||||
if mods := defNode.ChildByFieldName("modifiers"); mods != nil {
|
||||
return mods
|
||||
}
|
||||
for i, _nc := 0, int(defNode.NamedChildCount()); i < _nc; i++ {
|
||||
if c := defNode.NamedChild(i); c != nil && c.Type() == "modifiers" {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// swiftObjCAttr reports whether a declaration carries @objc and, if so,
|
||||
// the explicit selector from an @objc(customSelector:) override (empty
|
||||
// when @objc is bare). The explicit form is read verbatim from the
|
||||
// attribute's parenthesised text so a full keyword selector
|
||||
// (`@objc(moveFrom:to:)`) survives intact.
|
||||
func swiftObjCAttr(defNode *sitter.Node, src []byte) (isObjC bool, explicit string) {
|
||||
mods := swiftModifiers(defNode)
|
||||
if mods == nil {
|
||||
return false, ""
|
||||
}
|
||||
for i, _nc := 0, int(mods.NamedChildCount()); i < _nc; i++ {
|
||||
attr := mods.NamedChild(i)
|
||||
if attr == nil || attr.Type() != "attribute" {
|
||||
continue
|
||||
}
|
||||
name, _ := swiftAttributeNameAndArgs(attr, src)
|
||||
if name != "objc" {
|
||||
continue
|
||||
}
|
||||
isObjC = true
|
||||
text := attr.Content(src)
|
||||
if open := strings.IndexByte(text, '('); open >= 0 {
|
||||
if closeIdx := strings.LastIndexByte(text, ')'); closeIdx > open {
|
||||
explicit = strings.TrimSpace(text[open+1 : closeIdx])
|
||||
}
|
||||
}
|
||||
return isObjC, explicit
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// swiftObjCSelector computes the Objective-C selector a Swift method is
|
||||
// exposed under when it carries @objc, or "" when it is not @objc. An
|
||||
// explicit @objc("sel:") override wins; otherwise the selector is derived
|
||||
// from the method's base name and argument labels per Swift's bridging
|
||||
// rules: the first argument label folds into the base name capitalised
|
||||
// (`move(from:to:)` → `moveFrom:to:`), `init` inserts "With"
|
||||
// (`init(frame:)` → `initWithFrame:`), an omitted label (`_`) contributes
|
||||
// a bare colon (`insertSubview(_:at:)` → `insertSubview:at:`), and a
|
||||
// no-argument method keeps its bare name (`viewDidLoad`).
|
||||
func swiftObjCSelector(defNode *sitter.Node, baseName string, src []byte) string {
|
||||
isObjC, explicit := swiftObjCAttr(defNode, src)
|
||||
if !isObjC {
|
||||
return ""
|
||||
}
|
||||
if explicit != "" {
|
||||
return explicit
|
||||
}
|
||||
return buildSwiftObjCSelector(baseName, swiftArgLabels(swiftParamClause(defNode, src)))
|
||||
}
|
||||
|
||||
// swiftObjCPropertySelectors computes the Objective-C accessor selectors an
|
||||
// @objc property is exposed under: the getter is the property name (or an
|
||||
// explicit @objc(custom) override), and a mutable `var` also exposes a
|
||||
// `set<Name>:` setter. Returns ("", "") when the property is not @objc, and an
|
||||
// empty setter for an immutable `let`.
|
||||
func swiftObjCPropertySelectors(defNode *sitter.Node, name string, mutable bool, src []byte) (getter, setter string) {
|
||||
isObjC, explicit := swiftObjCAttr(defNode, src)
|
||||
if !isObjC || name == "" {
|
||||
return "", ""
|
||||
}
|
||||
getter = name
|
||||
if explicit != "" {
|
||||
getter = explicit
|
||||
}
|
||||
if mutable {
|
||||
setter = "set" + capitalizeFirst(getter) + ":"
|
||||
}
|
||||
return getter, setter
|
||||
}
|
||||
|
||||
// swiftParamClause returns the text inside a declaration's parameter
|
||||
// parentheses, skipping any leading attribute parens (`@objc(x)`) by
|
||||
// starting the scan after the `func` keyword.
|
||||
func swiftParamClause(defNode *sitter.Node, src []byte) string {
|
||||
if defNode == nil {
|
||||
return ""
|
||||
}
|
||||
text := defNode.Content(src)
|
||||
from := 0
|
||||
if kw := strings.Index(text, "func"); kw >= 0 {
|
||||
from = kw + len("func")
|
||||
}
|
||||
open := strings.IndexByte(text[from:], '(')
|
||||
if open < 0 {
|
||||
return ""
|
||||
}
|
||||
open += from
|
||||
depth := 0
|
||||
for i := open; i < len(text); i++ {
|
||||
switch text[i] {
|
||||
case '(':
|
||||
depth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return text[open+1 : i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// swiftArgLabels splits a parameter clause at top-level commas and
|
||||
// returns each parameter's external argument label, with "_" (omitted
|
||||
// label) mapped to the empty string.
|
||||
func swiftArgLabels(paramClause string) []string {
|
||||
paramClause = strings.TrimSpace(paramClause)
|
||||
if paramClause == "" {
|
||||
return nil
|
||||
}
|
||||
var labels []string
|
||||
for _, p := range splitSwiftTopLevelCommas(paramClause) {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
label := firstSwiftArgLabel(p)
|
||||
if label == "_" {
|
||||
label = ""
|
||||
}
|
||||
labels = append(labels, label)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// firstSwiftArgLabel returns the first identifier token of a parameter
|
||||
// declaration — its external argument label — stripping a trailing colon
|
||||
// from the single-name form (`x: Int` → "x").
|
||||
func firstSwiftArgLabel(param string) string {
|
||||
end := len(param)
|
||||
for i, r := range param {
|
||||
if r == ' ' || r == '\t' || r == ':' {
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return param[:end]
|
||||
}
|
||||
|
||||
// buildSwiftObjCSelector assembles the ObjC selector from a Swift base
|
||||
// name and its ordered argument labels.
|
||||
func buildSwiftObjCSelector(base string, labels []string) string {
|
||||
if len(labels) == 0 {
|
||||
return base
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(base)
|
||||
for i, label := range labels {
|
||||
switch {
|
||||
case i == 0 && base == "init" && label != "":
|
||||
sb.WriteString("With")
|
||||
sb.WriteString(capitalizeFirst(label))
|
||||
case i == 0 && label != "":
|
||||
sb.WriteString(capitalizeFirst(label))
|
||||
case i > 0:
|
||||
sb.WriteString(label)
|
||||
}
|
||||
sb.WriteByte(':')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// splitSwiftTopLevelCommas splits s at commas not nested inside (), <>,
|
||||
// [], or {} — so a generic / closure / default-value argument stays one
|
||||
// parameter.
|
||||
func splitSwiftTopLevelCommas(s string) []string {
|
||||
var parts []string
|
||||
depth := 0
|
||||
start := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '(', '<', '[', '{':
|
||||
depth++
|
||||
case ')', '>', ']', '}':
|
||||
if depth > 0 {
|
||||
depth--
|
||||
}
|
||||
case ',':
|
||||
if depth == 0 {
|
||||
parts = append(parts, s[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
parts = append(parts, s[start:])
|
||||
return parts
|
||||
}
|
||||
|
||||
// capitalizeFirst upper-cases the first ASCII letter of s.
|
||||
func capitalizeFirst(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
if s[0] >= 'a' && s[0] <= 'z' {
|
||||
return string(s[0]-'a'+'A') + s[1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// swiftUserTypeName pulls the trailing `type_identifier` out of a
|
||||
// `user_type` chain (`Foo.Bar.Baz` → "Baz") so qualified annotation
|
||||
// references collapse onto the same synthetic node.
|
||||
func swiftUserTypeName(node *sitter.Node, src []byte) string {
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
// Walk forward and remember the last type_identifier; Swift's
|
||||
// user_type nests left-to-right so the last identifier is the
|
||||
// leaf.
|
||||
var last string
|
||||
for i, _nc := 0, int(node.NamedChildCount()); i < _nc; i++ {
|
||||
c := node.NamedChild(i)
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
if c.Type() == "type_identifier" {
|
||||
last = strings.TrimSpace(c.Content(src))
|
||||
}
|
||||
if c.Type() == "user_type" {
|
||||
if inner := swiftUserTypeName(c, src); inner != "" {
|
||||
last = inner
|
||||
}
|
||||
}
|
||||
}
|
||||
if last == "" {
|
||||
// Fallback: take the verbatim content and slice on the last
|
||||
// `.` separator so qualified names still surface a leaf.
|
||||
text := strings.TrimSpace(node.Content(src))
|
||||
if idx := strings.LastIndex(text, "."); idx >= 0 {
|
||||
text = text[idx+1:]
|
||||
}
|
||||
last = text
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
// swiftHasAttr reports whether a declaration carries the named attribute
|
||||
// (e.g. "objc", "nonobjc", "objcMembers") among its modifiers.
|
||||
func swiftHasAttr(defNode *sitter.Node, attrName string, src []byte) bool {
|
||||
mods := swiftModifiers(defNode)
|
||||
if mods == nil {
|
||||
return false
|
||||
}
|
||||
for i, _nc := 0, int(mods.NamedChildCount()); i < _nc; i++ {
|
||||
attr := mods.NamedChild(i)
|
||||
if attr == nil || attr.Type() != "attribute" {
|
||||
continue
|
||||
}
|
||||
if name, _ := swiftAttributeNameAndArgs(attr, src); name == attrName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// swiftObjCSelectorExposed computes the Objective-C selector a Swift method
|
||||
// is exposed under, accounting for an @objcMembers class that exposes every
|
||||
// member implicitly. The member's own @objc wins; otherwise, when the
|
||||
// enclosing class is @objcMembers and the member is not opted out with
|
||||
// @nonobjc, the selector is derived as if the member were @objc. Returns ""
|
||||
// when the member is not exposed to Objective-C.
|
||||
func swiftObjCSelectorExposed(defNode *sitter.Node, baseName string, classObjCMembers bool, src []byte) string {
|
||||
if sel := swiftObjCSelector(defNode, baseName, src); sel != "" {
|
||||
return sel
|
||||
}
|
||||
if classObjCMembers && !swiftHasAttr(defNode, "nonobjc", src) {
|
||||
return buildSwiftObjCSelector(baseName, swiftArgLabels(swiftParamClause(defNode, src)))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// swiftObjCPropertySelectorsExposed mirrors swiftObjCPropertySelectors but
|
||||
// also exposes a property implicitly when its enclosing class is
|
||||
// @objcMembers and the property is not opted out with @nonobjc.
|
||||
func swiftObjCPropertySelectorsExposed(defNode *sitter.Node, name string, mutable, classObjCMembers bool, src []byte) (getter, setter string) {
|
||||
if g, s := swiftObjCPropertySelectors(defNode, name, mutable, src); g != "" {
|
||||
return g, s
|
||||
}
|
||||
if name != "" && classObjCMembers && !swiftHasAttr(defNode, "nonobjc", src) {
|
||||
getter = name
|
||||
if mutable {
|
||||
setter = "set" + capitalizeFirst(getter) + ":"
|
||||
}
|
||||
return getter, setter
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
Reference in New Issue
Block a user