Files
SwiftLint/Source/SwiftLintFramework/Rules/Lint/TodoRule.swift
Zev Eisenberg fcf848608e Add Inline test failure messages (#3040)
* Add Example wrapper in order to display test failures inline when running in Xcode.
* Stop using Swift 5.1-only features so we can compile on Xcode 10.2.
* Wrap strings in Example.
* Add Changelog entry.
* Wrap all examples in Example struct.
* Better and more complete capturing of line numbers.
* Fix broken test.
* Better test traceability.
* Address or disable linting warnings.
* Add documentation comments.
* Disable linter for a few cases.
* Limit mutability and add copy-and-mutate utility functions.
* Limit scope of mutability.
2020-02-02 10:35:37 +02:00

88 lines
3.1 KiB
Swift

import Foundation
import SourceKittenFramework
public extension SyntaxKind {
/// Returns if the syntax kind is comment-like.
var isCommentLike: Bool {
return SyntaxKind.commentKinds.contains(self)
}
}
public struct TodoRule: ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "todo",
name: "Todo",
description: "TODOs and FIXMEs should be resolved.",
kind: .lint,
nonTriggeringExamples: [
Example("// notaTODO:\n"),
Example("// notaFIXME:\n")
],
triggeringExamples: [
Example("// ↓TODO:\n"),
Example("// ↓FIXME:\n"),
Example("// ↓TODO(note)\n"),
Example("// ↓FIXME(note)\n"),
Example("/* ↓FIXME: */\n"),
Example("/* ↓TODO: */\n"),
Example("/** ↓FIXME: */\n"),
Example("/** ↓TODO: */\n")
]
)
private func customMessage(file: SwiftLintFile, range: NSRange) -> String {
var reason = type(of: self).description.description
let offset = NSMaxRange(range)
guard let (lineNumber, _) = file.stringView.lineAndCharacter(forCharacterOffset: offset) else {
return reason
}
let line = file.lines[lineNumber - 1]
// customizing the reason message to be specific to fixme or todo
let violationSubstring = file.stringView.substring(with: range)
let range = NSRange(location: offset, length: NSMaxRange(line.range) - offset)
var message = file.stringView.substring(with: range)
let kind = violationSubstring.hasPrefix("FIXME") ? "FIXMEs" : "TODOs"
// trim whitespace
message = message.trimmingCharacters(in: .whitespacesAndNewlines)
// limiting the output length of todo message
let maxLengthOfMessage = 30
if message.utf16.count > maxLengthOfMessage {
let index = message.index(message.startIndex,
offsetBy: maxLengthOfMessage,
limitedBy: message.endIndex) ?? message.endIndex
message = message[..<index] + "..."
}
if message.isEmpty {
reason = "\(kind) should be resolved."
} else {
reason = "\(kind) should be resolved (\(message))."
}
return reason
}
public func validate(file: SwiftLintFile) -> [StyleViolation] {
return file.match(pattern: "\\b(?:TODO|FIXME)(?::|\\b)").compactMap { range, syntaxKinds in
if syntaxKinds.contains(where: { !$0.isCommentLike }) {
return nil
}
let reason = customMessage(file: file, range: range)
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: range.location),
reason: reason)
}
}
}