diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d55b6659..cef66787e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ * Now supports docinfo requests for sourcetext and module keys. [Erik Abair](https://github.com/abaire) +* Now supports Objective-C class properties. + [Jérémie Girault](https://github.com/jeremiegirault) + [JP Simard](https://github.com/jpsim) + [#243](https://github.com/jpsim/SourceKitten/issues/243) + ##### Bug Fixes * `NSString.lines()` generated surplus line when string ended with newline diff --git a/Source/SourceKittenFramework/SourceDeclaration.swift b/Source/SourceKittenFramework/SourceDeclaration.swift index 0cbbb54ce..b5cbda4b0 100644 --- a/Source/SourceKittenFramework/SourceDeclaration.swift +++ b/Source/SourceKittenFramework/SourceDeclaration.swift @@ -71,21 +71,48 @@ public struct SourceDeclaration { guard let declaration = declaration else { fatalError("Couldn't extract declaration") } - let pyStartIndex = usr.range(of: "(py)")!.lowerBound - let usrPrefix = usr.substring(to: pyStartIndex) - let fullDeclarationRange = NSRange(location: 0, length: (declaration as NSString).length) - let regex = try! NSRegularExpression(pattern: getter ? "getter\\s*=\\s*(\\w+)" : "setter\\s*=\\s*(\\w+:)", options: []) - let matches = regex.matches(in: declaration, options: [], range: fullDeclarationRange) + enum AccessorType { + case `class`, instance + + var propertyTypeString: String { + switch self { + case .class: return "(cpy)" + case .instance: return "(py)" + } + } + + var methodTypeString: String { + switch self { + case .class: return "(cm)" + case .instance: return "(im)" + } + } + } + let propertyTypeStringStart: String.Index + let accessorType: AccessorType + if let accessorTypeStringStartIndex = usr.range(of: AccessorType.class.propertyTypeString)?.lowerBound { + propertyTypeStringStart = accessorTypeStringStartIndex + accessorType = .class + } else if let accessorTypeStringStartIndex = usr.range(of: AccessorType.instance.propertyTypeString)?.lowerBound { + propertyTypeStringStart = accessorTypeStringStartIndex + accessorType = .instance + } else { + fatalError("expected an instance or class property by got \(usr)") + } + let nsDeclaration = declaration as NSString + let usrPrefix = usr.substring(to: propertyTypeStringStart) + let regex = try! NSRegularExpression(pattern: getter ? "getter\\s*=\\s*(\\w+)" : "setter\\s*=\\s*(\\w+:)") + let matches = regex.matches(in: declaration, options: [], range: NSRange(location: 0, length: nsDeclaration.length)) if matches.count > 0 { - let accessorName = (declaration as NSString).substring(with: matches[0].rangeAt(1)) - return usrPrefix + "(im)\(accessorName)" + let accessorName = nsDeclaration.substring(with: matches[0].rangeAt(1)) + return usrPrefix + accessorType.methodTypeString + accessorName } else if getter { - return usr.replacingOccurrences(of: "(py)", with: "(im)") + return usr.replacingOccurrences(of: accessorType.propertyTypeString, with: accessorType.methodTypeString) } // Setter - let capitalFirstLetter = String(usr.characters[usr.characters.index(pyStartIndex, offsetBy: 4)]).capitalized - let restOfSetterName = usr.substring(from: usr.characters.index(pyStartIndex, offsetBy: 5)) - return "\(usrPrefix)(im)set\(capitalFirstLetter)\(restOfSetterName):" + let setterOffset = accessorType.propertyTypeString.characters.count + let capitalizedSetterName = usr.substring(from: usr.characters.index(propertyTypeStringStart, offsetBy: setterOffset)).capitalizingFirstLetter() + return "\(usrPrefix)\(accessorType.methodTypeString)set\(capitalizedSetterName):" } } diff --git a/Source/SourceKittenFramework/String+SourceKitten.swift b/Source/SourceKittenFramework/String+SourceKitten.swift index bf754d851..d136a3ccb 100644 --- a/Source/SourceKittenFramework/String+SourceKitten.swift +++ b/Source/SourceKittenFramework/String+SourceKitten.swift @@ -407,6 +407,10 @@ extension String { return FileManager.default.fileExists(atPath: self) } + internal func capitalizingFirstLetter() -> String { + return String(characters.prefix(1)).capitalized + String(characters.dropFirst()) + } + #if !os(Linux) /// Returns the `#pragma mark`s in the string. /// Just the content; no leading dashes or leading `#pragma mark`. diff --git a/Tests/SourceKittenFrameworkTests/Fixtures/Musician.h b/Tests/SourceKittenFrameworkTests/Fixtures/Musician.h index fb7f9352c..21b7e89a0 100644 --- a/Tests/SourceKittenFrameworkTests/Fixtures/Musician.h +++ b/Tests/SourceKittenFrameworkTests/Fixtures/Musician.h @@ -13,10 +13,13 @@ #pragma mark - Properties +/// Always returns `YES`. +@property (class, readwrite, nonatomic) BOOL isMusician; + /** The name of the musician. i.e. "John Coltrane" */ -@property (nonatomic, readonly) NSString *name; +@property (nonatomic, readonly) NSString *name; /** The year the musician was born. i.e. 1926 diff --git a/Tests/SourceKittenFrameworkTests/Fixtures/Musician.json b/Tests/SourceKittenFrameworkTests/Fixtures/Musician.json index e3cf00f49..29b535e8e 100644 --- a/Tests/SourceKittenFrameworkTests/Fixtures/Musician.json +++ b/Tests/SourceKittenFrameworkTests/Fixtures/Musician.json @@ -24,18 +24,37 @@ "key.deprecation_message" : "", "key.always_unavailable" : false, "key.unavailable_message" : "", - "key.parsed_scope.start" : 19, + "key.parsed_scope.start" : 17, + "key.kind" : "sourcekitten.source.lang.objc.decl.property", + "key.swift_declaration" : "class var isMusician: Bool { get set }", + "key.doc.full_as_xml" : "", + "key.always_deprecated" : false, + "key.doc.line" : 17, + "key.doc.column" : 46, + "key.name" : "isMusician", + "key.doc.comment" : "Always returns `YES`.", + "key.usr" : "c:objc(cs)JAZMusician(cpy)isMusician", + "key.parsed_declaration" : "@property (assign, readwrite, nonatomic, class) BOOL isMusician;", + "key.parsed_scope.end" : 17 + }, + { + "key.filepath" : "Musician.h", + "key.doc.file" : "Musician.h", + "key.deprecation_message" : "", + "key.always_unavailable" : false, + "key.unavailable_message" : "", + "key.parsed_scope.start" : 22, "key.kind" : "sourcekitten.source.lang.objc.decl.property", "key.swift_declaration" : "var name: String! { get }", "key.doc.full_as_xml" : "", "key.always_deprecated" : false, - "key.doc.line" : 19, - "key.doc.column" : 44, + "key.doc.line" : 22, + "key.doc.column" : 43, "key.name" : "name", "key.doc.comment" : "The name of the musician. i.e. \"John Coltrane\"", "key.usr" : "c:objc(cs)JAZMusician(py)name", "key.parsed_declaration" : "@property (readonly, nonatomic) NSString *name;", - "key.parsed_scope.end" : 19 + "key.parsed_scope.end" : 22 }, { "key.filepath" : "Musician.h", @@ -43,28 +62,28 @@ "key.deprecation_message" : "", "key.always_unavailable" : false, "key.unavailable_message" : "", - "key.parsed_scope.start" : 24, + "key.parsed_scope.start" : 27, "key.kind" : "sourcekitten.source.lang.objc.decl.property", "key.swift_declaration" : "var birthyear: UInt { get }", "key.doc.full_as_xml" : "", "key.always_deprecated" : false, - "key.doc.line" : 24, + "key.doc.line" : 27, "key.doc.column" : 44, "key.name" : "birthyear", "key.doc.comment" : "The year the musician was born. i.e. 1926", "key.usr" : "c:objc(cs)JAZMusician(py)birthyear", "key.parsed_declaration" : "@property (readonly, nonatomic) NSUInteger birthyear;", - "key.parsed_scope.end" : 24 + "key.parsed_scope.end" : 27 }, { "key.filepath" : "Musician.h", "key.doc.file" : "Musician.h", - "key.doc.line" : 26, + "key.doc.line" : 29, "key.doc.column" : 1, "key.name" : "Initializers-hyphenated", - "key.parsed_scope.start" : 26, + "key.parsed_scope.start" : 29, "key.kind" : "sourcekitten.source.lang.objc.mark", - "key.parsed_scope.end" : 26 + "key.parsed_scope.end" : 29 }, { "key.filepath" : "Musician.h", @@ -92,12 +111,12 @@ } ], "key.unavailable_message" : "", - "key.parsed_scope.start" : 40, + "key.parsed_scope.start" : 43, "key.kind" : "sourcekitten.source.lang.objc.decl.method.instance", "key.swift_declaration" : "init!(name: String!, birthyear: UInt)", "key.doc.full_as_xml" : "", "key.always_deprecated" : false, - "key.doc.line" : 40, + "key.doc.line" : 43, "key.doc.column" : 17, "key.name" : "-initWithName:birthyear:", "key.doc.comment" : "Initialize a JAZMusician.\nDon't forget to have a name and a birthyear.\n\n- warning: Jazz can be addicting.\nPlease be careful out there.\n\n- parameter: name The name of the musician.\n- parameter: birthyear The year the musician was born.\n\n- returns: An initialized JAZMusician instance.", @@ -109,7 +128,7 @@ "Para" : "An initialized JAZMusician instance." } ], - "key.parsed_scope.end" : 40 + "key.parsed_scope.end" : 43 } ], "key.unavailable_message" : "", @@ -124,7 +143,7 @@ "key.doc.comment" : "JAZMusician models, you guessed it... Jazz Musicians!\nFrom Ellington to Marsalis, this class has you covered.", "key.usr" : "c:objc(cs)JAZMusician", "key.parsed_declaration" : "@interface JAZMusician : NSObject", - "key.parsed_scope.end" : 42 + "key.parsed_scope.end" : 45 } ], "key.diagnostic_stage" : ""