mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
99 lines
4.2 KiB
Swift
99 lines
4.2 KiB
Swift
import Foundation
|
|
|
|
struct InvalidSwiftLintCommandRule: Rule, SourceKitFreeRule {
|
|
var configuration = SeverityConfiguration<Self>(.warning)
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "invalid_swiftlint_command",
|
|
name: "Invalid SwiftLint Command",
|
|
description: "swiftlint command is invalid",
|
|
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"),
|
|
Example("//swiftlint:disable:this unused_import"),
|
|
Example("_ = \"🤵🏼♀️\" // swiftlint:disable:this unused_import", excludeFromDocumentation: true),
|
|
Example("_ = \"🤵🏼♀️ 🤵🏼♀️\" // swiftlint:disable:this unused_import", excludeFromDocumentation: true),
|
|
],
|
|
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"),
|
|
Example("// s↓swiftlint:disable unused_import"),
|
|
Example("// 🤵🏼♀️swiftlint:disable unused_import", excludeFromDocumentation: true),
|
|
].skipWrappingInCommentTests()
|
|
)
|
|
|
|
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
|
badPrefixViolations(in: file) + invalidCommandViolations(in: file)
|
|
}
|
|
|
|
private func badPrefixViolations(in file: SwiftLintFile) -> [StyleViolation] {
|
|
(file.commands + file.invalidCommands).compactMap { command in
|
|
command.isPrecededByInvalidCharacter(in: file)
|
|
? styleViolation(
|
|
for: command,
|
|
in: file,
|
|
reason: "swiftlint command should be preceded by whitespace or a comment character"
|
|
)
|
|
: nil
|
|
}
|
|
}
|
|
|
|
private func invalidCommandViolations(in file: SwiftLintFile) -> [StyleViolation] {
|
|
file.invalidCommands.map { command in
|
|
styleViolation(for: command, in: file, reason: command.invalidReason() ?? Self.description.description)
|
|
}
|
|
}
|
|
|
|
private func styleViolation(for command: Command, in file: SwiftLintFile, reason: String) -> StyleViolation {
|
|
StyleViolation(
|
|
ruleDescription: Self.description,
|
|
severity: configuration.severity,
|
|
location: Location(file: file.path, line: command.line, character: command.range?.lowerBound),
|
|
reason: reason
|
|
)
|
|
}
|
|
}
|
|
|
|
private extension Command {
|
|
func isPrecededByInvalidCharacter(in file: SwiftLintFile) -> Bool {
|
|
guard line > 0, let character = range?.lowerBound, character > 1, line <= file.lines.count else {
|
|
return false
|
|
}
|
|
let line = file.lines[line - 1].content
|
|
guard line.count > character,
|
|
let char = line[line.index(line.startIndex, offsetBy: character - 2)].unicodeScalars.first else {
|
|
return false
|
|
}
|
|
return !CharacterSet.whitespaces.union(CharacterSet(charactersIn: "/")).contains(char)
|
|
}
|
|
|
|
func invalidReason() -> String? {
|
|
if action == .invalid {
|
|
return "swiftlint command does not have a valid action"
|
|
}
|
|
if modifier == .invalid {
|
|
return "swiftlint command does not have a valid modifier"
|
|
}
|
|
if ruleIdentifiers.isEmpty {
|
|
return "swiftlint command does not specify any rules"
|
|
}
|
|
return nil
|
|
}
|
|
}
|