Repository: terryyin/lizard

Issue:
Incorrect behavior when a variable named `get` is used in Swift
I have the following Swift file, but lizard (master branch at b631dfd3561366539633a7279f755e237541f3bf) believes it is one single giant getter:

```
//  AFNetworkingWrapper.swift
//  Created by Antoine Cœur on 20/10/2016.
//  Copyright © 2016 EF Education. All rights reserved.

/*...*/

/// HTTP method definitions.
///
/// See https://tools.ietf.org/html/rfc7231#section-4.3
public enum HTTPMethod: String {
    case options = "OPTIONS"
    case get     = "GET"
    case head    = "HEAD"
    case post    = "POST"
    case put     = "PUT"
    case patch   = "PATCH"
    case delete  = "DELETE"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

extension AFHTTPSessionManager {
    
    convenience init(baseURL url: URL?, requestEncoding: ParameterSerializer?, responseEncoding: ParameterSerializer? = nil) {
        self.init(baseURL: url)
        /*...*/
    }
    
    func foo(_ requestHeaders: [String:String]) {
        /*...*/
    }
    
    func bar(_ requestHeaders: [String:String]) {
        /*...*/
    }
    /*...*/
}
```

lizard output:
```
$ python lizard.py AFNetworkingWrapper.swift 
================================================
  NLOC    CCN   token  PARAM  length  location  
------------------------------------------------
     106     29    714      0     118 get@29-146@/coeur/AFNetworkingWrapper.swift
1 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
    115     106.0    29.0      714.0         1     /coeur/AFNetworkingWrapper.swift

=========================================================================================
!!!! Warnings (cyclomatic_complexity > 15 or length > 1000 or parameter_count > 100) !!!!
================================================
  NLOC    CCN   token  PARAM  length  location  
------------------------------------------------
     106     29    714      0     118 get@29-146@/coeur/AFNetworkingWrapper.swift
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
       115     106.0    29.0      714.0        1            1      1.00    1.00
```

Discussion:
Coeur: There is a Travis test failing on:

                class Time
                {
                    var minutes: Double
                    {
                        get
                        {
                            return (seconds / 60)
                        }
                        set
                        {
                            self.seconds = (newValue * 60)
                        }
                    }
                }

The reason may be that `'\n'` isn't the correct way to identify a newline token?

Coeur: If a newline can't be tokenized, an alternative may be to leave the state on `:` and `=`.

Patch:
diff --git a/lizard_languages/swift.py b/lizard_languages/swift.py
index 8aea377..a7f4795 100644
--- a/lizard_languages/swift.py
+++ b/lizard_languages/swift.py
@@ -30,17 +30,24 @@ class SwiftStates(CodeStateMachine):  # pylint: disable=R0903
     def _state_global(self, token):
         if token == 'func':
             self._state = self._function_name
-        if token == 'init':
+        if token in ('init', 'subscript'):
             self._function_name(token)
-        if token in ('get', 'set', 'deinit'):
+        if token in ('get', 'set', 'willSet', 'didSet', 'deinit'):
             self.context.start_new_function(token)
             self._state = self._expect_function_impl
         if token == 'protocol':
             self._state = self._protocol
+        if token in ('let', 'var', 'case', ','):
+            self._state = self._expect_declaration_name
+
+    def _expect_declaration_name(self, token):
+        if token != '`':
+            self._state = self._state_global
 
     def _function_name(self, token):
-        self.context.start_new_function(token)
-        self._state = self._expect_function_dec
+        if token != '`':
+            self.context.start_new_function(token)
+            self._state = self._expect_function_dec
 
     def _expect_function_dec(self, token):
         if token == '(':


Test patch:
diff --git a/test/test_languages/testSwift.py b/test/test_languages/testSwift.py
index 06bed37..0fec26e 100644
--- a/test/test_languages/testSwift.py
+++ b/test/test_languages/testSwift.py
@@ -93,18 +93,39 @@ class Test_parser_for_Swift(unittest.TestCase):
                 ''')
         self.assertEqual(0, len(result))
 
+#https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
     def test_init(self):
         result = get_swift_function_list('''
             init() {}
                 ''')
         self.assertEqual("init", result[0].name)
 
+#https://docs.swift.org/swift-book/LanguageGuide/Deinitialization.html
     def test_deinit(self):
         result = get_swift_function_list('''
             deinit {}
                 ''')
         self.assertEqual("deinit", result[0].name)
 
+#https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html
+    def test_subscript(self):
+        result = get_swift_function_list('''
+            override subscript(index: Int) -> Int {}
+                ''')
+        self.assertEqual("subscript", result[0].name)
+
+#https://stackoverflow.com/a/30593673
+    def test_labeled_subscript(self):
+        result = get_swift_function_list('''
+            extension Collection {
+                /// Returns the element at the specified index iff it is within bounds, otherwise nil.
+                subscript (safe index: Index) -> Iterator.Element? {
+                    return indices.contains(index) ? self[index] : nil
+                }
+            }
+                ''')
+        self.assertEqual("subscript", result[0].name)
+
     def test_getter_setter(self):
         result = get_swift_function_list('''
             class Time
@@ -125,6 +146,70 @@ class Test_parser_for_Swift(unittest.TestCase):
         self.assertEqual("get", result[0].name)
         self.assertEqual("set", result[1].name)
 
+#https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID259
+    def test_explicit_getter_setter(self):
+        result = get_swift_function_list('''
+            var center: Point {
+                get {
+                    let centerX = origin.x + (size.width / 2)
+                    let centerY = origin.y + (size.height / 2)
+                    return Point(x: centerX, y: centerY)
+                }
+                set(newCenter) {
+                    origin.x = newCenter.x - (size.width / 2)
+                    origin.y = newCenter.y - (size.height / 2)
+                }
+            }
+                ''')
+        self.assertEqual("get", result[0].name)
+        self.assertEqual("set", result[1].name)
+
+    def test_willset_didset(self):
+        result = get_swift_function_list('''
+            var cue = -1 {
+                willSet {
+                    if newValue != cue {
+                        tableView.reloadData()
+                    }
+                }
+                didSet {
+                    tableView.scrollToRow(at: IndexPath(row: cue, section: 0), at: .bottom, animated: true)
+                }
+            }
+                ''')
+        self.assertEqual("willSet", result[0].name)
+        self.assertEqual("didSet", result[1].name)
+
+#https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID262
+    def test_explicit_willset_didset(self):
+        result = get_swift_function_list('''
+            class StepCounter {
+                var totalSteps: Int = 0 {
+                    willSet(newTotalSteps) {
+                        print("About to set totalSteps to \(newTotalSteps)")
+                    }
+                    didSet {
+                        if totalSteps > oldValue  {
+                            print("Added \(totalSteps - oldValue) steps")
+                        }
+                    }
+                }
+            }
+                ''')
+        self.assertEqual("willSet", result[0].name)
+        self.assertEqual("didSet", result[1].name)
+
+    def test_keyword_declarations(self):
+        result = get_swift_function_list('''
+            enum Func {
+                static var `init`: Bool?, willSet: Bool?
+                static let `deinit` = 0, didSet = 0
+                case `func`; case get, set
+                func `default`() {}
+            }
+                ''')
+        self.assertEqual("default", result[0].name)
+
     def test_generic_function(self):
         result = get_swift_function_list('''
             func f<T>() {}