Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit cd142ed9a2229dcc83bb871b968a50992453ba9e @dcramer committed Feb 3, 2011
Showing with 1,093 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +4 −0 README.rst
  3. 0 pyflakes/__init__.py
  4. +311 −0 pyflakes/ast.py
  5. +408 −0 pyflakes/checker.py
  6. +77 −0 pyflakes/messages.py
  7. +292 −0 sublimeflakes.py
1 .gitignore
@@ -0,0 +1 @@
+*.pyc
4 README.rst
@@ -0,0 +1,4 @@
+This is a fork of SublimeFlakes which uses a newer/faster version of PyFlakes. Specifically, it uses David Cramer's fork, which is based on a number of other forks.
+
+PyFlakes fork: https://github.com/dcramer/pyflakes
+More information on SublimeFlakes (the original): http://www.sublimetext.com/forum/viewtopic.php?f=5&p=6938
0 pyflakes/__init__.py
No changes.
311 pyflakes/ast.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+"""
+ ast
+ ~~~
+
+ The `ast` module helps Python applications to process trees of the Python
+ abstract syntax grammar. The abstract syntax itself might change with
+ each Python release; this module helps to find out programmatically what
+ the current grammar looks like and allows modifications of it.
+
+ An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
+ a flag to the `compile()` builtin function or by using the `parse()`
+ function from this module. The result will be a tree of objects whose
+ classes all inherit from `ast.AST`.
+
+ A modified abstract syntax tree can be compiled into a Python code object
+ using the built-in `compile()` function.
+
+ Additionally various helper functions are provided that make working with
+ the trees simpler. The main intention of the helper functions and this
+ module in general is to provide an easy to use interface for libraries
+ that work tightly with the python syntax (template engines for example).
+
+
+ :copyright: Copyright 2008 by Armin Ronacher.
+ :license: Python License.
+"""
+from _ast import *
+from _ast import __version__
+
+
+def parse(expr, filename='<unknown>', mode='exec'):
+ """
+ Parse an expression into an AST node.
+ Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
+ """
+ return compile(expr, filename, mode, PyCF_ONLY_AST)
+
+
+def literal_eval(node_or_string):
+ """
+ Safely evaluate an expression node or a string containing a Python
+ expression. The string or node provided may only consist of the following
+ Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
+ and None.
+ """
+ _safe_names = {'None': None, 'True': True, 'False': False}
+ if isinstance(node_or_string, basestring):
+ node_or_string = parse(node_or_string, mode='eval')
+ if isinstance(node_or_string, Expression):
+ node_or_string = node_or_string.body
+ def _convert(node):
+ if isinstance(node, Str):
+ return node.s
+ elif isinstance(node, Num):
+ return node.n
+ elif isinstance(node, Tuple):
+ return tuple(map(_convert, node.elts))
+ elif isinstance(node, List):
+ return list(map(_convert, node.elts))
+ elif isinstance(node, Dict):
+ return dict((_convert(k), _convert(v)) for k, v
+ in zip(node.keys, node.values))
+ elif isinstance(node, Name):
+ if node.id in _safe_names:
+ return _safe_names[node.id]
+ raise ValueError('malformed string')
+ return _convert(node_or_string)
+
+
+def dump(node, annotate_fields=True, include_attributes=False):
+ """
+ Return a formatted dump of the tree in *node*. This is mainly useful for
+ debugging purposes. The returned string will show the names and the values
+ for fields. This makes the code impossible to evaluate, so if evaluation is
+ wanted *annotate_fields* must be set to False. Attributes such as line
+ numbers and column offsets are not dumped by default. If this is wanted,
+ *include_attributes* can be set to True.
+ """
+ def _format(node):
+ if isinstance(node, AST):
+ fields = [(a, _format(b)) for a, b in iter_fields(node)]
+ rv = '%s(%s' % (node.__class__.__name__, ', '.join(
+ ('%s=%s' % field for field in fields)
+ if annotate_fields else
+ (b for a, b in fields)
+ ))
+ if include_attributes and node._attributes:
+ rv += fields and ', ' or ' '
+ rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
+ for a in node._attributes)
+ return rv + ')'
+ elif isinstance(node, list):
+ return '[%s]' % ', '.join(_format(x) for x in node)
+ return repr(node)
+ if not isinstance(node, AST):
+ raise TypeError('expected AST, got %r' % node.__class__.__name__)
+ return _format(node)
+
+
+def copy_location(new_node, old_node):
+ """
+ Copy source location (`lineno` and `col_offset` attributes) from
+ *old_node* to *new_node* if possible, and return *new_node*.
+ """
+ for attr in 'lineno', 'col_offset':
+ if attr in old_node._attributes and attr in new_node._attributes \
+ and hasattr(old_node, attr):
+ setattr(new_node, attr, getattr(old_node, attr))
+ return new_node
+
+
+def fix_missing_locations(node):
+ """
+ When you compile a node tree with compile(), the compiler expects lineno and
+ col_offset attributes for every node that supports them. This is rather
+ tedious to fill in for generated nodes, so this helper adds these attributes
+ recursively where not already set, by setting them to the values of the
+ parent node. It works recursively starting at *node*.
+ """
+ def _fix(node, lineno, col_offset):
+ if 'lineno' in node._attributes:
+ if not hasattr(node, 'lineno'):
+ node.lineno = lineno
+ else:
+ lineno = node.lineno
+ if 'col_offset' in node._attributes:
+ if not hasattr(node, 'col_offset'):
+ node.col_offset = col_offset
+ else:
+ col_offset = node.col_offset
+ for child in iter_child_nodes(node):
+ _fix(child, lineno, col_offset)
+ _fix(node, 1, 0)
+ return node
+
+def add_col_end(node):
+ def _fix(node, next):
+ children = list(iter_child_nodes(node))
+ for i, child in enumerate(children):
+ next_offset = children[i+1].col_offset if i < len(children) else next.col_offset
+ child.col_end = next_offset
+
+
+def increment_lineno(node, n=1):
+ """
+ Increment the line number of each node in the tree starting at *node* by *n*.
+ This is useful to "move code" to a different location in a file.
+ """
+ if 'lineno' in node._attributes:
+ node.lineno = getattr(node, 'lineno', 0) + n
+ for child in walk(node):
+ if 'lineno' in child._attributes:
+ child.lineno = getattr(child, 'lineno', 0) + n
+ return node
+
+
+def iter_fields(node):
+ """
+ Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
+ that is present on *node*.
+ """
+ if node._fields is None:
+ return
+
+ for field in node._fields:
+ try:
+ yield field, getattr(node, field)
+ except AttributeError:
+ pass
+
+
+def iter_child_nodes(node):
+ """
+ Yield all direct child nodes of *node*, that is, all fields that are nodes
+ and all items of fields that are lists of nodes.
+ """
+ for name, field in iter_fields(node):
+ if isinstance(field, AST):
+ yield field
+ elif isinstance(field, list):
+ for item in field:
+ if isinstance(item, AST):
+ yield item
+
+
+def get_docstring(node, clean=True):
+ """
+ Return the docstring for the given node or None if no docstring can
+ be found. If the node provided does not have docstrings a TypeError
+ will be raised.
+ """
+ if not isinstance(node, (FunctionDef, ClassDef, Module)):
+ raise TypeError("%r can't have docstrings" % node.__class__.__name__)
+ if node.body and isinstance(node.body[0], Expr) and \
+ isinstance(node.body[0].value, Str):
+ if clean:
+ import inspect
+ return inspect.cleandoc(node.body[0].value.s)
+ return node.body[0].value.s
+
+
+def walk(node):
+ """
+ Recursively yield all child nodes of *node*, in no specified order. This is
+ useful if you only want to modify nodes in place and don't care about the
+ context.
+ """
+ from collections import deque
+ todo = deque([node])
+ while todo:
+ node = todo.popleft()
+ todo.extend(iter_child_nodes(node))
+ yield node
+
+
+class NodeVisitor(object):
+ """
+ A node visitor base class that walks the abstract syntax tree and calls a
+ visitor function for every node found. This function may return a value
+ which is forwarded by the `visit` method.
+
+ This class is meant to be subclassed, with the subclass adding visitor
+ methods.
+
+ Per default the visitor functions for the nodes are ``'visit_'`` +
+ class name of the node. So a `TryFinally` node visit function would
+ be `visit_TryFinally`. This behavior can be changed by overriding
+ the `visit` method. If no visitor function exists for a node
+ (return value `None`) the `generic_visit` visitor is used instead.
+
+ Don't use the `NodeVisitor` if you want to apply changes to nodes during
+ traversing. For this a special visitor exists (`NodeTransformer`) that
+ allows modifications.
+ """
+
+ def visit(self, node):
+ """Visit a node."""
+ method = 'visit_' + node.__class__.__name__
+ visitor = getattr(self, method, self.generic_visit)
+ return visitor(node)
+
+ def generic_visit(self, node):
+ """Called if no explicit visitor function exists for a node."""
+ for field, value in iter_fields(node):
+ if isinstance(value, list):
+ for item in value:
+ if isinstance(item, AST):
+ self.visit(item)
+ elif isinstance(value, AST):
+ self.visit(value)
+
+
+class NodeTransformer(NodeVisitor):
+ """
+ A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
+ allows modification of nodes.
+
+ The `NodeTransformer` will walk the AST and use the return value of the
+ visitor methods to replace or remove the old node. If the return value of
+ the visitor method is ``None``, the node will be removed from its location,
+ otherwise it is replaced with the return value. The return value may be the
+ original node in which case no replacement takes place.
+
+ Here is an example transformer that rewrites all occurrences of name lookups
+ (``foo``) to ``data['foo']``::
+
+ class RewriteName(NodeTransformer):
+
+ def visit_Name(self, node):
+ return copy_location(Subscript(
+ value=Name(id='data', ctx=Load()),
+ slice=Index(value=Str(s=node.id)),
+ ctx=node.ctx
+ ), node)
+
+ Keep in mind that if the node you're operating on has child nodes you must
+ either transform the child nodes yourself or call the :meth:`generic_visit`
+ method for the node first.
+
+ For nodes that were part of a collection of statements (that applies to all
+ statement nodes), the visitor may also return a list of nodes rather than
+ just a single node.
+
+ Usually you use the transformer like this::
+
+ node = YourTransformer().visit(node)
+ """
+
+ def generic_visit(self, node):
+ for field, old_value in iter_fields(node):
+ old_value = getattr(node, field, None)
+ if isinstance(old_value, list):
+ new_values = []
+ for value in old_value:
+ if isinstance(value, AST):
+ value = self.visit(value)
+ if value is None:
+ continue
+ elif not isinstance(value, AST):
+ new_values.extend(value)
+ continue
+ new_values.append(value)
+ old_value[:] = new_values
+ elif isinstance(old_value, AST):
+ new_node = self.visit(old_value)
+ if new_node is None:
+ delattr(node, field)
+ else:
+ setattr(node, field, new_node)
+ return node
408 pyflakes/checker.py
@@ -0,0 +1,408 @@
+import ast
+from pyflakes import messages
+import __builtin__
+
+
+allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str)
+defined_names = set(('__file__', '__builtins__'))
+
+class Binding(object):
+ """
+ @ivar used: pair of (L{Scope}, line-number) indicating the scope and
+ line number that this binding was last used
+ """
+ def __init__(self, name, source):
+ self.name = name
+ self.source = source
+ self.used = False
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
+ self.name,
+ self.source.lineno,
+ id(self))
+
+class UnBinding(Binding):
+ '''Created by the 'del' operator.'''
+
+class Importation(Binding):
+ def __init__(self, name, source):
+ name = name.split('.')[0]
+ super(Importation, self).__init__(name, source)
+
+class Assignment(Binding):
+ pass
+
+class FunctionDefinition(Binding):
+ _property_decorator = False
+
+
+class Scope(dict):
+ import_starred = False # set to True when import * is found
+
+ def __repr__(self):
+ return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
+
+ def __init__(self):
+ super(Scope, self).__init__()
+
+class ClassScope(Scope):
+ pass
+
+
+
+class FunctionScope(Scope):
+ """
+ I represent a name scope for a function.
+
+ @ivar globals: Names declared 'global' in this function.
+ """
+ def __init__(self):
+ super(FunctionScope, self).__init__()
+ self.globals = {}
+
+
+
+class ModuleScope(Scope):
+ pass
+
+class Checker(ast.NodeVisitor):
+ def __init__(self, tree, filename='(none)', builtins = None):
+ ast.NodeVisitor.__init__(self)
+
+ self.deferred = []
+ self.dead_scopes = []
+ self.messages = []
+ self.filename = filename
+ self.scope_stack = [ModuleScope()]
+ self.futures_allowed = True
+ self.builtins = frozenset(builtins or [])
+
+ self.visit(tree)
+ for handler, scope in self.deferred:
+ self.scope_stack = scope
+ handler()
+ del self.scope_stack[1:]
+ self.pop_scope()
+ self.check_dead_scopes()
+
+ def defer(self, callable):
+ '''Schedule something to be called after just before completion.
+
+ This is used for handling function bodies, which must be deferred
+ because code later in the file might modify the global scope. When
+ `callable` is called, the scope at the time this is called will be
+ restored, however it will contain any new bindings added to it.
+ '''
+ self.deferred.append( (callable, self.scope_stack[:]) )
+
+ def check_dead_scopes(self):
+ # Check for modules that were imported but unused
+ for scope in self.dead_scopes:
+ for importation in scope.itervalues():
+ if isinstance(importation, Importation) and not importation.used:
+ self.report(messages.UnusedImport, importation.source.lineno, importation.name)
+
+ def push_function_scope(self):
+ self.scope_stack.append(FunctionScope())
+
+ def push_class_scope(self):
+ self.scope_stack.append(ClassScope())
+
+ def pop_scope(self):
+ scope = self.scope_stack.pop()
+ self.dead_scopes.append(scope)
+
+ @property
+ def scope(self):
+ return self.scope_stack[-1]
+
+ def report(self, message_class, *args, **kwargs):
+ self.messages.append(message_class(self.filename, *args, **kwargs))
+
+ def visit_Import(self, node):
+ for name_node in node.names:
+ # "import bar as foo" -> name=bar, asname=foo
+ name = name_node.asname or name_node.name
+ self.add_binding(node, Importation(name, node))
+
+ def visit_GeneratorExp(self, node):
+ for generator in node.generators:
+ self.visit(generator.iter)
+ self.assign_vars(generator.target)
+
+ for generator in node.generators:
+ if hasattr(node, 'elt'):
+ self.visit(node.elt)
+
+ self.visit_nodes(generator.ifs)
+
+ visit_ListComp = visit_GeneratorExp
+
+ def visit_For(self, node):
+ '''
+ Process bindings for loop variables.
+ '''
+ self.visit_nodes(node.iter)
+
+ for var in self.flatten(node.target):
+ upval = self.scope.get(var.id)
+ if isinstance(upval, Importation) and upval.used:
+ self.report(messages.ImportShadowedByLoopVar,
+ node.lineno, node.col_offset, var.id, upval.source.lineno)
+
+ self.add_binding(var, Assignment(var.id, var))
+
+ self.visit_nodes(node.body + node.orelse)
+
+ def visit_FunctionDef(self, node):
+
+ try:
+ decorators = node.decorator_list
+ except AttributeError:
+ # Use .decorators for Python 2.5 compatibility
+ decorators = node.decorators
+
+ self.visit_nodes(decorators)
+
+ # Check for property decorator
+ func_def = FunctionDefinition(node.name, node)
+
+ for decorator in decorators:
+ if getattr(decorator, 'attr', None) in ('setter', 'deleter'):
+ func_def._property_decorator = True
+
+ self.add_binding(node, func_def)
+
+ self.visit_Lambda(node)
+
+ def visit_Lambda(self, node):
+ self.visit_nodes(node.args.defaults)
+
+ def run_function():
+ self.push_function_scope()
+
+ # Check for duplicate arguments
+ argnames = set()
+ for arg in self.flatten(node.args.args):
+ if arg.id in argnames:
+ self.report(messages.DuplicateArgument, arg.lineno, arg.col_offset, arg.id)
+ argnames.add(arg.id)
+
+ self.assign_vars(node.args.args, report_redef=False)
+ if node.args.vararg is not None:
+ self.add_binding(node, Assignment(node.args.vararg, node), False)
+ if node.args.kwarg is not None:
+ self.add_binding(node, Assignment(node.args.kwarg, node), False)
+ self.visit_nodes(node.body)
+ self.pop_scope()
+
+ self.defer(run_function)
+
+ def visit_Name(self, node):
+ '''
+ Locate names in locals / function / globals scopes.
+ '''
+ scope, name = self.scope, node.id
+
+ # try local scope
+ import_starred = scope.import_starred
+ try:
+ scope[name].used = (scope, node.lineno, node.col_offset)
+ except KeyError:
+ pass
+ else:
+ return
+
+ # try enclosing function scopes
+ for func_scope in self.scope_stack[-2:0:-1]:
+ import_starred = import_starred or func_scope.import_starred
+ if not isinstance(func_scope, FunctionScope):
+ continue
+ try:
+ func_scope[name].used = (scope, node.lineno, node.col_offset)
+ except KeyError:
+ pass
+ else:
+ return
+
+ # try global scope
+ import_starred = import_starred or self.scope_stack[0].import_starred
+ try:
+ self.scope_stack[0][node.id].used = (scope, node.lineno, node.col_offset)
+ except KeyError:
+ if not import_starred and not self.is_builtin(name):
+ self.report(messages.UndefinedName, node.lineno, node.col_offset, name)
+
+ def assign_vars(self, targets, report_redef=True):
+ scope = self.scope
+
+ for target in self.flatten(targets):
+ name = target.id
+ # if the name hasn't already been defined in the current scope
+ if isinstance(scope, FunctionScope) and name not in scope:
+ # for each function or module scope above us
+ for upscope in self.scope_stack[:-1]:
+ if not isinstance(upscope, (FunctionScope, ModuleScope)):
+ continue
+
+ upval = upscope.get(name)
+ # if the name was defined in that scope, and the name has
+ # been accessed already in the current scope, and hasn't
+ # been declared global
+ if upval is not None:
+ if upval.used and upval.used[0] is scope and name not in scope.globals:
+ # then it's probably a mistake
+ self.report(messages.UndefinedLocal,
+ upval.used[1], upval.used[2], name, upval.source.lineno, upval.source.col_offset)
+
+ self.add_binding(target, Assignment(name, target), report_redef)
+
+ def visit_Assign(self, node):
+ for target in node.targets:
+ self.visit_nodes(node.value)
+ self.assign_vars(node.targets)
+
+ def visit_Delete(self, node):
+ for target in self.flatten(node.targets):
+ if isinstance(self.scope, FunctionScope) and target.id in self.scope.globals:
+ del self.scope.globals[target.id]
+ else:
+ self.add_binding(target, UnBinding(target.id, target))
+
+ def visit_With(self, node):
+ self.visit(node.context_expr)
+
+ # handle new bindings made by optional "as" part
+ if node.optional_vars is not None:
+ self.assign_vars(node.optional_vars)
+
+ self.visit_nodes(node.body)
+
+ def visit_ImportFrom(self, node):
+ if node.module == '__future__':
+ if not self.futures_allowed:
+ self.report(messages.LateFutureImport, node.lineno, node.col_offset, [alias.name for alias in node.names])
+ else:
+ self.futures_allowed = False
+
+ for alias in node.names:
+ if alias.name == '*':
+ self.scope.import_starred = True
+ self.report(messages.ImportStarUsed, node.lineno, node.col_offset, node.module)
+ continue
+ name = alias.asname or alias.name
+ importation = Importation(name, node)
+ if node.module == '__future__':
+ importation.used = (self.scope, node.lineno, node.col_offset)
+ self.add_binding(node, importation)
+
+ def visit_Global(self, node):
+ '''
+ Keep track of global declarations.
+ '''
+ scope = self.scope
+ if isinstance(scope, FunctionScope):
+ scope.globals.update(dict.fromkeys(node.names))
+
+ def visit_ClassDef(self, node):
+ try:
+ decorators = node.decorator_list
+ except AttributeError:
+ # Use .decorators for Python 2.5 compatibility
+ decorators = getattr(node, 'decorators', [])
+
+ self.visit_nodes(decorators)
+
+ self.add_binding(node, Assignment(node.name, node))
+ self.visit_nodes(node.bases)
+
+ self.push_class_scope()
+ self.visit_nodes(node.body)
+ self.pop_scope()
+
+ def visit_excepthandler(self, node):
+ if node.type is not None:
+ self.visit(node.type)
+ if node.name is not None:
+ self.assign_vars(node.name)
+ self.visit_nodes(node.body)
+
+ visit_ExceptHandler = visit_excepthandler # in 2.6, this was CamelCased
+
+ def flatten(self, nodes):
+ if isinstance(nodes, ast.Attribute):
+ self.visit(nodes)
+ return []
+ elif isinstance(nodes, ast.Subscript):
+ self.visit(nodes.value)
+ self.visit(nodes.slice)
+ return []
+ elif isinstance(nodes, ast.Name):
+ return [nodes]
+ elif isinstance(nodes, (ast.Tuple, ast.List)):
+ return self.flatten(nodes.elts)
+
+ flattened_nodes = []
+ for node in nodes:
+ if hasattr(node, 'elts'):
+ flattened_nodes += self.flatten(node.elts)
+ elif node is not None:
+ flattened_nodes += self.flatten(node)
+
+ return flattened_nodes
+
+ def add_binding(self, node, value, report_redef=True):
+ line, col, scope, name = node.lineno, node.col_offset, self.scope, value.name
+
+ # Check for a redefined function
+ func = scope.get(name)
+ if (isinstance(func, FunctionDefinition) and isinstance(value, FunctionDefinition)):
+ # Property-decorated functions (@x.setter) should have duplicate names
+ if not value._property_decorator:
+ self.report(messages.RedefinedFunction, line, name, func.source.lineno)
+
+ # Check for redefining an unused import
+ if report_redef and not isinstance(scope, ClassScope):
+ for up_scope in self.scope_stack[::-1]:
+ upval = up_scope.get(name)
+ if isinstance(upval, Importation) and not upval.used:
+ self.report(messages.RedefinedWhileUnused, line, col, name, upval.source.lineno)
+
+ # Check for "del undefined_name"
+ if isinstance(value, UnBinding):
+ try:
+ del scope[name]
+ except KeyError:
+ self.report(messages.UndefinedName, line, col, name)
+ else:
+ scope[name] = value
+
+ def visit(self, node):
+ if not isinstance(node, allowed_before_future):
+ self.futures_allowed = False
+
+ return super(Checker, self).visit(node)
+
+ def visit_nodes(self, nodes):
+ try:
+ nodes = list(getattr(nodes, 'elts', nodes))
+ except TypeError:
+ nodes = [nodes]
+
+ for node in nodes:
+ self.visit(node)
+
+ def is_builtin(self, name):
+ if hasattr(__builtin__, name):
+ return True
+ if name in defined_names:
+ return True
+ if name in self.builtins:
+ return True
+
+ return False
+
77 pyflakes/messages.py
@@ -0,0 +1,77 @@
+# (c) 2005 Divmod, Inc. See LICENSE file for details
+
+class Message(object):
+ message = ''
+ message_args = ()
+
+ def __init__(self, filename, lineno, col = None, level = 'W', message_args = None):
+ self.filename = filename
+ self.lineno = lineno
+ self.col = col
+ self.level = level
+ if message_args:
+ self.message_args = message_args
+
+ def __str__(self):
+ if self.col is not None:
+ return '%s:%s(%d): [%s] %s' % (self.filename, self.lineno, self.col, self.level, self.message % self.message_args)
+ else:
+ return '%s:%s: [%s] %s' % (self.filename, self.lineno, self.level, self.message % self.message_args)
+
+
+class UnusedImport(Message):
+ message = '%r imported but unused'
+
+ def __init__(self, filename, lineno, name):
+ Message.__init__(self, filename, lineno, message_args=(name,))
+
+class RedefinedWhileUnused(Message):
+ message = 'redefinition of unused %r from line %r'
+
+ def __init__(self, filename, lineno, col, name, orig_lineno):
+ Message.__init__(self, filename, lineno, message_args=(name, orig_lineno))
+
+class ImportShadowedByLoopVar(Message):
+ message = 'import %r from line %r shadowed by loop variable'
+
+ def __init__(self, filename, lineno, col, name, orig_lineno):
+ Message.__init__(self, filename, lineno, message_args=(name, orig_lineno))
+
+class ImportStarUsed(Message):
+ message = "'from %s import *' used; unable to detect undefined names"
+
+ def __init__(self, filename, lineno, col, modname):
+ Message.__init__(self, filename, lineno, col, message_args=(modname,))
+
+class UndefinedName(Message):
+ message = 'undefined name %r'
+
+ def __init__(self, filename, lineno, col, name):
+ Message.__init__(self, filename, lineno, col, 'E', (name,))
+
+class UndefinedLocal(Message):
+ message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
+
+ def __init__(self, filename, lineno, col, name, orig_lineno, orig_col):
+ Message.__init__(self, filename, lineno, message_args=(name, orig_lineno))
+
+
+class DuplicateArgument(Message):
+ message = 'duplicate argument %r in function definition'
+
+ def __init__(self, filename, lineno, col, name):
+ Message.__init__(self, filename, lineno, col, message_args=(name,))
+
+
+class RedefinedFunction(Message):
+ message = 'redefinition of function %r from line %r'
+
+ def __init__(self, filename, lineno, name, orig_lineno):
+ Message.__init__(self, filename, lineno, message_args=(name, orig_lineno))
+
+
+class LateFutureImport(Message):
+ message = 'future import(s) %r after other statements'
+
+ def __init__(self, filename, lineno, col, names):
+ Message.__init__(self, filename, lineno, message_args=(names,))
292 sublimeflakes.py
@@ -0,0 +1,292 @@
+import sublime, sublime_plugin
+import os, sys, compiler, re
+
+from pyflakes import checker, messages
+
+drawType = (sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+
+class OffsetError(messages.Message):
+ message = '%r at offset %r'
+ def __init__(self, filename, lineno, text, offset):
+ messages.Message.__init__(self, filename, lineno)
+ self.offset = offset
+ self.message_args = (text, offset)
+
+class PythonError(messages.Message):
+ message = '%r'
+ def __init__(self, filename, lineno, text):
+ messages.Message.__init__(self, filename, lineno)
+ self.message_args = (text,)
+
+def check(codeString, filename):
+ codeString = codeString.rstrip()
+ try:
+ try:
+ compile(codeString, filename, "exec")
+ except MemoryError:
+ # Python 2.4 will raise MemoryError if the source can't be
+ # decoded.
+ if sys.version_info[:2] == (2, 4):
+ raise SyntaxError(None)
+ raise
+ except (SyntaxError, IndentationError), value:
+ # print traceback.format_exc() # helps debug new cases
+ msg = value.args[0]
+
+ lineno, offset, text = value.lineno, value.offset, value.text
+
+ # If there's an encoding problem with the file, the text is None.
+ if text is None:
+ # Avoid using msg, since for the only known case, it contains a
+ # bogus message that claims the encoding the file declared was
+ # unknown.
+ if msg.startswith('duplicate argument'):
+ arg = msg.split('duplicate argument ',1)[1].split(' ',1)[0].strip('\'"')
+ error = messages.DuplicateArgument(filename, lineno, arg)
+ else:
+ error = PythonError(filename, lineno, msg)
+ else:
+ line = text.splitlines()[-1]
+
+ if offset is not None:
+ offset = offset - (len(text) - len(line))
+
+ if offset is not None:
+ error = OffsetError(filename, lineno, msg, offset)
+ else:
+ error = PythonError(filename, lineno, msg)
+
+ return [error]
+ else:
+ # Okay, it's syntactically valid. Now parse it into an ast and check
+ # it.
+ tree = compiler.parse(codeString)
+ w = checker.Checker(tree, filename)
+ w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+ return w.messages
+
+def printf(*args): print '"' + ' '.join(args) + '"'
+
+global lineMessages
+lineMessages = {}
+def validate(view):
+ global lineMessages
+ vid = view.id()
+
+ text = view.substr(sublime.Region(0, view.size()))
+
+ stripped_lines = []
+ good_lines = []
+ lines = text.split('\n')
+ for i in xrange(len(lines)):
+ line = lines[i]
+ if not line.strip() or line.strip().startswith('#'):
+ stripped_lines.append(i)
+ else:
+ good_lines.append(line)
+
+ text = '\n'.join(good_lines)
+ if view.file_name(): filename = os.path.split(view.file_name())[-1]
+ else: filename = 'untitled'
+
+ errors = check(text, filename)
+
+ lines = set()
+ underline = []
+
+ def underlineRange(lineno, position, length=1):
+ line = view.full_line(view.text_point(lineno, 0))
+ position += line.begin()
+
+ for i in xrange(length):
+ underline.append(sublime.Region(position + i))
+
+ def underlineRegex(lineno, regex, wordmatch=None, linematch=None):
+ lines.add(lineno)
+ offset = 0
+
+ line = view.full_line(view.text_point(lineno, 0))
+ lineText = view.substr(line)
+ if linematch:
+ match = re.match(linematch, lineText)
+ if match:
+ lineText = match.group('match')
+ offset = match.start('match')
+ else:
+ return
+
+ iters = re.finditer(regex, lineText)
+ results = [(result.start('underline'), result.end('underline')) for result in iters if
+ not wordmatch or result.group('underline') == wordmatch]
+
+ for start, end in results:
+ underlineRange(lineno, start+offset, end-start)
+
+ def underlineWord(lineno, word):
+ regex = '((and|or|not|if|elif|while|in)\s+|[+\-*^%%<>=({[])*\s*(?P<underline>[\w]*%s[\w]*)' % (word)
+ underlineRegex(lineno, regex, word)
+
+ def underlineImport(lineno, word):
+ linematch = 'import\s+(?P<match>[^#;]+)'
+ regex = '(\s+|,\s*|as\s+)(?P<underline>[\w]*%s[\w]*)' % word
+ underlineRegex(lineno, regex, word, linematch)
+
+ def underlineForVar(lineno, word):
+ regex = 'for\s+(?P<underline>[\w]*%s[\w*])' % word
+ underlineRegex(lineno, regex, word)
+
+ def underlineDuplicateArgument(lineno, word):
+ regex = 'def [\w_]+\(.*?(?P<underline>[\w]*%s[\w]*)' % word
+ underlineRegex(lineno, regex, word)
+
+ errorMessages = {}
+ def addMessage(lineno, message):
+ message = str(message)
+ if lineno in lineMessages:
+ errorMessages[lineno].append(message)
+ else:
+ errorMessages[lineno] = [message]
+
+ view.erase_regions('pyflakes-syntax')
+ view.erase_regions('pyflakes-syntax-underline')
+ view.erase_regions('pyflakes-underline')
+ for error in errors:
+ error.lineno -= 1
+ for i in stripped_lines:
+ if error.lineno >= i:
+ error.lineno += 1
+
+ lines.add(error.lineno)
+ addMessage(error.lineno, error)
+ if isinstance(error, OffsetError):
+ underlineRange(error.lineno, error.offset)
+ if len(errors) == 1 and False:
+ outlines = [view.full_line(view.text_point(error.lineno, 0)) for lineno in lines]
+ view.add_regions('pyflakes-syntax', outlines, 'keyword', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+ view.add_regions('pyflakes-syntax-underline', underline, 'keyword', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+ return
+
+ elif isinstance(error, PythonError):
+ if len(errors) == 1 and False:
+ outlines = [view.full_line(view.text_point(error.lineno, 0)) for lineno in lines]
+ view.add_regions('pyflakes-syntax', outlines, 'keyword', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+ return
+
+ elif isinstance(error, messages.UnusedImport):
+ underlineImport(error.lineno, error.name)
+
+ elif isinstance(error, messages.RedefinedWhileUnused):
+ underlineWord(error.lineno, error.name)
+
+ elif isinstance(error, messages.ImportShadowedByLoopVar):
+ underlineForVar(error.lineno, error.name)
+
+ elif isinstance(error, messages.ImportStarUsed):
+ underlineImport(error.lineno, '\*')
+
+ elif isinstance(error, messages.UndefinedName):
+ underlineWord(error.lineno, error.name)
+
+ elif isinstance(error, messages.UndefinedExport):
+ underlineWord(error.lineno, error.name)
+
+ elif isinstance(error, messages.UndefinedLocal):
+ underlineWord(error.lineno, error.name)
+
+ elif isinstance(error, messages.DuplicateArgument):
+ underlineDuplicateArgument(error.lineno, error.name)
+
+ elif isinstance(error, messages.RedefinedFunction):
+ underlineWord(error.lineno, error.name)
+
+ elif isinstance(error, messages.LateFutureImport):
+ pass
+
+ elif isinstance(error, messages.UnusedVariable):
+ underlineWord(error.lineno, error.name)
+
+ else:
+ print 'Oops, we missed an error type!'
+
+ view.erase_regions('pyflakes-outlines')
+ if underline or lines:
+ outlines = [view.full_line(view.text_point(lineno, 0)) for lineno in lines]
+
+ view.add_regions('pyflakes-underline', underline, 'keyword', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+ view.add_regions('pyflakes-outlines', outlines, 'keyword', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED)
+
+ lineMessages[vid] = errorMessages
+
+import time, thread
+global queue, lookup
+queue = {}
+lookup = {}
+
+def validate_runner(): # this threaded runner keeps it from slowing down UI while you type
+ global queue, lookup
+ while True:
+ time.sleep(0.5)
+ for vid in dict(queue):
+ if vid not in queue:
+ return
+ if queue[vid] == 0:
+ validate(lookup[vid])
+ try: del queue[vid]
+ except: pass
+ try: del lookup[vid]
+ except: pass
+ else:
+ queue[vid] = 0
+
+def validate_hit(view):
+ global lookup
+ global queue
+
+ if not 'Python' in view.settings().get("syntax"):
+ view.erase_regions('pyflakes-syntax')
+ view.erase_regions('pyflakes-syntax-underline')
+ view.erase_regions('pyflakes-underline')
+ view.erase_regions('pyflakes-outlines')
+ return
+
+ vid = view.id()
+ lookup[vid] = view
+ queue[vid] = 1
+
+thread.start_new_thread(validate_runner, ())
+
+class pyflakes(sublime_plugin.EventListener):
+ def __init__(self, *args, **kwargs):
+ sublime_plugin.EventListener.__init__(self, *args, **kwargs)
+ self.lastCount = {}
+
+ def on_modified(self, view):
+ validate_hit(view)
+ return
+
+ # alternate method which works alright when we don't have threads/set_timeout
+ # from when I ported to early X beta :P
+ text = view.substr(sublime.Region(0, view.size()))
+ count = text.count('\n')
+ if count > 500: return
+ bid = view.buffer_id()
+
+ if bid in self.lastCount:
+ if self.lastCount[bid] != count:
+ validate(view)
+
+ self.lastCount[bid] = count
+
+ def on_load(self, view):
+ validate_hit(view)
+
+ def on_post_save(self, view):
+ validate_hit(view)
+
+ def on_selection_modified(self, view):
+ vid = view.id()
+ lineno = view.rowcol(view.sel()[0].end())[0]
+ if vid in lineMessages and lineno in lineMessages[vid]:
+ view.set_status('pyflakes', '; '.join(lineMessages[vid][lineno]))
+ else:
+ view.erase_status('pyflakes')

0 comments on commit cd142ed

Please sign in to comment.