Add new `invalid_swiftlint_command` rule (#4546)
This commit is contained in:
parent
d6e3bbb64d
commit
1c3c62e422
|
@ -89,6 +89,11 @@
|
||||||
* Catch more valid `no_magic_numbers` violations.
|
* Catch more valid `no_magic_numbers` violations.
|
||||||
[JP Simard](https://github.com/jpsim)
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
|
||||||
|
* Add `invalid_swiftlint_command` rule that validates
|
||||||
|
`// swiftlint:enable` and `disable` commands.
|
||||||
|
[Martin Redington](https://github.com/mildm8nnered)
|
||||||
|
[#4546](https://github.com/realm/SwiftLint/pull/4546)
|
||||||
|
|
||||||
* Improve `identifier_name` documentation.
|
* Improve `identifier_name` documentation.
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
[Martin Redington](https://github.com/mildm8nnered)
|
||||||
[#4767](https://github.com/realm/SwiftLint/issues/4767)
|
[#4767](https://github.com/realm/SwiftLint/issues/4767)
|
||||||
|
|
|
@ -165,7 +165,8 @@ extension SwiftLintFile {
|
||||||
|
|
||||||
internal var locationConverter: SourceLocationConverter { locationConverterCache.get(self) }
|
internal var locationConverter: SourceLocationConverter { locationConverterCache.get(self) }
|
||||||
|
|
||||||
internal var commands: [Command] { commandsCache.get(self) }
|
internal var commands: [Command] { commandsCache.get(self).filter { $0.isValid } }
|
||||||
|
internal var invalidCommands: [Command] { commandsCache.get(self).filter { !$0.isValid } }
|
||||||
|
|
||||||
internal var syntaxTokensByLines: [[SwiftLintSyntaxToken]] {
|
internal var syntaxTokensByLines: [[SwiftLintSyntaxToken]] {
|
||||||
guard let syntaxTokensByLines = syntaxTokensByLinesCache.get(self) else {
|
guard let syntaxTokensByLines = syntaxTokensByLinesCache.get(self) else {
|
||||||
|
|
|
@ -31,6 +31,9 @@ extension SwiftLintFile {
|
||||||
|
|
||||||
case .enable:
|
case .enable:
|
||||||
disabledRules.subtract(command.ruleIdentifiers)
|
disabledRules.subtract(command.ruleIdentifiers)
|
||||||
|
|
||||||
|
case .invalid:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Location(file: path, line: command.line, character: command.character)
|
let start = Location(file: path, line: command.line, character: command.character)
|
||||||
|
|
|
@ -8,6 +8,8 @@ public struct Command: Equatable {
|
||||||
case enable
|
case enable
|
||||||
/// The rule(s) associated with this command should be disabled by the SwiftLint engine.
|
/// The rule(s) associated with this command should be disabled by the SwiftLint engine.
|
||||||
case disable
|
case disable
|
||||||
|
/// The action string was invalid.
|
||||||
|
case invalid
|
||||||
|
|
||||||
/// - returns: The inverse action that can cancel out the current action, restoring the SwifttLint engine's
|
/// - returns: The inverse action that can cancel out the current action, restoring the SwifttLint engine's
|
||||||
/// state prior to the current action.
|
/// state prior to the current action.
|
||||||
|
@ -15,6 +17,7 @@ public struct Command: Equatable {
|
||||||
switch self {
|
switch self {
|
||||||
case .enable: return .disable
|
case .enable: return .disable
|
||||||
case .disable: return .enable
|
case .disable: return .enable
|
||||||
|
case .invalid: return .invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +30,8 @@ public struct Command: Equatable {
|
||||||
case this
|
case this
|
||||||
/// The command should only apply to the line following its definition.
|
/// The command should only apply to the line following its definition.
|
||||||
case next
|
case next
|
||||||
|
/// The modifier string was invalid.
|
||||||
|
case invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text after this delimiter is not considered part of the rule.
|
/// Text after this delimiter is not considered part of the rule.
|
||||||
|
@ -36,6 +41,10 @@ public struct Command: Equatable {
|
||||||
/// swiftlint:disable:next force_try - Explanation here
|
/// swiftlint:disable:next force_try - Explanation here
|
||||||
private static let commentDelimiter = " - "
|
private static let commentDelimiter = " - "
|
||||||
|
|
||||||
|
var isValid: Bool {
|
||||||
|
action != .invalid && modifier != .invalid && !ruleIdentifiers.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
internal let action: Action
|
internal let action: Action
|
||||||
internal let ruleIdentifiers: Set<RuleIdentifier>
|
internal let ruleIdentifiers: Set<RuleIdentifier>
|
||||||
internal let line: Int
|
internal let line: Int
|
||||||
|
@ -53,7 +62,7 @@ public struct Command: Equatable {
|
||||||
/// defined.
|
/// defined.
|
||||||
/// - parameter modifier: This command's modifier, if any.
|
/// - parameter modifier: This command's modifier, if any.
|
||||||
/// - parameter trailingComment: The comment following this command's `-` delimiter, if any.
|
/// - parameter trailingComment: The comment following this command's `-` delimiter, if any.
|
||||||
public init(action: Action, ruleIdentifiers: Set<RuleIdentifier>, line: Int = 0,
|
public init(action: Action, ruleIdentifiers: Set<RuleIdentifier> = [], line: Int = 0,
|
||||||
character: Int? = nil, modifier: Modifier? = nil, trailingComment: String? = nil) {
|
character: Int? = nil, modifier: Modifier? = nil, trailingComment: String? = nil) {
|
||||||
self.action = action
|
self.action = action
|
||||||
self.ruleIdentifiers = ruleIdentifiers
|
self.ruleIdentifiers = ruleIdentifiers
|
||||||
|
@ -69,24 +78,24 @@ public struct Command: Equatable {
|
||||||
/// - parameter line: The line in the source file where this command is defined.
|
/// - parameter line: The line in the source file where this command is defined.
|
||||||
/// - parameter character: The character offset within the line in the source file where this command is
|
/// - parameter character: The character offset within the line in the source file where this command is
|
||||||
/// defined.
|
/// defined.
|
||||||
public init?(actionString: String, line: Int, character: Int) {
|
public init(actionString: String, line: Int, character: Int) {
|
||||||
let scanner = Scanner(string: actionString)
|
let scanner = Scanner(string: actionString)
|
||||||
_ = scanner.scanString("swiftlint:")
|
_ = scanner.scanString("swiftlint:")
|
||||||
// (enable|disable)(:previous|:this|:next)
|
// (enable|disable)(:previous|:this|:next)
|
||||||
guard let actionAndModifierString = scanner.scanUpToString(" ") else {
|
guard let actionAndModifierString = scanner.scanUpToString(" ") else {
|
||||||
return nil
|
self.init(action: .invalid, line: line, character: character)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
let actionAndModifierScanner = Scanner(string: actionAndModifierString)
|
let actionAndModifierScanner = Scanner(string: actionAndModifierString)
|
||||||
guard let actionString = actionAndModifierScanner.scanUpToString(":"),
|
guard let actionString = actionAndModifierScanner.scanUpToString(":"),
|
||||||
let action = Action(rawValue: actionString)
|
let action = Action(rawValue: actionString)
|
||||||
else {
|
else {
|
||||||
return nil
|
self.init(action: .invalid, line: line, character: character)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.action = action
|
|
||||||
self.line = line
|
|
||||||
self.character = character
|
|
||||||
|
|
||||||
let rawRuleTexts = scanner.scanUpToString(Self.commentDelimiter) ?? ""
|
let rawRuleTexts = scanner.scanUpToString(Self.commentDelimiter) ?? ""
|
||||||
|
var trailingComment: String?
|
||||||
if scanner.isAtEnd {
|
if scanner.isAtEnd {
|
||||||
trailingComment = nil
|
trailingComment = nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,19 +112,26 @@ public struct Command: Equatable {
|
||||||
return component.isNotEmpty && component != "*/"
|
return component.isNotEmpty && component != "*/"
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleIdentifiers = Set(ruleTexts.map(RuleIdentifier.init(_:)))
|
let ruleIdentifiers = Set(ruleTexts.map(RuleIdentifier.init(_:)))
|
||||||
|
|
||||||
// Modifier
|
// Modifier
|
||||||
let hasModifier = actionAndModifierScanner.scanString(":") != nil
|
let hasModifier = actionAndModifierScanner.scanString(":") != nil
|
||||||
|
let modifier: Modifier?
|
||||||
if hasModifier {
|
if hasModifier {
|
||||||
modifier = Modifier(
|
let modifierString = String(actionAndModifierScanner.string[actionAndModifierScanner.currentIndex...])
|
||||||
rawValue: String(
|
modifier = Modifier(rawValue: modifierString) ?? .invalid
|
||||||
actionAndModifierScanner.string[actionAndModifierScanner.currentIndex...]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
modifier = nil
|
modifier = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.init(
|
||||||
|
action: action,
|
||||||
|
ruleIdentifiers: ruleIdentifiers,
|
||||||
|
line: line,
|
||||||
|
character: character,
|
||||||
|
modifier: modifier,
|
||||||
|
trailingComment: trailingComment
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands the current command into its fully descriptive form without any modifiers.
|
/// Expands the current command into its fully descriptive form without any modifiers.
|
||||||
|
@ -142,6 +158,8 @@ public struct Command: Equatable {
|
||||||
Self(action: action, ruleIdentifiers: ruleIdentifiers, line: line + 1),
|
Self(action: action, ruleIdentifiers: ruleIdentifiers, line: line + 1),
|
||||||
Self(action: action.inverse(), ruleIdentifiers: ruleIdentifiers, line: line + 1, character: Int.max)
|
Self(action: action.inverse(), ruleIdentifiers: ruleIdentifiers, line: line + 1, character: Int.max)
|
||||||
]
|
]
|
||||||
|
case .invalid:
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ let builtInRules: [Rule.Type] = [
|
||||||
InclusiveLanguageRule.self,
|
InclusiveLanguageRule.self,
|
||||||
IndentationWidthRule.self,
|
IndentationWidthRule.self,
|
||||||
InertDeferRule.self,
|
InertDeferRule.self,
|
||||||
|
InvalidSwiftLintCommandRule.self,
|
||||||
IsDisjointRule.self,
|
IsDisjointRule.self,
|
||||||
JoinedDefaultParameterRule.self,
|
JoinedDefaultParameterRule.self,
|
||||||
LargeTupleRule.self,
|
LargeTupleRule.self,
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Foundation
|
||||||
|
import SwiftSyntax
|
||||||
|
|
||||||
|
struct InvalidSwiftLintCommandRule: ConfigurationProviderRule {
|
||||||
|
var configuration = SeverityConfiguration(.warning)
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
static let description = RuleDescription(
|
||||||
|
identifier: "invalid_swiftlint_command",
|
||||||
|
name: "Invalid SwiftLint Command",
|
||||||
|
description: "swiftlint command does not have a valid action or modifier",
|
||||||
|
kind: .lint,
|
||||||
|
nonTriggeringExamples: [
|
||||||
|
Example("// swiftlint:disable unused_import"),
|
||||||
|
Example("// swiftlint:enable unused_import"),
|
||||||
|
Example("// swiftlint:disable:next unused_import"),
|
||||||
|
Example("// swiftlint:disable:previous unused_import"),
|
||||||
|
Example("// swiftlint:disable:this unused_import")
|
||||||
|
],
|
||||||
|
triggeringExamples: [
|
||||||
|
Example("// swiftlint:"),
|
||||||
|
Example("// swiftlint: "),
|
||||||
|
Example("// swiftlint::"),
|
||||||
|
Example("// swiftlint:: "),
|
||||||
|
Example("// swiftlint:disable"),
|
||||||
|
Example("// swiftlint:dissable unused_import"),
|
||||||
|
Example("// swiftlint:enaaaable unused_import"),
|
||||||
|
Example("// swiftlint:disable:nxt unused_import"),
|
||||||
|
Example("// swiftlint:enable:prevus unused_import"),
|
||||||
|
Example("// swiftlint:enable:ths unused_import"),
|
||||||
|
Example("// swiftlint:enable"),
|
||||||
|
Example("// swiftlint:enable:"),
|
||||||
|
Example("// swiftlint:enable: "),
|
||||||
|
Example("// swiftlint:disable: unused_import")
|
||||||
|
].skipWrappingInCommentTests()
|
||||||
|
)
|
||||||
|
|
||||||
|
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||||
|
file.invalidCommands.map {
|
||||||
|
let location = Location(file: file.path, line: $0.line, character: $0.character)
|
||||||
|
return StyleViolation(
|
||||||
|
ruleDescription: Self.description,
|
||||||
|
severity: configuration.severity,
|
||||||
|
location: location
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,9 +36,9 @@ private extension Trivia {
|
||||||
case let actionString = String(comment[lower...]),
|
case let actionString = String(comment[lower...]),
|
||||||
case let end = locationConverter.location(for: offset + triviaOffset),
|
case let end = locationConverter.location(for: offset + triviaOffset),
|
||||||
let line = end.line,
|
let line = end.line,
|
||||||
let column = end.column,
|
let column = end.column
|
||||||
let command = Command(actionString: actionString, line: line, character: column)
|
|
||||||
{
|
{
|
||||||
|
let command = Command(actionString: actionString, line: line, character: column)
|
||||||
results.append(command)
|
results.append(command)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -517,6 +517,12 @@ class InertDeferRuleGeneratedTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InvalidSwiftLintCommandRuleGeneratedTests: XCTestCase {
|
||||||
|
func testWithDefaultConfiguration() {
|
||||||
|
verifyRule(InvalidSwiftLintCommandRule.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class IsDisjointRuleGeneratedTests: XCTestCase {
|
class IsDisjointRuleGeneratedTests: XCTestCase {
|
||||||
func testWithDefaultConfiguration() {
|
func testWithDefaultConfiguration() {
|
||||||
verifyRule(IsDisjointRule.description)
|
verifyRule(IsDisjointRule.description)
|
||||||
|
|
Loading…
Reference in New Issue