Tue, 31 Dec 2013 18:03:31 +0100
First implementation for the BackgroundService.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the syntax check for Python 2/3. +""" +from __future__ import unicode_literals + +import re +import sys +import traceback + +from .pyflakes.checker import Checker +from .pyflakes.messages import ImportStarUsed + + +def normalizeCode(codestring): + """ + Function to normalize the given code. + + @param codestring code to be normalized (string) + @return normalized code (string) + """ + codestring = codestring.replace("\r\n", "\n").replace("\r", "\n") + + if codestring and codestring[-1] != '\n': + codestring = codestring + '\n' + + # Check type for py2: if not str it's unicode + if sys.version_info[0] == 2: + try: + codestring = codestring.encode('utf-8') + except UnicodeError: + pass + + return codestring + + +def extractLineFlags(line, startComment="#", endComment=""): + """ + Function to extract flags starting and ending with '__' from a line + comment. + + @param line line to extract flags from (string) + @keyparam startComment string identifying the start of the comment (string) + @keyparam endComment string identifying the end of a comment (string) + @return list containing the extracted flags (list of strings) + """ + flags = [] + + pos = line.rfind(startComment) + if pos >= 0: + comment = line[pos + len(startComment):].strip() + if endComment: + comment = comment.replace("endComment", "") + flags = [f.strip() for f in comment.split() + if (f.startswith("__") and f.endswith("__"))] + return flags + + +def syntaxAndPyflakesCheck(filename, codestring="", checkFlakes=True, + ignoreStarImportWarnings=False): + """ + Function to compile one Python source file to Python bytecode + and to perform a pyflakes check. + + @param filename source filename (string) + @keyparam codestring string containing the code to compile (string) + @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) + @keyparam ignoreStarImportWarnings flag indicating to + ignore 'star import' warnings (boolean) + @return A tuple indicating status (True = an error was found), the + file name, the line number, the index number, the code string + and the error message (boolean, string, string, string, string, + string). If checkFlakes is True, a list of strings containing the + warnings (marker, file name, line number, message) + The values are only valid, if the status is True. + """ + try: + import builtins + except ImportError: + import __builtin__ as builtins #__IGNORE_WARNING__ + + try: + if sys.version_info[0] == 2: + file_enc = filename.encode(sys.getfilesystemencoding()) + else: + file_enc = filename + + # It also encoded the code back to avoid 'Encoding declaration in + # unicode string' exception on Python2 + codestring = normalizeCode(codestring) + + if filename.endswith('.ptl'): + try: + import quixote.ptl_compile + except ImportError: + return (True, filename, 0, 0, '', + 'Quixote plugin not found.', []) + template = quixote.ptl_compile.Template(codestring, file_enc) + template.compile() + + # ast.PyCF_ONLY_AST = 1024, speed optimisation + module = builtins.compile(codestring, file_enc, 'exec', 1024) + except SyntaxError as detail: + index = 0 + code = "" + error = "" + lines = traceback.format_exception_only(SyntaxError, detail) + if sys.version_info[0] == 2: + lines = [x.decode(sys.getfilesystemencoding()) for x in lines] + match = re.match('\s*File "(.+)", line (\d+)', + lines[0].replace('<string>', '{0}'.format(filename))) + if match is not None: + fn, line = match.group(1, 2) + if lines[1].startswith('SyntaxError:'): + error = re.match('SyntaxError: (.+)', lines[1]).group(1) + else: + code = re.match('(.+)', lines[1]).group(1) + for seLine in lines[2:]: + if seLine.startswith('SyntaxError:'): + error = re.match('SyntaxError: (.+)', seLine).group(1) + elif seLine.rstrip().endswith('^'): + index = len(seLine.rstrip()) - 4 + else: + fn = detail.filename + line = detail.lineno or 1 + error = detail.msg + return (True, fn, int(line), index, code, error, []) + except ValueError as detail: + index = 0 + code = "" + try: + fn = detail.filename + line = detail.lineno + error = detail.msg + except AttributeError: + fn = filename + line = 1 + error = str(detail) + return (True, fn, line, index, code, error, []) + except Exception as detail: + try: + fn = detail.filename + line = detail.lineno + index = 0 + code = "" + error = detail.msg + return (True, fn, line, index, code, error, []) + except: # this catchall is intentional + pass + + # pyflakes + if not checkFlakes: + return (False, "", -1, -1, "", "", []) + + strings = [] + lines = codestring.splitlines() + try: + warnings = Checker(module, filename) + warnings.messages.sort(key=lambda a: a.lineno) + for warning in warnings.messages: + if ignoreStarImportWarnings and \ + isinstance(warning, ImportStarUsed): + continue + + _fn, lineno, message, msg_args = warning.getMessageData() + if "__IGNORE_WARNING__" not in extractLineFlags( + lines[lineno - 1].strip()): + strings.append([ + "FLAKES_WARNING", _fn, lineno, message, msg_args]) + except SyntaxError as err: + if err.text.strip(): + msg = err.text.strip() + else: + msg = err.msg + strings.append(["FLAKES_ERROR", filename, err.lineno, msg, ()]) + + return (False, "", -1, -1, "", "", strings)
--- a/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Sun Dec 15 11:50:18 2013 +0100 +++ b/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py Tue Dec 31 18:03:31 2013 +0100 @@ -66,6 +66,9 @@ self.checkProgressLabel.setVisible(False) self.checkProgressLabel.setMaximumWidth(600) + self.backgroundService = e5App().getObject('BackgroundService') + self.backgroundService.syntaxChecked.connect(self.processResult) + def __resort(self): """ Private method to resort the tree. @@ -135,7 +138,7 @@ @param fn file or list of files or directory to be checked (string or list of strings) @param codestring string containing the code to be checked (string). - If this is given, file must be a single file name. + If this is given, fn must be a single file name. """ if self.__project is None: self.__project = e5App().getObject("Project") @@ -150,88 +153,127 @@ self.__clearErrors() if isinstance(fn, list): - files = fn + self.files = fn elif os.path.isdir(fn): - files = [] + self.files = [] extensions = set(Preferences.getPython("PythonExtensions") + Preferences.getPython("Python3Extensions")) for ext in extensions: - files.extend( + self.files.extend( Utilities.direntries(fn, True, '*{0}'.format(ext), 0)) else: - files = [fn] + self.files = [fn] - if codestring or len(files) > 0: - self.checkProgress.setMaximum(max(1, len(files))) - self.checkProgress.setVisible(len(files) > 1) - self.checkProgressLabel.setVisible(len(files) > 1) + if codestring or len(self.files) > 0: + self.checkProgress.setMaximum(max(1, len(self.files))) + self.checkProgress.setVisible(len(self.files) > 1) + self.checkProgressLabel.setVisible(len(self.files) > 1) QApplication.processEvents() + + self.checkFlakes = Preferences.getFlakes("IncludeInSyntaxCheck") + self.ignoreStarImportWarnings = Preferences.getFlakes( + "IgnoreStarImportWarnings") # now go through all the files - progress = 0 - for file in files: - self.checkProgress.setValue(progress) - self.checkProgressLabel.setPath(file) - QApplication.processEvents() - self.__resort() - - if self.cancelled: - return - - self.__lastFileItem = None - - if codestring: - source = codestring - else: - try: - source = Utilities.readEncodedFile(file)[0] - source = Utilities.normalizeCode(source) - except (UnicodeError, IOError) as msg: - self.noResults = False - self.__createResultItem( - file, 1, 0, - self.trUtf8("Error: {0}").format(str(msg)) - .rstrip()[1:-1], "") - progress += 1 - continue + self.progress = 0 + self.check(codestring) + + def check(self, codestring=''): + """ + Start a check for one file. + + The results are reported to the processResult slot. + @param codestring optional sourcestring (str) + """ + self.filename = self.files.pop(0) + self.checkProgress.setValue(self.progress) + self.checkProgressLabel.setPath(self.filename) + QApplication.processEvents() + self.__resort() + + if self.cancelled: # ??? + return + + self.__lastFileItem = None + + if codestring: + self.source = codestring + else: + try: + self.source = Utilities.readEncodedFile(self.filename)[0] + self.source = Utilities.normalizeCode(self.source) + except (UnicodeError, IOError) as msg: + self.noResults = False + self.__createResultItem( + self.filename, 1, 0, + self.trUtf8("Error: {0}").format(str(msg)) + .rstrip()[1:-1], "") + self.progress += 1 + # Continue with next file + self.check() + return + + self.backgroundService.syntaxCheck( + self.filename, self.source, self.checkFlakes, + self.ignoreStarImportWarnings) + + def processResult( + self, fn, nok, fname, line, index, code, error, warnings): + """ + Slot which reports the resulting messages. + + If checkFlakes is True, warnings contains a list of strings containing + the warnings (marker, file name, line number, message) + The values are only valid, if nok is False. + + @param fn filename of the checked file (str) + @param nok flag if an error in the source was found (boolean) + @param fname filename of the checked file (str) # TODO: remove dubl. + @param line number where the error occured (int) + @param index the column where the error occured (int) + @param code the part of the code where the error occured (str) + @param error the name of the error (str) + @param warnings a list of strings containing the warnings + (marker, file name, line number, message) + """ + # Check if it's the requested file, otherwise ignore signal + if fn != self.filename: + return + + if nok: + self.noResults = False + self.__createResultItem( + fname, line, index, error, code.strip(), False) + else: + source = self.source.splitlines() + for warning in warnings: + # TODO: Move to BackgroundService + # Translate messages + msg_args = warning.pop() + translated = QApplication.translate( + 'py3Flakes', warning[-1]).format(*msg_args) + # Avoid leading "u" at Python2 unicode strings + if translated.startswith("u'"): + translated = translated[1:] + warning[3] = translated.replace(" u'", " '") - flags = Utilities.extractFlags(source) - ext = os.path.splitext(file)[1] - if "FileType" in flags: - isPy2 = flags["FileType"] in ["Python", "Python2"] - elif (Preferences.getProject("DeterminePyFromProject") and - self.__project.isOpen() and - self.__project.isProjectFile(file)): - isPy2 = self.__project.getProjectLanguage() in \ - ["Python", "Python2"] - else: - isPy2 = flags.get("FileType") in ["Python", "Python2"] or \ - ext in Preferences.getPython("PythonExtensions") - - nok, fname, line, index, code, error, warnings = \ - Utilities.compile(file, source, isPy2) - if nok: - self.noResults = False - self.__createResultItem( - fname, line, index, error, code.strip(), False) - else: - source = source.splitlines() - for warning in warnings: - self.noResults = False - scr_line = source[warning[2] - 1].strip() - self.__createResultItem( - warning[1], warning[2], 0, - warning[3], scr_line, True) + self.noResults = False + scr_line = source[warning[2] - 1].strip() + self.__createResultItem( + warning[1], warning[2], 0, + warning[3], scr_line, True) + self.progress += 1 + self.checkProgress.setValue(self.progress) + self.checkProgressLabel.setPath("") + QApplication.processEvents() + self.__resort() - progress += 1 - self.checkProgress.setValue(progress) - self.checkProgressLabel.setPath("") - QApplication.processEvents() - self.__resort() + if self.files: + self.check() else: self.checkProgress.setMaximum(1) self.checkProgress.setValue(1) - self.__finish() + self.__finish() def __finish(self): """
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package containg the pyflakes Python2 port adapted for Qt. +""" + +""" License +Copyright 2005-2011 Divmod, Inc. +Copyright 2013 Florent Xicluna + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +""" Changes +0.6.1 (2013-01-29): + - Fix detection of variables in augmented assignments. + +0.6.0 (2013-01-29): + - Support Python 3 up to 3.3, based on the pyflakes3k project. + - Preserve compatibility with Python 2.5 and all recent versions of Python. + - Support custom reporters in addition to the default Reporter. + - Allow function redefinition for modern property construction via + property.setter/deleter. + - Fix spurious redefinition warnings in conditionals. + - Do not report undefined name in __all__ if import * is used. + - Add WindowsError as a known built-in name on all platforms. + - Support specifying additional built-ins in the `Checker` constructor. + - Don't issue Unused Variable warning when using locals() in current scope. + - Handle problems with the encoding of source files. + - Remove dependency on Twisted for the tests. + - Support `python setup.py test` and `python setup.py develop`. + - Create script using setuptools `entry_points` to support all platforms, + including Windows. + +0.5.0 (2011-09-02): + - Convert pyflakes to use newer _ast infrastructure rather than compiler. + - Support for new syntax in 2.7 (including set literals, set comprehensions, + and dictionary comprehensions). + - Make sure class names don't get bound until after class definition. +""" + +__version__ = '0.6.1'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,711 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# +# Original (c) 2005-2010 Divmod, Inc. +# +# This module is based on pyflakes for Python2 but was modified to +# work with eric5 + +import os.path +try: + import builtins + PY2 = False +except ImportError: + import __builtin__ as builtins #__IGNORE_WARNING__ + PY2 = True + +try: + import ast + iter_child_nodes = ast.iter_child_nodes +except (ImportError, AttributeError): # Python 2.5 + import _ast as ast + + def iter_child_nodes(node, astcls=ast.AST): + """ + 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 in node._fields: + field = getattr(node, name, None) + if isinstance(field, astcls): + yield field + elif isinstance(field, list): + for item in field: + yield item +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if hasattr(ast, 'Try'): + ast_TryExcept = ast.Try + ast_TryFinally = () +else: + ast_TryExcept = ast.TryExcept + ast_TryFinally = ast.TryFinally + +from . import messages + + +class Binding(object): + """ + Represents the binding of a value to a name. + + The checker uses this to keep track of which names have been bound and + which names have not. See L{Assignment} for a special type of binding that + is checked with stricter rules. + """ + + 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): + """ + A binding created by an import statement. + """ + def __init__(self, name, source): + self.fullName = name + name = name.split('.')[0] + super(Importation, self).__init__(name, source) + + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + + +class Assignment(Binding): + """ + Represents binding a name with an explicit assignment. + + The checker will raise warnings for any Assignment that isn't used. Also, + the checker does not consider assignments in tuple/list unpacking to be + Assignments, rather it treats them as simple Bindings. + """ + + +class FunctionDefinition(Definition): + pass + + +class ClassDefinition(Definition): + pass + + +class ExportBinding(Binding): + """ + A binding created by an C{__all__} assignment. If the names in the list + can be determined statically, they will be treated as names for export and + additional checking applied to them. + + The only C{__all__} assignment that can be recognized is one which takes + the value of a literal list containing literal strings. For example:: + + __all__ = ["foo", "bar"] + + Names which are imported and not otherwise used but appear in the value of + C{__all__} will not have an unused import warning reported for them. + """ + def names(self): + """ + Return a list of the names referenced by this binding. + """ + names = [] + if isinstance(self.source, ast.List): + for node in self.source.elts: + if isinstance(node, ast.Str): + names.append(node.s) + return names + + +class Scope(dict): + importStarred = False # set to True when import * is found + usesLocals = False + + def __repr__(self): + return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) + + +class ClassScope(Scope): + pass + + +class FunctionScope(Scope): + """ + I represent a name scope for a function. + """ + def __init__(self): + super(FunctionScope, self).__init__() + self.globals = {} + + +class ModuleScope(Scope): + pass + + +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] + + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # a ExceptHandler node + return node.name + + +class Checker(object): + """ + I check the cleanliness and sanity of Python code. + """ + + nodeDepth = 0 + traceTree = False + builtIns = set(dir(builtins)) | set(_MAGIC_GLOBALS) + + def __init__(self, tree, filename='(none)', builtins=None): + self._deferredFunctions = [] + self._deferredAssignments = [] + self.deadScopes = [] + self.messages = [] + self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) + self.scopeStack = [ModuleScope()] + self.futuresAllowed = True + self.root = tree + self.handleChildren(tree) + self.runDeferred(self._deferredFunctions) + # Set _deferredFunctions to None so that deferFunction will fail + # noisily if called after we've run through the deferred functions. + self._deferredFunctions = None + self.runDeferred(self._deferredAssignments) + # Set _deferredAssignments to None so that deferAssignment will fail + # noisily if called after we've run through the deferred assignments. + self._deferredAssignments = None + del self.scopeStack[1:] + self.popScope() + self.checkDeadScopes() + + def deferFunction(self, callable): + """ + Schedule a function handler to be called 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._deferredFunctions.append((callable, self.scopeStack[:])) + + def deferAssignment(self, callable): + """ + Schedule an assignment handler to be called just after deferred + function handlers. + """ + self._deferredAssignments.append((callable, self.scopeStack[:])) + + def runDeferred(self, deferred): + """ + Run the callables in C{deferred} using their associated scope stack. + """ + for handler, scope in deferred: + self.scopeStack = scope + handler() + + @property + def scope(self): + return self.scopeStack[-1] + + def popScope(self): + self.deadScopes.append(self.scopeStack.pop()) + + def checkDeadScopes(self): + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ + for scope in self.deadScopes: + export = isinstance(scope.get('__all__'), ExportBinding) + if export: + all = scope['__all__'].names() + if not scope.importStarred and os.path.basename(self.filename) != '__init__.py': + # Look for possible mistakes in the export list + undefined = set(all) - set(scope) + for name in undefined: + self.report(messages.UndefinedExport, + scope['__all__'].source.lineno, name) + else: + all = [] + + # Look for imported names that aren't used. + for importation in scope.values(): + if isinstance(importation, Importation): + if not importation.used and importation.name not in all: + self.report(messages.UnusedImport, + importation.source.lineno, importation.name) + + def pushFunctionScope(self): + self.scopeStack.append(FunctionScope()) + + def pushClassScope(self): + self.scopeStack.append(ClassScope()) + + def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) + + def hasParent(self, node, kind): + while hasattr(node, 'parent'): + node = node.parent + if isinstance(node, kind): + return True + + def getCommonAncestor(self, lnode, rnode, stop=None): + if not stop: + stop = self.root + if lnode is rnode: + return lnode + if stop in (lnode, rnode): + return stop + + if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'): + return + if (lnode.level > rnode.level): + return self.getCommonAncestor(lnode.parent, rnode, stop) + if (rnode.level > lnode.level): + return self.getCommonAncestor(lnode, rnode.parent, stop) + return self.getCommonAncestor(lnode.parent, rnode.parent, stop) + + def descendantOf(self, node, ancestors, stop=None): + for a in ancestors: + if self.getCommonAncestor(node, a, stop) not in (stop, None): + return True + return False + + def onFork(self, parent, lnode, rnode, items): + return (self.descendantOf(lnode, items, parent) ^ + self.descendantOf(rnode, items, parent)) + + def differentForks(self, lnode, rnode): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode) + if isinstance(ancestor, ast.If): + for fork in (ancestor.body, ancestor.orelse): + if self.onFork(ancestor, lnode, rnode, fork): + return True + elif isinstance(ancestor, ast_TryExcept): + body = ancestor.body + ancestor.orelse + for fork in [body] + [[hdl] for hdl in ancestor.handlers]: + if self.onFork(ancestor, lnode, rnode, fork): + return True + elif isinstance(ancestor, ast_TryFinally): + if self.onFork(ancestor, lnode, rnode, ancestor.body): + return True + return False + + def addBinding(self, node, value, reportRedef=True): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the optional new value, a Binding instance, associated + with the binding; if None, the binding is deleted if it exists. + - if `reportRedef` is True (default), rebinding while unused will be + reported. + """ + redefinedWhileUnused = False + if not isinstance(self.scope, ClassScope): + for scope in self.scopeStack[::-1]: + existing = scope.get(value.name) + if (isinstance(existing, Importation) + and not existing.used + and (not isinstance(value, Importation) or value.fullName == existing.fullName) + and reportRedef + and not self.differentForks(node, existing.source)): + redefinedWhileUnused = True + self.report(messages.RedefinedWhileUnused, + node.lineno, value.name, existing.source.lineno) + + existing = self.scope.get(value.name) + if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp): + if (existing and reportRedef + and not self.hasParent(existing.source, (ast.For, ast.ListComp))): + self.report(messages.RedefinedInListComp, + node.lineno, value.name, existing.source.lineno) + + if isinstance(value, UnBinding): + try: + del self.scope[value.name] + except KeyError: + self.report(messages.UndefinedName, node.lineno, value.name) + elif (isinstance(existing, Definition) + and not existing.used + and not self.differentForks(node, existing.source)): + self.report(messages.RedefinedWhileUnused, + node.lineno, value.name, existing.source.lineno) + else: + self.scope[value.name] = value + + def handleNodeLoad(self, node): + name = getNodeName(node) + if not name: + return + # try local scope + importStarred = self.scope.importStarred + try: + self.scope[name].used = (self.scope, node.lineno) + except KeyError: + pass + else: + return + + # try enclosing function scopes + for scope in self.scopeStack[-2:0:-1]: + importStarred = importStarred or scope.importStarred + if not isinstance(scope, FunctionScope): + continue + try: + scope[name].used = (self.scope, node.lineno) + except KeyError: + pass + else: + return + + # try global scope + importStarred = importStarred or self.scopeStack[0].importStarred + try: + self.scopeStack[0][name].used = (self.scope, node.lineno) + except KeyError: + if not importStarred and name not in self.builtIns: + if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'): + # the special name __path__ is valid only in packages + pass + else: + self.report(messages.UndefinedName, node.lineno, name) + + def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name not in self.scope: + # for each function or module scope above us + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): + continue + # 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 (name in scope and scope[name].used and scope[name].used[0] is self.scope + and name not in self.scope.globals): + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source.lineno) + break + + parent = getattr(node, 'parent', None) + if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): + binding = Binding(name, node) + elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, parent.value) + else: + binding = Assignment(name, node) + if name in self.scope: + binding.used = self.scope[name].used + self.addBinding(node, binding) + + def handleNodeDelete(self, node): + name = getNodeName(node) + if not name: + return + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + del self.scope.globals[name] + else: + self.addBinding(node, UnBinding(name, node)) + + def handleChildren(self, tree): + for node in iter_child_nodes(tree): + self.handleNode(node, tree) + + def isDocstring(self, node): + """ + Determine if the given node is a docstring, as long as it is at the + correct place in the node tree. + """ + return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and + isinstance(node.value, ast.Str)) + + def handleNode(self, node, parent): + if node is None: + return + node.parent = parent + if self.traceTree: + print(' ' * self.nodeDepth + node.__class__.__name__) + self.nodeDepth += 1 + if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or + self.isDocstring(node)): + self.futuresAllowed = False + nodeType = node.__class__.__name__.upper() + node.level = self.nodeDepth + try: + handler = getattr(self, nodeType) + handler(node) + finally: + self.nodeDepth -= 1 + if self.traceTree: + print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) + + def ignore(self, node): + pass + + # "stmt" type nodes + RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ + TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren + + CONTINUE = BREAK = PASS = ignore + + # "expr" type nodes + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \ + COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \ + STARRED = handleChildren + + NUM = STR = BYTES = ELLIPSIS = ignore + + # "slice" type nodes + SLICE = EXTSLICE = INDEX = handleChildren + + # expression contexts are node instances too, though being constants + LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore + + # same for operators + AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore + + # additional node types + COMPREHENSION = KEYWORD = handleChildren + + def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + if isinstance(self.scope, FunctionScope): + self.scope.globals.update(dict.fromkeys(node.names)) + + NONLOCAL = GLOBAL + + def LISTCOMP(self, node): + # handle generators before element + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.elt, node) + + GENERATOREXP = SETCOMP = LISTCOMP + + def DICTCOMP(self, node): + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.key, node) + self.handleNode(node.value, node) + + def FOR(self, node): + """ + Process bindings for loop variables. + """ + vars = [] + + def collectLoopVars(n): + if isinstance(n, ast.Name): + vars.append(n.id) + elif isinstance(n, ast.expr_context): + return + else: + for c in iter_child_nodes(n): + collectLoopVars(c) + + collectLoopVars(node.target) + for varn in vars: + if (isinstance(self.scope.get(varn), Importation) + # unused ones will get an unused import warning + and self.scope[varn].used): + self.report(messages.ImportShadowedByLoopVar, + node.lineno, varn, self.scope[varn].source.lineno) + + self.handleChildren(node) + + def NAME(self, node): + """ + Handle occurrence of Name (which can be a load/store/delete access.) + """ + if node.id == 'locals' and isinstance(node.parent, ast.Call): + # we are doing locals() call in current scope + self.scope.usesLocals = True + # Locate the name in locals / function / globals scopes. + if isinstance(node.ctx, (ast.Load, ast.AugLoad)): + self.handleNodeLoad(node) + elif isinstance(node.ctx, (ast.Store, ast.AugStore)): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(node) + else: + # must be a Param context -- this only happens for names in function + # arguments, but these aren't dispatched through here + raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) + + def FUNCTIONDEF(self, node): + if not hasattr(node, 'decorator_list'): # Python 2.5 + node.decorator_list = node.decorators + for deco in node.decorator_list: + self.handleNode(deco, node) + self.addBinding(node, FunctionDefinition(node.name, node)) + self.LAMBDA(node) + + def LAMBDA(self, node): + args = [] + + if PY2: + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, ast.Tuple): + addArgs(arg.elts) + else: + if arg.id in args: + self.report(messages.DuplicateArgument, + node.lineno, arg.id) + args.append(arg.id) + addArgs(node.args.args) + defaults = node.args.defaults + else: + for arg in node.args.args + node.args.kwonlyargs: + if arg.arg in args: + self.report(messages.DuplicateArgument, + node.lineno, arg.arg) + args.append(arg.arg) + self.handleNode(arg.annotation, node) + if hasattr(node, 'returns'): # Only for FunctionDefs + for annotation in (node.args.varargannotation, + node.args.kwargannotation, node.returns): + self.handleNode(annotation, node) + defaults = node.args.defaults + node.args.kw_defaults + + # vararg/kwarg identifiers are not Name nodes + for wildcard in (node.args.vararg, node.args.kwarg): + if not wildcard: + continue + if wildcard in args: + self.report(messages.DuplicateArgument, node.lineno, wildcard) + args.append(wildcard) + for default in defaults: + self.handleNode(default, node) + + def runFunction(): + + self.pushFunctionScope() + for name in args: + self.addBinding(node, Argument(name, node), reportRedef=False) + if isinstance(node.body, list): + # case for FunctionDefs + for stmt in node.body: + self.handleNode(stmt, node) + else: + # case for Lambdas + self.handleNode(node.body, node) + + def checkUnusedAssignments(): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.items(): + if (not binding.used and name not in self.scope.globals + and not self.scope.usesLocals + and isinstance(binding, Assignment)): + self.report(messages.UnusedVariable, + binding.source.lineno, name) + self.deferAssignment(checkUnusedAssignments) + self.popScope() + + self.deferFunction(runFunction) + + def CLASSDEF(self, node): + """ + Check names used in a class definition, including its decorators, base + classes, and the body of its definition. Additionally, add its name to + the current scope. + """ + # no class decorator in Python 2.5 + for deco in getattr(node, 'decorator_list', ''): + self.handleNode(deco, node) + for baseNode in node.bases: + self.handleNode(baseNode, node) + if not PY2: + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) + self.pushClassScope() + for stmt in node.body: + self.handleNode(stmt, node) + self.popScope() + self.addBinding(node, ClassDefinition(node.name, node)) + + def ASSIGN(self, node): + self.handleNode(node.value, node) + for target in node.targets: + self.handleNode(target, node) + + def AUGASSIGN(self, node): + self.handleNodeLoad(node.target) + self.handleNode(node.value, node) + self.handleNode(node.target, node) + + def IMPORT(self, node): + for alias in node.names: + name = alias.asname or alias.name + importation = Importation(name, node) + self.addBinding(node, importation) + + def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, + node.lineno, [n.name for n in node.names]) + else: + self.futuresAllowed = False + + for alias in node.names: + if alias.name == '*': + self.scope.importStarred = True + self.report(messages.ImportStarUsed, node.lineno, node.module) + continue + name = alias.asname or alias.name + importation = Importation(name, node) + if node.module == '__future__': + importation.used = (self.scope, node.lineno) + self.addBinding(node, importation) + + def EXCEPTHANDLER(self, node): + # 3.x: in addition to handling children, we must handle the name of + # the exception, which is not a Name node, but a simple string. + if isinstance(node.name, str): + self.handleNodeStore(node) + self.handleChildren(node)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# +# Original (c) 2005 Divmod, Inc. See LICENSE file for details +# +# This module is based on pyflakes for Python2 but was heavily hacked to +# work within Eric5 and Qt (translatable messages) + +""" +Module implementing the messages for pyflakes. +""" + +# Tell 'lupdate' which strings to keep for translation. +QT_TRANSLATE_NOOP = lambda mod, txt: txt + + +class Message(object): + """ + Class defining the base for all specific message classes. + """ + message = '' + message_args = () + + def __init__(self, filename, lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + """ + self.filename = filename + self.lineno = lineno + + def __str__(self): + """ + Special method return a string representation of the instance object. + + @return string representation of the object (string) + """ + return '%s:%s: %s' % ( + self.filename, self.lineno, self.message % self.message_args) + + def getMessageData(self): + """ + Public method to get the individual message data elements. + + @return tuple containing file name, line number and message + (string, integer, string) + """ + return (self.filename, self.lineno, self.message, self.message_args) + + +class UnusedImport(Message): + """ + Class defining the "Unused Import" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + '{0!r} imported but unused.') + + def __init__(self, filename, lineno, name): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the unused import (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name,) + + +class RedefinedWhileUnused(Message): + """ + Class defining the "Redefined While Unused" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Redefinition of unused {0!r} from line {1!r}.') + + def __init__(self, filename, lineno, name, orig_lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the redefined object (string) + @param orig_lineno line number of the original definition (integer) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + +class RedefinedInListComp(Message): + """ + Class defining the list comprehension redefinition. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'List comprehension redefines {0!r} from line {1!r}.') + + def __init__(self, filename, lineno, name, orig_lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the redefined object (string) + @param orig_lineno line number of the original definition (integer) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + +class ImportShadowedByLoopVar(Message): + """ + Class defining the "Import Shadowed By Loop Var" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Import {0!r} from line {1!r} shadowed by loop variable.') + + def __init__(self, filename, lineno, name, orig_lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the shadowed import (string) + @param orig_lineno line number of the import (integer) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + +class ImportStarUsed(Message): + """ + Class defining the "Import Star Used" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + "'from {0} import *' used; unable to detect undefined names.") + + def __init__(self, filename, lineno, modname): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param modname name of the module imported using star import (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (modname,) + + +class UndefinedName(Message): + """ + Class defining the "Undefined Name" message. + """ + message = QT_TRANSLATE_NOOP('py3Flakes', 'Undefined name {0!r}.') + + def __init__(self, filename, lineno, name): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name undefined name (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name,) + + +class UndefinedExport(Message): + """ + Class defining the "Undefined Export" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Undefined name {0!r} in __all__.') + + def __init__(self, filename, lineno, name): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name undefined exported name (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name,) + + +class UndefinedLocal(Message): + """ + Class defining the "Undefined Local Variable" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + "Local variable {0!r} (defined in enclosing scope on line {1!r})" + " referenced before assignment.") + + def __init__(self, filename, lineno, name, orig_lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the prematurely referenced variable (string) + @param orig_lineno line number of the variable definition (integer) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + +class DuplicateArgument(Message): + """ + Class defining the "Duplicate Argument" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Duplicate argument {0!r} in function definition.') + + def __init__(self, filename, lineno, name): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the duplicate argument (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name,) + + +class Redefined(Message): + """ + Class defining the "Redefined" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Redefinition of {0!r} from line {1!r}.') + + def __init__(self, filename, lineno, name, orig_lineno): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param name name of the redefined function (string) + @param orig_lineno line number of the original definition (integer) + """ + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + +class LateFutureImport(Message): + """ + Class defining the "Late Future Import" message. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Future import(s) {0!r} after other statements.') + + def __init__(self, filename, lineno, names): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param names names of the imported futures (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (names,) + + +class UnusedVariable(Message): + """ + Class defining the "Unused Variable" message. + + Indicates that a variable has been explicitly assigned to but not actually + used. + """ + message = QT_TRANSLATE_NOOP( + 'py3Flakes', + 'Local variable {0!r} is assigned to but never used.') + + def __init__(self, filename, lineno, names): + """ + Constructor + + @param filename name of the file (string) + @param lineno line number (integer) + @param names names of the unused variable (string) + """ + Message.__init__(self, filename, lineno) + self.message_args = (names,)
--- a/UI/UserInterface.py Sun Dec 15 11:50:18 2013 +0100 +++ b/UI/UserInterface.py Tue Dec 31 18:03:31 2013 +0100 @@ -202,6 +202,10 @@ from Debugger.DebugServer import DebugServer debugServer = DebugServer() + # Create the background service object + from Utilities.BackgroundService import BackgroundService + self.backgroundService = BackgroundService() + # Generate an empty project object and multi project object from Project.Project import Project self.project = Project(self) @@ -450,6 +454,7 @@ e5App().registerObject("UserInterface", self) e5App().registerObject("DebugUI", self.debuggerUI) e5App().registerObject("DebugServer", debugServer) + e5App().registerObject("BackgroundService", self.backgroundService) e5App().registerObject("ViewManager", self.viewmanager) e5App().registerObject("Project", self.project) e5App().registerObject("ProjectBrowser", self.projectBrowser) @@ -5741,6 +5746,8 @@ return False self.debuggerUI.shutdown() + self.backgroundService.shutdown() + self.cooperation.shutdown() self.pluginManager.doShutdown()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Utilities/BackgroundClient.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a Qt free version of a background client for the various +checkers and other python interpreter dependent functions. +""" + +from __future__ import unicode_literals +try: + bytes = unicode #__IGNORE_WARNING__ +except NameError: + pass + +import json +import os +import socket +import struct +import sys +from zlib import adler32 + +if __name__ == '__main__': + # Add Eric basepath to sys.path to be able to import modules which are + # laying not only below Utilities + path = os.path.dirname(sys.argv[0]) + path = os.path.dirname(path) + sys.path.append(path) + +from Plugins.CheckerPlugins.SyntaxChecker import SyntaxCheck + + +class BackgroundClient(object): + """ + Class implementing the main part of the background client. + """ + def __init__(self, host, port): + """ + Constructor of the BackgroundClient class. + + @param host ip address the background service is listening + @param port port of the background service + """ + self.connection = socket.create_connection((host, port)) + ver = b'2' if sys.version_info[0] == 2 else b'3' + self.connection.sendall(ver) + self.connection.settimeout(0.25) + + def __send(self, fx, fn, data): + """ + Private method to send a job response back to the BackgroundService. + + @param fx remote function name to execute (str) + @param fn filename for identification (str) + @param data return value(s) (any basic datatype) + """ + packedData = json.dumps([fx, fn, data]) + if sys.version_info[0] == 3: + packedData = bytes(packedData, 'utf-8') + header = struct.pack( + b'!II', len(packedData), adler32(packedData) & 0xffffffff) + self.connection.sendall(header) + self.connection.sendall(packedData) + + def run(self): + """ + Implement the main loop of the client. + """ + while True: + try: + header = self.connection.recv(8) # __IGNORE_EXCEPTION__ + except socket.timeout: + continue + except socket.error: + # Leave main loop if connection was closed. + break + # Leave main loop if connection was closed. + if not header: + break + + length, datahash = struct.unpack(b'!II', header) + + packedData = b'' + while len(packedData) < length: + packedData += self.connection.recv(length - len(packedData)) + + assert adler32(packedData) & 0xffffffff == datahash, \ + 'Hashes not equal' + if sys.version_info[0] == 3: + packedData = packedData.decode('utf-8') + fx, fn, data = json.loads(packedData) + if fx == 'syntax': + ret = SyntaxCheck.syntaxAndPyflakesCheck(fn, *data) + elif fx == 'style': + print(data) + elif fx == 'indent': + pass + else: + continue + + self.__send(fx, fn, ret) + + self.connection.close() + sys.exit() + + def __unhandled_exception(self, exctype, excval, exctb): + """ + Private method called to report an uncaught exception. + + @param exctype the type of the exception + @param excval data about the exception + @param exctb traceback for the exception + """ + # TODO: Wrap arguments so they can be serialized by JSON + self.__send( + 'exception', '?', [str(exctype), str(excval), str(exctb)]) + +if __name__ == '__main__': + if len(sys.argv) != 3: + print('Host and port parameters are missing. Abort.') + sys.exit(1) + + host, port = sys.argv[1:] + backgroundClient = BackgroundClient(host, int(port)) + # set the system exception handling function to ensure, that + # we report on all unhandled exceptions + sys.excepthook = backgroundClient._BackgroundClient__unhandled_exception + # Start the main loop + backgroundClient.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Utilities/BackgroundService.py Tue Dec 31 18:03:31 2013 +0100 @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a background service for the various checkers and other +python interpreter dependent functions. +""" + +from __future__ import unicode_literals + +import json +import os +import struct +import sys +import threading +from zlib import adler32 + +from PyQt4.QtCore import QProcess, pyqtSignal +from PyQt4.QtGui import QApplication +from PyQt4.QtNetwork import QTcpServer, QHostAddress + +from E5Gui.E5Application import e5App + +import Preferences +import Utilities +from Utilities.BackgroundClient import BackgroundClient + +from eric5config import getConfig + + +class BackgroundService(QTcpServer): + """ + Class implementing the main part of the background service. + """ + syntaxChecked = pyqtSignal(str, bool, str, int, int, str, str, list) + #styleChecked = pyqtSignal(TBD) + #indentChecked = pyqtSignal(TBD) + + def __init__(self): + """ + Constructor of the BackgroundService class. + """ + self.processes = [None, None] + self.connections = [None, None] + + super(BackgroundService, self).__init__() + + networkInterface = Preferences.getDebugger("NetworkInterface") + if networkInterface == "all" or '.' in networkInterface: + self.hostAddress = '127.0.0.1' + else: + self.hostAddress = '::1' + self.listen(QHostAddress(self.hostAddress)) + + self.newConnection.connect(self.on_newConnection) + port = self.serverPort() + ## NOTE: Need the port if started external in debugger: + print('BackgroundService listening on: %i' % port) + for pyIdx, pyName in enumerate(['Python', 'Python3']): + interpreter = Preferences.getDebugger( + pyName + "Interpreter") + + if Utilities.samefilepath(interpreter, sys.executable): + process = self.__startInternalClient(port) + else: + process = self.__startExternalClient(interpreter, port) + self.processes[pyIdx] = process + + def on_newConnection(self): + """ + Slot for new incomming connections from the clients. + """ + connection = self.nextPendingConnection() + if not connection.waitForReadyRead(1000): + return + ch = 0 if connection.read(1) == b'2' else 1 + self.connections[ch] = connection + connection.readyRead.connect( + lambda x=ch: self.__receive(x)) + + def shutdown(self): + """ + Cleanup the connections and processes when Eric is shuting down. + """ + for connection in self.connections: + if connection: + connection.close() + + for process in self.processes: + if isinstance(process, QProcess): + process.close() + process = None + elif isinstance(process, threading.Thread): + process.join(0.1) + process = None + + def __startExternalClient(self, interpreter, port): + """ + Private method to start the background client as external process. + + @param interpreter path and name of the executable to start (string) + @param port socket port to which the interpreter should connect (int) + @return the process object (QProcess) or None + """ + if interpreter == "" or not Utilities.isinpath(interpreter): + return None + + backgroundClient = os.path.join( + getConfig('ericDir'), + "Utilities", "BackgroundClient.py") + proc = QProcess() + args = [backgroundClient, self.hostAddress, str(port)] + proc.start(interpreter, args) + if not proc.waitForStarted(10000): + proc = None + return proc + + def __startInternalClient(self, port): + """ + Private method to start the background client as internal thread. + + @param port socket port to which the interpreter should connect (int) + @return the thread object (Thread) or None + """ + self.backgroundClient = BackgroundClient( + self.hostAddress, port) + thread = threading.Thread(target=self.backgroundClient.run) + thread.start() + return thread + + # TODO: Implement a queued processing of incomming events. Dublicate file + # checks should update an older request to avoid overrun or starving of + # the check. + def __send(self, fx, fn, data, isPy3): + """ + Private method to send a job request to one of the clients. + + @param fx remote function name to execute (str) + @param fn filename for identification (str) + @param data function argument(s) (any basic datatype) + @param isPy3 flag for the required interpreter (boolean) + """ + packedData = json.dumps([fx, fn, data]) + if sys.version_info[0] == 3: + packedData = bytes(packedData, 'utf-8') + connection = self.connections[int(isPy3)] + if connection is None: + self.__postResult( + fx, fn, [ + True, fn, 0, 0, '', + 'No connection to Python{0} interpreter. ' + 'Check your debugger settings.'.format(int(isPy3) + 2)]) + else: + header = struct.pack( + b'!II', len(packedData), adler32(packedData) & 0xffffffff) + connection.write(header) + connection.write(packedData) + + def __receive(self, channel): + """ + Private method to receive the response from the clients. + + @param channel of the incomming connection (int: 0 or 1) + """ + connection = self.connections[channel] + header = connection.read(8) + length, datahash = struct.unpack(b'!II', header) + + packedData = b'' + while len(packedData) < length: + connection.waitForReadyRead(50) + packedData += connection.read(length - len(packedData)) + + assert adler32(packedData) & 0xffffffff == datahash, 'Hashes not equal' + if sys.version_info[0] == 3: + packedData = packedData.decode('utf-8') + # "check" if is's a tuple of 3 values + try: + fx, fn, data = json.loads(packedData) + self.__postResult(fx, fn, data) + except: + pass + + def __postResult(self, fx, fn, data): + """ + Private method to emit the correspondig signal for the returned + function. + + @param fx remote function name to execute (str) + @param fn filename for identification (str) + @param data function argument(s) (any basic datatype) + """ + if fx == 'syntax': + self.syntaxChecked.emit(fn, *data) + elif fx == 'style': + pass + elif fx == 'indent': + pass + elif fx == 'exception': + # Call sys.excepthook(type, value, traceback) to emulate the + # exception which was caught on the client + sys.excepthook(*data) + + #QApplication.translate(packedData) + + # ggf. nach Utilities verschieben + def determinePythonVersion(self, filename, source): + """ + Determine the python version of a given file. + + @param filename name of the file with extension (str) + @param source of the file (str) + @return flag if file is Python2 or Python3 (boolean) + """ + flags = Utilities.extractFlags(source) + ext = os.path.splitext(filename)[1] + project = e5App().getObject('Project') + if "FileType" in flags: + isPy3 = flags["FileType"] not in ["Python", "Python2"] + elif (Preferences.getProject("DeterminePyFromProject") and + project.isOpen() and + project.isProjectFile(filename)): + isPy3 = project.getProjectLanguage() == "Python3" + else: + isPy3 = ext in Preferences.getPython("PythonExtensions") + return isPy3 + + def syntaxCheck(self, filename, source="", checkFlakes=True, + ignoreStarImportWarnings=False, isPy3=None): + """ + Function to compile one Python source file to Python bytecode + and to perform a pyflakes check. + + @param filename source filename (string) + @keyparam source string containing the code to check (string) + @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) + @keyparam ignoreStarImportWarnings flag indicating to + ignore 'star import' warnings (boolean) + @keyparam isPy3 flag sets the interpreter to use or None for autodetect + corresponding interpreter (boolean or None) + """ + if isPy3 is None: + isPy3 = self.determinePythonVersion(filename, source) + + data = [source, checkFlakes, ignoreStarImportWarnings] + self.__send('syntax', filename, data, isPy3)
--- a/Utilities/SyntaxCheck.py Sun Dec 15 11:50:18 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,356 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing the syntax check for Python 2/3. -""" - -import sys -if sys.version_info[0] >= 3: - if __name__ == '__main__': - from py3flakes.checker import Checker - from py3flakes.messages import ImportStarUsed - else: - from .py3flakes.checker import Checker #__IGNORE_WARNING__ - from .py3flakes.messages import ImportStarUsed #__IGNORE_WARNING__ -else: - str = unicode #__IGNORE_WARNING__ - if __name__ == '__main__': - from py2flakes.checker import Checker #__IGNORE_WARNING__ - from py2flakes.messages import ImportStarUsed #__IGNORE_WARNING__ - else: - from .py2flakes.checker import Checker #__IGNORE_WARNING__ - from .py2flakes.messages import ImportStarUsed #__IGNORE_WARNING__ - -import re -import traceback -from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 - -try: - import Preferences -except (ImportError): - pass - -codingBytes_regexps = [ - (2, re.compile(br'''coding[:=]\s*([-\w_.]+)''')), - (1, re.compile(br'''<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>''')), -] - - -def get_codingBytes(text): - """ - Function to get the coding of a bytes text. - - @param text bytes text to inspect (bytes) - @return coding string - """ - lines = text.splitlines() - for coding in codingBytes_regexps: - coding_re = coding[1] - head = lines[:coding[0]] - for l in head: - m = coding_re.search(l) - if m: - return str(m.group(1), "ascii").lower() - return None - - -def decode(text): - """ - Function to decode some byte text into a string. - - @param text byte text to decode (bytes) - @return tuple of decoded text and encoding (string, string) - """ - try: - if text.startswith(BOM_UTF8): - # UTF-8 with BOM - return str(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom' - elif text.startswith(BOM_UTF16): - # UTF-16 with BOM - return str(text[len(BOM_UTF16):], 'utf-16'), 'utf-16' - elif text.startswith(BOM_UTF32): - # UTF-32 with BOM - return str(text[len(BOM_UTF32):], 'utf-32'), 'utf-32' - coding = get_codingBytes(text) - if coding: - return str(text, coding), coding - except (UnicodeError, LookupError): - pass - - # Assume UTF-8 - try: - return str(text, 'utf-8'), 'utf-8-guessed' - except (UnicodeError, LookupError): - pass - - try: - guess = None - if Preferences.getEditor("AdvancedEncodingDetection"): - # Try the universal character encoding detector - try: - import ThirdParty.CharDet.chardet - guess = ThirdParty.CharDet.chardet.detect(text) - if guess and guess['confidence'] > 0.95 \ - and guess['encoding'] is not None: - codec = guess['encoding'].lower() - return str(text, codec), '{0}-guessed'.format(codec) - except (UnicodeError, LookupError, ImportError): - pass - except (NameError): - pass - - # Try default encoding - try: - codec = Preferences.getEditor("DefaultEncoding") - return str(text, codec), '{0}-default'.format(codec) - except (UnicodeError, LookupError, NameError): - pass - - try: - if Preferences.getEditor("AdvancedEncodingDetection"): - # Use the guessed one even if confifence level is low - if guess and guess['encoding'] is not None: - try: - codec = guess['encoding'].lower() - return str(text, codec), '{0}-guessed'.format(codec) - except (UnicodeError, LookupError): - pass - except (NameError): - pass - - # Assume UTF-8 loosing information - return str(text, "utf-8", "ignore"), 'utf-8-ignore' - - -def readEncodedFile(filename): - """ - Function to read a file and decode it's contents into proper text. - - @param filename name of the file to read (string) - @return tuple of decoded text and encoding (string, string) - """ - try: - filename = filename.encode(sys.getfilesystemencoding()) - except (UnicodeDecodeError): - pass - f = open(filename, "rb") - text = f.read() - f.close() - return decode(text) - - -def normalizeCode(codestring): - """ - Function to normalize the given code. - - @param codestring code to be normalized (string) - @return normalized code (string) - """ - codestring = codestring.replace("\r\n", "\n").replace("\r", "\n") - - if codestring and codestring[-1] != '\n': - codestring = codestring + '\n' - - # Check type for py2: if not str it's unicode - if sys.version_info[0] == 2: - try: - codestring = codestring.encode('utf-8') - except: - pass - - return codestring - - -def extractLineFlags(line, startComment="#", endComment=""): - """ - Function to extract flags starting and ending with '__' from a line - comment. - - @param line line to extract flags from (string) - @keyparam startComment string identifying the start of the comment (string) - @keyparam endComment string identifying the end of a comment (string) - @return list containing the extracted flags (list of strings) - """ - flags = [] - - pos = line.rfind(startComment) - if pos >= 0: - comment = line[pos + len(startComment):].strip() - if endComment: - comment = comment.replace("endComment", "") - flags = [f.strip() for f in comment.split() - if (f.startswith("__") and f.endswith("__"))] - return flags - - -def compile_and_check(file_, codestring="", checkFlakes=True, - ignoreStarImportWarnings=False): - """ - Function to compile one Python source file to Python bytecode - and to perform a pyflakes check. - - @param file_ source filename (string) - @param codestring string containing the code to compile (string) - @keyparam checkFlakes flag indicating to do a pyflakes check (boolean) - @keyparam ignoreStarImportWarnings flag indicating to - ignore 'star import' warnings (boolean) - @return A tuple indicating status (True = an error was found), the - file name, the line number, the index number, the code string - and the error message (boolean, string, string, string, string, - string). If checkFlakes is True, a list of strings containing the - warnings (marker, file name, line number, message) - The values are only valid, if the status is True. - """ - try: - import builtins - except ImportError: - import __builtin__ as builtins #__IGNORE_WARNING__ - - try: - if sys.version_info[0] == 2: - file_enc = file_.encode(sys.getfilesystemencoding()) - else: - file_enc = file_ - - if not codestring: - try: - codestring = readEncodedFile(file_)[0] - except (UnicodeDecodeError, IOError): - return (False, None, None, None, None, None, []) - - codestring = normalizeCode(codestring) - - if file_.endswith('.ptl'): - try: - import quixote.ptl_compile - except ImportError: - return (False, None, None, None, None, None, []) - template = quixote.ptl_compile.Template(codestring, file_enc) - template.compile() - - # ast.PyCF_ONLY_AST = 1024, speed optimisation - module = builtins.compile(codestring, file_enc, 'exec', 1024) - except SyntaxError as detail: - index = 0 - code = "" - error = "" - lines = traceback.format_exception_only(SyntaxError, detail) - match = re.match('\s*File "(.+)", line (\d+)', - lines[0].replace('<string>', '{0}'.format(file_))) - if match is not None: - fn, line = match.group(1, 2) - if lines[1].startswith('SyntaxError:'): - error = re.match('SyntaxError: (.+)', lines[1]).group(1) - else: - code = re.match('(.+)', lines[1]).group(1) - for seLine in lines[2:]: - if seLine.startswith('SyntaxError:'): - error = re.match('SyntaxError: (.+)', seLine).group(1) - elif seLine.rstrip().endswith('^'): - index = len(seLine.rstrip()) - 4 - else: - fn = detail.filename - line = detail.lineno or 1 - error = detail.msg - return (True, fn, int(line), index, code, error, []) - except ValueError as detail: - index = 0 - code = "" - try: - fn = detail.filename - line = detail.lineno - error = detail.msg - except AttributeError: - fn = file_ - line = 1 - error = str(detail) - return (True, fn, line, index, code, error, []) - except Exception as detail: - try: - fn = detail.filename - line = detail.lineno - index = 0 - code = "" - error = detail.msg - return (True, fn, line, index, code, error, []) - except: # this catchall is intentional - pass - - # pyflakes - if not checkFlakes: - return (False, "", -1, -1, "", "", []) - - strings = [] - lines = codestring.splitlines() - try: - warnings = Checker(module, file_) - warnings.messages.sort(key=lambda a: a.lineno) - for warning in warnings.messages: - if ignoreStarImportWarnings and \ - isinstance(warning, ImportStarUsed): - continue - - _fn, lineno, message, msg_args = warning.getMessageData() - if "__IGNORE_WARNING__" not in extractLineFlags( - lines[lineno - 1].strip()): - strings.append([ - "FLAKES_WARNING", _fn, lineno, message, msg_args]) - except SyntaxError as err: - if err.text.strip(): - msg = err.text.strip() - else: - msg = err.msg - strings.append(["FLAKES_ERROR", file_, err.lineno, msg, ()]) - - return (False, "", -1, -1, "", "", strings) - - -if __name__ == "__main__": - if len(sys.argv) < 2 or \ - len(sys.argv) > 3 or \ - (len(sys.argv) == 3 and sys.argv[1] not in ["-fi", "-fs"]): - print("ERROR") - print("") - print("") - print("") - print("") - print("No file name given.") - else: - filename = sys.argv[-1] - checkFlakes = len(sys.argv) == 3 - # Setting is ignored if checkFlakes is False - ignoreStarImportWarnings = sys.argv[1] == "-fi" - - try: - codestring = readEncodedFile(filename)[0] - - syntaxerror, fname, line, index, code, error, warnings = \ - compile_and_check(filename, codestring, checkFlakes, - ignoreStarImportWarnings) - except IOError as msg: - # fake a syntax error - syntaxerror, fname, line, index, code, error, warnings = \ - True, filename, 1, 0, "", "I/O Error: %s" % str(msg), [] - - if syntaxerror: - print("ERROR") - else: - print("NO_ERROR") - print(fname) - print(line) - print(index) - print(code) - print(error) - - if not syntaxerror: - for warningLine in warnings: - msg_args = warningLine.pop() - for warning in warningLine: - print(warning) - msg_args = [str(x) for x in msg_args] - print('#'.join(msg_args)) - - sys.exit(0)
--- a/Utilities/py2flakes/__init__.py Sun Dec 15 11:50:18 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Package containg the pyflakes Python2 port adapted for Qt. -""" - -""" License -Copyright 2005-2011 Divmod, Inc. -Copyright 2013 Florent Xicluna - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" - -""" Changes -0.6.1 (2013-01-29): - - Fix detection of variables in augmented assignments. - -0.6.0 (2013-01-29): - - Support Python 3 up to 3.3, based on the pyflakes3k project. - - Preserve compatibility with Python 2.5 and all recent versions of Python. - - Support custom reporters in addition to the default Reporter. - - Allow function redefinition for modern property construction via - property.setter/deleter. - - Fix spurious redefinition warnings in conditionals. - - Do not report undefined name in __all__ if import * is used. - - Add WindowsError as a known built-in name on all platforms. - - Support specifying additional built-ins in the `Checker` constructor. - - Don't issue Unused Variable warning when using locals() in current scope. - - Handle problems with the encoding of source files. - - Remove dependency on Twisted for the tests. - - Support `python setup.py test` and `python setup.py develop`. - - Create script using setuptools `entry_points` to support all platforms, - including Windows. - -0.5.0 (2011-09-02): - - Convert pyflakes to use newer _ast infrastructure rather than compiler. - - Support for new syntax in 2.7 (including set literals, set comprehensions, - and dictionary comprehensions). - - Make sure class names don't get bound until after class definition. -""" - -__version__ = '0.6.1'
--- a/Utilities/py2flakes/checker.py Sun Dec 15 11:50:18 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,711 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# -# Original (c) 2005-2010 Divmod, Inc. -# -# This module is based on pyflakes for Python2 but was modified to -# work with eric5 - -import os.path -try: - import builtins - PY2 = False -except ImportError: - import __builtin__ as builtins #__IGNORE_WARNING__ - PY2 = True - -try: - import ast - iter_child_nodes = ast.iter_child_nodes -except (ImportError, AttributeError): # Python 2.5 - import _ast as ast - - def iter_child_nodes(node, astcls=ast.AST): - """ - 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 in node._fields: - field = getattr(node, name, None) - if isinstance(field, astcls): - yield field - elif isinstance(field, list): - for item in field: - yield item -# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) -if hasattr(ast, 'Try'): - ast_TryExcept = ast.Try - ast_TryFinally = () -else: - ast_TryExcept = ast.TryExcept - ast_TryFinally = ast.TryFinally - -from . import messages - - -class Binding(object): - """ - Represents the binding of a value to a name. - - The checker uses this to keep track of which names have been bound and - which names have not. See L{Assignment} for a special type of binding that - is checked with stricter rules. - """ - - 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): - """ - A binding created by an import statement. - """ - def __init__(self, name, source): - self.fullName = name - name = name.split('.')[0] - super(Importation, self).__init__(name, source) - - -class Argument(Binding): - """ - Represents binding a name as an argument. - """ - - -class Definition(Binding): - """ - A binding that defines a function or a class. - """ - - -class Assignment(Binding): - """ - Represents binding a name with an explicit assignment. - - The checker will raise warnings for any Assignment that isn't used. Also, - the checker does not consider assignments in tuple/list unpacking to be - Assignments, rather it treats them as simple Bindings. - """ - - -class FunctionDefinition(Definition): - pass - - -class ClassDefinition(Definition): - pass - - -class ExportBinding(Binding): - """ - A binding created by an C{__all__} assignment. If the names in the list - can be determined statically, they will be treated as names for export and - additional checking applied to them. - - The only C{__all__} assignment that can be recognized is one which takes - the value of a literal list containing literal strings. For example:: - - __all__ = ["foo", "bar"] - - Names which are imported and not otherwise used but appear in the value of - C{__all__} will not have an unused import warning reported for them. - """ - def names(self): - """ - Return a list of the names referenced by this binding. - """ - names = [] - if isinstance(self.source, ast.List): - for node in self.source.elts: - if isinstance(node, ast.Str): - names.append(node.s) - return names - - -class Scope(dict): - importStarred = False # set to True when import * is found - usesLocals = False - - def __repr__(self): - return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) - - -class ClassScope(Scope): - pass - - -class FunctionScope(Scope): - """ - I represent a name scope for a function. - """ - def __init__(self): - super(FunctionScope, self).__init__() - self.globals = {} - - -class ModuleScope(Scope): - pass - - -# Globally defined names which are not attributes of the builtins module, or -# are only present on some platforms. -_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] - - -def getNodeName(node): - # Returns node.id, or node.name, or None - if hasattr(node, 'id'): # One of the many nodes with an id - return node.id - if hasattr(node, 'name'): # a ExceptHandler node - return node.name - - -class Checker(object): - """ - I check the cleanliness and sanity of Python code. - """ - - nodeDepth = 0 - traceTree = False - builtIns = set(dir(builtins)) | set(_MAGIC_GLOBALS) - - def __init__(self, tree, filename='(none)', builtins=None): - self._deferredFunctions = [] - self._deferredAssignments = [] - self.deadScopes = [] - self.messages = [] - self.filename = filename - if builtins: - self.builtIns = self.builtIns.union(builtins) - self.scopeStack = [ModuleScope()] - self.futuresAllowed = True - self.root = tree - self.handleChildren(tree) - self.runDeferred(self._deferredFunctions) - # Set _deferredFunctions to None so that deferFunction will fail - # noisily if called after we've run through the deferred functions. - self._deferredFunctions = None - self.runDeferred(self._deferredAssignments) - # Set _deferredAssignments to None so that deferAssignment will fail - # noisily if called after we've run through the deferred assignments. - self._deferredAssignments = None - del self.scopeStack[1:] - self.popScope() - self.checkDeadScopes() - - def deferFunction(self, callable): - """ - Schedule a function handler to be called 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._deferredFunctions.append((callable, self.scopeStack[:])) - - def deferAssignment(self, callable): - """ - Schedule an assignment handler to be called just after deferred - function handlers. - """ - self._deferredAssignments.append((callable, self.scopeStack[:])) - - def runDeferred(self, deferred): - """ - Run the callables in C{deferred} using their associated scope stack. - """ - for handler, scope in deferred: - self.scopeStack = scope - handler() - - @property - def scope(self): - return self.scopeStack[-1] - - def popScope(self): - self.deadScopes.append(self.scopeStack.pop()) - - def checkDeadScopes(self): - """ - Look at scopes which have been fully examined and report names in them - which were imported but unused. - """ - for scope in self.deadScopes: - export = isinstance(scope.get('__all__'), ExportBinding) - if export: - all = scope['__all__'].names() - if not scope.importStarred and os.path.basename(self.filename) != '__init__.py': - # Look for possible mistakes in the export list - undefined = set(all) - set(scope) - for name in undefined: - self.report(messages.UndefinedExport, - scope['__all__'].source.lineno, name) - else: - all = [] - - # Look for imported names that aren't used. - for importation in scope.values(): - if isinstance(importation, Importation): - if not importation.used and importation.name not in all: - self.report(messages.UnusedImport, - importation.source.lineno, importation.name) - - def pushFunctionScope(self): - self.scopeStack.append(FunctionScope()) - - def pushClassScope(self): - self.scopeStack.append(ClassScope()) - - def report(self, messageClass, *args, **kwargs): - self.messages.append(messageClass(self.filename, *args, **kwargs)) - - def hasParent(self, node, kind): - while hasattr(node, 'parent'): - node = node.parent - if isinstance(node, kind): - return True - - def getCommonAncestor(self, lnode, rnode, stop=None): - if not stop: - stop = self.root - if lnode is rnode: - return lnode - if stop in (lnode, rnode): - return stop - - if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'): - return - if (lnode.level > rnode.level): - return self.getCommonAncestor(lnode.parent, rnode, stop) - if (rnode.level > lnode.level): - return self.getCommonAncestor(lnode, rnode.parent, stop) - return self.getCommonAncestor(lnode.parent, rnode.parent, stop) - - def descendantOf(self, node, ancestors, stop=None): - for a in ancestors: - if self.getCommonAncestor(node, a, stop) not in (stop, None): - return True - return False - - def onFork(self, parent, lnode, rnode, items): - return (self.descendantOf(lnode, items, parent) ^ - self.descendantOf(rnode, items, parent)) - - def differentForks(self, lnode, rnode): - """True, if lnode and rnode are located on different forks of IF/TRY""" - ancestor = self.getCommonAncestor(lnode, rnode) - if isinstance(ancestor, ast.If): - for fork in (ancestor.body, ancestor.orelse): - if self.onFork(ancestor, lnode, rnode, fork): - return True - elif isinstance(ancestor, ast_TryExcept): - body = ancestor.body + ancestor.orelse - for fork in [body] + [[hdl] for hdl in ancestor.handlers]: - if self.onFork(ancestor, lnode, rnode, fork): - return True - elif isinstance(ancestor, ast_TryFinally): - if self.onFork(ancestor, lnode, rnode, ancestor.body): - return True - return False - - def addBinding(self, node, value, reportRedef=True): - """ - Called when a binding is altered. - - - `node` is the statement responsible for the change - - `value` is the optional new value, a Binding instance, associated - with the binding; if None, the binding is deleted if it exists. - - if `reportRedef` is True (default), rebinding while unused will be - reported. - """ - redefinedWhileUnused = False - if not isinstance(self.scope, ClassScope): - for scope in self.scopeStack[::-1]: - existing = scope.get(value.name) - if (isinstance(existing, Importation) - and not existing.used - and (not isinstance(value, Importation) or value.fullName == existing.fullName) - and reportRedef - and not self.differentForks(node, existing.source)): - redefinedWhileUnused = True - self.report(messages.RedefinedWhileUnused, - node.lineno, value.name, existing.source.lineno) - - existing = self.scope.get(value.name) - if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp): - if (existing and reportRedef - and not self.hasParent(existing.source, (ast.For, ast.ListComp))): - self.report(messages.RedefinedInListComp, - node.lineno, value.name, existing.source.lineno) - - if isinstance(value, UnBinding): - try: - del self.scope[value.name] - except KeyError: - self.report(messages.UndefinedName, node.lineno, value.name) - elif (isinstance(existing, Definition) - and not existing.used - and not self.differentForks(node, existing.source)): - self.report(messages.RedefinedWhileUnused, - node.lineno, value.name, existing.source.lineno) - else: - self.scope[value.name] = value - - def handleNodeLoad(self, node): - name = getNodeName(node) - if not name: - return - # try local scope - importStarred = self.scope.importStarred - try: - self.scope[name].used = (self.scope, node.lineno) - except KeyError: - pass - else: - return - - # try enclosing function scopes - for scope in self.scopeStack[-2:0:-1]: - importStarred = importStarred or scope.importStarred - if not isinstance(scope, FunctionScope): - continue - try: - scope[name].used = (self.scope, node.lineno) - except KeyError: - pass - else: - return - - # try global scope - importStarred = importStarred or self.scopeStack[0].importStarred - try: - self.scopeStack[0][name].used = (self.scope, node.lineno) - except KeyError: - if not importStarred and name not in self.builtIns: - if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'): - # the special name __path__ is valid only in packages - pass - else: - self.report(messages.UndefinedName, node.lineno, name) - - def handleNodeStore(self, node): - name = getNodeName(node) - if not name: - return - # if the name hasn't already been defined in the current scope - if isinstance(self.scope, FunctionScope) and name not in self.scope: - # for each function or module scope above us - for scope in self.scopeStack[:-1]: - if not isinstance(scope, (FunctionScope, ModuleScope)): - continue - # 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 (name in scope and scope[name].used and scope[name].used[0] is self.scope - and name not in self.scope.globals): - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[name].used[1], name, scope[name].source.lineno) - break - - parent = getattr(node, 'parent', None) - if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): - binding = Binding(name, node) - elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope): - binding = ExportBinding(name, parent.value) - else: - binding = Assignment(name, node) - if name in self.scope: - binding.used = self.scope[name].used - self.addBinding(node, binding) - - def handleNodeDelete(self, node): - name = getNodeName(node) - if not name: - return - if isinstance(self.scope, FunctionScope) and name in self.scope.globals: - del self.scope.globals[name] - else: - self.addBinding(node, UnBinding(name, node)) - - def handleChildren(self, tree): - for node in iter_child_nodes(tree): - self.handleNode(node, tree) - - def isDocstring(self, node): - """ - Determine if the given node is a docstring, as long as it is at the - correct place in the node tree. - """ - return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and - isinstance(node.value, ast.Str)) - - def handleNode(self, node, parent): - if node is None: - return - node.parent = parent - if self.traceTree: - print(' ' * self.nodeDepth + node.__class__.__name__) - self.nodeDepth += 1 - if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or - self.isDocstring(node)): - self.futuresAllowed = False - nodeType = node.__class__.__name__.upper() - node.level = self.nodeDepth - try: - handler = getattr(self, nodeType) - handler(node) - finally: - self.nodeDepth -= 1 - if self.traceTree: - print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) - - def ignore(self, node): - pass - - # "stmt" type nodes - RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ - TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren - - CONTINUE = BREAK = PASS = ignore - - # "expr" type nodes - BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \ - COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \ - STARRED = handleChildren - - NUM = STR = BYTES = ELLIPSIS = ignore - - # "slice" type nodes - SLICE = EXTSLICE = INDEX = handleChildren - - # expression contexts are node instances too, though being constants - LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore - - # same for operators - AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ - BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ - EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore - - # additional node types - COMPREHENSION = KEYWORD = handleChildren - - def GLOBAL(self, node): - """ - Keep track of globals declarations. - """ - if isinstance(self.scope, FunctionScope): - self.scope.globals.update(dict.fromkeys(node.names)) - - NONLOCAL = GLOBAL - - def LISTCOMP(self, node): - # handle generators before element - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.elt, node) - - GENERATOREXP = SETCOMP = LISTCOMP - - def DICTCOMP(self, node): - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.key, node) - self.handleNode(node.value, node) - - def FOR(self, node): - """ - Process bindings for loop variables. - """ - vars = [] - - def collectLoopVars(n): - if isinstance(n, ast.Name): - vars.append(n.id) - elif isinstance(n, ast.expr_context): - return - else: - for c in iter_child_nodes(n): - collectLoopVars(c) - - collectLoopVars(node.target) - for varn in vars: - if (isinstance(self.scope.get(varn), Importation) - # unused ones will get an unused import warning - and self.scope[varn].used): - self.report(messages.ImportShadowedByLoopVar, - node.lineno, varn, self.scope[varn].source.lineno) - - self.handleChildren(node) - - def NAME(self, node): - """ - Handle occurrence of Name (which can be a load/store/delete access.) - """ - if node.id == 'locals' and isinstance(node.parent, ast.Call): - # we are doing locals() call in current scope - self.scope.usesLocals = True - # Locate the name in locals / function / globals scopes. - if isinstance(node.ctx, (ast.Load, ast.AugLoad)): - self.handleNodeLoad(node) - elif isinstance(node.ctx, (ast.Store, ast.AugStore)): - self.handleNodeStore(node) - elif isinstance(node.ctx, ast.Del): - self.handleNodeDelete(node) - else: - # must be a Param context -- this only happens for names in function - # arguments, but these aren't dispatched through here - raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) - - def FUNCTIONDEF(self, node): - if not hasattr(node, 'decorator_list'): # Python 2.5 - node.decorator_list = node.decorators - for deco in node.decorator_list: - self.handleNode(deco, node) - self.addBinding(node, FunctionDefinition(node.name, node)) - self.LAMBDA(node) - - def LAMBDA(self, node): - args = [] - - if PY2: - def addArgs(arglist): - for arg in arglist: - if isinstance(arg, ast.Tuple): - addArgs(arg.elts) - else: - if arg.id in args: - self.report(messages.DuplicateArgument, - node.lineno, arg.id) - args.append(arg.id) - addArgs(node.args.args) - defaults = node.args.defaults - else: - for arg in node.args.args + node.args.kwonlyargs: - if arg.arg in args: - self.report(messages.DuplicateArgument, - node.lineno, arg.arg) - args.append(arg.arg) - self.handleNode(arg.annotation, node) - if hasattr(node, 'returns'): # Only for FunctionDefs - for annotation in (node.args.varargannotation, - node.args.kwargannotation, node.returns): - self.handleNode(annotation, node) - defaults = node.args.defaults + node.args.kw_defaults - - # vararg/kwarg identifiers are not Name nodes - for wildcard in (node.args.vararg, node.args.kwarg): - if not wildcard: - continue - if wildcard in args: - self.report(messages.DuplicateArgument, node.lineno, wildcard) - args.append(wildcard) - for default in defaults: - self.handleNode(default, node) - - def runFunction(): - - self.pushFunctionScope() - for name in args: - self.addBinding(node, Argument(name, node), reportRedef=False) - if isinstance(node.body, list): - # case for FunctionDefs - for stmt in node.body: - self.handleNode(stmt, node) - else: - # case for Lambdas - self.handleNode(node.body, node) - - def checkUnusedAssignments(): - """ - Check to see if any assignments have not been used. - """ - for name, binding in self.scope.items(): - if (not binding.used and name not in self.scope.globals - and not self.scope.usesLocals - and isinstance(binding, Assignment)): - self.report(messages.UnusedVariable, - binding.source.lineno, name) - self.deferAssignment(checkUnusedAssignments) - self.popScope() - - self.deferFunction(runFunction) - - def CLASSDEF(self, node): - """ - Check names used in a class definition, including its decorators, base - classes, and the body of its definition. Additionally, add its name to - the current scope. - """ - # no class decorator in Python 2.5 - for deco in getattr(node, 'decorator_list', ''): - self.handleNode(deco, node) - for baseNode in node.bases: - self.handleNode(baseNode, node) - if not PY2: - for keywordNode in node.keywords: - self.handleNode(keywordNode, node) - self.pushClassScope() - for stmt in node.body: - self.handleNode(stmt, node) - self.popScope() - self.addBinding(node, ClassDefinition(node.name, node)) - - def ASSIGN(self, node): - self.handleNode(node.value, node) - for target in node.targets: - self.handleNode(target, node) - - def AUGASSIGN(self, node): - self.handleNodeLoad(node.target) - self.handleNode(node.value, node) - self.handleNode(node.target, node) - - def IMPORT(self, node): - for alias in node.names: - name = alias.asname or alias.name - importation = Importation(name, node) - self.addBinding(node, importation) - - def IMPORTFROM(self, node): - if node.module == '__future__': - if not self.futuresAllowed: - self.report(messages.LateFutureImport, - node.lineno, [n.name for n in node.names]) - else: - self.futuresAllowed = False - - for alias in node.names: - if alias.name == '*': - self.scope.importStarred = True - self.report(messages.ImportStarUsed, node.lineno, node.module) - continue - name = alias.asname or alias.name - importation = Importation(name, node) - if node.module == '__future__': - importation.used = (self.scope, node.lineno) - self.addBinding(node, importation) - - def EXCEPTHANDLER(self, node): - # 3.x: in addition to handling children, we must handle the name of - # the exception, which is not a Name node, but a simple string. - if isinstance(node.name, str): - self.handleNodeStore(node) - self.handleChildren(node)
--- a/Utilities/py2flakes/messages.py Sun Dec 15 11:50:18 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> -# -# Original (c) 2005 Divmod, Inc. See LICENSE file for details -# -# This module is based on pyflakes for Python2 but was heavily hacked to -# work within Eric5 and Qt (translatable messages) - -""" -Module implementing the messages for py2flakes. -""" - - -def QT_TRANSLATE_NOOP(mod, txt): - """ - Function to tell 'lupdate' which strings to keep for translation. - - @param mod module name - @param txt translatable string - @return the untranslated! string - """ - return txt - - -class Message(object): - """ - Class defining the base for all specific message classes. - """ - message = '' - message_args = () - - def __init__(self, filename, lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - """ - self.filename = filename - self.lineno = lineno - - def __str__(self): - """ - Special method return a string representation of the instance object. - - @return string representation of the object (string) - """ - return '%s:%s: %s' % ( - self.filename, self.lineno, self.message % self.message_args) - - def getMessageData(self): - """ - Public method to get the individual message data elements. - - @return tuple containing file name, line number and message - (string, integer, string) - """ - return (self.filename, self.lineno, self.message, self.message_args) - - -class UnusedImport(Message): - """ - Class defining the "Unused Import" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - '{0!r} imported but unused.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the unused import (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class RedefinedWhileUnused(Message): - """ - Class defining the "Redefined While Unused" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Redefinition of unused {0!r} from line {1!r}.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the redefined object (string) - @param orig_lineno line number of the original definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class RedefinedInListComp(Message): - """ - Class defining the list comprehension redefinition. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'List comprehension redefines {0!r} from line {1!r}.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the redefined object (string) - @param orig_lineno line number of the original definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class ImportShadowedByLoopVar(Message): - """ - Class defining the "Import Shadowed By Loop Var" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Import {0!r} from line {1!r} shadowed by loop variable.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the shadowed import (string) - @param orig_lineno line number of the import (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class ImportStarUsed(Message): - """ - Class defining the "Import Star Used" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - "'from {0} import *' used; unable to detect undefined names.") - - def __init__(self, filename, lineno, modname): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param modname name of the module imported using star import (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (modname,) - - -class UndefinedName(Message): - """ - Class defining the "Undefined Name" message. - """ - message = QT_TRANSLATE_NOOP('py3Flakes', 'Undefined name {0!r}.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name undefined name (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class UndefinedExport(Message): - """ - Class defining the "Undefined Export" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Undefined name {0!r} in __all__.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name undefined exported name (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class UndefinedLocal(Message): - """ - Class defining the "Undefined Local Variable" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - "Local variable {0!r} (defined in enclosing scope on line {1!r})" - " referenced before assignment.") - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the prematurely referenced variable (string) - @param orig_lineno line number of the variable definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class DuplicateArgument(Message): - """ - Class defining the "Duplicate Argument" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Duplicate argument {0!r} in function definition.') - - def __init__(self, filename, lineno, name): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the duplicate argument (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name,) - - -class Redefined(Message): - """ - Class defining the "Redefined" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Redefinition of {0!r} from line {1!r}.') - - def __init__(self, filename, lineno, name, orig_lineno): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param name name of the redefined function (string) - @param orig_lineno line number of the original definition (integer) - """ - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) - - -class LateFutureImport(Message): - """ - Class defining the "Late Future Import" message. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Future import(s) {0!r} after other statements.') - - def __init__(self, filename, lineno, names): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param names names of the imported futures (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (names,) - - -class UnusedVariable(Message): - """ - Class defining the "Unused Variable" message. - - Indicates that a variable has been explicitly assigned to but not actually - used. - """ - message = QT_TRANSLATE_NOOP( - 'py3Flakes', - 'Local variable {0!r} is assigned to but never used.') - - def __init__(self, filename, lineno, names): - """ - Constructor - - @param filename name of the file (string) - @param lineno line number (integer) - @param names names of the unused variable (string) - """ - Message.__init__(self, filename, lineno) - self.message_args = (names,) - -# -# eflag: FileType = Python2