Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How lint rules implemented in swift-format

How lint rules implemented in swift-format

Lint Night #2

Yusuke Kita

August 04, 2023
Tweet

More Decks by Yusuke Kita

Other Decks in Programming

Transcript

  1. How lint rules implemented in swift- format 1 — How

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

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

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

    in swift-format, Yusuke Kita (@kitasuke)
  5. 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)
  6. Swift Compiler Pipeline 6 — How lint rules implemented in

    swift-format, Yusuke Kita (@kitasuke)
  7. 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)
  8. Overview of SwiftSyntax 9 — How lint rules implemented in

    swift-format, Yusuke Kita (@kitasuke)
  9. 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)
  10. SwiftSyntax → SwiftParser → SwiftOperators → SwiftSyntaxBuilder → SwiftSyntaxMacros 11

    — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)
  11. 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)
  12. Source text let foo = "bar" 14 — How lint

    rules implemented in swift-format, Yusuke Kita (@kitasuke)
  13. 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)
  14. 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)
  15. 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)
  16. = // 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)
  17. 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)
  18. More details Check out Quick Overview of SwiftParser 20 —

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

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

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

    23 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)
  22. 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)
  23. 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)
  24. Features → Formatting → Linting 27 — How lint rules

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

    — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)
  26. 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)
  27. 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)
  28. SyntaxNodes → SourceFile → VariableDecl → ForcedValueExpr → FunctionCallExpr →

    IdentifierExpr ... 32 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)
  29. 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)
  30. 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)
  31. 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)
  32. 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)
  33. 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)
  34. 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)
  35. visit or skip 39 — How lint rules implemented in

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

    implemented in swift-format, Yusuke Kita (@kitasuke)
  37. 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)
  38. 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)
  39. 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)
  40. 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)
  41. 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)
  42. 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)
  43. 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)
  44. 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)
  45. 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)
  46. 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)
  47. 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)
  48. Recap → Swift syntax tree → walk and visit children

    → AST not available 52 — How lint rules implemented in swift-format, Yusuke Kita (@kitasuke)
  49. 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)