File: OneVariableDeclarationPerLine.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 (217 lines) | stat: -rw-r--r-- 8,625 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//===----------------------------------------------------------------------===//
//
// 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

/// Each variable declaration, with the exception of tuple destructuring, should
/// declare 1 variable.
///
/// Lint: If a variable declaration declares multiple variables, a lint error is
/// raised.
///
/// Format: If a variable declaration declares multiple variables, it will be
/// split into multiple declarations, each declaring one of the variables, as
/// long as the result would still be syntactically valid.
@_spi(Rules)
public final class OneVariableDeclarationPerLine: SyntaxFormatRule {
  public override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax {
    guard node.contains(where: codeBlockItemHasMultipleVariableBindings) else {
      return super.visit(node)
    }

    var newItems = [CodeBlockItemSyntax]()
    for codeBlockItem in node {
      guard let varDecl = codeBlockItem.item.as(VariableDeclSyntax.self),
        varDecl.bindings.count > 1
      else {
        // It's not a variable declaration with multiple bindings, so visit it
        // recursively (in case it's something that contains bindings that need
        // to be split) but otherwise do nothing.
        let newItem = super.visit(codeBlockItem)
        newItems.append(newItem)
        continue
      }

      diagnose(.onlyOneVariableDeclaration(specifier: varDecl.bindingSpecifier.text), on: varDecl)

      // Visit the decl recursively to make sure nested code block items in the
      // bindings (for example, an initializer expression that contains a
      // closure expression) are transformed first before we rewrite the decl
      // itself.
      let visitedDecl = super.visit(varDecl).as(VariableDeclSyntax.self)!
      var splitter = VariableDeclSplitter {
        CodeBlockItemSyntax(
          item: .decl(DeclSyntax($0)),
          semicolon: nil)
      }
      newItems.append(contentsOf: splitter.nodes(bySplitting: visitedDecl))
    }

    return CodeBlockItemListSyntax(newItems)
  }

  /// Returns true if the given `CodeBlockItemSyntax` contains a `let` or `var`
  /// declaration with multiple bindings.
  private func codeBlockItemHasMultipleVariableBindings(
    _ node: CodeBlockItemSyntax
  ) -> Bool {
    if let varDecl = node.item.as(VariableDeclSyntax.self),
      varDecl.bindings.count > 1
    {
      return true
    }
    return false
  }
}

extension Finding.Message {
  fileprivate static func onlyOneVariableDeclaration(specifier: String) -> Finding.Message {
    "split this variable declaration to introduce only one variable per '\(specifier)'"
  }
}

/// Splits a variable declaration with multiple bindings into individual
/// declarations.
///
/// Swift's grammar allows each identifier in a variable declaration to have a
/// type annotation, an initializer expression, both, or neither. Stricter
/// checks occur after parsing, however; a lone identifier may only be followed
/// by zero or more other lone identifiers and then an identifier with *only* a
/// type annotation (and the type annotation is applied to all of them). If we
/// have something else, we should handle them gracefully (i.e., not destroy
/// them) but we don't need to try to fix them since they didn't compile in the
/// first place so we can't guess what the user intended.
///
/// So, this algorithm works by scanning forward and collecting lone identifiers
/// in a queue until we reach a binding that has an initializer or a type
/// annotation. If we see a type annotation (without an initializer), we can
/// create individual variable declarations for each entry in the queue by
/// projecting that type annotation onto each of them. If we reach a case that
/// isn't valid, we just flush the queue contents as a single declaration, to
/// effectively preserve what the user originally had.
private struct VariableDeclSplitter<Node: SyntaxProtocol> {
  /// A function that takes a `VariableDeclSyntax` and returns a new node, such
  /// as a `CodeBlockItemSyntax`, that wraps it.
  private let generator: (VariableDeclSyntax) -> Node

  /// Bindings that have been collected so far and the trivia that preceded them.
  private var bindingQueue = [(PatternBindingSyntax, Trivia)]()

  /// The variable declaration being split.
  ///
  /// This is an implicitly-unwrapped optional because it isn't initialized
  /// until `nodes(bySplitting:)` is called.
  private var varDecl: VariableDeclSyntax!

  /// The list of nodes generated by splitting the variable declaration into
  /// individual bindings.
  private var nodes = [Node]()

  /// Tracks whether the trivia of `varDecl` has already been fixed up for nodes
  /// after the first.
  private var fixedUpTrivia = false

  /// Creates a new variable declaration splitter.
  ///
  /// - Parameter generator: A function that takes a `VariableDeclSyntax` and
  ///   returns a new node, such as a `CodeBlockItemSyntax`, that wraps it.
  init(generator: @escaping (VariableDeclSyntax) -> Node) {
    self.generator = generator
  }

  /// Returns an array of nodes generated by splitting the given variable
  /// declaration into individual bindings.
  mutating func nodes(bySplitting varDecl: VariableDeclSyntax) -> [Node] {
    self.varDecl = varDecl
    self.nodes = []

    // We keep track of trivia that precedes each binding (which is reflected as trailing trivia
    // on the previous token) so that we can reassociate it if we flush the bindings out as
    // individual variable decls. This means that we can rewrite `let /*a*/ a, /*b*/ b: Int` as
    // `let /*a*/ a: Int; let /*b*/ b: Int`, for example.
    var precedingTrivia = varDecl.bindingSpecifier.trailingTrivia

    for binding in varDecl.bindings {
      if binding.initializer != nil {
        // If this is the only initializer in the queue so far, that's ok. If
        // it's an initializer following other un-flushed lone identifier
        // bindings, that's not valid Swift. But in either case, we'll flush
        // them as a single decl.
        var newBinding = binding
        newBinding.trailingComma = nil
        bindingQueue.append((newBinding, precedingTrivia))
        flushRemaining()
      } else if let typeAnnotation = binding.typeAnnotation {
        bindingQueue.append((binding, precedingTrivia))
        flushIndividually(typeAnnotation: typeAnnotation)
      } else {
        bindingQueue.append((binding, precedingTrivia))
      }
      precedingTrivia = binding.trailingComma?.trailingTrivia ?? []
    }
    flushRemaining()

    return nodes
  }

  /// Replaces the original variable declaration with a copy of itself with
  /// updates trivia appropriate for subsequent declarations inserted by the
  /// rule.
  private mutating func fixOriginalVarDeclTrivia() {
    guard !fixedUpTrivia else { return }

    // We intentionally don't try to infer the indentation for subsequent
    // lines because the pretty printer will re-indent them correctly; we just
    // need to ensure that a newline is inserted before new decls.
    varDecl.leadingTrivia = [.newlines(1)]
    fixedUpTrivia = true
  }

  /// Flushes any remaining bindings as a single variable declaration.
  private mutating func flushRemaining() {
    guard !bindingQueue.isEmpty else { return }

    var newDecl = varDecl!
    newDecl.bindings = PatternBindingListSyntax(bindingQueue.map(\.0))
    nodes.append(generator(newDecl))

    fixOriginalVarDeclTrivia()

    bindingQueue = []
  }

  /// Flushes any remaining bindings as individual variable declarations where
  /// each has the given type annotation.
  private mutating func flushIndividually(
    typeAnnotation: TypeAnnotationSyntax
  ) {
    assert(!bindingQueue.isEmpty)

    for (binding, trailingTrivia) in bindingQueue {
      assert(binding.initializer == nil)

      var newBinding = binding
      newBinding.typeAnnotation = typeAnnotation
      newBinding.trailingComma = nil

      var newDecl = varDecl!
      newDecl.bindingSpecifier.trailingTrivia = trailingTrivia
      newDecl.bindings = PatternBindingListSyntax([newBinding])
      nodes.append(generator(newDecl))

      fixOriginalVarDeclTrivia()
    }

    bindingQueue = []
  }
}