From 1f44d563573bd1a2d292fa05a7fe47f60ee511a9 Mon Sep 17 00:00:00 2001 From: Ben Fox <67343633+bdfox325@users.noreply.github.com> Date: Wed, 1 Sep 2021 14:42:47 -0700 Subject: [PATCH] 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. --- CHANGELOG.md | 7 +++ .../Rules/Lint/MissingDocsRule.swift | 26 ++++++--- .../MissingDocsRuleConfiguration.swift | 53 +++++++++++++++---- .../MissingDocsRuleConfigurationTests.swift | 49 +++++++++++++---- 4 files changed, 109 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 221bc2dd8..caa39e558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/Source/SwiftLintFramework/Rules/Lint/MissingDocsRule.swift b/Source/SwiftLintFramework/Rules/Lint/MissingDocsRule.swift index 651c7371f..21357d3fa 100644 --- a/Source/SwiftLintFramework/Rules/Lint/MissingDocsRule.swift +++ b/Source/SwiftLintFramework/Rules/Lint/MissingDocsRule.swift @@ -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 = [.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(severity: .warning, value: .open), - RuleParameter(severity: .warning, value: .public)]) + RuleParameter(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), diff --git a/Source/SwiftLintFramework/Rules/RuleConfigurations/MissingDocsRuleConfiguration.swift b/Source/SwiftLintFramework/Rules/RuleConfigurations/MissingDocsRuleConfiguration.swift index 549430cfd..a0fead079 100644 --- a/Source/SwiftLintFramework/Rules/RuleConfigurations/MissingDocsRuleConfiguration.swift +++ b/Source/SwiftLintFramework/Rules/RuleConfigurations/MissingDocsRuleConfiguration.swift @@ -1,35 +1,70 @@ public struct MissingDocsRuleConfiguration: RuleConfiguration, Equatable { private(set) var parameters = [RuleParameter]() + 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] 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] = [] + + 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] = try array + .map { val -> RuleParameter in + guard let acl = AccessControlLevel(description: val) else { + throw ConfigurationError.unknownConfiguration + } + return RuleParameter(severity: severity, value: acl) } - return RuleParameter(severity: severity, value: acl) - } + + parameters.append(contentsOf: rules) } else if let string = value as? String, let acl = AccessControlLevel(description: string) { - return [RuleParameter(severity: severity, value: acl)] + let rule = RuleParameter(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 } } diff --git a/Tests/SwiftLintFrameworkTests/MissingDocsRuleConfigurationTests.swift b/Tests/SwiftLintFrameworkTests/MissingDocsRuleConfigurationTests.swift index 5c5bcd743..03bffacb8 100644 --- a/Tests/SwiftLintFrameworkTests/MissingDocsRuleConfigurationTests.swift +++ b/Tests/SwiftLintFrameworkTests/MissingDocsRuleConfigurationTests.swift @@ -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(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(severity: .error, value: .open), RuleParameter(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(severity: .warning, value: .open), RuleParameter(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(severity: .warning, value: .open)]) + XCTAssertEqual( + configuration.parameters, + [RuleParameter(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(severity: .warning, value: .public), - RuleParameter(severity: .error, value: .open)]) + XCTAssertEqual( + configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue }, + [RuleParameter(severity: .warning, value: .public), + RuleParameter(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(severity: .warning, value: .public), - RuleParameter(severity: .warning, value: .open)]) + XCTAssertEqual( + configuration.parameters.sorted { $0.value.rawValue > $1.value.rawValue }, + [RuleParameter(severity: .warning, value: .public), + RuleParameter(severity: .warning, value: .open)] + ) } func testInvalidServety() {