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
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
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
def _fixes_for_move_after_select_clause( stop_seg: BaseSegment, 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 the select_clause in an illegal state -- a select_clause's *rightmost children cannot be whitespace or comments*. This function addresses that by moving these segments up the parse tree to an ancestor segment chosen by _choose_anchor_segment(). After these fixes are applied, these segments may, for example, be *siblings* of 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, ) fixes = [ LintFix.delete(seg) for seg in move_after_select_clause ] fixes.append( LintFix.create_after( self._choose_anchor_segment( context, "create_after", select_clause[0], filter_meta=True, ), ([NewlineSegment()] if add_newline else []) + list(move_after_select_clause), )) return fixes
def _eval(self, context: RuleContext) -> Optional[LintResult]: """Select clause modifiers must appear on same line as SELECT.""" # We only care about select_clause. if not context.segment.is_type("select_clause"): return None # Get children of select_clause and the corresponding select keyword. child_segments = context.functional.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 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 between select keyword and select clause modifier # and delete the original select clause modifier. fixes.extend((LintFix.delete(s) for s in leading_newline_segments)) 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, )