Exemple #1
0
    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
Exemple #2
0
    def _handle_semicolon_newline(
            self, context: RuleContext,
            info: SegmentMoveContext) -> Optional[LintResult]:
        # Adjust before_segment and anchor_segment for preceding inline
        # comments. Inline comments can contain noqa logic so we need to add the
        # newline after the inline comment.
        (
            before_segment,
            anchor_segment,
        ) = self._handle_preceding_inline_comments(info.before_segment,
                                                   info.anchor_segment)

        if (len(before_segment) == 1) and all(
                s.is_type("newline") for s in before_segment):
            return None

        # If preceding segment is not a single newline then delete the old
        # semi-colon/preceding whitespace and then insert the
        # semi-colon in the correct location.

        # This handles an edge case in which an inline comment comes after
        # the semi-colon.
        anchor_segment = self._handle_trailing_inline_comments(
            context, anchor_segment)
        fixes = []
        if anchor_segment is context.segment:
            fixes.append(
                LintFix.replace(
                    anchor_segment,
                    [
                        NewlineSegment(),
                        SymbolSegment(raw=";", type="symbol",
                                      name="semicolon"),
                    ],
                ))
        else:
            fixes.extend([
                LintFix.replace(
                    anchor_segment,
                    [
                        anchor_segment,
                        NewlineSegment(),
                        SymbolSegment(raw=";", type="symbol",
                                      name="semicolon"),
                    ],
                ),
                LintFix.delete(context.segment, ),
            ])
            fixes.extend(LintFix.delete(d) for d in info.whitespace_deletions)
        return LintResult(
            anchor=anchor_segment,
            fixes=fixes,
        )
Exemple #3
0
    def _handle_semicolon_same_line(
        self,
        target_segment: RawSegment,
        parent_segment: BaseSegment,
        info: SegmentMoveContext,
    ) -> Optional[LintResult]:
        if not info.before_segment:
            return None

        # If preceding segments are found then delete the old
        # semi-colon and its preceding whitespace and then insert
        # the semi-colon in the correct location.
        fixes = self._create_semicolon_and_delete_whitespace(
            target_segment,
            parent_segment,
            info.anchor_segment,
            info.whitespace_deletions,
            [
                SymbolSegment(raw=";", type="statement_terminator"),
            ],
        )
        return LintResult(
            anchor=info.anchor_segment,
            fixes=fixes,
        )
Exemple #4
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 = SymbolSegment(",", name="comma", 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
Exemple #5
0
    def _eval(self, context: RuleContext) -> Optional[LintResult]:
        """Trailing commas within select clause."""
        # Config type hints
        self.select_clause_trailing_comma: str

        segment = FunctionalContext(context).segment
        children = segment.children()
        # 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(",", 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
Exemple #6
0
 def make_symbol(cls, raw, seg_type, name=None):
     """Make a symbol segment."""
     # For the name of the segment, we force the string to lowercase.
     symbol_seg = SymbolSegment.make(raw.lower(),
                                     name=name or seg_type,
                                     type=seg_type)
     # At the moment we let the rule dictate *case* here.
     return symbol_seg(raw=raw, pos_marker=None)
Exemple #7
0
    def _handle_semicolon_same_line(
            context: RuleContext,
            info: SegmentMoveContext) -> Optional[LintResult]:
        if not info.before_segment:
            return None

        # If preceding segments are found then delete the old
        # semi-colon and its preceding whitespace and then insert
        # the semi-colon in the correct location.
        fixes = [
            LintFix.replace(
                info.anchor_segment,
                [
                    info.anchor_segment,
                    SymbolSegment(raw=";", type="symbol", name="semicolon"),
                ],
            ),
            LintFix.delete(context.segment, ),
        ]
        fixes.extend(LintFix.delete(d) for d in info.whitespace_deletions)
        return LintResult(
            anchor=info.anchor_segment,
            fixes=fixes,
        )
Exemple #8
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.",
                )
Exemple #9
0
exasol_fs_dialect.add(
    FunctionScriptTerminatorSegment=NamedSegment.make(
        "function_script_terminator", type="statement_terminator"
    ),
    WalrusOperatorSegment=NamedSegment.make(
        "walrus_operator", type="assignment_operator"
    ),
    VariableNameSegment=ReSegment.make(
        r"[A-Z][A-Z0-9_]*",
        name="function_variable",
        type="variable",
    ),
)

exasol_fs_dialect.replace(
    SemicolonSegment=SymbolSegment.make(";", name="semicolon", type="semicolon"),
)


@exasol_fs_dialect.segment(replace=True)
class StatementSegment(BaseSegment):
    """A generic segment, to any of its child subsegments."""

    type = "statement"

    match_grammar = GreedyUntil(Ref("FunctionScriptTerminatorSegment"))
    parse_grammar = OneOf(
        Ref("CreateFunctionStatementSegment"),
        Ref("CreateScriptingLuaScriptStatementSegment"),
        Ref("CreateUDFScriptStatementSegment"),
        Ref("CreateAdapterScriptStatementSegment"),
Exemple #10
0
    def _ensure_final_semicolon(self,
                                context: RuleContext) -> Optional[LintResult]:
        # Locate the end of the file.
        if not self.is_final_segment(context):
            return None

        # Include current segment for complete stack.
        complete_stack: List[BaseSegment] = list(context.raw_stack)
        complete_stack.append(context.segment)

        # Iterate backwards over complete stack to find
        # if the final semi-colon is already present.
        anchor_segment = context.segment
        semi_colon_exist_flag = False
        is_one_line = False
        before_segment = []
        for segment in complete_stack[::-1]:
            if segment.name == "semicolon":
                semi_colon_exist_flag = True
            elif segment.is_code:
                is_one_line = self._is_one_line_statement(context, segment)
                break
            elif not segment.is_meta:
                before_segment.append(segment)
            anchor_segment = segment

        semicolon_newline = self.multiline_newline if not is_one_line else False

        if not semi_colon_exist_flag:
            # Create the final semi-colon if it does not yet exist.

            # Semi-colon on same line.
            if not semicolon_newline:
                fixes = [
                    LintFix.replace(
                        anchor_segment,
                        [
                            anchor_segment,
                            SymbolSegment(
                                raw=";", type="symbol", name="semicolon"),
                        ],
                    )
                ]
            # Semi-colon on new line.
            else:
                # Adjust before_segment and anchor_segment for inline
                # comments.
                (
                    before_segment,
                    anchor_segment,
                ) = self._handle_preceding_inline_comments(
                    before_segment, anchor_segment)
                fixes = [
                    LintFix.replace(
                        anchor_segment,
                        [
                            anchor_segment,
                            NewlineSegment(),
                            SymbolSegment(
                                raw=";", type="symbol", name="semicolon"),
                        ],
                    )
                ]

            return LintResult(
                anchor=anchor_segment,
                fixes=fixes,
            )
        return None
snowflake_dialect.sets("reserved_keywords").update([
    "CLONE",
    "MASKING",
    "NOTIFICATION",
    "PIVOT",
    "SAMPLE",
    "TABLESAMPLE",
    "UNPIVOT",
])

snowflake_dialect.add(
    # In snowflake, these are case sensitive even though they're not quoted
    # so they need a different `name` and `type` so they're not picked up
    # by other rules.
    ParameterAssignerSegment=SymbolSegment.make("=>",
                                                name="parameter_assigner",
                                                type="parameter_assigner"),
    NakedSemiStructuredElementSegment=ReSegment.make(
        r"[A-Z0-9_]*",
        name="naked_semi_structured_element",
        type="semi_structured_element",
    ),
    QuotedSemiStructuredElementSegment=NamedSegment.make(
        "double_quote",
        name="quoted_semi_structured_element",
        type="semi_structured_element",
    ),
    ColumnIndexIdentifierSegment=ReSegment.make(
        r"\$[0-9]+", name="column_index_identifier_segment",
        type="identifier"),
)
Exemple #12
0
    def _ensure_final_semicolon(
            self, parent_segment: BaseSegment) -> Optional[LintResult]:
        # Iterate backwards over complete stack to find
        # if the final semi-colon is already present.
        anchor_segment = parent_segment.segments[-1]
        trigger_segment = parent_segment.segments[-1]
        semi_colon_exist_flag = False
        is_one_line = False
        before_segment = []
        for segment in parent_segment.segments[::-1]:
            anchor_segment = segment
            if segment.is_type("statement_terminator"):
                semi_colon_exist_flag = True
            elif segment.is_code:
                is_one_line = self._is_one_line_statement(
                    parent_segment, segment)
                break
            elif not segment.is_meta:
                before_segment.append(segment)
            trigger_segment = segment
        self.logger.debug("Trigger on: %s", trigger_segment)
        self.logger.debug("Anchoring on: %s", anchor_segment)

        semicolon_newline = self.multiline_newline if not is_one_line else False

        if not semi_colon_exist_flag:
            # Create the final semi-colon if it does not yet exist.

            # Semi-colon on same line.
            if not semicolon_newline:
                fixes = [
                    LintFix.create_after(
                        self._choose_anchor_segment(
                            parent_segment,
                            "create_after",
                            anchor_segment,
                            filter_meta=True,
                        ),
                        [
                            SymbolSegment(raw=";",
                                          type="statement_terminator"),
                        ],
                    )
                ]
            # Semi-colon on new line.
            else:
                # Adjust before_segment and anchor_segment for inline
                # comments.
                (
                    before_segment,
                    anchor_segment,
                ) = self._handle_preceding_inline_comments(
                    before_segment, anchor_segment)
                self.logger.debug("Revised anchor on: %s", anchor_segment)
                fixes = [
                    LintFix.create_after(
                        self._choose_anchor_segment(
                            parent_segment,
                            "create_after",
                            anchor_segment,
                            filter_meta=True,
                        ),
                        [
                            NewlineSegment(),
                            SymbolSegment(raw=";",
                                          type="statement_terminator"),
                        ],
                    )
                ]
            return LintResult(
                anchor=trigger_segment,
                fixes=fixes,
            )
        return None
Exemple #13
0
    ),
] + exasol_fs_dialect.get_lexer_struct())

exasol_fs_dialect.add(
    FunctionScriptTerminatorSegment=NamedSegment.make(
        "function_script_terminator", type="statement_terminator"),
    WalrusOperatorSegment=NamedSegment.make("walrus_operator",
                                            type="assignment_operator"),
    VariableNameSegment=ReSegment.make(
        r"[A-Z][A-Z0-9_]*",
        name="function_variable",
        type="variable",
    ),
)

exasol_fs_dialect.replace(SemicolonSegment=SymbolSegment.make(
    ";", name="semicolon", type="semicolon"), )


@exasol_fs_dialect.segment(replace=True)
class StatementSegment(BaseSegment):
    """A generic segment, to any of its child subsegments."""

    type = "statement"

    match_grammar = GreedyUntil(Ref("FunctionScriptTerminatorSegment"))
    parse_grammar = OneOf(
        Ref("CreateFunctionStatementSegment"),
        Ref("CreateScriptingLuaScriptStatementSegment"),
        Ref("CreateUDFScriptStatementSegment"),
        Ref("CreateAdapterScriptStatementSegment"),
    )