--- /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)