Last active
          March 16, 2022 22:39 
        
      - 
      
- 
        Save withakay/a09a8f95e0d275318bb040be756b8947 to your computer and use it in GitHub Desktop. 
    pylint deprecation checker
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | from astroid.nodes import Call, ClassDef, FunctionDef, Name, node_classes | |
| from pylint.checkers import BaseChecker | |
| from pylint.interfaces import IAstroidChecker | |
| def register(linter) -> None: | |
| linter.register_checker(DeprecatedChecker(linter)) | |
| class DeprecatedChecker(BaseChecker): | |
| """ | |
| A pylint checker to detect @deprecated decorators on classes and functions | |
| Basic usage: | |
| `pylint --load-plugins=deprecated_checker --disable=all --enable=no-deprecated test.py` | |
| (where deprecated_checker is a python module that is in the PYTHONPATH) | |
| """ | |
| # BaseChecker overrides | |
| __implements__ = IAstroidChecker | |
| name = "no-deprecated" | |
| priority = -1 | |
| msgs = { | |
| "W0001": ( | |
| "%s %s is deprecated. %s", | |
| name, | |
| "Functions and Classes that have been marked as @deprecated should not be used", | |
| ) | |
| } | |
| message_name = name | |
| # an array of all the decorators to match against | |
| decorator_names = ["deprecated"] | |
| # There are various packages that implement a @deprecated_check decorator | |
| # in similar but slightly different ways. | |
| # change the `reason_keyword` value to the name of the argument you | |
| # want to capture the value of when outputting a linter warning. | |
| # Given: | |
| # | |
| # `@deprecated(reason="some reason")` | |
| # | |
| # `reason_keyword` would be set to "reason" | |
| # | |
| reason_keyword = "details" | |
| def __init__(self, linter=None) -> None: | |
| super().__init__(linter) | |
| def visit_decorators(self, node: node_classes.Decorators) -> None: | |
| # Check if the node has decorators | |
| if not node.nodes: | |
| return | |
| parent_type = "unknown" | |
| # Figure out whether it's a class or function | |
| # that is deprecated_check, and get relevant info | |
| if isinstance(node.parent, ClassDef): | |
| parent_type = "class" | |
| elif isinstance(node.parent, FunctionDef): | |
| parent_type = "function" | |
| parent_name = node.parent.name | |
| # Check each decorator to see if it's @deprecated_check | |
| for decorator in node.get_children(): | |
| if isinstance(decorator, Call): | |
| if decorator.func.name in self.decorator_names: | |
| reason = "No reason specified" | |
| if decorator.keywords is not None: | |
| for keyword in decorator.keywords: | |
| if keyword.arg == self.reason_keyword: | |
| reason = f'"{keyword.value.value}"' | |
| self.add_message( | |
| msgid=self.message_name, | |
| node=node.parent, | |
| args=(parent_type, parent_name, reason), | |
| ) | |
| elif isinstance(decorator, Name): | |
| if decorator.name in self.decorator_names: | |
| self.add_message( | |
| msgid=self.message_name, | |
| node=node.parent, | |
| args=( | |
| parent_type, | |
| parent_name, | |
| "No reason specified", | |
| ), | |
| ) | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | """ | |
| To test `deprecated_checker` cd into the folder this script is in and run pytest | |
| against this file with the following command: | |
| ``` | |
| PYTHONPATH="$PYTHONPATH:$PWD" \ | |
| pylint --load-plugins=deprecated_checker --disable=all --enable=no-deprecated \ | |
| test_deprecated_checker.py | |
| ``` | |
| Note the plugin is named 'deprecated_checker' (and this maps on to the deprecated_checker.py file) and the | |
| check is called 'no-deprecated' | |
| """ | |
| from deprecation import deprecated | |
| @deprecated | |
| def test_with_no_details() -> None: | |
| # should generate a warning from pylint with no extra details | |
| pass | |
| @deprecated(details="this is test2's deprecation message") | |
| def test_with_details() -> None: | |
| # should generate a warning from pylint with details | |
| pass | |
| def test_with_no_decorator() -> None: | |
| # should be ignored | |
| pass | |
| if __name__ == '__main__': | |
| test_with_no_details() | |
| test_with_details() | |
| test_with_no_decorator() | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment