Repository: jsfehler/flake8-multiline-containers

Issue:
False positives inside multiline string
I evaluated `flake8-multiline-containers 0.0.19` and running it on the following code raises two warnings:
```
foo = """
(lorem
 ) lorem ipsum!
"""
```

```
test.py:2:6: JS101 Multi-line container not broken after opening character
test.py:3:2: JS102 Multi-line container does not close on same column as opening
```

As there are no containers and hence no multi-line containers in this code I see these as false positives.


Discussion:

Patch:
diff --git a/flake8_multiline_containers.py b/flake8_multiline_containers.py
index e96b0b9..7803c14 100644
--- a/flake8_multiline_containers.py
+++ b/flake8_multiline_containers.py
@@ -10,6 +10,10 @@ STRING_REGEX = re.compile(
     r'"([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\'',
 )
 
+MULTILINE_STRING_REGEX = re.compile(
+    r'"""|\'\'\'',
+)
+
 # Matches anything that looks like a:
 # function call, function definition, or class definition with inheritance
 # Actual tuples should be ignored
@@ -69,6 +73,8 @@ class MultilineContainers:
 
     inside_conditional_block = attr.ib(default=0)
 
+    inside_multiline_string = False
+
     def _number_of_matches_in_line(
             self,
             open_character: str,
@@ -91,6 +97,15 @@ class MultilineContainers:
         if re.search(r'^\s*#', line):
             return 0, 0, ONLY_COMMENTS_STRING
 
+        # Multiline strings should be ignored.
+        # If a line has only 1 triple quote, assume it's multiline
+        matches = MULTILINE_STRING_REGEX.findall(line)
+        if len(matches) == 1:
+            self.inside_multiline_string = not self.inside_multiline_string
+
+        if self.inside_multiline_string:
+            return 0, 0, line
+
         # Remove strings from the line. Strings are always ignored.
         temp_line = STRING_REGEX.sub('', line)
 


Test patch:
diff --git a/tests/dummy/string/multiline.py b/tests/dummy/string/multiline.py
new file mode 100644
index 0000000..5c52218
--- /dev/null
+++ b/tests/dummy/string/multiline.py
@@ -0,0 +1,23 @@
+# Triple quote strings should be ignored
+
+foo = """(a=10"""
+
+foo = """(a=10
+)
+"""
+
+foo = """
+(a=10
+ )
+"""
+
+foo = '''(a=10'''
+
+foo = '''(a=10
+)
+'''
+
+foo = '''
+(a=10
+ )
+'''
diff --git a/tests/test_string_ignore.py b/tests/test_string_ignore.py
index 60a4230..f32b081 100644
--- a/tests/test_string_ignore.py
+++ b/tests/test_string_ignore.py
@@ -15,6 +15,11 @@ def string_brackets_file_path(dummy_file_path):
     return f'{dummy_file_path}/string/string_brackets.py'
 
 
+@pytest.fixture
+def multiline_string_file_path(dummy_file_path):
+    return f'{dummy_file_path}/string/multiline.py'
+
+
 def test_js101_string_ignore(string_file_path):
     """When opening and closing characters are in a string
     Then the linter should not detect them.
@@ -69,3 +74,31 @@ def test_js102_string_brackets_ignore(string_brackets_file_path):
     r = style_guide.check_files([p])
 
     assert 1 == r.total_errors
+
+
+def test_js101_multiline_string_ignore(multiline_string_file_path):
+    """When opening and closing characters are in a string
+    Then the linter should not detect them.
+    """
+    style_guide = flake8.get_style_guide(
+        select=['JS101'],
+    )
+
+    p = os.path.abspath(multiline_string_file_path)
+    r = style_guide.check_files([p])
+
+    assert 0 == r.total_errors
+
+
+def test_js102_multiline_string_ignore(multiline_string_file_path):
+    """When opening and closing characters are in a string
+    Then the linter should not detect them.
+    """
+    style_guide = flake8.get_style_guide(
+        select=['JS102'],
+    )
+
+    p = os.path.abspath(multiline_string_file_path)
+    r = style_guide.check_files([p])
+
+    assert 0 == r.total_errors