@@ -35,6 +35,16 @@ import SwiftSyntax
3535/// 2. | let a = 123
3636/// Ignores `RuleName` and `OtherRuleName` for line 2.
3737///
38+ /// 1. | // swift-format-ignore-file: RuleName
39+ /// 2. | let a = 123
40+ /// 3. | class Foo { }
41+ /// Ignores `RuleName` for the entire file (lines 2-3).
42+ ///
43+ /// 1. | // swift-format-ignore-file: RuleName, OtherRuleName
44+ /// 2. | let a = 123
45+ /// 3. | class Foo { }
46+ /// Ignores `RuleName` and `OtherRuleName` for the entire file (lines 2-3).
47+ ///
3848/// The rules themselves reference RuleMask to see if it is disabled for the line it is currently
3949/// examining.
4050@_spi ( Testing)
@@ -115,7 +125,7 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
115125 private let ignoreRegex : NSRegularExpression
116126
117127 /// Regex pattern to match an ignore comment that applies to an entire file.
118- private let ignoreFilePattern = #"^\s*\/\/\s*swift-format-ignore-file$ "#
128+ private let ignoreFilePattern = #"^\s*\/\/\s*swift-format-ignore-file((:\s+(([A-z0-9]+[,\s]*)+))?$|\s+$) "#
119129
120130 /// Rule ignore regex object.
121131 private let ignoreFileRegex : NSRegularExpression
@@ -140,40 +150,28 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
140150 guard let firstToken = node. firstToken ( viewMode: . sourceAccurate) else {
141151 return . visitChildren
142152 }
143- let comments = loneLineComments ( in: firstToken. leadingTrivia, isFirstToken: true )
144- var foundIgnoreFileComment = false
145- for comment in comments {
146- let range = NSRange ( comment. startIndex..< comment. endIndex, in: comment)
147- if ignoreFileRegex. firstMatch ( in: comment, options: [ ] , range: range) != nil {
148- foundIgnoreFileComment = true
149- break
150- }
151- }
152- guard foundIgnoreFileComment else {
153- return . visitChildren
154- }
155-
156153 let sourceRange = node. sourceRange (
157154 converter: sourceLocationConverter,
158155 afterLeadingTrivia: false ,
159156 afterTrailingTrivia: true
160157 )
161- allRulesIgnoredRanges. append ( sourceRange)
162- return . skipChildren
158+ return appendRuleStatusDirectives ( from: firstToken, of: sourceRange, using: ignoreFileRegex)
163159 }
164160
165161 override func visit( _ node: CodeBlockItemSyntax ) -> SyntaxVisitorContinueKind {
166162 guard let firstToken = node. firstToken ( viewMode: . sourceAccurate) else {
167163 return . visitChildren
168164 }
169- return appendRuleStatusDirectives ( from: firstToken, of: Syntax ( node) )
165+ let sourceRange = node. sourceRange ( converter: sourceLocationConverter)
166+ return appendRuleStatusDirectives ( from: firstToken, of: sourceRange, using: ignoreRegex)
170167 }
171168
172169 override func visit( _ node: MemberBlockItemSyntax ) -> SyntaxVisitorContinueKind {
173170 guard let firstToken = node. firstToken ( viewMode: . sourceAccurate) else {
174171 return . visitChildren
175172 }
176- return appendRuleStatusDirectives ( from: firstToken, of: Syntax ( node) )
173+ let sourceRange = node. sourceRange ( converter: sourceLocationConverter)
174+ return appendRuleStatusDirectives ( from: firstToken, of: sourceRange, using: ignoreRegex)
177175 }
178176
179177 // MARK: - Helper Methods
@@ -183,17 +181,19 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
183181 ///
184182 /// - Parameters:
185183 /// - token: A token that may have comments that modify the status of rules.
186- /// - node: The node to which the token belongs.
184+ /// - sourceRange: The range covering the node to which `token` belongs. If an ignore directive
185+ /// is found among the comments, this entire range is used to ignore the specified rules.
186+ /// - regex: The regular expression used to detect ignore directives.
187187 private func appendRuleStatusDirectives(
188188 from token: TokenSyntax ,
189- of node: Syntax
189+ of sourceRange: SourceRange ,
190+ using regex: NSRegularExpression
190191 ) -> SyntaxVisitorContinueKind {
191192 let isFirstInFile = token. previousToken ( viewMode: . sourceAccurate) == nil
192- let matches = loneLineComments ( in: token. leadingTrivia, isFirstToken: isFirstInFile)
193- . compactMap ( ruleStatusDirectiveMatch)
194- let sourceRange = node. sourceRange ( converter: sourceLocationConverter)
195- for match in matches {
196- switch match {
193+ let comments = loneLineComments ( in: token. leadingTrivia, isFirstToken: isFirstInFile)
194+ for comment in comments {
195+ guard let matchResult = ruleStatusDirectiveMatch ( in: comment, using: regex) else { continue }
196+ switch matchResult {
197197 case . all:
198198 allRulesIgnoredRanges. append ( sourceRange)
199199
@@ -210,9 +210,12 @@ fileprivate class RuleStatusCollectionVisitor: SyntaxVisitor {
210210
211211 /// Checks if a comment containing the given text matches a rule status directive. When it does
212212 /// match, its contents (e.g. list of rule names) are returned.
213- private func ruleStatusDirectiveMatch( in text: String ) -> RuleStatusDirectiveMatch ? {
213+ private func ruleStatusDirectiveMatch(
214+ in text: String ,
215+ using regex: NSRegularExpression
216+ ) -> RuleStatusDirectiveMatch ? {
214217 let textRange = NSRange ( text. startIndex..< text. endIndex, in: text)
215- guard let match = ignoreRegex . firstMatch ( in: text, options: [ ] , range: textRange) else {
218+ guard let match = regex . firstMatch ( in: text, options: [ ] , range: textRange) else {
216219 return nil
217220 }
218221 guard match. numberOfRanges == 5 else { return . all }
0 commit comments