summaryrefslogtreecommitdiff
path: root/doc/plugins.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/plugins.rst')
-rw-r--r--doc/plugins.rst122
1 files changed, 122 insertions, 0 deletions
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