Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py

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

eric ide

mercurial