import SwiftSyntax @SwiftSyntaxRule struct OrphanedDocCommentRule: Rule { var configuration = SeverityConfiguration(.warning) static let description = RuleDescription( identifier: "orphaned_doc_comment", name: "Orphaned Doc Comment", description: "A doc comment should be attached to a declaration", kind: .lint, nonTriggeringExamples: [ Example(""" /// My great property var myGreatProperty: String! """), Example(""" ////////////////////////////////////// // // Copyright header. // ////////////////////////////////////// """), Example(""" /// Look here for more info: https://github.com. var myGreatProperty: String! """), Example(""" /// Look here for more info: /// https://github.com. var myGreatProperty: String! """), ], triggeringExamples: [ Example(""" ↓/// My great property // Not a doc string var myGreatProperty: String! """), Example(""" ↓/// Look here for more info: https://github.com. // Not a doc string var myGreatProperty: String! """), Example(""" ↓/// Look here for more info: https://github.com. // Not a doc string var myGreatProperty: String! """), Example(""" ↓/// Look here for more info: https://github.com. // Not a doc string ↓/// My great property // Not a doc string var myGreatProperty: String! """), Example(""" extension Nested { ↓/// /// Look here for more info: https://github.com. // Not a doc string var myGreatProperty: String! } """), ] ) } private extension OrphanedDocCommentRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: TokenSyntax) { let pieces = node.leadingTrivia.pieces var iterator = pieces.enumerated().makeIterator() while let (index, piece) = iterator.next() { switch piece { case .docLineComment(let comment), .docBlockComment(let comment): // These patterns are often used for "file header" style comments if !comment.hasPrefix("////"), !comment.hasPrefix("/***") { if isOrphanedDocComment(with: &iterator) { let utf8Length = pieces[.. ) -> Bool { while let (_, piece) = iterator.next() { switch piece { case .docLineComment, .docBlockComment, .carriageReturns, .carriageReturnLineFeeds, .newlines, .spaces: break case .lineComment, .blockComment: return true default: return false } } return false }