Files
zzet--gortex/internal/parser/languages/objc_depth.go
T
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

169 lines
4.9 KiB
Go

package languages
import (
"regexp"
"strings"
)
var (
// objcPropertyRe captures the declared name of an @property (the last
// identifier before the terminating semicolon).
objcPropertyRe = regexp.MustCompile(`(?m)^\s*@property\b[^;\n]*?\b([A-Za-z_]\w*)\s*;`)
// objcTypedefRe captures the type name of a typedef NS_ENUM / NS_OPTIONS.
objcTypedefRe = regexp.MustCompile(`(?m)^\s*typedef\s+NS_(?:ENUM|OPTIONS)\s*\(\s*\w+\s*,\s*([A-Za-z_]\w*)\s*\)`)
objcMsgKeywordRe = regexp.MustCompile(`([A-Za-z_]\w*)\s*:`)
objcMsgUnaryRe = regexp.MustCompile(`^\s*([A-Za-z_]\w*)\s*$`)
)
// objcSpan is an id with a 1-based inclusive line range.
type objcSpan struct {
id string
start int
end int
}
// objcEnclosing returns the id of the smallest span containing line, or "".
func objcEnclosing(spans []objcSpan, line int) string {
best := ""
bestSize := 1 << 30
for _, s := range spans {
if line >= s.start && line <= s.end {
if sz := s.end - s.start; sz < bestSize {
bestSize = sz
best = s.id
}
}
}
return best
}
// objcMethodRanges returns the line span of every Objective-C method (keyed by
// its file-qualified selector id), used to attribute message-sends.
func objcMethodRanges(src []byte, lines []string, filePath string) []objcSpan {
var spans []objcSpan
for _, m := range objcMethodRe.FindAllSubmatchIndex(src, -1) {
if m[4] < 0 {
continue
}
sel := objcBuildSelector(string(src[m[4]:m[5]]))
if sel == "" {
continue
}
line := lineAt(src, m[0])
spans = append(spans, objcSpan{id: filePath + "::" + sel, start: line, end: findBlockEnd(lines, line)})
}
return spans
}
// objcClassRanges returns the line span of every @interface / @implementation
// block, used to attribute @property fields to their owning class.
func objcClassRanges(src []byte, lines []string, filePath string) []objcSpan {
var spans []objcSpan
for _, re := range []*regexp.Regexp{objcInterfaceRe, objcImplRe} {
for _, m := range re.FindAllSubmatchIndex(src, -1) {
name := string(src[m[2]:m[3]])
line := lineAt(src, m[0])
spans = append(spans, objcSpan{id: filePath + "::" + name, start: line, end: findKeywordBlockEnd(lines, line, "@end")})
}
}
return spans
}
// objcMsgSend is one reconstructed message-send: a receiver, its canonical
// selector, and the 1-based line it appears on.
type objcMsgSend struct {
receiver string
selector string
line int
}
// objcMessageSends scans src for [receiver selector ...] message-sends and
// reconstructs each canonical selector (colon-joined keyword labels for keyword
// messages, the bare name for unary messages). Nested sends are captured because
// the scan does not skip past a bracket's contents.
func objcMessageSends(src []byte) []objcMsgSend {
s := string(src)
var sends []objcMsgSend
for i := 0; i < len(s); i++ {
if s[i] != '[' {
continue
}
end := objcMatchBracket(s, i)
if end < 0 {
continue
}
if recv, sel := objcParseMessage(s[i+1 : end]); recv != "" && sel != "" {
sends = append(sends, objcMsgSend{receiver: recv, selector: sel, line: strings.Count(s[:i], "\n") + 1})
}
}
return sends
}
// objcReturnType extracts the method return type from the text between the
// +/- sign and the selector (e.g. "- (nullable NSArray<User *> *)") — the type
// inside the parentheses with nullability/ARC qualifiers, pointer stars, and
// generic arguments stripped.
func objcReturnType(prefix []byte) string {
s := string(prefix)
open := strings.IndexByte(s, '(')
if open < 0 {
return ""
}
rel := strings.IndexByte(s[open:], ')')
if rel < 0 {
return ""
}
rt := s[open+1 : open+rel]
for _, q := range []string{"nullable", "_Nonnull", "_Nullable", "__kindof", "const", "oneway", "bycopy", "byref"} {
rt = strings.ReplaceAll(rt, q, " ")
}
rt = strings.ReplaceAll(rt, "*", " ")
if i := strings.IndexByte(rt, '<'); i >= 0 {
rt = rt[:i]
}
return strings.TrimSpace(strings.Join(strings.Fields(rt), " "))
}
// objcMatchBracket returns the index of the ']' that closes the '[' at open.
func objcMatchBracket(s string, open int) int {
depth := 0
for i := open; i < len(s); i++ {
switch s[i] {
case '[':
depth++
case ']':
depth--
if depth == 0 {
return i
}
}
}
return -1
}
// objcParseMessage splits a bracket body into its receiver and canonical
// selector. Returns ("","") when it is not a message-send (e.g. an array
// subscript or collection literal element).
func objcParseMessage(inner string) (receiver, selector string) {
inner = strings.TrimSpace(inner)
sp := strings.IndexAny(inner, " \t\n")
if sp < 0 {
return "", ""
}
receiver = strings.TrimSpace(inner[:sp])
rest := inner[sp:]
if labels := objcMsgKeywordRe.FindAllStringSubmatch(rest, -1); len(labels) > 0 {
var b strings.Builder
for _, l := range labels {
b.WriteString(l[1])
b.WriteByte(':')
}
return receiver, b.String()
}
if m := objcMsgUnaryRe.FindStringSubmatch(rest); m != nil {
return receiver, m[1]
}
return "", ""
}