Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure that content is complete using a queue #5986

Merged
merged 3 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions Source/SwiftLintCore/Models/Issue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public enum Issue: LocalizedError, Equatable {
/// Flag to enable warnings for deprecations being printed to the console. Printing is enabled by default.
package nonisolated(unsafe) static var printDeprecationWarnings = true

@TaskLocal private static var messageConsumer: (@Sendable (String) -> Void)?
@TaskLocal private static var printQueueContinuation: AsyncStream<String>.Continuation?

/// Hook used to capture all messages normally printed to stdout and return them back to the caller.
///
Expand All @@ -95,18 +95,10 @@ public enum Issue: LocalizedError, Equatable {
/// - returns: The collected messages produced while running the code in the runner.
@MainActor
static func captureConsole(runner: @Sendable () throws -> Void) async rethrows -> String {
actor Console {
static var content = ""
}
defer {
Console.content = ""
}
try $messageConsumer.withValue(
{ Console.content += (Console.content.isEmpty ? "" : "\n") + $0 },
operation: runner
)
await Task.yield()
return Console.content
let (stream, continuation) = AsyncStream.makeStream(of: String.self)
try $printQueueContinuation.withValue(continuation, operation: runner)
continuation.finish()
return await stream.reduce(into: "") { @Sendable in $0 += $0.isEmpty ? $1 : "\n\($1)" }
}

/// Wraps any `Error` into a `SwiftLintError.genericWarning` if it is not already a `SwiftLintError`.
Expand Down Expand Up @@ -140,13 +132,8 @@ public enum Issue: LocalizedError, Equatable {
if case .ruleDeprecated = self, !Self.printDeprecationWarnings {
return
}
Task(priority: .high) { @MainActor in
if let consumer = Self.messageConsumer {
consumer(errorDescription)
} else {
queuedPrintError(errorDescription)
}
}
Self.printQueueContinuation?.yield(errorDescription)
queuedPrintError(errorDescription)
}

private var message: String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ final class ExplicitTypeInterfaceConfigurationTests: SwiftLintTestCase {
}

func testInvalidKeyInCustomConfiguration() async throws {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var config = ExplicitTypeInterfaceConfiguration()
try config.apply(configuration: ["invalidKey": "error"])
},
let console = try await Issue.captureConsole {
var config = ExplicitTypeInterfaceConfiguration()
try config.apply(configuration: ["invalidKey": "error"])
}
XCTAssertEqual(
console,
"warning: Configuration for 'explicit_type_interface' rule contains the invalid key(s) 'invalidKey'."
)
}
Expand Down
17 changes: 9 additions & 8 deletions Tests/BuiltInRulesTests/IndentationWidthRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ final class IndentationWidthRuleTests: SwiftLintTestCase {
let defaultValue = IndentationWidthConfiguration().indentationWidth

for indentation in [0, -1, -5] {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var testee = IndentationWidthConfiguration()
try testee.apply(configuration: ["indentation_width": indentation])

// Value remains the default.
XCTAssertEqual(testee.indentationWidth, defaultValue)
},
let console = try await Issue.captureConsole {
var testee = IndentationWidthConfiguration()
try testee.apply(configuration: ["indentation_width": indentation])

// Value remains the default.
XCTAssertEqual(testee.indentationWidth, defaultValue)
}
XCTAssertEqual(
console,
"warning: Invalid configuration for 'indentation_width' rule. Falling back to default."
)
}
Expand Down
30 changes: 16 additions & 14 deletions Tests/BuiltInRulesTests/MultilineParametersConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import XCTest
final class MultilineParametersConfigurationTests: SwiftLintTestCase {
func testInvalidMaxNumberOfSingleLineParameters() async throws {
for maxNumberOfSingleLineParameters in [0, -1] {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var config = MultilineParametersConfiguration()
try config.apply(
configuration: ["max_number_of_single_line_parameters": maxNumberOfSingleLineParameters]
)
},
let console = try await Issue.captureConsole {
var config = MultilineParametersConfiguration()
try config.apply(
configuration: ["max_number_of_single_line_parameters": maxNumberOfSingleLineParameters]
)
}
XCTAssertEqual(
console,
"""
warning: Inconsistent configuration for 'multiline_parameters' rule: Option \
'max_number_of_single_line_parameters' should be >= 1.
Expand All @@ -22,13 +23,14 @@ final class MultilineParametersConfigurationTests: SwiftLintTestCase {
}

func testInvalidMaxNumberOfSingleLineParametersWithSingleLineEnabled() async throws {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var config = MultilineParametersConfiguration()
try config.apply(
configuration: ["max_number_of_single_line_parameters": 2, "allows_single_line": false]
)
},
let console = try await Issue.captureConsole {
var config = MultilineParametersConfiguration()
try config.apply(
configuration: ["max_number_of_single_line_parameters": 2, "allows_single_line": false]
)
}
XCTAssertEqual(
console,
"""
warning: Inconsistent configuration for 'multiline_parameters' rule: Option \
'max_number_of_single_line_parameters' has no effect when 'allows_single_line' is false.
Expand Down
11 changes: 6 additions & 5 deletions Tests/BuiltInRulesTests/NoEmptyBlockConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ final class NoEmptyBlockConfigurationTests: SwiftLintTestCase {
}

func testInvalidKeyInCustomConfiguration() async throws {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var config = NoEmptyBlockConfiguration()
try config.apply(configuration: ["invalidKey": "error"])
},
let console = try await Issue.captureConsole {
var config = NoEmptyBlockConfiguration()
try config.apply(configuration: ["invalidKey": "error"])
}
XCTAssertEqual(
console,
"warning: Configuration for 'no_empty_block' rule contains the invalid key(s) 'invalidKey'."
)
}
Expand Down
43 changes: 22 additions & 21 deletions Tests/FrameworkTests/RuleConfigurationDescriptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -491,35 +491,36 @@ final class RuleConfigurationDescriptionTests: SwiftLintTestCase {
}

func testDeprecationWarning() async throws {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: ["set": [6, 7]])
},
let console = try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: ["set": [6, 7]])
}
XCTAssertEqual(
console,
"warning: Configuration option 'set' in 'my_rule' rule is deprecated. Use the option 'other_opt' instead."
)
}

func testNoDeprecationWarningIfNoDeprecatedPropertySet() async throws {
try await AsyncAssertTrue(
try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: ["flag": false])
}.isEmpty
)
let console = try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: ["flag": false])
}
XCTAssertTrue(console.isEmpty)
}

func testInvalidKeys() async throws {
try await AsyncAssertEqual(
try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: [
"severity": "error",
"warning": 3,
"unknown": 1,
"unsupported": true,
])
},
let console = try await Issue.captureConsole {
var configuration = TestConfiguration()
try configuration.apply(configuration: [
"severity": "error",
"warning": 3,
"unknown": 1,
"unsupported": true,
])
}
XCTAssertEqual(
console,
"warning: Configuration for 'RuleMock' rule contains the invalid key(s) 'unknown', 'unsupported'."
)
}
Expand Down