@@ -12,5 +12,109 @@ import SwiftSyntax
1212///
1313/// - SeeAlso: https://google.github.io/swift#initializers-2
1414public final class UseSynthesizedInitializer : SyntaxLintRule {
15+ public override func visit( _ node: StructDeclSyntax ) {
16+ var storedProperties : [ VariableDeclSyntax ] = [ ]
17+ var initializers : [ InitializerDeclSyntax ] = [ ]
1518
19+ for member in node. members. members {
20+ // Collect all stored variables into a list
21+ if let varDecl = member. decl as? VariableDeclSyntax {
22+ guard let modifiers = varDecl. modifiers else {
23+ storedProperties. append ( varDecl)
24+ continue
25+ }
26+ guard !modifiers. has ( modifier: " static " ) else { continue }
27+ storedProperties. append ( varDecl)
28+ // Collect any possible redundant initializers into a list
29+ } else if let initDecl = member. decl as? InitializerDeclSyntax {
30+ guard initDecl. modifiers == nil ||
31+ initDecl. modifiers!. has ( modifier: " internal " ) else { continue }
32+ guard initDecl. optionalMark == nil else { continue }
33+ guard initDecl. throwsOrRethrowsKeyword == nil else { continue }
34+ initializers. append ( initDecl)
35+ }
36+ }
37+
38+ for initializer in initializers {
39+ guard matchesPropertyList ( parameters: initializer. parameters. parameterList,
40+ properties: storedProperties) else { continue }
41+ guard matchesAssignmentBody ( variables: storedProperties,
42+ initBody: initializer. body) else { continue }
43+ diagnose ( . removeRedundantInitializer, on: initializer)
44+ }
45+ }
46+
47+ // Compares initializer parameters to stored properties of the struct
48+ func matchesPropertyList( parameters: FunctionParameterListSyntax ,
49+ properties: [ VariableDeclSyntax ] ) -> Bool {
50+ guard parameters. count == properties. count else { return false }
51+ for (idx, parameter) in parameters. enumerated ( ) {
52+
53+ guard let paramId = parameter. firstName, parameter. secondName == nil else { return false }
54+ guard let paramType = parameter. type else { return false }
55+
56+ let property = properties [ idx]
57+ let propertyId = property. firstIdentifier
58+ guard let propertyType = property. firstType else { return false }
59+
60+ // Sythesized initializer only keeps default argument if the declaration uses 'var'
61+ if property. letOrVarKeyword. tokenKind == . varKeyword {
62+ if let initializer = property. firstInitializer {
63+ guard let defaultArg = parameter. defaultArgument else { return false }
64+ guard initializer. value. description == defaultArg. value. description else { return false }
65+ }
66+ }
67+
68+ if propertyId. identifier. text != paramId. text ||
69+ propertyType. description. trimmingCharacters ( in: . whitespaces) !=
70+ paramType. description. trimmingCharacters ( in: . whitespacesAndNewlines) { return false }
71+ }
72+ return true
73+ }
74+
75+ // Evaluates if all, and only, the stored properties are initialized in the body
76+ func matchesAssignmentBody( variables: [ VariableDeclSyntax ] ,
77+ initBody: CodeBlockSyntax ? ) -> Bool {
78+ guard let initBody = initBody else { return false }
79+ guard variables. count == initBody. statements. count else { return false }
80+
81+ var statements : [ String ] = [ ]
82+ for statement in initBody. statements {
83+ guard let exp = statement. item as? SequenceExprSyntax else { return false }
84+ var leftName = " "
85+ var rightName = " "
86+
87+ for element in exp. elements {
88+ switch element {
89+ case let element as MemberAccessExprSyntax :
90+ guard let base = element. base else { return false }
91+ guard base. description. trimmingCharacters ( in: . whitespacesAndNewlines) == " self " else {
92+ return false
93+ }
94+ leftName = element. name. text
95+ case let element as AssignmentExprSyntax :
96+ guard element. assignToken. tokenKind == . equal else { return false }
97+ case let element as IdentifierExprSyntax :
98+ rightName = element. identifier. text
99+ default :
100+ return false
101+ }
102+ }
103+ guard leftName == rightName else { return false }
104+ statements. append ( leftName)
105+ }
106+
107+ for variable in variables {
108+ let id = variable. firstIdentifier. identifier. text
109+ guard statements. contains ( id) else { return false }
110+ guard let idx = statements. firstIndex ( of: id) else { return false }
111+ statements. remove ( at: idx)
112+ }
113+ return statements. isEmpty
114+ }
115+ }
116+
117+ extension Diagnostic . Message {
118+ static let removeRedundantInitializer = Diagnostic . Message ( . warning,
119+ " initializer is the same as synthesized initializer " )
16120}
0 commit comments