173 lines
6.0 KiB
Swift
173 lines
6.0 KiB
Swift
import Foundation
|
||
import IDEUtils
|
||
import SourceKittenFramework
|
||
|
||
struct CommentSpacingRule: SourceKitFreeRule, ConfigurationProviderRule, SubstitutionCorrectableRule {
|
||
var configuration = SeverityConfiguration(.warning)
|
||
|
||
init() {}
|
||
|
||
static let description = RuleDescription(
|
||
identifier: "comment_spacing",
|
||
name: "Comment Spacing",
|
||
description: "Prefer at least one space after slashes for comments.",
|
||
kind: .lint,
|
||
nonTriggeringExamples: [
|
||
Example("""
|
||
// This is a comment
|
||
"""),
|
||
Example("""
|
||
/// Triple slash comment
|
||
"""),
|
||
Example("""
|
||
// Multiline double-slash
|
||
// comment
|
||
"""),
|
||
Example("""
|
||
/// Multiline triple-slash
|
||
/// comment
|
||
"""),
|
||
Example("""
|
||
/// Multiline triple-slash
|
||
/// - This is indented
|
||
"""),
|
||
Example("""
|
||
// - MARK: Mark comment
|
||
"""),
|
||
Example("""
|
||
//: Swift Playground prose section
|
||
"""),
|
||
Example("""
|
||
///////////////////////////////////////////////
|
||
// Comment with some lines of slashes boxing it
|
||
///////////////////////////////////////////////
|
||
"""),
|
||
Example("""
|
||
//:#localized(key: "SwiftPlaygroundLocalizedProse")
|
||
"""),
|
||
Example("""
|
||
/* Asterisk comment */
|
||
"""),
|
||
Example("""
|
||
/*
|
||
Multiline asterisk comment
|
||
*/
|
||
"""),
|
||
Example("""
|
||
/*:
|
||
Multiline Swift Playground prose section
|
||
*/
|
||
"""),
|
||
Example("""
|
||
/*#-editable-code Swift Platground editable area*/default/*#-end-editable-code*/
|
||
""")
|
||
],
|
||
triggeringExamples: [
|
||
Example("""
|
||
//↓Something
|
||
"""),
|
||
Example("""
|
||
//↓MARK
|
||
"""),
|
||
Example("""
|
||
//↓👨👨👦👦Something
|
||
"""),
|
||
Example("""
|
||
func a() {
|
||
//↓This needs refactoring
|
||
print("Something")
|
||
}
|
||
//↓We should improve above function
|
||
"""),
|
||
Example("""
|
||
///↓This is a comment
|
||
"""),
|
||
Example("""
|
||
/// Multiline triple-slash
|
||
///↓This line is incorrect, though
|
||
"""),
|
||
Example("""
|
||
//↓- MARK: Mark comment
|
||
"""),
|
||
Example("""
|
||
//:↓Swift Playground prose section
|
||
""")
|
||
],
|
||
corrections: [
|
||
Example("//↓Something"): Example("// Something"),
|
||
Example("//↓- MARK: Mark comment"): Example("// - MARK: Mark comment"),
|
||
Example("""
|
||
/// Multiline triple-slash
|
||
///↓This line is incorrect, though
|
||
"""): Example("""
|
||
/// Multiline triple-slash
|
||
/// This line is incorrect, though
|
||
"""),
|
||
Example("""
|
||
func a() {
|
||
//↓This needs refactoring
|
||
print("Something")
|
||
}
|
||
//↓We should improve above function
|
||
"""): Example("""
|
||
func a() {
|
||
// This needs refactoring
|
||
print("Something")
|
||
}
|
||
// We should improve above function
|
||
""")
|
||
]
|
||
)
|
||
|
||
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
|
||
// Find all comment tokens in the file and regex search them for violations
|
||
file.syntaxClassifications
|
||
.compactMap { (classifiedRange: SyntaxClassifiedRange) -> [NSRange]? in
|
||
switch classifiedRange.kind {
|
||
case .blockComment, .docBlockComment, .lineComment, .docLineComment:
|
||
break
|
||
default:
|
||
return nil
|
||
}
|
||
|
||
let range = classifiedRange.range.toSourceKittenByteRange()
|
||
return file.stringView
|
||
.substringWithByteRange(range)
|
||
.map(StringView.init)
|
||
.map { commentBody in
|
||
// Look for 2+ slash characters followed immediately by
|
||
// a non-colon, non-whitespace character or by a colon
|
||
// followed by a non-whitespace character other than #
|
||
regex(#"^(?:\/){2,}+(?:[^\s:]|:[^\s#])"#).matches(in: commentBody, options: .anchored)
|
||
.compactMap { result in
|
||
// Set the location to be directly before the first non-slash,
|
||
// non-whitespace character which was matched
|
||
return file.stringView.byteRangeToNSRange(
|
||
ByteRange(
|
||
// Safe to mix NSRange offsets with byte offsets here because the regex can't
|
||
// contain multi-byte characters
|
||
location: ByteCount(range.lowerBound.value + result.range.upperBound - 1),
|
||
length: 0
|
||
)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
.flatMap { $0 }
|
||
}
|
||
|
||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||
return violationRanges(in: file).map { range in
|
||
StyleViolation(
|
||
ruleDescription: Self.description,
|
||
severity: configuration.severity,
|
||
location: Location(file: file, characterOffset: range.location)
|
||
)
|
||
}
|
||
}
|
||
|
||
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
|
||
return (violationRange, " ")
|
||
}
|
||
}
|