Files
SwiftLint/Source/SwiftLintCore/Models/Example.swift
T
JP Simard 86d60400c1 Move core SwiftLint functionality to new SwiftLintCore module
Over the years, SwiftLintFramework had become a fairly massive monolith,
containing over 400 source files with both core infrastructure and
rules.

Architecturally, the rules should rely on the core infrastructure but
not the other way around. There are two exceptions to this:
`custom_rules` and `superfluous_disable_command` which need special
integration with the linter infrastructure.

Now the time has come to formalize this architecture and one way to do
that is to move the core SwiftLint functionality out of
SwiftLintFramework and into a new SwiftLintCore module that the rules
can depend on.

Beyond enforcing architectural patterns, this also has the advantage of
speeding up incremental compilation by skipping rebuilding the core
functionality when iterating on rules.

Because the core functionality is always useful when building rules, I'm
opting to import SwiftLintCore in SwiftLintFramework as `@_exported` so
that it's implicitly available to all files in SwiftLintFramework
without needing to import it directly.

In a follow-up I'll also split the built-in rules and the extra rules
into their own modules. More modularization is possible from there, but
not planned.

The bulk of this PR just moves files from `Source/SwiftLintFramework/*`
to `Source/SwiftLintCore/*`. There are some other changes that can't be
split up into their own PRs:

* Change jazzy to document the SwiftLintCore module instead of
  SwiftLintFramework.
* Change imports in unit tests to reflect where code was moved to.
* Update `sourcery` make rule to reflect where code was moved to.
* Create a new `coreRules` array and register those rules with the
  registry. This allows the `custom_rules` and
  `superfluous_disable_command` rule implementations to remain internal
  to the SwiftLintCore module, preventing more implementation details
  from leaking across architectural layers.
* Move `RuleRegistry.registerAllRulesOnce()` out of the type declaration
  and up one level so it can access rules defined downstream from
  SwiftLintCore.
2023-04-26 21:10:19 -04:00

171 lines
7.0 KiB
Swift

/// Captures code and context information for an example of a triggering or
/// non-triggering style
public struct Example {
/// The contents of the example
public private(set) var code: String
/// The untyped configuration to apply to the rule, if deviating from the default configuration.
/// The structure should match what is expected as a configuration value for the rule being tested.
///
/// For example, if the following YAML would be used to configure the rule:
///
/// ```
/// severity: warning
/// ```
///
/// Then the equivalent configuration value would be `["severity": "warning"]`.
public private(set) var configuration: Any?
/// Whether the example should be tested by prepending multibyte grapheme clusters
///
/// - SeeAlso: addEmoji(_:)
public private(set) var testMultiByteOffsets: Bool
/// Whether tests shall verify that the example wrapped in a comment doesn't trigger
@_spi(TestHelper)
public private(set) var testWrappingInComment: Bool
/// Whether tests shall verify that the example wrapped into a string doesn't trigger
@_spi(TestHelper)
public private(set) var testWrappingInString: Bool
/// Whether tests shall verify that the disabled rule (comment in the example) doesn't trigger
@_spi(TestHelper)
public private(set) var testDisableCommand: Bool
/// Whether the example should be tested on Linux
public private(set) var testOnLinux: Bool
/// The path to the file where the example was created
public private(set) var file: StaticString
/// The line in the file where the example was created
public var line: UInt
/// Specifies whether the example should be excluded from the rule documentation.
///
/// It can be set to `true` if an example has mainly been added as another test case, but is not suitable
/// as a user example. User examples should be easy to understand. They should clearly show where and
/// why a rule is applied and where not. Complex examples with rarely used language constructs or
/// pathological use cases which are indeed important to test but not helpful for understanding can be
/// hidden from the documentation with this option.
let excludeFromDocumentation: Bool
/// Specifies whether the test example should be the only example run during the current test case execution.
@_spi(TestHelper)
public var isFocused: Bool
}
public extension Example {
/// Create a new Example with the specified code, file, and line.
/// - Parameters:
/// - code: The contents of the example.
/// - configuration: The untyped configuration to apply to the rule, if deviating from the default
/// configuration.
/// - testMultibyteOffsets: Whether the example should be tested by prepending multibyte grapheme clusters.
/// - testWrappingInComment:Whether test shall verify that the example wrapped in a comment doesn't trigger.
/// - testWrappingInString: Whether tests shall verify that the example wrapped into a string doesn't trigger.
/// - testDisableCommand: Whether tests shall verify that the disabled rule (comment in the example) doesn't
/// trigger.
/// - testOnLinux: Whether the example should be tested on Linux.
/// - file: The path to the file where the example is located.
/// Defaults to the file where this initializer is called.
/// - line: The line in the file where the example is located.
/// Defaults to the line where this initializer is called.
init(_ code: String, configuration: Any? = nil, testMultiByteOffsets: Bool = true,
testWrappingInComment: Bool = true, testWrappingInString: Bool = true, testDisableCommand: Bool = true,
testOnLinux: Bool = true, file: StaticString = #file, line: UInt = #line,
excludeFromDocumentation: Bool = false) {
self.code = code
self.configuration = configuration
self.testMultiByteOffsets = testMultiByteOffsets
self.testOnLinux = testOnLinux
self.file = file
self.line = line
self.excludeFromDocumentation = excludeFromDocumentation
self.testWrappingInComment = testWrappingInComment
self.testWrappingInString = testWrappingInString
self.testDisableCommand = testDisableCommand
self.isFocused = false
}
/// Returns the same example, but with the `code` that is passed in
/// - Parameter code: the new code to use in the modified example
func with(code: String) -> Example {
var new = self
new.code = code
return new
}
/// Returns a copy of the Example with all instances of the "↓" character removed.
func removingViolationMarkers() -> Example {
return with(code: code.replacingOccurrences(of: "↓", with: ""))
}
}
extension Example {
func skipWrappingInCommentTest() -> Self {
var new = self
new.testWrappingInComment = false
return new
}
func skipWrappingInStringTest() -> Self {
var new = self
new.testWrappingInString = false
return new
}
func skipMultiByteOffsetTest() -> Self {
var new = self
new.testMultiByteOffsets = false
return new
}
func skipDisableCommandTest() -> Self {
var new = self
new.testDisableCommand = false
return new
}
/// Makes the current example focused. This is for debugging purposes only.
func focused() -> Example { // swiftlint:disable:this unused_declaration
var new = self
new.isFocused = true
return new
}
}
extension Example: Hashable {
public static func == (lhs: Example, rhs: Example) -> Bool {
// Ignoring file/line metadata because two Examples could represent
// the same idea, but captured at two different points in the code
return lhs.code == rhs.code
}
public func hash(into hasher: inout Hasher) {
// Ignoring file/line metadata because two Examples could represent
// the same idea, but captured at two different points in the code
hasher.combine(code)
}
}
extension Example: Comparable {
public static func < (lhs: Example, rhs: Example) -> Bool {
return lhs.code < rhs.code
}
}
public extension Array where Element == Example {
/// Make these examples skip wrapping in comment tests.
func skipWrappingInCommentTests() -> Self {
map { $0.skipWrappingInCommentTest() }
}
/// Make these examples skip wrapping in string tests.
func skipWrappingInStringTests() -> Self {
map { $0.skipWrappingInStringTest() }
}
/// Make these examples skip multi-byte offset tests.
func skipMultiByteOffsetTests() -> Self {
map { $0.skipMultiByteOffsetTest() }
}
/// Make these examples skip disable command tests.
func skipDisableCommandTests() -> Self {
map { $0.skipDisableCommandTest() }
}
}