예제 #1
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        # We only care about commas.
        if context.segment.name != "comma":
            return None

        # Get subsequent whitespace segment and the first non-whitespace segment.
        subsequent_whitespace, first_non_whitespace = self._get_subsequent_whitespace(
            context
        )

        if (
            not subsequent_whitespace
            and (first_non_whitespace is not None)
            and (not first_non_whitespace.is_type("newline"))
        ):
            # No trailing whitespace and not followed by a newline,
            # therefore create a whitespace after the comma.
            return LintResult(
                anchor=first_non_whitespace,
                fixes=[LintFix.create_after(context.segment, [WhitespaceSegment()])],
            )
        elif (
            subsequent_whitespace
            and (subsequent_whitespace.raw != " ")
            and (first_non_whitespace is not None)
            and (not first_non_whitespace.is_comment)
        ):
            # Excess trailing whitespace therefore edit to only be one space long.
            return LintResult(
                anchor=subsequent_whitespace,
                fixes=[LintFix.replace(subsequent_whitespace, [WhitespaceSegment()])],
            )

        return None
예제 #2
0
    def _eval(self, segment, parent_stack, raw_stack, **kwargs):
        """Implicit aliasing of table/column not allowed. Use explicit `AS` clause.

        We look for the alias segment, and then evaluate its parent and whether
        it contains an AS keyword. This is the _eval function for both L011 and L012.

        The use of `raw_stack` is just for working out how much whitespace to add.

        """
        if segment.is_type("alias_expression"):
            if parent_stack[-1].is_type(*self._target_elems):
                if not any(e.name.lower() == "as" for e in segment.segments):
                    insert_buff = []
                    insert_str = ""

                    # Add initial whitespace if we need to...
                    if raw_stack[-1].name not in ["whitespace", "newline"]:
                        insert_buff.append(WhitespaceSegment())
                        insert_str += " "

                    # Add an AS (Uppercase for now, but could be corrected later)
                    insert_buff.append(KeywordSegment("AS"))
                    insert_str += "AS"

                    # Add a trailing whitespace if we need to
                    if segment.segments[0].name not in ["whitespace", "newline"]:
                        insert_buff.append(WhitespaceSegment())
                        insert_str += " "

                    return LintResult(
                        anchor=segment,
                        fixes=[LintFix("create", segment.segments[0], insert_buff)],
                    )
        return None
예제 #3
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Commas should be followed by a single whitespace unless followed by a comment.

        This is a slightly odd one, because we'll almost always evaluate from a point a few places
        after the problem site. NB: We need at least two segments behind us for this to work.
        """
        if len(raw_stack) < 2:
            return None

        cm1 = raw_stack[-1]
        cm2 = raw_stack[-2]
        if cm2.name == "comma":
            # comma followed by something that isn't whitespace?
            if cm1.name not in ["whitespace", "newline"]:
                ins = WhitespaceSegment(raw=" ")
                return LintResult(anchor=cm1,
                                  fixes=[LintFix("create", cm1, ins)])
            # comma followed by too much whitespace?
            if (cm1.raw != " "
                    and cm1.name != "newline") and not segment.is_comment:
                repl = WhitespaceSegment(raw=" ")
                return LintResult(anchor=cm1,
                                  fixes=[LintFix("edit", cm1, repl)])
        # Otherwise we're fine
        return None
예제 #4
0
파일: L043.py 프로젝트: sqlfluff/sqlfluff
    def _coalesce_fix_list(
        context: RuleContext,
        coalesce_arg_1: BaseSegment,
        coalesce_arg_2: BaseSegment,
        preceding_not: bool = False,
    ) -> List[LintFix]:
        """Generate list of fixes to convert CASE statement to COALESCE function."""
        # Add coalesce and opening parenthesis.
        edits = [
            KeywordSegment("coalesce"),
            SymbolSegment("(", type="start_bracket"),
            coalesce_arg_1,
            SymbolSegment(",", type="comma"),
            WhitespaceSegment(),
            coalesce_arg_2,
            SymbolSegment(")", type="end_bracket"),
        ]

        if preceding_not:
            not_edits: List[BaseSegment] = [
                KeywordSegment("not"),
                WhitespaceSegment(),
            ]
            edits = not_edits + edits

        fixes = [LintFix.replace(
            context.segment,
            edits,
        )]
        return fixes
예제 #5
0
파일: L003.py 프로젝트: stjordanis/sqlfluff
 def _coerce_indent_to(self, desired_indent, current_indent_buffer, current_anchor):
     """Generate fixes to make an indent a certain size."""
     # If there shouldn't be an indent at all, just delete.
     if len(desired_indent) == 0:
         fixes = [LintFix("delete", elem) for elem in current_indent_buffer]
     # If we don't have any indent and we should, then add a single
     elif len("".join(elem.raw for elem in current_indent_buffer)) == 0:
         fixes = [
             LintFix(
                 "create",
                 current_anchor,
                 WhitespaceSegment(
                     raw=desired_indent,
                 ),
             )
         ]
     # Otherwise edit the first element to be the right size
     else:
         # Edit the first element of this line's indent.
         fixes = [
             LintFix(
                 "edit",
                 current_indent_buffer[0],
                 WhitespaceSegment(
                     raw=desired_indent,
                 ),
             )
         ]
     return fixes
예제 #6
0
    def _eval(self, segment, parent_stack, raw_stack, **kwargs):
        """Implicit aliasing of table/column not allowed. Use explicit `AS` clause.

        We look for the alias segment, and then evaluate its parent and whether
        it contains an AS keyword. This is the _eval function for both L011 and L012.

        The use of `raw_stack` is just for working out how much whitespace to add.

        """
        fixes = []

        if segment.is_type("alias_expression"):
            if parent_stack[-1].is_type(*self._target_elems):
                if any(e.name.lower() == "as" for e in segment.segments):
                    if self.aliasing == "implicit":
                        if segment.segments[0].name.lower() == "as":

                            # Remove the AS as we're using implict aliasing
                            fixes.append(LintFix("delete",
                                                 segment.segments[0]))
                            anchor = raw_stack[-1]

                            # Remove whitespace before (if exists) or after (if not)
                            if (len(raw_stack) > 0
                                    and raw_stack[-1].type == "whitespace"):
                                fixes.append(LintFix("delete", raw_stack[-1]))
                            elif (len(segment.segments) > 0 and
                                  segment.segments[1].type == "whitespace"):
                                fixes.append(
                                    LintFix("delete", segment.segments[1]))

                            return LintResult(anchor=anchor, fixes=fixes)

                else:
                    insert_buff = []

                    # Add initial whitespace if we need to...
                    if raw_stack[-1].name not in ["whitespace", "newline"]:
                        insert_buff.append(WhitespaceSegment())

                    # Add an AS (Uppercase for now, but could be corrected later)
                    insert_buff.append(KeywordSegment("AS"))

                    # Add a trailing whitespace if we need to
                    if segment.segments[0].name not in [
                            "whitespace", "newline"
                    ]:
                        insert_buff.append(WhitespaceSegment())

                    return LintResult(
                        anchor=segment,
                        fixes=[
                            LintFix("create", segment.segments[0], insert_buff)
                        ],
                    )
        return None
예제 #7
0
    def _eval(self, context: RuleContext) -> LintResult:
        """Look for UNION keyword not immediately followed by DISTINCT or ALL.

        Note that UNION DISTINCT is valid, rule only applies to bare UNION.
        The function does this by looking for a segment of type set_operator
        which has a UNION but no DISTINCT or ALL.

        Note only some dialects have concept of UNION DISTINCT, so rule is only
        applied to dialects that are known to support this syntax.
        """
        if context.dialect.name not in [
                "ansi",
                "bigquery",
                "hive",
                "mysql",
                "redshift",
        ]:
            return LintResult()

        if context.segment.is_type("set_operator"):
            if "union" in context.segment.raw and not (
                    "ALL" in context.segment.raw.upper()
                    or "DISTINCT" in context.segment.raw.upper()):
                return LintResult(
                    anchor=context.segment,
                    fixes=[
                        LintFix.replace(
                            context.segment.segments[0],
                            [
                                KeywordSegment("union"),
                                WhitespaceSegment(),
                                KeywordSegment("distinct"),
                            ],
                        )
                    ],
                )
            elif "UNION" in context.segment.raw.upper() and not (
                    "ALL" in context.segment.raw.upper()
                    or "DISTINCT" in context.segment.raw.upper()):
                return LintResult(
                    anchor=context.segment,
                    fixes=[
                        LintFix.replace(
                            context.segment.segments[0],
                            [
                                KeywordSegment("UNION"),
                                WhitespaceSegment(),
                                KeywordSegment("DISTINCT"),
                            ],
                        )
                    ],
                )
        return LintResult()
예제 #8
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Look for UNION keyword not immediately followed by DISTINCT or ALL.

        Note that UNION DISTINCT is valid, rule only applies to bare UNION.
        The function does this by looking for a segment of type set_operator
        which has a UNION but no DISTINCT or ALL.
        """
        if segment.is_type("set_operator"):
            if "UNION" in segment.raw.upper() and not (
                "ALL" in segment.raw.upper() or "DISTINCT" in segment.raw.upper()
            ):
                return LintResult(
                    anchor=segment,
                    fixes=[
                        LintFix(
                            "edit",
                            segment.segments[0],
                            [
                                KeywordSegment("UNION"),
                                WhitespaceSegment(),
                                KeywordSegment("DISTINCT"),
                            ],
                        )
                    ],
                )
        return LintResult()
예제 #9
0
파일: L058.py 프로젝트: sqlfluff/sqlfluff
    def _eval(self, context: RuleContext) -> LintResult:
        """Nested CASE statement in ELSE clause could be flattened."""
        segment = FunctionalContext(context).segment
        assert segment.select(sp.is_type("case_expression"))
        case1_children = segment.children()
        case1_last_when = case1_children.last(sp.is_type("when_clause")).get()
        case1_else_clause = case1_children.select(sp.is_type("else_clause"))
        case1_else_expressions = case1_else_clause.children(
            sp.is_type("expression"))
        expression_children = case1_else_expressions.children()
        case2 = expression_children.select(sp.is_type("case_expression"))
        # The len() checks below are for safety, to ensure the CASE inside
        # the ELSE is not part of a larger expression. In that case, it's
        # not safe to simplify in this way -- we'd be deleting other code.
        if (not case1_last_when or len(case1_else_expressions) > 1
                or len(expression_children) > 1 or not case2):
            return LintResult()

        # We can assert that this exists because of the previous check.
        assert case1_last_when
        # We can also assert that we'll also have an else clause because
        # otherwise the case2 check above would fail.
        case1_else_clause_seg = case1_else_clause.get()
        assert case1_else_clause_seg

        # Delete stuff between the last "WHEN" clause and the "ELSE" clause.
        case1_to_delete = case1_children.select(start_seg=case1_last_when,
                                                stop_seg=case1_else_clause_seg)

        # Delete the nested "CASE" expression.
        fixes = case1_to_delete.apply(lambda seg: LintFix.delete(seg))

        # Determine the indentation to use when we move the nested "WHEN"
        # and "ELSE" clauses, based on the indentation of case1_last_when.
        # If no whitespace segments found, use default indent.
        indent = (case1_children.select(
            stop_seg=case1_last_when).reversed().select(
                sp.is_type("whitespace")))
        indent_str = "".join(seg.raw
                             for seg in indent) if indent else self.indent

        # Move the nested "when" and "else" clauses after the last outer
        # "when".
        nested_clauses = case2.children(
            sp.is_type("when_clause", "else_clause"))
        create_after_last_when = nested_clauses.apply(
            lambda seg: [NewlineSegment(),
                         WhitespaceSegment(indent_str), seg])
        segments = [
            item for sublist in create_after_last_when for item in sublist
        ]
        fixes.append(
            LintFix.create_after(case1_last_when, segments, source=segments))

        # Delete the outer "else" clause.
        fixes.append(LintFix.delete(case1_else_clause_seg))
        return LintResult(case2[0], fixes=fixes)
예제 #10
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Commas should be followed by a single whitespace unless followed by a comment.

        This is a slightly odd one, because we'll almost always evaluate from a point a few places
        after the problem site. NB: We need at least two segments behind us for this to work.
        """
        if len(raw_stack) < 1:
            return None

        # Get the first element of this segment.
        first_elem = next(segment.iter_raw_seg())

        cm1 = raw_stack[-1]
        if cm1.name == "comma":
            # comma followed by something that isn't whitespace?
            if first_elem.name not in ["whitespace", "newline", "Dedent"]:
                self.logger.debug(
                    "Comma followed by something other than whitespace: %s",
                    first_elem)
                ins = WhitespaceSegment(raw=" ")
                return LintResult(
                    anchor=cm1,
                    fixes=[LintFix("edit", segment, [ins, segment])])

        if len(raw_stack) < 2:
            return None

        cm2 = raw_stack[-2]
        if cm2.name == "comma":
            # comma followed by too much whitespace?
            if (cm1.is_whitespace  # Must be whitespace
                    and cm1.raw != " "  # ...and not a single one
                    and cm1.name != "newline"  # ...and not a newline
                    and not first_elem.
                    is_comment  # ...and not followed by a comment
                ):
                self.logger.debug("Comma followed by too much whitespace: %s",
                                  cm1)
                repl = WhitespaceSegment(raw=" ")
                return LintResult(anchor=cm1,
                                  fixes=[LintFix("edit", cm1, repl)])
        # Otherwise we're fine
        return None
예제 #11
0
    def _eval(self, context: RuleContext) -> LintResult:
        """Incorrect indentation found in file."""
        # Config type hints
        self.tab_space_size: int
        self.indent_unit: str

        tab = "\t"
        space = " "
        correct_indent = self.indent
        wrong_indent = (
            tab if self.indent_unit == "space" else space * self.tab_space_size
        )
        if (
            context.segment.is_type("whitespace")
            and wrong_indent in context.segment.raw
        ):
            fixes = []
            description = "Incorrect indentation type found in file."
            edit_indent = context.segment.raw.replace(wrong_indent, correct_indent)
            pre_seg = context.raw_stack[-1] if context.raw_stack else None
            # Ensure that the number of space indents is a multiple of tab_space_size
            # before attempting to convert spaces to tabs to avoid mixed indents
            # unless we are converted tabs to spaces (indent_unit = space)
            if (
                (
                    self.indent_unit == "space"
                    or context.segment.raw.count(space) % self.tab_space_size == 0
                )
                # Only attempt a fix at the start of a newline for now
                and (pre_seg is None or pre_seg.is_type("newline"))
            ):
                fixes = [
                    LintFix.replace(
                        context.segment,
                        [
                            WhitespaceSegment(raw=edit_indent),
                        ],
                    )
                ]
            elif not (pre_seg is None or pre_seg.is_type("newline")):
                # give a helpful message if the wrong indent has been found and is not
                # at the start of a newline
                description += (
                    " The indent occurs after other text, so a manual fix is needed."
                )
            else:
                # If we get here, the indent_unit is tabs, and the number of spaces is
                # not a multiple of tab_space_size
                description += " The number of spaces is not a multiple of "
                "tab_space_size, so a manual fix is needed."
            return LintResult(
                anchor=context.segment, fixes=fixes, description=description
            )
        return LintResult()
예제 #12
0
 def _eval(self, context):
     """Stars make newlines."""
     if context.segment.is_type("whitespace"):
         return LintResult(
             anchor=context.segment,
             fixes=[
                 LintFix.replace(
                     context.segment,
                     [WhitespaceSegment(context.segment.raw + " ")])
             ],
         )
예제 #13
0
파일: L049.py 프로젝트: sqlfluff/sqlfluff
def _create_base_is_null_sequence(
    is_upper: bool,
    operator_raw: str,
) -> CorrectionListType:
    is_seg = KeywordSegment("IS" if is_upper else "is")
    not_seg = KeywordSegment("NOT" if is_upper else "not")
    if operator_raw == "=":
        return [is_seg]

    return [
        is_seg,
        WhitespaceSegment(),
        not_seg,
    ]
예제 #14
0
파일: L003.py 프로젝트: sti0/sqlfluff
 def _coerce_indent_to(
     self,
     desired_indent: str,
     current_indent_buffer: Tuple[RawSegment, ...],
     current_anchor: BaseSegment,
 ) -> List[LintFix]:
     """Generate fixes to make an indent a certain size."""
     # If there shouldn't be an indent at all, just delete.
     if len(desired_indent) == 0:
         fixes = [LintFix.delete(elem) for elem in current_indent_buffer]
     # If we don't have any indent and we should, then add a single
     elif len("".join(elem.raw for elem in current_indent_buffer)) == 0:
         fixes = [
             LintFix.create_before(
                 current_anchor,
                 [
                     WhitespaceSegment(
                         raw=desired_indent,
                     ),
                 ],
             ),
         ]
     # Otherwise edit the first element to be the right size
     else:
         # Edit the first element of this line's indent and remove any other
         # indents.
         fixes = [
             LintFix.replace(
                 current_indent_buffer[0],
                 [
                     WhitespaceSegment(
                         raw=desired_indent,
                     ),
                 ],
             ),
         ] + [LintFix.delete(elem) for elem in current_indent_buffer[1:]]
     return fixes
예제 #15
0
파일: L023.py 프로젝트: sti0/sqlfluff
 def _eval(self, context: RuleContext) -> Optional[List[LintResult]]:
     """Single whitespace expected in mother middle segment."""
     error_buffer: List[LintResult] = []
     if context.segment.is_type(self.expected_mother_segment_type):
         last_code = None
         mid_segs: List[BaseSegment] = []
         for seg in context.segment.iter_segments(
                 expanding=self.expand_children):
             if seg.is_code:
                 if (last_code and self.matches_target_tuples(
                         last_code, [self.pre_segment_identifier])
                         and self.matches_target_tuples(
                             seg, [self.post_segment_identifier])):
                     # Do we actually have the right amount of whitespace?
                     raw_inner = "".join(s.raw for s in mid_segs)
                     if raw_inner != " " and not (self.allow_newline and
                                                  any(s.name == "newline"
                                                      for s in mid_segs)):
                         if not raw_inner.strip():
                             # There's some whitespace and/or newlines, or nothing
                             fixes = []
                             if raw_inner:
                                 # There's whitespace and/or newlines. Drop those.
                                 fixes += [
                                     LintFix.delete(mid_seg)
                                     for mid_seg in mid_segs
                                 ]
                             # Enforce a single space
                             fixes += [
                                 LintFix.create_before(
                                     seg,
                                     [WhitespaceSegment()],
                                 )
                             ]
                         else:
                             # Don't otherwise suggest a fix for now.
                             # Only whitespace & newlines are covered.
                             # At least a comment section between `AS` and `(` can
                             # result in an unfixable error.
                             # TODO: Enable more complex fixing here.
                             fixes = None  # pragma: no cover
                         error_buffer.append(
                             LintResult(anchor=last_code, fixes=fixes))
                 mid_segs = []
                 if not seg.is_meta:
                     last_code = seg
             else:
                 mid_segs.append(seg)
     return error_buffer or None
예제 #16
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Looking for DISTINCT before a bracket.

        Look for DISTINCT keyword immediately followed by open parenthesis.
        """
        # We only trigger when "DISTINCT" is the immediate parent of an
        # expression that begins with start_bracket.
        raw_stack_filtered = self.filter_meta(raw_stack)
        if raw_stack_filtered and raw_stack_filtered[-1].name == "distinct":
            if segment.is_type("expression"):
                segments_filtered = self.filter_meta(segment.segments)
                if segments_filtered and segments_filtered[0].is_type(
                        "start_bracket"):
                    # If we find open_bracket immediately following DISTINCT,
                    # then bad.
                    fixes = []
                    # The end bracket could be anywhere in segments_filtered,
                    # e.g. if the expression is (a + b) * c. If and only if it's
                    # at the *end*, then the parentheses are unnecessary and
                    # confusing. Remove them.
                    if segments_filtered[-1].is_type("end_bracket"):
                        fixes += [
                            LintFix("delete", segments_filtered[0]),
                            LintFix("delete", segments_filtered[-1]),
                        ]
                        # Update segments_filtered to reflect the pending
                        # deletions.
                        segments_filtered = segments_filtered[1:-1]
                    # If there are still segments remaining after the potential
                    # deletions above, insert a space between DISTINCT and the
                    # remainder of the expression. (I think there will always
                    # be remaining segments; this is a sanity check to ensure
                    # we don't cause an IndexError.)
                    if segments_filtered:
                        # Insert a single space after the open parenthesis being
                        # removed. Reason: DISTINCT is not a function; it's more
                        # of a modifier that acts on all the columns. Therefore,
                        # adding a space makes it clearer what the SQL is
                        # actually doing.
                        insert_str = " "
                        first_segment = segments_filtered[0]
                        fixes.append(
                            LintFix(
                                "create",
                                first_segment,
                                [WhitespaceSegment(raw=insert_str)],
                            ))
                    return LintResult(anchor=segment, fixes=fixes)
        return None
예제 #17
0
    def _eval(self, segment, **kwargs):
        """Select clause modifiers must appear on same line as SELECT."""
        if segment.is_type("select_clause"):
            # Does the select clause have modifiers?
            select_modifier = segment.get_child("select_clause_modifier")
            if not select_modifier:
                return None  # No. We're done.
            select_modifier_idx = segment.segments.index(select_modifier)

            # Does the select clause contain a newline?
            newline = segment.get_child("newline")
            if not newline:
                return None  # No. We're done.
            newline_idx = segment.segments.index(newline)

            # Is there a newline before the select modifier?
            if newline_idx > select_modifier_idx:
                return None  # No, we're done.

            # Yes to all the above. We found an issue.

            # E.g.: " DISTINCT\n"
            replace_newline_with = [
                WhitespaceSegment(),
                select_modifier,
                NewlineSegment(),
            ]
            fixes = [
                # E.g. "\n" -> " DISTINCT\n.
                LintFix("edit", newline, replace_newline_with),
                # E.g. "DISTINCT" -> X
                LintFix("delete", select_modifier),
            ]

            # E.g. " " after "DISTINCT"
            ws_to_delete = segment.select_children(
                start_seg=select_modifier,
                select_if=lambda s: s.is_type("whitespace"),
                loop_while=lambda s: s.is_type("whitespace") or s.is_meta,
            )

            # E.g. " " -> X
            fixes += [LintFix("delete", ws) for ws in ws_to_delete]
            return LintResult(
                anchor=segment,
                fixes=fixes,
            )
예제 #18
0
def test__parser__grammar_anysetof(generate_test_segments):
    """Test the AnySetOf grammar."""
    token_list = ["bar", "  \t ", "foo", "  \t ", "bar"]
    seg_list = generate_test_segments(token_list)

    bs = StringParser("bar", KeywordSegment)
    fs = StringParser("foo", KeywordSegment)
    g = AnySetOf(fs, bs)
    with RootParseContext(dialect=None) as ctx:
        # Check directly
        assert g.match(seg_list, parse_context=ctx).matched_segments == (
            KeywordSegment("bar", seg_list[0].pos_marker),
            WhitespaceSegment("  \t ", seg_list[1].pos_marker),
            KeywordSegment("foo", seg_list[2].pos_marker),
        )
        # Check with a bit of whitespace
        assert not g.match(seg_list[1:], parse_context=ctx)
예제 #19
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Looking for DISTINCT before a bracket.

        Look for DISTINCT keyword immediately followed by open parenthesis.
        """
        # We trigger on `select_clause` and look for `select_clause_modifier`
        if segment.is_type("select_clause"):
            modifier = segment.get_child("select_clause_modifier")
            if not modifier:
                return None
            first_element = segment.get_child("select_clause_element")
            if not first_element:
                return None
            # is the first element only an expression with only brackets?
            expression = first_element.get_child("expression")
            if not expression:
                expression = first_element
            bracketed = expression.get_child("bracketed")
            if not bracketed:
                return None
            fixes = []
            # If there's nothing else in the expression, remove the brackets.
            if len(expression.segments) == 1:
                # Remove the brackets, and strip any meta segments.
                fixes = [
                    LintFix(
                        "edit", bracketed, self.filter_meta(bracketed.segments)[1:-1]
                    ),
                ]
            # Is there any whitespace between DISTINCT and the expression?
            distinct_idx = segment.segments.index(modifier)
            elem_idx = segment.segments.index(first_element)
            if not any(
                seg.is_whitespace for seg in segment.segments[distinct_idx:elem_idx]
            ):
                fixes.append(
                    LintFix(
                        "create",
                        first_element,
                        WhitespaceSegment(),
                    )
                )
            # If no fixes, no problem.
            if fixes:
                return LintResult(anchor=modifier, fixes=fixes)
        return None
예제 #20
0
    def _coerce_indent_to(
        self,
        desired_indent: str,
        current_indent_buffer: List[BaseSegment],
        current_anchor: BaseSegment,
    ) -> List[LintFix]:
        """Generate fixes to make an indent a certain size.

        Rather than blindly creating indent, we should _edit_
        if at all possible, this stops other rules trying to
        remove floating double indents.
        """
        existing_whitespace = [
            seg for seg in current_indent_buffer if seg.is_type("whitespace")
        ]
        # Should we have an indent?
        if len(desired_indent) == 0:
            # No? Just delete everything
            return [LintFix.delete(seg) for seg in existing_whitespace]
        else:
            # Is there already an indent?
            if existing_whitespace:
                # Edit the first, delete the rest.
                edit_fix = LintFix.replace(
                    existing_whitespace[0],
                    [existing_whitespace[0].edit(desired_indent)],
                )
                delete_fixes = [
                    LintFix.delete(seg) for seg in existing_whitespace[1:]
                ]
                return [edit_fix] + delete_fixes
            else:
                # Just create an indent.
                return [
                    LintFix.create_before(
                        current_anchor,
                        [
                            WhitespaceSegment(raw=desired_indent, ),
                        ],
                    )
                ]
예제 #21
0
 def _eval(self, segment, raw_stack, **kwargs):
     """Incorrect indentation found in file."""
     tab = "\t"
     space = " "
     correct_indent = (
         space * self.tab_space_size if self.indent_unit == "space" else tab
     )
     wrong_indent = (
         tab if self.indent_unit == "space" else space * self.tab_space_size
     )
     if segment.is_type("whitespace") and wrong_indent in segment.raw:
         fixes = []
         description = "Incorrect indentation type found in file."
         edit_indent = segment.raw.replace(wrong_indent, correct_indent)
         # Ensure that the number of space indents is a multiple of tab_space_size
         # before attempting to convert spaces to tabs to avoid mixed indents
         # unless we are converted tabs to spaces (indent_unit = space)
         if (
             (
                 self.indent_unit == "space"
                 or segment.raw.count(space) % self.tab_space_size == 0
             )
             # Only attempt a fix at the start of a newline for now
             and (len(raw_stack) == 0 or raw_stack[-1].is_type("newline"))
         ):
             fixes = [
                 LintFix(
                     "edit",
                     segment,
                     WhitespaceSegment(raw=edit_indent),
                 )
             ]
         elif not (len(raw_stack) == 0 or raw_stack[-1].is_type("newline")):
             # give a helpful message if the wrong indent has been found and is not at the start of a newline
             description += (
                 " The indent occurs after other text, so a manual fix is needed."
             )
         else:
             # If we get here, the indent_unit is tabs, and the number of spaces is not a multiple of tab_space_size
             description += " The number of spaces is not a multiple of tab_space_size, so a manual fix is needed."
         return LintResult(anchor=segment, fixes=fixes, description=description)
예제 #22
0
    def _eval(self, segment, parent_stack, **kwargs):
        """Ambiguous ordering directions for columns in order by clause.

        This rule checks if some ORDER BY columns explicitly specify ASC or
        DESC and some don't.
        """
        # We only trigger on orderby_clause
        if segment.is_type("orderby_clause"):
            lint_fixes = []
            orderby_spec = self._get_orderby_info(segment)
            order_types = {o.order for o in orderby_spec}
            # If ALL columns or NO columns explicitly specify ASC/DESC, all is
            # well.
            if None not in order_types or order_types == {None}:
                return None

            # There's a mix of explicit and default sort order. Make everything
            # explicit.
            for col_info in orderby_spec:
                if not col_info.order:
                    # Since ASC is default in SQL, add in ASC for fix
                    lint_fixes.append(
                        LintFix(
                            "create",
                            col_info.separator,
                            [WhitespaceSegment(),
                             KeywordSegment("ASC")],
                        ))

            return [
                LintResult(
                    anchor=segment,
                    fixes=lint_fixes,
                    description=
                    ("Ambiguous order by clause. Order by "
                     "clauses should specify order direction for ALL columns or NO columns."
                     ),
                )
            ]
        return None
예제 #23
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Looking for DISTINCT before a bracket.

        Look for DISTINCT keyword immediately followed by open parenthesis.
        """
        # We trigger on `select_clause` and look for `select_clause_modifier`
        assert context.segment.is_type("select_clause")
        children = FunctionalContext(context).segment.children()
        modifier = children.select(sp.is_type("select_clause_modifier"))
        first_element = children.select(
            sp.is_type("select_clause_element")).first()
        if not modifier or not first_element:
            return None
        # is the first element only an expression with only brackets?
        expression = (first_element.children(sp.is_type("expression")).first()
                      or first_element)
        bracketed = expression.children(sp.is_type("bracketed")).first()
        if bracketed:
            fixes = []
            # If there's nothing else in the expression, remove the brackets.
            if len(expression[0].segments) == 1:
                # Remove the brackets and strip any meta segments.
                fixes.append(
                    LintFix.replace(
                        bracketed[0],
                        self.filter_meta(bracketed[0].segments)[1:-1]), )
            # If no whitespace between DISTINCT and expression, add it.
            if not children.select(sp.is_whitespace(),
                                   start_seg=modifier[0],
                                   stop_seg=first_element[0]):
                fixes.append(
                    LintFix.create_before(
                        first_element[0],
                        [WhitespaceSegment()],
                    ))
            # If no fixes, no problem.
            if fixes:
                return LintResult(anchor=modifier[0], fixes=fixes)
        return None
예제 #24
0
 def _eval(self, segment, **kwargs):
     """Single whitespace expected in mother segment between pre and post segments."""
     error_buffer = []
     if segment.is_type(self.expected_mother_segment_type):
         last_code = None
         mid_segs = []
         for seg in segment.iter_segments(expanding=self.expand_children):
             if seg.is_code:
                 if (last_code and self.matches_target_tuples(
                         last_code, [self.pre_segment_identifier])
                         and self.matches_target_tuples(
                             seg, [self.post_segment_identifier])):
                     # Do we actually have the right amount of whitespace?
                     raw_inner = "".join(s.raw for s in mid_segs)
                     if raw_inner != " " and not (self.allow_newline and
                                                  any(s.name == "newline"
                                                      for s in mid_segs)):
                         if not raw_inner:
                             # There's nothing between. Just add a whitespace
                             fixes = [
                                 LintFix(
                                     "create",
                                     seg,
                                     [WhitespaceSegment()],
                                 )
                             ]
                         else:
                             # Don't otherwise suggest a fix for now.
                             # TODO: Enable more complex fixing here.
                             fixes = None  # pragma: no cover
                         error_buffer.append(
                             LintResult(anchor=last_code, fixes=fixes))
                 mid_segs = []
                 if not seg.is_meta:
                     last_code = seg
             else:
                 mid_segs.append(seg)
     return error_buffer or None
예제 #25
0
 def _eval(self, segment, parent_stack, **kwargs):
     """Unnecessary whitespace."""
     # For the given segment, lint whitespace directly within it.
     prev_newline = True
     prev_whitespace = None
     violations = []
     for seg in segment.segments:
         if seg.is_type("newline"):
             prev_newline = True
             prev_whitespace = None
         elif seg.is_type("whitespace"):
             # This is to avoid indents
             if not prev_newline:
                 prev_whitespace = seg
             prev_newline = False
         elif seg.is_type("comment"):
             prev_newline = False
             prev_whitespace = None
         else:
             if prev_whitespace:
                 if prev_whitespace.raw != " ":
                     violations.append(
                         LintResult(
                             anchor=prev_whitespace,
                             fixes=[
                                 LintFix(
                                     "edit",
                                     prev_whitespace,
                                     WhitespaceSegment(),
                                 )
                             ],
                         ))
             prev_newline = False
             prev_whitespace = None
     if violations:
         return violations
예제 #26
0
    def _eval(self, segment, raw_stack, **kwargs):
        """WITH clause closing bracket should be aligned with WITH keyword.

        Look for a with clause and evaluate the position of closing brackets.
        """
        # We only trigger on start_bracket (open parenthesis)
        if segment.is_type("with_compound_statement"):
            raw_stack_buff = list(raw_stack)
            # Look for the with keyword
            for seg in segment.segments:
                if seg.name.lower() == "with":
                    seg_line_no = seg.pos_marker.line_no
                    break
            else:  # pragma: no cover
                # This *could* happen if the with statement is unparsable,
                # in which case then the user will have to fix that first.
                if any(s.is_type("unparsable") for s in segment.segments):
                    return LintResult()
                # If it's parsable but we still didn't find a with, then
                # we should raise that.
                raise RuntimeError("Didn't find WITH keyword!")

            def indent_size_up_to(segs):
                seg_buff = []
                # Get any segments running up to the WITH
                for elem in reversed(segs):
                    if elem.is_type("newline"):
                        break
                    elif elem.is_meta:
                        continue
                    else:
                        seg_buff.append(elem)
                # reverse the indent if we have one
                if seg_buff:
                    seg_buff = list(reversed(seg_buff))
                indent_str = "".join(seg.raw for seg in seg_buff).replace(
                    "\t", " " * self.tab_space_size)
                indent_size = len(indent_str)
                return indent_size, indent_str

            balance = 0
            with_indent, with_indent_str = indent_size_up_to(raw_stack_buff)
            for seg in segment.iter_segments(
                    expanding=["common_table_expression", "bracketed"],
                    pass_through=True):
                if seg.name == "start_bracket":
                    balance += 1
                elif seg.name == "end_bracket":
                    balance -= 1
                    if balance == 0:
                        closing_bracket_indent, _ = indent_size_up_to(
                            raw_stack_buff)
                        indent_diff = closing_bracket_indent - with_indent
                        # Is indent of closing bracket not the same as
                        # indent of WITH keyword.
                        if seg.pos_marker.line_no == seg_line_no:
                            # Skip if it's the one-line version. That's ok
                            pass
                        elif indent_diff < 0:
                            return LintResult(
                                anchor=seg,
                                fixes=[
                                    LintFix(
                                        "create",
                                        seg,
                                        WhitespaceSegment(" " *
                                                          (-indent_diff)),
                                    )
                                ],
                            )
                        elif indent_diff > 0:
                            # Is it all whitespace before the bracket on this line?
                            prev_segs_on_line = [
                                elem for elem in segment.iter_segments(
                                    expanding=[
                                        "common_table_expression", "bracketed"
                                    ],
                                    pass_through=True,
                                ) if elem.pos_marker.line_no == seg.pos_marker.
                                line_no and elem.pos_marker.line_pos <
                                seg.pos_marker.line_pos
                            ]
                            if all(
                                    elem.is_type("whitespace")
                                    for elem in prev_segs_on_line):
                                # We can move it back, it's all whitespace
                                fixes = [
                                    LintFix(
                                        "create",
                                        seg,
                                        [WhitespaceSegment(with_indent_str)],
                                    )
                                ] + [
                                    LintFix("delete", elem)
                                    for elem in prev_segs_on_line
                                ]
                            else:
                                # We have to move it to a newline
                                fixes = [
                                    LintFix(
                                        "create",
                                        seg,
                                        [
                                            NewlineSegment(),
                                            WhitespaceSegment(with_indent_str),
                                        ],
                                    )
                                ]
                            return LintResult(anchor=seg, fixes=fixes)
                else:
                    raw_stack_buff.append(seg)
        return LintResult()
예제 #27
0
            base_module="test.fixtures.rules.custom.bad_rule_name",
        )

    e.match("Rule classes must be named in the format of")


def test_rule_set_return_informative_error_when_rule_not_registered():
    """Assert that a rule that throws an exception returns it as a validation."""
    cfg = FluffConfig(overrides={"dialect": "ansi"})
    with pytest.raises(ValueError) as e:
        get_rule_from_set("L000", config=cfg)

    e.match("'L000' not in")


seg = WhitespaceSegment(pos_marker=PositionMarker(
    slice(0, 1), slice(0, 1), TemplatedFile(" ", fname="<str>")))


@pytest.mark.parametrize(
    "lint_result, expected",
    [
        (LintResult(), "LintResult(<empty>)"),
        (LintResult(seg),
         "LintResult(<WhitespaceSegment: ([L:  1, P:  1]) ' '>)"),
        (
            LintResult(seg, description="foo"),
            "LintResult(foo: <WhitespaceSegment: ([L:  1, P:  1]) ' '>)",
        ),
        (
            LintResult(
                seg,
예제 #28
0
    def _eval_single_select_target_element(self, select_targets_info,
                                           select_clause, parent_stack):
        is_wildcard = False
        for segment in select_clause.segments:
            if segment.is_type("select_clause_element"):
                for sub_segment in segment.segments:
                    if sub_segment.is_type("wildcard_expression"):
                        is_wildcard = True

        if is_wildcard:
            return None
        elif (select_targets_info.select_idx <
              select_targets_info.first_new_line_idx <
              select_targets_info.first_select_target_idx):
            # Do we have a modifier?
            modifier = select_clause.get_child("select_clause_modifier")

            # Prepare the select clause which will be inserted
            # In most (but not all) case we'll want to replace the newline with
            # the statement and a newline, but in some cases however (see #1424)
            # we don't need the final newline.
            copy_with_newline = True
            insert_buff = [
                WhitespaceSegment(),
                select_clause.segments[
                    select_targets_info.first_select_target_idx],
            ]

            # Check if the modifier is one we care about
            if modifier:
                # If it's already on the first line, ignore it.
                if (select_clause.segments.index(modifier) <
                        select_targets_info.first_new_line_idx):
                    modifier = None
            fixes = [
                # Delete the first select target from its original location.
                # We'll add it to the right section at the end, once we know
                # what to add.
                LintFix(
                    "delete",
                    select_clause.segments[
                        select_targets_info.first_select_target_idx],
                ),
            ]

            start_idx = 0

            # If we have a modifier to move:
            if modifier:

                # Add it to the insert
                insert_buff = [WhitespaceSegment(), modifier] + insert_buff

                modifier_idx = select_clause.segments.index(modifier)
                # Delete the whitespace after it (which is two after, thanks to indent)
                if (len(select_clause.segments) > modifier_idx + 1
                        and select_clause.segments[modifier_idx +
                                                   2].is_whitespace):
                    fixes += [
                        LintFix(
                            "delete",
                            select_clause.segments[modifier_idx + 2],
                        ),
                    ]

                # Delete the modifier itself
                fixes += [
                    LintFix(
                        "delete",
                        modifier,
                    ),
                ]

                # Set the position marker for removing the preceding
                # whitespace and newline, which we'll use below.
                start_idx = modifier_idx
            else:
                # Set the position marker for removing the preceding
                # whitespace and newline, which we'll use below.
                start_idx = select_targets_info.first_select_target_idx

            if parent_stack and parent_stack[-1].is_type("select_statement"):
                select_stmt = parent_stack[-1]
                select_clause_idx = select_stmt.segments.index(select_clause)
                after_select_clause_idx = select_clause_idx + 1
                if len(select_stmt.segments) > after_select_clause_idx:
                    if select_stmt.segments[after_select_clause_idx].is_type(
                            "newline"):
                        # The select_clause is immediately followed by a
                        # newline. Delete the newline in order to avoid leaving
                        # behind an empty line after fix.
                        delete_last_newline = True

                        # Since, we're deleting the newline, we should also delete all
                        # whitespace before it or it will add random whitespace to
                        # following statements. So walk back through the segment
                        # deleting whitespace until you get the previous newline, or
                        # something else.
                        idx = 1
                        while start_idx - idx < len(select_clause.segments):
                            # Delete any whitespace
                            if select_clause.segments[start_idx - idx].is_type(
                                    "whitespace"):
                                fixes += [
                                    LintFix(
                                        "delete",
                                        select_clause.segments[start_idx -
                                                               idx],
                                    ),
                                ]

                            # Once we see a newline, then we're done
                            if select_clause.segments[start_idx - idx].is_type(
                                    "newline", ):
                                break

                            # If we see anything other than whitespace,
                            # then we're done, but in this case we want to
                            # keep the final newline.
                            if not select_clause.segments[start_idx -
                                                          idx].is_type(
                                                              "whitespace",
                                                              "newline"):
                                delete_last_newline = False
                                break

                            idx += 1

                        # Finally delete the newline, unless we've decided not to
                        if delete_last_newline:
                            fixes.append(
                                LintFix(
                                    "delete",
                                    select_stmt.
                                    segments[after_select_clause_idx],
                                ))

                    elif select_stmt.segments[after_select_clause_idx].is_type(
                            "whitespace"):
                        # The select_clause has stuff after (most likely a comment)
                        # Delete the whitespace immeadiately after the select clause
                        # so the other stuff aligns nicely based on where the select
                        # clause started
                        fixes += [
                            LintFix(
                                "delete",
                                select_stmt.segments[after_select_clause_idx],
                            ),
                        ]
                    elif select_stmt.segments[after_select_clause_idx].is_type(
                            "dedent"):
                        # The end of the select statement, so this is the one
                        # case we don't want the newline added to end of
                        # select_clause (see #1424)
                        copy_with_newline = False

                        # Again let's strip back the whitespace, bnut simpler
                        # as don't need to worry about new line so just break
                        # if see non-whitespace
                        idx = 1
                        start_idx = select_clause_idx - 1
                        while start_idx - idx < len(select_clause.segments):
                            # Delete any whitespace
                            if select_clause.segments[start_idx - idx].is_type(
                                    "whitespace"):
                                fixes += [
                                    LintFix(
                                        "delete",
                                        select_clause.segments[start_idx -
                                                               idx],
                                    ),
                                ]

                            # Once we see a newline, then we're done
                            if select_clause.segments[start_idx -
                                                      idx].is_type("newline"):
                                break

                            # If we see anything other than whitespace,
                            # then we're done, but in this case we want to
                            # keep the final newline.
                            if not select_clause.segments[start_idx -
                                                          idx].is_type(
                                                              "whitespace",
                                                              "newline"):
                                copy_with_newline = True
                                break
                            idx += 1

            if copy_with_newline:
                insert_buff = insert_buff + [NewlineSegment()]

            fixes += [
                # Insert the select_clause in place of the first newlin in the
                # Select statement
                LintFix(
                    "edit",
                    select_clause.segments[
                        select_targets_info.first_new_line_idx],
                    insert_buff,
                ),
            ]

            return LintResult(
                anchor=select_clause,
                fixes=fixes,
            )
        else:
            return None
예제 #29
0
    def _eval(self, segment, **kwargs):
        """Find rule violations and provide fixes.

        0. Look for a case expression
        1. Find the first expression and "then"
        2. Determine if the "then" is followed by a boolean
        3. If so, determine if the first then-bool is followed by an else-bool
        4. If so, delete everything but the first expression
        5a. If then-true-else-false
            * return deletions
            * wrap with coalesce
        5b. If then-false-else-true
            * return deletions
            * add a not condition
            * wrap with parenthesis and coalesce
        """
        # Look for a case expression
        if segment.is_type("case_expression") and segment.segments[0].name == "case":
            # Find the first expression and "then"
            idx = 0
            while segment.segments[idx].name != "then":
                if segment.segments[idx].is_type("expression"):
                    expression_idx = idx
                idx += 1
            # Determine if "then" is followed by a boolean
            then_bool_type = None
            while segment.segments[idx].name not in ["when", "else", "end"]:
                if segment.segments[idx].raw_upper in ["TRUE", "FALSE"]:
                    then_bool_type = segment.segments[idx].raw_upper
                idx += 1
            if then_bool_type:
                # Determine if the first then-bool is followed by an else-bool
                while segment.segments[idx].name != "else":
                    # If the first then-bool is followed by a "WHEN" or "END", exit
                    if segment.segments[idx].name in ["when", "end"]:
                        return None
                    idx += 1  # pragma: no cover
                # Determine if "else" is followed by a boolean
                else_bool_type = None
                while segment.segments[idx].name != "end":
                    if segment.segments[idx].raw_upper in ["TRUE", "FALSE"]:
                        else_bool_type = segment.segments[idx].raw_upper
                    idx += 1
            # If then-bool-else-bool, return fixes
            if (
                then_bool_type is not None
                and else_bool_type is not None
                and then_bool_type != else_bool_type
            ):
                # Generate list of segments to delete -- everything but the
                # first expression.
                delete_segments = []
                for s in segment.segments:
                    if s != segment.segments[expression_idx]:
                        delete_segments.append(s)
                # If then-false, add "not" and space
                edits = []
                if then_bool_type == "FALSE":
                    edits.extend(
                        [
                            KeywordSegment("not"),
                            WhitespaceSegment(),
                        ]
                    )
                # Add coalesce and parenthesis
                edits.extend(
                    [
                        KeywordSegment("coalesce"),
                        SymbolSegment("(", name="start_bracket", type="start_bracket"),
                    ]
                )
                edit_coalesce_target = segment.segments[0]
                fixes = []
                fixes.append(
                    LintFix(
                        "edit",
                        edit_coalesce_target,
                        edits,
                    )
                )
                # Add comma, bool, closing parenthesis
                expression = segment.segments[expression_idx + 1]
                closing_parenthesis = [
                    SymbolSegment(",", name="comma", type="comma"),
                    WhitespaceSegment(),
                    KeywordSegment("false"),
                    SymbolSegment(")", name="end_bracket", type="end_bracket"),
                ]
                fixes.append(
                    LintFix(
                        "edit",
                        expression,
                        closing_parenthesis,
                    )
                )
                # Generate a "delete" action for each segment in
                # delete_segments EXCEPT the one being edited to become a call
                # to "coalesce(". Deleting and editing the same segment has
                # unpredictable behavior.
                fixes += [
                    LintFix("delete", s)
                    for s in delete_segments
                    if s is not edit_coalesce_target
                ]
                return LintResult(
                    anchor=segment.segments[expression_idx],
                    fixes=fixes,
                    description="Case when returns booleans.",
                )
예제 #30
0
파일: base.py 프로젝트: yaegassy/sqlfluff
 def make_whitespace(cls, raw):
     """Make a whitespace segment."""
     return WhitespaceSegment(raw=raw, pos_marker=None)