diff --git a/Sources/SwiftFormat/Core/LintPipeline.swift b/Sources/SwiftFormat/Core/LintPipeline.swift index 94e02e6c..3eb10072 100644 --- a/Sources/SwiftFormat/Core/LintPipeline.swift +++ b/Sources/SwiftFormat/Core/LintPipeline.swift @@ -29,8 +29,13 @@ extension LintPipeline { _ visitor: (Rule) -> (Node) -> SyntaxVisitorContinueKind, for node: Node ) { guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + let ruleId = ObjectIdentifier(Rule.self) + guard self.shouldSkipChildren[ruleId] == nil else { return } let rule = self.rule(Rule.self) - _ = visitor(rule)(node) + let continueKind = visitor(rule)(node) + if case .skipChildren = continueKind { + self.shouldSkipChildren[ruleId] = node + } } /// Calls the `visit` method of a rule for the given node if that rule is enabled for the node. @@ -50,10 +55,27 @@ extension LintPipeline { // cannot currently be expressed as constraints without duplicating this function for each of // them individually. guard context.isRuleEnabled(Rule.self, node: Syntax(node)) else { return } + guard self.shouldSkipChildren[ObjectIdentifier(Rule.self)] == nil else { return } let rule = self.rule(Rule.self) _ = visitor(rule)(node) } + /// Cleans up any state associated with `rule` when we leave syntax node `node` + /// + /// - Parameters: + /// - rule: The type of the syntax rule we're cleaning up. + /// - node: The syntax node htat our traversal has left. + func onVisitPost( + rule: R.Type, for node: Node + ) { + let rule = ObjectIdentifier(rule) + if case .some(let skipNode) = self.shouldSkipChildren[rule] { + if node.id == skipNode.id { + self.shouldSkipChildren.removeValue(forKey: rule) + } + } + } + /// Retrieves an instance of a lint or format rule based on its type. /// /// There is at most 1 instance of each rule allocated per `LintPipeline`. This method will diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 6e961582..9707ee80 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -26,6 +26,10 @@ class LintPipeline: SyntaxVisitor { /// class type. var ruleCache = [ObjectIdentifier: Rule]() + /// Rules present in this dictionary skip visiting children until they leave the + /// syntax node stored as their value + var shouldSkipChildren = [ObjectIdentifier: SyntaxProtocol]() + /// Creates a new lint pipeline. init(context: Context) { self.context = context @@ -36,11 +40,17 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } + override func visitPost(_ node: ActorDeclSyntax) { + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + } override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } + override func visitPost(_ node: AsExprSyntax) { + onVisitPost(rule: NeverForceUnwrap.self, for: node) + } override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) @@ -48,6 +58,11 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(TypeNamesShouldBeCapitalized.visit, for: node) return .visitChildren } + override func visitPost(_ node: AssociatedTypeDeclSyntax) { + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -59,22 +74,41 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClassDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(OmitExplicitReturns.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureExprSyntax) { + onVisitPost(rule: OmitExplicitReturns.self, for: node) + } override func visit(_ node: ClosureParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: ClosureSignatureSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) return .visitChildren } + override func visitPost(_ node: ClosureSignatureSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: ReturnVoidInsteadOfEmptyTuple.self, for: node) + } override func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) @@ -83,17 +117,30 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseEarlyExits.visit, for: node) return .visitChildren } + override func visitPost(_ node: CodeBlockItemListSyntax) { + onVisitPost(rule: DoNotUseSemicolons.self, for: node) + onVisitPost(rule: NoAssignmentInExpressions.self, for: node) + onVisitPost(rule: OneVariableDeclarationPerLine.self, for: node) + onVisitPost(rule: UseEarlyExits.self, for: node) + } override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } + override func visitPost(_ node: CodeBlockSyntax) { + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + } override func visit(_ node: ConditionElementSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) visitIfEnabled(UseExplicitNilCheckInConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: ConditionElementSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + onVisitPost(rule: UseExplicitNilCheckInConditions.self, for: node) + } override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -101,17 +148,29 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: DeinitializerDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumCaseElementSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: EnumCaseParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumCaseParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(BeginDocumentationCommentWithOneLineSummary.visit, for: node) @@ -123,6 +182,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: EnumDeclSyntax) { + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: FullyIndirectEnum.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: OneCasePerLine.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DontRepeatTypeInStaticProperties.visit, for: node) @@ -130,16 +198,27 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ExtensionDeclSyntax) { + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoAccessLevelOnExtensionDeclaration.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseWhereClausesInForLoops.visit, for: node) return .visitChildren } + override func visitPost(_ node: ForStmtSyntax) { + onVisitPost(rule: UseWhereClausesInForLoops.self, for: node) + } override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverForceUnwrap.visit, for: node) return .visitChildren } + override func visitPost(_ node: ForceUnwrapExprSyntax) { + onVisitPost(rule: NeverForceUnwrap.self, for: node) + } override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoEmptyTrailingClosureParentheses.visit, for: node) @@ -147,6 +226,11 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ReplaceForEachWithForLoop.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionCallExprSyntax) { + onVisitPost(rule: NoEmptyTrailingClosureParentheses.self, for: node) + onVisitPost(rule: OnlyOneTrailingClosureArgument.self, for: node) + onVisitPost(rule: ReplaceForEachWithForLoop.self, for: node) + } override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -158,58 +242,99 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + onVisitPost(rule: ValidateDocumentationComments.self, for: node) + } override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionParameterSyntax) { + onVisitPost(rule: AlwaysUseLiteralForEmptyCollectionInit.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoVoidReturnOnFunctionSignature.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionSignatureSyntax) { + onVisitPost(rule: NoVoidReturnOnFunctionSignature.self, for: node) + } override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(ReturnVoidInsteadOfEmptyTuple.visit, for: node) return .visitChildren } + override func visitPost(_ node: FunctionTypeSyntax) { + onVisitPost(rule: ReturnVoidInsteadOfEmptyTuple.self, for: node) + } override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: GenericParameterSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: GenericSpecializationExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } + override func visitPost(_ node: GenericSpecializationExprSyntax) { + onVisitPost(rule: UseShorthandTypeNames.self, for: node) + } override func visit(_ node: GuardStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: GuardStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(IdentifiersMustBeASCII.visit, for: node) visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: IdentifierPatternSyntax) { + onVisitPost(rule: IdentifiersMustBeASCII.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: IdentifierTypeSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseShorthandTypeNames.visit, for: node) return .visitChildren } + override func visitPost(_ node: IdentifierTypeSyntax) { + onVisitPost(rule: UseShorthandTypeNames.self, for: node) + } override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: IfExprSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: InfixOperatorExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoAssignmentInExpressions.visit, for: node) return .visitChildren } + override func visitPost(_ node: InfixOperatorExprSyntax) { + onVisitPost(rule: NoAssignmentInExpressions.self, for: node) + } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -218,31 +343,52 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(ValidateDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: InitializerDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + onVisitPost(rule: ValidateDocumentationComments.self, for: node) + } override func visit(_ node: IntegerLiteralExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(GroupNumericLiterals.visit, for: node) return .visitChildren } + override func visitPost(_ node: IntegerLiteralExprSyntax) { + onVisitPost(rule: GroupNumericLiterals.self, for: node) + } override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoPlaygroundLiterals.visit, for: node) return .visitChildren } + override func visitPost(_ node: MacroExpansionExprSyntax) { + onVisitPost(rule: NoPlaygroundLiterals.self, for: node) + } override func visit(_ node: MemberBlockItemListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(DoNotUseSemicolons.visit, for: node) return .visitChildren } + override func visitPost(_ node: MemberBlockItemListSyntax) { + onVisitPost(rule: DoNotUseSemicolons.self, for: node) + } override func visit(_ node: MemberBlockSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AmbiguousTrailingClosureOverload.visit, for: node) return .visitChildren } + override func visitPost(_ node: MemberBlockSyntax) { + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + } override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) return .visitChildren } + override func visitPost(_ node: OptionalBindingConditionSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + } override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLiteralForEmptyCollectionInit.visit, for: node) @@ -250,11 +396,19 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseSingleLinePropertyGetter.visit, for: node) return .visitChildren } + override func visitPost(_ node: PatternBindingSyntax) { + onVisitPost(rule: AlwaysUseLiteralForEmptyCollectionInit.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseSingleLinePropertyGetter.self, for: node) + } override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLeadingUnderscores.visit, for: node) return .visitChildren } + override func visitPost(_ node: PrecedenceGroupDeclSyntax) { + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -265,11 +419,22 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: ProtocolDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: RepeatStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: RepeatStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AlwaysUseLowerCamelCase.visit, for: node) @@ -281,6 +446,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(OrderedImports.visit, for: node) return .visitChildren } + override func visitPost(_ node: SourceFileSyntax) { + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: AmbiguousTrailingClosureOverload.self, for: node) + onVisitPost(rule: FileScopedDeclarationPrivacy.self, for: node) + onVisitPost(rule: NeverForceUnwrap.self, for: node) + onVisitPost(rule: NeverUseForceTry.self, for: node) + onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node) + onVisitPost(rule: OrderedImports.self, for: node) + } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -292,6 +466,15 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: StructDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: DontRepeatTypeInStaticProperties.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseSynthesizedInitializer.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -300,31 +483,52 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: SubscriptDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: OmitExplicitReturns.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: SwitchCaseLabelSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoLabelsInCasePatterns.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchCaseLabelSyntax) { + onVisitPost(rule: NoLabelsInCasePatterns.self, for: node) + } override func visit(_ node: SwitchCaseListSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoCasesWithOnlyFallthrough.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchCaseListSyntax) { + onVisitPost(rule: NoCasesWithOnlyFallthrough.self, for: node) + } override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: SwitchExprSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoBlockComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: TokenSyntax) { + onVisitPost(rule: NoBlockComments.self, for: node) + } override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NeverUseForceTry.visit, for: node) return .visitChildren } + override func visitPost(_ node: TryExprSyntax) { + onVisitPost(rule: NeverUseForceTry.self, for: node) + } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -334,11 +538,21 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: TypeAliasDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NoLeadingUnderscores.self, for: node) + onVisitPost(rule: TypeNamesShouldBeCapitalized.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(UseLetInEveryBoundCaseVariable.visit, for: node) return .visitChildren } + override func visitPost(_ node: ValueBindingPatternSyntax) { + onVisitPost(rule: UseLetInEveryBoundCaseVariable.self, for: node) + } override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(AllPublicDeclarationsHaveDocumentation.visit, for: node) @@ -348,11 +562,21 @@ class LintPipeline: SyntaxVisitor { visitIfEnabled(UseTripleSlashForDocumentationComments.visit, for: node) return .visitChildren } + override func visitPost(_ node: VariableDeclSyntax) { + onVisitPost(rule: AllPublicDeclarationsHaveDocumentation.self, for: node) + onVisitPost(rule: AlwaysUseLowerCamelCase.self, for: node) + onVisitPost(rule: BeginDocumentationCommentWithOneLineSummary.self, for: node) + onVisitPost(rule: NeverUseImplicitlyUnwrappedOptionals.self, for: node) + onVisitPost(rule: UseTripleSlashForDocumentationComments.self, for: node) + } override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind { visitIfEnabled(NoParensAroundConditions.visit, for: node) return .visitChildren } + override func visitPost(_ node: WhileStmtSyntax) { + onVisitPost(rule: NoParensAroundConditions.self, for: node) + } } extension FormatPipeline { diff --git a/Sources/generate-swift-format/PipelineGenerator.swift b/Sources/generate-swift-format/PipelineGenerator.swift index abf8b2c0..e2c2c8d0 100644 --- a/Sources/generate-swift-format/PipelineGenerator.swift +++ b/Sources/generate-swift-format/PipelineGenerator.swift @@ -54,6 +54,10 @@ final class PipelineGenerator: FileGenerator { /// class type. var ruleCache = [ObjectIdentifier: Rule]() + /// Rules present in this dictionary skip visiting children until they leave the + /// syntax node stored as their value + var shouldSkipChildren = [ObjectIdentifier: SyntaxProtocol]() + /// Creates a new lint pipeline. init(context: Context) { self.context = context @@ -85,6 +89,25 @@ final class PipelineGenerator: FileGenerator { } """) + + handle.write( + """ + override func visitPost(_ node: \(nodeType)) { + """ + ) + for ruleName in lintRules.sorted() { + handle.write( + """ + onVisitPost(rule: \(ruleName).self, for: node) + + """) + } + handle.write( + """ + } + + """ + ) } handle.write( diff --git a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift index e9c342f9..16ddda95 100644 --- a/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift +++ b/Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift @@ -50,6 +50,27 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { context: context, file: file, line: line) + + var emittedPipelineFindings = [Finding]() + // Disable default rules, so only select rule runs in pipeline + configuration.rules = [type.ruleName: true] + let pipeline = SwiftLinter( + configuration: configuration, + findingConsumer: { emittedPipelineFindings.append($0) }) + pipeline.debugOptions.insert(.disablePrettyPrint) + try! pipeline.lint( + syntax: sourceFileSyntax, + operatorTable: OperatorTable.standardOperators, + assumingFileURL: URL(string: file.description)!) + + // Check that pipeline produces the same findings as the isolated linter rule + assertFindings( + expected: findings, + markerLocations: markedText.markers, + emittedFindings: emittedPipelineFindings, + context: context, + file: file, + line: line) } /// Asserts that the result of applying a formatter to the provided input code yields the output. @@ -111,5 +132,20 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase { printTokenStream: false, whitespaceOnly: false ).prettyPrint() + + var emittedPipelineFindings = [Finding]() + // Disable default rules, so only select rule runs in pipeline + configuration.rules = [formatType.ruleName: true] + let pipeline = SwiftFormatter( + configuration: configuration, findingConsumer: { emittedPipelineFindings.append($0) }) + pipeline.debugOptions.insert(.disablePrettyPrint) + var pipelineActual = "" + try! pipeline.format( + syntax: sourceFileSyntax, operatorTable: OperatorTable.standardOperators, + assumingFileURL: nil, to: &pipelineActual) + assertStringsEqualWithDiff(pipelineActual, expected) + assertFindings( + expected: findings, markerLocations: markedInput.markers, + emittedFindings: emittedPipelineFindings, context: context, file: file, line: line) } }