Slide 1

Slide 1 text

SourceKit and You JP Simard – @simjp – App Builders – Zürich – April 25, 2016 1

Slide 2

Slide 2 text

SourceKit and Me JP Simard ( ! ➡ ") ObjC/Swift @ Realm 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

SourceKit and Me Product Needed API Docs & Clean Code SourceKit was the key 4

Slide 5

Slide 5 text

The SourceKit You Know • More than just crashes & HUDs • Very powerful tool 5

Slide 6

Slide 6 text

The SourceKit You See 6

Slide 7

Slide 7 text

The SourceKit You See • Syntax Highlighting • Code Completion • Indentation • Interface Generation • Documentation Rendering • Code Structure • USR Generation 7

Slide 8

Slide 8 text

apple/swift/tools/SourceKit SourceKit is a framework for supporting IDE features like indexing, syntax-coloring, code-completion, etc. In general it provides the infrastructure that an IDE needs for excellent language support. – Apple 8

Slide 9

Slide 9 text

The SourceKit You Don't See 9

Slide 10

Slide 10 text

The SourceKit You Don't See • Components • libIDE • libParse • libFormat • libXPC (Darwin-Only) • libdispatch (Darwin-Only) • C++ Implementation 10

Slide 11

Slide 11 text

The SourceKit You Interface With 11

Slide 12

Slide 12 text

The SourceKit You Interface With • C Interface • sourcekitd.framework • libsourcekitdInProc.dylib • Official Python Binding • Unofficial Swift Binding: SourceKitten 12

Slide 13

Slide 13 text

apple/swift/tools/SourceKit The stable C API for SourceKit is provided via the sourcekitd.framework which uses an XPC service for process isolation and the libsourcekitdInProc.dylib library which is in- process. – Apple 13

Slide 14

Slide 14 text

SourceKitten An adorable little framework and command line tool for interacting with SourceKit. github.com/jpsim/SourceKitten 14

Slide 15

Slide 15 text

$ brew install sourcekitten ... $ sourcekitten version 0.12.2 $ sourcekitten help Available commands: complete Generate code completion options doc Print Swift docs as JSON or Objective-C docs as XML help Display general or command-specific help index Index Swift file and print as JSON structure Print Swift structure information as JSON syntax Print Swift syntax information as JSON version Display the current version of SourceKitten 15

Slide 16

Slide 16 text

The SourceKit You Build On 16

Slide 17

Slide 17 text

The SourceKit You Build On • Code Analysis • Refactoring • Migration • Code Generation 17

Slide 18

Slide 18 text

Code Analysis 18

Slide 19

Slide 19 text

$ cat PublicDeclarations.swift public struct A { public func b() {} private func c() {} } $ echo $query [recurse(.["key.substructure"][]?) | select(."key.accessibility" == "source.lang.swift.accessibility.public") | ."key.name"?] $ sourcekitten structure --file PublicDeclarations.swift | jq $query ["A", "b()"] 19

Slide 20

Slide 20 text

$ cat FunctionsPerStruct.swift struct A { func one() {} } struct B { let nope = 0; func two() {}; func three() {} } $ echo $query [."key.substructure"[] | select(."key.kind" == "source.lang.swift.decl.struct") | {key: ."key.name", value: [ (."key.substructure"[] | select(."key.kind" == "source.lang.swift.decl.function.method.instance") | ."key.name")]} ] | from_entries $ sourcekitten structure --file FunctionsPerStruct.swift | jq $query {"A": ["one()"], "B": ["two()", "three()"]} 20

Slide 21

Slide 21 text

$ cat LongFunctionNames.swift func okThisFunctionMightHaveAnOverlyLongNameThatYouMightWantToRefactor() {} func nahThisOnesFine() { func youCanEvenFindNestedOnesIfYouRecurse() {} } $ echo $query [recurse(.["key.substructure"][]?) | select(."key.kind" | tostring | startswith("source.lang.swift.decl.function")) | select((."key.name" | length) > 20) | ."key.name"] $ sourcekitten structure --file LongFunctionNames.swift | jq $query [ "okThisFunctionMightHaveAnOverlyLongNameThatYouMightWantToRefactor()", "youCanEvenFindNestedOnesIfYouRecurse()" ] 21

Slide 22

Slide 22 text

Code Refactoring In ~30 lines of Swift... 22

Slide 23

Slide 23 text

Refactoring (refactor.swift 1/2) import SourceKittenFramework let arguments = Process.arguments let (file, usr, oldName, newName) = (arguments[1], arguments[2], arguments[3], arguments[4]) let index = (Request.Index(file: file).send()["key.entities"] as! [SourceKitRepresentable]) .map({ $0 as! [String: SourceKitRepresentable] }) func usesOfUSR(usr: String, dictionary: [String: SourceKitRepresentable]) -> [(line: Int, column: Int)] { if dictionary["key.usr"] as? String == usr, let line = dictionary["key.line"] as? Int64, let column = dictionary["key.column"] as? Int64 { return [(Int(line - 1), Int(column))] } return (dictionary["key.entities"] as? [SourceKitRepresentable])? .map({ $0 as! [String: SourceKitRepresentable] }) .flatMap { usesOfUSR(usr, dictionary: $0) } ?? [] } 23

Slide 24

Slide 24 text

Refactoring (refactor.swift 2/2) func renameUSR(usr: String, toName: String) { let uses = index.flatMap({ usesOfUSR(usr, dictionary: $0) }).sort(>) let fileContents = try! String(contentsOfFile: file) var lines = (fileContents as NSString).lines().map({ $0.content }) for use in uses { lines[use.line] = lines[use.line] .stringByReplacingOccurrencesOfString(oldName, withString: newName) } print(lines.joinWithSeparator("\n")) } renameUSR(usr, toName: newName) 24

Slide 25

Slide 25 text

$ cat CodeToRefactor.swift struct A { let prop = 0 } struct B { let prop = 1 } print(A().prop) print(B().prop) $ ./refactor.swift CodeToRefactor.swift s:vV4file1A4propSi prop newProp struct A { let newProp = 0 } struct B { let prop = 1 } print(A().newProp) print(B().prop) 25

Slide 26

Slide 26 text

Code Migration Same principle as refactoring... 26

Slide 27

Slide 27 text

Code Generation 27

Slide 28

Slide 28 text

$ cat GenerateXCTestManifest.swift class MyTests: XCTestCase { func nope() {}; func testYolo() {} } $ echo $query [."key.substructure"[] | select(."key.kind" == "source.lang.swift.decl.class") | select(."key.inheritedtypes"[]."key.name" == "XCTestCase") | {key: ."key.name", value: [ (."key.substructure"[] | select(."key.kind" == "source.lang.swift.decl.function.method.instance") | select(."key.name" | startswith("test")) | ."key.name")]} ] | from_entries $ sourcekitten structure --file GenerateXCTestManifest.swift | jq $query {"MyTests": ["testYolo()"]} 28

Slide 29

Slide 29 text

QueryKit Model Generation (generate.swift 1/2) import SourceKittenFramework struct Property { let name: String let type: String var swiftSourceRepresentation: String { return "static let \(name) = Property<\(type)>(name: \"\(name)\")" } } struct Model { let name: String let properties: [Property] var swiftSourceRepresentation: String { return "extension \(name) {\n" + properties.map({" \($0.swiftSourceRepresentation)"}).joinWithSeparator("\n") + "\n}" } } 29

Slide 30

Slide 30 text

QueryKit Model Generation (generate.swift 2/2) let structure = Structure(file: File(path: Process.arguments[1])!) let models = (structure.dictionary["key.substructure"] as! [SourceKitRepresentable]).map({ $0 as! [String: SourceKitRepresentable] }).filter({ substructure in return SwiftDeclarationKind(rawValue: substructure["key.kind"] as! String) == .Struct }).map { modelStructure in return Model(name: modelStructure["key.name"] as! String, properties: (modelStructure["key.substructure"] as! [SourceKitRepresentable]).map({ $0 as! [String: SourceKitRepresentable] }).filter({ substructure in return SwiftDeclarationKind(rawValue: substructure["key.kind"] as! String) == .VarInstance }).map { Property(name: $0["key.name"] as! String, type: $0["key.typename"] as! String) } ) } print(models.map({ $0.swiftSourceRepresentation }).joinWithSeparator("\n")) 30

Slide 31

Slide 31 text

$ cat QueryKitModels.swift struct Person { let name: String let age: Int } $ ./generate.swift QueryKitModels.swift extension Person { static let name = Property(name: "name") static let age = Property(name: "age") } 31

Slide 32

Slide 32 text

The SourceKit I Didn't Have Time To Cover In This Talk 32

Slide 33

Slide 33 text

The SourceKit I Didn't Have Time To Cover In This Talk • Code Formatting • Code Completion • Syntax Highlighting • Documentation Generation • More IDE-like Features 33

Slide 34

Slide 34 text

The SourceKit Others Have Built On 34

Slide 35

Slide 35 text

The SourceKit Others Have Built On • Jazzẏ㽈: Soulful docs for Swift & Objective-C • SwiftLint: A tool to enforce Swift style and conventions • Refactorator: Xcode Plugin that Refactors Swift & Objective-C • SourceKittenDaemon: Swift auto completion for any text editor • SwiftKitten: Swift autocompleter for Sublime Text • sourcekittendaemon.vim: Autocomplete Swift in Vim • company-sourcekit: Emacs company-mode completion for Swift 35

Slide 36

Slide 36 text

Next time you need to push Swift further... Try SourceKit 36

Slide 37

Slide 37 text

Thanks! JP Simard – @simjp – App Builders – Zürich – April 25, 2016 37