From b55546e83b0d69d37e13f1b5d5a55fa0c22552ca Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 12 Oct 2022 23:33:13 -0400 Subject: [PATCH 01/29] Rewrite `custom_rules` with SwiftSyntax --- CHANGELOG.md | 1 + .../Extensions/SwiftLintFile+Regex.swift | 85 ++++++++++++++++++- .../Models/SwiftLintSyntaxToken.swift | 2 +- .../Rules/Style/CustomRules.swift | 2 +- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d1500d5f..63a45614cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ - `contains_over_filter_is_empty` - `contains_over_first_not_nil` - `contains_over_range_nil_comparison` + - `custom_rules` - `deployment_target` - `discouraged_assert` - `discouraged_direct_init` diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index d961d7bfb3..2c01a83bc2 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -1,5 +1,6 @@ import Foundation import SourceKittenFramework +import SwiftSyntax internal func regex(_ pattern: String, options: NSRegularExpression.Options? = nil) -> NSRegularExpression { @@ -100,10 +101,9 @@ extension SwiftLintFile { range: NSRange? = nil) -> [(NSTextCheckingResult, [SwiftLintSyntaxToken])] { let contents = stringView let range = range ?? contents.range - let syntax = syntaxMap return regex(pattern).matches(in: contents, options: [], range: range).compactMap { match in let matchByteRange = contents.NSRangeToByteRange(start: match.range.location, length: match.range.length) - return matchByteRange.map { (match, syntax.tokens(inByteRange: $0)) } + return matchByteRange.map { (match, tokens(inByteRange: $0)) } } } @@ -293,3 +293,84 @@ extension SwiftLintFile { return stringView.substringWithByteRange(token.range) } } + +private extension SwiftLintFile { + func tokens(inByteRange byteRange: ByteRange) -> [SwiftLintSyntaxToken] { + let byteSourceRange = ByteSourceRange(offset: byteRange.location.value, length: byteRange.length.value) + let classifications = syntaxTree.classifications(in: byteSourceRange) + let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in + guard var syntaxKind = classification.kind.toSyntaxKind() else { + return nil + } + + let offset = ByteCount(classification.offset) + let length = ByteCount(classification.length) + + // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in attributes. + if syntaxKind == .keyword, + case let byteRange = ByteRange(location: offset, length: length), + let substring = stringView.substringWithByteRange(byteRange), + AccessControlLevel(description: substring) != nil { + syntaxKind = .attributeBuiltin + } + + let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) + return SwiftLintSyntaxToken(value: syntaxToken) + } + // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax +// let old = syntaxMap.tokens(inByteRange: byteRange) +// if new != old { +// queuedPrint("Old") +// queuedPrint(old) +// queuedPrint("New") +// queuedPrint(new) +// queuedPrint("Classifications") +// var desc = "" +// dump(classifications, to: &desc) +// queuedFatalError(desc) +// } + return new + } +} + +private extension SyntaxClassification { + // swiftlint:disable:next cyclomatic_complexity + func toSyntaxKind() -> SyntaxKind? { + switch self { + case .none: + return nil + case .keyword: + return .keyword + case .identifier: + return .identifier + case .typeIdentifier: + return .typeidentifier + case .dollarIdentifier: + return .identifier + case .integerLiteral: + return .number + case .floatingLiteral: + return .number + case .stringLiteral: + return .string + case .stringInterpolationAnchor: + return .stringInterpolationAnchor + case .poundDirectiveKeyword, .buildConfigId: + return .poundDirectiveKeyword + case .attribute: + return .attributeBuiltin + case .objectLiteral: + return .objectLiteral + case .editorPlaceholder: + return .placeholder + case .lineComment: + return .comment + case .docLineComment: + return .docComment + case .blockComment: + return .comment + case .docBlockComment: + return .docComment + } + } +} diff --git a/Source/SwiftLintFramework/Models/SwiftLintSyntaxToken.swift b/Source/SwiftLintFramework/Models/SwiftLintSyntaxToken.swift index 99e490de21..bfb61138b2 100644 --- a/Source/SwiftLintFramework/Models/SwiftLintSyntaxToken.swift +++ b/Source/SwiftLintFramework/Models/SwiftLintSyntaxToken.swift @@ -1,7 +1,7 @@ import SourceKittenFramework /// A SwiftLint-aware Swift syntax token. -public struct SwiftLintSyntaxToken { +public struct SwiftLintSyntaxToken: Equatable { /// The raw `SyntaxToken` obtained by SourceKitten. public let value: SyntaxToken diff --git a/Source/SwiftLintFramework/Rules/Style/CustomRules.swift b/Source/SwiftLintFramework/Rules/Style/CustomRules.swift index b9acd5a048..e96b87c20a 100644 --- a/Source/SwiftLintFramework/Rules/Style/CustomRules.swift +++ b/Source/SwiftLintFramework/Rules/Style/CustomRules.swift @@ -66,7 +66,7 @@ public struct CustomRulesConfiguration: RuleConfiguration, Equatable, CacheDescr // MARK: - CustomRules -public struct CustomRules: Rule, ConfigurationProviderRule, CacheDescriptionProvider { +public struct CustomRules: Rule, ConfigurationProviderRule, CacheDescriptionProvider, SourceKitFreeRule { internal var cacheDescription: String { return configuration.cacheDescription } From 99782043dd5d6a2bbacaa5bae649a6ae54e48f4d Mon Sep 17 00:00:00 2001 From: JP Simard Date: Mon, 17 Oct 2022 17:54:36 -0400 Subject: [PATCH 02/29] Annotate some rules as SourceKitFreeRule --- .../Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift | 3 ++- Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift | 2 +- .../Rules/Style/PreferSelfTypeOverTypeOfSelfRule.swift | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift index aa60e36b8b..488620be8d 100644 --- a/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift +++ b/Source/SwiftLintFramework/Rules/Idiomatic/PreferZeroOverExplicitInitRule.swift @@ -1,7 +1,8 @@ import Foundation import SourceKittenFramework -public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRule, SubstitutionCorrectableRule { +public struct PreferZeroOverExplicitInitRule: OptInRule, ConfigurationProviderRule, SubstitutionCorrectableRule, + SourceKitFreeRule { public var configuration = SeverityConfiguration(.warning) private var pattern: String { let zero = "\\s*:\\s*0(\\.0*)?\\s*" diff --git a/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift b/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift index 0b5a36c270..012ad0ebb7 100644 --- a/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift +++ b/Source/SwiftLintFramework/Rules/Lint/ExpiringTodoRule.swift @@ -1,7 +1,7 @@ import Foundation import SourceKittenFramework -public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule { +public struct ExpiringTodoRule: ConfigurationProviderRule, OptInRule, SourceKitFreeRule { enum ExpiryViolationLevel { case approachingExpiry case expired diff --git a/Source/SwiftLintFramework/Rules/Style/PreferSelfTypeOverTypeOfSelfRule.swift b/Source/SwiftLintFramework/Rules/Style/PreferSelfTypeOverTypeOfSelfRule.swift index 56a8d63add..746435e6e0 100644 --- a/Source/SwiftLintFramework/Rules/Style/PreferSelfTypeOverTypeOfSelfRule.swift +++ b/Source/SwiftLintFramework/Rules/Style/PreferSelfTypeOverTypeOfSelfRule.swift @@ -1,7 +1,8 @@ import Foundation import SourceKittenFramework -public struct PreferSelfTypeOverTypeOfSelfRule: OptInRule, ConfigurationProviderRule, SubstitutionCorrectableRule { +public struct PreferSelfTypeOverTypeOfSelfRule: OptInRule, ConfigurationProviderRule, SubstitutionCorrectableRule, + SourceKitFreeRule { public var configuration = SeverityConfiguration(.warning) public static let description = RuleDescription( From 5535a86fe123c2b0768a7d706e4b05ce553897ba Mon Sep 17 00:00:00 2001 From: JP Simard Date: Mon, 17 Oct 2022 22:49:07 -0400 Subject: [PATCH 03/29] Fix `throws` handling --- .../Extensions/SwiftLintFile+Regex.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index 2c01a83bc2..ddd7e05073 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -306,12 +306,17 @@ private extension SwiftLintFile { let offset = ByteCount(classification.offset) let length = ByteCount(classification.length) - // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in attributes. if syntaxKind == .keyword, case let byteRange = ByteRange(location: offset, length: length), - let substring = stringView.substringWithByteRange(byteRange), - AccessControlLevel(description: substring) != nil { - syntaxKind = .attributeBuiltin + let substring = stringView.substringWithByteRange(byteRange) { + if substring == "throws" { + // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. + return nil + } else if AccessControlLevel(description: substring) != nil { + // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in + // attributes. + syntaxKind = .attributeBuiltin + } } let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) From 998f59594f471b8ad7da5678adf3c6b4fa1de530 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 19 Oct 2022 10:51:13 -0400 Subject: [PATCH 04/29] Add repro case for DiscouragedOptionalCollection regression --- .../Extensions/SwiftLintFile+Regex.swift | 5 +- ...iscouragedOptionalCollectionExamples.swift | 67 ++++++++++++++++++- .../Rules/Style/VoidReturnRule.swift | 3 +- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index ddd7e05073..f445b404c6 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -309,7 +309,10 @@ private extension SwiftLintFile { if syntaxKind == .keyword, case let byteRange = ByteRange(location: offset, length: length), let substring = stringView.substringWithByteRange(byteRange) { - if substring == "throws" { + if substring == "Self" { + // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. + syntaxKind = .typeidentifier + } else if substring == "throws" { // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. return nil } else if AccessControlLevel(description: substring) != nil { diff --git a/Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedOptionalCollectionExamples.swift b/Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedOptionalCollectionExamples.swift index 4a798ea359..081d6ceac3 100644 --- a/Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedOptionalCollectionExamples.swift +++ b/Source/SwiftLintFramework/Rules/Idiomatic/DiscouragedOptionalCollectionExamples.swift @@ -204,7 +204,72 @@ internal struct DiscouragedOptionalCollectionExamples { wrapExample("enum", "static func foo(↓input: [String: [String: String]?]) {}"), wrapExample("enum", "static func foo(↓↓input: [String: [String: String]?]?) {}"), wrapExample("enum", "static func foo(_ dict1: [K: V], ↓_ dict2: [K: V]?) -> [K: V]"), - wrapExample("enum", "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]") + wrapExample("enum", "static func foo(dict1: [K: V], ↓dict2: [K: V]?) -> [K: V]"), + Example(#""" + import WidgetKit + + protocol HomeWidgetData: Codable { + + var siteID: Int { get } + var siteName: String { get } + var url: String { get } + var timeZone: TimeZone { get } + var date: Date { get } + + static var filename: String { get } + } + + + // MARK: - Local cache + extension HomeWidgetData { + + static func ↓read(from cache: HomeWidgetCache? = nil) -> [Int: Self]? { + + let cache = cache ?? HomeWidgetCache(fileName: Self.filename, + appGroup: WPAppGroupName) + do { + return try cache.read() + } catch { + DDLogError("HomeWidgetToday: Failed loading data: \(error.localizedDescription)") + return nil + } + } + + static func write(items: [Int: Self], to cache: HomeWidgetCache? = nil) { + + let cache = cache ?? HomeWidgetCache(fileName: Self.filename, + appGroup: WPAppGroupName) + + do { + try cache.write(items: items) + } catch { + DDLogError("HomeWidgetToday: Failed writing data: \(error.localizedDescription)") + } + } + + static func delete(cache: HomeWidgetCache? = nil) { + let cache = cache ?? HomeWidgetCache(fileName: Self.filename, + appGroup: WPAppGroupName) + + do { + try cache.delete() + } catch { + DDLogError("HomeWidgetToday: Failed deleting data: \(error.localizedDescription)") + } + } + + static func setItem(item: Self, to cache: HomeWidgetCache? = nil) { + let cache = cache ?? HomeWidgetCache(fileName: Self.filename, + appGroup: WPAppGroupName) + + do { + try cache.setItem(item: item) + } catch { + DDLogError("HomeWidgetToday: Failed writing data item: \(error.localizedDescription)") + } + } + } + """#, excludeFromDocumentation: true) ] } diff --git a/Source/SwiftLintFramework/Rules/Style/VoidReturnRule.swift b/Source/SwiftLintFramework/Rules/Style/VoidReturnRule.swift index c094995b16..d9e03fe724 100644 --- a/Source/SwiftLintFramework/Rules/Style/VoidReturnRule.swift +++ b/Source/SwiftLintFramework/Rules/Style/VoidReturnRule.swift @@ -27,7 +27,8 @@ public struct VoidReturnRule: ConfigurationProviderRule, SubstitutionCorrectable Example("func foo(completion: () -> ↓())\n"), Example("func foo(completion: () -> ↓( ))\n"), Example("func foo(completion: () -> ↓(Void))\n"), - Example("let foo: (ConfigurationTests) -> () throws -> ↓()\n") + Example("let foo: (ConfigurationTests) -> () throws -> ↓()\n"), + Example("typealias ReplaceEditorCallback = (EditorViewController, EditorViewController) -> ↓()") ], corrections: [ Example("let abc: () -> ↓() = {}\n"): Example("let abc: () -> Void = {}\n"), From 98973cf77055dcf5a9fcea1539f3d46597ace9ec Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 19 Oct 2022 11:03:11 -0400 Subject: [PATCH 05/29] Add more rules to changelog --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a45614cc..04fc3e893b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ - `empty_parentheses_with_trailing_closure` - `empty_string` - `enum_case_associated_values_count` + - `expiring_todo` - `explicit_enum_raw_value` - `explicit_init` - `fallthrough` @@ -105,13 +106,15 @@ - `no_space_in_method_call` - `nslocalizedstring_require_bundle` - `nsobject_prefer_isequal` + - `nsobject_prefer_isequal` - `number_separator` - `operator_whitespace` - - `nsobject_prefer_isequal` - `prefer_nimble` + - `prefer_self_type_over_type_of_self` + - `prefer_zero_over_explicit_init` - `private_action` - - `private_over_fileprivate` - `private_outlet` + - `private_over_fileprivate` - `private_unit_test` - `prohibited_interface_builder` - `protocol_property_accessors_order` From ca8573c52634de605a5b6535c03261449483e569 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 19 Oct 2022 11:07:54 -0400 Subject: [PATCH 06/29] fixup! Add more rules to changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04fc3e893b..76a5732f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,7 +106,6 @@ - `no_space_in_method_call` - `nslocalizedstring_require_bundle` - `nsobject_prefer_isequal` - - `nsobject_prefer_isequal` - `number_separator` - `operator_whitespace` - `prefer_nimble` From 5294a32abcceaed41269d073d055ab8041fa47ae Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 19 Oct 2022 11:28:18 -0400 Subject: [PATCH 07/29] Handle some whitespace differences --- .../Extensions/SwiftLintFile+Regex.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index f445b404c6..1e3edaaec5 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -322,6 +322,13 @@ private extension SwiftLintFile { } } + if (offset + length) == byteRange.lowerBound, + let stringAtOffset = stringView + .substringWithByteRange(ByteRange(location: byteRange.lowerBound, length: 1)), + stringAtOffset.allSatisfy(\.isWhitespace) { + return nil + } + let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) return SwiftLintSyntaxToken(value: syntaxToken) } From b8f1bd8a3be18359f8c3cf2f774de6811b9ebc4a Mon Sep 17 00:00:00 2001 From: JP Simard Date: Wed, 19 Oct 2022 13:20:11 -0400 Subject: [PATCH 08/29] Fixes & Hacks --- .../Extensions/SwiftLintFile+Regex.swift | 100 +---------- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 162 ++++++++++++++++++ 2 files changed, 164 insertions(+), 98 deletions(-) create mode 100644 Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index 1e3edaaec5..6d8116fe39 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -1,6 +1,5 @@ import Foundation import SourceKittenFramework -import SwiftSyntax internal func regex(_ pattern: String, options: NSRegularExpression.Options? = nil) -> NSRegularExpression { @@ -103,7 +102,8 @@ extension SwiftLintFile { let range = range ?? contents.range return regex(pattern).matches(in: contents, options: [], range: range).compactMap { match in let matchByteRange = contents.NSRangeToByteRange(start: match.range.location, length: match.range.length) - return matchByteRange.map { (match, tokens(inByteRange: $0)) } + return matchByteRange + .map { (match, SwiftSyntaxSourceKitBridge.tokens(file: self, in: $0)) } } } @@ -293,99 +293,3 @@ extension SwiftLintFile { return stringView.substringWithByteRange(token.range) } } - -private extension SwiftLintFile { - func tokens(inByteRange byteRange: ByteRange) -> [SwiftLintSyntaxToken] { - let byteSourceRange = ByteSourceRange(offset: byteRange.location.value, length: byteRange.length.value) - let classifications = syntaxTree.classifications(in: byteSourceRange) - let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in - guard var syntaxKind = classification.kind.toSyntaxKind() else { - return nil - } - - let offset = ByteCount(classification.offset) - let length = ByteCount(classification.length) - - if syntaxKind == .keyword, - case let byteRange = ByteRange(location: offset, length: length), - let substring = stringView.substringWithByteRange(byteRange) { - if substring == "Self" { - // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. - syntaxKind = .typeidentifier - } else if substring == "throws" { - // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. - return nil - } else if AccessControlLevel(description: substring) != nil { - // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in - // attributes. - syntaxKind = .attributeBuiltin - } - } - - if (offset + length) == byteRange.lowerBound, - let stringAtOffset = stringView - .substringWithByteRange(ByteRange(location: byteRange.lowerBound, length: 1)), - stringAtOffset.allSatisfy(\.isWhitespace) { - return nil - } - - let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) - return SwiftLintSyntaxToken(value: syntaxToken) - } - // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax -// let old = syntaxMap.tokens(inByteRange: byteRange) -// if new != old { -// queuedPrint("Old") -// queuedPrint(old) -// queuedPrint("New") -// queuedPrint(new) -// queuedPrint("Classifications") -// var desc = "" -// dump(classifications, to: &desc) -// queuedFatalError(desc) -// } - return new - } -} - -private extension SyntaxClassification { - // swiftlint:disable:next cyclomatic_complexity - func toSyntaxKind() -> SyntaxKind? { - switch self { - case .none: - return nil - case .keyword: - return .keyword - case .identifier: - return .identifier - case .typeIdentifier: - return .typeidentifier - case .dollarIdentifier: - return .identifier - case .integerLiteral: - return .number - case .floatingLiteral: - return .number - case .stringLiteral: - return .string - case .stringInterpolationAnchor: - return .stringInterpolationAnchor - case .poundDirectiveKeyword, .buildConfigId: - return .poundDirectiveKeyword - case .attribute: - return .attributeBuiltin - case .objectLiteral: - return .objectLiteral - case .editorPlaceholder: - return .placeholder - case .lineComment: - return .comment - case .docLineComment: - return .docComment - case .blockComment: - return .comment - case .docBlockComment: - return .docComment - } - } -} diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift new file mode 100644 index 0000000000..80c1b32c73 --- /dev/null +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -0,0 +1,162 @@ +import SourceKittenFramework +import SwiftSyntax + +// This file contains a pile of hacks in order to convert the syntax classifications provided by SwiftSyntax to the +// data that SourceKit used to provide. + +/// Holds code to bridge SwiftSyntax concepts to SourceKit concepts. +enum SwiftSyntaxSourceKitBridge { + static func tokens(file: SwiftLintFile, in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { + file.tokens(in: byteRange) + } +} + +// MARK: - Private + +private extension SwiftLintFile { + func tokens(in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { + let visitor = QuoteVisitor(viewMode: .sourceAccurate) + let syntaxTree = self.syntaxTree + visitor.walk(syntaxTree) + let openQuoteRanges = visitor.openQuoteRanges.sorted(by: { $0.offset < $1.offset }) + let closeQuoteRanges = visitor.closeQuoteRanges.sorted(by: { $0.offset < $1.offset }) + let byteSourceRange = ByteSourceRange(offset: byteRange.location.value, length: byteRange.length.value) + let classifications = syntaxTree.classifications(in: byteSourceRange) + let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in + guard var syntaxKind = classification.kind.toSyntaxKind() else { + return nil + } + + var offset = ByteCount(classification.offset) + var length = ByteCount(classification.length) + + let lastCharRange = ByteRange(location: offset + length, length: 1) + if syntaxKind.isCommentLike, + classification.kind != .docBlockComment, + stringView.substringWithByteRange(lastCharRange)?.allSatisfy(\.isNewline) == true { + length += 1 + } else if syntaxKind == .string { + if let openQuote = openQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { + let diff = offset - ByteCount(openQuote.offset) + offset = ByteCount(openQuote.offset) + length += diff + } + + if let closeQuote = closeQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { + length = ByteCount(closeQuote.endOffset) - offset + } + } + + if syntaxKind == .keyword, + case let byteRange = ByteRange(location: offset, length: length), + let substring = stringView.substringWithByteRange(byteRange) { + if substring == "Self" { + // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. + syntaxKind = .typeidentifier + } else if substring == "throws" { + // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. + return nil + } else if AccessControlLevel(description: substring) != nil { + // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in + // attributes. + syntaxKind = .attributeBuiltin + } + } + + let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) + return SwiftLintSyntaxToken(value: syntaxToken) + } + // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax +// let old = syntaxMap.tokens(inByteRange: byteRange) +// if new != old, !new.contains(where: { $0.shouldIgnoreMismatches() }) { +// queuedPrint("File: \(self.path!)") +// queuedPrint("Requested byte range: \(byteRange)") +// queuedPrint("Old") +// queuedPrint(old) +// queuedPrint("New") +// queuedPrint(new) +// queuedPrint("Classifications") +// var desc = "" +// dump(classifications, to: &desc) +// queuedFatalError(desc) +// } + return new + } +} + +private extension SyntaxClassification { + // swiftlint:disable:next cyclomatic_complexity + func toSyntaxKind() -> SyntaxKind? { + switch self { + case .none: + return nil + case .keyword: + return .keyword + case .identifier: + return .identifier + case .typeIdentifier: + return .typeidentifier + case .dollarIdentifier: + return .identifier + case .integerLiteral: + return .number + case .floatingLiteral: + return .number + case .stringLiteral: + return .string + case .stringInterpolationAnchor: + return .stringInterpolationAnchor + case .poundDirectiveKeyword, .buildConfigId: + return .poundDirectiveKeyword + case .attribute: + return .attributeBuiltin + case .objectLiteral: + return .objectLiteral + case .editorPlaceholder: + return .placeholder + case .lineComment: + return .comment + case .docLineComment: + return .docComment + case .blockComment: + return .comment + case .docBlockComment: + return .docComment + case .operatorIdentifier: + return nil + } + } +} + +private final class QuoteVisitor: SyntaxVisitor { + var openQuoteRanges: [ByteSourceRange] = [] + var closeQuoteRanges: [ByteSourceRange] = [] + + override func visitPost(_ node: StringLiteralExprSyntax) { + if let openDelimiter = node.openDelimiter { + let offset = openDelimiter.positionAfterSkippingLeadingTrivia.utf8Offset + let end = node.openQuote.endPosition.utf8Offset + openQuoteRanges.append(ByteSourceRange(offset: offset, length: end - offset)) + } else { + let offset = node.openQuote.positionAfterSkippingLeadingTrivia.utf8Offset + let range = ByteSourceRange( + offset: offset, + length: node.openQuote.endPositionBeforeTrailingTrivia.utf8Offset - offset + ) + openQuoteRanges.append(range) + } + + if let closeDelimiter = node.closeDelimiter { + let offset = node.closeQuote.position.utf8Offset + let end = closeDelimiter.endPositionBeforeTrailingTrivia.utf8Offset + closeQuoteRanges.append(ByteSourceRange(offset: offset, length: end - offset)) + } else { + let offset = node.closeQuote.position.utf8Offset + let range = ByteSourceRange( + offset: offset, + length: node.closeQuote.endPositionBeforeTrailingTrivia.utf8Offset - offset + ) + closeQuoteRanges.append(range) + } + } +} From 0395692a3273ab74379e89c4287af41208556ea1 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:28:43 -0400 Subject: [PATCH 09/29] Fix commented out code --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 80c1b32c73..afb8f89940 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -68,7 +68,7 @@ private extension SwiftLintFile { } // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax // let old = syntaxMap.tokens(inByteRange: byteRange) -// if new != old, !new.contains(where: { $0.shouldIgnoreMismatches() }) { +// if new != old { // queuedPrint("File: \(self.path!)") // queuedPrint("Requested byte range: \(byteRange)") // queuedPrint("Old") From 69833d1e787daa227d0094163bd8b6cbfe5e9352 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:36:58 -0400 Subject: [PATCH 10/29] WIP: Always get tokens from SwiftSyntax --- .../Extensions/SwiftLintFile+Cache.swift | 4 +- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 71 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift index f13ec0c37f..5b96bd465b 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift @@ -33,8 +33,8 @@ private let commandsCache = Cache { file -> [Command] in return CommandVisitor(locationConverter: file.locationConverter) .walk(file: file, handler: \.commands) } -private let syntaxMapCache = Cache { file in - responseCache.get(file).map { SwiftLintSyntaxMap(value: SyntaxMap(sourceKitResponse: $0)) } +private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in // TODO: Remove optional + SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.allTokens(file: file).map(\.value))) } private let syntaxKindsByLinesCache = Cache { file in file.syntaxKindsByLine() } private let syntaxTokensByLinesCache = Cache { file in file.syntaxTokensByLine() } diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index afb8f89940..3542e37946 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -9,11 +9,82 @@ enum SwiftSyntaxSourceKitBridge { static func tokens(file: SwiftLintFile, in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { file.tokens(in: byteRange) } + + static func allTokens(file: SwiftLintFile) -> [SwiftLintSyntaxToken] { + file.allTokens() + } } // MARK: - Private private extension SwiftLintFile { + func allTokens() -> [SwiftLintSyntaxToken] { + let visitor = QuoteVisitor(viewMode: .sourceAccurate) + let syntaxTree = self.syntaxTree + visitor.walk(syntaxTree) + let openQuoteRanges = visitor.openQuoteRanges.sorted(by: { $0.offset < $1.offset }) + let closeQuoteRanges = visitor.closeQuoteRanges.sorted(by: { $0.offset < $1.offset }) + let classifications = syntaxTree.classifications + let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in + guard var syntaxKind = classification.kind.toSyntaxKind() else { + return nil + } + + var offset = ByteCount(classification.offset) + var length = ByteCount(classification.length) + + let lastCharRange = ByteRange(location: offset + length, length: 1) + if syntaxKind.isCommentLike, + classification.kind != .docBlockComment, + stringView.substringWithByteRange(lastCharRange)?.allSatisfy(\.isNewline) == true { + length += 1 + } else if syntaxKind == .string { + if let openQuote = openQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { + let diff = offset - ByteCount(openQuote.offset) + offset = ByteCount(openQuote.offset) + length += diff + } + + if let closeQuote = closeQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { + length = ByteCount(closeQuote.endOffset) - offset + } + } + + if syntaxKind == .keyword, + case let byteRange = ByteRange(location: offset, length: length), + let substring = stringView.substringWithByteRange(byteRange) { + if substring == "Self" { + // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. + syntaxKind = .typeidentifier + } else if substring == "throws" { + // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. + return nil + } else if AccessControlLevel(description: substring) != nil { + // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in + // attributes. + syntaxKind = .attributeBuiltin + } + } + + let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) + return SwiftLintSyntaxToken(value: syntaxToken) + } + // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax + let old = syntaxMap.tokens + if new != old { + queuedPrint("File: \(self.path!)") + queuedPrint("Old") + queuedPrint(old) + queuedPrint("New") + queuedPrint(new) + queuedPrint("Classifications") + var desc = "" + dump(classifications, to: &desc) + queuedFatalError(desc) + } + return new + } + func tokens(in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { let visitor = QuoteVisitor(viewMode: .sourceAccurate) let syntaxTree = self.syntaxTree From 86433076fb40b123671cf22cf2cf2d6b95235e95 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:42:27 -0400 Subject: [PATCH 11/29] DEBUG --- .../Extensions/SwiftLintFile+Cache.swift | 16 ++++++++++++++++ .../Helpers/SwiftSyntaxSourceKitBridge.swift | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift index 5b96bd465b..b3fb508af2 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift @@ -33,6 +33,9 @@ private let commandsCache = Cache { file -> [Command] in return CommandVisitor(locationConverter: file.locationConverter) .walk(file: file, handler: \.commands) } +private let oldSyntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in + responseCache.get(file).map { SwiftLintSyntaxMap(value: SyntaxMap(sourceKitResponse: $0)) } +} private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in // TODO: Remove optional SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.allTokens(file: file).map(\.value))) } @@ -158,6 +161,17 @@ extension SwiftLintFile { return syntaxMap } + internal var oldSyntaxMap: SwiftLintSyntaxMap { + guard let syntaxMap = oldSyntaxMapCache.get(self) else { + if let handler = assertHandler { + handler() + return SwiftLintSyntaxMap(value: SyntaxMap(data: [])) + } + queuedFatalError("Never call this for file that sourcekitd fails.") + } + return syntaxMap + } + internal var syntaxTree: SourceFileSyntax { syntaxTreeCache.get(self) } internal var locationConverter: SourceLocationConverter { @@ -196,6 +210,7 @@ extension SwiftLintFile { structureCache.invalidate(self) structureDictionaryCache.invalidate(self) syntaxMapCache.invalidate(self) + oldSyntaxMapCache.invalidate(self) syntaxTokensByLinesCache.invalidate(self) syntaxKindsByLinesCache.invalidate(self) syntaxTreeCache.invalidate(self) @@ -209,6 +224,7 @@ extension SwiftLintFile { structureCache.clear() structureDictionaryCache.clear() syntaxMapCache.clear() + oldSyntaxMapCache.clear() syntaxTokensByLinesCache.clear() syntaxKindsByLinesCache.clear() syntaxTreeCache.clear() diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 3542e37946..eaacbda67e 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -70,9 +70,9 @@ private extension SwiftLintFile { return SwiftLintSyntaxToken(value: syntaxToken) } // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax - let old = syntaxMap.tokens + let old = oldSyntaxMap.tokens if new != old { - queuedPrint("File: \(self.path!)") + queuedPrint("File: \(self.path ?? "")") queuedPrint("Old") queuedPrint(old) queuedPrint("New") From 32f3b771cbe8b17c4086875f8aea2ef70fcd50a1 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:45:44 -0400 Subject: [PATCH 12/29] One more fix --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index eaacbda67e..aee6230754 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -59,7 +59,7 @@ private extension SwiftLintFile { } else if substring == "throws" { // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. return nil - } else if AccessControlLevel(description: substring) != nil { + } else if AccessControlLevel(description: substring) != nil || substring == "final" { // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in // attributes. syntaxKind = .attributeBuiltin From 5661f70fa8119e60092c6d7527bb63688ea921da Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:48:02 -0400 Subject: [PATCH 13/29] More fixes --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index aee6230754..0de33fd1c2 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -36,6 +36,7 @@ private extension SwiftLintFile { let lastCharRange = ByteRange(location: offset + length, length: 1) if syntaxKind.isCommentLike, classification.kind != .docBlockComment, + classification.kind != .blockComment, stringView.substringWithByteRange(lastCharRange)?.allSatisfy(\.isNewline) == true { length += 1 } else if syntaxKind == .string { @@ -72,7 +73,12 @@ private extension SwiftLintFile { // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax let old = oldSyntaxMap.tokens if new != old { - queuedPrint("File: \(self.path ?? "")") + if let path = path { + queuedPrint("File Path: \(path)") + } else { + queuedPrint("File Contents: \(contents)") + } + queuedPrint("Old") queuedPrint(old) queuedPrint("New") From 46645cc5c4f7e6d9dee62e94cd4860f2f2bc4a56 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 09:53:54 -0400 Subject: [PATCH 14/29] One more hack --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 0de33fd1c2..df992f3027 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -24,8 +24,8 @@ private extension SwiftLintFile { visitor.walk(syntaxTree) let openQuoteRanges = visitor.openQuoteRanges.sorted(by: { $0.offset < $1.offset }) let closeQuoteRanges = visitor.closeQuoteRanges.sorted(by: { $0.offset < $1.offset }) - let classifications = syntaxTree.classifications - let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in + let classifications = Array(syntaxTree.classifications) + let new = classifications.enumerated().compactMap { index, classification -> SwiftLintSyntaxToken? in guard var syntaxKind = classification.kind.toSyntaxKind() else { return nil } @@ -37,6 +37,7 @@ private extension SwiftLintFile { if syntaxKind.isCommentLike, classification.kind != .docBlockComment, classification.kind != .blockComment, + index != classifications.count - 1, // Don't adjust length if this is the last classification stringView.substringWithByteRange(lastCharRange)?.allSatisfy(\.isNewline) == true { length += 1 } else if syntaxKind == .string { @@ -49,7 +50,7 @@ private extension SwiftLintFile { if let closeQuote = closeQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { length = ByteCount(closeQuote.endOffset) - offset } - } + } if syntaxKind == .keyword, case let byteRange = ByteRange(location: offset, length: length), From 4754343923f1d41408baeaabfed73d91d08cd1f8 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:12:31 -0400 Subject: [PATCH 15/29] Guess what, yet again more hacks --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index df992f3027..afa967807b 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -25,7 +25,7 @@ private extension SwiftLintFile { let openQuoteRanges = visitor.openQuoteRanges.sorted(by: { $0.offset < $1.offset }) let closeQuoteRanges = visitor.closeQuoteRanges.sorted(by: { $0.offset < $1.offset }) let classifications = Array(syntaxTree.classifications) - let new = classifications.enumerated().compactMap { index, classification -> SwiftLintSyntaxToken? in + let new1 = classifications.enumerated().compactMap { index, classification -> SwiftLintSyntaxToken? in guard var syntaxKind = classification.kind.toSyntaxKind() else { return nil } @@ -58,6 +58,9 @@ private extension SwiftLintFile { if substring == "Self" { // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. syntaxKind = .typeidentifier + } else if substring == "unavailable" { + // SwiftSyntax considers 'unavailable' a keyword, but SourceKit considers it an identifier. + syntaxKind = .identifier } else if substring == "throws" { // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. return nil @@ -71,6 +74,30 @@ private extension SwiftLintFile { let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) return SwiftLintSyntaxToken(value: syntaxToken) } + + // Combine `@` with next keyword + var new: [SwiftLintSyntaxToken] = [] + var eatNext = false + for (index, asdf) in new1.enumerated() { + if eatNext { + let previous = new.removeLast() + let newToken = SwiftLintSyntaxToken( + value: SyntaxToken( + type: previous.value.type, + offset: previous.offset, + length: previous.length + asdf.length + ) + ) + new.append(newToken) + eatNext = false + } else if asdf.kind == .attributeBuiltin && asdf.length == 1 && new1[index + 1].kind == .keyword { + eatNext = true + new.append(asdf) + } else { + new.append(asdf) + } + } + // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax let old = oldSyntaxMap.tokens if new != old { @@ -184,8 +211,10 @@ private extension SyntaxClassification { return .string case .stringInterpolationAnchor: return .stringInterpolationAnchor - case .poundDirectiveKeyword, .buildConfigId: - return .poundDirectiveKeyword + case .buildConfigId: + return .buildconfigID + case .poundDirectiveKeyword: + return .buildconfigKeyword case .attribute: return .attributeBuiltin case .objectLiteral: From 115ee6ccc747915ef0b9b1025727fa02b325788a Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:25:33 -0400 Subject: [PATCH 16/29] omg hacks --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index afa967807b..bc2a08e545 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -58,19 +58,28 @@ private extension SwiftLintFile { if substring == "Self" { // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. syntaxKind = .typeidentifier - } else if substring == "unavailable" { - // SwiftSyntax considers 'unavailable' a keyword, but SourceKit considers it an identifier. + } else if ["unavailable", "swift", "deprecated", "introduced"].contains(substring) { + // SwiftSyntax considers 'unavailable' & 'swift' a keyword, but SourceKit considers it an + // identifier. syntaxKind = .identifier } else if substring == "throws" { // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. return nil - } else if AccessControlLevel(description: substring) != nil || substring == "final" { + } else if AccessControlLevel(description: substring) != nil || substring == "final" || + substring == "lazy" { // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in // attributes. syntaxKind = .attributeBuiltin } } + if classification.kind == .poundDirectiveKeyword, + case let byteRange = ByteRange(location: offset, length: length), + let substring = stringView.substringWithByteRange(byteRange), + substring == "#warning" || substring == "#error" { + syntaxKind = .poundDirectiveKeyword + } + let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) return SwiftLintSyntaxToken(value: syntaxToken) } From 8f5a6d5436836130ecd4ab5dccf1567d8577500e Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:28:51 -0400 Subject: [PATCH 17/29] omg this is absurd --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index bc2a08e545..75007f9fe6 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -102,6 +102,8 @@ private extension SwiftLintFile { } else if asdf.kind == .attributeBuiltin && asdf.length == 1 && new1[index + 1].kind == .keyword { eatNext = true new.append(asdf) + } else if asdf.kind == .attributeBuiltin && asdf.length == 1 && new1[index + 1].kind == .typeidentifier { + continue } else { new.append(asdf) } From 51fa671fd4e22ca3e88590320082d022de0c25c4 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:31:27 -0400 Subject: [PATCH 18/29] Ignore comment URL kinds --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 75007f9fe6..68c9beada4 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -111,7 +111,7 @@ private extension SwiftLintFile { // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax let old = oldSyntaxMap.tokens - if new != old { + if new != old, !old.contains(where: { $0.kind == .commentURL }) { if let path = path { queuedPrint("File Path: \(path)") } else { From 95070b230995f7733ee9d99ef2a0632507798456 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:35:02 -0400 Subject: [PATCH 19/29] what's one more hack? --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 68c9beada4..889e04c93e 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -70,6 +70,8 @@ private extension SwiftLintFile { // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in // attributes. syntaxKind = .attributeBuiltin + } else if substring == "for" && stringView.substringWithByteRange(lastCharRange) == ":" { + syntaxKind = .identifier } } From 2c407b587eeb4da2cbaffb3321f0ba6490f6e1ee Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:38:48 -0400 Subject: [PATCH 20/29] Only all tokens --- .../SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift | 2 +- .../SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift | 4 ++-- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift index b3fb508af2..1264eb4821 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift @@ -37,7 +37,7 @@ private let oldSyntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in responseCache.get(file).map { SwiftLintSyntaxMap(value: SyntaxMap(sourceKitResponse: $0)) } } private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in // TODO: Remove optional - SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.allTokens(file: file).map(\.value))) + SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.tokens(file: file).map(\.value))) } private let syntaxKindsByLinesCache = Cache { file in file.syntaxKindsByLine() } private let syntaxTokensByLinesCache = Cache { file in file.syntaxTokensByLine() } diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift index 6d8116fe39..d961d7bfb3 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Regex.swift @@ -100,10 +100,10 @@ extension SwiftLintFile { range: NSRange? = nil) -> [(NSTextCheckingResult, [SwiftLintSyntaxToken])] { let contents = stringView let range = range ?? contents.range + let syntax = syntaxMap return regex(pattern).matches(in: contents, options: [], range: range).compactMap { match in let matchByteRange = contents.NSRangeToByteRange(start: match.range.location, length: match.range.length) - return matchByteRange - .map { (match, SwiftSyntaxSourceKitBridge.tokens(file: self, in: $0)) } + return matchByteRange.map { (match, syntax.tokens(inByteRange: $0)) } } } diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 889e04c93e..a38e282079 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -6,11 +6,7 @@ import SwiftSyntax /// Holds code to bridge SwiftSyntax concepts to SourceKit concepts. enum SwiftSyntaxSourceKitBridge { - static func tokens(file: SwiftLintFile, in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { - file.tokens(in: byteRange) - } - - static func allTokens(file: SwiftLintFile) -> [SwiftLintSyntaxToken] { + static func tokens(file: SwiftLintFile) -> [SwiftLintSyntaxToken] { file.allTokens() } } From b77c3529cd3a8d02c9f526adf2f0c1c83baf894d Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:49:28 -0400 Subject: [PATCH 21/29] handle special comment kinds in comparisons --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 93 ++++--------------- 1 file changed, 20 insertions(+), 73 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index a38e282079..37604d7831 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -62,7 +62,7 @@ private extension SwiftLintFile { // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. return nil } else if AccessControlLevel(description: substring) != nil || substring == "final" || - substring == "lazy" { + substring == "lazy" || substring == "convenience" { // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in // attributes. syntaxKind = .attributeBuiltin @@ -109,7 +109,8 @@ private extension SwiftLintFile { // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax let old = oldSyntaxMap.tokens - if new != old, !old.contains(where: { $0.kind == .commentURL }) { + let (shouldCompare, oldToCompare, newToCompare) = shouldCompare(old: old, new: new) + if shouldCompare { if let path = path { queuedPrint("File Path: \(path)") } else { @@ -117,9 +118,9 @@ private extension SwiftLintFile { } queuedPrint("Old") - queuedPrint(old) + queuedPrint(oldToCompare) queuedPrint("New") - queuedPrint(new) + queuedPrint(newToCompare) queuedPrint("Classifications") var desc = "" dump(classifications, to: &desc) @@ -127,75 +128,6 @@ private extension SwiftLintFile { } return new } - - func tokens(in byteRange: ByteRange) -> [SwiftLintSyntaxToken] { - let visitor = QuoteVisitor(viewMode: .sourceAccurate) - let syntaxTree = self.syntaxTree - visitor.walk(syntaxTree) - let openQuoteRanges = visitor.openQuoteRanges.sorted(by: { $0.offset < $1.offset }) - let closeQuoteRanges = visitor.closeQuoteRanges.sorted(by: { $0.offset < $1.offset }) - let byteSourceRange = ByteSourceRange(offset: byteRange.location.value, length: byteRange.length.value) - let classifications = syntaxTree.classifications(in: byteSourceRange) - let new = classifications.compactMap { classification -> SwiftLintSyntaxToken? in - guard var syntaxKind = classification.kind.toSyntaxKind() else { - return nil - } - - var offset = ByteCount(classification.offset) - var length = ByteCount(classification.length) - - let lastCharRange = ByteRange(location: offset + length, length: 1) - if syntaxKind.isCommentLike, - classification.kind != .docBlockComment, - stringView.substringWithByteRange(lastCharRange)?.allSatisfy(\.isNewline) == true { - length += 1 - } else if syntaxKind == .string { - if let openQuote = openQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { - let diff = offset - ByteCount(openQuote.offset) - offset = ByteCount(openQuote.offset) - length += diff - } - - if let closeQuote = closeQuoteRanges.first(where: { $0.intersectsOrTouches(classification.range) }) { - length = ByteCount(closeQuote.endOffset) - offset - } - } - - if syntaxKind == .keyword, - case let byteRange = ByteRange(location: offset, length: length), - let substring = stringView.substringWithByteRange(byteRange) { - if substring == "Self" { - // SwiftSyntax considers 'Self' a keyword, but SourceKit considers it a type identifier. - syntaxKind = .typeidentifier - } else if substring == "throws" { - // SwiftSyntax considers `throws` a keyword, but SourceKit ignores it. - return nil - } else if AccessControlLevel(description: substring) != nil { - // SwiftSyntax considers ACL keywords as keywords, but SourceKit considers them to be built-in - // attributes. - syntaxKind = .attributeBuiltin - } - } - - let syntaxToken = SyntaxToken(type: syntaxKind.rawValue, offset: offset, length: length) - return SwiftLintSyntaxToken(value: syntaxToken) - } - // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax -// let old = syntaxMap.tokens(inByteRange: byteRange) -// if new != old { -// queuedPrint("File: \(self.path!)") -// queuedPrint("Requested byte range: \(byteRange)") -// queuedPrint("Old") -// queuedPrint(old) -// queuedPrint("New") -// queuedPrint(new) -// queuedPrint("Classifications") -// var desc = "" -// dump(classifications, to: &desc) -// queuedFatalError(desc) -// } - return new - } } private extension SyntaxClassification { @@ -276,3 +208,18 @@ private final class QuoteVisitor: SyntaxVisitor { } } } + +private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) + -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) +{ + guard old != new else { + return (false, old, new) + } + + let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } + if containsSubCommentKinds { + return (true, old.filter { $0.kind?.isCommentLike == true }, new.filter { $0.kind?.isCommentLike == true }) + } + + return (true, old, new) +} From 8da6c1e56b1b3be18e4015e20bbad467d0278016 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:51:01 -0400 Subject: [PATCH 22/29] boolean logic is hard --- .../SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 37604d7831..3327f22293 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -218,7 +218,7 @@ private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxTok let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } if containsSubCommentKinds { - return (true, old.filter { $0.kind?.isCommentLike == true }, new.filter { $0.kind?.isCommentLike == true }) + return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) } return (true, old, new) From 18fc9afbaf3845c272f642f27460362461a5483d Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:55:30 -0400 Subject: [PATCH 23/29] Remove debug code --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 3327f22293..208ba1bc66 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -108,24 +108,24 @@ private extension SwiftLintFile { } // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax - let old = oldSyntaxMap.tokens - let (shouldCompare, oldToCompare, newToCompare) = shouldCompare(old: old, new: new) - if shouldCompare { - if let path = path { - queuedPrint("File Path: \(path)") - } else { - queuedPrint("File Contents: \(contents)") - } - - queuedPrint("Old") - queuedPrint(oldToCompare) - queuedPrint("New") - queuedPrint(newToCompare) - queuedPrint("Classifications") - var desc = "" - dump(classifications, to: &desc) - queuedFatalError(desc) - } +// let old = oldSyntaxMap.tokens +// let (shouldCompare, oldToCompare, newToCompare) = shouldCompare(old: old, new: new) +// if shouldCompare { +// if let path = path { +// queuedPrint("File Path: \(path)") +// } else { +// queuedPrint("File Contents: \(contents)") +// } +// +// queuedPrint("Old") +// queuedPrint(oldToCompare) +// queuedPrint("New") +// queuedPrint(newToCompare) +// queuedPrint("Classifications") +// var desc = "" +// dump(classifications, to: &desc) +// queuedFatalError(desc) +// } return new } } @@ -209,17 +209,17 @@ private final class QuoteVisitor: SyntaxVisitor { } } -private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) - -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) -{ - guard old != new else { - return (false, old, new) - } - - let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } - if containsSubCommentKinds { - return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) - } - - return (true, old, new) -} +//private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) +// -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) +//{ +// guard old != new else { +// return (false, old, new) +// } +// +// let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } +// if containsSubCommentKinds { +// return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) +// } +// +// return (true, old, new) +//} From 54f7ed037812f84fb4eaa695f5c3b77fcfa12c13 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:56:52 -0400 Subject: [PATCH 24/29] Remove optional --- .../Extensions/SwiftLintFile+Cache.swift | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift index 1264eb4821..e34c2e8f87 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift @@ -33,10 +33,7 @@ private let commandsCache = Cache { file -> [Command] in return CommandVisitor(locationConverter: file.locationConverter) .walk(file: file, handler: \.commands) } -private let oldSyntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in - responseCache.get(file).map { SwiftLintSyntaxMap(value: SyntaxMap(sourceKitResponse: $0)) } -} -private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap? in // TODO: Remove optional +private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap in SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.tokens(file: file).map(\.value))) } private let syntaxKindsByLinesCache = Cache { file in file.syntaxKindsByLine() } @@ -150,27 +147,7 @@ extension SwiftLintFile { return structureDictionary } - internal var syntaxMap: SwiftLintSyntaxMap { - guard let syntaxMap = syntaxMapCache.get(self) else { - if let handler = assertHandler { - handler() - return SwiftLintSyntaxMap(value: SyntaxMap(data: [])) - } - queuedFatalError("Never call this for file that sourcekitd fails.") - } - return syntaxMap - } - - internal var oldSyntaxMap: SwiftLintSyntaxMap { - guard let syntaxMap = oldSyntaxMapCache.get(self) else { - if let handler = assertHandler { - handler() - return SwiftLintSyntaxMap(value: SyntaxMap(data: [])) - } - queuedFatalError("Never call this for file that sourcekitd fails.") - } - return syntaxMap - } + internal var syntaxMap: SwiftLintSyntaxMap { syntaxMapCache.get(self) } internal var syntaxTree: SourceFileSyntax { syntaxTreeCache.get(self) } @@ -210,7 +187,6 @@ extension SwiftLintFile { structureCache.invalidate(self) structureDictionaryCache.invalidate(self) syntaxMapCache.invalidate(self) - oldSyntaxMapCache.invalidate(self) syntaxTokensByLinesCache.invalidate(self) syntaxKindsByLinesCache.invalidate(self) syntaxTreeCache.invalidate(self) @@ -224,7 +200,6 @@ extension SwiftLintFile { structureCache.clear() structureDictionaryCache.clear() syntaxMapCache.clear() - oldSyntaxMapCache.clear() syntaxTokensByLinesCache.clear() syntaxKindsByLinesCache.clear() syntaxTreeCache.clear() From 0fa0fbd104182190fdec08e16eafc150efbe0269 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:58:24 -0400 Subject: [PATCH 25/29] Fix linter violations --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 208ba1bc66..da4caf8d1f 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -1,6 +1,8 @@ import SourceKittenFramework import SwiftSyntax +// swiftlint:disable cyclomatic_complexity function_body_length + // This file contains a pile of hacks in order to convert the syntax classifications provided by SwiftSyntax to the // data that SourceKit used to provide. @@ -209,17 +211,17 @@ private final class QuoteVisitor: SyntaxVisitor { } } -//private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) -// -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) -//{ -// guard old != new else { -// return (false, old, new) -// } +// private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) +// -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) +// { +// guard old != new else { +// return (false, old, new) +// } // -// let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } -// if containsSubCommentKinds { -// return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) -// } +// let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } +// if containsSubCommentKinds { +// return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) +// } // -// return (true, old, new) -//} +// return (true, old, new) +// } From cb3af20df5aab183114a888040e9cecb6a71ca99 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 10:59:14 -0400 Subject: [PATCH 26/29] Remove extra type annotation --- Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift index e34c2e8f87..81599099d5 100644 --- a/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift +++ b/Source/SwiftLintFramework/Extensions/SwiftLintFile+Cache.swift @@ -33,7 +33,7 @@ private let commandsCache = Cache { file -> [Command] in return CommandVisitor(locationConverter: file.locationConverter) .walk(file: file, handler: \.commands) } -private let syntaxMapCache = Cache { file -> SwiftLintSyntaxMap in +private let syntaxMapCache = Cache { file in SwiftLintSyntaxMap(value: SyntaxMap(tokens: SwiftSyntaxSourceKitBridge.tokens(file: file).map(\.value))) } private let syntaxKindsByLinesCache = Cache { file in file.syntaxKindsByLine() } From d86fc8f13184205f3f4a8480911d0282cbe5596d Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 11:00:09 -0400 Subject: [PATCH 27/29] Remove debug code --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index da4caf8d1f..0875596578 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -109,25 +109,6 @@ private extension SwiftLintFile { } } - // Uncomment to debug mismatches from what SourceKit provides and what we get via SwiftSyntax -// let old = oldSyntaxMap.tokens -// let (shouldCompare, oldToCompare, newToCompare) = shouldCompare(old: old, new: new) -// if shouldCompare { -// if let path = path { -// queuedPrint("File Path: \(path)") -// } else { -// queuedPrint("File Contents: \(contents)") -// } -// -// queuedPrint("Old") -// queuedPrint(oldToCompare) -// queuedPrint("New") -// queuedPrint(newToCompare) -// queuedPrint("Classifications") -// var desc = "" -// dump(classifications, to: &desc) -// queuedFatalError(desc) -// } return new } } @@ -210,18 +191,3 @@ private final class QuoteVisitor: SyntaxVisitor { } } } - -// private func shouldCompare(old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) -// -> (Bool, old: [SwiftLintSyntaxToken], new: [SwiftLintSyntaxToken]) -// { -// guard old != new else { -// return (false, old, new) -// } -// -// let containsSubCommentKinds = old.contains { [.commentURL, .docCommentField].contains($0.kind) } -// if containsSubCommentKinds { -// return (true, old.filter { $0.kind?.isCommentLike != true }, new.filter { $0.kind?.isCommentLike != true }) -// } -// -// return (true, old, new) -// } From 682b3665df63a4505f24b3d19620cdce172a1945 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 11:03:41 -0400 Subject: [PATCH 28/29] Reduce scope of linter disables --- .../Helpers/SwiftSyntaxSourceKitBridge.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift index 0875596578..344cef8589 100644 --- a/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift +++ b/Source/SwiftLintFramework/Helpers/SwiftSyntaxSourceKitBridge.swift @@ -1,8 +1,6 @@ import SourceKittenFramework import SwiftSyntax -// swiftlint:disable cyclomatic_complexity function_body_length - // This file contains a pile of hacks in order to convert the syntax classifications provided by SwiftSyntax to the // data that SourceKit used to provide. @@ -16,6 +14,7 @@ enum SwiftSyntaxSourceKitBridge { // MARK: - Private private extension SwiftLintFile { + // swiftlint:disable:next cyclomatic_complexity function_body_length func allTokens() -> [SwiftLintSyntaxToken] { let visitor = QuoteVisitor(viewMode: .sourceAccurate) let syntaxTree = self.syntaxTree From d0f60fa3be4289c58526564d9f0765ae669a0183 Mon Sep 17 00:00:00 2001 From: JP Simard Date: Thu, 20 Oct 2022 11:05:27 -0400 Subject: [PATCH 29/29] Fix test --- Tests/SwiftLintFrameworkTests/SourceKitCrashTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftLintFrameworkTests/SourceKitCrashTests.swift b/Tests/SwiftLintFrameworkTests/SourceKitCrashTests.swift index e3e092b24a..54eea136bd 100644 --- a/Tests/SwiftLintFrameworkTests/SourceKitCrashTests.swift +++ b/Tests/SwiftLintFrameworkTests/SourceKitCrashTests.swift @@ -42,8 +42,8 @@ class SourceKitCrashTests: XCTestCase { assertHandlerCalled = false _ = file.syntaxMap - XCTAssertTrue(assertHandlerCalled, - "Expects assert handler was called on accessing SwiftLintFile.syntaxMap") + XCTAssertFalse(assertHandlerCalled, + "Expects assert handler was not called on accessing SwiftLintFile.syntaxMap") assertHandlerCalled = false _ = file.syntaxKindsByLines