package resolver import "strings" // cocoaPrepositions are the linking words the Objective-C → Swift importer // treats as argument-label boundaries. When a selector's base keyword ends // with one of these (optionally followed by the argument's type noun), the // Swift method drops everything from the preposition onward from its base // name and folds it into the first argument label: `cellForRowAtIndexPath:` // surfaces in Swift as `cellForRow(at:)`, `moveFrom:to:` as `move(from:to:)`, // `initWithFrame:` as `init(frame:)`. var cocoaPrepositions = map[string]bool{ "with": true, "for": true, "at": true, "by": true, "from": true, "to": true, "in": true, "into": true, "of": true, "on": true, "using": true, "as": true, "and": true, "about": true, "against": true, "around": true, "over": true, "under": true, "within": true, "before": true, "after": true, "between": true, "through": true, "until": true, "since": true, } // swiftObjCBaseNameCandidates derives the Swift method base names an // Objective-C selector could surface under. It always yields the verbatim // first keyword, plus — when that keyword ends in a Cocoa preposition phrase — // the shortened base name the importer would produce. The result is the set a // reverse bridge matches Swift method nodes against by name. func swiftObjCBaseNameCandidates(selector string) []string { head := selector if i := strings.IndexByte(selector, ':'); i >= 0 { head = selector[:i] } head = strings.TrimSpace(head) if head == "" { return nil } cands := []string{head} words := splitCamelWords(head) last := -1 for i := 1; i < len(words); i++ { if cocoaPrepositions[strings.ToLower(words[i])] { last = i } } if last > 0 { if base := lowerFirstASCII(strings.Join(words[:last], "")); base != "" && base != head { cands = append(cands, base) } } // Drop ubiquitous NSObject / Cocoa-runtime selectors: matching Swift // methods by these names would bridge unrelated code wholesale. A // specific selector keeps its verbatim candidate (`initWithFrame`) even // when its shortened form (`init`) is generic and suppressed. var out []string for _, c := range cands { if !isGenericCocoaSelector(c) { out = append(out, c) } } return out } // genericCocoaSelectors are the NSObject / Objective-C runtime method names a // candidate base name must not match on — memory management, introspection, // copying and the universal `init`/`description` family. These appear on // effectively every type, so a name-based bridge through them is noise. var genericCocoaSelectors = map[string]bool{ "init": true, "alloc": true, "allocWithZone": true, "new": true, "dealloc": true, "finalize": true, "load": true, "initialize": true, "copy": true, "mutableCopy": true, "copyWithZone": true, "mutableCopyWithZone": true, "retain": true, "release": true, "autorelease": true, "retainCount": true, "description": true, "debugDescription": true, "hash": true, "isEqual": true, "self": true, "class": true, "superclass": true, "isKindOfClass": true, "isMemberOfClass": true, "isProxy": true, "zone": true, "respondsToSelector": true, "conformsToProtocol": true, "performSelector": true, "methodForSelector": true, "methodSignatureForSelector": true, "doesNotRecognizeSelector": true, "forwardInvocation": true, "forwardingTargetForSelector": true, } // isGenericCocoaSelector reports whether a candidate base name is a universal // NSObject / runtime selector that must be excluded from heuristic bridging. func isGenericCocoaSelector(name string) bool { return genericCocoaSelectors[name] } // splitCamelWords breaks a lowerCamelCase / UpperCamelCase identifier into its // word components on lower→upper boundaries, keeping runs of capitals (e.g. // acronyms) together: `cellForRowAtIndexPath` → [cell For Row At Index Path], // `URLForKey` → [URL For Key]. func splitCamelWords(s string) []string { var words []string runes := []rune(s) start := 0 for i := 1; i < len(runes); i++ { prev, cur := runes[i-1], runes[i] boundary := isLowerOrDigit(prev) && isUpper(cur) // End of an acronym run: ...URLFor → URL | For. if !boundary && isUpper(prev) && isUpper(cur) && i+1 < len(runes) && isLowerOrDigit(runes[i+1]) { boundary = true } if boundary { words = append(words, string(runes[start:i])) start = i } } words = append(words, string(runes[start:])) return words } func isUpper(r rune) bool { return r >= 'A' && r <= 'Z' } func isLowerOrDigit(r rune) bool { return (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') } // lowerFirstASCII lowercases the first ASCII letter of s, leaving the rest // intact, so a joined word-prefix reads as a Swift method base name. func lowerFirstASCII(s string) string { if s == "" { return s } r := []rune(s) if r[0] >= 'A' && r[0] <= 'Z' { r[0] = r[0] - 'A' + 'a' } return string(r) }