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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 SwiftSyntax
import SwiftParser
@_spi(Rules) import SwiftFormat
/// Collects information about rules in the formatter code base.
final class RuleCollector {
/// Information about a detected rule.
struct DetectedRule: Hashable {
/// The type name of the rule.
let typeName: String
/// The description of the rule, extracted from the rule class or struct DocC comment
/// with `DocumentationCommentText(extractedFrom:)`
let description: String?
/// The syntax node types visited by the rule type.
let visitedNodes: [String]
/// Indicates whether the rule can format code (all rules can lint).
let canFormat: Bool
/// Indicates whether the rule is disabled by default, i.e. requires opting in to use it.
let isOptIn: Bool
}
/// A list of all rules that can lint (thus also including format rules) found in the code base.
var allLinters = Set<DetectedRule>()
/// A list of all the format-only rules found in the code base.
var allFormatters = Set<DetectedRule>()
/// A dictionary mapping syntax node types to the lint/format rules that visit them.
var syntaxNodeLinters = [String: [String]]()
/// Populates the internal collections with rules in the given directory.
///
/// - Parameter url: The file system URL that should be scanned for rules.
func collect(from url: URL) throws {
// For each file in the Rules directory, find types that either conform to SyntaxLintRule or
// inherit from SyntaxFormatRule.
let fm = FileManager.default
guard let rulesEnumerator = fm.enumerator(atPath: url.path) else {
fatalError("Could not list the directory \(url.path)")
}
for baseName in rulesEnumerator {
// Ignore files that aren't Swift source files.
guard let baseName = baseName as? String, baseName.hasSuffix(".swift") else { continue }
let fileURL = url.appendingPathComponent(baseName)
let fileInput = try String(contentsOf: fileURL)
let sourceFile = Parser.parse(source: fileInput)
for statement in sourceFile.statements {
guard let detectedRule = self.detectedRule(at: statement) else { continue }
if detectedRule.canFormat {
// Format rules just get added to their own list; we run them each over the entire tree in
// succession.
allFormatters.insert(detectedRule)
}
// Lint rules (this includes format rules, which can also lint) get added to a mapping over
// the names of the types they touch so that they can be interleaved into one pass over the
// tree.
allLinters.insert(detectedRule)
for visitedNode in detectedRule.visitedNodes {
syntaxNodeLinters[visitedNode, default: []].append(detectedRule.typeName)
}
}
}
}
/// Determine the rule kind for the declaration in the given statement, if any.
private func detectedRule(at statement: CodeBlockItemSyntax) -> DetectedRule? {
let typeName: String
let members: MemberBlockItemListSyntax
let maybeInheritanceClause: InheritanceClauseSyntax?
let description = DocumentationCommentText(extractedFrom: statement.item.leadingTrivia)
if let classDecl = statement.item.as(ClassDeclSyntax.self) {
typeName = classDecl.name.text
members = classDecl.memberBlock.members
maybeInheritanceClause = classDecl.inheritanceClause
} else if let structDecl = statement.item.as(StructDeclSyntax.self) {
typeName = structDecl.name.text
members = structDecl.memberBlock.members
maybeInheritanceClause = structDecl.inheritanceClause
} else {
return nil
}
// Make sure it has an inheritance clause.
guard let inheritanceClause = maybeInheritanceClause else {
return nil
}
// Scan through the inheritance clause to find one of the protocols/types we're interested in.
for inheritance in inheritanceClause.inheritedTypes {
guard let identifier = inheritance.type.as(IdentifierTypeSyntax.self) else {
continue
}
let canFormat: Bool
switch identifier.name.text {
case "SyntaxLintRule":
canFormat = false
case "SyntaxFormatRule":
canFormat = true
default:
// Keep looking at the other inheritances.
continue
}
// Now that we know it's a format or lint rule, collect the `visit` methods.
var visitedNodes = [String]()
for member in members {
guard let function = member.decl.as(FunctionDeclSyntax.self) else { continue }
guard function.name.text == "visit" else { continue }
let params = function.signature.parameterClause.parameters
guard let firstType = params.firstAndOnly?.type.as(IdentifierTypeSyntax.self) else {
continue
}
visitedNodes.append(firstType.name.text)
}
/// Ignore it if it doesn't have any; there's no point in putting no-op rules in the pipeline.
/// Otherwise, return it (we don't need to look at the rest of the inheritances).
guard !visitedNodes.isEmpty else { return nil }
guard let ruleType = _typeByName("SwiftFormat.\(typeName)") as? Rule.Type else {
preconditionFailure("Failed to find type for rule named \(typeName)")
}
return DetectedRule(
typeName: typeName,
description: description?.text,
visitedNodes: visitedNodes,
canFormat: canFormat,
isOptIn: ruleType.isOptIn)
}
return nil
}
}
|