Skip to content

Commit da2f1ec

Browse files
authored
Merge pull request #265 from allevato/swift-5.5-cherrypicks
Cherry-pick recent changes for Swift 5.5.
2 parents aaed43c + d8658f3 commit da2f1ec

19 files changed

+179
-52
lines changed

Package.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ let package = Package(
4747
),
4848
.target(
4949
name: "SwiftFormatTestSupport",
50-
dependencies: ["SwiftFormatCore", "SwiftFormatConfiguration"]
50+
dependencies: [
51+
"SwiftFormatCore",
52+
"SwiftFormatRules",
53+
"SwiftFormatConfiguration",
54+
]
5155
),
5256
.target(
5357
name: "SwiftFormatWhitespaceLinter",

Sources/SwiftFormat/LintPipeline.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension LintPipeline {
2929
func visitIfEnabled<Rule: SyntaxLintRule, Node: SyntaxProtocol>(
3030
_ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, for node: Node
3131
) {
32-
guard context.isRuleEnabled(Rule.self.ruleName, node: Syntax(node)) else { return }
32+
guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return }
3333
let rule = self.rule(Rule.self)
3434
_ = visitor(rule)(node)
3535
}
@@ -50,7 +50,7 @@ extension LintPipeline {
5050
// more importantly because the `visit` methods return protocol refinements of `Syntax` that
5151
// cannot currently be expressed as constraints without duplicating this function for each of
5252
// them individually.
53-
guard context.isRuleEnabled(Rule.self.ruleName, node: Syntax(node)) else { return }
53+
guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return }
5454
let rule = self.rule(Rule.self)
5555
_ = visitor(rule)(node)
5656
}

Sources/SwiftFormat/Pipelines+Generated.swift

+8
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class LintPipeline: SyntaxVisitor {
6363
override func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind {
6464
visitIfEnabled(DoNotUseSemicolons.visit, for: node)
6565
visitIfEnabled(OneVariableDeclarationPerLine.visit, for: node)
66+
visitIfEnabled(UseEarlyExits.visit, for: node)
6667
return .visitChildren
6768
}
6869

@@ -106,6 +107,11 @@ class LintPipeline: SyntaxVisitor {
106107
return .visitChildren
107108
}
108109

110+
override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind {
111+
visitIfEnabled(UseWhereClausesInForLoops.visit, for: node)
112+
return .visitChildren
113+
}
114+
109115
override func visit(_ node: ForcedValueExprSyntax) -> SyntaxVisitorContinueKind {
110116
visitIfEnabled(NeverForceUnwrap.visit, for: node)
111117
return .visitChildren
@@ -314,9 +320,11 @@ extension FormatPipeline {
314320
node = OneVariableDeclarationPerLine(context: context).visit(node)
315321
node = OrderedImports(context: context).visit(node)
316322
node = ReturnVoidInsteadOfEmptyTuple(context: context).visit(node)
323+
node = UseEarlyExits(context: context).visit(node)
317324
node = UseShorthandTypeNames(context: context).visit(node)
318325
node = UseSingleLinePropertyGetter(context: context).visit(node)
319326
node = UseTripleSlashForDocumentationComments(context: context).visit(node)
327+
node = UseWhereClausesInForLoops(context: context).visit(node)
320328
return node
321329
}
322330
}

Sources/SwiftFormat/SwiftFormatter.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Foundation
1414
import SwiftFormatConfiguration
1515
import SwiftFormatCore
1616
import SwiftFormatPrettyPrint
17+
import SwiftFormatRules
1718
import SwiftSyntax
1819

1920
/// Formats Swift source code or syntax trees according to the Swift style guidelines.
@@ -108,7 +109,7 @@ public final class SwiftFormatter {
108109
let assumedURL = url ?? URL(fileURLWithPath: "source")
109110
let context = Context(
110111
configuration: configuration, diagnosticEngine: diagnosticEngine, fileURL: assumedURL,
111-
sourceFileSyntax: syntax, source: source)
112+
sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache)
112113
let pipeline = FormatPipeline(context: context)
113114
let transformedSyntax = pipeline.visit(Syntax(syntax))
114115

Sources/SwiftFormat/SwiftLinter.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Foundation
1414
import SwiftFormatConfiguration
1515
import SwiftFormatCore
1616
import SwiftFormatPrettyPrint
17+
import SwiftFormatRules
1718
import SwiftFormatWhitespaceLinter
1819
import SwiftSyntax
1920

@@ -88,7 +89,7 @@ public final class SwiftLinter {
8889

8990
let context = Context(
9091
configuration: configuration, diagnosticEngine: diagnosticEngine, fileURL: url,
91-
sourceFileSyntax: syntax, source: source)
92+
sourceFileSyntax: syntax, source: source, ruleNameCache: ruleNameCache)
9293
let pipeline = LintPipeline(context: context)
9394
pipeline.walk(Syntax(syntax))
9495

Sources/SwiftFormatConfiguration/RuleRegistry+Generated.swift

+2
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ enum RuleRegistry {
4040
"OnlyOneTrailingClosureArgument": true,
4141
"OrderedImports": true,
4242
"ReturnVoidInsteadOfEmptyTuple": true,
43+
"UseEarlyExits": false,
4344
"UseLetInEveryBoundCaseVariable": true,
4445
"UseShorthandTypeNames": true,
4546
"UseSingleLinePropertyGetter": true,
4647
"UseSynthesizedInitializer": true,
4748
"UseTripleSlashForDocumentationComments": true,
49+
"UseWhereClausesInForLoops": false,
4850
"ValidateDocumentationComments": false,
4951
]
5052
}

Sources/SwiftFormatCore/Context.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,17 @@ public class Context {
5252
/// Contains the rules have been disabled by comments for certain line numbers.
5353
public let ruleMask: RuleMask
5454

55+
/// Contains all the available rules' names associated to their types' object identifiers.
56+
public let ruleNameCache: [ObjectIdentifier: String]
57+
5558
/// Creates a new Context with the provided configuration, diagnostic engine, and file URL.
5659
public init(
5760
configuration: Configuration,
5861
diagnosticEngine: DiagnosticEngine?,
5962
fileURL: URL,
6063
sourceFileSyntax: SourceFileSyntax,
61-
source: String? = nil
64+
source: String? = nil,
65+
ruleNameCache: [ObjectIdentifier: String]
6266
) {
6367
self.configuration = configuration
6468
self.diagnosticEngine = diagnosticEngine
@@ -71,12 +75,22 @@ public class Context {
7175
syntaxNode: Syntax(sourceFileSyntax),
7276
sourceLocationConverter: sourceLocationConverter
7377
)
78+
self.ruleNameCache = ruleNameCache
7479
}
7580

7681
/// Given a rule's name and the node it is examining, determine if the rule is disabled at this
7782
/// location or not.
78-
public func isRuleEnabled(_ ruleName: String, node: Syntax) -> Bool {
83+
public func isRuleEnabled<R: Rule>(_ rule: R.Type, node: Syntax) -> Bool {
7984
let loc = node.startLocation(converter: self.sourceLocationConverter)
85+
86+
assert(
87+
ruleNameCache[ObjectIdentifier(rule)] != nil,
88+
"""
89+
Missing cached rule name for '\(rule)'! \
90+
Ensure `generate-pipelines` has been run and `ruleNameCache` was injected.
91+
""")
92+
93+
let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName
8094
switch ruleMask.ruleState(ruleName, at: loc) {
8195
case .default:
8296
return configuration.rules[ruleName] ?? false

Sources/SwiftFormatCore/Rule.swift

+2-22
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public protocol Rule {
1717
/// The context in which the rule is executed.
1818
var context: Context { get }
1919

20-
/// The human-readable name of the rule. This defaults to the class name.
20+
/// The human-readable name of the rule. This defaults to the type name.
2121
static var ruleName: String { get }
2222

2323
/// Whether this rule is opt-in, meaning it is disabled by default.
@@ -27,27 +27,7 @@ public protocol Rule {
2727
init(context: Context)
2828
}
2929

30-
fileprivate var nameCache = [ObjectIdentifier: String]()
31-
fileprivate var nameCacheQueue = DispatchQueue(
32-
label: "com.apple.SwiftFormat.NameCache", attributes: .concurrent)
33-
3430
extension Rule {
3531
/// By default, the `ruleName` is just the name of the implementing rule class.
36-
public static var ruleName: String {
37-
let identifier = ObjectIdentifier(self)
38-
let cachedName = nameCacheQueue.sync {
39-
nameCache[identifier]
40-
}
41-
42-
if let cachedName = cachedName {
43-
return cachedName
44-
}
45-
46-
let name = String("\(self)".split(separator: ".").last!)
47-
nameCacheQueue.async(flags: .barrier) {
48-
nameCache[identifier] = name
49-
}
50-
51-
return name
52-
}
32+
public static var ruleName: String { String("\(self)".split(separator: ".").last!) }
5333
}

Sources/SwiftFormatCore/SyntaxFormatRule.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ open class SyntaxFormatRule: SyntaxRewriter, Rule {
3131
open override func visitAny(_ node: Syntax) -> Syntax? {
3232
// If the rule is not enabled, then return the node unmodified; otherwise, returning nil tells
3333
// SwiftSyntax to continue with the standard dispatch.
34-
guard context.isRuleEnabled(Self.ruleName, node: node) else { return node }
34+
guard context.isRuleEnabled(type(of: self), node: node) else { return node }
3535
return nil
3636
}
3737
}

Sources/SwiftFormatRules/OrderedImports.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte
324324
lines.append(currentLine)
325325
currentLine = Line()
326326
}
327-
let sortable = context.isRuleEnabled(OrderedImports.ruleName, node: Syntax(block))
327+
let sortable = context.isRuleEnabled(OrderedImports.self, node: Syntax(block))
328328
currentLine.syntaxNode = .importCodeBlock(block, sortable: sortable)
329329
} else {
330330
guard let syntaxNode = currentLine.syntaxNode else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
// This file is automatically generated with generate-pipeline. Do Not Edit!
14+
15+
/// By default, the `Rule.ruleName` should be the name of the implementing rule type.
16+
public let ruleNameCache: [ObjectIdentifier: String] = [
17+
ObjectIdentifier(AllPublicDeclarationsHaveDocumentation.self): "AllPublicDeclarationsHaveDocumentation",
18+
ObjectIdentifier(AlwaysUseLowerCamelCase.self): "AlwaysUseLowerCamelCase",
19+
ObjectIdentifier(AmbiguousTrailingClosureOverload.self): "AmbiguousTrailingClosureOverload",
20+
ObjectIdentifier(BeginDocumentationCommentWithOneLineSummary.self): "BeginDocumentationCommentWithOneLineSummary",
21+
ObjectIdentifier(DoNotUseSemicolons.self): "DoNotUseSemicolons",
22+
ObjectIdentifier(DontRepeatTypeInStaticProperties.self): "DontRepeatTypeInStaticProperties",
23+
ObjectIdentifier(FileScopedDeclarationPrivacy.self): "FileScopedDeclarationPrivacy",
24+
ObjectIdentifier(FullyIndirectEnum.self): "FullyIndirectEnum",
25+
ObjectIdentifier(GroupNumericLiterals.self): "GroupNumericLiterals",
26+
ObjectIdentifier(IdentifiersMustBeASCII.self): "IdentifiersMustBeASCII",
27+
ObjectIdentifier(NeverForceUnwrap.self): "NeverForceUnwrap",
28+
ObjectIdentifier(NeverUseForceTry.self): "NeverUseForceTry",
29+
ObjectIdentifier(NeverUseImplicitlyUnwrappedOptionals.self): "NeverUseImplicitlyUnwrappedOptionals",
30+
ObjectIdentifier(NoAccessLevelOnExtensionDeclaration.self): "NoAccessLevelOnExtensionDeclaration",
31+
ObjectIdentifier(NoBlockComments.self): "NoBlockComments",
32+
ObjectIdentifier(NoCasesWithOnlyFallthrough.self): "NoCasesWithOnlyFallthrough",
33+
ObjectIdentifier(NoEmptyTrailingClosureParentheses.self): "NoEmptyTrailingClosureParentheses",
34+
ObjectIdentifier(NoLabelsInCasePatterns.self): "NoLabelsInCasePatterns",
35+
ObjectIdentifier(NoLeadingUnderscores.self): "NoLeadingUnderscores",
36+
ObjectIdentifier(NoParensAroundConditions.self): "NoParensAroundConditions",
37+
ObjectIdentifier(NoVoidReturnOnFunctionSignature.self): "NoVoidReturnOnFunctionSignature",
38+
ObjectIdentifier(OneCasePerLine.self): "OneCasePerLine",
39+
ObjectIdentifier(OneVariableDeclarationPerLine.self): "OneVariableDeclarationPerLine",
40+
ObjectIdentifier(OnlyOneTrailingClosureArgument.self): "OnlyOneTrailingClosureArgument",
41+
ObjectIdentifier(OrderedImports.self): "OrderedImports",
42+
ObjectIdentifier(ReturnVoidInsteadOfEmptyTuple.self): "ReturnVoidInsteadOfEmptyTuple",
43+
ObjectIdentifier(UseEarlyExits.self): "UseEarlyExits",
44+
ObjectIdentifier(UseLetInEveryBoundCaseVariable.self): "UseLetInEveryBoundCaseVariable",
45+
ObjectIdentifier(UseShorthandTypeNames.self): "UseShorthandTypeNames",
46+
ObjectIdentifier(UseSingleLinePropertyGetter.self): "UseSingleLinePropertyGetter",
47+
ObjectIdentifier(UseSynthesizedInitializer.self): "UseSynthesizedInitializer",
48+
ObjectIdentifier(UseTripleSlashForDocumentationComments.self): "UseTripleSlashForDocumentationComments",
49+
ObjectIdentifier(UseWhereClausesInForLoops.self): "UseWhereClausesInForLoops",
50+
ObjectIdentifier(ValidateDocumentationComments.self): "ValidateDocumentationComments",
51+
]

Sources/SwiftFormatRules/UseEarlyExits.swift

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import SwiftSyntax
4444
/// equivalent `guard ... else { return/throw/break/continue }` constructs.
4545
public final class UseEarlyExits: SyntaxFormatRule {
4646

47+
/// Identifies this rule as being opt-in. This rule is experimental and not yet stable enough to
48+
/// be enabled by default.
49+
public override class var isOptIn: Bool { return true }
50+
4751
public override func visit(_ node: CodeBlockItemListSyntax) -> Syntax {
4852
// Continue recursing down the tree first, so that any nested/child nodes get transformed first.
4953
let nodeAfterTransformingChildren = super.visit(node)

Sources/SwiftFormatRules/UseWhereClausesInForLoops.swift

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import SwiftSyntax
2121
/// statement factored out to a `where` clause.
2222
public final class UseWhereClausesInForLoops: SyntaxFormatRule {
2323

24+
/// Identifies this rule as being opt-in. This rule is experimental and not yet stable enough to
25+
/// be enabled by default.
26+
public override class var isOptIn: Bool { return true }
27+
2428
public override func visit(_ node: ForInStmtSyntax) -> StmtSyntax {
2529
// Extract IfStmt node if it's the only node in the function's body.
2630
guard !node.body.statements.isEmpty else { return StmtSyntax(node) }

Sources/SwiftFormatTestSupport/DiagnosingTestCase.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SwiftFormatConfiguration
22
import SwiftFormatCore
3+
import SwiftFormatRules
34
import SwiftSyntax
45
import XCTest
56

@@ -39,7 +40,8 @@ open class DiagnosingTestCase: XCTestCase {
3940
configuration: configuration ?? Configuration(),
4041
diagnosticEngine: DiagnosticEngine(),
4142
fileURL: URL(fileURLWithPath: "/tmp/test.swift"),
42-
sourceFileSyntax: sourceFileSyntax)
43+
sourceFileSyntax: sourceFileSyntax,
44+
ruleNameCache: ruleNameCache)
4345
consumer = DiagnosticTrackingConsumer()
4446
context.diagnosticEngine?.addConsumer(consumer)
4547
return context

Sources/generate-pipeline/RuleCollector.swift

+2-5
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ import Foundation
1414
import SwiftFormatCore
1515
import SwiftSyntax
1616

17-
// These rules will not be added to the pipeline.
18-
let suppressRules = ["UseEarlyExits", "UseWhereClausesInForLoops"]
19-
2017
/// Collects information about rules in the formatter code base.
2118
final class RuleCollector {
2219
/// Information about a detected rule.
@@ -99,8 +96,8 @@ final class RuleCollector {
9996
return nil
10097
}
10198

102-
// Make sure the rule isn't suppressed, and it must have an inheritance clause.
103-
guard !suppressRules.contains(typeName), let inheritanceClause = maybeInheritanceClause else {
99+
// Make sure it has an inheritance clause.
100+
guard let inheritanceClause = maybeInheritanceClause else {
104101
return nil
105102
}
106103

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// Generates the rule registry file used to populate the default configuration.
16+
final class RuleNameCacheGenerator: FileGenerator {
17+
18+
/// The rules collected by scanning the formatter source code.
19+
let ruleCollector: RuleCollector
20+
21+
/// Creates a new rule registry generator.
22+
init(ruleCollector: RuleCollector) {
23+
self.ruleCollector = ruleCollector
24+
}
25+
26+
func write(into handle: FileHandle) throws {
27+
handle.write(
28+
"""
29+
//===----------------------------------------------------------------------===//
30+
//
31+
// This source file is part of the Swift.org open source project
32+
//
33+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
34+
// Licensed under Apache License v2.0 with Runtime Library Exception
35+
//
36+
// See https://swift.org/LICENSE.txt for license information
37+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
38+
//
39+
//===----------------------------------------------------------------------===//
40+
41+
// This file is automatically generated with generate-pipeline. Do Not Edit!
42+
43+
/// By default, the `Rule.ruleName` should be the name of the implementing rule type.
44+
public let ruleNameCache: [ObjectIdentifier: String] = [
45+
46+
"""
47+
)
48+
49+
for detectedRule in ruleCollector.allLinters.sorted(by: { $0.typeName < $1.typeName }) {
50+
handle.write(" ObjectIdentifier(\(detectedRule.typeName).self): \"\(detectedRule.typeName)\",\n")
51+
}
52+
handle.write("]\n")
53+
}
54+
}
55+

Sources/generate-pipeline/main.swift

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ let ruleRegistryFile = sourcesDirectory
2424
.appendingPathComponent("SwiftFormatConfiguration")
2525
.appendingPathComponent("RuleRegistry+Generated.swift")
2626

27+
let ruleNameCacheFile = sourcesDirectory
28+
.appendingPathComponent("SwiftFormatRules")
29+
.appendingPathComponent("RuleNameCache+Generated.swift")
30+
2731
var ruleCollector = RuleCollector()
2832
try ruleCollector.collect(from: rulesDirectory)
2933

@@ -34,3 +38,7 @@ try pipelineGenerator.generateFile(at: pipelineFile)
3438
// Generate the rule registry dictionary for configuration.
3539
let registryGenerator = RuleRegistryGenerator(ruleCollector: ruleCollector)
3640
try registryGenerator.generateFile(at: ruleRegistryFile)
41+
42+
// Generate the rule name cache.
43+
let ruleNameCacheGenerator = RuleNameCacheGenerator(ruleCollector: ruleCollector)
44+
try ruleNameCacheGenerator.generateFile(at: ruleNameCacheFile)

0 commit comments

Comments
 (0)