1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import SwiftOperators
import SwiftSyntax
import SwiftParser
/// Context contains the bits that each formatter and linter will need access to.
///
/// Specifically, it is the container for the shared configuration, diagnostic consumer, and URL of
/// the current file.
@_spi(Rules)
public final class Context {
/// Tracks whether `XCTest` has been imported so that certain logic can be modified for files that
/// are known to be tests.
public enum XCTestImportState {
/// Whether `XCTest` is imported or not has not yet been determined.
case notDetermined
/// The file is known to import `XCTest`.
case importsXCTest
/// The file is known to not import `XCTest`.
case doesNotImportXCTest
}
/// The configuration for this run of the pipeline, provided by a configuration JSON file.
let configuration: Configuration
/// The selection to process
let selection: Selection
/// Defines the operators and their precedence relationships that were used during parsing.
let operatorTable: OperatorTable
/// Emits findings to the finding consumer.
let findingEmitter: FindingEmitter
/// The URL of the file being linted or formatted.
let fileURL: URL
/// Indicates whether the file is known to import XCTest.
public var importsXCTest: XCTestImportState
/// An object that converts `AbsolutePosition` values to `SourceLocation` values.
public let sourceLocationConverter: SourceLocationConverter
/// Contains the rules have been disabled by comments for certain line numbers.
let ruleMask: RuleMask
/// Contains all the available rules' names associated to their types' object identifiers.
let ruleNameCache: [ObjectIdentifier: String]
/// Creates a new Context with the provided configuration, diagnostic engine, and file URL.
public init(
configuration: Configuration,
operatorTable: OperatorTable,
findingConsumer: ((Finding) -> Void)?,
fileURL: URL,
selection: Selection = .infinite,
sourceFileSyntax: SourceFileSyntax,
source: String? = nil,
ruleNameCache: [ObjectIdentifier: String]
) {
self.configuration = configuration
self.operatorTable = operatorTable
self.findingEmitter = FindingEmitter(consumer: findingConsumer)
self.fileURL = fileURL
self.selection = selection
self.importsXCTest = .notDetermined
let tree = source.map { Parser.parse(source: $0) } ?? sourceFileSyntax
self.sourceLocationConverter =
SourceLocationConverter(fileName: fileURL.relativePath, tree: tree)
self.ruleMask = RuleMask(
syntaxNode: Syntax(sourceFileSyntax),
sourceLocationConverter: sourceLocationConverter
)
self.ruleNameCache = ruleNameCache
}
/// Given a rule's name and the node it is examining, determine if the rule is disabled at this
/// location or not. Also makes sure the entire node is contained inside any selection.
func shouldFormat<R: Rule>(_ rule: R.Type, node: Syntax) -> Bool {
guard node.isInsideSelection(selection) else { return false }
let loc = node.startLocation(converter: self.sourceLocationConverter)
assert(
ruleNameCache[ObjectIdentifier(rule)] != nil,
"""
Missing cached rule name for '\(rule)'! \
Ensure `generate-swift-format` has been run and `ruleNameCache` was injected.
""")
let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName
switch ruleMask.ruleState(ruleName, at: loc) {
case .default:
return configuration.rules[ruleName] ?? false
case .disabled:
return false
}
}
}
|