diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index 6f1092103..8eca259e4 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -103,6 +103,18 @@ An example `.swift-format` configuration file is shown below. } ``` +## Linter and Formatter Rules Configuration + +In the `rules` block of `.swift-format`, you can specify which rules to apply +when linting and formatting your project. Read the +[rules documentation](Documentation/RuleDocumentation.md) to see the list of all +supported linter and formatter rules, and their overview. + +You can also run this command to see the list of rules in the default +`swift-format` configuration: + + $ swift-format dump-configuration + ## API Configuration The `SwiftConfiguration` module contains a `Configuration` type that is diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md new file mode 100644 index 000000000..f1d68b89f --- /dev/null +++ b/Documentation/RuleDocumentation.md @@ -0,0 +1,528 @@ + + +# `swift-format` Lint and Format Rules + +Use the rules below in the `rules` block of your `.swift-format` +configuration file, as described in +[Configuration](Documentation/Configuration.md). All of these rules can be +applied in the linter, but only some of them can format your source code +automatically. + +Here's the list of available rules: + +- [AllPublicDeclarationsHaveDocumentation](#AllPublicDeclarationsHaveDocumentation) +- [AlwaysUseLowerCamelCase](#AlwaysUseLowerCamelCase) +- [AmbiguousTrailingClosureOverload](#AmbiguousTrailingClosureOverload) +- [BeginDocumentationCommentWithOneLineSummary](#BeginDocumentationCommentWithOneLineSummary) +- [DoNotUseSemicolons](#DoNotUseSemicolons) +- [DontRepeatTypeInStaticProperties](#DontRepeatTypeInStaticProperties) +- [FileScopedDeclarationPrivacy](#FileScopedDeclarationPrivacy) +- [FullyIndirectEnum](#FullyIndirectEnum) +- [GroupNumericLiterals](#GroupNumericLiterals) +- [IdentifiersMustBeASCII](#IdentifiersMustBeASCII) +- [NeverForceUnwrap](#NeverForceUnwrap) +- [NeverUseForceTry](#NeverUseForceTry) +- [NeverUseImplicitlyUnwrappedOptionals](#NeverUseImplicitlyUnwrappedOptionals) +- [NoAccessLevelOnExtensionDeclaration](#NoAccessLevelOnExtensionDeclaration) +- [NoAssignmentInExpressions](#NoAssignmentInExpressions) +- [NoBlockComments](#NoBlockComments) +- [NoCasesWithOnlyFallthrough](#NoCasesWithOnlyFallthrough) +- [NoEmptyTrailingClosureParentheses](#NoEmptyTrailingClosureParentheses) +- [NoLabelsInCasePatterns](#NoLabelsInCasePatterns) +- [NoLeadingUnderscores](#NoLeadingUnderscores) +- [NoParensAroundConditions](#NoParensAroundConditions) +- [NoPlaygroundLiterals](#NoPlaygroundLiterals) +- [NoVoidReturnOnFunctionSignature](#NoVoidReturnOnFunctionSignature) +- [OmitExplicitReturns](#OmitExplicitReturns) +- [OneCasePerLine](#OneCasePerLine) +- [OneVariableDeclarationPerLine](#OneVariableDeclarationPerLine) +- [OnlyOneTrailingClosureArgument](#OnlyOneTrailingClosureArgument) +- [OrderedImports](#OrderedImports) +- [ReplaceForEachWithForLoop](#ReplaceForEachWithForLoop) +- [ReturnVoidInsteadOfEmptyTuple](#ReturnVoidInsteadOfEmptyTuple) +- [TypeNamesShouldBeCapitalized](#TypeNamesShouldBeCapitalized) +- [UseEarlyExits](#UseEarlyExits) +- [UseLetInEveryBoundCaseVariable](#UseLetInEveryBoundCaseVariable) +- [UseShorthandTypeNames](#UseShorthandTypeNames) +- [UseSingleLinePropertyGetter](#UseSingleLinePropertyGetter) +- [UseSynthesizedInitializer](#UseSynthesizedInitializer) +- [UseTripleSlashForDocumentationComments](#UseTripleSlashForDocumentationComments) +- [UseWhereClausesInForLoops](#UseWhereClausesInForLoops) +- [ValidateDocumentationComments](#ValidateDocumentationComments) + +### AllPublicDeclarationsHaveDocumentation + +All public or open declarations must have a top-level documentation comment. + +Lint: If a public declaration is missing a documentation comment, a lint error is raised. + +`AllPublicDeclarationsHaveDocumentation` is a linter-only rule. + +### AlwaysUseLowerCamelCase + +All values should be written in lower camel-case (`lowerCamelCase`). +Underscores (except at the beginning of an identifier) are disallowed. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: If an identifier contains underscores or begins with a capital letter, a lint error is + raised. + +`AlwaysUseLowerCamelCase` is a linter-only rule. + +### AmbiguousTrailingClosureOverload + +Overloads with only a closure argument should not be disambiguated by parameter labels. + +Lint: If two overloaded functions with one closure parameter appear in the same scope, a lint + error is raised. + +`AmbiguousTrailingClosureOverload` is a linter-only rule. + +### BeginDocumentationCommentWithOneLineSummary + +All documentation comments must begin with a one-line summary of the declaration. + +Lint: If a comment does not begin with a single-line summary, a lint error is raised. + +`BeginDocumentationCommentWithOneLineSummary` is a linter-only rule. + +### DoNotUseSemicolons + +Semicolons should not be present in Swift code. + +Lint: If a semicolon appears anywhere, a lint error is raised. + +Format: All semicolons will be replaced with line breaks. + +`DoNotUseSemicolons` rule can format your code automatically. + +### DontRepeatTypeInStaticProperties + +Static properties of a type that return that type should not include a reference to their type. + +"Reference to their type" means that the property name includes part, or all, of the type. If +the type contains a namespace (i.e. `UIColor`) the namespace is ignored; +`public class var redColor: UIColor` would trigger this rule. + +Lint: Static properties of a type that return that type will yield a lint error. + +`DontRepeatTypeInStaticProperties` is a linter-only rule. + +### FileScopedDeclarationPrivacy + +Declarations at file scope with effective private access should be consistently declared as +either `fileprivate` or `private`, determined by configuration. + +Lint: If a file-scoped declaration has formal access opposite to the desired access level in the + formatter's configuration, a lint error is raised. + +Format: File-scoped declarations that have formal access opposite to the desired access level in + the formatter's configuration will have their access level changed. + +`FileScopedDeclarationPrivacy` rule can format your code automatically. + +### FullyIndirectEnum + +If all cases of an enum are `indirect`, the entire enum should be marked `indirect`. + +Lint: If every case of an enum is `indirect`, but the enum itself is not, a lint error is + raised. + +Format: Enums where all cases are `indirect` will be rewritten such that the enum is marked + `indirect`, and each case is not. + +`FullyIndirectEnum` rule can format your code automatically. + +### GroupNumericLiterals + +Numeric literals should be grouped with `_`s to delimit common separators. + +Specifically, decimal numeric literals should be grouped every 3 numbers, hexadecimal every 4, +and binary every 8. + +Lint: If a numeric literal is too long and should be grouped, a lint error is raised. + +Format: All numeric literals that should be grouped will have `_`s inserted where appropriate. + +TODO: Minimum numeric literal length bounds and numeric groupings have been selected arbitrarily; +these could be reevaluated. +TODO: Handle floating point literals. + +`GroupNumericLiterals` rule can format your code automatically. + +### IdentifiersMustBeASCII + +All identifiers must be ASCII. + +Lint: If an identifier contains non-ASCII characters, a lint error is raised. + +`IdentifiersMustBeASCII` is a linter-only rule. + +### NeverForceUnwrap + +Force-unwraps are strongly discouraged and must be documented. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: If a force unwrap is used, a lint warning is raised. + +`NeverForceUnwrap` is a linter-only rule. + +### NeverUseForceTry + +Force-try (`try!`) is forbidden. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +Lint: Using `try!` results in a lint error. + +TODO: Create exception for NSRegularExpression + +`NeverUseForceTry` is a linter-only rule. + +### NeverUseImplicitlyUnwrappedOptionals + +Implicitly unwrapped optionals (e.g. `var s: String!`) are forbidden. + +Certain properties (e.g. `@IBOutlet`) tied to the UI lifecycle are ignored. + +This rule does not apply to test code, defined as code which: + * Contains the line `import XCTest` + +TODO: Create exceptions for other UI elements (ex: viewDidLoad) + +Lint: Declaring a property with an implicitly unwrapped type yields a lint error. + +`NeverUseImplicitlyUnwrappedOptionals` is a linter-only rule. + +### NoAccessLevelOnExtensionDeclaration + +Specifying an access level for an extension declaration is forbidden. + +Lint: Specifying an access level for an extension declaration yields a lint error. + +Format: The access level is removed from the extension declaration and is added to each + declaration in the extension; declarations with redundant access levels (e.g. + `internal`, as that is the default access level) have the explicit access level removed. + +`NoAccessLevelOnExtensionDeclaration` rule can format your code automatically. + +### NoAssignmentInExpressions + +Assignment expressions must be their own statements. + +Assignment should not be used in an expression context that expects a `Void` value. For example, +assigning a variable within a `return` statement existing a `Void` function is prohibited. + +Lint: If an assignment expression is found in a position other than a standalone statement, a + lint finding is emitted. + +Format: A `return` statement containing an assignment expression is expanded into two separate + statements. + +`NoAssignmentInExpressions` rule can format your code automatically. + +### NoBlockComments + +Block comments should be avoided in favor of line comments. + +Lint: If a block comment appears, a lint error is raised. + +`NoBlockComments` is a linter-only rule. + +### NoCasesWithOnlyFallthrough + +Cases that contain only the `fallthrough` statement are forbidden. + +Lint: Cases containing only the `fallthrough` statement yield a lint error. + +Format: The fallthrough `case` is added as a prefix to the next case unless the next case is + `default`; in that case, the fallthrough `case` is deleted. + +`NoCasesWithOnlyFallthrough` rule can format your code automatically. + +### NoEmptyTrailingClosureParentheses + +Function calls with no arguments and a trailing closure should not have empty parentheses. + +Lint: If a function call with a trailing closure has an empty argument list with parentheses, + a lint error is raised. + +Format: Empty parentheses in function calls with trailing closures will be removed. + +`NoEmptyTrailingClosureParentheses` rule can format your code automatically. + +### NoLabelsInCasePatterns + +Redundant labels are forbidden in case patterns. + +In practice, *all* case pattern labels should be redundant. + +Lint: Using a label in a case statement yields a lint error unless the label does not match the + binding identifier. + +Format: Redundant labels in case patterns are removed. + +`NoLabelsInCasePatterns` rule can format your code automatically. + +### NoLeadingUnderscores + +Identifiers in declarations and patterns should not have leading underscores. + +This is intended to avoid certain antipatterns; `self.member = member` should be preferred to +`member = _member` and the leading underscore should not be used to signal access level. + +This rule intentionally checks only the parameter variable names of a function declaration, not +the parameter labels. It also only checks identifiers at the declaration site, not at usage +sites. + +Lint: Declaring an identifier with a leading underscore yields a lint error. + +`NoLeadingUnderscores` is a linter-only rule. + +### NoParensAroundConditions + +Enforces rules around parentheses in conditions or matched expressions. + +Parentheses are not used around any condition of an `if`, `guard`, or `while` statement, or +around the matched expression in a `switch` statement. + +Lint: If a top-most expression in a `switch`, `if`, `guard`, or `while` statement is surrounded + by parentheses, and it does not include a function call with a trailing closure, a lint + error is raised. + +Format: Parentheses around such expressions are removed, if they do not cause a parse ambiguity. + Specifically, parentheses are allowed if and only if the expression contains a function + call with a trailing closure. + +`NoParensAroundConditions` rule can format your code automatically. + +### NoPlaygroundLiterals + +The playground literals (`#colorLiteral`, `#fileLiteral`, and `#imageLiteral`) are forbidden. + +Lint: Using a playground literal will yield a lint error with a suggestion of an API to replace +it. + +`NoPlaygroundLiterals` is a linter-only rule. + +### NoVoidReturnOnFunctionSignature + +Functions that return `()` or `Void` should omit the return signature. + +Lint: Function declarations that explicitly return `()` or `Void` will yield a lint error. + +Format: Function declarations with explicit returns of `()` or `Void` will have their return + signature stripped. + +`NoVoidReturnOnFunctionSignature` rule can format your code automatically. + +### OmitExplicitReturns + +Single-expression functions, closures, subscripts can omit `return` statement. + +Lint: `func () { return ... }` and similar single expression constructs will yield a lint error. + +Format: `func () { return ... }` constructs will be replaced with + equivalent `func () { ... }` constructs. + +`OmitExplicitReturns` rule can format your code automatically. + +### OneCasePerLine + +Each enum case with associated values or a raw value should appear in its own case declaration. + +Lint: If a single `case` declaration declares multiple cases, and any of them have associated + values or raw values, a lint error is raised. + +Format: All case declarations with associated values or raw values will be moved to their own + case declarations. + +`OneCasePerLine` rule can format your code automatically. + +### OneVariableDeclarationPerLine + +Each variable declaration, with the exception of tuple destructuring, should +declare 1 variable. + +Lint: If a variable declaration declares multiple variables, a lint error is +raised. + +Format: If a variable declaration declares multiple variables, it will be +split into multiple declarations, each declaring one of the variables, as +long as the result would still be syntactically valid. + +`OneVariableDeclarationPerLine` rule can format your code automatically. + +### OnlyOneTrailingClosureArgument + +Function calls should never mix normal closure arguments and trailing closures. + +Lint: If a function call with a trailing closure also contains a non-trailing closure argument, + a lint error is raised. + +`OnlyOneTrailingClosureArgument` is a linter-only rule. + +### OrderedImports + +Imports must be lexicographically ordered and logically grouped at the top of each source file. +The order of the import groups is 1) regular imports, 2) declaration imports, and 3) @testable +imports. These groups are separated by a single blank line. Blank lines in between the import +declarations are removed. + +Lint: If an import appears anywhere other than the beginning of the file it resides in, + not lexicographically ordered, or not in the appropriate import group, a lint error is + raised. + +Format: Imports will be reordered and grouped at the top of the file. + +`OrderedImports` rule can format your code automatically. + +### ReplaceForEachWithForLoop + +Replace `forEach` with `for-in` loop unless its argument is a function reference. + +Lint: invalid use of `forEach` yield will yield a lint error. + +`ReplaceForEachWithForLoop` is a linter-only rule. + +### ReturnVoidInsteadOfEmptyTuple + +Return `Void`, not `()`, in signatures. + +Note that this rule does *not* apply to function declaration signatures in order to avoid +conflicting with `NoVoidReturnOnFunctionSignature`. + +Lint: Returning `()` in a signature yields a lint error. + +Format: `-> ()` is replaced with `-> Void` + +`ReturnVoidInsteadOfEmptyTuple` rule can format your code automatically. + +### TypeNamesShouldBeCapitalized + +`struct`, `class`, `enum` and `protocol` declarations should have a capitalized name. + +Lint: Types with un-capitalized names will yield a lint error. + +`TypeNamesShouldBeCapitalized` is a linter-only rule. + +### UseEarlyExits + +Early exits should be used whenever possible. + +This means that `if ... else { return/throw/break/continue }` constructs should be replaced by +`guard ... else { return/throw/break/continue }` constructs in order to keep indentation levels +low. Specifically, code of the following form: + +```swift +if condition { + trueBlock +} else { + falseBlock + return/throw/break/continue +} +``` + +will be transformed into: + +```swift +guard condition else { + falseBlock + return/throw/break/continue +} +trueBlock +``` + +Lint: `if ... else { return/throw/break/continue }` constructs will yield a lint error. + +Format: `if ... else { return/throw/break/continue }` constructs will be replaced with + equivalent `guard ... else { return/throw/break/continue }` constructs. + +`UseEarlyExits` rule can format your code automatically. + +### UseLetInEveryBoundCaseVariable + +Every variable bound in a `case` pattern must have its own `let/var`. + +For example, `case let .identifier(x, y)` is forbidden. Use +`case .identifier(let x, let y)` instead. + +Lint: `case let .identifier(...)` will yield a lint error. + +`UseLetInEveryBoundCaseVariable` is a linter-only rule. + +### UseShorthandTypeNames + +Shorthand type forms must be used wherever possible. + +Lint: Using a non-shorthand form (e.g. `Array`) yields a lint error unless the long + form is necessary (e.g. `Array.Index` cannot be shortened today.) + +Format: Where possible, shorthand types replace long form types; e.g. `Array` is + converted to `[Element]`. + +`UseShorthandTypeNames` rule can format your code automatically. + +### UseSingleLinePropertyGetter + +Read-only computed properties must use implicit `get` blocks. + +Lint: Read-only computed properties with explicit `get` blocks yield a lint error. + +Format: Explicit `get` blocks are rendered implicit by removing the `get`. + +`UseSingleLinePropertyGetter` rule can format your code automatically. + +### UseSynthesizedInitializer + +When possible, the synthesized `struct` initializer should be used. + +This means the creation of a (non-public) memberwise initializer with the same structure as the +synthesized initializer is forbidden. + +Lint: (Non-public) memberwise initializers with the same structure as the synthesized + initializer will yield a lint error. + +`UseSynthesizedInitializer` is a linter-only rule. + +### UseTripleSlashForDocumentationComments + +Documentation comments must use the `///` form. + +This is similar to `NoBlockComments` but is meant to prevent documentation block comments. + +Lint: If a doc block comment appears, a lint error is raised. + +Format: If a doc block comment appears on its own on a line, or if a doc block comment spans + multiple lines without appearing on the same line as code, it will be replaced with + multiple doc line comments. + +`UseTripleSlashForDocumentationComments` rule can format your code automatically. + +### UseWhereClausesInForLoops + +`for` loops that consist of a single `if` statement must use `where` clauses instead. + +Lint: `for` loops that consist of a single `if` statement yield a lint error. + +Format: `for` loops that consist of a single `if` statement have the conditional of that + statement factored out to a `where` clause. + +`UseWhereClausesInForLoops` rule can format your code automatically. + +### ValidateDocumentationComments + +Documentation comments must be complete and valid. + +"Command + Option + /" in Xcode produces a minimal valid documentation comment. + +Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or + invalid (uses `Parameters` when there is only one parameter) will yield a lint error. + +`ValidateDocumentationComments` is a linter-only rule. diff --git a/Sources/SwiftFormat/Core/DocumentationCommentText.swift b/Sources/SwiftFormat/Core/DocumentationCommentText.swift index 335bbbab8..44ef1a61a 100644 --- a/Sources/SwiftFormat/Core/DocumentationCommentText.swift +++ b/Sources/SwiftFormat/Core/DocumentationCommentText.swift @@ -18,6 +18,7 @@ import SwiftSyntax /// structural organization. It automatically handles trimming leading indentation from comments as /// well as "ASCII art" in block comments (i.e., leading asterisks on each line). @_spi(Testing) +@_spi(Rules) public struct DocumentationCommentText { /// Denotes the kind of punctuation used to introduce the comment. public enum Introducer { diff --git a/Sources/generate-pipeline/RuleCollector.swift b/Sources/generate-pipeline/RuleCollector.swift index 186757f32..d3564f11c 100644 --- a/Sources/generate-pipeline/RuleCollector.swift +++ b/Sources/generate-pipeline/RuleCollector.swift @@ -23,6 +23,10 @@ final class RuleCollector { /// The type name of the rule. let typeName: String + /// The description of the rule, extracted from the rule class or struct DocC comment + /// with `DocumentationCommentText(extractedFrom:)` + let description: String? + /// The syntax node types visited by the rule type. let visitedNodes: [String] @@ -86,6 +90,7 @@ final class RuleCollector { let typeName: String let members: MemberBlockItemListSyntax let maybeInheritanceClause: InheritanceClauseSyntax? + let description = DocumentationCommentText(extractedFrom: statement.item.leadingTrivia) if let classDecl = statement.item.as(ClassDeclSyntax.self) { typeName = classDecl.name.text @@ -140,7 +145,10 @@ final class RuleCollector { preconditionFailure("Failed to find type for rule named \(typeName)") } return DetectedRule( - typeName: typeName, visitedNodes: visitedNodes, canFormat: canFormat, + typeName: typeName, + description: description?.text, + visitedNodes: visitedNodes, + canFormat: canFormat, isOptIn: ruleType.isOptIn) } diff --git a/Sources/generate-pipeline/RuleDocumentationGenerator.swift b/Sources/generate-pipeline/RuleDocumentationGenerator.swift new file mode 100644 index 000000000..fccddee4f --- /dev/null +++ b/Sources/generate-pipeline/RuleDocumentationGenerator.swift @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftFormat + +/// Generates the markdown file with extended documenation on the available rules. +final class RuleDocumentationGenerator: FileGenerator { + + /// The rules collected by scanning the formatter source code. + let ruleCollector: RuleCollector + + /// Creates a new rule registry generator. + init(ruleCollector: RuleCollector) { + self.ruleCollector = ruleCollector + } + + func write(into handle: FileHandle) throws { + handle.write( + """ + + + # `swift-format` Lint and Format Rules + + Use the rules below in the `rules` block of your `.swift-format` + configuration file, as described in + [Configuration](Documentation/Configuration.md). All of these rules can be + applied in the linter, but only some of them can format your source code + automatically. + + Here's the list of available rules: + + + """ + ) + + for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { + handle.write(""" + - [\(detectedRule.typeName)](#\(detectedRule.typeName)) + + """) + } + + for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) { + handle.write(""" + + ### \(detectedRule.typeName) + + \(detectedRule.description ?? "") + \(ruleFormatSupportDescription(for: detectedRule)) + + """) + } + } + + private func ruleFormatSupportDescription(for rule: RuleCollector.DetectedRule) -> String { + return rule.canFormat ? + "`\(rule.typeName)` rule can format your code automatically." : + "`\(rule.typeName)` is a linter-only rule." + } +} diff --git a/Sources/generate-pipeline/main.swift b/Sources/generate-pipeline/main.swift index fd3c337ca..75df61f9e 100644 --- a/Sources/generate-pipeline/main.swift +++ b/Sources/generate-pipeline/main.swift @@ -33,6 +33,11 @@ let ruleNameCacheFile = sourcesDirectory .appendingPathComponent("Core") .appendingPathComponent("RuleNameCache+Generated.swift") +let ruleDocumentationFile = sourcesDirectory + .appendingPathComponent("..") + .appendingPathComponent("Documentation") + .appendingPathComponent("RuleDocumentation.md") + var ruleCollector = RuleCollector() try ruleCollector.collect(from: rulesDirectory) @@ -47,3 +52,8 @@ try registryGenerator.generateFile(at: ruleRegistryFile) // Generate the rule name cache. let ruleNameCacheGenerator = RuleNameCacheGenerator(ruleCollector: ruleCollector) try ruleNameCacheGenerator.generateFile(at: ruleNameCacheFile) + +// Generate the Documentation/RuleDocumentation.md file with rule descriptions. +// This uses DocC comments from rule implementations. +let ruleDocumentationGenerator = RuleDocumentationGenerator(ruleCollector: ruleCollector) +try ruleDocumentationGenerator.generateFile(at: ruleDocumentationFile)