Example #1
0
    def _eval(self, segment, parent_stack, **kwargs):
        """Trailing commas within select clause."""
        if segment.is_type("select_clause"):
            # Iterate content to find last element
            last_content = None
            for seg in segment.segments:
                if seg.is_code:
                    last_content = seg

            # What mode are we in?
            if self.select_clause_trailing_comma == "forbid":
                # Is it a comma?
                if last_content.is_type("comma"):
                    return LintResult(
                        anchor=last_content,
                        fixes=[LintFix("delete", last_content)],
                        description=
                        "Trailing comma in select statement forbidden",
                    )
            elif self.select_clause_trailing_comma == "require":
                if not last_content.is_type("comma"):
                    new_comma = self.make_symbol(",", seg_type="comma")
                    return LintResult(
                        anchor=last_content,
                        fixes=[
                            LintFix("edit", last_content,
                                    [last_content, new_comma])
                        ],
                        description=
                        "Trailing comma in select statement required",
                    )
        return None
Example #2
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
Example #3
0
 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,
                 self.make_whitespace(raw=desired_indent,
                                      pos_marker=current_anchor.pos_marker),
             )
         ]
     # 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],
                 self.make_whitespace(
                     raw=desired_indent,
                     pos_marker=current_indent_buffer[0].pos_marker,
                 ),
             )
         ]
     return fixes
Example #4
0
    def _eval_multiple_select_target_elements(self, select_targets_info,
                                              segment):
        """Multiple select targets. Ensure each is on a separate line."""
        # Insert newline before every select target.
        fixes = []
        for i, select_target in enumerate(select_targets_info.select_targets):
            base_segment = (segment if not i else
                            select_targets_info.select_targets[i - 1])
            if (base_segment.pos_marker.working_line_no ==
                    select_target.pos_marker.working_line_no):
                # Find and delete any whitespace before the select target.
                start_seg = select_targets_info.select_idx
                # If any select modifier (e.g. distinct ) is present, start
                # there rather than at the beginning.
                modifier = segment.get_child("select_clause_modifier")
                if modifier:
                    start_seg = segment.segments.index(modifier)

                ws_to_delete = segment.select_children(
                    start_seg=segment.segments[start_seg]
                    if not i else select_targets_info.select_targets[i - 1],
                    select_if=lambda s: s.is_type("whitespace"),
                    loop_while=lambda s: s.is_type("whitespace", "comma") or s.
                    is_meta,
                )
                fixes += [LintFix("delete", ws) for ws in ws_to_delete]
                fixes.append(LintFix("create", select_target,
                                     NewlineSegment()))
        if fixes:
            return LintResult(anchor=segment, fixes=fixes)
Example #5
0
    def _eval(self, segment, **kwargs):
        """Find rule violations and provide fixes.

        0. Look for a case expression
        1. Look for "ELSE"
        2. Mark "ELSE" for deletion (populate "fixes")
        3. Backtrack and mark all newlines/whitespaces for deletion
        4. Look for a raw "NULL" segment
        5.a. The raw "NULL" segment is found, we mark it for deletion and return
        5.b. We reach the end of case when without matching "NULL": the rule passes
        """
        if segment.is_type("case_expression"):
            fixes = []
            for idx, seg in enumerate(segment.segments):
                # When we find ELSE we delete
                # everything up to NULL
                if fixes:
                    fixes.append(LintFix("delete", seg))
                    # Safe to look for NULL, as an expression
                    # would contain NULL but not be == NULL
                    if seg.raw_upper == "NULL":
                        return LintResult(anchor=segment, fixes=fixes)

                if not fixes and seg.name == "ELSE":
                    fixes.append(LintFix("delete", seg))
                    # Walk back to remove indents/whitespaces
                    walk_idx = idx - 1
                    while (
                        segment.segments[walk_idx].name == "whitespace"
                        or segment.segments[walk_idx].name == "newline"
                        or segment.segments[walk_idx].is_meta
                    ):
                        fixes.append(LintFix("delete", segment.segments[walk_idx]))
                        walk_idx = walk_idx - 1
Example #6
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.type == "expression":
                segments_filtered = self.filter_meta(segment.segments)
                if segments_filtered and segments_filtered[0].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].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,
                                [
                                    self.make_whitespace(
                                        raw=insert_str,
                                        pos_marker=first_segment.pos_marker.advance_by(
                                            insert_str
                                        ),
                                    )
                                ],
                            )
                        )
                    return LintResult(anchor=segment, fixes=fixes)
        return None
Example #7
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
Example #8
0
    def _eval(self, segment, **kwargs):
        """Find rule violations and provide fixes."""
        if (
            segment.is_type("function")
            and segment.get_child("function_name").raw_upper == "COUNT"
        ):
            f_content = [
                seg
                for seg in segment.segments
                if not seg.is_meta
                and not seg.is_type(
                    "start_bracket",
                    "end_bracket",
                    "function_name",
                    "whitespace",
                    "newline",
                )
            ]
            if len(f_content) != 1:
                return None

            if self.prefer_count_1 and f_content[0].type == "star":
                return LintResult(
                    anchor=segment,
                    fixes=[
                        LintFix(
                            "edit",
                            f_content[0],
                            f_content[0].edit(f_content[0].raw.replace("*", "1")),
                        ),
                    ],
                )
            if not self.prefer_count_1 and f_content[0].type == "expression":
                expression_content = [
                    seg for seg in f_content[0].segments if not seg.is_meta
                ]
                if (
                    len(expression_content) == 1
                    and expression_content[0].type == "literal"
                    and expression_content[0].raw == "1"
                ):
                    return LintResult(
                        anchor=segment,
                        fixes=[
                            LintFix(
                                "edit",
                                expression_content[0],
                                expression_content[0].edit(
                                    expression_content[0].raw.replace("1", "*")
                                ),
                            ),
                        ],
                    )
Example #9
0
    def _lint_references_and_aliases(
        self,
        table_aliases,
        value_table_function_aliases,
        references,
        col_aliases,
        using_cols,
        parent_select,
    ):
        """Check all aliased references against tables referenced in the query."""
        # A buffer to keep any violations.
        violation_buff = []
        # Check all the references that we have, keep track of which aliases we refer to.
        tbl_refs = set()
        for r in references:
            tbl_refs.update(
                tr.part
                for tr in r.extract_possible_references(
                    level=r.ObjectReferenceLevel.TABLE
                )
            )

        alias: AliasInfo
        for alias in table_aliases:
            if alias.aliased and alias.ref_str not in tbl_refs:
                fixes = [LintFix("delete", alias.alias_expression)]
                found_alias_segment = False
                # Walk back to remove indents/whitespaces
                for segment in reversed(alias.from_expression_element.segments):
                    if not found_alias_segment:
                        if segment is alias.alias_expression:
                            found_alias_segment = True
                    else:
                        if (
                            segment.name == "whitespace"
                            or segment.name == "newline"
                            or segment.is_meta
                        ):
                            fixes.append(LintFix("delete", segment))
                        else:
                            # Stop once we reach an other, "regular" segment.
                            break
                violation_buff.append(
                    LintResult(
                        anchor=alias.segment,
                        description="Alias {!r} is never used in SELECT statement.".format(
                            alias.ref_str
                        ),
                        fixes=fixes,
                    )
                )
        return violation_buff or None
Example #10
0
    def _eval(self, segment, raw_stack, dialect, **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.

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

        if segment.is_type("set_operator"):
            if "union" in segment.raw 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"),
                            ],
                        )
                    ],
                )
            elif "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()
Example #11
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
Example #12
0
 def _eval(self, segment, raw_stack, **kwargs):
     """Stars make newlines."""
     if segment.is_type("star"):
         return LintResult(
             anchor=segment,
             fixes=[LintFix("create", segment, self.make_newline())],
         )
Example #13
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.type == "set_operator":
            if "UNION" in segment.raw.upper() and not (
                "ALL" in segment.raw.upper() or "DISTINCT" in segment.raw.upper()
            ):
                union = self.make_keyword(
                    raw="UNION",
                    pos_marker=segment.pos_marker,
                )
                ins = self.make_whitespace(raw=" ", pos_marker=segment.pos_marker)
                distinct = self.make_keyword(
                    raw="DISTINCT",
                    pos_marker=segment.pos_marker,
                )
                return LintResult(
                    anchor=segment,
                    fixes=[
                        LintFix("edit", segment.segments[0], [union, ins, distinct])
                    ],
                )
        return LintResult()
Example #14
0
    def _get_fix(self, segment, fixed_raw):
        """Given a segment found to have a fix, returns a LintFix for it.

        May be overridden by subclasses, which is useful when the parse tree
        structure varies from this simple base case.
        """
        return LintFix("edit", segment, segment.edit(fixed_raw))
Example #15
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()
Example #16
0
    def _eval(self, segment, siblings_post, parent_stack, **kwargs):
        """Files must end with a trailing newline.

        We only care about the segment and the siblings which come after it
        for this rule, we discard the others into the kwargs argument.

        """
        if len(self.filter_meta(siblings_post)) > 0:
            # This can only fail on the last segment
            return None
        elif len(segment.segments) > 0:
            # This can only fail on the last base segment
            return None
        elif segment.name == "newline":
            # If this is the last segment, and it's a newline then we're good
            return None
        elif segment.is_meta:
            # We can't fail on a meta segment
            return None
        else:
            # so this looks like the end of the file, but we
            # need to check that each parent segment is also the last
            file_len = len(parent_stack[0].raw)
            pos = segment.pos_marker.char_pos
            # Does the length of the file, equal the length of the segment plus its position
            if file_len != pos + len(segment.raw):
                return None

        ins = self.make_newline(
            pos_marker=segment.pos_marker.advance_by(segment.raw))
        # We're going to make an edit because otherwise we would never get a match!
        return LintResult(anchor=segment,
                          fixes=[LintFix("edit", segment, [segment, ins])])
Example #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 = [
                self.make_whitespace(raw=" "),
                select_modifier,
                self.make_newline(),
            ]
            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,
            )
Example #18
0
    def _eval_multiple_select_target_elements(self, select_targets_info, segment):
        if select_targets_info.first_new_line_idx == -1:
            # there are multiple select targets but no new lines

            # Find and delete any whitespace between "SELECT" and its targets.
            ws_to_delete = segment.select_children(
                start_seg=segment.segments[select_targets_info.select_idx],
                select_if=lambda s: s.is_type("whitespace"),
                loop_while=lambda s: s.is_type("whitespace") or s.is_meta,
            )
            fixes = [LintFix("delete", ws) for ws in ws_to_delete]
            # Insert newline before the first select target.
            ins = self.make_newline(
                pos_marker=segment.pos_marker.advance_by(segment.raw)
            )
            fixes.append(LintFix("create", select_targets_info.select_targets[0], ins))
            return LintResult(anchor=segment, fixes=fixes)
Example #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
Example #20
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
Example #21
0
        def _handle_previous_segments(segments_since_code, anchor,
                                      this_segment, fixes):
            """Handle the list of previous segments and return the new anchor and fixes.

            NB: This function mutates `fixes`.
            """
            if len(segments_since_code) == 0:
                # No whitespace, anchor is the segment AFTER where the whitespace
                # should be.
                anchor = this_segment
                fixes.append(
                    LintFix(
                        "create",
                        this_segment,
                        self.make_whitespace(
                            raw=" ", pos_marker=this_segment.pos_marker),
                    ))
            elif len(segments_since_code) > 1 or any(
                    elem.is_type("newline") for elem in segments_since_code):
                # TODO: This is a case we should deal with, but there are probably
                # some cases that SHOULDN'T apply here (like comments and newlines)
                # so let's deal with them later
                anchor = None
            else:
                # We know it's just one thing.
                gap_seg = segments_since_code[-1]
                if gap_seg.raw != " ":
                    # It's not just a single space
                    anchor = gap_seg
                    fixes.append(
                        LintFix(
                            "edit",
                            gap_seg,
                            self.make_whitespace(
                                raw=" ", pos_marker=gap_seg.pos_marker),
                        ))
                else:
                    # We have just the right amount of whitespace!
                    # Unset our signal.
                    anchor = None
            return anchor, fixes
Example #22
0
 def _eval_multiple_select_target_elements(self, select_targets_info,
                                           segment):
     """Multiple select targets. Ensure each is on a separate line."""
     # Insert newline before every select target.
     fixes = []
     for i, select_target in enumerate(select_targets_info.select_targets):
         base_segment = (segment if not i else
                         select_targets_info.select_targets[i - 1])
         if base_segment.pos_marker.line_no == select_target.pos_marker.line_no:
             # Find and delete any whitespace before the select target.
             ws_to_delete = segment.select_children(
                 start_seg=segment.segments[select_targets_info.select_idx]
                 if not i else select_targets_info.select_targets[i - 1],
                 select_if=lambda s: s.is_type("whitespace"),
                 loop_while=lambda s: s.is_type("whitespace", "comma") or s.
                 is_meta,
             )
             fixes += [LintFix("delete", ws) for ws in ws_to_delete]
             ins = self.make_newline(pos_marker=select_target.pos_marker)
             fixes.append(LintFix("create", select_target, ins))
     if fixes:
         return LintResult(anchor=segment, fixes=fixes)
Example #23
0
    def _get_fix(self, segment, fixed_raw):
        """Overrides the base class.

        We need to do this because function_name nodes have a child
        function_name_identifier that holds the actual name.
        """
        child_segment = segment.segments[0]
        return LintFix(
            "edit",
            child_segment,
            child_segment.__class__(raw=fixed_raw,
                                    pos_marker=child_segment.pos_marker),
        )
Example #24
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Commas should not have whitespace directly before them.

        We need at least one segment behind us for this to work.

        """
        if len(raw_stack) >= 1:
            cm1 = raw_stack[-1]
            if (segment.is_type("comma") and cm1.is_type("whitespace")
                    and cm1.pos_marker.line_pos > 1):
                anchor = cm1
                return LintResult(anchor=anchor,
                                  fixes=[LintFix("delete", cm1)])
        # Otherwise fine
        return None
Example #25
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Unnecessary trailing whitespace.

        Look for newline segments, and then evaluate what
        it was preceded by.
        """
        # We only trigger on newlines
        if (segment.is_type("newline") and len(raw_stack) > 0
                and raw_stack[-1].is_type("whitespace")):
            # If we find a newline, which is preceded by whitespace, then bad
            deletions = []
            idx = -1
            while raw_stack[idx].is_type("whitespace"):
                deletions.append(raw_stack[idx])
                idx -= 1
            return LintResult(anchor=deletions[-1],
                              fixes=[LintFix("delete", d) for d in deletions])
        return LintResult()
Example #26
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,
                     self.make_whitespace(
                         raw=edit_indent, pos_marker=segment.pos_marker
                     ),
                 )
             ]
         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)
Example #27
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
                    new_whitespace = self.make_whitespace(
                        raw=" ", pos_marker=col_info.separator.pos_marker
                    )
                    new_keyword = self.make_keyword(
                        raw="ASC", pos_marker=col_info.separator.pos_marker
                    )
                    order_lint_fix = LintFix(
                        "create", col_info.separator, [new_whitespace, new_keyword]
                    )
                    lint_fixes.append(order_lint_fix)

            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
Example #28
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 getattr(last_code, self.pre_segment_identifier[0])
                     == self.pre_segment_identifier[1]
                     and getattr(seg, self.post_segment_identifier[0])
                     == self.post_segment_identifier[1]
                 ):
                     # 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,
                                     [self.make_whitespace(raw=" ")],
                                 )
                             ]
                         else:
                             # Don't otherwise suggest a fix for now.
                             # TODO: Enable more complex fixing here.
                             fixes = None
                         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
Example #29
0
    def _eval(self, segment, **kwargs):
        """Function name not immediately followed by bracket.

        Look for Function Segment with anything other than the
        function name before brackets
        """
        # We only trigger on start_bracket (open parenthesis)
        if segment.is_type("function"):
            # Look for the function name
            for fname_idx, seg in enumerate(segment.segments):
                if seg.is_type("function_name"):
                    break

            # Look for the start bracket
            for bracket_idx, seg in enumerate(segment.segments):
                if seg.is_type("bracketed"):
                    break

            if bracket_idx != fname_idx + 1:
                # It's only safe to fix if there is only whitespace
                # or newlines in the intervening section.
                intermediate_segments = segment.segments[fname_idx +
                                                         1:bracket_idx]
                if all(
                        seg.is_type("whitespace", "newline")
                        for seg in intermediate_segments):
                    return LintResult(
                        anchor=intermediate_segments[0],
                        fixes=[
                            LintFix("delete", seg)
                            for seg in intermediate_segments
                        ],
                    )
                else:
                    # It's not all whitespace, just report the error.
                    return LintResult(anchor=intermediate_segments[0], )

        return LintResult()
Example #30
0
    def _eval(self, segment, siblings_post, parent_stack, **kwargs):
        """Files must end with a trailing newline.

        We only care about the segment and the siblings which come after it
        for this rule, we discard the others into the kwargs argument.

        """
        if len(self.filter_meta(siblings_post)) > 0:
            # This can only fail on the last segment
            return None
        elif len(segment.segments) > 0:
            # This can only fail on the last base segment
            return None
        elif segment.name == "newline":
            # If this is the last segment, and it's a newline then we're good
            return None
        elif segment.is_meta:
            # We can't fail on a meta segment
            return None
        else:
            # So this looks like the end of the file, but we
            # need to check that each parent segment is also the last.
            # We do this with reference to the templated file, because it's
            # the best we can do given the information available.
            file_len = len(segment.pos_marker.templated_file.templated_str)
            pos = segment.pos_marker.templated_slice.stop
            # Does the length of the file equal the end of the templated position?
            if file_len != pos:
                return None

        # We're going to make an edit because we're appending to the end and there's
        # no segment after it to match on. Otherwise we would never get a match!
        return LintResult(
            anchor=segment,
            fixes=[LintFix("edit", segment,
                           [segment, NewlineSegment()])],
        )