Slide 1

Slide 1 text

How lint rules implemented in swift- format 1 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 2

Slide 2 text

kitasuke → Swift Compiler → SwiftSyntax → swift-format 2 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 3

Slide 3 text

Table of Contents → Swift Compiler → SwiftSyntax(SwiftParser) → swift-format → Lint Rules 3 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 4

Slide 4 text

Overview of Swift Compiler 4 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 5

Slide 5 text

Swift Swift is a high-performance system programming language. It has a clean and modern syntax, offers seamless access to existing C and Objective-C code and frameworks, and is memory-safe by default. 5 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 6

Slide 6 text

Swift Compiler Pipeline 6 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 7

Slide 7 text

Parsing 7 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 8

Slide 8 text

Parsing The parser is a simple, recursive-descent parser with an integrated, hand-coded lexer. The parser is responsible for generating an Abstract Syntax Tree (AST) without any semantic or type information, and emits warnings or errors for grammatical problems with the input source. 8 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 9

Slide 9 text

Overview of SwiftSyntax 9 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 10

Slide 10 text

SwiftSyntax SwiftSyntax is a set of Swift libraries for parsing, inspecting, generating, and transforming Swift source code. 10 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 11

Slide 11 text

SwiftSyntax → SwiftParser → SwiftOperators → SwiftSyntaxBuilder → SwiftSyntaxMacros 11 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 12

Slide 12 text

SwiftParser The SwiftParser framework implements a parser that accepts Swift source text as input and produces a SwiftSyntax syntax tree. 12 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 13

Slide 13 text

SwiftParser 13 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 14

Slide 14 text

Source text let foo = "bar" 14 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 15

Slide 15 text

Syntax tree // let foo = "bar" SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern foo InitializerClause = StringLiteralExpr " StringLiteralSegments StringSegment bar " 15 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 16

Slide 16 text

VariableDecl // let foo = "bar" SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern foo InitializerClause = StringLiteralExpr " StringLiteralSegments StringSegment bar " 16 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 17

Slide 17 text

IdentifierPattern // let foo = "bar" SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern foo InitializerClause = StringLiteralExpr " StringLiteralSegments StringSegment bar " 17 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 18

Slide 18 text

= // let foo = "bar" SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern foo InitializerClause = StringLiteralExpr " StringLiteralSegments StringSegment bar " 18 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 19

Slide 19 text

StringLiteralExpr // let foo = "bar" SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern foo InitializerClause = StringLiteralExpr " StringLiteralSegments StringSegment bar " 19 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 20

Slide 20 text

More details Check out Quick Overview of SwiftParser 20 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 21

Slide 21 text

Recap of static analysis in Swift 21 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 22

Slide 22 text

Swift Compiler → Syntax → Type → Semantic 22 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 23

Slide 23 text

Linter → Grammatically correct, but bad code → Coding styles 23 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 24

Slide 24 text

Overview of swift-format 24 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 25

Slide 25 text

swift-format swift-format provides the formatting technology for SourceKit-LSP and the building blocks for doing code formatting transformations. This package can be used as a command line tool or linked into other applications. 25 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 26

Slide 26 text

SourceKit-LSP SourceKit-LSP is an implementation of the Language Server Protocol (LSP) for Swift and C- based languages. It provides features like code- completion and jump-to-definition to editors that support LSP 26 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 27

Slide 27 text

Features → Formatting → Linting 27 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 28

Slide 28 text

Lint Rules → AllPublicDeclarationsHaveDocumentation → NeverForceUnwrap → NeverUseForceTry ... 28 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 29

Slide 29 text

NeverForceUnwrap /// Force-unwraps are strongly discouraged and must be documented. /// /// This rule does not apply to test code, defined as code which: /// * Contains the line `import XCTest` /// /// Lint: If a force unwrap is used, a lint warning is raised. 29 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 30

Slide 30 text

Optional in Swift ❌ // force unwrap → runtime crash let b = a as! Int let d = String(a)! ✅ // optional binding if let b = as? Int {...} if let d = String(a) {...} 30 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 31

Slide 31 text

SwiftSyntax APIs 31 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 32

Slide 32 text

SyntaxNodes → SourceFile → VariableDecl → ForcedValueExpr → FunctionCallExpr → IdentifierExpr ... 32 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 33

Slide 33 text

Syntax Tree // let b = String(a)! SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern b InitializerClause = ForcedValueExpr FunctionCallExpr IdentifierExpr String ( TupleExprElementList TupleExprElement IdentifierExpr a ) ! 33 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 34

Slide 34 text

ForcedValueExpr // let b = String(a)! SourceFile CodeBlockItemList CodeBlockItem VariableDecl let PatternBindingList PatternBinding IdentifierPattern b InitializerClause = ForcedValueExpr FunctionCallExpr IdentifierExpr String ( TupleExprElementList TupleExprElement IdentifierExpr a ) ! 34 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 35

Slide 35 text

SyntaxVisitor /// The enum describes how the SyntaxVistor should continue after visiting /// the current node. public enum SyntaxVisitorContinueKind { /// The visitor should visit the descendents of the current node. case visitChildren /// The visitor should avoid visiting the descendents of the current node. case skipChildren } open class SyntaxVisitor { /// Walk all nodes of the given syntax tree, calling the corresponding `visit` /// function for every node that is being visited. public func walk(_ node: some SyntaxProtocol) { visit(node.data) } open func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } ... } 35 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 36

Slide 36 text

walk /// The enum describes how the SyntaxVistor should continue after visiting /// the current node. public enum SyntaxVisitorContinueKind { /// The visitor should visit the descendents of the current node. case visitChildren /// The visitor should avoid visiting the descendents of the current node. case skipChildren } open class SyntaxVisitor { /// Walk all nodes of the given syntax tree, calling the corresponding `visit` /// function for every node that is being visited. public func walk(_ node: some SyntaxProtocol) { visit(node.data) } open func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } ... } 36 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 37

Slide 37 text

visit /// The enum describes how the SyntaxVistor should continue after visiting /// the current node. public enum SyntaxVisitorContinueKind { /// The visitor should visit the descendents of the current node. case visitChildren /// The visitor should avoid visiting the descendents of the current node. case skipChildren } open class SyntaxVisitor { /// Walk all nodes of the given syntax tree, calling the corresponding `visit` /// function for every node that is being visited. public func walk(_ node: some SyntaxProtocol) { visit(node.data) } open func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } ... } 37 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 38

Slide 38 text

visit for every node open func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { return .visitChildren // CodeBlockItemListSyntax } open func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { return .visitChildren // CodeBlockItemSyntax } open func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { return .visitChildren // VariableDeclSyntax } open func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { return .visitChildren // PatternBindingList } ... 38 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 39

Slide 39 text

visit or skip 39 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 40

Slide 40 text

How to implement in swift-format 40 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 41

Slide 41 text

parse & walk import SwiftParser import SwiftSyntax // swift source text let sourceText = "let b = String(a)!" // swift syntax let sourceFile: SourceFileSyntax = Parser.parse(source: sourceText) let pipeline = LintPipeline(context: context) pipeline.walk(Syntax(sourceFile)) 41 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 42

Slide 42 text

source text → SourceFileSyntax import SwiftParser import SwiftSyntax // swift source text let sourceText = "let b = String(a)!" // swift syntax let sourceFile: SourceFileSyntax = Parser.parse(source: sourceText) let pipeline = LintPipeline(context: context) pipeline.walk(Syntax(sourceFile)) 42 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 43

Slide 43 text

SyntaxVistor walks SourceFileSyntax import SwiftParser import SwiftSyntax // swift source text let sourceText = "let b = String(a)!" // swift syntax let sourceFile: SourceFileSyntax = Parser.parse(source: sourceText) let pipeline = LintPipeline(context: context) pipeline.walk(Syntax(sourceFile)) 43 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 44

Slide 44 text

LintPipeline import SwiftSyntax class LintPipeline: SyntaxVisitor { override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) visitIfEnabled(NeverUseForceTry.visit, for: node) ... return .visitChildren } // as! Int override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } // String(a)! override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } ... } 44 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 45

Slide 45 text

LintPipeline import SwiftSyntax class LintPipeline: SyntaxVisitor { override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) visitIfEnabled(NeverUseForceTry.visit, for: node) ... return .visitChildren } // as! Int override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } // String(a)! override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } ... } 45 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 46

Slide 46 text

LintPipeline import SwiftSyntax class LintPipeline: SyntaxVisitor { override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) visitIfEnabled(NeverUseForceTry.visit, for: node) ... return .visitChildren } // as! Int override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } // String(a)! override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } ... } 46 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 47

Slide 47 text

LintPipeline import SwiftSyntax class LintPipeline: SyntaxVisitor { override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) visitIfEnabled(NeverUseForceTry.visit, for: node) ... return .visitChildren } // as! Int override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } // String(a)! override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } ... } 47 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 48

Slide 48 text

NeverForceUnwrap import SwiftSyntax public final class NeverForceUnwrap: SyntaxVisitor { public override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { // diagnoses force unwrapping diagnose() // no need to visit children anymore return .skipChildren } ... } 48 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 49

Slide 49 text

diagnose import SwiftSyntax public final class NeverForceUnwrap: SyntaxVisitor { public override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { // diagnoses force unwrapping diagnose() // no need to visit children anymore return .skipChildren } ... } 49 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 50

Slide 50 text

skipChildren import SwiftSyntax public final class NeverForceUnwrap: SyntaxVisitor { public override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind { // diagnoses force unwrapping diagnose() // no need to visit children anymore return .skipChildren } ... } 50 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 51

Slide 51 text

NeverForceUnwrap ❌ // force unwrap → runtime crash let b = a as! Int let d = String(a)! ✅ // optional binding if let b = as? Int {...} if let d = String(a) {...} 51 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 52

Slide 52 text

Recap → Swift syntax tree → walk and visit children → AST not available 52 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)

Slide 53

Slide 53 text

References → swift-format/Sources/SwiftFormatRules/ NeverForceUnwrap.swift → swift-ast-explorer → Quick Overview of SwiftParser 53 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)