Files
SwiftLint/Source/SwiftLintFramework/Rules/MissingDocsRule.swift
T
JP Simard 8c4bfcc58d Merge branch 'master' into identifier-rule
* master: (44 commits)
  make valid_docs rule opt-in
  update LineLengthConfiguration.consoleDescription after #1264
  small refactoring after #1264
  fix for_where violation in CompilerProtocolInitRule.swift
  Add for_where rule
  explicitly set podspec dependency versions
  Add changelog entry
  Fix existing violations
  Navigate substructure even for different kinds
  Small style changes
  Modified so that kinds(forByteOffset:) does not require a separate filter() setup of the results
  Changes from PR feedback. Long comments following code in a line will now trigger,  configuration will now fail if invalud value types are set for options
  Fix false positive on large_tuple when using generics inside a tuple
  fix wording in changelog
  disable docs rules in Swift 2.3 and later
  Fix deadlock when stderr fills up OS buffer
  small Danger/oss-check improvements
  Fix links
  Fix cleanup
  Always use Xcode reporter on oss-check
  ...
2017-02-09 15:50:06 -08:00

180 lines
6.8 KiB
Swift

//
// MissingDocsRule.swift
// SwiftLint
//
// Created by JP Simard on 11/15/15.
// Copyright © 2015 Realm. All rights reserved.
//
import SourceKittenFramework
private func mappedDictValues(fromDictionary dictionary: [String: SourceKitRepresentable], key: String,
subKey: String) -> [String] {
return (dictionary[key] as? [SourceKitRepresentable])?.flatMap({
($0 as? [String: SourceKitRepresentable]) as? [String: String]
}).flatMap({ $0[subKey] }) ?? []
}
private func declarationOverrides(in dictionary: [String: SourceKitRepresentable]) -> Bool {
return dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.override")
}
private func inheritedMembers(for dictionary: [String: SourceKitRepresentable]) -> [String] {
return mappedDictValues(fromDictionary: dictionary, key: "key.inheritedtypes", subKey: "key.name").flatMap {
File.allDeclarationsByType[$0] ?? []
}
}
extension File {
fileprivate func missingDocOffsets(in dictionary: [String: SourceKitRepresentable],
acl: [AccessControlLevel], skipping: [String] = []) -> [Int] {
if declarationOverrides(in: dictionary) {
return []
}
if let name = dictionary.name, skipping.contains(name) {
return []
}
let inherited = inheritedMembers(for: dictionary)
let substructureOffsets = dictionary.substructure.flatMap {
missingDocOffsets(in: $0, acl: acl, skipping: inherited)
}
guard (dictionary.kind).flatMap(SwiftDeclarationKind.init) != nil,
let offset = dictionary.offset,
let accessibility = dictionary.accessibility,
acl.map({ $0.rawValue }).contains(accessibility) else {
return substructureOffsets
}
if parseDocumentationCommentBody(dictionary, syntaxMap: syntaxMap) != nil {
return substructureOffsets
}
return substructureOffsets + [offset]
}
}
public enum AccessControlLevel: String, CustomStringConvertible {
case `private` = "source.lang.swift.accessibility.private"
case `fileprivate` = "source.lang.swift.accessibility.fileprivate"
case `internal` = "source.lang.swift.accessibility.internal"
case `public` = "source.lang.swift.accessibility.public"
case `open` = "source.lang.swift.accessibility.open"
internal init?(description value: String) {
switch value {
case "private": self = .private
case "fileprivate": self = .fileprivate
case "internal": self = .internal
case "public": self = .public
case "open": self = .open
default: return nil
}
}
init?(identifier value: String) {
self.init(rawValue: value)
}
public var description: String {
switch self {
case .private: return "private"
case .fileprivate: return "fileprivate"
case .internal: return "internal"
case .public: return "public"
case .open: return "open"
}
}
// Returns true if is `private` or `fileprivate`
var isPrivate: Bool {
return self == .private || self == .fileprivate
}
}
public struct MissingDocsRule: OptInRule {
public init(configuration: Any) throws {
guard let array = [String].array(of: configuration) else {
throw ConfigurationError.unknownConfiguration
}
let acl = array.flatMap(AccessControlLevel.init(description:))
parameters = zip([.warning, .error], acl).map(RuleParameter<AccessControlLevel>.init)
}
public var configurationDescription: String {
return parameters.map({
"\($0.severity.rawValue): \($0.value.rawValue)"
}).joined(separator: ", ")
}
public init() {
parameters = [RuleParameter(severity: .warning, value: .public),
RuleParameter(severity: .warning, value: .open)]
}
public let parameters: [RuleParameter<AccessControlLevel>]
public static let description = RuleDescription(
identifier: "missing_docs",
name: "Missing Docs",
description: "Public declarations should be documented.",
nonTriggeringExamples: [
// public, documented using /// docs
"/// docs\npublic func a() {}\n",
// public, documented using /** docs */
"/** docs */\npublic func a() {}\n",
// internal (implicit), undocumented
"func a() {}\n",
// internal (explicit), undocumented
"internal func a() {}\n",
// private, undocumented
"private func a() {}\n",
// internal (implicit), undocumented
"// regular comment\nfunc a() {}\n",
// internal (implicit), undocumented
"/* regular comment */\nfunc a() {}\n",
// protocol member is documented, but inherited member is not
"/// docs\npublic protocol A {\n/// docs\nvar b: Int { get } }\n" +
"/// docs\npublic struct C: A {\npublic let b: Int\n}",
// locally-defined superclass member is documented, but subclass member is not
"/// docs\npublic class A {\n/// docs\npublic func b() {}\n}\n" +
"/// docs\npublic class B: A { override public func b() {} }\n",
// externally-defined superclass member is documented, but subclass member is not
"import Foundation\n/// docs\npublic class B: NSObject {\n" +
"// no docs\noverride public var description: String { fatalError() } }\n"
],
triggeringExamples: [
// public, undocumented
"public func a() {}\n",
// public, undocumented
"// regular comment\npublic func a() {}\n",
// public, undocumented
"/* regular comment */\npublic func a() {}\n",
// protocol member and inherited member are both undocumented
"/// docs\npublic protocol A {\n// no docs\nvar b: Int { get } }\n" +
"/// docs\npublic struct C: A {\n\npublic let b: Int\n}"
]
)
public func validate(file: File) -> [StyleViolation] {
guard SwiftVersion.current == .two else {
warnMissingDocsRuleDisabledOnce
return []
}
let acl = parameters.map { $0.value }
return file.missingDocOffsets(in: file.structure.dictionary, acl: acl).map {
StyleViolation(ruleDescription: type(of: self).description,
location: Location(file: file, byteOffset: $0))
}
}
public func isEqualTo(_ rule: Rule) -> Bool {
if let rule = rule as? MissingDocsRule {
return rule.parameters == parameters
}
return false
}
}
private let warnMissingDocsRuleDisabledOnce: Void = {
queuedPrintError("Missing Docs rule is disabled in Swift 2.3 and later as it is non functional.")
}()