diff options
author | amdev <amdev@AMDEV-WS01.cisco.com> | 2014-02-27 14:25:33 +0530 |
---|---|---|
committer | amdev <amdev@AMDEV-WS01.cisco.com> | 2014-02-27 14:25:33 +0530 |
commit | 55f062ea01923673f90d6921ef4e88ef04507dcd (patch) | |
tree | cdf4bd06dba0f457da43a3fc3f2c9803b890893b /doc | |
parent | 8240a55ab1ccab81925197e0a196fcfd6eac5e2b (diff) | |
download | pylint-55f062ea01923673f90d6921ef4e88ef04507dcd.tar.gz |
Initial draft of plugin how-to.
Diffstat (limited to 'doc')
-rw-r--r-- | doc/index.rst | 7 | ||||
-rw-r--r-- | doc/plugins.rst | 122 |
2 files changed, 123 insertions, 6 deletions
diff --git a/doc/index.rst b/doc/index.rst index 790ff93..bc6f9d8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,6 +15,7 @@ https://bitbucket.org/logilab/pylint features extend ide-integration + plugins contribute tutorial @@ -29,12 +30,6 @@ Content wanted It would be nice to include in the documentation the following information: -- pylint plugins (how to write one, how to install a 3rd party plugin, how to - configure Pylint to run it) - - pylint brain project : what it is, how to install it... Please send your pull requests via bitbucket if you can help with the above. - - - diff --git a/doc/plugins.rst b/doc/plugins.rst new file mode 100644 index 0000000..052c13f --- /dev/null +++ b/doc/plugins.rst @@ -0,0 +1,122 @@ +.. -*- coding: utf-8 -*-
+
+=======
+Plugins
+=======
+
+Why write a plugin?
+-------------------
+
+Pylint is a static analysis tool and Python is a dynamically typed language.
+So there will be cases where Pylint cannot analyze files properly (this problem
+can happen in statically typed languages also if reflection or dynamic
+evaluation is used). Plugin is a way to tell Pylint how to handle such cases,
+since only the user would know what needs to be done.
+
+Example
+-------
+
+Let us run Pylint on a module from the Python source: `warnings.py`_ and see what happens:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E Lib/warnings.py
+ E:297,36: Instance of 'WarningMessage' has no 'message' member (no-member)
+ E:298,36: Instance of 'WarningMessage' has no 'filename' member (no-member)
+ E:298,51: Instance of 'WarningMessage' has no 'lineno' member (no-member)
+ E:298,64: Instance of 'WarningMessage' has no 'line' member (no-member)
+
+
+Did we catch a genuine error? Let's open the code and look at ``WarningMessage`` class:
+
+.. sourcecode:: python
+
+ class WarningMessage(object):
+
+ """Holds the result of a single showwarning() call."""
+
+ _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
+ "line")
+
+ def __init__(self, message, category, filename, lineno, file=None,
+ line=None):
+ local_values = locals()
+ for attr in self._WARNING_DETAILS:
+ setattr(self, attr, local_values[attr])
+ self._category_name = category.__name__ if category else None
+
+ def __str__(self):
+ ...
+
+Ah, the fields (``message``, ``category`` etc) are not defined statically on the class.
+Instead they are added using ``setattr``. Pylint would have a tough time figuring
+this out.
+
+Enter Plugin
+------------
+
+We can write a plugin to tell Pylint about how to analyze this properly. A
+plugin is a module which should have a function ``register`` and takes the
+`lint`_ module as input. So a basic hello-world plugin can be implemented as:
+
+.. sourcecode:: python
+
+ # Inside hello_plugin.py
+ def register(linter):
+ print 'Hello world'
+
+We can run this plugin by placing this module in the PYTHONPATH and invoking as:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E --load-plugins hello_plugin foo.py
+ Hello world
+
+Back to our example: one way to fix that would be to transform the ``WarningMessage`` class
+and set the attributes using a plugin so that Pylint can see them. This can be done by
+registering a transform function. We can transform any node in the parsed AST like
+Module, Class, Function etc. In our case we need to transform a class. It can be done so:
+
+.. sourcecode:: python
+
+ from astroid import MANAGER
+ from astroid import scoped_nodes
+
+ def register(linter):
+ pass
+
+ def transform(cls):
+ if cls.name == 'WarningMessage':
+ import warnings
+ for f in warnings.WarningMessage._WARNING_DETAILS:
+ cls.locals[f] = [scoped_nodes.Class(f, None)]
+
+ MANAGER.register_transform(scoped_nodes.Class, transform)
+
+Let's go through the plugin. First, we need to register a class transform, which
+is done via the ``register_transform`` function in ``MANAGER``. It takes the node
+type and function as parameters. We need to change a class, so we use ``scoped_nodes.Class``.
+We also pass a ``transform`` function which does the actual transformation.
+
+``transform`` function is simple as well. If the class is ``WarningMessage`` then we
+add the attributes to its locals (we are not bothered about type of attributes, so setting
+them as class will do. But we could set them to any type we want). That's it.
+
+Note: We don't need to do anything in the ``register`` function of the plugin since we
+are not modifying anything in the linter itself.
+
+Lets run Pylint with this plugin and see:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E --load-plugins warning_plugin Lib/warnings.py
+ amitdev$
+
+All the false positives associated with ``WarningMessage`` are now gone. This is just
+an example, any code transformation can be done by plugins. See `nodes`_ and `scoped_nodes`_
+for details about all node types that can be transformed.
+
+.. _`warnings.py`: http://hg.python.org/cpython/file/2.7/Lib/warnings.py
+.. _`scoped_nodes`: https://bitbucket.org/logilab/astroid/src/64026ffc0d94fe09e4bdc2bf5efaab29444645e7/scoped_nodes.py?at=default
+.. _`nodes`: https://bitbucket.org/logilab/astroid/src/64026ffc0d94fe09e4bdc2bf5efaab29444645e7/nodes.py?at=default
+.. _`lint`: https://bitbucket.org/logilab/pylint/src/f2acea7b640def0237513f66e3de5fa3de73f2de/lint.py?at=default
\ No newline at end of file |