Skip to content

Commit 284ed05

Browse files
committed
Improvements to auto-generated rule docs
- Using `DocumentationCommentText` via `@_spi(Rules)` instead of our own implementation of rule description extracted from trivia. - Added table of contents with all the available rules linked to their respective blocks.
1 parent e350bde commit 284ed05

File tree

4 files changed

+68
-32
lines changed

4 files changed

+68
-32
lines changed

Documentation/RuleDocumentation.md

+50
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,47 @@ configuration file, as described in
99
applied in the linter, but only some of them can format your source code
1010
automatically.
1111

12+
Here's the list of available rules:
13+
14+
- [AllPublicDeclarationsHaveDocumentation](#AllPublicDeclarationsHaveDocumentation)
15+
- [AlwaysUseLowerCamelCase](#AlwaysUseLowerCamelCase)
16+
- [AmbiguousTrailingClosureOverload](#AmbiguousTrailingClosureOverload)
17+
- [BeginDocumentationCommentWithOneLineSummary](#BeginDocumentationCommentWithOneLineSummary)
18+
- [DoNotUseSemicolons](#DoNotUseSemicolons)
19+
- [DontRepeatTypeInStaticProperties](#DontRepeatTypeInStaticProperties)
20+
- [FileScopedDeclarationPrivacy](#FileScopedDeclarationPrivacy)
21+
- [FullyIndirectEnum](#FullyIndirectEnum)
22+
- [GroupNumericLiterals](#GroupNumericLiterals)
23+
- [IdentifiersMustBeASCII](#IdentifiersMustBeASCII)
24+
- [NeverForceUnwrap](#NeverForceUnwrap)
25+
- [NeverUseForceTry](#NeverUseForceTry)
26+
- [NeverUseImplicitlyUnwrappedOptionals](#NeverUseImplicitlyUnwrappedOptionals)
27+
- [NoAccessLevelOnExtensionDeclaration](#NoAccessLevelOnExtensionDeclaration)
28+
- [NoAssignmentInExpressions](#NoAssignmentInExpressions)
29+
- [NoBlockComments](#NoBlockComments)
30+
- [NoCasesWithOnlyFallthrough](#NoCasesWithOnlyFallthrough)
31+
- [NoEmptyTrailingClosureParentheses](#NoEmptyTrailingClosureParentheses)
32+
- [NoLabelsInCasePatterns](#NoLabelsInCasePatterns)
33+
- [NoLeadingUnderscores](#NoLeadingUnderscores)
34+
- [NoParensAroundConditions](#NoParensAroundConditions)
35+
- [NoPlaygroundLiterals](#NoPlaygroundLiterals)
36+
- [NoVoidReturnOnFunctionSignature](#NoVoidReturnOnFunctionSignature)
37+
- [OmitExplicitReturns](#OmitExplicitReturns)
38+
- [OneCasePerLine](#OneCasePerLine)
39+
- [OneVariableDeclarationPerLine](#OneVariableDeclarationPerLine)
40+
- [OnlyOneTrailingClosureArgument](#OnlyOneTrailingClosureArgument)
41+
- [OrderedImports](#OrderedImports)
42+
- [ReplaceForEachWithForLoop](#ReplaceForEachWithForLoop)
43+
- [ReturnVoidInsteadOfEmptyTuple](#ReturnVoidInsteadOfEmptyTuple)
44+
- [TypeNamesShouldBeCapitalized](#TypeNamesShouldBeCapitalized)
45+
- [UseEarlyExits](#UseEarlyExits)
46+
- [UseLetInEveryBoundCaseVariable](#UseLetInEveryBoundCaseVariable)
47+
- [UseShorthandTypeNames](#UseShorthandTypeNames)
48+
- [UseSingleLinePropertyGetter](#UseSingleLinePropertyGetter)
49+
- [UseSynthesizedInitializer](#UseSynthesizedInitializer)
50+
- [UseTripleSlashForDocumentationComments](#UseTripleSlashForDocumentationComments)
51+
- [UseWhereClausesInForLoops](#UseWhereClausesInForLoops)
52+
- [ValidateDocumentationComments](#ValidateDocumentationComments)
1253

1354
### AllPublicDeclarationsHaveDocumentation
1455

@@ -261,6 +302,15 @@ Format: Parentheses around such expressions are removed, if they do not cause a
261302

262303
`NoParensAroundConditions` rule can format your code automatically.
263304

305+
### NoPlaygroundLiterals
306+
307+
The playground literals (`#colorLiteral`, `#fileLiteral`, and `#imageLiteral`) are forbidden.
308+
309+
Lint: Using a playground literal will yield a lint error with a suggestion of an API to replace
310+
it.
311+
312+
`NoPlaygroundLiterals` is a linter-only rule.
313+
264314
### NoVoidReturnOnFunctionSignature
265315

266316
Functions that return `()` or `Void` should omit the return signature.

Sources/SwiftFormat/Core/DocumentationCommentText.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import SwiftSyntax
1818
/// structural organization. It automatically handles trimming leading indentation from comments as
1919
/// well as "ASCII art" in block comments (i.e., leading asterisks on each line).
2020
@_spi(Testing)
21+
@_spi(Rules)
2122
public struct DocumentationCommentText {
2223
/// Denotes the kind of punctuation used to introduce the comment.
2324
public enum Introducer {

Sources/generate-pipeline/RuleCollector.swift

+6-9
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ import SwiftParser
2020
final class RuleCollector {
2121
/// Information about a detected rule.
2222
struct DetectedRule: Hashable {
23-
24-
/// The DocC comments of the rule,
25-
/// extracted from the .leadingTrivia of the rule class or struct.
26-
let doccComment: String
27-
2823
/// The type name of the rule.
2924
let typeName: String
3025

26+
/// The description of the rule, extracted from the rule class or struct DocC comment
27+
/// with `DocumentationCommentText(extractedFrom:)`
28+
let description: String?
29+
3130
/// The syntax node types visited by the rule type.
3231
let visitedNodes: [String]
3332

@@ -91,15 +90,13 @@ final class RuleCollector {
9190
let typeName: String
9291
let members: MemberBlockItemListSyntax
9392
let maybeInheritanceClause: InheritanceClauseSyntax?
94-
let doccComment: String
93+
let description = DocumentationCommentText(extractedFrom: statement.item.leadingTrivia)
9594

9695
if let classDecl = statement.item.as(ClassDeclSyntax.self) {
97-
doccComment = classDecl.leadingTrivia.description
9896
typeName = classDecl.name.text
9997
members = classDecl.memberBlock.members
10098
maybeInheritanceClause = classDecl.inheritanceClause
10199
} else if let structDecl = statement.item.as(StructDeclSyntax.self) {
102-
doccComment = structDecl.leadingTrivia.description
103100
typeName = structDecl.name.text
104101
members = structDecl.memberBlock.members
105102
maybeInheritanceClause = structDecl.inheritanceClause
@@ -148,8 +145,8 @@ final class RuleCollector {
148145
preconditionFailure("Failed to find type for rule named \(typeName)")
149146
}
150147
return DetectedRule(
151-
doccComment: doccComment,
152148
typeName: typeName,
149+
description: description?.text,
153150
visitedNodes: visitedNodes,
154151
canFormat: canFormat,
155152
isOptIn: ruleType.isOptIn)

Sources/generate-pipeline/RuleDocumentationGenerator.swift

+11-23
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Foundation
14+
import SwiftFormat
1415

1516
/// Generates the markdown file with extended documenation on the available rules.
1617
final class RuleDocumentationGenerator: FileGenerator {
@@ -37,17 +38,25 @@ final class RuleDocumentationGenerator: FileGenerator {
3738
applied in the linter, but only some of them can format your source code
3839
automatically.
3940
41+
Here's the list of available rules:
42+
4043
4144
"""
4245
)
4346

4447
for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) {
4548
handle.write("""
49+
- [\(detectedRule.typeName)](#\(detectedRule.typeName))
4650
47-
### \(detectedRule.typeName)
51+
""")
52+
}
53+
54+
for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) {
55+
handle.write("""
4856
49-
\(ruleDescription(for: detectedRule))
57+
### \(detectedRule.typeName)
5058
59+
\(detectedRule.description ?? "")
5160
\(ruleFormatSupportDescription(for: detectedRule))
5261
5362
""")
@@ -59,25 +68,4 @@ final class RuleDocumentationGenerator: FileGenerator {
5968
"`\(rule.typeName)` rule can format your code automatically." :
6069
"`\(rule.typeName)` is a linter-only rule."
6170
}
62-
63-
/// Takes the DocC comment of the rule and strip `///` from the beginning of each line.
64-
/// Also removes empty lines with under 4 characters.
65-
private func ruleDescription(for rule: RuleCollector.DetectedRule) -> String {
66-
let described = rule.doccComment.split(whereSeparator: \.isNewline)
67-
.compactMap { line in
68-
// Remove the first 4 characters, i.e. `/// `
69-
if line.count >= 4 {
70-
let index = line.index(line.startIndex, offsetBy: 4)
71-
return line.suffix(from: index)
72-
} else {
73-
// For lines that have less than 4 characters, emit an empty line.
74-
return ""
75-
}
76-
}
77-
// not great, but that shoves multiple lines into the single
78-
// table cell in markdown
79-
.joined(separator: "\n")
80-
81-
return described
82-
}
8371
}

0 commit comments

Comments
 (0)