SwiftLint/Source/SwiftLintFramework/Rules/Lint/PeriodSpacingRule.swift

95 lines
4.3 KiB
Swift

import Foundation
import IDEUtils
import SourceKittenFramework
struct PeriodSpacingRule: SourceKitFreeRule, ConfigurationProviderRule, OptInRule, SubstitutionCorrectableRule {
var configuration = SeverityConfiguration(.warning)
init() {}
static let description = RuleDescription(
identifier: "period_spacing",
name: "Period Spacing",
description: "Periods should not be followed by more than one space",
kind: .style,
nonTriggeringExamples: [
Example("let pi = 3.2"),
Example("let pi = Double.pi"),
Example("let pi = Double. pi"),
Example("let pi = Double. pi"),
Example("// A. Single."),
Example("/// - code: Identifier of the error. Integer."),
Example("""
// value: Multiline.
// Comment.
"""),
Example("""
/**
Sentence ended in period.
- Sentence 2 new line characters after.
**/
""")
],
triggeringExamples: [
Example("/* Only god knows why. ↓ This symbol does nothing. */", testWrappingInComment: false),
Example("// Only god knows why. ↓ This symbol does nothing.", testWrappingInComment: false),
Example("// Single. Double. ↓ End.", testWrappingInComment: false),
Example("// Single. Double. ↓ Triple. ↓ End.", testWrappingInComment: false),
Example("// Triple. ↓ Quad. ↓ End.", testWrappingInComment: false),
Example("/// - code: Identifier of the error. ↓ Integer.", testWrappingInComment: false)
],
corrections: [
Example("/* Why. ↓ Symbol does nothing. */"): Example("/* Why. Symbol does nothing. */"),
Example("// Why. ↓ Symbol does nothing."): Example("// Why. Symbol does nothing."),
Example("// Single. Double. ↓ End."): Example("// Single. Double. End."),
Example("// Single. Double. ↓ Triple. ↓ End."): Example("// Single. Double. Triple. End."),
Example("// Triple. ↓ Quad. ↓ End."): Example("// Triple. Quad. End."),
Example("/// - code: Identifier. ↓ Integer."): Example("/// - code: Identifier. Integer.")
]
)
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
// Find all comment tokens in the file and regex search them for violations
file.syntaxClassifications
.filter(\.kind.isComment)
.map { $0.range.toSourceKittenByteRange() }
.compactMap { (range: ByteRange) -> [NSRange]? in
return file.stringView
.substringWithByteRange(range)
.map(StringView.init)
.map { commentBody in
// Look for a period followed by two or more whitespaces but not new line or carriage returns
return regex(#"\.[^\S\r\n]{2,}"#)
.matches(in: commentBody)
.compactMap { result in
// Set the location to start from the second whitespace till the last one.
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.lowerBound + 2),
length: ByteCount(result.range.length.advanced(by: -2))
)
)
}
}
}
.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, "")
}
}