Compare commits
1 Commits
main
...
marcelo/qu
Author | SHA1 | Date |
---|---|---|
![]() |
e400174591 |
|
@ -144,6 +144,7 @@
|
|||
- `prohibited_interface_builder`
|
||||
- `prohibited_super_call`
|
||||
- `protocol_property_accessors_order`
|
||||
- `quick_discouraged_call`
|
||||
- `quick_discouraged_focused_test`
|
||||
- `quick_discouraged_pending_test`
|
||||
- `raw_value_for_camel_cased_codable_enum`
|
||||
|
|
|
@ -188,6 +188,12 @@ extension FunctionDeclSyntax {
|
|||
return SuperCallVisitor(expectedFunctionName: identifier.text)
|
||||
.walk(tree: body, handler: \.superCallsCount)
|
||||
}
|
||||
|
||||
var isQuickSpecFunction: Bool {
|
||||
return identifier.tokenKind == .identifier("spec") &&
|
||||
signature.input.parameterList.isEmpty &&
|
||||
modifiers.containsOverride
|
||||
}
|
||||
}
|
||||
|
||||
extension AccessorBlockSyntax {
|
||||
|
@ -220,6 +226,16 @@ extension Trivia {
|
|||
}
|
||||
}
|
||||
|
||||
extension ClassDeclSyntax {
|
||||
var containsInheritance: Bool {
|
||||
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
|
||||
return false
|
||||
}
|
||||
|
||||
return inheritanceList.isNotEmpty
|
||||
}
|
||||
}
|
||||
|
||||
extension IntegerLiteralExprSyntax {
|
||||
var isZero: Bool {
|
||||
guard case let .integerLiteral(number) = digits.tokenKind else {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import SourceKittenFramework
|
||||
import SwiftSyntax
|
||||
|
||||
public struct QuickDiscouragedCallRule: OptInRule, ConfigurationProviderRule {
|
||||
public struct QuickDiscouragedCallRule: OptInRule, SwiftSyntaxRule, ConfigurationProviderRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
@ -14,92 +14,78 @@ public struct QuickDiscouragedCallRule: OptInRule, ConfigurationProviderRule {
|
|||
triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
|
||||
)
|
||||
|
||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
let dict = file.structureDictionary
|
||||
let testClasses = dict.substructure.filter {
|
||||
return $0.inheritedTypes.isNotEmpty &&
|
||||
$0.declarationKind == .class
|
||||
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
||||
Visitor(viewMode: .sourceAccurate)
|
||||
}
|
||||
}
|
||||
|
||||
private extension QuickDiscouragedCallRule {
|
||||
final class Visitor: ViolationsSyntaxVisitor {
|
||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
|
||||
.all
|
||||
}
|
||||
|
||||
let specDeclarations = testClasses.flatMap { classDict in
|
||||
return classDict.substructure.filter {
|
||||
return $0.name == "spec()" && $0.enclosedVarParameters.isEmpty &&
|
||||
$0.declarationKind == .functionMethodInstance &&
|
||||
$0.enclosedSwiftAttributes.contains(.override)
|
||||
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
|
||||
guard let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
|
||||
case let name = identifierExpr.identifier.text,
|
||||
let kind = QuickCallKind(rawValue: name),
|
||||
QuickCallKind.restrictiveKinds.contains(kind) else {
|
||||
return .skipChildren
|
||||
}
|
||||
|
||||
let functionViolations = FunctionCallVisitor(nameToReport: name)
|
||||
.walk(tree: node, handler: \.violations)
|
||||
violations.append(contentsOf: functionViolations.unique)
|
||||
return .skipChildren
|
||||
}
|
||||
|
||||
return specDeclarations.flatMap {
|
||||
validate(file: file, dictionary: $0)
|
||||
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
node.containsInheritance ? .visitChildren : .skipChildren
|
||||
}
|
||||
|
||||
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
node.isQuickSpecFunction ? .visitChildren : .skipChildren
|
||||
}
|
||||
}
|
||||
|
||||
private func validate(file: SwiftLintFile, dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
||||
return dictionary.traverseDepthFirst { subDict in
|
||||
guard let kind = subDict.expressionKind else { return nil }
|
||||
return validate(file: file, kind: kind, dictionary: subDict)
|
||||
private class FunctionCallVisitor: ViolationsSyntaxVisitor {
|
||||
private let nameToReport: String
|
||||
|
||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
|
||||
.allExcept(VariableDeclSyntax.self)
|
||||
}
|
||||
}
|
||||
|
||||
private func validate(file: SwiftLintFile,
|
||||
kind: SwiftExpressionKind,
|
||||
dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
||||
// is it a call to a restricted method?
|
||||
guard
|
||||
kind == .call,
|
||||
let name = dictionary.name,
|
||||
let kindName = QuickCallKind(rawValue: name),
|
||||
QuickCallKind.restrictiveKinds.contains(kindName)
|
||||
else { return [] }
|
||||
|
||||
return violationOffsets(in: dictionary.enclosedArguments).map {
|
||||
StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: $0),
|
||||
reason: "Discouraged call inside a '\(name)' block.")
|
||||
init(nameToReport: String) {
|
||||
self.nameToReport = nameToReport
|
||||
super.init(viewMode: .sourceAccurate)
|
||||
}
|
||||
}
|
||||
|
||||
private func violationOffsets(in substructure: [SourceKittenDictionary]) -> [ByteCount] {
|
||||
return substructure.flatMap { dictionary -> [ByteCount] in
|
||||
let substructure = dictionary.substructure.flatMap { dict -> [SourceKittenDictionary] in
|
||||
if dict.expressionKind == .closure {
|
||||
return dict.substructure
|
||||
} else {
|
||||
return [dict]
|
||||
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
|
||||
let hasViolation: Bool
|
||||
|
||||
if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self) {
|
||||
let name = identifierExpr.identifier.text
|
||||
if let kind = QuickCallKind(rawValue: name), QuickCallKind.restrictiveKinds.contains(kind) {
|
||||
return .visitChildren
|
||||
}
|
||||
|
||||
hasViolation = QuickCallKind(rawValue: name) == nil
|
||||
} else {
|
||||
hasViolation = true
|
||||
}
|
||||
|
||||
return substructure.flatMap(toViolationOffsets)
|
||||
if hasViolation {
|
||||
violations.append(
|
||||
ReasonedRuleViolation(
|
||||
position: node.positionAfterSkippingLeadingTrivia,
|
||||
reason: "Discouraged call inside a '\(nameToReport)' block."
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return .skipChildren
|
||||
}
|
||||
}
|
||||
|
||||
private func toViolationOffsets(dictionary: SourceKittenDictionary) -> [ByteCount] {
|
||||
guard
|
||||
dictionary.kind != nil,
|
||||
let offset = dictionary.offset
|
||||
else { return [] }
|
||||
|
||||
if dictionary.expressionKind == .call,
|
||||
let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
|
||||
return [offset]
|
||||
}
|
||||
|
||||
guard dictionary.expressionKind != .call else { return [] }
|
||||
|
||||
return dictionary.substructure.compactMap(toViolationOffset)
|
||||
}
|
||||
|
||||
private func toViolationOffset(dictionary: SourceKittenDictionary) -> ByteCount? {
|
||||
guard
|
||||
let name = dictionary.name,
|
||||
let offset = dictionary.offset,
|
||||
dictionary.expressionKind == .call,
|
||||
QuickCallKind(rawValue: name) == nil
|
||||
else { return nil }
|
||||
|
||||
return offset
|
||||
}
|
||||
}
|
||||
|
||||
private enum QuickCallKind: String {
|
||||
|
@ -107,6 +93,7 @@ private enum QuickCallKind: String {
|
|||
case context
|
||||
case sharedExamples
|
||||
case itBehavesLike
|
||||
case aroundEach
|
||||
case beforeEach
|
||||
case beforeSuite
|
||||
case afterEach
|
||||
|
|
|
@ -139,7 +139,12 @@ internal struct QuickDiscouragedCallRuleExamples {
|
|||
xitBehavesLike("foo")
|
||||
}
|
||||
}
|
||||
""")
|
||||
"""),
|
||||
Example("""
|
||||
class Foo: Bar {
|
||||
let a = something()
|
||||
}
|
||||
""", excludeFromDocumentation: true)
|
||||
]
|
||||
|
||||
static let triggeringExamples: [Example] = [
|
||||
|
@ -169,6 +174,22 @@ internal struct QuickDiscouragedCallRuleExamples {
|
|||
}
|
||||
"""),
|
||||
Example("""
|
||||
class TotoTests: QuickSpec {
|
||||
override func spec() {
|
||||
describe("foo") {
|
||||
context("bar") {
|
||||
let foo = ↓Foo()
|
||||
↓foo.bar()
|
||||
it("does something") {
|
||||
let bar = Bar()
|
||||
bar.toto()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""),
|
||||
Example("""
|
||||
class TotoTests: QuickSpec {
|
||||
override func spec() {
|
||||
describe("foo") {
|
||||
|
|
|
@ -36,29 +36,11 @@ private extension QuickDiscouragedFocusedTestRule {
|
|||
}
|
||||
|
||||
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
node.isSpecFunction ? .visitChildren : .skipChildren
|
||||
node.isQuickSpecFunction ? .visitChildren : .skipChildren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClassDeclSyntax {
|
||||
var containsInheritance: Bool {
|
||||
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
|
||||
return false
|
||||
}
|
||||
|
||||
return inheritanceList.isNotEmpty
|
||||
}
|
||||
}
|
||||
|
||||
private extension FunctionDeclSyntax {
|
||||
var isSpecFunction: Bool {
|
||||
return identifier.tokenKind == .identifier("spec") &&
|
||||
signature.input.parameterList.isEmpty &&
|
||||
modifiers.containsOverride
|
||||
}
|
||||
}
|
||||
|
||||
private enum QuickFocusedCallKind: String {
|
||||
case fdescribe
|
||||
case fcontext
|
||||
|
|
|
@ -36,29 +36,11 @@ private extension QuickDiscouragedPendingTestRule {
|
|||
}
|
||||
|
||||
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||
node.isSpecFunction ? .visitChildren : .skipChildren
|
||||
node.isQuickSpecFunction ? .visitChildren : .skipChildren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClassDeclSyntax {
|
||||
var containsInheritance: Bool {
|
||||
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
|
||||
return false
|
||||
}
|
||||
|
||||
return inheritanceList.isNotEmpty
|
||||
}
|
||||
}
|
||||
|
||||
private extension FunctionDeclSyntax {
|
||||
var isSpecFunction: Bool {
|
||||
return identifier.tokenKind == .identifier("spec") &&
|
||||
signature.input.parameterList.isEmpty &&
|
||||
modifiers.containsOverride
|
||||
}
|
||||
}
|
||||
|
||||
private enum QuickPendingCallKind: String {
|
||||
case pending
|
||||
case xdescribe
|
||||
|
|
Loading…
Reference in New Issue