Repository: alanmcruickshank/sqlfluff

Issue:
"Whitespace around operators" check reports the same warning twice
Query:
```
SELECT
    a/b AS click_rate
FROM
    t
```

Output:
```
$ sqlfluff lint test.sql
== [test.sql] FAIL
L:   2 | P:   6 | L006 | Operators should be surrounded by a single whitespace.
L:   2 | P:   7 | L006 | Operators should be surrounded by a single whitespace.
```

Note the warning is correct, but it is reported twice -- apparently once on the operator and once on the second operand.

Comments:

Patch:
diff --git a/src/sqlfluff/errors.py b/src/sqlfluff/errors.py
index 8400feab..f75dec43 100644
--- a/src/sqlfluff/errors.py
+++ b/src/sqlfluff/errors.py
@@ -21,9 +21,14 @@ class SQLBaseError(ValueError):
 
         NB: For violations which don't directly implement a rule
         this attempts to return the error message linked to whatever
-        caused the violation.
+        caused the violation. Optionally some errors may have their
+        description set directly.
         """
-        if hasattr(self, 'rule'):
+        if hasattr(self, 'description') and self.description:
+            # This can only override if it's present AND
+            # if it's non-null.
+            return self.description
+        elif hasattr(self, 'rule'):
             return self.rule.description
         else:
             # Return the first element - probably a string message
@@ -148,6 +153,7 @@ class SQLLintError(SQLBaseError):
         self.segment = kwargs.pop('segment', None)
         self.rule = kwargs.pop('rule', None)
         self.fixes = kwargs.pop('fixes', [])
+        self.description = kwargs.pop('description', None)
         super(SQLLintError, self).__init__(*args, **kwargs)
 
     def check_tuple(self):
diff --git a/src/sqlfluff/rules/base.py b/src/sqlfluff/rules/base.py
index 67e4d35e..01f7349e 100644
--- a/src/sqlfluff/rules/base.py
+++ b/src/sqlfluff/rules/base.py
@@ -37,21 +37,29 @@ class LintResult(object):
         memory (:obj:`dict`, optional): An object which stores any working
             memory for the crawler. The `memory` returned in any `LintResult`
             will be passed as an input to the next segment to be crawled.
+        description (:obj:`str`, optional): A description of the problem
+            identified as part of this result. This will override the
+            description of the rule as what gets reported to the user
+            with the problem if provided.
 
     """
 
-    def __init__(self, anchor=None, fixes=None, memory=None):
+    def __init__(self, anchor=None, fixes=None, memory=None, description=None):
         # An anchor of none, means no issue
         self.anchor = anchor
         # Fixes might be blank
         self.fixes = fixes or []
         # Memory is passed back in the linting result
         self.memory = memory
+        # store a description_override for later
+        self.description = description
 
     def to_linting_error(self, rule):
         """Convert a linting result to a :exc:`SQLLintError` if appropriate."""
         if self.anchor:
-            return SQLLintError(rule=rule, segment=self.anchor, fixes=self.fixes)
+            # Allow description override from the LintRestult
+            description = self.description or rule.description
+            return SQLLintError(rule=rule, segment=self.anchor, fixes=self.fixes, description=description)
         else:
             return None
 
diff --git a/src/sqlfluff/rules/std.py b/src/sqlfluff/rules/std.py
index 18985943..6d1b2c1f 100644
--- a/src/sqlfluff/rules/std.py
+++ b/src/sqlfluff/rules/std.py
@@ -241,6 +241,7 @@ class Rule_L006(BaseCrawler):
         # anchor is our signal as to whether there's a problem
         anchor = None
         fixes = []
+        description = None
 
         # The parent stack tells us whether we're in an expression or not.
         if parent_stack and parent_stack[-1].type == 'expression':
@@ -251,6 +252,8 @@ class Rule_L006(BaseCrawler):
                     anchor, fixes = _handle_previous_segments(
                         memory['since_code'], anchor=segment, this_segment=segment,
                         fixes=fixes)
+                    if anchor:
+                        description = "Operators should be preceded by a space."
                 else:
                     # It's not an operator, we can evaluate what happened after an
                     # operator if that's the last code we saw.
@@ -259,6 +262,8 @@ class Rule_L006(BaseCrawler):
                         anchor, fixes = _handle_previous_segments(
                             memory['since_code'], anchor=memory['last_code'],
                             this_segment=segment, fixes=fixes)
+                        if anchor:
+                            description = "Operators should be followed by a space."
                     else:
                         # This isn't an operator, and the thing before it wasn't
                         # either. I don't think that's an issue for now.
@@ -276,7 +281,7 @@ class Rule_L006(BaseCrawler):
 
         # Anchor is our signal as to whether there's a problem
         if anchor:
-            return LintResult(anchor=anchor, memory=memory, fixes=fixes)
+            return LintResult(anchor=anchor, memory=memory, fixes=fixes, description=description)
         else:
             return LintResult(memory=memory)
 


Test patch:
diff --git a/test/cli_formatters_test.py b/test/cli_formatters_test.py
index 84cad892..ca6f55d9 100644
--- a/test/cli_formatters_test.py
+++ b/test/cli_formatters_test.py
@@ -45,20 +45,21 @@ def test__cli__formatters__violations():
         'foo': [
             SQLLintError(
                 segment=RawSegment('blah', FilePositionMarker(0, 25, 2, 26)),
-                rule=RuleGhost('A', 'DESC')),
+                rule=RuleGhost('A', 'DESCR')),
+            # Here we check the optional description override
             SQLLintError(
                 segment=RawSegment('blah', FilePositionMarker(0, 21, 3, 22)),
-                rule=RuleGhost('B', 'DESC'))],
+                rule=RuleGhost('B', 'DESCR'), description='foo')],
         'bar': [
             SQLLintError(
                 segment=RawSegment('blah', FilePositionMarker(0, 2, 11, 3)),
-                rule=RuleGhost('C', 'DESC'))]
+                rule=RuleGhost('C', 'DESCR'))]
     }
     f = format_path_violations(v)
     k = sorted(['foo', 'bar'])
     chk = {
-        'foo': ["L:  21 | P:   3 | B | DESC", "L:  25 | P:   2 | A | DESC"],
-        'bar': ["L:   2 | P:  11 | C | DESC"]
+        'foo': ["L:  21 | P:   3 | B | foo", "L:  25 | P:   2 | A | DESCR"],
+        'bar': ["L:   2 | P:  11 | C | DESCR"]
     }
     chk2 = []
     for elem in k: