Skip to content

Commit cc40c36

Browse files
authored
Merge pull request #261 from p4checo/fix-and-improve-parallel-mode
Fix and improve parallel mode
2 parents 947f9f8 + 6d500ea commit cc40c36

File tree

13 files changed

+158
-46
lines changed

13 files changed

+158
-46
lines changed

Package.swift

Lines changed: 5 additions & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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/SwiftFormatter.swift

Lines changed: 2 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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/SwiftFormatCore/Context.swift

Lines changed: 16 additions & 2 deletions
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

Lines changed: 2 additions & 22 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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 {
Lines changed: 51 additions & 0 deletions
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/SwiftFormatTestSupport/DiagnosingTestCase.swift

Lines changed: 3 additions & 1 deletion
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
Lines changed: 55 additions & 0 deletions
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

Lines changed: 8 additions & 0 deletions
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)

Sources/swift-format/Frontend/Frontend.swift

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,35 +130,31 @@ class Frontend {
130130
"processPaths(_:) should only be called when paths is non-empty.")
131131

132132
if parallel {
133-
let allFilePaths = Array(FileIterator(paths: paths))
134-
DispatchQueue.concurrentPerform(iterations: allFilePaths.count) { index in
135-
let path = allFilePaths[index]
136-
openAndProcess(path)
133+
let filesToProcess = FileIterator(paths: paths).compactMap(openAndPrepareFile)
134+
DispatchQueue.concurrentPerform(iterations: filesToProcess.count) { index in
135+
processFile(filesToProcess[index])
137136
}
138137
} else {
139-
for path in FileIterator(paths: paths) {
140-
openAndProcess(path)
141-
}
138+
FileIterator(paths: paths).lazy.compactMap(openAndPrepareFile).forEach(processFile)
142139
}
143140
}
144141

145-
/// Read and process the given path, optionally synchronizing diagnostic output.
146-
private func openAndProcess(_ path: String) -> Void {
142+
/// Read and prepare the file at the given path for processing, optionally synchronizing
143+
/// diagnostic output.
144+
private func openAndPrepareFile(atPath path: String) -> FileToProcess? {
147145
guard let sourceFile = FileHandle(forReadingAtPath: path) else {
148146
diagnosticEngine.diagnose(Diagnostic.Message(.error, "Unable to open \(path)"))
149-
return
147+
return nil
150148
}
151149

152150
guard let configuration = configuration(
153151
atPath: lintFormatOptions.configurationPath, orInferredFromSwiftFileAtPath: path)
154152
else {
155153
// Already diagnosed in the called method.
156-
return
154+
return nil
157155
}
158156

159-
let fileToProcess = FileToProcess(
160-
fileHandle: sourceFile, path: path, configuration: configuration)
161-
processFile(fileToProcess)
157+
return FileToProcess(fileHandle: sourceFile, path: path, configuration: configuration)
162158
}
163159

164160
/// Returns the configuration that applies to the given `.swift` source file, when an explicit

0 commit comments

Comments
 (0)