mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
add configuration for missing_docs (#3701)
* `excludes_extensions` defaults to `true` to skip reporting violations for extensions with missing documentation comments. * `excludes_inherited_types` defaults to `true` to skip reporting violations for inherited declarations, like subclass overrides.
This commit is contained in:
@@ -11,6 +11,13 @@
|
||||
|
||||
#### Enhancements
|
||||
|
||||
* Add configuration options to `missing_docs` rule:
|
||||
* `excludes_extensions` defaults to `true` to skip reporting violations
|
||||
for extensions with missing documentation comments.
|
||||
* `excludes_inherited_types` defaults to `true` to skip reporting
|
||||
violations for inherited declarations, like subclass overrides.
|
||||
[Ben Fox](https://github.com/bdfox325)
|
||||
|
||||
* Fix false negative on `redundant_optional_initialization` rule when variable
|
||||
has observers.
|
||||
[Isaac Ressler](https://github.com/iressler)
|
||||
|
||||
@@ -2,18 +2,25 @@ import SourceKittenFramework
|
||||
|
||||
private extension SwiftLintFile {
|
||||
func missingDocOffsets(in dictionary: SourceKittenDictionary,
|
||||
acls: [AccessControlLevel]) -> [(ByteCount, AccessControlLevel)] {
|
||||
acls: [AccessControlLevel],
|
||||
excludesExtensions: Bool,
|
||||
excludesInheritedTypes: Bool) -> [(ByteCount, AccessControlLevel)] {
|
||||
if dictionary.enclosedSwiftAttributes.contains(.override) ||
|
||||
dictionary.inheritedTypes.isNotEmpty {
|
||||
(dictionary.inheritedTypes.isNotEmpty && excludesInheritedTypes) {
|
||||
return []
|
||||
}
|
||||
let substructureOffsets = dictionary.substructure.flatMap {
|
||||
missingDocOffsets(in: $0, acls: acls)
|
||||
missingDocOffsets(
|
||||
in: $0,
|
||||
acls: acls,
|
||||
excludesExtensions: excludesExtensions,
|
||||
excludesInheritedTypes: excludesInheritedTypes
|
||||
)
|
||||
}
|
||||
let extensionKinds: Set<SwiftDeclarationKind> = [.extension, .extensionEnum, .extensionClass,
|
||||
.extensionStruct, .extensionProtocol]
|
||||
guard let kind = dictionary.declarationKind,
|
||||
!extensionKinds.contains(kind),
|
||||
(!extensionKinds.contains(kind) || !excludesExtensions),
|
||||
case let isDeinit = kind == .functionMethodInstance && dictionary.name == "deinit",
|
||||
!isDeinit,
|
||||
let offset = dictionary.offset,
|
||||
@@ -32,7 +39,9 @@ public struct MissingDocsRule: OptInRule, ConfigurationProviderRule, AutomaticTe
|
||||
public init() {
|
||||
configuration = MissingDocsRuleConfiguration(
|
||||
parameters: [RuleParameter<AccessControlLevel>(severity: .warning, value: .open),
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .public)])
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .public)],
|
||||
excludesExtensions: true,
|
||||
excludesInheritedTypes: true)
|
||||
}
|
||||
|
||||
public typealias ConfigurationType = MissingDocsRuleConfiguration
|
||||
@@ -98,7 +107,12 @@ public struct MissingDocsRule: OptInRule, ConfigurationProviderRule, AutomaticTe
|
||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
let acls = configuration.parameters.map { $0.value }
|
||||
let dict = file.structureDictionary
|
||||
return file.missingDocOffsets(in: dict, acls: acls).map { offset, acl in
|
||||
return file.missingDocOffsets(
|
||||
in: dict,
|
||||
acls: acls,
|
||||
excludesExtensions: configuration.excludesExtensions,
|
||||
excludesInheritedTypes: configuration.excludesInheritedTypes
|
||||
).map { offset, acl in
|
||||
StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.parameters.first { $0.value == acl }?.severity ?? .warning,
|
||||
location: Location(file: file, byteOffset: offset),
|
||||
|
||||
+44
-9
@@ -1,35 +1,70 @@
|
||||
public struct MissingDocsRuleConfiguration: RuleConfiguration, Equatable {
|
||||
private(set) var parameters = [RuleParameter<AccessControlLevel>]()
|
||||
private(set) var excludesExtensions = true
|
||||
private(set) var excludesInheritedTypes = true
|
||||
|
||||
public var consoleDescription: String {
|
||||
return parameters.group { $0.severity }.sorted { $0.key.rawValue < $1.key.rawValue }.map {
|
||||
let parametersDescription = parameters.group { $0.severity }.sorted { $0.key.rawValue < $1.key.rawValue }.map {
|
||||
"\($0.rawValue): \($1.map { $0.value.description }.sorted(by: <).joined(separator: ", "))"
|
||||
}.joined(separator: ", ")
|
||||
|
||||
if parametersDescription.isEmpty {
|
||||
return [
|
||||
"excludes_extensions: \(excludesExtensions)",
|
||||
"excludes_inherited_types: \(excludesInheritedTypes)"
|
||||
]
|
||||
.joined(separator: ", ")
|
||||
} else {
|
||||
return [
|
||||
parametersDescription,
|
||||
"excludes_extensions: \(excludesExtensions)",
|
||||
"excludes_inherited_types: \(excludesInheritedTypes)"
|
||||
]
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func apply(configuration: Any) throws {
|
||||
guard let dict = configuration as? [String: Any] else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
let parameters = try dict.flatMap { (key: String, value: Any) -> [RuleParameter<AccessControlLevel>] in
|
||||
|
||||
if let shouldExcludeExtensions = dict["excludes_extensions"] as? Bool {
|
||||
excludesExtensions = shouldExcludeExtensions
|
||||
}
|
||||
|
||||
if let shouldExcludeInheritedTypes = dict["excludes_inherited_types"] as? Bool {
|
||||
excludesExtensions = shouldExcludeInheritedTypes
|
||||
}
|
||||
|
||||
var parameters: [RuleParameter<AccessControlLevel>] = []
|
||||
|
||||
for (key, value) in dict {
|
||||
guard let severity = ViolationSeverity(rawValue: key) else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
|
||||
if let array = [String].array(of: value) {
|
||||
return try array.map {
|
||||
guard let acl = AccessControlLevel(description: $0) else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
let rules: [RuleParameter<AccessControlLevel>] = try array
|
||||
.map { val -> RuleParameter<AccessControlLevel> in
|
||||
guard let acl = AccessControlLevel(description: val) else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
return RuleParameter<AccessControlLevel>(severity: severity, value: acl)
|
||||
}
|
||||
return RuleParameter<AccessControlLevel>(severity: severity, value: acl)
|
||||
}
|
||||
|
||||
parameters.append(contentsOf: rules)
|
||||
} else if let string = value as? String, let acl = AccessControlLevel(description: string) {
|
||||
return [RuleParameter<AccessControlLevel>(severity: severity, value: acl)]
|
||||
let rule = RuleParameter<AccessControlLevel>(severity: severity, value: acl)
|
||||
|
||||
parameters.append(rule)
|
||||
}
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
|
||||
guard parameters.count == parameters.map({ $0.value }).unique.count else {
|
||||
throw ConfigurationError.unknownConfiguration
|
||||
}
|
||||
|
||||
self.parameters = parameters
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,49 +4,76 @@ import XCTest
|
||||
class MissingDocsRuleConfigurationTests: XCTestCase {
|
||||
func testDescriptionEmpty() {
|
||||
let configuration = MissingDocsRuleConfiguration()
|
||||
XCTAssertEqual(configuration.consoleDescription, "")
|
||||
XCTAssertEqual(
|
||||
configuration.consoleDescription,
|
||||
"excludes_extensions: true, excludes_inherited_types: true"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescriptionExcludesFalse() {
|
||||
let configuration = MissingDocsRuleConfiguration(excludesExtensions: false, excludesInheritedTypes: false)
|
||||
XCTAssertEqual(
|
||||
configuration.consoleDescription,
|
||||
"excludes_extensions: false, excludes_inherited_types: false"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescriptionSingleServety() {
|
||||
let configuration = MissingDocsRuleConfiguration(
|
||||
parameters: [RuleParameter<AccessControlLevel>(severity: .error, value: .open)])
|
||||
XCTAssertEqual(configuration.consoleDescription, "error: open")
|
||||
XCTAssertEqual(
|
||||
configuration.consoleDescription,
|
||||
"error: open, excludes_extensions: true, excludes_inherited_types: true"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescriptionMultipleSeverities() {
|
||||
let configuration = MissingDocsRuleConfiguration(
|
||||
parameters: [RuleParameter<AccessControlLevel>(severity: .error, value: .open),
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .public)])
|
||||
XCTAssertEqual(configuration.consoleDescription, "error: open, warning: public")
|
||||
XCTAssertEqual(
|
||||
configuration.consoleDescription,
|
||||
"error: open, warning: public, excludes_extensions: true, excludes_inherited_types: true"
|
||||
)
|
||||
}
|
||||
|
||||
func testDescriptionMultipleAcls() {
|
||||
let configuration = MissingDocsRuleConfiguration(
|
||||
parameters: [RuleParameter<AccessControlLevel>(severity: .warning, value: .open),
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .public)])
|
||||
XCTAssertEqual(configuration.consoleDescription, "warning: open, public")
|
||||
XCTAssertEqual(
|
||||
configuration.consoleDescription,
|
||||
"warning: open, public, excludes_extensions: true, excludes_inherited_types: true"
|
||||
)
|
||||
}
|
||||
|
||||
func testParsingSingleServety() {
|
||||
var configuration = MissingDocsRuleConfiguration()
|
||||
try? configuration.apply(configuration: ["warning": "open"])
|
||||
XCTAssertEqual(configuration.parameters, [RuleParameter<AccessControlLevel>(severity: .warning, value: .open)])
|
||||
XCTAssertEqual(
|
||||
configuration.parameters,
|
||||
[RuleParameter<AccessControlLevel>(severity: .warning, value: .open)]
|
||||
)
|
||||
}
|
||||
|
||||
func testParsingMultipleSeverities() {
|
||||
var configuration = MissingDocsRuleConfiguration()
|
||||
try? configuration.apply(configuration: ["warning": "public", "error": "open"])
|
||||
XCTAssertEqual(configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue },
|
||||
[RuleParameter<AccessControlLevel>(severity: .warning, value: .public),
|
||||
RuleParameter<AccessControlLevel>(severity: .error, value: .open)])
|
||||
XCTAssertEqual(
|
||||
configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue },
|
||||
[RuleParameter<AccessControlLevel>(severity: .warning, value: .public),
|
||||
RuleParameter<AccessControlLevel>(severity: .error, value: .open)]
|
||||
)
|
||||
}
|
||||
|
||||
func testParsingMultipleAcls() {
|
||||
var configuration = MissingDocsRuleConfiguration()
|
||||
try? configuration.apply(configuration: ["warning": ["public", "open"]])
|
||||
XCTAssertEqual(configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue },
|
||||
[RuleParameter<AccessControlLevel>(severity: .warning, value: .public),
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .open)])
|
||||
XCTAssertEqual(
|
||||
configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue },
|
||||
[RuleParameter<AccessControlLevel>(severity: .warning, value: .public),
|
||||
RuleParameter<AccessControlLevel>(severity: .warning, value: .open)]
|
||||
)
|
||||
}
|
||||
|
||||
func testInvalidServety() {
|
||||
|
||||
Reference in New Issue
Block a user