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)'"
}
}
|