Files
SwiftLint/Source/SwiftLintFramework/Rules/MissingDocsRule.swift
T
JP Simard 576e1a5d4d remove unnecessary .lowercased() calls
now that ViolationSeverity is already lowercase.
2016-12-01 22:29:09 -08:00

174 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(_ 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(_ dictionary: [String: SourceKitRepresentable]) -> Bool {
return mappedDictValues(dictionary, key: "key.attributes", subKey: "key.attribute")
.contains("source.decl.attribute.override")
}
private func inheritedMembersForDictionary(_ dictionary: [String: SourceKitRepresentable]) ->
[String] {
return mappedDictValues(dictionary, key: "key.inheritedtypes", subKey: "key.name").flatMap {
File.allDeclarationsByType[$0] ?? []
}
}
extension File {
fileprivate func missingDocOffsets(_ dictionary: [String: SourceKitRepresentable],
acl: [AccessControlLevel], skipping: [String] = []) -> [Int] {
if declarationOverrides(dictionary) {
return []
}
if let name = dictionary["key.name"] as? String, skipping.contains(name) {
return []
}
let inheritedMembers = inheritedMembersForDictionary(dictionary)
let substructureOffsets = (dictionary["key.substructure"] as? [SourceKitRepresentable])?
.flatMap { $0 as? [String: SourceKitRepresentable] }
.flatMap({ self.missingDocOffsets($0, acl: acl, skipping: inheritedMembers) }) ?? []
guard let _ = (dictionary["key.kind"] as? String).flatMap(SwiftDeclarationKind.init),
let offset = dictionary["key.offset"] as? Int64,
let accessibility = dictionary["key.accessibility"] as? String,
acl.map({ $0.rawValue }).contains(accessibility) else {
return substructureOffsets
}
if parseDocumentationCommentBody(dictionary, syntaxMap: syntaxMap) != nil {
return substructureOffsets
}
return substructureOffsets + [Int(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 validateFile(_ file: File) -> [StyleViolation] {
let acl = parameters.map { $0.value }
return file.missingDocOffsets(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
}
}