Compare commits
13 Commits
main
...
use-index-
Author | SHA1 | Date |
---|---|---|
![]() |
31fb736284 | |
![]() |
00f6a76050 | |
![]() |
dcf67cac3c | |
![]() |
e207dcc16f | |
![]() |
0e6dfb9da2 | |
![]() |
0bec408655 | |
![]() |
9adb2deac2 | |
![]() |
1aec09282d | |
![]() |
d781a1261e | |
![]() |
783976aa52 | |
![]() |
bb2af8c5b3 | |
![]() |
3a39d8bb49 | |
![]() |
88283dc547 |
|
@ -36,7 +36,6 @@ opt_in_rules:
|
||||||
- last_where
|
- last_where
|
||||||
- legacy_multiple
|
- legacy_multiple
|
||||||
- legacy_random
|
- legacy_random
|
||||||
- let_var_whitespace
|
|
||||||
- literal_expression_end_indentation
|
- literal_expression_end_indentation
|
||||||
- lower_acl_than_parent
|
- lower_acl_than_parent
|
||||||
- modifier_order
|
- modifier_order
|
||||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -39,6 +39,19 @@
|
||||||
[Bryan Ricker](https://github.com/bricker)
|
[Bryan Ricker](https://github.com/bricker)
|
||||||
[#3426](https://github.com/realm/SwiftLint/pull/3426)
|
[#3426](https://github.com/realm/SwiftLint/pull/3426)
|
||||||
|
|
||||||
|
* The command line syntax has slightly changed due to migrating from the
|
||||||
|
Commandant command line parsing library to swift-argument-parser.
|
||||||
|
For the most part the breaking changes are all to make the syntax more
|
||||||
|
unix compliant and intuitive to use. For example, commands such as
|
||||||
|
`swiftlint --help` or `swiftlint -h` now work as expected.
|
||||||
|
The help output from various commands has greatly improved as well.
|
||||||
|
Notably: `swiftlint autocorrect` was removed in favor of
|
||||||
|
`swiftlint --fix`.
|
||||||
|
Previous commands should continue to work temporarily to help with the
|
||||||
|
transition. Please let us know if there's a command that no longer
|
||||||
|
works and we'll attempt to add a bridge to help with its transition.
|
||||||
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
|
||||||
#### Experimental
|
#### Experimental
|
||||||
|
|
||||||
* None.
|
* None.
|
||||||
|
@ -250,6 +263,9 @@
|
||||||
enable it to detect more occurrences of unused declarations.
|
enable it to detect more occurrences of unused declarations.
|
||||||
[JP Simard](https://github.com/jpsim)
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
|
||||||
|
* Make the `unused_import` rule run about 2 times faster.
|
||||||
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
|
||||||
* Remove unneeded internal locking overhead, leading to increased
|
* Remove unneeded internal locking overhead, leading to increased
|
||||||
performance in multithreaded operations.
|
performance in multithreaded operations.
|
||||||
[JP Simard](https://github.com/jpsim)
|
[JP Simard](https://github.com/jpsim)
|
||||||
|
|
|
@ -1,40 +1,22 @@
|
||||||
{
|
{
|
||||||
"object": {
|
"object": {
|
||||||
"pins": [
|
"pins": [
|
||||||
{
|
|
||||||
"package": "Commandant",
|
|
||||||
"repositoryURL": "https://github.com/Carthage/Commandant.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "ab68611013dec67413628ac87c1f29e8427bc8e4",
|
|
||||||
"version": "0.17.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Nimble",
|
|
||||||
"repositoryURL": "https://github.com/Quick/Nimble.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "7a46a5fc86cb917f69e3daf79fcb045283d8f008",
|
|
||||||
"version": "8.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Quick",
|
|
||||||
"repositoryURL": "https://github.com/Quick/Quick.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "09b3becb37cb2163919a3842a4c5fa6ec7130792",
|
|
||||||
"version": "2.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"package": "SourceKitten",
|
"package": "SourceKitten",
|
||||||
"repositoryURL": "https://github.com/jpsim/SourceKitten.git",
|
"repositoryURL": "https://github.com/jpsim/SourceKitten.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "c0f960f72fa1e6151695074ffa696e4da6c45ce8",
|
"revision": "7f4be006fe73211b0fd9666c73dc2f2303ffa756",
|
||||||
"version": "0.30.1"
|
"version": "0.31.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-argument-parser",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-argument-parser.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
|
||||||
|
"version": "0.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// swift-tools-version:5.0
|
// swift-tools-version:5.2
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
#if canImport(CommonCrypto)
|
#if canImport(CommonCrypto)
|
||||||
|
@ -14,8 +14,8 @@ let package = Package(
|
||||||
.library(name: "SwiftLintFramework", targets: ["SwiftLintFramework"])
|
.library(name: "SwiftLintFramework", targets: ["SwiftLintFramework"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/Carthage/Commandant.git", .upToNextMinor(from: "0.17.0")),
|
.package(name: "swift-argument-parser", url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.3.1")),
|
||||||
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.30.1")),
|
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.31.0")),
|
||||||
.package(url: "https://github.com/jpsim/Yams.git", from: "4.0.2"),
|
.package(url: "https://github.com/jpsim/Yams.git", from: "4.0.2"),
|
||||||
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
|
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
|
||||||
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.3.2"))] : []),
|
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.3.2"))] : []),
|
||||||
|
@ -23,7 +23,7 @@ let package = Package(
|
||||||
.target(
|
.target(
|
||||||
name: "swiftlint",
|
name: "swiftlint",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Commandant",
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
"SwiftLintFramework",
|
"SwiftLintFramework",
|
||||||
"SwiftyTextTable",
|
"SwiftyTextTable",
|
||||||
]
|
]
|
||||||
|
@ -31,7 +31,7 @@ let package = Package(
|
||||||
.target(
|
.target(
|
||||||
name: "SwiftLintFramework",
|
name: "SwiftLintFramework",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"SourceKittenFramework",
|
.product(name: "SourceKittenFramework", package: "SourceKitten"),
|
||||||
"Yams",
|
"Yams",
|
||||||
] + (addCryptoSwift ? ["CryptoSwift"] : [])
|
] + (addCryptoSwift ? ["CryptoSwift"] : [])
|
||||||
),
|
),
|
||||||
|
|
|
@ -30,6 +30,22 @@ extension Array where Element: Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
func unique<T: Hashable>(by transform: ((Element) -> T)) -> [Element] {
|
||||||
|
var set = Set<T>()
|
||||||
|
var result = [Element]()
|
||||||
|
for value in self {
|
||||||
|
let transformed = transform(value)
|
||||||
|
if !set.contains(transformed) {
|
||||||
|
set.insert(transformed)
|
||||||
|
result.append(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Array {
|
extension Array {
|
||||||
static func array(of obj: Any?) -> [Element]? {
|
static func array(of obj: Any?) -> [Element]? {
|
||||||
if let array = obj as? [Element] {
|
if let array = obj as? [Element] {
|
||||||
|
|
|
@ -133,6 +133,22 @@ public struct SourceKittenDictionary {
|
||||||
return value["key.column"] as? Int64
|
return value["key.column"] as? Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The USR for this declaration.
|
||||||
|
var usr: String? {
|
||||||
|
return value["key.usr"] as? String
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The annotated declaration.
|
||||||
|
var annotatedDeclaration: String? {
|
||||||
|
return value["key.annotated_decl"] as? String
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dependencies returned in an index response.
|
||||||
|
var dependencies: [SourceKittenDictionary]? {
|
||||||
|
return (value["key.dependencies"] as? [[String: SourceKitRepresentable]])?
|
||||||
|
.map(SourceKittenDictionary.init)
|
||||||
|
}
|
||||||
|
|
||||||
/// The `SwiftDeclarationAttributeKind` values associated with this dictionary.
|
/// The `SwiftDeclarationAttributeKind` values associated with this dictionary.
|
||||||
var enclosedSwiftAttributes: [SwiftDeclarationAttributeKind] {
|
var enclosedSwiftAttributes: [SwiftDeclarationAttributeKind] {
|
||||||
return swiftAttributes.compactMap { $0.attribute }
|
return swiftAttributes.compactMap { $0.attribute }
|
||||||
|
|
|
@ -118,3 +118,16 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StringView {
|
||||||
|
/// Converts a line and column pair to a ByteCount offset into the string.
|
||||||
|
///
|
||||||
|
/// - parameter line: The 1-indexed line number into the string.
|
||||||
|
/// - parameter column: The 1-indexed column number into the string.
|
||||||
|
///
|
||||||
|
/// - returns: The `ByteCount` offset in bytes into the string.
|
||||||
|
func byteOffset(forLine line: Int, column: Int) -> ByteCount {
|
||||||
|
guard line > 0 else { return ByteCount(column - 1) }
|
||||||
|
return lines[line - 1].byteRange.location + ByteCount(column - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,22 +19,4 @@ extension SyntaxKind {
|
||||||
.docComment, .docCommentField, .identifier, .keyword, .number,
|
.docComment, .docCommentField, .identifier, .keyword, .number,
|
||||||
.objectLiteral, .parameter, .placeholder, .string,
|
.objectLiteral, .parameter, .placeholder, .string,
|
||||||
.stringInterpolationAnchor, .typeidentifier]
|
.stringInterpolationAnchor, .typeidentifier]
|
||||||
|
|
||||||
/// Syntax kinds that don't have associated module info when getting their cursor info.
|
|
||||||
static var kindsWithoutModuleInfo: Set<SyntaxKind> {
|
|
||||||
return [
|
|
||||||
.attributeBuiltin,
|
|
||||||
.keyword,
|
|
||||||
.number,
|
|
||||||
.docComment,
|
|
||||||
.string,
|
|
||||||
.stringInterpolationAnchor,
|
|
||||||
.attributeID,
|
|
||||||
.buildconfigKeyword,
|
|
||||||
.buildconfigID,
|
|
||||||
.commentURL,
|
|
||||||
.comment,
|
|
||||||
.docCommentField
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,20 @@ public final class SwiftLintFile {
|
||||||
|
|
||||||
return containsAttributesRequiringFoundation(dict: structureDictionary)
|
return containsAttributesRequiringFoundation(dict: structureDictionary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the SourceKit index for the current file, obtained with the specified compiler arguments.
|
||||||
|
///
|
||||||
|
/// - parameter compilerArguments: The swiftc arguments needed to compile the file.
|
||||||
|
///
|
||||||
|
/// - returns: A `SourceKittenDictionary` with the index response.
|
||||||
|
func index(compilerArguments: [String]) -> SourceKittenDictionary? {
|
||||||
|
return path
|
||||||
|
.flatMap { path in
|
||||||
|
try? Request.index(file: path, arguments: compilerArguments)
|
||||||
|
.sendIfNotDisabled()
|
||||||
|
}
|
||||||
|
.map(SourceKittenDictionary.init)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Hashable Conformance
|
// MARK: - Hashable Conformance
|
||||||
|
|
|
@ -91,15 +91,6 @@ public struct UnusedDeclarationRule: AutomaticTestableRule, ConfigurationProvide
|
||||||
// MARK: - File Extensions
|
// MARK: - File Extensions
|
||||||
|
|
||||||
private extension SwiftLintFile {
|
private extension SwiftLintFile {
|
||||||
func index(compilerArguments: [String]) -> SourceKittenDictionary? {
|
|
||||||
return path
|
|
||||||
.flatMap { path in
|
|
||||||
try? Request.index(file: path, arguments: compilerArguments)
|
|
||||||
.send()
|
|
||||||
}
|
|
||||||
.map(SourceKittenDictionary.init)
|
|
||||||
}
|
|
||||||
|
|
||||||
func referencedUSRs(index: SourceKittenDictionary) -> Set<String> {
|
func referencedUSRs(index: SourceKittenDictionary) -> Set<String> {
|
||||||
return Set(index.traverseEntities { entity -> String? in
|
return Set(index.traverseEntities { entity -> String? in
|
||||||
if let usr = entity.usr,
|
if let usr = entity.usr,
|
||||||
|
@ -224,14 +215,6 @@ private extension SwiftLintFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension SourceKittenDictionary {
|
private extension SourceKittenDictionary {
|
||||||
var usr: String? {
|
|
||||||
return value["key.usr"] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
var annotatedDeclaration: String? {
|
|
||||||
return value["key.annotated_decl"] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
var isImplicit: Bool {
|
var isImplicit: Bool {
|
||||||
return value["key.is_implicit"] as? Bool == true
|
return value["key.is_implicit"] as? Bool == true
|
||||||
}
|
}
|
||||||
|
@ -321,10 +304,3 @@ private extension SourceKittenDictionary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StringView {
|
|
||||||
func byteOffset(forLine line: Int, column: Int) -> ByteCount {
|
|
||||||
guard line > 0 else { return ByteCount(column - 1) }
|
|
||||||
return lines[line - 1].byteRange.location + ByteCount(column - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -98,7 +98,12 @@ public struct UnusedImportRule: CorrectableRule, ConfigurationProviderRule, Anal
|
||||||
|
|
||||||
private extension SwiftLintFile {
|
private extension SwiftLintFile {
|
||||||
func getImportUsage(compilerArguments: [String], configuration: UnusedImportConfiguration) -> [ImportUsage] {
|
func getImportUsage(compilerArguments: [String], configuration: UnusedImportConfiguration) -> [ImportUsage] {
|
||||||
var (imports, usrFragments) = getImportsAndUSRFragments(compilerArguments: compilerArguments)
|
guard let index = index(compilerArguments: compilerArguments) else {
|
||||||
|
queuedPrintError("Could not get index")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var (imports, usrFragments) = getImportsAndUSRFragments(index: index, compilerArguments: compilerArguments)
|
||||||
|
|
||||||
// Always disallow 'import Swift' because it's available without importing.
|
// Always disallow 'import Swift' because it's available without importing.
|
||||||
usrFragments.remove("Swift")
|
usrFragments.remove("Swift")
|
||||||
|
@ -108,15 +113,6 @@ private extension SwiftLintFile {
|
||||||
unusedImports.remove("Foundation")
|
unusedImports.remove("Foundation")
|
||||||
}
|
}
|
||||||
|
|
||||||
if unusedImports.isNotEmpty {
|
|
||||||
unusedImports.subtract(
|
|
||||||
operatorImports(
|
|
||||||
arguments: compilerArguments,
|
|
||||||
processedTokenOffsets: Set(syntaxMap.tokens.map { $0.offset })
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentsNSString = stringView.nsString
|
let contentsNSString = stringView.nsString
|
||||||
let unusedImportUsages = rangedAndSortedUnusedImports(of: Array(unusedImports), contents: contentsNSString)
|
let unusedImportUsages = rangedAndSortedUnusedImports(of: Array(unusedImports), contents: contentsNSString)
|
||||||
.map { ImportUsage.unused(module: $0, range: $1) }
|
.map { ImportUsage.unused(module: $0, range: $1) }
|
||||||
|
@ -143,42 +139,88 @@ private extension SwiftLintFile {
|
||||||
return unusedImportUsages + missingImports.sorted().map { .missing(module: $0) }
|
return unusedImportUsages + missingImports.sorted().map { .missing(module: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImportsAndUSRFragments(compilerArguments: [String]) -> (imports: Set<String>, usrFragments: Set<String>) {
|
func getImportsAndUSRFragments(index: SourceKittenDictionary, compilerArguments: [String])
|
||||||
var imports = Set<String>()
|
-> (imports: Set<String>, usrFragments: Set<String>) {
|
||||||
var usrFragments = Set<String>()
|
var usrFragments = Set<String>()
|
||||||
var nextIsModuleImport = false
|
|
||||||
for token in syntaxMap.tokens {
|
let allEntities = flatEntities(entity: index)
|
||||||
guard let tokenKind = token.kind else {
|
|
||||||
continue
|
let referenceEntities = allEntities.filter { entity in
|
||||||
|
entity.kind?.starts(with: "source.lang.swift.ref") == true &&
|
||||||
|
entity.kind != "source.lang.swift.ref.module"
|
||||||
}
|
}
|
||||||
if tokenKind == .keyword, contents(for: token) == "import" {
|
|
||||||
nextIsModuleImport = true
|
// swiftlint:disable:next nesting - We really shouldn't trigger when these nested types are in functions
|
||||||
continue
|
struct Reference {
|
||||||
|
let line, column: Int
|
||||||
|
let usr: String
|
||||||
}
|
}
|
||||||
if SyntaxKind.kindsWithoutModuleInfo.contains(tokenKind) {
|
|
||||||
continue
|
let dedupedReferences = referenceEntities
|
||||||
|
.compactMap { entity in
|
||||||
|
entity.line.flatMap { line in
|
||||||
|
entity.column.flatMap { column in
|
||||||
|
entity.usr.map { usr in
|
||||||
|
Reference(line: Int(line), column: Int(column), usr: usr)
|
||||||
}
|
}
|
||||||
let cursorInfoRequest = Request.cursorInfo(file: path!, offset: token.offset,
|
}
|
||||||
arguments: compilerArguments)
|
}
|
||||||
|
}
|
||||||
|
// don't cursor-info the same USR at different locations
|
||||||
|
.unique(by: { $0.usr })
|
||||||
|
|
||||||
|
var seenUSRs = Set<String>()
|
||||||
|
for reference in dedupedReferences {
|
||||||
|
let nameOffset = stringView.byteOffset(forLine: reference.line, column: reference.column)
|
||||||
|
let usr = reference.usr
|
||||||
|
|
||||||
|
let cursorInfoRequest = Request.cursorInfo(file: path!, offset: nameOffset, arguments: compilerArguments)
|
||||||
guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled()).map(SourceKittenDictionary.init) else {
|
guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled()).map(SourceKittenDictionary.init) else {
|
||||||
queuedPrintError("Could not get cursor info")
|
queuedPrintError("Could not get cursor info")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if nextIsModuleImport {
|
|
||||||
if let importedModule = cursorInfo.moduleName,
|
if cursorInfo.usr == usr {
|
||||||
cursorInfo.kind == "source.lang.swift.ref.module" {
|
seenUSRs.insert(usr)
|
||||||
imports.insert(importedModule)
|
if let rootModuleName = cursorInfo.moduleName?.split(separator: ".").first.map(String.init) {
|
||||||
nextIsModuleImport = false
|
usrFragments.insert(rootModuleName)
|
||||||
continue
|
}
|
||||||
} else {
|
} else {
|
||||||
nextIsModuleImport = false
|
let tokens = syntaxMap.tokens
|
||||||
|
guard let firstTokenIndexAfterNameOffset = tokens.firstIndex(where: { $0.offset > nameOffset }) else {
|
||||||
|
queuedPrintError("Could not get tokens")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var extraTokensToCheck = 3
|
||||||
|
let tokensAfterNameOffset = tokens.suffix(from: firstTokenIndexAfterNameOffset)
|
||||||
|
for token in tokensAfterNameOffset {
|
||||||
|
extraTokensToCheck -= 1
|
||||||
|
if extraTokensToCheck == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenCursorInfoRequest = Request.cursorInfo(file: path!, offset: token.offset,
|
||||||
|
arguments: compilerArguments)
|
||||||
|
let tokenCursorInfo = (try? tokenCursorInfoRequest.sendIfNotDisabled())
|
||||||
|
.map(SourceKittenDictionary.init)
|
||||||
|
if tokenCursorInfo?.usr == usr {
|
||||||
|
seenUSRs.insert(usr)
|
||||||
|
if let rootModuleName = tokenCursorInfo?.moduleName?.split(separator: ".").first.map(String.init) {
|
||||||
|
usrFragments.insert(rootModuleName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendUsedImports(cursorInfo: cursorInfo, usrFragments: &usrFragments)
|
let imports = index.dependencies?
|
||||||
}
|
.filter { $0.kind?.starts(with: "source.lang.swift.import.module") == true }
|
||||||
|
.compactMap { $0.name }
|
||||||
|
.filter { $0 != "Swift" }
|
||||||
|
|
||||||
return (imports: imports, usrFragments: usrFragments)
|
return (imports: Set(imports ?? []), usrFragments: usrFragments)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rangedAndSortedUnusedImports(of unusedImports: [String], contents: NSString) -> [(String, NSRange)] {
|
func rangedAndSortedUnusedImports(of unusedImports: [String], contents: NSString) -> [(String, NSRange)] {
|
||||||
|
@ -189,42 +231,6 @@ private extension SwiftLintFile {
|
||||||
.sorted(by: { $0.1.location < $1.1.location })
|
.sorted(by: { $0.1.location < $1.1.location })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operators are omitted in the editor.open request and thus have to be looked up by the indexsource request
|
|
||||||
func operatorImports(arguments: [String], processedTokenOffsets: Set<ByteCount>) -> Set<String> {
|
|
||||||
guard let index = (try? Request.index(file: path!, arguments: arguments).sendIfNotDisabled())
|
|
||||||
.map(SourceKittenDictionary.init) else {
|
|
||||||
queuedPrintError("Could not get index")
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
let operatorEntities = flatEntities(entity: index).filter { mightBeOperator(kind: $0.kind) }
|
|
||||||
let offsetPerLine = self.offsetPerLine()
|
|
||||||
var imports = Set<String>()
|
|
||||||
|
|
||||||
for entity in operatorEntities {
|
|
||||||
if
|
|
||||||
let line = entity.line,
|
|
||||||
let column = entity.column,
|
|
||||||
let lineOffset = offsetPerLine[Int(line) - 1] {
|
|
||||||
let offset = lineOffset + column - 1
|
|
||||||
|
|
||||||
// Filter already processed tokens such as static methods that are not operators
|
|
||||||
guard !processedTokenOffsets.contains(ByteCount(offset)) else { continue }
|
|
||||||
|
|
||||||
let cursorInfoRequest = Request.cursorInfo(file: path!, offset: ByteCount(offset), arguments: arguments)
|
|
||||||
guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled())
|
|
||||||
.map(SourceKittenDictionary.init) else {
|
|
||||||
queuedPrintError("Could not get cursor info")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
appendUsedImports(cursorInfo: cursorInfo, usrFragments: &imports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imports
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatEntities(entity: SourceKittenDictionary) -> [SourceKittenDictionary] {
|
func flatEntities(entity: SourceKittenDictionary) -> [SourceKittenDictionary] {
|
||||||
let entities = entity.entities
|
let entities = entity.entities
|
||||||
if entities.isEmpty {
|
if entities.isEmpty {
|
||||||
|
@ -233,34 +239,4 @@ private extension SwiftLintFile {
|
||||||
return [entity] + entities.flatMap { flatEntities(entity: $0) }
|
return [entity] + entities.flatMap { flatEntities(entity: $0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func offsetPerLine() -> [Int: Int64] {
|
|
||||||
return Dictionary(
|
|
||||||
uniqueKeysWithValues: contents.bridge()
|
|
||||||
.components(separatedBy: "\n")
|
|
||||||
.map { Int64($0.bridge().lengthOfBytes(using: .utf8)) }
|
|
||||||
.reduce(into: [0]) { result, length in
|
|
||||||
let newLineCharacterLength = Int64(1)
|
|
||||||
let lineLength = length + newLineCharacterLength
|
|
||||||
result.append(contentsOf: [(result.last ?? 0) + lineLength])
|
|
||||||
}
|
|
||||||
.enumerated()
|
|
||||||
.map { ($0.offset, $0.element) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operators that are a part of some body are reported as method.static
|
|
||||||
func mightBeOperator(kind: String?) -> Bool {
|
|
||||||
guard let kind = kind else { return false }
|
|
||||||
return [
|
|
||||||
"source.lang.swift.ref.function.operator",
|
|
||||||
"source.lang.swift.ref.function.method.static"
|
|
||||||
].contains { kind.hasPrefix($0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendUsedImports(cursorInfo: SourceKittenDictionary, usrFragments: inout Set<String>) {
|
|
||||||
if let rootModuleName = cursorInfo.moduleName?.split(separator: ".").first.map(String.init) {
|
|
||||||
usrFragments.insert(rootModuleName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,11 +261,6 @@ private extension SwiftLintFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StringView {
|
private extension StringView {
|
||||||
func byteOffset(forLine line: Int, column: Int) -> ByteCount {
|
|
||||||
guard line > 0 else { return ByteCount(column - 1) }
|
|
||||||
return lines[line - 1].byteRange.location + ByteCount(column - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func recursiveByteOffsets(_ dict: [String: Any]) -> [ByteCount] {
|
func recursiveByteOffsets(_ dict: [String: Any]) -> [ByteCount] {
|
||||||
let cur: [ByteCount]
|
let cur: [ByteCount]
|
||||||
if let line = dict["key.line"] as? Int64,
|
if let line = dict["key.line"] as? Int64,
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct Analyze: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(abstract: "Run analysis rules")
|
||||||
|
|
||||||
|
@OptionGroup
|
||||||
|
var common: LintOrAnalyzeArguments
|
||||||
|
@Option(help: pathOptionDescription(for: .analyze))
|
||||||
|
var path: String?
|
||||||
|
@Flag(help: quietOptionDescription(for: .analyze))
|
||||||
|
var quiet = false
|
||||||
|
@Option(help: "The path of the full xcodebuild log to use when running AnalyzerRules.")
|
||||||
|
var compilerLogPath: String?
|
||||||
|
@Option(help: "The path of a compilation database to use when running AnalyzerRules.")
|
||||||
|
var compileCommands: String?
|
||||||
|
@Argument(help: pathsArgumentDescription(for: .analyze))
|
||||||
|
var paths = [String]()
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
let allPaths: [String]
|
||||||
|
if let path = path {
|
||||||
|
allPaths = [path]
|
||||||
|
} else if !paths.isEmpty {
|
||||||
|
allPaths = paths
|
||||||
|
} else {
|
||||||
|
allPaths = [""] // Analyze files in current working directory if no paths were specified.
|
||||||
|
}
|
||||||
|
let options = LintOrAnalyzeOptions(
|
||||||
|
mode: .analyze,
|
||||||
|
paths: allPaths,
|
||||||
|
useSTDIN: false,
|
||||||
|
configurationFiles: common.config,
|
||||||
|
strict: common.leniency == .strict,
|
||||||
|
lenient: common.leniency == .lenient,
|
||||||
|
forceExclude: common.forceExclude,
|
||||||
|
useExcludingByPrefix: common.useAlternativeExcluding,
|
||||||
|
useScriptInputFiles: common.useScriptInputFiles,
|
||||||
|
benchmark: common.benchmark,
|
||||||
|
reporter: common.reporter,
|
||||||
|
quiet: quiet,
|
||||||
|
cachePath: nil,
|
||||||
|
ignoreCache: true,
|
||||||
|
enableAllRules: false,
|
||||||
|
autocorrect: common.fix,
|
||||||
|
compilerLogPath: compilerLogPath,
|
||||||
|
compileCommands: compileCommands
|
||||||
|
)
|
||||||
|
|
||||||
|
let result = LintOrAnalyzeCommand.run(options)
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
return
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,98 +0,0 @@
|
||||||
import Commandant
|
|
||||||
import SwiftLintFramework
|
|
||||||
|
|
||||||
struct AnalyzeCommand: CommandProtocol {
|
|
||||||
let verb = "analyze"
|
|
||||||
let function = "[Experimental] Run analysis rules"
|
|
||||||
|
|
||||||
func run(_ options: AnalyzeOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
let options = LintOrAnalyzeOptions(options)
|
|
||||||
if options.autocorrect {
|
|
||||||
return autocorrect(options)
|
|
||||||
} else {
|
|
||||||
return LintOrAnalyzeCommand.run(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func autocorrect(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
let storage = RuleStorage()
|
|
||||||
let configuration = Configuration(options: options)
|
|
||||||
return configuration.visitLintableFiles(options: options, cache: nil, storage: storage) { linter in
|
|
||||||
let corrections = linter.correct(using: storage)
|
|
||||||
if !corrections.isEmpty && !options.quiet {
|
|
||||||
let correctionLogs = corrections.map({ $0.consoleDescription })
|
|
||||||
queuedPrint(correctionLogs.joined(separator: "\n"))
|
|
||||||
}
|
|
||||||
}.flatMap { files in
|
|
||||||
if !options.quiet {
|
|
||||||
let pluralSuffix = { (collection: [Any]) -> String in
|
|
||||||
return collection.count != 1 ? "s" : ""
|
|
||||||
}
|
|
||||||
queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!")
|
|
||||||
}
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AnalyzeOptions: OptionsProtocol {
|
|
||||||
let paths: [String]
|
|
||||||
let configurationFiles: [String]
|
|
||||||
let strict: Bool
|
|
||||||
let lenient: Bool
|
|
||||||
let forceExclude: Bool
|
|
||||||
let excludeByPrefix: Bool
|
|
||||||
let useScriptInputFiles: Bool
|
|
||||||
let benchmark: Bool
|
|
||||||
let reporter: String
|
|
||||||
let quiet: Bool
|
|
||||||
let enableAllRules: Bool
|
|
||||||
let autocorrect: Bool
|
|
||||||
let compilerLogPath: String
|
|
||||||
let compileCommands: String
|
|
||||||
|
|
||||||
// swiftlint:disable line_length
|
|
||||||
static func create(_ path: String) -> (_ configurationFiles: [String]) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ forceExclude: Bool) -> (_ excludeByPrefix: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ enableAllRules: Bool) -> (_ autocorrect: Bool) -> (_ compilerLogPath: String) -> (_ compileCommands: String) -> (_ paths: [String]) -> AnalyzeOptions {
|
|
||||||
return { configurationFiles in { strict in { lenient in { forceExclude in { excludeByPrefix in { useScriptInputFiles in { benchmark in { reporter in { quiet in { enableAllRules in { autocorrect in { compilerLogPath in { compileCommands in { paths in
|
|
||||||
let allPaths: [String]
|
|
||||||
if !path.isEmpty {
|
|
||||||
allPaths = [path]
|
|
||||||
} else {
|
|
||||||
allPaths = paths
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.init(paths: allPaths, configurationFiles: configurationFiles, strict: strict, lenient: lenient, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, enableAllRules: enableAllRules, autocorrect: autocorrect, compilerLogPath: compilerLogPath, compileCommands: compileCommands)
|
|
||||||
// swiftlint:enable line_length
|
|
||||||
}}}}}}}}}}}}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func evaluate(_ mode: CommandMode) -> Result<AnalyzeOptions, CommandantError<CommandantError<()>>> {
|
|
||||||
return create
|
|
||||||
<*> mode <| pathOption(action: "analyze")
|
|
||||||
<*> mode <| configOption
|
|
||||||
<*> mode <| Option(key: "strict", defaultValue: false,
|
|
||||||
usage: "upgrades warnings to serious violations (errors)")
|
|
||||||
<*> mode <| Option(key: "lenient", defaultValue: false,
|
|
||||||
usage: "downgrades serious violations to warnings, warning threshold is disabled")
|
|
||||||
<*> mode <| Option(key: "force-exclude", defaultValue: false,
|
|
||||||
usage: "exclude files in config `excluded` even if their paths are explicitly specified")
|
|
||||||
<*> mode <| useAlternativeExcludingOption
|
|
||||||
<*> mode <| useScriptInputFilesOption
|
|
||||||
<*> mode <| Option(key: "benchmark", defaultValue: false,
|
|
||||||
usage: "save benchmarks to benchmark_files.txt " +
|
|
||||||
"and benchmark_rules.txt")
|
|
||||||
<*> mode <| Option(key: "reporter", defaultValue: "",
|
|
||||||
usage: "the reporter used to log errors and warnings")
|
|
||||||
<*> mode <| quietOption(action: "linting")
|
|
||||||
<*> mode <| Option(key: "enable-all-rules", defaultValue: false,
|
|
||||||
usage: "run all rules, even opt-in and disabled ones, ignoring `only_rules`")
|
|
||||||
<*> mode <| Option(key: "autocorrect", defaultValue: false,
|
|
||||||
usage: "correct violations whenever possible")
|
|
||||||
<*> mode <| Option(key: "compiler-log-path", defaultValue: "",
|
|
||||||
usage: "the path of the full xcodebuild log to use when linting AnalyzerRules")
|
|
||||||
<*> mode <| Option(key: "compile-commands", defaultValue: "",
|
|
||||||
usage: "the path of a compilation database to use when linting AnalyzerRules")
|
|
||||||
// This should go last to avoid eating other args
|
|
||||||
<*> mode <| pathsArgument(action: "analyze")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
import Commandant
|
|
||||||
import SwiftLintFramework
|
|
||||||
|
|
||||||
struct AutoCorrectCommand: CommandProtocol {
|
|
||||||
let verb = "autocorrect"
|
|
||||||
let function = "Automatically correct warnings and errors"
|
|
||||||
|
|
||||||
func run(_ options: AutoCorrectOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
let configuration = Configuration(options: options)
|
|
||||||
let storage = RuleStorage()
|
|
||||||
let visitor = options.visitor(with: configuration, storage: storage)
|
|
||||||
return configuration.visitLintableFiles(with: visitor, storage: storage).flatMap { files in
|
|
||||||
if !options.quiet {
|
|
||||||
let pluralSuffix = { (collection: [Any]) -> String in
|
|
||||||
return collection.count != 1 ? "s" : ""
|
|
||||||
}
|
|
||||||
queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!")
|
|
||||||
}
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AutoCorrectOptions: OptionsProtocol {
|
|
||||||
let paths: [String]
|
|
||||||
let configurationFiles: [String]
|
|
||||||
let useScriptInputFiles: Bool
|
|
||||||
let quiet: Bool
|
|
||||||
let forceExclude: Bool
|
|
||||||
let excludeByPrefix: Bool
|
|
||||||
let format: Bool
|
|
||||||
let cachePath: String
|
|
||||||
let ignoreCache: Bool
|
|
||||||
|
|
||||||
// swiftlint:disable line_length
|
|
||||||
static func create(_ path: String) -> (_ configurationFiles: [String]) -> (_ useScriptInputFiles: Bool) -> (_ quiet: Bool) -> (_ forceExclude: Bool) ->
|
|
||||||
(_ excludeByPrefix: Bool) -> (_ format: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ paths: [String]) -> AutoCorrectOptions {
|
|
||||||
return { configurationFiles in { useScriptInputFiles in { quiet in { forceExclude in { excludeByPrefix in { format in { cachePath in { ignoreCache in { paths in
|
|
||||||
let allPaths: [String]
|
|
||||||
if !path.isEmpty {
|
|
||||||
allPaths = [path]
|
|
||||||
} else {
|
|
||||||
allPaths = paths
|
|
||||||
}
|
|
||||||
return self.init(paths: allPaths, configurationFiles: configurationFiles, useScriptInputFiles: useScriptInputFiles, quiet: quiet, forceExclude: forceExclude,
|
|
||||||
excludeByPrefix: excludeByPrefix, format: format, cachePath: cachePath, ignoreCache: ignoreCache)
|
|
||||||
// swiftlint:enable line_length
|
|
||||||
}}}}}}}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func evaluate(_ mode: CommandMode) -> Result<AutoCorrectOptions, CommandantError<CommandantError<()>>> {
|
|
||||||
return create
|
|
||||||
<*> mode <| pathOption(action: "correct")
|
|
||||||
<*> mode <| configOption
|
|
||||||
<*> mode <| useScriptInputFilesOption
|
|
||||||
<*> mode <| quietOption(action: "correcting")
|
|
||||||
<*> mode <| Option(key: "force-exclude", defaultValue: false,
|
|
||||||
usage: "exclude files in config `excluded` even if their paths are explicitly specified")
|
|
||||||
<*> mode <| useAlternativeExcludingOption
|
|
||||||
<*> mode <| Option(key: "format", defaultValue: false,
|
|
||||||
usage: "should reformat the Swift files")
|
|
||||||
<*> mode <| Option(key: "cache-path", defaultValue: "",
|
|
||||||
usage: "the directory of the cache used when correcting")
|
|
||||||
<*> mode <| Option(key: "no-cache", defaultValue: false,
|
|
||||||
usage: "ignore cache when correcting")
|
|
||||||
// This should go last to avoid eating other args
|
|
||||||
<*> mode <| pathsArgument(action: "correct")
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func visitor(with configuration: Configuration, storage: RuleStorage) -> LintableFilesVisitor {
|
|
||||||
let cache = ignoreCache ? nil : LinterCache(configuration: configuration)
|
|
||||||
return LintableFilesVisitor(paths: paths, action: "Correcting", useSTDIN: false, quiet: quiet,
|
|
||||||
useScriptInputFiles: useScriptInputFiles, forceExclude: forceExclude,
|
|
||||||
useExcludingByPrefix: excludeByPrefix, cache: cache, parallel: true,
|
|
||||||
allowZeroLintableFiles: configuration.allowZeroLintableFiles) { linter in
|
|
||||||
if self.format {
|
|
||||||
switch configuration.indentation {
|
|
||||||
case .tabs:
|
|
||||||
linter.format(useTabs: true, indentWidth: 4)
|
|
||||||
case .spaces(let count):
|
|
||||||
linter.format(useTabs: false, indentWidth: count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let corrections = linter.correct(using: storage)
|
|
||||||
if !corrections.isEmpty && !self.quiet {
|
|
||||||
let correctionLogs = corrections.map({ $0.consoleDescription })
|
|
||||||
queuedPrint(correctionLogs.joined(separator: "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import ArgumentParser
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct Docs: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(
|
||||||
|
abstract: "Open SwiftLint documentation website in the default web browser"
|
||||||
|
)
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
open(URL(string: "https://realm.github.io/SwiftLint")!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func open(_ url: URL) {
|
||||||
|
let process = Process()
|
||||||
|
#if os(Linux)
|
||||||
|
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
||||||
|
let command = "xdg-open"
|
||||||
|
process.arguments = [command, url.absoluteString]
|
||||||
|
try? process.run()
|
||||||
|
#else
|
||||||
|
process.launchPath = "/usr/bin/env"
|
||||||
|
let command = "open"
|
||||||
|
process.arguments = [command, url.absoluteString]
|
||||||
|
process.launch()
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import ArgumentParser
|
||||||
|
import Foundation
|
||||||
|
import SwiftLintFramework
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct GenerateDocs: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(abstract: "Generates markdown documentation for all rules")
|
||||||
|
|
||||||
|
@Option(help: "The directory where the documentation should be saved")
|
||||||
|
var path = "rule_docs"
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
try RuleListDocumentation(primaryRuleList)
|
||||||
|
.write(to: URL(fileURLWithPath: path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
import Commandant
|
|
||||||
import Foundation
|
|
||||||
import SwiftLintFramework
|
|
||||||
|
|
||||||
struct GenerateDocsCommand: CommandProtocol {
|
|
||||||
let verb = "generate-docs"
|
|
||||||
let function = "Generates markdown documentation for all rules"
|
|
||||||
|
|
||||||
func run(_ options: GenerateDocsOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
let docs = RuleListDocumentation(primaryRuleList)
|
|
||||||
do {
|
|
||||||
try docs.write(to: URL(fileURLWithPath: options.path))
|
|
||||||
} catch {
|
|
||||||
return .failure(.usageError(description: error.localizedDescription))
|
|
||||||
}
|
|
||||||
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GenerateDocsOptions: OptionsProtocol {
|
|
||||||
let path: String
|
|
||||||
|
|
||||||
static func create(_ path: String) -> GenerateDocsOptions {
|
|
||||||
return self.init(path: path)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func evaluate(_ mode: CommandMode) -> Result<GenerateDocsOptions, CommandantError<CommandantError<()>>> {
|
|
||||||
return create
|
|
||||||
<*> mode <| Option(key: "path", defaultValue: "rule_docs",
|
|
||||||
usage: "the directory where the documentation should be saved. defaults to `rule_docs`.")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct Lint: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(abstract: "Print lint warnings and errors")
|
||||||
|
|
||||||
|
@OptionGroup
|
||||||
|
var common: LintOrAnalyzeArguments
|
||||||
|
@Option(help: pathOptionDescription(for: .lint))
|
||||||
|
var path: String?
|
||||||
|
@Flag(help: "Lint standard input.")
|
||||||
|
var useSTDIN = false
|
||||||
|
@Flag(help: quietOptionDescription(for: .lint))
|
||||||
|
var quiet = false
|
||||||
|
@Option(help: "The directory of the cache used when linting.")
|
||||||
|
var cachePath: String?
|
||||||
|
@Flag(help: "Ignore cache when linting.")
|
||||||
|
var noCache = false
|
||||||
|
@Flag(help: "Run all rules, even opt-in and disabled ones, ignoring `only_rules`.")
|
||||||
|
var enableAllRules = false
|
||||||
|
@Argument(help: pathsArgumentDescription(for: .lint))
|
||||||
|
var paths = [String]()
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
let allPaths: [String]
|
||||||
|
if let path = path {
|
||||||
|
allPaths = [path]
|
||||||
|
} else if !paths.isEmpty {
|
||||||
|
allPaths = paths
|
||||||
|
} else {
|
||||||
|
allPaths = [""] // Lint files in current working directory if no paths were specified.
|
||||||
|
}
|
||||||
|
let options = LintOrAnalyzeOptions(
|
||||||
|
mode: .lint,
|
||||||
|
paths: allPaths,
|
||||||
|
useSTDIN: useSTDIN,
|
||||||
|
configurationFiles: common.config,
|
||||||
|
strict: common.leniency == .strict,
|
||||||
|
lenient: common.leniency == .lenient,
|
||||||
|
forceExclude: common.forceExclude,
|
||||||
|
useExcludingByPrefix: common.useAlternativeExcluding,
|
||||||
|
useScriptInputFiles: common.useScriptInputFiles,
|
||||||
|
benchmark: common.benchmark,
|
||||||
|
reporter: common.reporter,
|
||||||
|
quiet: quiet,
|
||||||
|
cachePath: cachePath,
|
||||||
|
ignoreCache: noCache,
|
||||||
|
enableAllRules: enableAllRules,
|
||||||
|
autocorrect: common.fix,
|
||||||
|
compilerLogPath: nil,
|
||||||
|
compileCommands: nil
|
||||||
|
)
|
||||||
|
let result = LintOrAnalyzeCommand.run(options)
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
return
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
import Commandant
|
|
||||||
|
|
||||||
struct LintCommand: CommandProtocol {
|
|
||||||
let verb = "lint"
|
|
||||||
let function = "Print lint warnings and errors (default command)"
|
|
||||||
|
|
||||||
func run(_ options: LintOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
return LintOrAnalyzeCommand.run(LintOrAnalyzeOptions(options))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LintOptions: OptionsProtocol {
|
|
||||||
let paths: [String]
|
|
||||||
let useSTDIN: Bool
|
|
||||||
let configurationFiles: [String]
|
|
||||||
let strict: Bool
|
|
||||||
let lenient: Bool
|
|
||||||
let forceExclude: Bool
|
|
||||||
let excludeByPrefix: Bool
|
|
||||||
let useScriptInputFiles: Bool
|
|
||||||
let benchmark: Bool
|
|
||||||
let reporter: String
|
|
||||||
let quiet: Bool
|
|
||||||
let cachePath: String
|
|
||||||
let ignoreCache: Bool
|
|
||||||
let enableAllRules: Bool
|
|
||||||
|
|
||||||
// swiftlint:disable line_length
|
|
||||||
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFiles: [String]) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ forceExclude: Bool) -> (_ excludeByPrefix: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> (_ paths: [String]) -> LintOptions {
|
|
||||||
return { useSTDIN in { configurationFiles in { strict in { lenient in { forceExclude in { excludeByPrefix in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in { paths in
|
|
||||||
let allPaths: [String]
|
|
||||||
if !path.isEmpty {
|
|
||||||
allPaths = [path]
|
|
||||||
} else {
|
|
||||||
allPaths = paths
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.init(paths: allPaths, useSTDIN: useSTDIN, configurationFiles: configurationFiles, strict: strict, lenient: lenient, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules)
|
|
||||||
// swiftlint:enable line_length
|
|
||||||
}}}}}}}}}}}}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func evaluate(_ mode: CommandMode) -> Result<LintOptions, CommandantError<CommandantError<()>>> {
|
|
||||||
return create
|
|
||||||
<*> mode <| pathOption(action: "lint")
|
|
||||||
<*> mode <| Option(key: "use-stdin", defaultValue: false,
|
|
||||||
usage: "lint standard input")
|
|
||||||
<*> mode <| configOption
|
|
||||||
<*> mode <| Option(key: "strict", defaultValue: false,
|
|
||||||
usage: "upgrades warnings to serious violations (errors)")
|
|
||||||
<*> mode <| Option(key: "lenient", defaultValue: false,
|
|
||||||
usage: "downgrades serious violations to warnings, warning threshold is disabled")
|
|
||||||
<*> mode <| Option(key: "force-exclude", defaultValue: false,
|
|
||||||
usage: "exclude files in config `excluded` even if their paths are explicitly specified")
|
|
||||||
<*> mode <| useAlternativeExcludingOption
|
|
||||||
<*> mode <| useScriptInputFilesOption
|
|
||||||
<*> mode <| Option(key: "benchmark", defaultValue: false,
|
|
||||||
usage: "save benchmarks to benchmark_files.txt " +
|
|
||||||
"and benchmark_rules.txt")
|
|
||||||
<*> mode <| Option(key: "reporter", defaultValue: "",
|
|
||||||
usage: "the reporter used to log errors and warnings")
|
|
||||||
<*> mode <| quietOption(action: "linting")
|
|
||||||
<*> mode <| Option(key: "cache-path", defaultValue: "",
|
|
||||||
usage: "the directory of the cache used when linting")
|
|
||||||
<*> mode <| Option(key: "no-cache", defaultValue: false,
|
|
||||||
usage: "ignore cache when linting")
|
|
||||||
<*> mode <| Option(key: "enable-all-rules", defaultValue: false,
|
|
||||||
usage: "run all rules, even opt-in and disabled ones, ignoring `only_rules`")
|
|
||||||
// This should go last to avoid eating other args
|
|
||||||
<*> mode <| pathsArgument(action: "lint")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
import ArgumentParser
|
||||||
|
#if canImport(Darwin)
|
||||||
|
import Darwin
|
||||||
|
#elseif canImport(Glibc)
|
||||||
|
import Glibc
|
||||||
|
#else
|
||||||
|
#error("Unsupported platform")
|
||||||
|
#endif
|
||||||
|
import Foundation
|
||||||
|
import SwiftLintFramework
|
||||||
|
import SwiftyTextTable
|
||||||
|
|
||||||
|
enum RuleEnablementOptions: String, EnumerableFlag {
|
||||||
|
case enabled, disabled
|
||||||
|
|
||||||
|
static func name(for value: RuleEnablementOptions) -> NameSpecification {
|
||||||
|
return .shortAndLong
|
||||||
|
}
|
||||||
|
|
||||||
|
static func help(for value: RuleEnablementOptions) -> ArgumentHelp? {
|
||||||
|
return "Only show \(value.rawValue) rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct Rules: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(abstract: "Display the list of rules and their identifiers")
|
||||||
|
|
||||||
|
@Option(help: "The path to a SwiftLint configuration file")
|
||||||
|
var config: String?
|
||||||
|
@Flag(exclusivity: .exclusive)
|
||||||
|
var ruleEnablement: RuleEnablementOptions?
|
||||||
|
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
||||||
|
var correctable = false
|
||||||
|
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
||||||
|
var verbose = false
|
||||||
|
@Argument(help: "The rule identifier to display description for")
|
||||||
|
var ruleID: String?
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
if let ruleID = ruleID {
|
||||||
|
guard let rule = primaryRuleList.list[ruleID] else {
|
||||||
|
throw SwiftLintError.usageError(description: "No rule with identifier: \(ruleID)")
|
||||||
|
}
|
||||||
|
|
||||||
|
rule.description.printDescription()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||||
|
let rules = ruleList(configuration: configuration)
|
||||||
|
let table = TextTable(ruleList: rules, configuration: configuration, verbose: verbose)
|
||||||
|
print(table.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ruleList(configuration: Configuration) -> RuleList {
|
||||||
|
guard ruleEnablement != nil || correctable else {
|
||||||
|
return primaryRuleList
|
||||||
|
}
|
||||||
|
|
||||||
|
let filtered: [Rule.Type] = primaryRuleList.list.compactMap { ruleID, ruleType in
|
||||||
|
let configuredRule = configuration.rules.first { rule in
|
||||||
|
return type(of: rule).description.identifier == ruleID
|
||||||
|
}
|
||||||
|
|
||||||
|
if ruleEnablement == .enabled && configuredRule == nil {
|
||||||
|
return nil
|
||||||
|
} else if ruleEnablement == .disabled && configuredRule != nil {
|
||||||
|
return nil
|
||||||
|
} else if correctable && !(configuredRule is CorrectableRule) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleType
|
||||||
|
}
|
||||||
|
|
||||||
|
return RuleList(rules: filtered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension RuleDescription {
|
||||||
|
func printDescription() {
|
||||||
|
print("\(consoleDescription)")
|
||||||
|
|
||||||
|
guard !triggeringExamples.isEmpty else { return }
|
||||||
|
|
||||||
|
func indent(_ string: String) -> String {
|
||||||
|
return string.components(separatedBy: "\n")
|
||||||
|
.map { " \($0)" }
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
print("\nTriggering Examples (violation is marked with '↓'):")
|
||||||
|
for (index, example) in triggeringExamples.enumerated() {
|
||||||
|
print("\nExample #\(index + 1)\n\n\(indent(example.code))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SwiftyTextTable
|
||||||
|
|
||||||
|
private extension TextTable {
|
||||||
|
init(ruleList: RuleList, configuration: Configuration, verbose: Bool) {
|
||||||
|
let columns = [
|
||||||
|
TextTableColumn(header: "identifier"),
|
||||||
|
TextTableColumn(header: "opt-in"),
|
||||||
|
TextTableColumn(header: "correctable"),
|
||||||
|
TextTableColumn(header: "enabled in your config"),
|
||||||
|
TextTableColumn(header: "kind"),
|
||||||
|
TextTableColumn(header: "analyzer"),
|
||||||
|
TextTableColumn(header: "configuration")
|
||||||
|
]
|
||||||
|
self.init(columns: columns)
|
||||||
|
let sortedRules = ruleList.list.sorted { $0.0 < $1.0 }
|
||||||
|
func truncate(_ string: String) -> String {
|
||||||
|
let stringWithNoNewlines = string.replacingOccurrences(of: "\n", with: "\\n")
|
||||||
|
let minWidth = "configuration".count - "...".count
|
||||||
|
let configurationStartColumn = 124
|
||||||
|
let maxWidth = verbose ? Int.max : Terminal.currentWidth()
|
||||||
|
let truncatedEndIndex = stringWithNoNewlines.index(
|
||||||
|
stringWithNoNewlines.startIndex,
|
||||||
|
offsetBy: max(minWidth, maxWidth - configurationStartColumn),
|
||||||
|
limitedBy: stringWithNoNewlines.endIndex
|
||||||
|
)
|
||||||
|
if let truncatedEndIndex = truncatedEndIndex {
|
||||||
|
return stringWithNoNewlines[..<truncatedEndIndex] + "..."
|
||||||
|
}
|
||||||
|
return stringWithNoNewlines
|
||||||
|
}
|
||||||
|
for (ruleID, ruleType) in sortedRules {
|
||||||
|
let rule = ruleType.init()
|
||||||
|
let configuredRule = configuration.rules.first { rule in
|
||||||
|
guard type(of: rule).description.identifier == ruleID else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard let customRules = rule as? CustomRules else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !customRules.configuration.customRuleConfigurations.isEmpty
|
||||||
|
}
|
||||||
|
addRow(values: [
|
||||||
|
ruleID,
|
||||||
|
(rule is OptInRule) ? "yes" : "no",
|
||||||
|
(rule is CorrectableRule) ? "yes" : "no",
|
||||||
|
configuredRule != nil ? "yes" : "no",
|
||||||
|
ruleType.description.kind.rawValue,
|
||||||
|
(rule is AnalyzerRule) ? "yes" : "no",
|
||||||
|
truncate((configuredRule ?? rule).configurationDescription)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Terminal {
|
||||||
|
static func currentWidth() -> Int {
|
||||||
|
var size = winsize()
|
||||||
|
#if os(Linux)
|
||||||
|
_ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size)
|
||||||
|
#else
|
||||||
|
_ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)
|
||||||
|
#endif
|
||||||
|
return Int(size.ws_col)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,182 +0,0 @@
|
||||||
import Commandant
|
|
||||||
#if canImport(Darwin)
|
|
||||||
import Darwin
|
|
||||||
#elseif canImport(Glibc)
|
|
||||||
import Glibc
|
|
||||||
#else
|
|
||||||
#error("Unsupported platform")
|
|
||||||
#endif
|
|
||||||
import SwiftLintFramework
|
|
||||||
import SwiftyTextTable
|
|
||||||
|
|
||||||
private func print(ruleDescription desc: RuleDescription) {
|
|
||||||
print("\(desc.consoleDescription)")
|
|
||||||
|
|
||||||
if !desc.triggeringExamples.isEmpty {
|
|
||||||
func indent(_ string: String) -> String {
|
|
||||||
return string.components(separatedBy: "\n")
|
|
||||||
.map { " \($0)" }
|
|
||||||
.joined(separator: "\n")
|
|
||||||
}
|
|
||||||
print("\nTriggering Examples (violation is marked with '↓'):")
|
|
||||||
for (index, example) in desc.triggeringExamples.enumerated() {
|
|
||||||
print("\nExample #\(index + 1)\n\n\(indent(example.code))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RulesCommand: CommandProtocol {
|
|
||||||
let verb = "rules"
|
|
||||||
let function = "Display the list of rules and their identifiers"
|
|
||||||
|
|
||||||
func run(_ options: RulesOptions) -> Result<(), CommandantError<()>> {
|
|
||||||
if let ruleID = options.ruleID {
|
|
||||||
guard let rule = primaryRuleList.list[ruleID] else {
|
|
||||||
return .failure(.usageError(description: "No rule with identifier: \(ruleID)"))
|
|
||||||
}
|
|
||||||
|
|
||||||
print(ruleDescription: rule.description)
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.onlyDisabledRules && options.onlyEnabledRules {
|
|
||||||
return .failure(.usageError(description: "You can't use --disabled and --enabled at the same time."))
|
|
||||||
}
|
|
||||||
|
|
||||||
let configuration = Configuration(options: options)
|
|
||||||
let rules = ruleList(for: options, configuration: configuration)
|
|
||||||
|
|
||||||
print(TextTable(ruleList: rules, configuration: configuration, verbose: options.verbose).render())
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
|
|
||||||
private func ruleList(for options: RulesOptions, configuration: Configuration) -> RuleList {
|
|
||||||
guard options.onlyEnabledRules || options.onlyDisabledRules || options.onlyCorrectableRules else {
|
|
||||||
return primaryRuleList
|
|
||||||
}
|
|
||||||
|
|
||||||
let filtered: [Rule.Type] = primaryRuleList.list.compactMap { ruleID, ruleType in
|
|
||||||
let configuredRule = configuration.rules.first { rule in
|
|
||||||
return type(of: rule).description.identifier == ruleID
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.onlyEnabledRules && configuredRule == nil {
|
|
||||||
return nil
|
|
||||||
} else if options.onlyDisabledRules && configuredRule != nil {
|
|
||||||
return nil
|
|
||||||
} else if options.onlyCorrectableRules && !(configuredRule is CorrectableRule) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ruleType
|
|
||||||
}
|
|
||||||
|
|
||||||
return RuleList(rules: filtered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RulesOptions: OptionsProtocol {
|
|
||||||
fileprivate let ruleID: String?
|
|
||||||
let configurationFiles: [String]
|
|
||||||
fileprivate let onlyEnabledRules: Bool
|
|
||||||
fileprivate let onlyDisabledRules: Bool
|
|
||||||
fileprivate let onlyCorrectableRules: Bool
|
|
||||||
fileprivate let verbose: Bool
|
|
||||||
|
|
||||||
// swiftlint:disable line_length
|
|
||||||
static func create(_ configurationFiles: [String]) -> (_ ruleID: String) -> (_ onlyEnabledRules: Bool) -> (_ onlyDisabledRules: Bool) -> (_ onlyCorrectableRules: Bool) -> (_ verbose: Bool) -> RulesOptions {
|
|
||||||
return { ruleID in { onlyEnabledRules in { onlyDisabledRules in { onlyCorrectableRules in { verbose in
|
|
||||||
self.init(ruleID: (ruleID.isEmpty ? nil : ruleID),
|
|
||||||
configurationFiles: configurationFiles,
|
|
||||||
onlyEnabledRules: onlyEnabledRules,
|
|
||||||
onlyDisabledRules: onlyDisabledRules,
|
|
||||||
onlyCorrectableRules: onlyCorrectableRules,
|
|
||||||
verbose: verbose)
|
|
||||||
}}}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func evaluate(_ mode: CommandMode) -> Result<RulesOptions, CommandantError<CommandantError<()>>> {
|
|
||||||
return create
|
|
||||||
<*> mode <| configOption
|
|
||||||
<*> mode <| Argument(defaultValue: "",
|
|
||||||
usage: "the rule identifier to display description for")
|
|
||||||
<*> mode <| Switch(flag: "e",
|
|
||||||
key: "enabled",
|
|
||||||
usage: "only display enabled rules")
|
|
||||||
<*> mode <| Switch(flag: "d",
|
|
||||||
key: "disabled",
|
|
||||||
usage: "only display disabled rules")
|
|
||||||
<*> mode <| Switch(flag: "c",
|
|
||||||
key: "correctable",
|
|
||||||
usage: "only display correctable rules")
|
|
||||||
<*> mode <| Switch(flag: "v",
|
|
||||||
key: "verbose",
|
|
||||||
usage: "display full configuration detail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - SwiftyTextTable
|
|
||||||
|
|
||||||
extension TextTable {
|
|
||||||
init(ruleList: RuleList, configuration: Configuration, verbose: Bool) {
|
|
||||||
let columns = [
|
|
||||||
TextTableColumn(header: "identifier"),
|
|
||||||
TextTableColumn(header: "opt-in"),
|
|
||||||
TextTableColumn(header: "correctable"),
|
|
||||||
TextTableColumn(header: "enabled in your config"),
|
|
||||||
TextTableColumn(header: "kind"),
|
|
||||||
TextTableColumn(header: "analyzer"),
|
|
||||||
TextTableColumn(header: "configuration")
|
|
||||||
]
|
|
||||||
self.init(columns: columns)
|
|
||||||
let sortedRules = ruleList.list.sorted { $0.0 < $1.0 }
|
|
||||||
func truncate(_ string: String) -> String {
|
|
||||||
let stringWithNoNewlines = string.replacingOccurrences(of: "\n", with: "\\n")
|
|
||||||
let minWidth = "configuration".count - "...".count
|
|
||||||
let configurationStartColumn = 124
|
|
||||||
let maxWidth = verbose ? Int.max : Terminal.currentWidth()
|
|
||||||
let truncatedEndIndex = stringWithNoNewlines.index(
|
|
||||||
stringWithNoNewlines.startIndex,
|
|
||||||
offsetBy: max(minWidth, maxWidth - configurationStartColumn),
|
|
||||||
limitedBy: stringWithNoNewlines.endIndex
|
|
||||||
)
|
|
||||||
if let truncatedEndIndex = truncatedEndIndex {
|
|
||||||
return stringWithNoNewlines[..<truncatedEndIndex] + "..."
|
|
||||||
}
|
|
||||||
return stringWithNoNewlines
|
|
||||||
}
|
|
||||||
for (ruleID, ruleType) in sortedRules {
|
|
||||||
let rule = ruleType.init()
|
|
||||||
let configuredRule = configuration.rules.first { rule in
|
|
||||||
guard type(of: rule).description.identifier == ruleID else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
guard let customRules = rule as? CustomRules else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !customRules.configuration.customRuleConfigurations.isEmpty
|
|
||||||
}
|
|
||||||
addRow(values: [
|
|
||||||
ruleID,
|
|
||||||
(rule is OptInRule) ? "yes" : "no",
|
|
||||||
(rule is CorrectableRule) ? "yes" : "no",
|
|
||||||
configuredRule != nil ? "yes" : "no",
|
|
||||||
ruleType.description.kind.rawValue,
|
|
||||||
(rule is AnalyzerRule) ? "yes" : "no",
|
|
||||||
truncate((configuredRule ?? rule).configurationDescription)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Terminal {
|
|
||||||
static func currentWidth() -> Int {
|
|
||||||
var size = winsize()
|
|
||||||
#if os(Linux)
|
|
||||||
_ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size)
|
|
||||||
#else
|
|
||||||
_ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)
|
|
||||||
#endif
|
|
||||||
return Int(size.ws_col)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import Commandant
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct ShowDocsCommand: CommandProtocol {
|
|
||||||
let verb = "docs"
|
|
||||||
let function = "Open SwiftLint Docs on web browser"
|
|
||||||
|
|
||||||
func run(_ options: NoOptions<CommandantError<()>>) -> Result<(), CommandantError<()>> {
|
|
||||||
let url = URL(string: "https://realm.github.io/SwiftLint")!
|
|
||||||
open(url)
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension ShowDocsCommand {
|
|
||||||
func open(_ url: URL) {
|
|
||||||
let process = Process()
|
|
||||||
#if os(Linux)
|
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
|
||||||
let command = "xdg-open"
|
|
||||||
process.arguments = [command, url.absoluteString]
|
|
||||||
try? process.run()
|
|
||||||
#else
|
|
||||||
process.launchPath = "/usr/bin/env"
|
|
||||||
let command = "open"
|
|
||||||
process.arguments = [command, url.absoluteString]
|
|
||||||
process.launch()
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import ArgumentParser
|
||||||
|
import SwiftLintFramework
|
||||||
|
|
||||||
|
struct SwiftLint: ParsableCommand {
|
||||||
|
static var configuration = CommandConfiguration(
|
||||||
|
commandName: "swiftlint",
|
||||||
|
abstract: "A tool to enforce Swift style and conventions.",
|
||||||
|
version: Version.value,
|
||||||
|
subcommands: [
|
||||||
|
Analyze.self,
|
||||||
|
Docs.self,
|
||||||
|
GenerateDocs.self,
|
||||||
|
Lint.self,
|
||||||
|
Rules.self,
|
||||||
|
Version.self
|
||||||
|
],
|
||||||
|
defaultSubcommand: Lint.self
|
||||||
|
)
|
||||||
|
|
||||||
|
// Temporary convenience to help migrate users to the new command.
|
||||||
|
static func mainHandlingDeprecatedCommands(_ arguments: [String]? = nil) {
|
||||||
|
let argumentsToCheck = arguments ?? Array(CommandLine.arguments.dropFirst())
|
||||||
|
guard argumentsToCheck.first == "autocorrect" else {
|
||||||
|
main(arguments)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queuedPrintError(
|
||||||
|
"""
|
||||||
|
The `swiftlint autocorrect` command is no longer available.
|
||||||
|
Please use `swiftlint --fix` instead.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
var newArguments = argumentsToCheck
|
||||||
|
newArguments[0] = "--fix"
|
||||||
|
main(newArguments)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ArgumentParser
|
||||||
|
import SwiftLintFramework
|
||||||
|
|
||||||
|
extension SwiftLint {
|
||||||
|
struct Version: ParsableCommand {
|
||||||
|
static let configuration = CommandConfiguration(abstract: "Display the current version of SwiftLint")
|
||||||
|
|
||||||
|
static var value: String { SwiftLintFramework.Version.current.value }
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
print(Self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
import Commandant
|
|
||||||
import SwiftLintFramework
|
|
||||||
|
|
||||||
struct VersionCommand: CommandProtocol {
|
|
||||||
let verb = "version"
|
|
||||||
let function = "Display the current version of SwiftLint"
|
|
||||||
|
|
||||||
func run(_ options: NoOptions<CommandantError<()>>) -> Result<(), CommandantError<()>> {
|
|
||||||
print(Version.current.value)
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Commandant
|
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import Foundation
|
import Foundation
|
||||||
import SourceKittenFramework
|
import SourceKittenFramework
|
||||||
|
@ -6,8 +5,8 @@ import SwiftLintFramework
|
||||||
|
|
||||||
private let indexIncrementerQueue = DispatchQueue(label: "io.realm.swiftlint.indexIncrementer")
|
private let indexIncrementerQueue = DispatchQueue(label: "io.realm.swiftlint.indexIncrementer")
|
||||||
|
|
||||||
private func scriptInputFiles() -> Result<[SwiftLintFile], CommandantError<()>> {
|
private func scriptInputFiles() -> Result<[SwiftLintFile], SwiftLintError> {
|
||||||
func getEnvironmentVariable(_ variable: String) -> Result<String, CommandantError<()>> {
|
func getEnvironmentVariable(_ variable: String) -> Result<String, SwiftLintError> {
|
||||||
let environment = ProcessInfo.processInfo.environment
|
let environment = ProcessInfo.processInfo.environment
|
||||||
if let value = environment[variable] {
|
if let value = environment[variable] {
|
||||||
return .success(value)
|
return .success(value)
|
||||||
|
@ -15,7 +14,7 @@ private func scriptInputFiles() -> Result<[SwiftLintFile], CommandantError<()>>
|
||||||
return .failure(.usageError(description: "Environment variable not set: \(variable)"))
|
return .failure(.usageError(description: "Environment variable not set: \(variable)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let count: Result<Int, CommandantError<()>> = {
|
let count: Result<Int, SwiftLintError> = {
|
||||||
let inputFileKey = "SCRIPT_INPUT_FILE_COUNT"
|
let inputFileKey = "SCRIPT_INPUT_FILE_COUNT"
|
||||||
guard let countString = ProcessInfo.processInfo.environment[inputFileKey] else {
|
guard let countString = ProcessInfo.processInfo.environment[inputFileKey] else {
|
||||||
return .failure(.usageError(description: "\(inputFileKey) variable not set"))
|
return .failure(.usageError(description: "\(inputFileKey) variable not set"))
|
||||||
|
@ -48,7 +47,7 @@ private func autoreleasepool<T>(block: () -> T) -> T { return block() }
|
||||||
|
|
||||||
extension Configuration {
|
extension Configuration {
|
||||||
func visitLintableFiles(with visitor: LintableFilesVisitor, storage: RuleStorage)
|
func visitLintableFiles(with visitor: LintableFilesVisitor, storage: RuleStorage)
|
||||||
-> Result<[SwiftLintFile], CommandantError<()>> {
|
-> Result<[SwiftLintFile], SwiftLintError> {
|
||||||
return getFiles(with: visitor)
|
return getFiles(with: visitor)
|
||||||
.flatMap { groupFiles($0, visitor: visitor) }
|
.flatMap { groupFiles($0, visitor: visitor) }
|
||||||
.map { linters(for: $0, visitor: visitor) }
|
.map { linters(for: $0, visitor: visitor) }
|
||||||
|
@ -59,7 +58,7 @@ extension Configuration {
|
||||||
|
|
||||||
private func groupFiles(_ files: [SwiftLintFile],
|
private func groupFiles(_ files: [SwiftLintFile],
|
||||||
visitor: LintableFilesVisitor)
|
visitor: LintableFilesVisitor)
|
||||||
-> Result<[Configuration: [SwiftLintFile]], CommandantError<()>> {
|
-> Result<[Configuration: [SwiftLintFile]], SwiftLintError> {
|
||||||
if files.isEmpty && !visitor.allowZeroLintableFiles {
|
if files.isEmpty && !visitor.allowZeroLintableFiles {
|
||||||
let errorMessage = "No lintable files found at paths: '\(visitor.paths.joined(separator: ", "))'"
|
let errorMessage = "No lintable files found at paths: '\(visitor.paths.joined(separator: ", "))'"
|
||||||
return .failure(.usageError(description: errorMessage))
|
return .failure(.usageError(description: errorMessage))
|
||||||
|
@ -188,7 +187,7 @@ extension Configuration {
|
||||||
return visitor.parallel ? linters.parallelMap(transform: visit) : linters.map(visit)
|
return visitor.parallel ? linters.parallelMap(transform: visit) : linters.map(visit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func getFiles(with visitor: LintableFilesVisitor) -> Result<[SwiftLintFile], CommandantError<()>> {
|
fileprivate func getFiles(with visitor: LintableFilesVisitor) -> Result<[SwiftLintFile], SwiftLintError> {
|
||||||
if visitor.useSTDIN {
|
if visitor.useSTDIN {
|
||||||
let stdinData = FileHandle.standardInput.readDataToEndOfFile()
|
let stdinData = FileHandle.standardInput.readDataToEndOfFile()
|
||||||
if let stdinString = String(data: stdinData, encoding: .utf8) {
|
if let stdinString = String(data: stdinData, encoding: .utf8) {
|
||||||
|
@ -225,20 +224,9 @@ extension Configuration {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: LintOrAnalyze Command
|
|
||||||
|
|
||||||
init(options: LintOrAnalyzeOptions) {
|
|
||||||
let cachePath = options.cachePath.isEmpty ? nil : options.cachePath
|
|
||||||
self.init(
|
|
||||||
configurationFiles: options.configurationFiles,
|
|
||||||
enableAllRules: options.enableAllRules,
|
|
||||||
cachePath: cachePath
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, storage: RuleStorage,
|
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, storage: RuleStorage,
|
||||||
visitorBlock: @escaping (CollectedLinter) -> Void)
|
visitorBlock: @escaping (CollectedLinter) -> Void)
|
||||||
-> Result<[SwiftLintFile], CommandantError<()>> {
|
-> Result<[SwiftLintFile], SwiftLintError> {
|
||||||
return LintableFilesVisitor.create(options,
|
return LintableFilesVisitor.create(options,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
allowZeroLintableFiles: allowZeroLintableFiles,
|
allowZeroLintableFiles: allowZeroLintableFiles,
|
||||||
|
@ -247,20 +235,13 @@ extension Configuration {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: AutoCorrect Command
|
// MARK: LintOrAnalyze Command
|
||||||
|
|
||||||
init(options: AutoCorrectOptions) {
|
init(options: LintOrAnalyzeOptions) {
|
||||||
let cachePath = options.cachePath.isEmpty ? nil : options.cachePath
|
|
||||||
self.init(
|
self.init(
|
||||||
configurationFiles: options.configurationFiles,
|
configurationFiles: options.configurationFiles,
|
||||||
cachePath: cachePath
|
enableAllRules: options.enableAllRules,
|
||||||
)
|
cachePath: options.cachePath
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Rules command
|
|
||||||
init(options: RulesOptions) {
|
|
||||||
self.init(
|
|
||||||
configurationFiles: options.configurationFiles
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ extension Reporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reporterFrom(optionsReporter: String, configuration: Configuration) -> Reporter.Type {
|
func reporterFrom(optionsReporter: String?, configuration: Configuration) -> Reporter.Type {
|
||||||
let string = optionsReporter.isEmpty ? configuration.reporter : optionsReporter
|
return reporterFrom(identifier: optionsReporter ?? configuration.reporter)
|
||||||
return reporterFrom(identifier: string)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import Commandant
|
|
||||||
|
|
||||||
func pathOption(action: String) -> Option<String> {
|
|
||||||
return Option(key: "path",
|
|
||||||
defaultValue: "",
|
|
||||||
usage: "the path to the file or directory to \(action)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathsArgument(action: String) -> Argument<[String]> {
|
|
||||||
return Argument(defaultValue: [""],
|
|
||||||
usage: "list of paths to the files or directories to \(action)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let configOption = Option(key: "config",
|
|
||||||
defaultValue: [String](),
|
|
||||||
usage: "the path to one or more SwiftLint configuration files, "
|
|
||||||
+ "evaluated as a parent-child hierarchy")
|
|
||||||
|
|
||||||
let useScriptInputFilesOption = Option(key: "use-script-input-files",
|
|
||||||
defaultValue: false,
|
|
||||||
usage: "read SCRIPT_INPUT_FILE* environment variables " +
|
|
||||||
"as files")
|
|
||||||
|
|
||||||
let useAlternativeExcludingOption = Option(key: "use-alternative-excluding",
|
|
||||||
defaultValue: false,
|
|
||||||
usage: "alternative exclude paths algorithm for `excluded`." +
|
|
||||||
"may speed up excluding for some cases")
|
|
||||||
|
|
||||||
func quietOption(action: String) -> Option<Bool> {
|
|
||||||
return Option(key: "quiet",
|
|
||||||
defaultValue: false,
|
|
||||||
usage: "don't print status logs like '\(action.capitalized) <file>' & " +
|
|
||||||
"'Done \(action)'")
|
|
||||||
}
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
enum LeniencyOptions: String, EnumerableFlag {
|
||||||
|
case strict, lenient
|
||||||
|
|
||||||
|
static func help(for value: LeniencyOptions) -> ArgumentHelp? {
|
||||||
|
switch value {
|
||||||
|
case .strict:
|
||||||
|
return "Upgrades warnings to serious violations (errors)."
|
||||||
|
case .lenient:
|
||||||
|
return "Downgrades serious violations to warnings, warning threshold is disabled."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Common Arguments
|
||||||
|
|
||||||
|
struct LintOrAnalyzeArguments: ParsableArguments {
|
||||||
|
@Option(help: "The path to one or more SwiftLint configuration files, evaluated as a parent-child hierarchy.")
|
||||||
|
var config = [String]()
|
||||||
|
@Flag(name: [.long, .customLong("autocorrect")], help: "Correct violations whenever possible.")
|
||||||
|
var fix = false
|
||||||
|
@Flag(help: "Use an alternative algorithm to exclude paths for `excluded`, which may be faster in some cases.")
|
||||||
|
var useAlternativeExcluding = false
|
||||||
|
@Flag(help: "Read SCRIPT_INPUT_FILE* environment variables as files.")
|
||||||
|
var useScriptInputFiles = false
|
||||||
|
@Flag(exclusivity: .exclusive)
|
||||||
|
var leniency: LeniencyOptions?
|
||||||
|
@Flag(help: "Exclude files in config `excluded` even if their paths are explicitly specified.")
|
||||||
|
var forceExclude = false
|
||||||
|
@Flag(help: "Save benchmarks to `benchmark_files.txt` and `benchmark_rules.txt`.")
|
||||||
|
var benchmark = false
|
||||||
|
@Option(help: "The reporter used to log errors and warnings.")
|
||||||
|
var reporter: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Common Argument Help
|
||||||
|
|
||||||
|
// It'd be great to be able to parameterize an `@OptionGroup` so we could move these options into
|
||||||
|
// `LintOrAnalyzeArguments`.
|
||||||
|
|
||||||
|
func pathOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||||
|
"The path to the file or directory to \(mode.imperative)."
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathsArgumentDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||||
|
"List of paths to the files or directories to \(mode.imperative)."
|
||||||
|
}
|
||||||
|
|
||||||
|
func quietOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||||
|
"Don't print status logs like '\(mode.verb.capitalized) <file>' & 'Done \(mode.verb)'."
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
import Commandant
|
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftLintFramework
|
import SwiftLintFramework
|
||||||
|
@ -6,6 +5,15 @@ import SwiftLintFramework
|
||||||
enum LintOrAnalyzeMode {
|
enum LintOrAnalyzeMode {
|
||||||
case lint, analyze
|
case lint, analyze
|
||||||
|
|
||||||
|
var imperative: String {
|
||||||
|
switch self {
|
||||||
|
case .lint:
|
||||||
|
return "lint"
|
||||||
|
case .analyze:
|
||||||
|
return "analyze"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var verb: String {
|
var verb: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .lint:
|
case .lint:
|
||||||
|
@ -17,7 +25,11 @@ enum LintOrAnalyzeMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LintOrAnalyzeCommand {
|
struct LintOrAnalyzeCommand {
|
||||||
static func run(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> {
|
static func run(_ options: LintOrAnalyzeOptions) -> Result<(), SwiftLintError> {
|
||||||
|
return options.autocorrect ? autocorrect(options) : lintOrAnalyze(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func lintOrAnalyze(_ options: LintOrAnalyzeOptions) -> Result<(), SwiftLintError> {
|
||||||
var fileBenchmark = Benchmark(name: "files")
|
var fileBenchmark = Benchmark(name: "files")
|
||||||
var ruleBenchmark = Benchmark(name: "rules")
|
var ruleBenchmark = Benchmark(name: "rules")
|
||||||
var violations = [StyleViolation]()
|
var violations = [StyleViolation]()
|
||||||
|
@ -125,6 +137,26 @@ struct LintOrAnalyzeCommand {
|
||||||
queuedFatalError("Invalid command line options: 'lenient' and 'strict' are mutually exclusive.")
|
queuedFatalError("Invalid command line options: 'lenient' and 'strict' are mutually exclusive.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func autocorrect(_ options: LintOrAnalyzeOptions) -> Result<(), SwiftLintError> {
|
||||||
|
let storage = RuleStorage()
|
||||||
|
let configuration = Configuration(options: options)
|
||||||
|
return configuration.visitLintableFiles(options: options, cache: nil, storage: storage) { linter in
|
||||||
|
let corrections = linter.correct(using: storage)
|
||||||
|
if !corrections.isEmpty && !options.quiet {
|
||||||
|
let correctionLogs = corrections.map({ $0.consoleDescription })
|
||||||
|
queuedPrint(correctionLogs.joined(separator: "\n"))
|
||||||
|
}
|
||||||
|
}.flatMap { files in
|
||||||
|
if !options.quiet {
|
||||||
|
let pluralSuffix = { (collection: [Any]) -> String in
|
||||||
|
return collection.count != 1 ? "s" : ""
|
||||||
|
}
|
||||||
|
queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!")
|
||||||
|
}
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LintOrAnalyzeOptions {
|
struct LintOrAnalyzeOptions {
|
||||||
|
@ -138,56 +170,14 @@ struct LintOrAnalyzeOptions {
|
||||||
let useExcludingByPrefix: Bool
|
let useExcludingByPrefix: Bool
|
||||||
let useScriptInputFiles: Bool
|
let useScriptInputFiles: Bool
|
||||||
let benchmark: Bool
|
let benchmark: Bool
|
||||||
let reporter: String
|
let reporter: String?
|
||||||
let quiet: Bool
|
let quiet: Bool
|
||||||
let cachePath: String
|
let cachePath: String?
|
||||||
let ignoreCache: Bool
|
let ignoreCache: Bool
|
||||||
let enableAllRules: Bool
|
let enableAllRules: Bool
|
||||||
let autocorrect: Bool
|
let autocorrect: Bool
|
||||||
let compilerLogPath: String
|
let compilerLogPath: String?
|
||||||
let compileCommands: String
|
let compileCommands: String?
|
||||||
|
|
||||||
init(_ options: LintOptions) {
|
|
||||||
mode = .lint
|
|
||||||
paths = options.paths
|
|
||||||
useSTDIN = options.useSTDIN
|
|
||||||
configurationFiles = options.configurationFiles
|
|
||||||
strict = options.strict
|
|
||||||
lenient = options.lenient
|
|
||||||
forceExclude = options.forceExclude
|
|
||||||
useExcludingByPrefix = options.excludeByPrefix
|
|
||||||
useScriptInputFiles = options.useScriptInputFiles
|
|
||||||
benchmark = options.benchmark
|
|
||||||
reporter = options.reporter
|
|
||||||
quiet = options.quiet
|
|
||||||
cachePath = options.cachePath
|
|
||||||
ignoreCache = options.ignoreCache
|
|
||||||
enableAllRules = options.enableAllRules
|
|
||||||
autocorrect = false
|
|
||||||
compilerLogPath = ""
|
|
||||||
compileCommands = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
init(_ options: AnalyzeOptions) {
|
|
||||||
mode = .analyze
|
|
||||||
paths = options.paths
|
|
||||||
useSTDIN = false
|
|
||||||
configurationFiles = options.configurationFiles
|
|
||||||
strict = options.strict
|
|
||||||
lenient = options.lenient
|
|
||||||
forceExclude = options.forceExclude
|
|
||||||
useExcludingByPrefix = options.excludeByPrefix
|
|
||||||
useScriptInputFiles = options.useScriptInputFiles
|
|
||||||
benchmark = options.benchmark
|
|
||||||
reporter = options.reporter
|
|
||||||
quiet = options.quiet
|
|
||||||
cachePath = ""
|
|
||||||
ignoreCache = true
|
|
||||||
enableAllRules = options.enableAllRules
|
|
||||||
autocorrect = options.autocorrect
|
|
||||||
compilerLogPath = options.compilerLogPath
|
|
||||||
compileCommands = options.compileCommands
|
|
||||||
}
|
|
||||||
|
|
||||||
var verb: String {
|
var verb: String {
|
||||||
if autocorrect {
|
if autocorrect {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Commandant
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SourceKittenFramework
|
import SourceKittenFramework
|
||||||
import SwiftLintFramework
|
import SwiftLintFramework
|
||||||
|
@ -96,7 +95,7 @@ struct LintableFilesVisitor {
|
||||||
cache: LinterCache?,
|
cache: LinterCache?,
|
||||||
allowZeroLintableFiles: Bool,
|
allowZeroLintableFiles: Bool,
|
||||||
block: @escaping (CollectedLinter) -> Void)
|
block: @escaping (CollectedLinter) -> Void)
|
||||||
-> Result<LintableFilesVisitor, CommandantError<()>> {
|
-> Result<LintableFilesVisitor, SwiftLintError> {
|
||||||
let compilerInvocations: CompilerInvocations?
|
let compilerInvocations: CompilerInvocations?
|
||||||
if options.mode == .lint {
|
if options.mode == .lint {
|
||||||
compilerInvocations = nil
|
compilerInvocations = nil
|
||||||
|
@ -141,9 +140,8 @@ struct LintableFilesVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func loadCompilerInvocations(_ options: LintOrAnalyzeOptions)
|
private static func loadCompilerInvocations(_ options: LintOrAnalyzeOptions)
|
||||||
-> Result<CompilerInvocations, CommandantError<()>> {
|
-> Result<CompilerInvocations, SwiftLintError> {
|
||||||
if !options.compilerLogPath.isEmpty {
|
if let path = options.compilerLogPath {
|
||||||
let path = options.compilerLogPath
|
|
||||||
guard let compilerInvocations = self.loadLogCompilerInvocations(path) else {
|
guard let compilerInvocations = self.loadLogCompilerInvocations(path) else {
|
||||||
return .failure(
|
return .failure(
|
||||||
.usageError(description: "Could not read compiler log at path: '\(path)'")
|
.usageError(description: "Could not read compiler log at path: '\(path)'")
|
||||||
|
@ -151,17 +149,14 @@ struct LintableFilesVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(.buildLog(compilerInvocations: compilerInvocations))
|
return .success(.buildLog(compilerInvocations: compilerInvocations))
|
||||||
} else if !options.compileCommands.isEmpty {
|
} else if let path = options.compileCommands {
|
||||||
let path = options.compileCommands
|
|
||||||
switch self.loadCompileCommands(path) {
|
switch self.loadCompileCommands(path) {
|
||||||
case .success(let compileCommands):
|
case .success(let compileCommands):
|
||||||
return .success(.compilationDatabase(compileCommands: compileCommands))
|
return .success(.compilationDatabase(compileCommands: compileCommands))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
return .failure(
|
return .failure(.usageError(
|
||||||
.usageError(
|
description: "Could not read compilation database at path: '\(path)' \(error.localizedDescription)"
|
||||||
description: "Could not read compilation database at path: '\(path)' \(error.description)"
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,14 +215,14 @@ struct LintableFilesVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CompileCommandsLoadError: Error {
|
private enum CompileCommandsLoadError: LocalizedError {
|
||||||
case nonExistentFile(String)
|
case nonExistentFile(String)
|
||||||
case malformedCommands(String)
|
case malformedCommands(String)
|
||||||
case malformedFile(String, Int)
|
case malformedFile(String, Int)
|
||||||
case malformedArguments(String, Int)
|
case malformedArguments(String, Int)
|
||||||
case missingFileInArguments(String, Int, [String])
|
case missingFileInArguments(String, Int, [String])
|
||||||
|
|
||||||
var description: String {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .nonExistentFile(path):
|
case let .nonExistentFile(path):
|
||||||
return "Could not read compile commands file at '\(path)'"
|
return "Could not read compile commands file at '\(path)'"
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SwiftLintError: LocalizedError {
|
||||||
|
case usageError(description: String)
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .usageError(let description):
|
||||||
|
return description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,15 @@
|
||||||
import Commandant
|
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import SwiftLintFramework
|
#if canImport(Darwin)
|
||||||
|
import Darwin
|
||||||
|
#elseif canImport(Glibc)
|
||||||
|
import Glibc
|
||||||
|
#else
|
||||||
|
#error("Unsupported platform")
|
||||||
|
#endif
|
||||||
|
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
let registry = CommandRegistry<CommandantError<()>>()
|
SwiftLint.mainHandlingDeprecatedCommands()
|
||||||
registry.register(LintCommand())
|
exit(EXIT_SUCCESS)
|
||||||
registry.register(AutoCorrectCommand())
|
|
||||||
registry.register(AnalyzeCommand())
|
|
||||||
registry.register(VersionCommand())
|
|
||||||
registry.register(RulesCommand())
|
|
||||||
registry.register(GenerateDocsCommand())
|
|
||||||
registry.register(ShowDocsCommand())
|
|
||||||
registry.register(HelpCommand(registry: registry))
|
|
||||||
|
|
||||||
registry.main(defaultVerb: LintCommand().verb) { error in
|
|
||||||
queuedPrintError(String(describing: error))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchMain()
|
dispatchMain()
|
||||||
|
|
|
@ -10,6 +10,6 @@ Pod::Spec.new do |s|
|
||||||
s.source_files = 'Source/SwiftLintFramework/**/*.swift'
|
s.source_files = 'Source/SwiftLintFramework/**/*.swift'
|
||||||
s.swift_versions = ['5.2', '5.3']
|
s.swift_versions = ['5.2', '5.3']
|
||||||
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
||||||
s.dependency 'SourceKittenFramework', '~> 0.30.1'
|
s.dependency 'SourceKittenFramework', '~> 0.31.0'
|
||||||
s.dependency 'Yams', '~> 4.0.2'
|
s.dependency 'Yams', '~> 4.0.2'
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue