def _eval(self, context: RuleContext) -> Optional[LintResult]: # We only care about commas. assert context.segment.is_type("comma") # 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
def _eval(self, context: RuleContext) -> LintResult: """Nested CASE statement in ELSE clause could be flattened.""" segment = FunctionalContext(context).segment assert segment.select(sp.is_type("case_expression")) case1_children = segment.children() case1_last_when = case1_children.last(sp.is_type("when_clause")).get() case1_else_clause = case1_children.select(sp.is_type("else_clause")) case1_else_expressions = case1_else_clause.children( sp.is_type("expression")) expression_children = case1_else_expressions.children() case2 = expression_children.select(sp.is_type("case_expression")) # The len() checks below are for safety, to ensure the CASE inside # the ELSE is not part of a larger expression. In that case, it's # not safe to simplify in this way -- we'd be deleting other code. if (not case1_last_when or len(case1_else_expressions) > 1 or len(expression_children) > 1 or not case2): return LintResult() # We can assert that this exists because of the previous check. assert case1_last_when # We can also assert that we'll also have an else clause because # otherwise the case2 check above would fail. case1_else_clause_seg = case1_else_clause.get() assert case1_else_clause_seg # Delete stuff between the last "WHEN" clause and the "ELSE" clause. case1_to_delete = case1_children.select(start_seg=case1_last_when, stop_seg=case1_else_clause_seg) # Delete the nested "CASE" expression. fixes = case1_to_delete.apply(lambda seg: LintFix.delete(seg)) # Determine the indentation to use when we move the nested "WHEN" # and "ELSE" clauses, based on the indentation of case1_last_when. # If no whitespace segments found, use default indent. indent = (case1_children.select( stop_seg=case1_last_when).reversed().select( sp.is_type("whitespace"))) indent_str = "".join(seg.raw for seg in indent) if indent else self.indent # Move the nested "when" and "else" clauses after the last outer # "when". nested_clauses = case2.children( sp.is_type("when_clause", "else_clause")) create_after_last_when = nested_clauses.apply( lambda seg: [NewlineSegment(), WhitespaceSegment(indent_str), seg]) segments = [ item for sublist in create_after_last_when for item in sublist ] fixes.append( LintFix.create_after(case1_last_when, segments, source=segments)) # Delete the outer "else" clause. fixes.append(LintFix.delete(case1_else_clause_seg)) return LintResult(case2[0], fixes=fixes)
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. parent_stack, segment = get_last_segment(FunctionalContext(context).segment) self.logger.debug("Found last segment as: %s", segment) trailing_newlines = Segments(*get_trailing_newlines(context.segment)) trailing_literal_newlines = trailing_newlines self.logger.debug( "Untemplated trailing newlines: %s", trailing_literal_newlines ) if context.templated_file: trailing_literal_newlines = trailing_newlines.select( loop_while=lambda seg: sp.templated_slices( seg, context.templated_file ).all(tsp.is_slice_type("literal")) ) self.logger.debug("Templated trailing newlines: %s", trailing_literal_newlines) 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 = segment[0] else: fix_anchor_segment = parent_stack[1] self.logger.debug("Anchor on: %s", fix_anchor_segment) return LintResult( anchor=segment[0], fixes=[ LintFix.create_after( fix_anchor_segment, [NewlineSegment()], ) ], ) elif len(trailing_literal_newlines) > 1: # Delete extra newlines. return LintResult( anchor=segment[0], fixes=[LintFix.delete(d) for d in trailing_literal_newlines[1:]], ) else: # Single newline, no need for fix. return None
def _fixes_for_move_after_select_clause( stop_seg: BaseSegment, delete_segments: Optional[Segments] = None, add_newline: bool = True, ) -> List[LintFix]: """Cleans up by moving leftover select_clause segments. Context: Some of the other fixes we make in _eval_single_select_target_element() leave leftover child segments that need to be moved to become *siblings* of the select_clause. """ start_seg = ( modifier[0] if modifier else select_children[select_targets_info.first_new_line_idx] ) move_after_select_clause = select_children.select( start_seg=start_seg, stop_seg=stop_seg, ) # :TRICKY: Below, we have a couple places where we # filter to guard against deleting the same segment # multiple times -- this is illegal. # :TRICKY: Use IdentitySet rather than set() since # different segments may compare as equal. all_deletes = IdentitySet( fix.anchor for fix in fixes if fix.edit_type == "delete" ) fixes_ = [] for seg in delete_segments or []: if seg not in all_deletes: fixes.append(LintFix.delete(seg)) all_deletes.add(seg) fixes_ += [ LintFix.delete(seg) for seg in move_after_select_clause if seg not in all_deletes ] fixes_.append( LintFix.create_after( select_clause[0], ([NewlineSegment()] if add_newline else []) + list(move_after_select_clause), ) ) return fixes_
def _eval(self, context: RuleContext) -> List[LintResult]: """Top-level statements should not be wrapped in brackets.""" # Because of the root_only_crawler, this can control its own # crawling behaviour. results = [] for parent, bracketed_segment in self._iter_bracketed_statements( context.segment): self.logger.debug("Evaluating %s in %s", bracketed_segment, parent) # Replace the bracketed segment with it's # children, excluding the bracket symbols. bracket_set = {"start_bracket", "end_bracket"} filtered_children = Segments(*[ segment for segment in bracketed_segment.segments if segment.get_type() not in bracket_set and not segment.is_meta ]) # Lift leading/trailing whitespace and inline comments to the # segment above. This avoids introducing a parse error (ANSI and other # dialects generally don't allow this at lower levels of the parse # tree). to_lift_predicate = sp.or_(sp.is_whitespace(), sp.is_name("inline_comment")) leading = filtered_children.select(loop_while=to_lift_predicate) self.logger.debug("Leading: %s", leading) trailing = (filtered_children.reversed().select( loop_while=to_lift_predicate).reversed()) self.logger.debug("Trailing: %s", trailing) lift_nodes = IdentitySet(leading + trailing) fixes = [] if lift_nodes: fixes.append(LintFix.create_before(parent, list(leading))) fixes.append(LintFix.create_after(parent, list(trailing))) fixes.extend( [LintFix.delete(segment) for segment in lift_nodes]) filtered_children = filtered_children[len(leading ):-len(trailing)] fixes.append( LintFix.replace( bracketed_segment, filtered_children, )) results.append(LintResult(anchor=bracketed_segment, fixes=fixes)) return results
def _eval(self, context: RuleContext) -> Optional[LintResult]: """Select clause modifiers must appear on same line as SELECT.""" # We only care about select_clause. assert context.segment.is_type("select_clause") # Get children of select_clause and the corresponding select keyword. child_segments = FunctionalContext(context).segment.children() select_keyword = child_segments[0] # See if we have a select_clause_modifier. select_clause_modifier_seg = child_segments.first( sp.is_type("select_clause_modifier")) # Rule doesn't apply if there's no select clause modifier. if not select_clause_modifier_seg: return None select_clause_modifier = select_clause_modifier_seg[0] # Are there any newlines between the select keyword # and the select clause modifier. leading_newline_segments = child_segments.select( select_if=sp.is_type("newline"), loop_while=sp.or_(sp.is_whitespace(), sp.is_meta()), start_seg=select_keyword, ) # Rule doesn't apply if select clause modifier # is already on the same line as the select keyword. if not leading_newline_segments: return None # We should check if there is whitespace before the select clause modifier # and remove this during the lint fix. leading_whitespace_segments = child_segments.select( select_if=sp.is_type("whitespace"), loop_while=sp.or_(sp.is_whitespace(), sp.is_meta()), start_seg=select_keyword, ) # We should also check if the following select clause element # is on the same line as the select clause modifier. trailing_newline_segments = child_segments.select( select_if=sp.is_type("newline"), loop_while=sp.or_(sp.is_whitespace(), sp.is_meta()), start_seg=select_clause_modifier, ) # We will insert these segments directly after the select keyword. edit_segments = [ WhitespaceSegment(), select_clause_modifier, ] if not trailing_newline_segments: # if the first select clause element is on the same line # as the select clause modifier then also insert a newline. edit_segments.append(NewlineSegment()) fixes = [] # Move select clause modifier after select keyword. fixes.append( LintFix.create_after( anchor_segment=select_keyword, edit_segments=edit_segments, )) # Delete original newlines and whitespace between select keyword # and select clause modifier. # If there is not a newline after the select clause modifier then delete # newlines between the select keyword and select clause modifier. if not trailing_newline_segments: fixes.extend(LintFix.delete(s) for s in leading_newline_segments) # If there is a newline after the select clause modifier then delete both the # newlines and whitespace between the select keyword and select clause modifier. else: fixes.extend( LintFix.delete(s) for s in leading_newline_segments + leading_whitespace_segments) # Delete the original select clause modifier. fixes.append(LintFix.delete(select_clause_modifier)) # If there is whitespace (on the same line) after the select clause modifier # then also delete this. trailing_whitespace_segments = child_segments.select( select_if=sp.is_whitespace(), loop_while=sp.or_(sp.is_type("whitespace"), sp.is_meta()), start_seg=select_clause_modifier, ) if trailing_whitespace_segments: fixes.extend( (LintFix.delete(s) for s in trailing_whitespace_segments)) return LintResult( anchor=context.segment, fixes=fixes, )
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