@@ -1015,6 +1015,11 @@ private final class TokenStreamCreator: SyntaxVisitor {
1015
1015
}
1016
1016
1017
1017
func visit( _ node: MemberDeclListItemSyntax ) -> SyntaxVisitorContinueKind {
1018
+ if shouldFormatterIgnore ( node: node) {
1019
+ appendFormatterIgnored ( node: node)
1020
+ return . skipChildren
1021
+ }
1022
+
1018
1023
before ( node. firstToken, tokens: . open)
1019
1024
let resetSize = node. semicolon != nil ? 1 : 0
1020
1025
after ( node. lastToken, tokens: . close, . break( . reset, size: resetSize) )
@@ -1117,6 +1122,11 @@ private final class TokenStreamCreator: SyntaxVisitor {
1117
1122
}
1118
1123
1119
1124
func visit( _ node: CodeBlockItemSyntax ) -> SyntaxVisitorContinueKind {
1125
+ if shouldFormatterIgnore ( node: node) {
1126
+ appendFormatterIgnored ( node: node)
1127
+ return . skipChildren
1128
+ }
1129
+
1120
1130
before ( node. firstToken, tokens: . open)
1121
1131
let resetSize = node. semicolon != nil ? 1 : 0
1122
1132
after ( node. lastToken, tokens: . close, . break( . reset, size: resetSize) )
@@ -2655,6 +2665,86 @@ private final class TokenStreamCreator: SyntaxVisitor {
2655
2665
// always parse the former as "4 +- 5".
2656
2666
return true
2657
2667
}
2668
+
2669
+ /// Appends the given node to the token stream without applying any formatting or printing tokens.
2670
+ ///
2671
+ /// - Parameter node: A node that is ignored by the formatter.
2672
+ private func appendFormatterIgnored( node: Syntax ) {
2673
+ // The first line of text in the `verbatim` token is printed with correct indentation, based on
2674
+ // the previous tokens. The leading trivia of the first token needs to be excluded from the
2675
+ // `verbatim` token in order for the first token to be printed with correct indentation. All
2676
+ // following lines in the ignored node are printed as-is with no changes to indentation.
2677
+ var nodeText = node. description
2678
+ if let firstToken = node. firstToken {
2679
+ extractLeadingTrivia ( firstToken)
2680
+ let leadingTriviaText = firstToken. leadingTrivia. reduce ( into: " " ) { $1. write ( to: & $0) }
2681
+ nodeText = String ( nodeText. dropFirst ( leadingTriviaText. count) )
2682
+ }
2683
+
2684
+ // The leading trivia of the next token, after the ignored node, may contain content that
2685
+ // belongs with the ignored node. The trivia extraction that is performed for `lastToken` later
2686
+ // excludes that content so it needs to be extracted and added to the token stream here.
2687
+ if let next = node. lastToken? . nextToken, let trivia = next. leadingTrivia. first {
2688
+ switch trivia {
2689
+ case . lineComment, . blockComment:
2690
+ trivia. write ( to: & nodeText)
2691
+ break
2692
+ default :
2693
+ // All other kinds of trivia are inserted into the token stream by `extractLeadingTrivia`
2694
+ // when the relevant token is visited.
2695
+ break
2696
+ }
2697
+ }
2698
+
2699
+ appendToken ( . verbatim( Verbatim ( text: nodeText, indentingBehavior: . firstLine) ) )
2700
+
2701
+ // Add this break so that trivia parsing will allow discretionary newlines after the node.
2702
+ appendToken ( . break( . same, size: 0 ) )
2703
+ }
2704
+
2705
+ /// Returns whether the given trivia includes a directive to ignore formatting for the next node.
2706
+ ///
2707
+ /// - Parameter trivia: Leading trivia for a node that the formatter supports ignoring.
2708
+ private func isFormatterIgnorePresent( inTrivia trivia: Trivia ) -> Bool {
2709
+ func isFormatterIgnore( in commentText: String , prefix: String , suffix: String ) -> Bool {
2710
+ let trimmed =
2711
+ commentText. dropFirst ( prefix. count)
2712
+ . dropLast ( suffix. count)
2713
+ . trimmingCharacters ( in: . whitespaces)
2714
+ return trimmed == " swift-format-ignore "
2715
+ }
2716
+
2717
+ for piece in trivia {
2718
+ switch piece {
2719
+ case . lineComment( let text) :
2720
+ if isFormatterIgnore ( in: text, prefix: " // " , suffix: " " ) { return true }
2721
+ break
2722
+ case . blockComment( let text) :
2723
+ if isFormatterIgnore ( in: text, prefix: " /*", suffix: "*/" ) { return true }
2724
+ break
2725
+ default :
2726
+ break
2727
+ }
2728
+ }
2729
+ return false
2730
+ }
2731
+
2732
+ /// Returns whether the formatter should ignore the given node by printing it without changing the
2733
+ /// node's internal text representation (i.e. print all text inside of the node as it was in the
2734
+ /// original source).
2735
+ ///
2736
+ /// - Note: The caller is responsible for ensuring that the given node is a type of node that can
2737
+ /// be safely ignored.
2738
+ ///
2739
+ /// - Parameter node: A node that can be safely ignored.
2740
+ private func shouldFormatterIgnore( node: Syntax ) -> Bool {
2741
+ // Regardless of the level of nesting, if the ignore directive is present on the first token
2742
+ // contained within the node then the entire node is eligible for ignoring.
2743
+ if let firstTrivia = node. firstToken? . leadingTrivia {
2744
+ return isFormatterIgnorePresent ( inTrivia: firstTrivia)
2745
+ }
2746
+ return false
2747
+ }
2658
2748
}
2659
2749
2660
2750
extension Syntax {
0 commit comments