Repository: sqlfluff/sqlfluff
Pull request: L007 autofix
L007 hasn't been a fixable Rule. This PR fixes that! :)

Fixes #1432

### Are there any other side effects of this change that we should be aware of?
No side-effects

### Pull Request checklist
- [x] Please confirm you have completed any of the necessary steps below.

- Included test cases to demonstrate any code changes, which may be one or more of the following:
  - [x] `.yml` rule test cases in `test/fixtures/rules/std_rule_cases`.
  - Full autofix test cases in `test/fixtures/linter/autofix`.


Problem statement:
Rule L007 should be autofixable
Rule L007 checks for operators before or after newlines.

That is:

```sql
SELECT
    a
    + b
FROM foo
```

is preferred over

```sql
SELECT
    a +
    b
FROM foo
```

(or vice versa) depending on config.

As we can detect this pretty reliably, it seems it should be one that is auto fixable reliably too.

Only concern would be clashing with rule L016 (line length).

Hints from issue discussion:

Commit message: remove newlines that imply new test but arent

Patch to review:
diff --git a/src/sqlfluff/rules/L007.py b/src/sqlfluff/rules/L007.py
index 1b3f89e4558..50337d55409 100644
--- a/src/sqlfluff/rules/L007.py
+++ b/src/sqlfluff/rules/L007.py
@@ -1,11 +1,18 @@
 """Implementation of Rule L007."""
-from sqlfluff.core.rules.base import BaseRule, LintResult, RuleContext
-from sqlfluff.core.rules.doc_decorators import document_configuration
+import copy
+from typing import List, Optional, Tuple, cast
+from sqlfluff.core.parser.segments.base import BaseSegment
+from sqlfluff.core.rules.base import BaseRule, LintFix, LintResult, RuleContext
+from sqlfluff.core.rules.doc_decorators import (
+    document_configuration,
+    document_fix_compatible,
+)
 
 after_description = "Operators near newlines should be after, not before the newline"
 before_description = "Operators near newlines should be before, not after the newline"
 
 
+@document_fix_compatible
 @document_configuration
 class Rule_L007(BaseRule):
     """Operators should follow a standard for being before/after newlines.
@@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
+        fixes = _generate_fixes(expr, anchor, operator_new_lines)
+        return LintResult(
+            anchor=anchor,
+            memory=memory,
+            description=description,
+            fixes=fixes,
+        )
+
+
+def _generate_fixes(
+    expr: Tuple[BaseSegment],
+    anchor: BaseSegment,
+    operator_new_lines: str,
+) -> List[LintFix]:
+    """Generate Fixes.
+
+    Take operator_new_lines==after as the default case.
+    Consider "before" as a modification on this behaviour.
+
+    """
+    fixes = [LintFix.delete(anchor)]
+    res = list(_get_surrounding_segments(expr, anchor))
+    if operator_new_lines == "before":
+        res = [list(reversed(els)) for els in reversed(res)]
+
+    change_list, anchor_list = res
+    insert_anchor = anchor_list[-1]
+    change_list = change_list[1:]
+    for el in change_list:
+        fixes.append(LintFix.delete(el))
+
+    change_list.append(anchor)
+    if operator_new_lines == "before":
+        # We do yet another reverse here,
+        # This could be avoided but makes all "changes" relate to "before" config state
+        change_list = list(reversed(change_list))
+
+    change_list = list(map(_copy_el, reversed(change_list)))
+    edit_type = "create_after" if operator_new_lines == "before" else "create_before"
+    fixes.append(
+        LintFix(
+            edit_type=edit_type,
+            edit=change_list,
+            anchor=insert_anchor,
+        )
+    )
+    return fixes
+
+
+def _copy_el(segment: BaseSegment):
+    """Safely duplicate segment without position markers."""
+    el = copy.deepcopy(segment)
+    el.pos_marker = None  # type: ignore
+    return el
+
+
+def _get_surrounding_segments(
+    expr: Tuple[BaseSegment],
+    symbol: BaseSegment,
+) -> Tuple[List[BaseSegment], List[BaseSegment]]:
+    """Create two lists of symbols surrounding operator.
+
+    1 from last code to operator/symbol,
+    2 from operator to next code
+
+    """
+    before_list: List[BaseSegment] = []
+    after_list: List[BaseSegment] = []
+    start_code: Optional[BaseSegment] = None
+    symbol_hit = False
+    for el in expr:
+        if el == symbol:
+            symbol_hit = True
+            continue
+        if el.is_code:
+            if symbol_hit:
+                # Exit condition, we have found code on the other side of the operator
+                after_list.append(el)
+                return before_list, after_list
+
+            start_code = el
+            before_list = []
+            after_list = []
+
+        if symbol_hit:
+            after_list.append(el)
+            continue
+
+        if start_code:
+            before_list.append(el)
+    # This code is unreachable if a valid expr and symbol/operator segment are passed.
+    err = "Ivalid Expr: no binary_operator/comparison_operator"  # pragma: no cover
+    raise Exception(err)  # pragma: no cover
diff --git a/test/fixtures/rules/std_rule_cases/L007.yml b/test/fixtures/rules/std_rule_cases/L007.yml
index 026e204b573..64e2c4f8241 100644
--- a/test/fixtures/rules/std_rule_cases/L007.yml
+++ b/test/fixtures/rules/std_rule_cases/L007.yml
@@ -13,6 +13,39 @@ fails_on_after_default:
         a +
         b
     from foo
+  fix_str: |
+    select
+        a
+        + b
+    from foo
+
+fails_on_after_default_on_and:
+  fail_str: |
+    select
+        a AND
+        b
+    from foo
+  fix_str: |
+    select
+        a
+        AND b
+    from foo
+
+fails_on_after_with_comment_order_preserved:
+  fail_str: |
+    select
+        a AND
+        -- comment1!
+        -- comment2!
+        b
+    from foo
+  fix_str: |
+    select
+        a
+        -- comment1!
+        -- comment2!
+        AND b
+    from foo
 
 passes_on_before_explicit:
   pass_str: |
@@ -20,7 +53,6 @@ passes_on_before_explicit:
         a
         + b
     from foo
-
   configs: &operator_after
     rules:
       L007:
@@ -32,7 +64,11 @@ fails_on_after_explicit:
         a +
         b
     from foo
-
+  fix_str: |
+    select
+        a
+        + b
+    from foo
   configs: *operator_after
 
 fails_on_before_override:
@@ -41,7 +77,11 @@ fails_on_before_override:
         a
         + b
     from foo
-
+  fix_str: |
+    select
+        a +
+        b
+    from foo
   configs: &operator_before
     rules:
       L007:
@@ -53,5 +93,21 @@ passes_on_after_override:
         a +
         b
     from foo
+  configs: *operator_before
 
+fails_on_before_override_with_comment_order:
+  fail_str: |
+    select
+        a
+        -- comment1!
+        -- comment2!
+        + b
+    from foo
+  fix_str: |
+    select
+        a +
+        -- comment1!
+        -- comment2!
+        b
+    from foo
   configs: *operator_before


Code review comments:
diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
path: src/sqlfluff/rules/L007.py
text: @user1:
Why are we casting this here? Would it be reasonable to actually just create a tuple? Generally, using `cast` is more of a workaround.

diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
+        fixes = _generate_fixes(expr, anchor, operator_new_lines)
+        return LintResult(
+            anchor=anchor,
+            memory=memory,
+            description=description,
+            fixes=fixes,
+        )
+
+
+def _generate_fixes(
+    expr: Tuple[BaseSegment],
+    anchor: BaseSegment,
+    operator_new_lines: str,
+) -> List[LintFix]:
+    """Generate Fixes.
+
+    Take operator_new_lines==after as the default case.
+    Consider "before" as a modification on this behaviour.
+
+    """
+    fixes = [LintFix.delete(anchor)]
+    res = list(_get_surrounding_segments(expr, anchor))
+    if operator_new_lines == "before":
+        res = [list(reversed(els)) for els in reversed(res)]
+
+    change_list, anchor_list = res
+    insert_anchor = anchor_list[-1]
+    change_list = change_list[1:]
+    for el in change_list:
+        fixes.append(LintFix.delete(el))
+
+    change_list.append(anchor)
+    if operator_new_lines == "before":
+        # We do yet another reverse here,
+        # This could be avoided but makes all "changes" relate to "before" config state
+        change_list = list(reversed(change_list))
+
+    change_list = list(map(_copy_el, reversed(change_list)))
+    edit_type = "create_after" if operator_new_lines == "before" else "create_before"
+    fixes.append(
+        LintFix(
+            edit_type=edit_type,
+            edit=change_list,
+            anchor=insert_anchor,
+        )
+    )
+    return fixes
+
+
+def _copy_el(segment: BaseSegment):
+    """Safely duplicate segment without position markers."""
+    el = copy.deepcopy(segment)
+    el.pos_marker = None  # type: ignore
+    return el
+
+
+def _get_surrounding_segments(
+    expr: Tuple[BaseSegment],
+    symbol: BaseSegment,
+) -> Tuple[List[BaseSegment], List[BaseSegment]]:
+    """Create two lists of symbols surrounding operator.
+
+    1 from last code to operator/symbol,
+    2 from operator to next code
+
+    """
+    before_list: List[BaseSegment] = []
+    after_list: List[BaseSegment] = []
+    start_code: Optional[BaseSegment] = None
+    symbol_hit = False
+    for el in expr:
+        if el == symbol:
+            symbol_hit = True
+            continue
+        if el.is_code:
+            if symbol_hit:
+                # Exit condition, we have found code on the other side of the operator
+                after_list.append(el)
+                return before_list, after_list
+
+            start_code = el
+            before_list = []
+            after_list = []
+
+        if symbol_hit:
+            after_list.append(el)
+            continue
+
+        if start_code:
+            before_list.append(el)
+    # This code is unreachable if a valid expr and symbol/operator segment are passed.
+    err = "Ivalid Expr: no binary_operator/comparison_operator"  # pragma: no cover
+    raise Exception(err)  # pragma: no cover
path: src/sqlfluff/rules/L007.py
text: @user1:
I think we usually raise `ValueError` for these kinds of things.
```suggestion
    raise ValueError(err)  # pragma: no cover
```

diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
+        fixes = _generate_fixes(expr, anchor, operator_new_lines)
+        return LintResult(
+            anchor=anchor,
+            memory=memory,
+            description=description,
+            fixes=fixes,
+        )
+
+
+def _generate_fixes(
+    expr: Tuple[BaseSegment],
+    anchor: BaseSegment,
+    operator_new_lines: str,
+) -> List[LintFix]:
+    """Generate Fixes.
+
+    Take operator_new_lines==after as the default case.
+    Consider "before" as a modification on this behaviour.
+
+    """
+    fixes = [LintFix.delete(anchor)]
+    res = list(_get_surrounding_segments(expr, anchor))
+    if operator_new_lines == "before":
+        res = [list(reversed(els)) for els in reversed(res)]
+
+    change_list, anchor_list = res
+    insert_anchor = anchor_list[-1]
+    change_list = change_list[1:]
+    for el in change_list:
+        fixes.append(LintFix.delete(el))
+
+    change_list.append(anchor)
+    if operator_new_lines == "before":
+        # We do yet another reverse here,
+        # This could be avoided but makes all "changes" relate to "before" config state
+        change_list = list(reversed(change_list))
+
+    change_list = list(map(_copy_el, reversed(change_list)))
+    edit_type = "create_after" if operator_new_lines == "before" else "create_before"
+    fixes.append(
+        LintFix(
+            edit_type=edit_type,
+            edit=change_list,
+            anchor=insert_anchor,
+        )
+    )
+    return fixes
+
+
+def _copy_el(segment: BaseSegment):
+    """Safely duplicate segment without position markers."""
+    el = copy.deepcopy(segment)
+    el.pos_marker = None  # type: ignore
+    return el
+
+
+def _get_surrounding_segments(
+    expr: Tuple[BaseSegment],
+    symbol: BaseSegment,
+) -> Tuple[List[BaseSegment], List[BaseSegment]]:
+    """Create two lists of symbols surrounding operator.
+
+    1 from last code to operator/symbol,
+    2 from operator to next code
+
+    """
+    before_list: List[BaseSegment] = []
path: src/sqlfluff/rules/L007.py
text: @user1:
Could you look at writing this code using the `Segments()` class? I think it would be much shorter. In other words, if `expr` were an instance of `Segments()`, I think you could use `select()` and the keyword arguments `start_seg` and `end_seg` to build `before_list` and `after_list`.

@author:
Nice - I didn't know about that functionality. Will. use in the future. However not sure if it will work, as I need the distance from last start token (last code) to first symbol. and .select is first start token to first symbol.
Or am I wrong


@user1:
What do you mean by:
>I need the distance from last start token (last code) to first symbol

I don't see anything using distance in the code. It looks like you're building two lists:  a list of segments before and after the symbol.

Don't worry about this; working code is 💯 . We do encourage use of `Segments()` and the similar classes in general, because they often result in shorter, easier to understand code. (Generally, they tend to reduce the need to write custom loop code.)

@author:
I rewrote the whole test in the functional manner. Fns for the win!

It turns out to be only a net add of 80 lines (not including test) - I think this is mostly due to not using memory etc. and basing our guard statement on is this segment an expr containing one of our operators.


I did my best to avoid loops. But like I said it cant really be handled by select and this adds quite a few cycles over a naive loop.


What I meant is that this sequence where the segs are numbered in brackets.



`"("[0] "col_ref_1"[1] ")"[2] " "[3] "+"[4] "\n"[5] " "[6] "col_ref2"[7]`

All segs are `is_code` with the exception of `3,5,6`.

We want the stretch from `2-->7` ie the nearest code elements either side of the operator[4].
With select we are doing a lot of dancing, we need some sort of memory or marker to understand the position relative to "+" which requires extra and to be honest just as confusing loops :)



@user1:
I added a comment with a _possible_ way to do this using `select()` and `first()`.

This is great work. Thanks for doing this!

When we added the functional API a few months ago, we also looked at using another package called `awesome-pattern-matching`. It can handle some tricky scenarios that are too complex for the functional API. There's a GitHub issue talking about it (#2221) which includes some rules I rewrote to use it (as a proof of concept). Take a look if you are interested. (Not for this PR!)

diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
path: src/sqlfluff/rules/L007.py
text: @user1:
```suggestion
        is_relevent_segment = parent_stack and parent_stack[-1].is_type("expression")
```

diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
+        fixes = _generate_fixes(expr, anchor, operator_new_lines)
+        return LintResult(
+            anchor=anchor,
+            memory=memory,
+            description=description,
+            fixes=fixes,
+        )
+
+
+def _generate_fixes(
+    expr: Tuple[BaseSegment],
+    anchor: BaseSegment,
+    operator_new_lines: str,
+) -> List[LintFix]:
+    """Generate Fixes.
+
+    Take operator_new_lines==after as the default case.
+    Consider "before" as a modification on this behaviour.
+
+    """
+    fixes = [LintFix.delete(anchor)]
+    res = list(_get_surrounding_segments(expr, anchor))
+    if operator_new_lines == "before":
+        res = [list(reversed(els)) for els in reversed(res)]
+
+    change_list, anchor_list = res
+    insert_anchor = anchor_list[-1]
+    change_list = change_list[1:]
+    for el in change_list:
+        fixes.append(LintFix.delete(el))
+
+    change_list.append(anchor)
+    if operator_new_lines == "before":
+        # We do yet another reverse here,
+        # This could be avoided but makes all "changes" relate to "before" config state
+        change_list = list(reversed(change_list))
+
+    change_list = list(map(_copy_el, reversed(change_list)))
+    edit_type = "create_after" if operator_new_lines == "before" else "create_before"
+    fixes.append(
+        LintFix(
+            edit_type=edit_type,
+            edit=change_list,
+            anchor=insert_anchor,
+        )
+    )
+    return fixes
+
+
+def _copy_el(segment: BaseSegment):
+    """Safely duplicate segment without position markers."""
+    el = copy.deepcopy(segment)
+    el.pos_marker = None  # type: ignore
+    return el
+
+
+def _get_surrounding_segments(
+    expr: Tuple[BaseSegment],
+    symbol: BaseSegment,
+) -> Tuple[List[BaseSegment], List[BaseSegment]]:
+    """Create two lists of symbols surrounding operator.
+
+    1 from last code to operator/symbol,
+    2 from operator to next code
+
+    """
+    before_list: List[BaseSegment] = []
+    after_list: List[BaseSegment] = []
+    start_code: Optional[BaseSegment] = None
+    symbol_hit = False
+    for el in expr:
+        if el == symbol:
+            symbol_hit = True
+            continue
+        if el.is_code:
+            if symbol_hit:
+                # Exit condition, we have found code on the other side of the operator
+                after_list.append(el)
+                return before_list, after_list
+
+            start_code = el
+            before_list = []
+            after_list = []
+
+        if symbol_hit:
+            after_list.append(el)
+            continue
+
+        if start_code:
+            before_list.append(el)
+    # This code is unreachable if a valid expr and symbol/operator segment are passed.
+    err = "Ivalid Expr: no binary_operator/comparison_operator"  # pragma: no cover
path: src/sqlfluff/rules/L007.py
text: @user1:
```suggestion
    err = "Invalid Expr: no binary_operator/comparison_operator"  # pragma: no cover
```

diff_hunk: @@ -60,49 +67,149 @@ def _eval(self, context: RuleContext) -> LintResult:
         """
         anchor = None
         memory = context.memory
+        parent_stack = context.parent_stack
+        fixes: List[LintFix] = []
+        # bring var to this scope so as to only have one type ignore
+        operator_new_lines: str = self.operator_new_lines  # type: ignore
         description = after_description
-        if self.operator_new_lines == "before":  # type: ignore
+        if operator_new_lines == "before":
             description = before_description
 
         # The parent stack tells us whether we're in an expression or not.
-        if context.parent_stack and context.parent_stack[-1].is_type("expression"):
-            if context.segment.is_code:
-                # This is code, what kind?
-                if context.segment.is_type("binary_operator", "comparison_operator"):
-                    # If it's an operator, then check if in "before" mode
-                    if self.operator_new_lines == "before":  # type: ignore
-                        # If we're in "before" mode, then check if newline since last
-                        # code
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark this operator as a fail
-                                anchor = context.segment
-                                # TODO: Work out a nice fix for this failure.
-                elif memory["last_code"] and memory["last_code"].is_type(
-                    "binary_operator", "comparison_operator"
-                ):
-                    # It's not an operator, but the last code was.
-                    if self.operator_new_lines != "before":  # type: ignore
-                        # If in "after" mode, then check to see
-                        # there is a newline between us and the last operator.
-                        for s in memory["since_code"]:
-                            if s.name == "newline":
-                                # Had a newline - so mark last operator as a fail
-                                anchor = memory["last_code"]
-                                # TODO: Work out a nice fix for this failure.
-                # Prepare memory for later
-                memory["last_code"] = context.segment
-                memory["since_code"] = []
-            else:
-                # This isn't a code segment...
-                # Prepare memory for later
-                memory["since_code"].append(context.segment)
-        else:
+        is_relavent_segment = parent_stack and parent_stack[-1].is_type("expression")
+        if not is_relavent_segment:
             # Reset the memory if we're not in an expression
             memory = {"last_code": None, "since_code": []}
+            return LintResult(memory=memory)
+
+        if not context.segment.is_code:
+            # This isn't a code segment...
+            # Prepare memory for later
+            memory["since_code"].append(context.segment)
+            return LintResult(memory=memory)
+
+        # This is code, what kind?
+        if context.segment.is_type("binary_operator", "comparison_operator"):
+            # If it's an operator, then check if in "before" mode
+            if operator_new_lines == "before":
+                # If we're in "before" mode, then check if newline since last
+                # code
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        anchor = context.segment
+                        # Had a newline - so mark this operator as a fail
+
+        elif memory["last_code"] and memory["last_code"].is_type(
+            "binary_operator", "comparison_operator"
+        ):
+            # It's not an operator, but the last code was.
+            if operator_new_lines != "before":
+                # If in "after" mode, then check to see
+                # there is a newline between us and the last operator.
+                for s in memory["since_code"]:
+                    if s.name == "newline":
+                        # Had a newline - so mark last operator as a fail
+                        anchor = memory["last_code"]
+
+        # Prepare memory for later
+        memory["last_code"] = context.segment
+        memory["since_code"] = []
 
         # Anchor is our signal as to whether there's a problem
-        if anchor:
-            return LintResult(anchor=anchor, memory=memory, description=description)
-        else:
+        if not anchor:
             return LintResult(memory=memory)
+
+        expr = cast(Tuple[BaseSegment], parent_stack[-1].segments)
+        fixes = _generate_fixes(expr, anchor, operator_new_lines)
+        return LintResult(
+            anchor=anchor,
+            memory=memory,
+            description=description,
+            fixes=fixes,
+        )
+
+
+def _generate_fixes(
+    expr: Tuple[BaseSegment],
+    anchor: BaseSegment,
+    operator_new_lines: str,
+) -> List[LintFix]:
+    """Generate Fixes.
+
+    Take operator_new_lines==after as the default case.
+    Consider "before" as a modification on this behaviour.
+
+    """
+    fixes = [LintFix.delete(anchor)]
+    res = list(_get_surrounding_segments(expr, anchor))
+    if operator_new_lines == "before":
+        res = [list(reversed(els)) for els in reversed(res)]
+
+    change_list, anchor_list = res
+    insert_anchor = anchor_list[-1]
+    change_list = change_list[1:]
+    for el in change_list:
+        fixes.append(LintFix.delete(el))
+
+    change_list.append(anchor)
+    if operator_new_lines == "before":
+        # We do yet another reverse here,
+        # This could be avoided but makes all "changes" relate to "before" config state
+        change_list = list(reversed(change_list))
+
+    change_list = list(map(_copy_el, reversed(change_list)))
+    edit_type = "create_after" if operator_new_lines == "before" else "create_before"
+    fixes.append(
+        LintFix(
+            edit_type=edit_type,
+            edit=change_list,
+            anchor=insert_anchor,
+        )
+    )
+    return fixes
+
+
+def _copy_el(segment: BaseSegment):
+    """Safely duplicate segment without position markers."""
+    el = copy.deepcopy(segment)
+    el.pos_marker = None  # type: ignore
+    return el
+
+
+def _get_surrounding_segments(
+    expr: Tuple[BaseSegment],
path: src/sqlfluff/rules/L007.py
text: @user1:
Does `expr` contain multiple `BaseSegments`? If so, I think this should be:
```suggestion
    expr: Tuple[BaseSegment, ...],
```