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
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
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
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)
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
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
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
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", "*") ), ), ], )
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
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()
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
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())], )
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()
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))
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()
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])])
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, )
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)
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
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
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
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)
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), )
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
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()
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)
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
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
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()
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()])], )