File: NoParensAroundConditions.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (131 lines) | stat: -rw-r--r-- 4,707 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

/// Enforces rules around parentheses in conditions or matched expressions.
///
/// Parentheses are not used around any condition of an `if`, `guard`, or `while` statement, or
/// around the matched expression in a `switch` statement.
///
/// Lint: If a top-most expression in a `switch`, `if`, `guard`, or `while` statement is surrounded
///       by parentheses, and it does not include a function call with a trailing closure, a lint
///       error is raised.
///
/// Format: Parentheses around such expressions are removed, if they do not cause a parse ambiguity.
///         Specifically, parentheses are allowed if and only if the expression contains a function
///         call with a trailing closure.
@_spi(Rules)
public final class NoParensAroundConditions: SyntaxFormatRule {
  public override func visit(_ node: IfExprSyntax) -> ExprSyntax {
    var result = node
    fixKeywordTrailingTrivia(&result.ifKeyword.trailingTrivia)
    result.conditions = visit(node.conditions)
    result.body = visit(node.body)
    if let elseBody = node.elseBody {
      result.elseBody = visit(elseBody)
    }
    return ExprSyntax(result)
  }

  public override func visit(_ node: ConditionElementSyntax) -> ConditionElementSyntax {
    guard
      case .expression(let condition) = node.condition,
      let newExpr = minimalSingleExpression(condition)
    else {
      return super.visit(node)
    }

    var result = node
    result.condition = .expression(newExpr)
    return result
  }

  public override func visit(_ node: GuardStmtSyntax) -> StmtSyntax {
    var result = node
    fixKeywordTrailingTrivia(&result.guardKeyword.trailingTrivia)
    result.conditions = visit(node.conditions)
    result.body = visit(node.body)
    return StmtSyntax(result)
  }

  public override func visit(_ node: SwitchExprSyntax) -> ExprSyntax {
    guard let newSubject = minimalSingleExpression(node.subject) else {
      return super.visit(node)
    }

    var result = node
    fixKeywordTrailingTrivia(&result.switchKeyword.trailingTrivia)
    result.subject = newSubject
    result.cases = visit(node.cases)
    return ExprSyntax(result)
  }

  public override func visit(_ node: RepeatStmtSyntax) -> StmtSyntax {
    guard let newCondition = minimalSingleExpression(node.condition) else {
      return super.visit(node)
    }

    var result = node
    fixKeywordTrailingTrivia(&result.whileKeyword.trailingTrivia)
    result.condition = newCondition
    result.body = visit(node.body)
    return StmtSyntax(result)
  }

  public override func visit(_ node: WhileStmtSyntax) -> StmtSyntax {
    var result = node
    fixKeywordTrailingTrivia(&result.whileKeyword.trailingTrivia)
    result.conditions = visit(node.conditions)
    result.body = visit(node.body)
    return StmtSyntax(result)
  }

  private func fixKeywordTrailingTrivia(_ trivia: inout Trivia) {
    guard trivia.isEmpty else { return }
    trivia = [.spaces(1)]
  }

  private func minimalSingleExpression(_ original: ExprSyntax) -> ExprSyntax? {
    guard
      let tuple = original.as(TupleExprSyntax.self),
      tuple.elements.count == 1,
      let expr = tuple.elements.first?.expression
    else {
      return nil
    }

    // If the condition is a function with a trailing closure or if it's an immediately called
    // closure, removing the outer set of parentheses introduces a parse ambiguity.
    if let fnCall = expr.as(FunctionCallExprSyntax.self) {
      if fnCall.trailingClosure != nil {
        // Leave parentheses around call with trailing closure.
        return ExprSyntax(tuple)
      } else if fnCall.calledExpression.as(ClosureExprSyntax.self) != nil {
        // Leave parentheses around immediately called closure.
        return ExprSyntax(tuple)
      }
    }

    diagnose(.removeParensAroundExpression, on: tuple.leftParen)

    var visitedExpr = visit(expr)
    visitedExpr.leadingTrivia = tuple.leftParen.leadingTrivia
    visitedExpr.trailingTrivia = tuple.rightParen.trailingTrivia
    return visitedExpr
  }
}

extension Finding.Message {
  fileprivate static let removeParensAroundExpression: Finding.Message =
    "remove the parentheses around this expression"
}