File: NoLeadingUnderscores.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-- 5,557 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

/// Identifiers in declarations and patterns should not have leading underscores.
///
/// This is intended to avoid certain antipatterns; `self.member = member` should be preferred to
/// `member = _member` and the leading underscore should not be used to signal access level.
///
/// This rule intentionally checks only the parameter variable names of a function declaration, not
/// the parameter labels. It also only checks identifiers at the declaration site, not at usage
/// sites.
///
/// Lint: Declaring an identifier with a leading underscore yields a lint error.
@_spi(Rules)
public final class NoLeadingUnderscores: SyntaxLintRule {

  /// Identifies this rule as being opt-in. While leading underscores aren't meant to be used in
  /// normal circumstances, there are situations where they can be used to hint which APIs should be
  /// avoided by general users. In particular when APIs must be exported publicly, but the author
  /// doesn't intend for arbitrary usage.
  public override class var isOptIn: Bool { return true }

  public override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind {
    // If both names are provided, we want to check `secondName`, which will be the parameter name
    // (in that case, `firstName` is the label). If only one name is present, then it is recorded in
    // `firstName`, and it is both the label and the parameter name.
    diagnoseIfNameStartsWithUnderscore(node.secondName ?? node.firstName)
    return .visitChildren
  }

  public override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind {
    // If both names are provided, we want to check `secondName`, which will be the parameter name
    // (in that case, `firstName` is the label). If only one name is present, then it is recorded in
    // `firstName`, and it is both the label and the parameter name.
    if let variableIdentifier = node.secondName ?? node.firstName {
      diagnoseIfNameStartsWithUnderscore(variableIdentifier)
    }
    return .visitChildren
  }

  public override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind {
    // If both names are provided, we want to check `secondName`, which will be the parameter name
    // (in that case, `firstName` is the label). If only one name is present, then it is recorded in
    // `firstName`, and it is both the label and the parameter name.
    diagnoseIfNameStartsWithUnderscore(node.secondName ?? node.firstName)
    return .visitChildren
  }

  public override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.identifier)
    return .visitChildren
  }

  public override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  public override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
    diagnoseIfNameStartsWithUnderscore(node.name)
    return .visitChildren
  }

  /// Checks the given token to determine if it begins with an underscore (but is not *just* an
  /// underscore, which is allowed), emitting a diagnostic if it does.
  ///
  /// - Parameter token: The token to check.
  private func diagnoseIfNameStartsWithUnderscore(_ token: TokenSyntax) {
    let text = token.text
    if text.count > 1 && text.first == "_" {
      diagnose(.doNotStartWithUnderscore(identifier: text), on: token)
    }
  }
}

extension Finding.Message {
  fileprivate static func doNotStartWithUnderscore(identifier: String) -> Finding.Message {
    "remove the leading '_' from the name '\(identifier)'"
  }
}