├── .gitignore ├── README.rst ├── pyflakes ├── __init__.py ├── ast.py ├── checker.py └── messages.py └── sublimeflakes.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 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. 2 | 3 | PyFlakes fork: https://github.com/dcramer/pyflakes 4 | More information on SublimeFlakes (the original): http://www.sublimetext.com/forum/viewtopic.php?f=5&p=6938 -------------------------------------------------------------------------------- /pyflakes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcramer/SublimeFlakes/0296d0f9469f47df753927e181d16b34487e48e6/pyflakes/__init__.py -------------------------------------------------------------------------------- /pyflakes/ast.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ast 4 | ~~~ 5 | 6 | The `ast` module helps Python applications to process trees of the Python 7 | abstract syntax grammar. The abstract syntax itself might change with 8 | each Python release; this module helps to find out programmatically what 9 | the current grammar looks like and allows modifications of it. 10 | 11 | An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as 12 | a flag to the `compile()` builtin function or by using the `parse()` 13 | function from this module. The result will be a tree of objects whose 14 | classes all inherit from `ast.AST`. 15 | 16 | A modified abstract syntax tree can be compiled into a Python code object 17 | using the built-in `compile()` function. 18 | 19 | Additionally various helper functions are provided that make working with 20 | the trees simpler. The main intention of the helper functions and this 21 | module in general is to provide an easy to use interface for libraries 22 | that work tightly with the python syntax (template engines for example). 23 | 24 | 25 | :copyright: Copyright 2008 by Armin Ronacher. 26 | :license: Python License. 27 | """ 28 | from _ast import * 29 | from _ast import __version__ 30 | 31 | 32 | def parse(expr, filename='', mode='exec'): 33 | """ 34 | Parse an expression into an AST node. 35 | Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST). 36 | """ 37 | return compile(expr, filename, mode, PyCF_ONLY_AST) 38 | 39 | 40 | def literal_eval(node_or_string): 41 | """ 42 | Safely evaluate an expression node or a string containing a Python 43 | expression. The string or node provided may only consist of the following 44 | Python literal structures: strings, numbers, tuples, lists, dicts, booleans, 45 | and None. 46 | """ 47 | _safe_names = {'None': None, 'True': True, 'False': False} 48 | if isinstance(node_or_string, basestring): 49 | node_or_string = parse(node_or_string, mode='eval') 50 | if isinstance(node_or_string, Expression): 51 | node_or_string = node_or_string.body 52 | def _convert(node): 53 | if isinstance(node, Str): 54 | return node.s 55 | elif isinstance(node, Num): 56 | return node.n 57 | elif isinstance(node, Tuple): 58 | return tuple(map(_convert, node.elts)) 59 | elif isinstance(node, List): 60 | return list(map(_convert, node.elts)) 61 | elif isinstance(node, Dict): 62 | return dict((_convert(k), _convert(v)) for k, v 63 | in zip(node.keys, node.values)) 64 | elif isinstance(node, Name): 65 | if node.id in _safe_names: 66 | return _safe_names[node.id] 67 | raise ValueError('malformed string') 68 | return _convert(node_or_string) 69 | 70 | 71 | def dump(node, annotate_fields=True, include_attributes=False): 72 | """ 73 | Return a formatted dump of the tree in *node*. This is mainly useful for 74 | debugging purposes. The returned string will show the names and the values 75 | for fields. This makes the code impossible to evaluate, so if evaluation is 76 | wanted *annotate_fields* must be set to False. Attributes such as line 77 | numbers and column offsets are not dumped by default. If this is wanted, 78 | *include_attributes* can be set to True. 79 | """ 80 | def _format(node): 81 | if isinstance(node, AST): 82 | fields = [(a, _format(b)) for a, b in iter_fields(node)] 83 | rv = '%s(%s' % (node.__class__.__name__, ', '.join( 84 | ('%s=%s' % field for field in fields) 85 | if annotate_fields else 86 | (b for a, b in fields) 87 | )) 88 | if include_attributes and node._attributes: 89 | rv += fields and ', ' or ' ' 90 | rv += ', '.join('%s=%s' % (a, _format(getattr(node, a))) 91 | for a in node._attributes) 92 | return rv + ')' 93 | elif isinstance(node, list): 94 | return '[%s]' % ', '.join(_format(x) for x in node) 95 | return repr(node) 96 | if not isinstance(node, AST): 97 | raise TypeError('expected AST, got %r' % node.__class__.__name__) 98 | return _format(node) 99 | 100 | 101 | def copy_location(new_node, old_node): 102 | """ 103 | Copy source location (`lineno` and `col_offset` attributes) from 104 | *old_node* to *new_node* if possible, and return *new_node*. 105 | """ 106 | for attr in 'lineno', 'col_offset': 107 | if attr in old_node._attributes and attr in new_node._attributes \ 108 | and hasattr(old_node, attr): 109 | setattr(new_node, attr, getattr(old_node, attr)) 110 | return new_node 111 | 112 | 113 | def fix_missing_locations(node): 114 | """ 115 | When you compile a node tree with compile(), the compiler expects lineno and 116 | col_offset attributes for every node that supports them. This is rather 117 | tedious to fill in for generated nodes, so this helper adds these attributes 118 | recursively where not already set, by setting them to the values of the 119 | parent node. It works recursively starting at *node*. 120 | """ 121 | def _fix(node, lineno, col_offset): 122 | if 'lineno' in node._attributes: 123 | if not hasattr(node, 'lineno'): 124 | node.lineno = lineno 125 | else: 126 | lineno = node.lineno 127 | if 'col_offset' in node._attributes: 128 | if not hasattr(node, 'col_offset'): 129 | node.col_offset = col_offset 130 | else: 131 | col_offset = node.col_offset 132 | for child in iter_child_nodes(node): 133 | _fix(child, lineno, col_offset) 134 | _fix(node, 1, 0) 135 | return node 136 | 137 | def add_col_end(node): 138 | def _fix(node, next): 139 | children = list(iter_child_nodes(node)) 140 | for i, child in enumerate(children): 141 | next_offset = children[i+1].col_offset if i < len(children) else next.col_offset 142 | child.col_end = next_offset 143 | 144 | 145 | def increment_lineno(node, n=1): 146 | """ 147 | Increment the line number of each node in the tree starting at *node* by *n*. 148 | This is useful to "move code" to a different location in a file. 149 | """ 150 | if 'lineno' in node._attributes: 151 | node.lineno = getattr(node, 'lineno', 0) + n 152 | for child in walk(node): 153 | if 'lineno' in child._attributes: 154 | child.lineno = getattr(child, 'lineno', 0) + n 155 | return node 156 | 157 | 158 | def iter_fields(node): 159 | """ 160 | Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields`` 161 | that is present on *node*. 162 | """ 163 | if node._fields is None: 164 | return 165 | 166 | for field in node._fields: 167 | try: 168 | yield field, getattr(node, field) 169 | except AttributeError: 170 | pass 171 | 172 | 173 | def iter_child_nodes(node): 174 | """ 175 | Yield all direct child nodes of *node*, that is, all fields that are nodes 176 | and all items of fields that are lists of nodes. 177 | """ 178 | for name, field in iter_fields(node): 179 | if isinstance(field, AST): 180 | yield field 181 | elif isinstance(field, list): 182 | for item in field: 183 | if isinstance(item, AST): 184 | yield item 185 | 186 | 187 | def get_docstring(node, clean=True): 188 | """ 189 | Return the docstring for the given node or None if no docstring can 190 | be found. If the node provided does not have docstrings a TypeError 191 | will be raised. 192 | """ 193 | if not isinstance(node, (FunctionDef, ClassDef, Module)): 194 | raise TypeError("%r can't have docstrings" % node.__class__.__name__) 195 | if node.body and isinstance(node.body[0], Expr) and \ 196 | isinstance(node.body[0].value, Str): 197 | if clean: 198 | import inspect 199 | return inspect.cleandoc(node.body[0].value.s) 200 | return node.body[0].value.s 201 | 202 | 203 | def walk(node): 204 | """ 205 | Recursively yield all child nodes of *node*, in no specified order. This is 206 | useful if you only want to modify nodes in place and don't care about the 207 | context. 208 | """ 209 | from collections import deque 210 | todo = deque([node]) 211 | while todo: 212 | node = todo.popleft() 213 | todo.extend(iter_child_nodes(node)) 214 | yield node 215 | 216 | 217 | class NodeVisitor(object): 218 | """ 219 | A node visitor base class that walks the abstract syntax tree and calls a 220 | visitor function for every node found. This function may return a value 221 | which is forwarded by the `visit` method. 222 | 223 | This class is meant to be subclassed, with the subclass adding visitor 224 | methods. 225 | 226 | Per default the visitor functions for the nodes are ``'visit_'`` + 227 | class name of the node. So a `TryFinally` node visit function would 228 | be `visit_TryFinally`. This behavior can be changed by overriding 229 | the `visit` method. If no visitor function exists for a node 230 | (return value `None`) the `generic_visit` visitor is used instead. 231 | 232 | Don't use the `NodeVisitor` if you want to apply changes to nodes during 233 | traversing. For this a special visitor exists (`NodeTransformer`) that 234 | allows modifications. 235 | """ 236 | 237 | def visit(self, node): 238 | """Visit a node.""" 239 | method = 'visit_' + node.__class__.__name__ 240 | visitor = getattr(self, method, self.generic_visit) 241 | return visitor(node) 242 | 243 | def generic_visit(self, node): 244 | """Called if no explicit visitor function exists for a node.""" 245 | for field, value in iter_fields(node): 246 | if isinstance(value, list): 247 | for item in value: 248 | if isinstance(item, AST): 249 | self.visit(item) 250 | elif isinstance(value, AST): 251 | self.visit(value) 252 | 253 | 254 | class NodeTransformer(NodeVisitor): 255 | """ 256 | A :class:`NodeVisitor` subclass that walks the abstract syntax tree and 257 | allows modification of nodes. 258 | 259 | The `NodeTransformer` will walk the AST and use the return value of the 260 | visitor methods to replace or remove the old node. If the return value of 261 | the visitor method is ``None``, the node will be removed from its location, 262 | otherwise it is replaced with the return value. The return value may be the 263 | original node in which case no replacement takes place. 264 | 265 | Here is an example transformer that rewrites all occurrences of name lookups 266 | (``foo``) to ``data['foo']``:: 267 | 268 | class RewriteName(NodeTransformer): 269 | 270 | def visit_Name(self, node): 271 | return copy_location(Subscript( 272 | value=Name(id='data', ctx=Load()), 273 | slice=Index(value=Str(s=node.id)), 274 | ctx=node.ctx 275 | ), node) 276 | 277 | Keep in mind that if the node you're operating on has child nodes you must 278 | either transform the child nodes yourself or call the :meth:`generic_visit` 279 | method for the node first. 280 | 281 | For nodes that were part of a collection of statements (that applies to all 282 | statement nodes), the visitor may also return a list of nodes rather than 283 | just a single node. 284 | 285 | Usually you use the transformer like this:: 286 | 287 | node = YourTransformer().visit(node) 288 | """ 289 | 290 | def generic_visit(self, node): 291 | for field, old_value in iter_fields(node): 292 | old_value = getattr(node, field, None) 293 | if isinstance(old_value, list): 294 | new_values = [] 295 | for value in old_value: 296 | if isinstance(value, AST): 297 | value = self.visit(value) 298 | if value is None: 299 | continue 300 | elif not isinstance(value, AST): 301 | new_values.extend(value) 302 | continue 303 | new_values.append(value) 304 | old_value[:] = new_values 305 | elif isinstance(old_value, AST): 306 | new_node = self.visit(old_value) 307 | if new_node is None: 308 | delattr(node, field) 309 | else: 310 | setattr(node, field, new_node) 311 | return node 312 | -------------------------------------------------------------------------------- /pyflakes/checker.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from pyflakes import messages 3 | import __builtin__ 4 | 5 | 6 | allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str) 7 | defined_names = set(('__file__', '__builtins__')) 8 | 9 | class Binding(object): 10 | """ 11 | @ivar used: pair of (L{Scope}, line-number) indicating the scope and 12 | line number that this binding was last used 13 | """ 14 | def __init__(self, name, source): 15 | self.name = name 16 | self.source = source 17 | self.used = False 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | def __repr__(self): 23 | return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, 24 | self.name, 25 | self.source.lineno, 26 | id(self)) 27 | 28 | class UnBinding(Binding): 29 | '''Created by the 'del' operator.''' 30 | 31 | class Importation(Binding): 32 | def __init__(self, name, source): 33 | name = name.split('.')[0] 34 | super(Importation, self).__init__(name, source) 35 | 36 | class Assignment(Binding): 37 | pass 38 | 39 | class FunctionDefinition(Binding): 40 | _property_decorator = False 41 | 42 | 43 | class Scope(dict): 44 | import_starred = False # set to True when import * is found 45 | 46 | def __repr__(self): 47 | return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) 48 | 49 | def __init__(self): 50 | super(Scope, self).__init__() 51 | 52 | class ClassScope(Scope): 53 | pass 54 | 55 | 56 | 57 | class FunctionScope(Scope): 58 | """ 59 | I represent a name scope for a function. 60 | 61 | @ivar globals: Names declared 'global' in this function. 62 | """ 63 | def __init__(self): 64 | super(FunctionScope, self).__init__() 65 | self.globals = {} 66 | 67 | 68 | 69 | class ModuleScope(Scope): 70 | pass 71 | 72 | class Checker(ast.NodeVisitor): 73 | def __init__(self, tree, filename='(none)', builtins = None): 74 | ast.NodeVisitor.__init__(self) 75 | 76 | self.deferred = [] 77 | self.dead_scopes = [] 78 | self.messages = [] 79 | self.filename = filename 80 | self.scope_stack = [ModuleScope()] 81 | self.futures_allowed = True 82 | self.builtins = frozenset(builtins or []) 83 | 84 | self.visit(tree) 85 | for handler, scope in self.deferred: 86 | self.scope_stack = scope 87 | handler() 88 | del self.scope_stack[1:] 89 | self.pop_scope() 90 | self.check_dead_scopes() 91 | 92 | def defer(self, callable): 93 | '''Schedule something to be called after just before completion. 94 | 95 | This is used for handling function bodies, which must be deferred 96 | because code later in the file might modify the global scope. When 97 | `callable` is called, the scope at the time this is called will be 98 | restored, however it will contain any new bindings added to it. 99 | ''' 100 | self.deferred.append( (callable, self.scope_stack[:]) ) 101 | 102 | def check_dead_scopes(self): 103 | # Check for modules that were imported but unused 104 | for scope in self.dead_scopes: 105 | for importation in scope.itervalues(): 106 | if isinstance(importation, Importation) and not importation.used: 107 | self.report(messages.UnusedImport, importation.source.lineno, importation.name) 108 | 109 | def push_function_scope(self): 110 | self.scope_stack.append(FunctionScope()) 111 | 112 | def push_class_scope(self): 113 | self.scope_stack.append(ClassScope()) 114 | 115 | def pop_scope(self): 116 | scope = self.scope_stack.pop() 117 | self.dead_scopes.append(scope) 118 | 119 | @property 120 | def scope(self): 121 | return self.scope_stack[-1] 122 | 123 | def report(self, message_class, *args, **kwargs): 124 | self.messages.append(message_class(self.filename, *args, **kwargs)) 125 | 126 | def visit_Import(self, node): 127 | for name_node in node.names: 128 | # "import bar as foo" -> name=bar, asname=foo 129 | name = name_node.asname or name_node.name 130 | self.add_binding(node, Importation(name, node)) 131 | 132 | def visit_GeneratorExp(self, node): 133 | for generator in node.generators: 134 | self.visit(generator.iter) 135 | self.assign_vars(generator.target) 136 | 137 | for generator in node.generators: 138 | if hasattr(node, 'elt'): 139 | self.visit(node.elt) 140 | 141 | self.visit_nodes(generator.ifs) 142 | 143 | visit_ListComp = visit_GeneratorExp 144 | 145 | def visit_For(self, node): 146 | ''' 147 | Process bindings for loop variables. 148 | ''' 149 | self.visit_nodes(node.iter) 150 | 151 | for var in self.flatten(node.target): 152 | upval = self.scope.get(var.id) 153 | if isinstance(upval, Importation) and upval.used: 154 | self.report(messages.ImportShadowedByLoopVar, 155 | node.lineno, node.col_offset, var.id, upval.source.lineno) 156 | 157 | self.add_binding(var, Assignment(var.id, var)) 158 | 159 | self.visit_nodes(node.body + node.orelse) 160 | 161 | def visit_FunctionDef(self, node): 162 | 163 | try: 164 | decorators = node.decorator_list 165 | except AttributeError: 166 | # Use .decorators for Python 2.5 compatibility 167 | decorators = node.decorators 168 | 169 | self.visit_nodes(decorators) 170 | 171 | # Check for property decorator 172 | func_def = FunctionDefinition(node.name, node) 173 | 174 | for decorator in decorators: 175 | if getattr(decorator, 'attr', None) in ('setter', 'deleter'): 176 | func_def._property_decorator = True 177 | 178 | self.add_binding(node, func_def) 179 | 180 | self.visit_Lambda(node) 181 | 182 | def visit_Lambda(self, node): 183 | self.visit_nodes(node.args.defaults) 184 | 185 | def run_function(): 186 | self.push_function_scope() 187 | 188 | # Check for duplicate arguments 189 | argnames = set() 190 | for arg in self.flatten(node.args.args): 191 | if arg.id in argnames: 192 | self.report(messages.DuplicateArgument, arg.lineno, arg.col_offset, arg.id) 193 | argnames.add(arg.id) 194 | 195 | self.assign_vars(node.args.args, report_redef=False) 196 | if node.args.vararg is not None: 197 | self.add_binding(node, Assignment(node.args.vararg, node), False) 198 | if node.args.kwarg is not None: 199 | self.add_binding(node, Assignment(node.args.kwarg, node), False) 200 | self.visit_nodes(node.body) 201 | self.pop_scope() 202 | 203 | self.defer(run_function) 204 | 205 | def visit_Name(self, node): 206 | ''' 207 | Locate names in locals / function / globals scopes. 208 | ''' 209 | scope, name = self.scope, node.id 210 | 211 | # try local scope 212 | import_starred = scope.import_starred 213 | try: 214 | scope[name].used = (scope, node.lineno, node.col_offset) 215 | except KeyError: 216 | pass 217 | else: 218 | return 219 | 220 | # try enclosing function scopes 221 | for func_scope in self.scope_stack[-2:0:-1]: 222 | import_starred = import_starred or func_scope.import_starred 223 | if not isinstance(func_scope, FunctionScope): 224 | continue 225 | try: 226 | func_scope[name].used = (scope, node.lineno, node.col_offset) 227 | except KeyError: 228 | pass 229 | else: 230 | return 231 | 232 | # try global scope 233 | import_starred = import_starred or self.scope_stack[0].import_starred 234 | try: 235 | self.scope_stack[0][node.id].used = (scope, node.lineno, node.col_offset) 236 | except KeyError: 237 | if not import_starred and not self.is_builtin(name): 238 | self.report(messages.UndefinedName, node.lineno, node.col_offset, name) 239 | 240 | def assign_vars(self, targets, report_redef=True): 241 | scope = self.scope 242 | 243 | for target in self.flatten(targets): 244 | name = target.id 245 | # if the name hasn't already been defined in the current scope 246 | if isinstance(scope, FunctionScope) and name not in scope: 247 | # for each function or module scope above us 248 | for upscope in self.scope_stack[:-1]: 249 | if not isinstance(upscope, (FunctionScope, ModuleScope)): 250 | continue 251 | 252 | upval = upscope.get(name) 253 | # if the name was defined in that scope, and the name has 254 | # been accessed already in the current scope, and hasn't 255 | # been declared global 256 | if upval is not None: 257 | if upval.used and upval.used[0] is scope and name not in scope.globals: 258 | # then it's probably a mistake 259 | self.report(messages.UndefinedLocal, 260 | upval.used[1], upval.used[2], name, upval.source.lineno, upval.source.col_offset) 261 | 262 | self.add_binding(target, Assignment(name, target), report_redef) 263 | 264 | def visit_Assign(self, node): 265 | for target in node.targets: 266 | self.visit_nodes(node.value) 267 | self.assign_vars(node.targets) 268 | 269 | def visit_Delete(self, node): 270 | for target in self.flatten(node.targets): 271 | if isinstance(self.scope, FunctionScope) and target.id in self.scope.globals: 272 | del self.scope.globals[target.id] 273 | else: 274 | self.add_binding(target, UnBinding(target.id, target)) 275 | 276 | def visit_With(self, node): 277 | self.visit(node.context_expr) 278 | 279 | # handle new bindings made by optional "as" part 280 | if node.optional_vars is not None: 281 | self.assign_vars(node.optional_vars) 282 | 283 | self.visit_nodes(node.body) 284 | 285 | def visit_ImportFrom(self, node): 286 | if node.module == '__future__': 287 | if not self.futures_allowed: 288 | self.report(messages.LateFutureImport, node.lineno, node.col_offset, [alias.name for alias in node.names]) 289 | else: 290 | self.futures_allowed = False 291 | 292 | for alias in node.names: 293 | if alias.name == '*': 294 | self.scope.import_starred = True 295 | self.report(messages.ImportStarUsed, node.lineno, node.col_offset, node.module) 296 | continue 297 | name = alias.asname or alias.name 298 | importation = Importation(name, node) 299 | if node.module == '__future__': 300 | importation.used = (self.scope, node.lineno, node.col_offset) 301 | self.add_binding(node, importation) 302 | 303 | def visit_Global(self, node): 304 | ''' 305 | Keep track of global declarations. 306 | ''' 307 | scope = self.scope 308 | if isinstance(scope, FunctionScope): 309 | scope.globals.update(dict.fromkeys(node.names)) 310 | 311 | def visit_ClassDef(self, node): 312 | try: 313 | decorators = node.decorator_list 314 | except AttributeError: 315 | # Use .decorators for Python 2.5 compatibility 316 | decorators = getattr(node, 'decorators', []) 317 | 318 | self.visit_nodes(decorators) 319 | 320 | self.add_binding(node, Assignment(node.name, node)) 321 | self.visit_nodes(node.bases) 322 | 323 | self.push_class_scope() 324 | self.visit_nodes(node.body) 325 | self.pop_scope() 326 | 327 | def visit_excepthandler(self, node): 328 | if node.type is not None: 329 | self.visit(node.type) 330 | if node.name is not None: 331 | self.assign_vars(node.name) 332 | self.visit_nodes(node.body) 333 | 334 | visit_ExceptHandler = visit_excepthandler # in 2.6, this was CamelCased 335 | 336 | def flatten(self, nodes): 337 | if isinstance(nodes, ast.Attribute): 338 | self.visit(nodes) 339 | return [] 340 | elif isinstance(nodes, ast.Subscript): 341 | self.visit(nodes.value) 342 | self.visit(nodes.slice) 343 | return [] 344 | elif isinstance(nodes, ast.Name): 345 | return [nodes] 346 | elif isinstance(nodes, (ast.Tuple, ast.List)): 347 | return self.flatten(nodes.elts) 348 | 349 | flattened_nodes = [] 350 | for node in nodes: 351 | if hasattr(node, 'elts'): 352 | flattened_nodes += self.flatten(node.elts) 353 | elif node is not None: 354 | flattened_nodes += self.flatten(node) 355 | 356 | return flattened_nodes 357 | 358 | def add_binding(self, node, value, report_redef=True): 359 | line, col, scope, name = node.lineno, node.col_offset, self.scope, value.name 360 | 361 | # Check for a redefined function 362 | func = scope.get(name) 363 | if (isinstance(func, FunctionDefinition) and isinstance(value, FunctionDefinition)): 364 | # Property-decorated functions (@x.setter) should have duplicate names 365 | if not value._property_decorator: 366 | self.report(messages.RedefinedFunction, line, name, func.source.lineno) 367 | 368 | # Check for redefining an unused import 369 | if report_redef and not isinstance(scope, ClassScope): 370 | for up_scope in self.scope_stack[::-1]: 371 | upval = up_scope.get(name) 372 | if isinstance(upval, Importation) and not upval.used: 373 | self.report(messages.RedefinedWhileUnused, line, col, name, upval.source.lineno) 374 | 375 | # Check for "del undefined_name" 376 | if isinstance(value, UnBinding): 377 | try: 378 | del scope[name] 379 | except KeyError: 380 | self.report(messages.UndefinedName, line, col, name) 381 | else: 382 | scope[name] = value 383 | 384 | def visit(self, node): 385 | if not isinstance(node, allowed_before_future): 386 | self.futures_allowed = False 387 | 388 | return super(Checker, self).visit(node) 389 | 390 | def visit_nodes(self, nodes): 391 | try: 392 | nodes = list(getattr(nodes, 'elts', nodes)) 393 | except TypeError: 394 | nodes = [nodes] 395 | 396 | for node in nodes: 397 | self.visit(node) 398 | 399 | def is_builtin(self, name): 400 | if hasattr(__builtin__, name): 401 | return True 402 | if name in defined_names: 403 | return True 404 | if name in self.builtins: 405 | return True 406 | 407 | return False 408 | 409 | -------------------------------------------------------------------------------- /pyflakes/messages.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Divmod, Inc. See LICENSE file for details 2 | 3 | class Message(object): 4 | message = '' 5 | message_args = () 6 | 7 | def __init__(self, filename, lineno, col = None, level = 'W', message_args = None): 8 | self.filename = filename 9 | self.lineno = lineno 10 | self.col = col 11 | self.level = level 12 | if message_args: 13 | self.message_args = message_args 14 | 15 | def __str__(self): 16 | if self.col is not None: 17 | return '%s:%s(%d): [%s] %s' % (self.filename, self.lineno, self.col, self.level, self.message % self.message_args) 18 | else: 19 | return '%s:%s: [%s] %s' % (self.filename, self.lineno, self.level, self.message % self.message_args) 20 | 21 | 22 | class UnusedImport(Message): 23 | message = '%r imported but unused' 24 | 25 | def __init__(self, filename, lineno, name): 26 | Message.__init__(self, filename, lineno, message_args=(name,)) 27 | 28 | class RedefinedWhileUnused(Message): 29 | message = 'redefinition of unused %r from line %r' 30 | 31 | def __init__(self, filename, lineno, col, name, orig_lineno): 32 | Message.__init__(self, filename, lineno, message_args=(name, orig_lineno)) 33 | 34 | class ImportShadowedByLoopVar(Message): 35 | message = 'import %r from line %r shadowed by loop variable' 36 | 37 | def __init__(self, filename, lineno, col, name, orig_lineno): 38 | Message.__init__(self, filename, lineno, message_args=(name, orig_lineno)) 39 | 40 | class ImportStarUsed(Message): 41 | message = "'from %s import *' used; unable to detect undefined names" 42 | 43 | def __init__(self, filename, lineno, col, modname): 44 | Message.__init__(self, filename, lineno, col, message_args=(modname,)) 45 | 46 | class UndefinedName(Message): 47 | message = 'undefined name %r' 48 | 49 | def __init__(self, filename, lineno, col, name): 50 | Message.__init__(self, filename, lineno, col, 'E', (name,)) 51 | 52 | class UndefinedLocal(Message): 53 | message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment" 54 | 55 | def __init__(self, filename, lineno, col, name, orig_lineno, orig_col): 56 | Message.__init__(self, filename, lineno, message_args=(name, orig_lineno)) 57 | 58 | 59 | class DuplicateArgument(Message): 60 | message = 'duplicate argument %r in function definition' 61 | 62 | def __init__(self, filename, lineno, col, name): 63 | Message.__init__(self, filename, lineno, col, message_args=(name,)) 64 | 65 | 66 | class RedefinedFunction(Message): 67 | message = 'redefinition of function %r from line %r' 68 | 69 | def __init__(self, filename, lineno, name, orig_lineno): 70 | Message.__init__(self, filename, lineno, message_args=(name, orig_lineno)) 71 | 72 | 73 | class LateFutureImport(Message): 74 | message = 'future import(s) %r after other statements' 75 | 76 | def __init__(self, filename, lineno, col, names): 77 | Message.__init__(self, filename, lineno, message_args=(names,)) 78 | -------------------------------------------------------------------------------- /sublimeflakes.py: -------------------------------------------------------------------------------- 1 | import sublime, sublime_plugin 2 | import os, sys, compiler, re 3 | 4 | from pyflakes import checker, messages 5 | 6 | drawType = (sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 7 | 8 | class OffsetError(messages.Message): 9 | message = '%r at offset %r' 10 | def __init__(self, filename, lineno, text, offset): 11 | messages.Message.__init__(self, filename, lineno) 12 | self.offset = offset 13 | self.message_args = (text, offset) 14 | 15 | class PythonError(messages.Message): 16 | message = '%r' 17 | def __init__(self, filename, lineno, text): 18 | messages.Message.__init__(self, filename, lineno) 19 | self.message_args = (text,) 20 | 21 | def check(codeString, filename): 22 | codeString = codeString.rstrip() 23 | try: 24 | try: 25 | compile(codeString, filename, "exec") 26 | except MemoryError: 27 | # Python 2.4 will raise MemoryError if the source can't be 28 | # decoded. 29 | if sys.version_info[:2] == (2, 4): 30 | raise SyntaxError(None) 31 | raise 32 | except (SyntaxError, IndentationError), value: 33 | # print traceback.format_exc() # helps debug new cases 34 | msg = value.args[0] 35 | 36 | lineno, offset, text = value.lineno, value.offset, value.text 37 | 38 | # If there's an encoding problem with the file, the text is None. 39 | if text is None: 40 | # Avoid using msg, since for the only known case, it contains a 41 | # bogus message that claims the encoding the file declared was 42 | # unknown. 43 | if msg.startswith('duplicate argument'): 44 | arg = msg.split('duplicate argument ',1)[1].split(' ',1)[0].strip('\'"') 45 | error = messages.DuplicateArgument(filename, lineno, arg) 46 | else: 47 | error = PythonError(filename, lineno, msg) 48 | else: 49 | line = text.splitlines()[-1] 50 | 51 | if offset is not None: 52 | offset = offset - (len(text) - len(line)) 53 | 54 | if offset is not None: 55 | error = OffsetError(filename, lineno, msg, offset) 56 | else: 57 | error = PythonError(filename, lineno, msg) 58 | 59 | return [error] 60 | else: 61 | # Okay, it's syntactically valid. Now parse it into an ast and check 62 | # it. 63 | ucodeString = unicode(codeString).encode('utf-8') 64 | tree = compiler.parse(ucodeString) 65 | w = checker.Checker(tree, filename) 66 | w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) 67 | return w.messages 68 | 69 | def printf(*args): print '"' + ' '.join(args) + '"' 70 | 71 | global lineMessages 72 | lineMessages = {} 73 | def validate(view): 74 | global lineMessages 75 | vid = view.id() 76 | 77 | text = view.substr(sublime.Region(0, view.size())) 78 | 79 | stripped_lines = [] 80 | good_lines = [] 81 | lines = text.split('\n') 82 | for i in xrange(len(lines)): 83 | line = lines[i] 84 | if not line.strip() or line.strip().startswith('#'): 85 | stripped_lines.append(i) 86 | else: 87 | good_lines.append(line) 88 | 89 | text = '\n'.join(good_lines) 90 | if view.file_name(): filename = os.path.split(view.file_name())[-1] 91 | else: filename = 'untitled' 92 | 93 | errors = check(text, filename) 94 | 95 | lines = set() 96 | underline = [] 97 | 98 | def underlineRange(lineno, position, length=1): 99 | line = view.full_line(view.text_point(lineno, 0)) 100 | position += line.begin() 101 | 102 | for i in xrange(length): 103 | underline.append(sublime.Region(position + i)) 104 | 105 | def underlineRegex(lineno, regex, wordmatch=None, linematch=None): 106 | lines.add(lineno) 107 | offset = 0 108 | 109 | line = view.full_line(view.text_point(lineno, 0)) 110 | lineText = view.substr(line) 111 | if linematch: 112 | match = re.match(linematch, lineText) 113 | if match: 114 | lineText = match.group('match') 115 | offset = match.start('match') 116 | else: 117 | return 118 | 119 | iters = re.finditer(regex, lineText) 120 | results = [(result.start('underline'), result.end('underline')) for result in iters if 121 | not wordmatch or result.group('underline') == wordmatch] 122 | 123 | for start, end in results: 124 | underlineRange(lineno, start+offset, end-start) 125 | 126 | def underlineWord(lineno, word): 127 | regex = '((and|or|not|if|elif|while|in)\s+|[+\-*^%%<>=({[])*\s*(?P[\w]*%s[\w]*)' % (word) 128 | underlineRegex(lineno, regex, word) 129 | 130 | def underlineImport(lineno, word): 131 | linematch = 'import\s+(?P[^#;]+)' 132 | regex = '(\s+|,\s*|as\s+)(?P[\w]*%s[\w]*)' % word 133 | underlineRegex(lineno, regex, word, linematch) 134 | 135 | def underlineForVar(lineno, word): 136 | regex = 'for\s+(?P[\w]*%s[\w*])' % word 137 | underlineRegex(lineno, regex, word) 138 | 139 | def underlineDuplicateArgument(lineno, word): 140 | regex = 'def [\w_]+\(.*?(?P[\w]*%s[\w]*)' % word 141 | underlineRegex(lineno, regex, word) 142 | 143 | errorMessages = {} 144 | def addMessage(lineno, message): 145 | message = str(message) 146 | if lineno in errorMessages: 147 | errorMessages[lineno].append(message) 148 | else: 149 | errorMessages[lineno] = [message] 150 | 151 | view.erase_regions('pyflakes-syntax') 152 | view.erase_regions('pyflakes-syntax-underline') 153 | view.erase_regions('pyflakes-underline') 154 | for error in errors: 155 | error.lineno -= 1 156 | for i in stripped_lines: 157 | if error.lineno >= i: 158 | error.lineno += 1 159 | 160 | lines.add(error.lineno) 161 | addMessage(error.lineno, error) 162 | if isinstance(error, OffsetError): 163 | underlineRange(error.lineno, error.offset) 164 | if len(errors) == 1 and False: 165 | outlines = [view.full_line(view.text_point(error.lineno, 0)) for lineno in lines] 166 | view.add_regions('pyflakes-syntax', outlines, 'Invalid', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 167 | view.add_regions('pyflakes-syntax-underline', underline, 'Invalid', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 168 | return 169 | 170 | elif isinstance(error, PythonError): 171 | if len(errors) == 1 and False: 172 | outlines = [view.full_line(view.text_point(error.lineno, 0)) for lineno in lines] 173 | view.add_regions('pyflakes-syntax', outlines, 'Invalid', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 174 | return 175 | 176 | elif isinstance(error, messages.UnusedImport): 177 | underlineImport(error.lineno, error.name) 178 | 179 | elif isinstance(error, messages.RedefinedWhileUnused): 180 | underlineWord(error.lineno, error.name) 181 | 182 | elif isinstance(error, messages.ImportShadowedByLoopVar): 183 | underlineForVar(error.lineno, error.name) 184 | 185 | elif isinstance(error, messages.ImportStarUsed): 186 | underlineImport(error.lineno, '\*') 187 | 188 | elif isinstance(error, messages.UndefinedName): 189 | underlineWord(error.lineno, error.name) 190 | 191 | elif isinstance(error, messages.UndefinedExport): 192 | underlineWord(error.lineno, error.name) 193 | 194 | elif isinstance(error, messages.UndefinedLocal): 195 | underlineWord(error.lineno, error.name) 196 | 197 | elif isinstance(error, messages.DuplicateArgument): 198 | underlineDuplicateArgument(error.lineno, error.name) 199 | 200 | elif isinstance(error, messages.RedefinedFunction): 201 | underlineWord(error.lineno, error.name) 202 | 203 | elif isinstance(error, messages.LateFutureImport): 204 | pass 205 | 206 | elif isinstance(error, messages.UnusedVariable): 207 | underlineWord(error.lineno, error.name) 208 | 209 | else: 210 | print 'Oops, we missed an error type!' 211 | 212 | view.erase_regions('pyflakes-outlines') 213 | if underline or lines: 214 | outlines = [view.full_line(view.text_point(lineno, 0)) for lineno in lines] 215 | 216 | view.add_regions('pyflakes-underline', underline, 'Invalid', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 217 | view.add_regions('pyflakes-outlines', outlines, 'Invalid', drawType)#sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_OUTLINED) 218 | 219 | lineMessages[vid] = errorMessages 220 | 221 | import time, thread 222 | global queue, lookup 223 | queue = {} 224 | lookup = {} 225 | 226 | def validate_runner(): # this threaded runner keeps it from slowing down UI while you type 227 | global queue, lookup 228 | while True: 229 | time.sleep(0.5) 230 | for vid in dict(queue): 231 | if vid not in queue: 232 | return 233 | if queue[vid] == 0: 234 | validate(lookup[vid]) 235 | try: del queue[vid] 236 | except: pass 237 | try: del lookup[vid] 238 | except: pass 239 | else: 240 | queue[vid] = 0 241 | 242 | def validate_hit(view): 243 | global lookup 244 | global queue 245 | 246 | if not 'Python' in view.settings().get("syntax"): 247 | view.erase_regions('pyflakes-syntax') 248 | view.erase_regions('pyflakes-syntax-underline') 249 | view.erase_regions('pyflakes-underline') 250 | view.erase_regions('pyflakes-outlines') 251 | return 252 | 253 | vid = view.id() 254 | lookup[vid] = view 255 | queue[vid] = 1 256 | 257 | thread.start_new_thread(validate_runner, ()) 258 | 259 | class pyflakes(sublime_plugin.EventListener): 260 | def __init__(self, *args, **kwargs): 261 | sublime_plugin.EventListener.__init__(self, *args, **kwargs) 262 | self.lastCount = {} 263 | 264 | def on_modified(self, view): 265 | validate_hit(view) 266 | return 267 | 268 | # alternate method which works alright when we don't have threads/set_timeout 269 | # from when I ported to early X beta :P 270 | text = view.substr(sublime.Region(0, view.size())) 271 | count = text.count('\n') 272 | if count > 500: return 273 | bid = view.buffer_id() 274 | 275 | if bid in self.lastCount: 276 | if self.lastCount[bid] != count: 277 | validate(view) 278 | 279 | self.lastCount[bid] = count 280 | 281 | def on_load(self, view): 282 | validate_hit(view) 283 | 284 | def on_post_save(self, view): 285 | validate_hit(view) 286 | 287 | def on_selection_modified(self, view): 288 | vid = view.id() 289 | lineno = view.rowcol(view.sel()[0].end())[0] 290 | if vid in lineMessages and lineno in lineMessages[vid]: 291 | view.set_status('pyflakes', '; '.join(lineMessages[vid][lineno])) 292 | else: 293 | view.erase_status('pyflakes') 294 | --------------------------------------------------------------------------------