def visit_ClassDef(self, node): position = self.get_metadata(PositionProvider, node) length = position.length - 1 # Minus one for the class' header if True: docstring = node.get_docstring() if docstring is not None: length -= len( docstring.splitlines()) + 2 # Docstring delimiters methods = findall(node, FunctionDef()) for method in methods: docstring = method.get_docstring() if docstring is not None: length -= len( docstring.splitlines()) + 2 # Docstring delimiters if True: comments = findall(node, Comment()) for comment in comments: parent_node = self.get_metadata(ParentNodeProvider, comment) if isinstance(parent_node, EmptyLine): # The comment is not inline length -= 1 # One comment = one line if length > 200: self.add_error('class-too-long', node=node, args=(length, 200))
def visit_FunctionDef(self, node): scope = self.get_metadata(ScopeProvider, node) for index, param_node in enumerate(node.params.all_params): param_name = param_node.name.value # Check the first parameter of non static methods if index == 0 and isinstance(scope, ClassScope) and not findall( node, self.MATCHER_STATICMETHOD): if findall(node, self.MATCHER_CLASSMETHOD): if param_name != 'cls': self.add_error('bad-parameter-name', node=param_node, args=(param_name, "should be 'cls'")) elif param_name != 'self': self.add_error('bad-parameter-name', node=param_node, args=(param_name, "should be 'self'")) elif getattr(builtins, param_name, None): self.add_error('builtin-parameter-name', node=param_node, args=(param_name, )) elif not self.CRE_FORMATS['snake_case'].match(param_name): self.add_error('invalid-parameter-name', node=param_node, args=(param_name, 'snake_case')) elif param_name in self.BAD_PARAMETER_NAMES: self.add_error('bad-parameter-name', node=param_node, args=(param_name, 'is blacklisted'))
def visit_FunctionDef(self, node): position = self.get_metadata(PositionProvider, node) length = position.length - 1 # Minus one for the function's signature if True: docstring = node.get_docstring() if docstring is not None: length -= len( docstring.splitlines()) + 2 # Docstring delimiters # We might have nested functions that are documented... nested_functions = findall(node, FunctionDef()) for nested_function in nested_functions: docstring = nested_function.get_docstring() if docstring is not None: length -= len( docstring.splitlines()) + 2 # Docstring delimiters if True: comments = findall(node, Comment()) for comment in comments: parent_node = self.get_metadata(ParentNodeProvider, comment) if isinstance(parent_node, EmptyLine): # The comment is not inline length -= 1 # One comment = one line if length > 25: self.add_error('function-too-long', node=node, args=(length, 25))
def test_findall_with_sentinels(self) -> None: # Verify behavior when provided a sentinel nothing = findall(cst.RemovalSentinel.REMOVE, m.Name("True") | m.Name("False")) self.assertNodeSequenceEqual(nothing, []) nothing = findall(cst.MaybeSentinel.DEFAULT, m.Name("True") | m.Name("False")) self.assertNodeSequenceEqual(nothing, [])
def test_findall_with_metadata_wrapper(self) -> None: # Find all assignments in a tree code = """ a = 1 b = True def foo(bar: int) -> bool: return False """ module = cst.parse_module(dedent(code)) wrapper = meta.MetadataWrapper(module) # Test that when we find over a wrapper, we implicitly use it for # metadata as well as traversal. booleans = findall( wrapper, m.MatchMetadata(meta.ExpressionContextProvider, meta.ExpressionContext.STORE), ) self.assertNodeSequenceEqual( booleans, [ cst.Name("a"), cst.Name("b"), cst.Name("foo"), cst.Name("bar"), ], ) # Test that we can provide an explicit resolver and tree booleans = findall( wrapper.module, m.MatchMetadata(meta.ExpressionContextProvider, meta.ExpressionContext.STORE), metadata_resolver=wrapper, ) self.assertNodeSequenceEqual( booleans, [ cst.Name("a"), cst.Name("b"), cst.Name("foo"), cst.Name("bar"), ], ) # Test that failing to provide metadata leads to no match booleans = findall( wrapper.module, m.MatchMetadata(meta.ExpressionContextProvider, meta.ExpressionContext.STORE), ) self.assertNodeSequenceEqual(booleans, [])
def find_required_modules(all_types): req_mod = set() for _, a_node in all_types: m = match.findall( a_node.annotation, match.Attribute(value=match.DoNotCare(), attr=match.DoNotCare())) if len(m) != 0: for i in m: req_mod.add([ n.value for n in match.findall( i, match.Name(value=match.DoNotCare())) ][0]) return req_mod
def __extract_names_multi_assign(self, elements): # Add self vars. in tuple assignments, e.g. self.x, self.y = 1, 2 # Adds variables in tuple(s) in multiple assignments, e.g. a, (b, c) = 1, (2, 3) names: List[cst.Name] = [] i = 0 while i < len(elements): if match.matches( elements[i], match.Element(value=match.Name(value=match.DoNotCare()))): names.append(elements[i].value) elif match.matches( elements[i], match.Element(value=match.Attribute(attr=match.Name( value=match.DoNotCare())))): names.append(elements[i].value) elif match.matches( elements[i], match.Element(value=match.Tuple( elements=match.DoNotCare()))): elements.extend( match.findall( elements[i].value, match.Element(value=match.OneOf( match.Attribute(attr=match.Name( value=match.DoNotCare())), match.Name(value=match.DoNotCare()))))) i += 1 return names
def discard_empty_else_blocks(self, _, updated_node): # An `else: pass` block can always simply be discarded, and libcst ensures # that an Else node can only ever occur attached to an If, While, For, or Try # node; in each case `None` is the valid way to represent "no else block". if m.findall(updated_node, m.Comment()): return updated_node # If there are any comments, keep the node return cst.RemoveFromParent()
def visit_FunctionDef(self, node): scope = self.get_metadata(ScopeProvider, node) if isinstance(scope, (FunctionScope, ClassScope)): offset = 1 elif isinstance(scope, GlobalScope): offset = 2 parent_node = self.get_metadata(ParentNodeProvider, node) parent_position = self.get_metadata(PositionProvider, parent_node) position = self.get_metadata(PositionProvider, node) # A module is actually counted as one line longer than it is, # so we use offset to take that in account # This gives us '+ 0' for functions and classes # and '+ 1' for modules if position.end.line + (offset - 1) == parent_position.end.line: return # Finds true empty lines (no comments) following the function matcher = EmptyLine(comment=None, metadata=MatchMetadataIfTrue( PositionProvider, lambda p: position.end.line < p.start.line <= position.end.line + offset + 1)) nodes_count = len(findall(parent_node, matcher, metadata_resolver=self)) if nodes_count > offset: self.add_error('extra-trailing-function-newline', node=node, args=(node.name.value, )) elif nodes_count < offset: self.add_error('missing-trailing-function-newline', node=node, args=(node.name.value, ))
def extract_names_from_type_annot(type_annot: str): """ Extracts all the names/identifiers from a type annotation """ return [ n.value for n in match.findall(cst.parse_expression(type_annot), match.Name(value=match.DoNotCare())) ]
def test_simple_findall(self) -> None: # Find all booleans in a tree code = """ a = 1 b = True def foo(bar: int) -> bool: return False """ module = cst.parse_module(dedent(code)) booleans = findall(module, m.Name("True") | m.Name("False")) self.assertNodeSequenceEqual(booleans, [cst.Name("True"), cst.Name("False")])
def visit_SimpleStatementLine(self, node: cst.SimpleStatementLine): smt_names = [ n.value for n in match.findall( node, match.Name( value=match.SaveMatchedNode(match.DoNotCare(), 'name'))) ] if len(self.stack) > 0: self.fn_may_args_var_use.append(smt_names) if len(self.cls_stack) > 0: if self.cls_stack[0].name in smt_names: self.cls_may_vars_use.append(smt_names) self.__find_module_vars_use(smt_names)
def visit_If(self, node: cst.If): if_names = [ n.value for n in match.findall( node.test, match.Name( value=match.SaveMatchedNode(match.DoNotCare(), 'name'))) ] if len(self.cls_stack) > 0: if self.cls_stack[0].name in if_names: self.cls_may_vars_use.append(if_names) if len(self.stack) > 0: self.fn_may_args_var_use.append(if_names) self.__find_module_vars_use(if_names)
def visit_TrailingWhitespace(self, node): if node.comment is not None: return if node.whitespace.value == '': return position = self.get_metadata(PositionProvider, node) parent_node = self.get_metadata(ParentNodeProvider, node) matcher = RightCurlyBrace( metadata=MatchMetadataIfTrue( PositionProvider, lambda p: position.start.line == p.start.line and position.start.column == p.end.column ) ) matches = findall(parent_node, matcher, metadata_resolver=self) if matches: self.add_error('extra-trailing-brace-whitespace', node=matches[0], args=('}',))
def visit_With(self, node: cst.With): with_names = [ n.value for n in match.findall( match.extract( node, match.With(items=match.SaveMatchedNode( match.DoNotCare(), 'with_items')))['with_items'][0], match.Name( value=match.SaveMatchedNode(match.DoNotCare(), 'name'))) ] if len(self.stack) > 0: self.fn_may_args_var_use.append(with_names) if len(self.cls_stack) > 0: if self.cls_stack[0].name in with_names: self.cls_may_vars_use.append(with_names) self.__find_module_vars_use(with_names)
def visit_FunctionDef(self, node): complexity = 1 for child_node in findall(node, OneOf(*self.NODES)): class_name = child_node.__class__.__name__ if class_name == 'Try': complexity += len(child_node.handlers) + bool(child_node.orelse) elif class_name == 'BooleanOperation': complexity += 1 elif class_name in {'If', 'IfExp', 'Assert'}: complexity += 1 elif class_name in {'For', 'While'}: complexity += bool(child_node.orelse) + 1 elif class_name == 'CompFor': complexity += len(child_node.ifs) + 1 if complexity > 10: self.add_error('function-too-complex', node=node, args=(complexity, 10))
def _line_ranges_spanned_by_format_strings( source: str, ) -> Dict[libcst.CSTNode, LineRange]: def _code_range_to_line_range( code_range: libcst._position.CodeRange, ) -> LineRange: return code_range.start.line, code_range.end.line try: wrapper = libcst.metadata.MetadataWrapper(libcst.parse_module(source)) except libcst._exceptions.ParserSyntaxError as exception: # NOTE: This should not happen. If a file is unparseable for libcst, it # would probably have been unparseable for Pyre as well. In that case, # we would not have raised a 404 parse error and not reached here in the # first place. Still, catch the exception and just skip the special # handling of format strings. LOG.warning("Not moving out fixmes from f-strings because" f" libcst failed to parse the file: {exception}") return {} position_map = wrapper.resolve(libcst.metadata.PositionProvider) return { format_string: _code_range_to_line_range(position_map[format_string]) for format_string in libcst_matchers.findall( wrapper.module, libcst_matchers.FormattedString()) }
def remove_trailing_comma(node): # Remove the comma from this node, *unless* it's already a comma node with comments if node.comma is cst.MaybeSentinel.DEFAULT or m.findall(node, m.Comment()): return node return node.with_changes(comma=cst.MaybeSentinel.DEFAULT)
def visit_ImportFrom(self, node: cst.ImportFrom): if node.module is not None: self.imports.extend([ n.value for n in match.findall( node.module, match.Name(value=match.DoNotCare())) ])