mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
b0cd950a35
Virtual files are not stored on disk and can thus also not be read from there again. Their content needs to stay in memory and be modified right there.
247 lines
7.9 KiB
Swift
247 lines
7.9 KiB
Swift
#if canImport(Darwin)
|
|
import Darwin
|
|
#endif
|
|
import Foundation
|
|
import SourceKittenFramework
|
|
import SwiftDiagnostics
|
|
import SwiftIDEUtils
|
|
import SwiftOperators
|
|
import SwiftParser
|
|
import SwiftParserDiagnostics
|
|
import SwiftSyntax
|
|
|
|
private typealias FileCacheKey = UUID
|
|
|
|
private let responseCache = Cache { file -> [String: any SourceKitRepresentable]? in
|
|
do {
|
|
return try Request.editorOpen(file: file.file).sendIfNotDisabled()
|
|
} catch let error as Request.Error {
|
|
queuedPrintError(error.description)
|
|
return nil
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
private let structureDictionaryCache = Cache { file in
|
|
responseCache.get(file).map(Structure.init).map { SourceKittenDictionary($0.dictionary) }
|
|
}
|
|
private let syntaxTreeCache = Cache { file -> SourceFileSyntax in
|
|
Parser.parse(source: file.contents)
|
|
}
|
|
private let foldedSyntaxTreeCache = Cache { file -> SourceFileSyntax? in
|
|
OperatorTable.standardOperators
|
|
.foldAll(file.syntaxTree) { _ in /* Don't handle errors. */ }
|
|
.as(SourceFileSyntax.self)
|
|
}
|
|
private let locationConverterCache = Cache { file -> SourceLocationConverter in
|
|
SourceLocationConverter(fileName: file.path ?? "<nopath>", tree: file.syntaxTree)
|
|
}
|
|
private let commandsCache = Cache { file -> [Command] in
|
|
guard file.contents.contains("swiftlint:") else {
|
|
return []
|
|
}
|
|
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 syntaxClassificationsCache = Cache { $0.syntaxTree.classifications }
|
|
private let linesWithTokensCache = Cache { $0.computeLinesWithTokens() }
|
|
private let swiftSyntaxTokensCache = Cache { file -> [SwiftLintSyntaxToken]? in
|
|
// Use SwiftSyntaxKindBridge to derive SourceKitten-compatible tokens from SwiftSyntax
|
|
SwiftSyntaxKindBridge.sourceKittenSyntaxKinds(for: file)
|
|
}
|
|
private let commentLinesCache = Cache { CommentLinesVisitor.commentLines(in: $0) }
|
|
private let emptyLinesCache = Cache { EmptyLinesVisitor.emptyLines(in: $0) }
|
|
|
|
package typealias AssertHandler = () -> Void
|
|
// Re-enable once all parser diagnostics in tests have been addressed.
|
|
// https://github.com/realm/SwiftLint/issues/3348
|
|
@TaskLocal package var parserDiagnosticsDisabledForTests = false
|
|
|
|
private let assertHandlerCache = Cache { (_: SwiftLintFile) -> AssertHandler? in nil }
|
|
|
|
private final class Cache<T>: Sendable {
|
|
nonisolated(unsafe) private var values = [FileCacheKey: T]()
|
|
private let factory: @Sendable (SwiftLintFile) -> T
|
|
private let lock = PlatformLock()
|
|
|
|
fileprivate init(_ factory: @escaping @Sendable (SwiftLintFile) -> T) {
|
|
self.factory = factory
|
|
}
|
|
|
|
fileprivate func get(_ file: SwiftLintFile) -> T {
|
|
let key = file.cacheKey
|
|
return lock.doLocked {
|
|
if let cachedValue = values[key] {
|
|
return cachedValue
|
|
}
|
|
let value = factory(file)
|
|
values[key] = value
|
|
return value
|
|
}
|
|
}
|
|
|
|
fileprivate func invalidate(_ file: SwiftLintFile) {
|
|
lock.doLocked { values.removeValue(forKey: file.cacheKey) }
|
|
}
|
|
|
|
fileprivate func clear() {
|
|
lock.doLocked { values.removeAll(keepingCapacity: false) }
|
|
}
|
|
|
|
fileprivate func set(key: FileCacheKey, value: T) {
|
|
lock.doLocked { values[key] = value }
|
|
}
|
|
|
|
fileprivate func unset(key: FileCacheKey) {
|
|
lock.doLocked { values.removeValue(forKey: key) }
|
|
}
|
|
}
|
|
|
|
extension SwiftLintFile {
|
|
fileprivate var cacheKey: FileCacheKey {
|
|
id
|
|
}
|
|
|
|
public var sourcekitdFailed: Bool {
|
|
get {
|
|
responseCache.get(self) == nil
|
|
}
|
|
set {
|
|
if newValue {
|
|
responseCache.set(key: cacheKey, value: nil)
|
|
} else {
|
|
responseCache.unset(key: cacheKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
package var assertHandler: AssertHandler? {
|
|
get {
|
|
assertHandlerCache.get(self)
|
|
}
|
|
set {
|
|
assertHandlerCache.set(key: cacheKey, value: newValue)
|
|
}
|
|
}
|
|
|
|
public var parserDiagnostics: [String] {
|
|
if parserDiagnosticsDisabledForTests {
|
|
return []
|
|
}
|
|
|
|
return ParseDiagnosticsGenerator.diagnostics(for: syntaxTree)
|
|
.filter { $0.diagMessage.severity == .error }
|
|
.map(\.message)
|
|
}
|
|
|
|
public var linesWithTokens: Set<Int> { linesWithTokensCache.get(self) }
|
|
|
|
public var structureDictionary: SourceKittenDictionary {
|
|
guard let structureDictionary = structureDictionaryCache.get(self) else {
|
|
if let handler = assertHandler {
|
|
handler()
|
|
return SourceKittenDictionary([:])
|
|
}
|
|
queuedFatalError("Never call this for file that sourcekitd fails.")
|
|
}
|
|
return structureDictionary
|
|
}
|
|
|
|
public var syntaxClassifications: SyntaxClassifications { syntaxClassificationsCache.get(self) }
|
|
|
|
public 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
|
|
}
|
|
|
|
public var syntaxTree: SourceFileSyntax { syntaxTreeCache.get(self) }
|
|
|
|
public var foldedSyntaxTree: SourceFileSyntax? { foldedSyntaxTreeCache.get(self) }
|
|
|
|
public var locationConverter: SourceLocationConverter { locationConverterCache.get(self) }
|
|
|
|
public var commands: [Command] { commandsCache.get(self).filter(\.isValid) }
|
|
|
|
public var invalidCommands: [Command] { commandsCache.get(self).filter { !$0.isValid } }
|
|
|
|
public var swiftSyntaxDerivedSourceKittenTokens: [SwiftLintSyntaxToken]? {
|
|
swiftSyntaxTokensCache.get(self)
|
|
}
|
|
|
|
public var commentLines: Set<Int> { commentLinesCache.get(self) }
|
|
public var emptyLines: Set<Int> { emptyLinesCache.get(self) }
|
|
|
|
/// Invalidates all cached data for this file.
|
|
public func invalidateCache() {
|
|
if !isVirtual {
|
|
file.clearCaches()
|
|
}
|
|
responseCache.invalidate(self)
|
|
assertHandlerCache.invalidate(self)
|
|
structureDictionaryCache.invalidate(self)
|
|
syntaxClassificationsCache.invalidate(self)
|
|
syntaxMapCache.invalidate(self)
|
|
swiftSyntaxTokensCache.invalidate(self)
|
|
syntaxTreeCache.invalidate(self)
|
|
foldedSyntaxTreeCache.invalidate(self)
|
|
locationConverterCache.invalidate(self)
|
|
commandsCache.invalidate(self)
|
|
linesWithTokensCache.invalidate(self)
|
|
commentLinesCache.invalidate(self)
|
|
emptyLinesCache.invalidate(self)
|
|
}
|
|
|
|
package static func clearCaches() {
|
|
responseCache.clear()
|
|
assertHandlerCache.clear()
|
|
structureDictionaryCache.clear()
|
|
syntaxClassificationsCache.clear()
|
|
syntaxMapCache.clear()
|
|
swiftSyntaxTokensCache.clear()
|
|
syntaxTreeCache.clear()
|
|
foldedSyntaxTreeCache.clear()
|
|
locationConverterCache.clear()
|
|
commandsCache.clear()
|
|
linesWithTokensCache.clear()
|
|
commentLinesCache.clear()
|
|
emptyLinesCache.clear()
|
|
}
|
|
}
|
|
|
|
private final class PlatformLock: Sendable {
|
|
#if canImport(Darwin)
|
|
nonisolated(unsafe) private let primitiveLock: UnsafeMutablePointer<os_unfair_lock>
|
|
#else
|
|
private let primitiveLock = NSLock()
|
|
#endif
|
|
|
|
init() {
|
|
#if canImport(Darwin)
|
|
primitiveLock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
|
|
primitiveLock.initialize(to: os_unfair_lock())
|
|
#endif
|
|
}
|
|
|
|
@discardableResult
|
|
func doLocked<U>(_ closure: () -> U) -> U {
|
|
#if canImport(Darwin)
|
|
os_unfair_lock_lock(primitiveLock)
|
|
defer { os_unfair_lock_unlock(primitiveLock) }
|
|
return closure()
|
|
#else
|
|
primitiveLock.lock()
|
|
defer { primitiveLock.unlock() }
|
|
return closure()
|
|
#endif
|
|
}
|
|
}
|