text: """Implementation of Rule L052."""
  from typing import List, Optional
  
  from sqlfluff.core.parser import SymbolSegment
  from sqlfluff.core.parser.segments.base import BaseSegment
  from sqlfluff.core.parser.segments.raw import NewlineSegment
  
  from sqlfluff.core.rules.base import BaseRule, LintResult, LintFix, RuleContext
  from sqlfluff.core.rules.doc_decorators import (
      document_configuration,
      document_fix_compatible,
  )
  import sqlfluff.core.rules.functional.segment_predicates as sp
  
  
  @document_configuration
  @document_fix_compatible
  class Rule_L052(BaseRule):
      """Statements must end with a semi-colon.
  
      | **Anti-pattern**
      | A statement is not immediately terminated with a semi-colon, the • represents
      | space.
  
      .. code-block:: sql
         :force:
  
          SELECT
              a
          FROM foo
  
          ;
  
          SELECT
              b
          FROM bar••;
  
      | **Best practice**
      | Immediately terminate the statement with a semi-colon.
  
      .. code-block:: sql
         :force:
  
          SELECT
              a
          FROM foo;
      """
  
      config_keywords = ["multiline_newline", "require_final_semicolon"]
  
      @staticmethod
      def _handle_preceding_inline_comments(pre_semicolon_segments, anchor_segment):
          """Adjust segments to not move preceding inline comments.
  
          We don't want to move inline comments that are on the same line
          as the preceding code segment as they could contain noqa instructions.
          """
          # See if we have a preceding inline comment on the same line as the preceding
          # segment.
          same_line_comment = next(
              (
                  s
                  for s in pre_semicolon_segments
                  if s.is_comment
                  and s.name != "block_comment"
                  and s.pos_marker.working_line_no
                  == anchor_segment.pos_marker.working_line_no
              ),
              None,
          )
          # If so then make that our new anchor segment and adjust
          # pre_semicolon_segments accordingly.
          if same_line_comment:
              anchor_segment = same_line_comment
              pre_semicolon_segments = pre_semicolon_segments[
                  : pre_semicolon_segments.index(same_line_comment)
              ]
  
          return pre_semicolon_segments, anchor_segment
  
      @staticmethod
      def _handle_trailing_inline_comments(context, anchor_segment):
          """Adjust anchor_segment to not move trailing inline comment.
  
          We don't want to move inline comments that are on the same line
          as the preceding code segment as they could contain noqa instructions.
          """
          # See if we have a trailing inline comment on the same line as the preceding
          # segment.
          for parent_segment in context.parent_stack[::-1]:
              for comment_segment in parent_segment.recursive_crawl("comment"):
                  if (
                      comment_segment.pos_marker.working_line_no
                      == anchor_segment.pos_marker.working_line_no
                  ) and (comment_segment.name != "block_comment"):
                      anchor_segment = comment_segment
  
          return anchor_segment
  
      @staticmethod
      def _is_one_line_statement(context, segment):
          """Check if the statement containing the provided segment is one line."""
          # Find statement segment containing the current segment.
          statement_segment = next(
              (
                  s
                  for s in context.parent_stack[0].path_to(segment)
                  if s.is_type("statement")
              ),
              None,
          )
          if statement_segment is None:  # pragma: no cover
              # If we can't find a parent statement segment then don't try anything
              # special.
              return False
  
          if not any(statement_segment.recursive_crawl("newline")):
              # Statement segment has no newlines therefore starts and ends on the same
              # line.
              return True
  
          return False
  
      def _eval(self, context: RuleContext) -> Optional[LintResult]:
          """Statements must end with a semi-colon."""
          # Config type hints
          self.multiline_newline: bool
          self.require_final_semicolon: bool
  
          # First we can simply handle the case of existing semi-colon alignment.
          if context.segment.name == "semicolon":
  
              # Locate semicolon and search back over the raw stack
              # to find the end of the preceding statement.
              reversed_raw_stack = context.functional.raw_stack.reversed()
              before_code = reversed_raw_stack.select(loop_while=sp.not_(sp.is_code()))
              pre_semicolon_segments = before_code.select(sp.not_(sp.is_meta()))
              anchor_segment = before_code[-1] if before_code else context.segment
              first_code = reversed_raw_stack.select(sp.is_code()).first()
              is_one_line = (
                  self._is_one_line_statement(context, first_code[0])
                  if first_code
                  else False
              )
  
              # We can tidy up any whitespace between the semi-colon
              # and the preceding code/comment segment.
              # Don't mess with comment spacing/placement.
              whitespace_deletions = pre_semicolon_segments.select(
                  loop_while=sp.is_whitespace()
              )
  
              semicolon_newline = self.multiline_newline if not is_one_line else False
  
              # Semi-colon on same line.
              if not semicolon_newline:
                  if len(pre_semicolon_segments) >= 1:
                      # If preceding segments are found then delete the old
                      # semi-colon and its preceding whitespace and then insert
                      # the semi-colon in the correct location.
                      fixes = [
                          LintFix.replace(
                              anchor_segment,
                              [
                                  anchor_segment,
                                  SymbolSegment(raw=";", type="symbol", name="semicolon"),
                              ],
                          ),
                          LintFix.delete(
                              context.segment,
                          ),
                      ]
                      fixes.extend(LintFix.delete(d) for d in whitespace_deletions)
                      return LintResult(
                          anchor=anchor_segment,
                          fixes=fixes,
                      )
              # Semi-colon on new line.
              else:
                  # Adjust pre_semicolon_segments and anchor_segment for preceding inline
                  # comments. Inline comments can contain noqa logic so we need to add the
                  # newline after the inline comment.
                  (
                      pre_semicolon_segments,
                      anchor_segment,
                  ) = self._handle_preceding_inline_comments(
                      pre_semicolon_segments, anchor_segment
                  )
  
                  if not (
                      (len(pre_semicolon_segments) == 1)
                      and all(s.is_type("newline") for s in pre_semicolon_segments)
                  ):
                      # If preceding segment is not a single newline then delete the old
                      # semi-colon/preceding whitespace and then insert the
                      # semi-colon in the correct location.
  
                      # This handles an edge case in which an inline comment comes after
                      # the semi-colon.
                      anchor_segment = self._handle_trailing_inline_comments(
                          context, anchor_segment
                      )
                      fixes = []
                      if anchor_segment is context.segment:
                          fixes.append(
                              LintFix.replace(
                                  anchor_segment,
                                  [
                                      NewlineSegment(),
                                      SymbolSegment(
                                          raw=";", type="symbol", name="semicolon"
                                      ),
                                  ],
                              )
                          )
                      else:
                          fixes.extend(
                              [
                                  LintFix.replace(
                                      anchor_segment,
                                      [
                                          anchor_segment,
                                          NewlineSegment(),
                                          SymbolSegment(
                                              raw=";", type="symbol", name="semicolon"
                                          ),
                                      ],
                                  ),
                                  LintFix.delete(
                                      context.segment,
                                  ),
                              ]
                          )
                          fixes.extend(LintFix.delete(d) for d in whitespace_deletions)
                      return LintResult(
                          anchor=anchor_segment,
                          fixes=fixes,
                      )
  
          # SQL does not require a final trailing semi-colon, however
          # this rule looks to enforce that it is there.
          if self.require_final_semicolon:
              # Locate the end of the file.
              if not self.is_final_segment(context):
                  return None
  
              # Include current segment for complete stack.
              complete_stack: List[BaseSegment] = list(context.raw_stack)
              complete_stack.append(context.segment)
  
              # Iterate backwards over complete stack to find
              # if the final semi-colon is already present.
              anchor_segment = context.segment
              semi_colon_exist_flag = False
              is_one_line = False
              pre_semicolon_segments = []
              for segment in complete_stack[::-1]:
                  if segment.name == "semicolon":
                      semi_colon_exist_flag = True
                  elif segment.is_code:
                      is_one_line = self._is_one_line_statement(context, segment)
                      break
                  elif not segment.is_meta:
                      pre_semicolon_segments.append(segment)
                  anchor_segment = segment
  
              semicolon_newline = self.multiline_newline if not is_one_line else False
  
              if not semi_colon_exist_flag:
                  # Create the final semi-colon if it does not yet exist.
  
                  # Semi-colon on same line.
                  if not semicolon_newline:
                      fixes = [
                          LintFix.replace(
                              anchor_segment,
                              [
                                  anchor_segment,
                                  SymbolSegment(raw=";", type="symbol", name="semicolon"),
                              ],
                          )
                      ]
                  # Semi-colon on new line.
                  else:
                      # Adjust pre_semicolon_segments and anchor_segment for inline
                      # comments.
                      (
                          pre_semicolon_segments,
                          anchor_segment,
                      ) = self._handle_preceding_inline_comments(
                          pre_semicolon_segments, anchor_segment
                      )
                      fixes = [
                          LintFix.replace(
                              anchor_segment,
                              [
                                  anchor_segment,
                                  NewlineSegment(),
                                  SymbolSegment(raw=";", type="symbol", name="semicolon"),
                              ],
                          )
                      ]
  
                  return LintResult(
                      anchor=anchor_segment,
                      fixes=fixes,
                  )
  
          return None
  