Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6af9419

Browse files
authored
Rewritetrailing_closure rule with SwiftSyntax (realm#5414)
1 parent2f15f66 commit6af9419

File tree

2 files changed

+57
-102
lines changed

2 files changed

+57
-102
lines changed

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@
3333
*`multiline_literal_brackets`
3434
*`nimble_operator`
3535
*`opening_brace`
36+
*`trailing_closure`
3637
*`void_return`
3738

3839
[SimplyDanny](https://github.com/SimplyDanny)
3940
[kishikawakatsumi](https://github.com/kishikawakatsumi)
4041
[Marcelo Fabri](https://github.com/marcelofabri)
4142
[swiftty](https://github.com/swiftty)
43+
[KS1019](https://github.com/KS1019)
4244

4345
* Print invalid keys when configuration parsing fails.
4446
[SimplyDanny](https://github.com/SimplyDanny)
Lines changed: 55 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
importFoundation
2-
importSourceKittenFramework
1+
importSwiftLintCore
2+
importSwiftSyntax
33

4+
@SwiftSyntaxRule
45
structTrailingClosureRule:OptInRule{
56
varconfiguration=TrailingClosureConfiguration()
67

@@ -18,127 +19,79 @@ struct TrailingClosureRule: OptInRule {
1819
Example("offsets.sorted { $0.offset < $1.offset }"),
1920
Example("foo.something({ return 1 }())"),
2021
Example("foo.something({ return $0 }(1))"),
21-
Example("foo.something(0, { return 1 }())")
22+
Example("foo.something(0, { return 1 }())"),
23+
Example("for x in list.filter({ $0.isValid }) {}"),
24+
Example("if list.allSatisfy({ $0.isValid }) {}"),
25+
Example("foo(param1: 1, param2: { _ in true }, param3: 0)"),
26+
Example("foo(param1: 1, param2: { _ in true }) { $0 + 1 }"),
27+
Example("foo(param1: { _ in false }, param2: { _ in true })"),
28+
Example("foo(param1: { _ in false }, param2: { _ in true }, param3: { _ in false })"),
29+
Example("""
30+
if f({ true }), g({ true }) {
31+
print("Hello")
32+
}
33+
"""),
34+
Example("""
35+
for i in h({ [1,2,3] }) {
36+
print(i)
37+
}
38+
""")
2239
],
2340
triggeringExamples:[
2441
Example("↓foo.map({ $0 + 1 })"),
2542
Example("↓foo.reduce(0, combine: { $0 + 1 })"),
2643
Example("↓offsets.sorted(by: { $0.offset < $1.offset })"),
27-
Example("↓foo.something(0, { $0 + 1 })")
44+
Example("↓foo.something(0, { $0 + 1 })"),
45+
Example("↓foo.something(param1: { _ in true }, param2: 0, param3: { _ in false })"),
46+
Example("""
47+
for n in list {
48+
↓n.forEach({ print($0) })
49+
}
50+
""", excludeFromDocumentation:true)
2851
]
2952
)
53+
}
3054

31-
func validate(file:SwiftLintFile)->[StyleViolation]{
32-
letdict= file.structureDictionary
33-
returnviolationOffsets(for: dict, file: file).map{
34-
StyleViolation(ruleDescription:Self.description,
35-
severity: configuration.severityConfiguration.severity,
36-
location:Location(file: file, byteOffset: $0))
37-
}
38-
}
39-
40-
privatefunc violationOffsets(for dictionary:SourceKittenDictionary, file:SwiftLintFile)->[ByteCount]{
41-
varresults=[ByteCount]()
42-
43-
if dictionary.expressionKind==.call,
44-
shouldBeTrailingClosure(dictionary: dictionary, file: file),
45-
let offset= dictionary.offset{
46-
results=[offset]
47-
}
55+
privateextensionTrailingClosureRule{
56+
finalclassVisitor:ViolationsSyntaxVisitor<ConfigurationType>{
57+
overridefunc visitPost(_ node:FunctionCallExprSyntax){
58+
guard node.trailingClosure==nilelse{return}
4859

49-
iflet kind= dictionary.statementKind, kind!=.brace{
50-
// trailing closures are not allowed in `if`, `guard`, etc
51-
results+= dictionary.substructure.flatMap{ subDict->[ByteCount]in
52-
guard subDict.statementKind==.braceelse{
53-
return[]
60+
if configuration.onlySingleMutedParameter{
61+
if node.containsOnlySingleMutedParameter{
62+
violations.append(node.positionAfterSkippingLeadingTrivia)
5463
}
55-
56-
returnviolationOffsets(for: subDict, file: file)
57-
}
58-
}else{
59-
results+= dictionary.substructure.flatMap{ subDictin
60-
violationOffsets(for: subDict, file: file)
64+
}elseif node.shouldTrigger{
65+
violations.append(node.positionAfterSkippingLeadingTrivia)
6166
}
6267
}
6368

64-
return results
65-
}
66-
67-
privatefunc shouldBeTrailingClosure(dictionary:SourceKittenDictionary, file:SwiftLintFile)->Bool{
68-
func shouldTrigger()->Bool{
69-
return !isAlreadyTrailingClosure(dictionary: dictionary, file: file) &&
70-
!isAnonymousClosureCall(dictionary: dictionary, file: file)
71-
}
72-
73-
letarguments= dictionary.enclosedArguments
74-
75-
// check if last parameter should be trailing closure
76-
if !configuration.onlySingleMutedParameter, arguments.isNotEmpty,
77-
caselet closureArguments=filterClosureArguments(arguments, file: file),
78-
closureArguments.count==1,
79-
closureArguments.last?.offset== arguments.last?.offset{
80-
returnshouldTrigger()
69+
overridefunc visit(_ node:ConditionElementListSyntax)->SyntaxVisitorContinueKind{
70+
.skipChildren
8171
}
8272

83-
letargumentsCountIsExpected:Bool={
84-
ifSwiftVersion.current>=.fiveDotSix, arguments.count==1,
85-
arguments[0].expressionKind==.argument{
86-
returntrue
87-
}
88-
89-
return arguments.isEmpty
90-
}()
91-
// check if there's only one unnamed parameter that is a closure
92-
if argumentsCountIsExpected,
93-
let offset= dictionary.offset,
94-
let totalLength= dictionary.length,
95-
let nameOffset= dictionary.nameOffset,
96-
let nameLength= dictionary.nameLength,
97-
caselet start= nameOffset+ nameLength,
98-
caselet length= totalLength+ offset- start,
99-
caselet byteRange=ByteRange(location: start, length: length),
100-
let range= file.stringView.byteRangeToNSRange(byteRange),
101-
let match=regex("\\s*\\(\\s*\\{").firstMatch(in: file.contents, options:[], range: range)?.range,
102-
match.location== range.location{
103-
returnshouldTrigger()
73+
overridefunc visit(_ node:ForStmtSyntax)->SyntaxVisitorContinueKind{
74+
walk(node.body)
75+
return.skipChildren
10476
}
105-
106-
returnfalse
10777
}
78+
}
10879

109-
privatefunc filterClosureArguments(_ arguments:[SourceKittenDictionary],
110-
file:SwiftLintFile)->[SourceKittenDictionary]{
111-
return arguments.filter{ argumentin
112-
guardlet bodyByteRange= argument.bodyByteRange,
113-
let range= file.stringView.byteRangeToNSRange(bodyByteRange),
114-
let match=regex("\\s*\\{").firstMatch(in: file.contents, options:[], range: range)?.range,
115-
match.location== range.location
116-
else{
117-
returnfalse
118-
}
119-
120-
returntrue
121-
}
80+
privateextensionFunctionCallExprSyntax{
81+
varcontainsOnlySingleMutedParameter:Bool{
82+
arguments.onlyElement?.isMutedClosure==true
12283
}
12384

124-
privatefunc isAlreadyTrailingClosure(dictionary:SourceKittenDictionary, file:SwiftLintFile)->Bool{
125-
guardlet byteRange= dictionary.byteRange,
126-
let text= file.stringView.substringWithByteRange(byteRange)
127-
else{
128-
returnfalse
129-
}
130-
131-
return !text.hasSuffix(")")
85+
varshouldTrigger:Bool{
86+
arguments.last?.expression.is(ClosureExprSyntax.self)==true
87+
// If at least last two arguments were ClosureExprSyntax, a violation should not be triggered.
88+
&&(arguments.count<=1
89+
|| !arguments.dropFirst(arguments.count-2).allSatisfy({ $0.expression.is(ClosureExprSyntax.self)}))
13290
}
91+
}
13392

134-
privatefunc isAnonymousClosureCall(dictionary:SourceKittenDictionary, file:SwiftLintFile)->Bool{
135-
guardlet byteRange= dictionary.byteRange,
136-
let range= file.stringView.byteRangeToNSRange(byteRange)
137-
else{
138-
returnfalse
139-
}
140-
141-
letpattern=regex("\\)\\s*\\)\\z")
142-
return pattern.numberOfMatches(in: file.contents, range: range)>0
93+
privateextensionLabeledExprSyntax{
94+
varisMutedClosure:Bool{
95+
label==nil && expression.is(ClosureExprSyntax.self)
14396
}
14497
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp