Beispiel #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
Beispiel #2
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()
Beispiel #3
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
Beispiel #4
0
    def _eval(self, context: RuleContext) -> LintResult:
        """Function name not immediately followed by bracket.

        Look for Function Segment with anything other than the
        function name before brackets
        """
        segment = context.functional.segment
        # We only trigger on start_bracket (open parenthesis)
        if segment.all(sp.is_type("function")):
            children = segment.children()

            function_name = children.first(sp.is_type("function_name"))[0]
            start_bracket = children.first(sp.is_type("bracketed"))[0]

            intermediate_segments = children.select(start_seg=function_name,
                                                    stop_seg=start_bracket)
            if intermediate_segments:
                # It's only safe to fix if there is only whitespace
                # or newlines in the intervening section.
                if intermediate_segments.all(
                        sp.is_type("whitespace", "newline")):
                    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()
Beispiel #5
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()
Beispiel #6
0
    def _eval(self, segment, memory, **kwargs):
        """Look for non-literal segments."""
        if not segment.pos_marker.is_literal():
            # Does it actually look like a tag?
            src_raw = segment.pos_marker.source_str()
            if not src_raw or src_raw[0] != "{" or src_raw[-1] != "}":
                return LintResult(memory=memory)

            # Dedupe using a memory of source indexes.
            # This is important because several positions in the
            # templated file may refer to the same position in the
            # source file and we only want to get one violation.
            src_idx = segment.pos_marker.source_slice.start
            if memory and src_idx in memory:
                return LintResult(memory=memory)
            if not memory:
                memory = set()
            memory.add(src_idx)

            # Get the inner section
            ws_pre, inner, ws_post = self._get_whitespace_ends(src_raw)

            # For the following section, whitespace should be a single
            # whitespace OR it should contain a newline.

            # Check the initial whitespace.
            if not ws_pre or (ws_pre != " " and "\n" not in ws_pre):
                return LintResult(memory=memory, anchor=segment)
            # Check latter whitespace.
            if not ws_post or (ws_post != " " and "\n" not in ws_post):
                return LintResult(memory=memory, anchor=segment)

        return LintResult(memory=memory)
Beispiel #7
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
Beispiel #8
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Trailing commas within select clause."""
        # Config type hints
        self.select_clause_trailing_comma: str

        segment = context.functional.segment
        children = segment.children()
        if segment.all(sp.is_type("select_clause")):
            # Iterate content to find last element
            last_content: BaseSegment = children.last(sp.is_code())[0]

            # 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 = SymbolSegment(",", name="comma", type="comma")
                    return LintResult(
                        anchor=last_content,
                        fixes=[
                            LintFix.replace(last_content,
                                            [last_content, new_comma])
                        ],
                        description=
                        "Trailing comma in select statement required",
                    )
        return None
Beispiel #9
0
    def _eval(self, segment, memory, parent_stack, **kwargs):
        """Operators should follow a standard for being before/after newlines.

        We use the memory to keep track of whitespace up to now, and
        whether the last code segment was an operator or not.
        Anchor is our signal as to whether there's a problem.

        We only trigger if we have an operator FOLLOWED BY a newline
        before the next meaningful code segment.

        """
        anchor = None
        description = after_description
        if self.operator_new_lines == "before":
            description = before_description

        # The parent stack tells us whether we're in an expression or not.
        if parent_stack and parent_stack[-1].is_type("expression"):
            if segment.is_code:
                # This is code, what kind?
                if segment.is_type("binary_operator", "comparison_operator"):
                    # If it's an operator, then check if in "before" mode
                    if self.operator_new_lines == "before":
                        # If we're in "before" mode, then check if newline since last code
                        for s in memory["since_code"]:
                            if s.name == "newline":
                                # Had a newline - so mark this operator as a fail
                                anchor = segment
                                # TODO: Work out a nice fix for this failure.
                elif memory["last_code"] and memory["last_code"].is_type(
                        "binary_operator", "comparison_operator"):
                    # It's not an operator, but the last code was.
                    if self.operator_new_lines != "before":
                        # If in "after" mode, then check to see
                        # there is a newline between us and the last operator.
                        for s in memory["since_code"]:
                            if s.name == "newline":
                                # Had a newline - so mark last operator as a fail
                                anchor = memory["last_code"]
                                # TODO: Work out a nice fix for this failure.
                # Prepare memory for later
                memory["last_code"] = segment
                memory["since_code"] = []
            else:
                # This isn't a code segment...
                # Prepare memory for later
                memory["since_code"].append(segment)
        else:
            # Reset the memory if we're not in an expression
            memory = {"last_code": None, "since_code": []}

        # Anchor is our signal as to whether there's a problem
        if anchor:
            return LintResult(anchor=anchor,
                              memory=memory,
                              description=description)
        else:
            return LintResult(memory=memory)
Beispiel #10
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
Beispiel #11
0
    def _eval(self, segment, raw_stack, **kwargs):
        """Look for UNION keyword not immediately followed by ALL keyword. 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()
            ):
                return LintResult(anchor=segment)
        return LintResult()
Beispiel #12
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()
Beispiel #13
0
 def _eval_multiple_select_target_elements(eval_result, segment):
     if eval_result.first_new_line_idx == -1:
         # there are multiple select targets but no new lines
         return LintResult(anchor=segment)
     else:
         # ensure newline before select target and whitespace segment
         if (eval_result.first_new_line_idx <
                 eval_result.first_whitespace_idx <
                 eval_result.first_select_target_idx):
             return None
         else:
             return LintResult(anchor=segment)
Beispiel #14
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Files must end with a single 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.

        """
        # We only care about the final segment of the parse tree.
        if not self.is_final_segment(context):
            return None

        # Include current segment for complete stack and reverse.
        parent_stack: Segments = context.functional.parent_stack
        complete_stack: Segments = (context.functional.raw_stack +
                                    context.functional.segment)
        reversed_complete_stack = complete_stack.reversed()

        # Find the trailing newline segments.
        trailing_newlines = reversed_complete_stack.select(
            select_if=sp.is_type("newline"),
            loop_while=sp.or_(sp.is_whitespace(), sp.is_type("dedent")),
        )
        trailing_literal_newlines = trailing_newlines.select(
            loop_while=lambda seg: sp.templated_slices(
                seg, context.templated_file).all(tsp.is_slice_type("literal")))

        if not trailing_literal_newlines:
            # We make an edit to create this segment after the child of the FileSegment.
            if len(parent_stack) == 1:
                fix_anchor_segment = context.segment
            else:
                fix_anchor_segment = parent_stack[1]

            return LintResult(
                anchor=context.segment,
                fixes=[
                    LintFix.create_after(
                        fix_anchor_segment,
                        [NewlineSegment()],
                    )
                ],
            )
        elif len(trailing_literal_newlines) > 1:
            # Delete extra newlines.
            return LintResult(
                anchor=context.segment,
                fixes=[
                    LintFix.delete(d) for d in trailing_literal_newlines[1:]
                ],
            )
        else:
            # Single newline, no need for fix.
            return None
Beispiel #15
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", "*")
                                ),
                            ),
                        ],
                    )
Beispiel #16
0
    def _lint_references_and_aliases(
        self,
        table_aliases,
        value_table_function_aliases,
        references,
        col_aliases,
        using_cols,
        parent_select,
    ):
        """Iterate through references and check consistency."""
        # How many aliases are there? If more than one then abort.
        if len(table_aliases) > 1:
            return None
        standalone_aliases = [t[0] for t in value_table_function_aliases]
        # A buffer to keep any violations.
        violation_buff = []
        # Check all the references that we have.
        seen_ref_types = set()
        for ref in references:
            # We skip any unqualified wildcard references (i.e. *). They shouldn't count.
            if not ref.is_qualified() and ref.is_type("wildcard_identifier"):
                continue
            # Oddball case: Column aliases provided via function calls in by
            # FROM or JOIN. References to these don't need to be qualified.
            # Note there could be a table with a column by the same name as
            # this alias, so avoid bogus warnings by just skipping them
            # entirely rather than trying to enforce anything.
            if ref.raw in standalone_aliases:
                continue
            this_ref_type = ref.qualification()
            if self.single_table_references == "consistent":
                if seen_ref_types and this_ref_type not in seen_ref_types:
                    violation_buff.append(
                        LintResult(
                            anchor=ref,
                            description=
                            f"{this_ref_type.capitalize()} reference "
                            f"{ref.raw!r} found in single table select which is "
                            "inconsistent with previous references.",
                        ))
            elif self.single_table_references != this_ref_type:
                violation_buff.append(
                    LintResult(
                        anchor=ref,
                        description=
                        "{} reference {!r} found in single table select.".
                        format(this_ref_type.capitalize(), ref.raw),
                    ))
            seen_ref_types.add(this_ref_type)

        return violation_buff or None
Beispiel #17
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Fully qualify JOINs."""
        # Config type hints
        self.fully_qualify_join_types: str

        # We are only interested in JOIN clauses.
        if not context.segment.is_type("join_clause"):
            return None

        join_clause_keywords = [
            segment for segment in context.segment.segments if segment.type == "keyword"
        ]

        # Identify LEFT/RIGHT/OUTER JOIN and if the next keyword is JOIN.
        if (
            self.fully_qualify_join_types in ["outer", "both"]
            and join_clause_keywords[0].raw_upper in ["RIGHT", "LEFT", "FULL"]
            and join_clause_keywords[1].raw_upper == "JOIN"
        ):
            # Define basic-level OUTER capitalization based on JOIN
            outer_kw = ("outer", "OUTER")[join_clause_keywords[1].raw == "JOIN"]
            # Insert OUTER after LEFT/RIGHT/FULL
            return LintResult(
                context.segment.segments[0],
                fixes=[
                    LintFix.create_after(
                        context.segment.segments[0],
                        [WhitespaceSegment(), KeywordSegment(outer_kw)],
                    )
                ],
            )

        # Identify lone JOIN by looking at first child segment.
        if (
            self.fully_qualify_join_types in ["inner", "both"]
            and join_clause_keywords[0].raw_upper == "JOIN"
        ):
            # Define basic-level INNER capitalization based on JOIN
            inner_kw = ("inner", "INNER")[join_clause_keywords[0].raw == "JOIN"]
            # Replace lone JOIN with INNER JOIN.
            return LintResult(
                context.segment.segments[0],
                fixes=[
                    LintFix.create_before(
                        context.segment.segments[0],
                        [KeywordSegment(inner_kw), WhitespaceSegment()],
                    )
                ],
            )

        return None
Beispiel #18
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)
            # 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 (len(context.raw_stack) == 0
                         or context.raw_stack[-1].is_type("newline"))):
                fixes = [
                    LintFix.replace(
                        context.segment,
                        [
                            WhitespaceSegment(raw=edit_indent),
                        ],
                    )
                ]
            elif not (len(context.raw_stack) == 0
                      or context.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=context.segment,
                              fixes=fixes,
                              description=description)
        return LintResult()
Beispiel #19
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        r"""``SP_`` prefix should not be used for user-defined stored procedures."""
        # Rule only applies to T-SQL syntax.
        if context.dialect.name != "tsql":
            return None

        # We are only interested in CREATE PROCEDURE statements.
        if context.segment.type != "create_procedure_statement":
            return None

        # Find the object reference for the stored procedure.
        object_reference_segment = next(
            (s for s in context.segment.segments if s.type == "object_reference")
        )

        # We only want to check the stored procedure name.
        procedure_segment = object_reference_segment.segments[-1]

        # If stored procedure name starts with 'SP\_' then raise lint error.
        if procedure_segment.raw_upper.lstrip('["').startswith("SP_"):
            "s".lstrip
            return LintResult(
                procedure_segment,
                description="'SP_' prefix should not be used for user-defined stored "
                "procedures.",
            )

        return None
Beispiel #20
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)
Beispiel #21
0
    def _lint_references_and_aliases(
        self,
        table_aliases: List[AliasInfo],
        standalone_aliases: List[str],
        references: List[BaseSegment],
        col_aliases: List[ColumnAliasInfo],
        using_cols: List[str],
        parent_select: Optional[BaseSegment],
    ) -> Optional[List[LintResult]]:
        """Check whether any aliases are duplicates.

        NB: Subclasses of this error should override this function.

        """
        # Are any of the aliases the same?
        duplicate = set()
        for a1, a2 in itertools.combinations(table_aliases, 2):
            # Compare the strings
            if a1.ref_str == a2.ref_str and a1.ref_str:
                duplicate.add(a2)
        if duplicate:
            return [
                LintResult(
                    # Reference the element, not the string.
                    anchor=aliases.segment,
                    description=("Duplicate table alias {!r}. Table "
                                 "aliases should be unique.").format(
                                     aliases.ref_str),
                ) for aliases in duplicate
            ]
        else:
            return None
Beispiel #22
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])])
Beispiel #23
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Use ``COALESCE`` instead of ``IFNULL`` or ``NVL``."""
        # We only care about function names.
        if context.segment.name != "function_name_identifier":
            return None

        # Only care if the function is ``IFNULL`` or ``NVL``.
        if context.segment.raw_upper not in {"IFNULL", "NVL"}:
            return None

        # Create fix to replace ``IFNULL`` or ``NVL`` with ``COALESCE``.
        fix = LintFix.replace(
            context.segment,
            [
                CodeSegment(
                    raw="COALESCE",
                    name="function_name_identifier",
                    type="function_name_identifier",
                )
            ],
        )

        return LintResult(
            anchor=context.segment,
            fixes=[fix],
            description=
            f"Use 'COALESCE' instead of '{context.segment.raw_upper}'.",
        )
Beispiel #24
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        # Config type hints
        self.blocked_words: Optional[str]

        # Exit early if no block list set
        if not self.blocked_words:
            return None

        # Get the ignore list configuration and cache it
        try:
            blocked_words_list = self.blocked_words_list
        except AttributeError:
            # First-time only, read the settings from configuration.
            # So we can cache them for next time for speed.
            blocked_words_list = self._init_blocked_words()

        # Only look at child elements
        # Note: we do not need to ignore comments or meta types
        # or the like as they will not have single word raws
        if context.segment.segments:
            return None

        if context.segment.raw_upper in blocked_words_list:
            return LintResult(
                anchor=context.segment,
                description=f"Use of blocked word '{context.segment.raw}'.",
            )

        return None
Beispiel #25
0
    def _lint_references_and_aliases(
        self,
        table_aliases,
        value_table_function_aliases,
        references,
        col_aliases,
        using_cols,
        parent_select,
    ):
        # Do we have more than one? If so, all references should be qualified.
        if len(table_aliases) <= 1:
            return None
        # A buffer to keep any violations.
        violation_buff = []
        # Check all the references that we have.
        for r in references:
            this_ref_type = r.qualification()
            if (this_ref_type == "unqualified" and r.raw not in col_aliases
                    and r.raw not in using_cols):
                violation_buff.append(
                    LintResult(
                        anchor=r,
                        description=f"Unqualified reference {r.raw!r} found in "
                        "select with more than one referenced table/view.",
                    ))

        return violation_buff or None
Beispiel #26
0
def _mock_crawl(rule,
                segment,
                ignore_mask,
                templated_file=None,
                *args,
                **kwargs):
    # For test__cli__fix_loop_limit_behavior, we mock BaseRule.crawl(),
    # replacing it with this function. This function generates an infinite
    # sequence of fixes without ever repeating the same fix. This causes the
    # linter to hit the loop limit, allowing us to test that behavior.
    if segment.is_type("comment") and "Comment" in segment.raw:
        global _fix_counter
        _fix_counter += 1
        fix = LintFix.replace(segment,
                              [CommentSegment(f"-- Comment {_fix_counter}")])
        result = LintResult(segment, fixes=[fix])
        errors = []
        fixes = []
        rule._process_lint_result(result, templated_file, ignore_mask, errors,
                                  fixes)
        return (
            errors,
            None,
            fixes,
            None,
        )
    else:
        return _old_crawl(rule,
                          segment,
                          ignore_mask,
                          templated_file=templated_file,
                          *args,
                          **kwargs)
Beispiel #27
0
    def _lint_references_and_aliases(
        self,
        table_aliases,
        value_table_function_aliases,
        references,
        col_aliases,
        using_cols,
        parent_select,
    ):
        """Check whether any aliases are duplicates.

        NB: Subclasses of this error should override this function.

        """
        # Are any of the aliases the same?
        for a1, a2 in itertools.combinations(table_aliases, 2):
            # Compare the strings
            if a1.ref_str == a2.ref_str and a1.ref_str:
                # If there are any, then the rest of the code
                # won't make sense so just return here.
                return [
                    LintResult(
                        # Reference the element, not the string.
                        anchor=a2.segment,
                        description=("Duplicate table alias {!r}. Table "
                                     "aliases should be unique.").format(
                                         a2.ref_str),
                    )
                ]
        return None
Beispiel #28
0
    def _eval(self, segment, **kwargs):
        """Join/From clauses should not contain subqueries. Use CTEs instead.

        NB: No fix for this routine because it would be very complex to
        implement reliably.
        """
        parent_types = self._config_mapping[self.forbid_subquery_in]
        for parent_type in parent_types:
            if segment.is_type(parent_type):
                # Get the referenced table segment
                from_expression_element = segment.get_child("from_expression_element")
                if not from_expression_element:
                    return None  # There isn't one. We're done.
                # Get the main bit
                from_expression_element = from_expression_element.get_child(
                    "table_expression"
                )
                if not from_expression_element:
                    return None  # There isn't one. We're done.

                # If any of the following are found, raise an issue.
                # If not, we're fine.
                problem_children = [
                    "with_compound_statement",
                    "set_expression",
                    "select_statement",
                ]
                for seg_type in problem_children:
                    seg = from_expression_element.get_child(seg_type)
                    if seg:
                        return LintResult(
                            anchor=seg,
                            description=f"{parent_type} clauses should not contain subqueries. Use CTEs instead",
                        )
Beispiel #29
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
Beispiel #30
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())],
         )