Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py

branch
BgService
changeset 3159
02cb2adb4868
parent 3065
070b35dde35e
child 3173
1fb284abe46e
equal deleted inserted replaced
3146:14721e0f3b5b 3159:02cb2adb4868
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the syntax check for Python 2/3.
8 """
9 from __future__ import unicode_literals
10
11 import re
12 import sys
13 import traceback
14
15 from .pyflakes.checker import Checker
16 from .pyflakes.messages import ImportStarUsed
17
18
19 def normalizeCode(codestring):
20 """
21 Function to normalize the given code.
22
23 @param codestring code to be normalized (string)
24 @return normalized code (string)
25 """
26 codestring = codestring.replace("\r\n", "\n").replace("\r", "\n")
27
28 if codestring and codestring[-1] != '\n':
29 codestring = codestring + '\n'
30
31 # Check type for py2: if not str it's unicode
32 if sys.version_info[0] == 2:
33 try:
34 codestring = codestring.encode('utf-8')
35 except UnicodeError:
36 pass
37
38 return codestring
39
40
41 def extractLineFlags(line, startComment="#", endComment=""):
42 """
43 Function to extract flags starting and ending with '__' from a line
44 comment.
45
46 @param line line to extract flags from (string)
47 @keyparam startComment string identifying the start of the comment (string)
48 @keyparam endComment string identifying the end of a comment (string)
49 @return list containing the extracted flags (list of strings)
50 """
51 flags = []
52
53 pos = line.rfind(startComment)
54 if pos >= 0:
55 comment = line[pos + len(startComment):].strip()
56 if endComment:
57 comment = comment.replace("endComment", "")
58 flags = [f.strip() for f in comment.split()
59 if (f.startswith("__") and f.endswith("__"))]
60 return flags
61
62
63 def syntaxAndPyflakesCheck(filename, codestring="", checkFlakes=True,
64 ignoreStarImportWarnings=False):
65 """
66 Function to compile one Python source file to Python bytecode
67 and to perform a pyflakes check.
68
69 @param filename source filename (string)
70 @keyparam codestring string containing the code to compile (string)
71 @keyparam checkFlakes flag indicating to do a pyflakes check (boolean)
72 @keyparam ignoreStarImportWarnings flag indicating to
73 ignore 'star import' warnings (boolean)
74 @return A tuple indicating status (True = an error was found), the
75 file name, the line number, the index number, the code string
76 and the error message (boolean, string, string, string, string,
77 string). If checkFlakes is True, a list of strings containing the
78 warnings (marker, file name, line number, message)
79 The values are only valid, if the status is True.
80 """
81 try:
82 import builtins
83 except ImportError:
84 import __builtin__ as builtins #__IGNORE_WARNING__
85
86 try:
87 if sys.version_info[0] == 2:
88 file_enc = filename.encode(sys.getfilesystemencoding())
89 else:
90 file_enc = filename
91
92 # It also encoded the code back to avoid 'Encoding declaration in
93 # unicode string' exception on Python2
94 codestring = normalizeCode(codestring)
95
96 if filename.endswith('.ptl'):
97 try:
98 import quixote.ptl_compile
99 except ImportError:
100 return (True, filename, 0, 0, '',
101 'Quixote plugin not found.', [])
102 template = quixote.ptl_compile.Template(codestring, file_enc)
103 template.compile()
104
105 # ast.PyCF_ONLY_AST = 1024, speed optimisation
106 module = builtins.compile(codestring, file_enc, 'exec', 1024)
107 except SyntaxError as detail:
108 index = 0
109 code = ""
110 error = ""
111 lines = traceback.format_exception_only(SyntaxError, detail)
112 if sys.version_info[0] == 2:
113 lines = [x.decode(sys.getfilesystemencoding()) for x in lines]
114 match = re.match('\s*File "(.+)", line (\d+)',
115 lines[0].replace('<string>', '{0}'.format(filename)))
116 if match is not None:
117 fn, line = match.group(1, 2)
118 if lines[1].startswith('SyntaxError:'):
119 error = re.match('SyntaxError: (.+)', lines[1]).group(1)
120 else:
121 code = re.match('(.+)', lines[1]).group(1)
122 for seLine in lines[2:]:
123 if seLine.startswith('SyntaxError:'):
124 error = re.match('SyntaxError: (.+)', seLine).group(1)
125 elif seLine.rstrip().endswith('^'):
126 index = len(seLine.rstrip()) - 4
127 else:
128 fn = detail.filename
129 line = detail.lineno or 1
130 error = detail.msg
131 return (True, fn, int(line), index, code, error, [])
132 except ValueError as detail:
133 index = 0
134 code = ""
135 try:
136 fn = detail.filename
137 line = detail.lineno
138 error = detail.msg
139 except AttributeError:
140 fn = filename
141 line = 1
142 error = str(detail)
143 return (True, fn, line, index, code, error, [])
144 except Exception as detail:
145 try:
146 fn = detail.filename
147 line = detail.lineno
148 index = 0
149 code = ""
150 error = detail.msg
151 return (True, fn, line, index, code, error, [])
152 except: # this catchall is intentional
153 pass
154
155 # pyflakes
156 if not checkFlakes:
157 return (False, "", -1, -1, "", "", [])
158
159 strings = []
160 lines = codestring.splitlines()
161 try:
162 warnings = Checker(module, filename)
163 warnings.messages.sort(key=lambda a: a.lineno)
164 for warning in warnings.messages:
165 if ignoreStarImportWarnings and \
166 isinstance(warning, ImportStarUsed):
167 continue
168
169 _fn, lineno, message, msg_args = warning.getMessageData()
170 if "__IGNORE_WARNING__" not in extractLineFlags(
171 lines[lineno - 1].strip()):
172 strings.append([
173 "FLAKES_WARNING", _fn, lineno, message, msg_args])
174 except SyntaxError as err:
175 if err.text.strip():
176 msg = err.text.strip()
177 else:
178 msg = err.msg
179 strings.append(["FLAKES_ERROR", filename, err.lineno, msg, ()])
180
181 return (False, "", -1, -1, "", "", strings)

eric ide

mercurial