|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Syntax Checker plugin. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import contextlib |
|
12 |
|
13 from PyQt6.QtCore import QObject |
|
14 |
|
15 from EricGui.EricAction import EricAction |
|
16 from EricWidgets.EricApplication import ericApp |
|
17 from eric7config import getConfig |
|
18 |
|
19 from Project.ProjectBrowserModel import ProjectBrowserFileItem |
|
20 |
|
21 import Preferences |
|
22 import UI.Info |
|
23 |
|
24 # Start-Of-Header |
|
25 name = "Syntax Checker Plugin" |
|
26 author = "Detlev Offenbach <detlev@die-offenbachs.de>" |
|
27 autoactivate = True |
|
28 deactivateable = True |
|
29 version = UI.Info.VersionOnly |
|
30 className = "SyntaxCheckerPlugin" |
|
31 packageName = "__core__" |
|
32 shortDescription = "Show the Syntax Checker dialog." |
|
33 longDescription = ( |
|
34 """This plugin implements the Syntax Checker dialog.""" |
|
35 """ Syntax Checker is used to check Python source files for correct""" |
|
36 """ syntax.""" |
|
37 ) |
|
38 pyqtApi = 2 |
|
39 # End-Of-Header |
|
40 |
|
41 error = "" |
|
42 |
|
43 |
|
44 class SyntaxCheckerPlugin(QObject): |
|
45 """ |
|
46 Class implementing the Syntax Checker plugin. |
|
47 """ |
|
48 def __init__(self, ui): |
|
49 """ |
|
50 Constructor |
|
51 |
|
52 @param ui reference to the user interface object (UI.UserInterface) |
|
53 """ |
|
54 super().__init__(ui) |
|
55 self.__ui = ui |
|
56 self.__initialize() |
|
57 |
|
58 from Plugins.CheckerPlugins.SyntaxChecker.SyntaxCheckService import ( |
|
59 SyntaxCheckService |
|
60 ) |
|
61 self.syntaxCheckService = SyntaxCheckService() |
|
62 ericApp().registerObject("SyntaxCheckService", self.syntaxCheckService) |
|
63 |
|
64 ericPath = getConfig('ericDir') |
|
65 path = os.path.join(ericPath, 'Plugins', 'CheckerPlugins', |
|
66 'SyntaxChecker') |
|
67 |
|
68 self.syntaxCheckService.addLanguage( |
|
69 'Python3', 'Python3', path, 'SyntaxCheck', |
|
70 self.__getPythonOptions, |
|
71 lambda: Preferences.getPython("Python3Extensions"), |
|
72 self.__translateSyntaxCheck, |
|
73 self.syntaxCheckService.serviceErrorPy3) |
|
74 |
|
75 self.syntaxCheckService.addLanguage( |
|
76 'JavaScript', 'Python3', path, |
|
77 'jsCheckSyntax', |
|
78 lambda: [], # No options |
|
79 lambda: ['.js'], |
|
80 lambda fn, problems: |
|
81 self.syntaxCheckService.syntaxChecked.emit(fn, problems), |
|
82 self.syntaxCheckService.serviceErrorJavaScript) |
|
83 |
|
84 # YAML syntax check via Python3 |
|
85 self.syntaxCheckService.addLanguage( |
|
86 'YAML', 'Python3', path, |
|
87 'yamlCheckSyntax', |
|
88 lambda: [], # No options |
|
89 lambda: ['.yml', '.yaml'], |
|
90 lambda fn, problems: |
|
91 self.syntaxCheckService.syntaxChecked.emit(fn, problems), |
|
92 self.syntaxCheckService.serviceErrorYAML) |
|
93 |
|
94 # JSON syntax check via Python3 |
|
95 self.syntaxCheckService.addLanguage( |
|
96 'JSON', 'Python3', path, |
|
97 'jsonCheckSyntax', |
|
98 lambda: [], # No options |
|
99 lambda: ['.json'], |
|
100 lambda fn, problems: |
|
101 self.syntaxCheckService.syntaxChecked.emit(fn, problems), |
|
102 self.syntaxCheckService.serviceErrorJSON) |
|
103 |
|
104 # TOML syntax check via Python3 |
|
105 self.syntaxCheckService.addLanguage( |
|
106 'TOML', 'Python3', path, |
|
107 'tomlCheckSyntax', |
|
108 lambda: [], # No options |
|
109 lambda: ['.toml'], |
|
110 lambda fn, problems: |
|
111 self.syntaxCheckService.syntaxChecked.emit(fn, problems), |
|
112 self.syntaxCheckService.serviceErrorTOML) |
|
113 |
|
114 def __initialize(self): |
|
115 """ |
|
116 Private slot to (re)initialize the plugin. |
|
117 """ |
|
118 self.__projectAct = None |
|
119 self.__projectSyntaxCheckerDialog = None |
|
120 |
|
121 self.__projectBrowserAct = None |
|
122 self.__projectBrowserMenu = None |
|
123 self.__projectBrowserSyntaxCheckerDialog = None |
|
124 |
|
125 self.__editors = [] |
|
126 self.__editorAct = None |
|
127 self.__editorSyntaxCheckerDialog = None |
|
128 |
|
129 def __getPythonOptions(self): |
|
130 """ |
|
131 Private method to determine the syntax check options. |
|
132 |
|
133 @return state of checkFlakes and ignoreStarImportWarnings (bool, bool) |
|
134 """ |
|
135 checkFlakes = Preferences.getFlakes("IncludeInSyntaxCheck") |
|
136 ignoreStarImportWarnings = Preferences.getFlakes( |
|
137 "IgnoreStarImportWarnings") |
|
138 return checkFlakes, ignoreStarImportWarnings |
|
139 |
|
140 def __translateSyntaxCheck(self, fn, problems): |
|
141 """ |
|
142 Private slot to translate the resulting messages. |
|
143 |
|
144 If checkFlakes is True, warnings contains a list of strings containing |
|
145 the warnings (marker, file name, line number, message) |
|
146 The values are only valid, if nok is False. |
|
147 |
|
148 @param fn filename of the checked file (str) |
|
149 @param problems dictionary with the keys 'error' and 'warnings' which |
|
150 hold a list containing details about the error/ warnings |
|
151 (file name, line number, column, codestring (only at syntax |
|
152 errors), the message, a list with arguments for the message) |
|
153 """ |
|
154 from CheckerPlugins.SyntaxChecker.pyflakes.translations import ( |
|
155 getTranslatedFlakesMessage |
|
156 ) |
|
157 warnings = problems.get('warnings', []) |
|
158 for warning in warnings: |
|
159 # Translate messages |
|
160 msg_args = warning.pop() |
|
161 warning[4] = getTranslatedFlakesMessage(warning[4], msg_args) |
|
162 |
|
163 problems['warnings'] = warnings |
|
164 self.syntaxCheckService.syntaxChecked.emit(fn, problems) |
|
165 |
|
166 def activate(self): |
|
167 """ |
|
168 Public method to activate this plugin. |
|
169 |
|
170 @return tuple of None and activation status (boolean) |
|
171 """ |
|
172 menu = ericApp().getObject("Project").getMenu("Checks") |
|
173 if menu: |
|
174 self.__projectAct = EricAction( |
|
175 self.tr('Check Syntax'), |
|
176 self.tr('&Syntax...'), 0, 0, |
|
177 self, 'project_check_syntax') |
|
178 self.__projectAct.setStatusTip( |
|
179 self.tr('Check syntax.')) |
|
180 self.__projectAct.setWhatsThis(self.tr( |
|
181 """<b>Check Syntax...</b>""" |
|
182 """<p>This checks Python files for syntax errors.</p>""" |
|
183 )) |
|
184 self.__projectAct.triggered.connect(self.__projectSyntaxCheck) |
|
185 ericApp().getObject("Project").addEricActions([self.__projectAct]) |
|
186 menu.addAction(self.__projectAct) |
|
187 |
|
188 self.__editorAct = EricAction( |
|
189 self.tr('Check Syntax'), |
|
190 self.tr('&Syntax...'), 0, 0, |
|
191 self, "") |
|
192 self.__editorAct.setWhatsThis(self.tr( |
|
193 """<b>Check Syntax...</b>""" |
|
194 """<p>This checks Python files for syntax errors.</p>""" |
|
195 )) |
|
196 self.__editorAct.triggered.connect(self.__editorSyntaxCheck) |
|
197 |
|
198 ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu) |
|
199 ericApp().getObject("ProjectBrowser").getProjectBrowser( |
|
200 "sources").showMenu.connect(self.__projectBrowserShowMenu) |
|
201 ericApp().getObject("ViewManager").editorOpenedEd.connect( |
|
202 self.__editorOpened) |
|
203 ericApp().getObject("ViewManager").editorClosedEd.connect( |
|
204 self.__editorClosed) |
|
205 |
|
206 for editor in ericApp().getObject("ViewManager").getOpenEditors(): |
|
207 self.__editorOpened(editor) |
|
208 |
|
209 return None, True |
|
210 |
|
211 def deactivate(self): |
|
212 """ |
|
213 Public method to deactivate this plugin. |
|
214 """ |
|
215 ericApp().getObject("Project").showMenu.disconnect( |
|
216 self.__projectShowMenu) |
|
217 ericApp().getObject("ProjectBrowser").getProjectBrowser( |
|
218 "sources").showMenu.disconnect(self.__projectBrowserShowMenu) |
|
219 ericApp().getObject("ViewManager").editorOpenedEd.disconnect( |
|
220 self.__editorOpened) |
|
221 ericApp().getObject("ViewManager").editorClosedEd.disconnect( |
|
222 self.__editorClosed) |
|
223 |
|
224 menu = ericApp().getObject("Project").getMenu("Checks") |
|
225 if menu: |
|
226 menu.removeAction(self.__projectAct) |
|
227 |
|
228 if self.__projectBrowserMenu and self.__projectBrowserAct: |
|
229 self.__projectBrowserMenu.removeAction( |
|
230 self.__projectBrowserAct) |
|
231 |
|
232 for editor in self.__editors: |
|
233 editor.showMenu.disconnect(self.__editorShowMenu) |
|
234 menu = editor.getMenu("Checks") |
|
235 if menu is not None: |
|
236 menu.removeAction(self.__editorAct) |
|
237 |
|
238 self.__initialize() |
|
239 |
|
240 def __projectShowMenu(self, menuName, menu): |
|
241 """ |
|
242 Private slot called, when the the project menu or a submenu is |
|
243 about to be shown. |
|
244 |
|
245 @param menuName name of the menu to be shown (string) |
|
246 @param menu reference to the menu (QMenu) |
|
247 """ |
|
248 if menuName == "Checks" and self.__projectAct is not None: |
|
249 self.__projectAct.setEnabled( |
|
250 ericApp().getObject("Project").getProjectLanguage() in |
|
251 self.syntaxCheckService.getLanguages()) |
|
252 |
|
253 def __projectBrowserShowMenu(self, menuName, menu): |
|
254 """ |
|
255 Private slot called, when the the project browser menu or a submenu is |
|
256 about to be shown. |
|
257 |
|
258 @param menuName name of the menu to be shown (string) |
|
259 @param menu reference to the menu (QMenu) |
|
260 """ |
|
261 if ( |
|
262 menuName == "Checks" and |
|
263 ericApp().getObject("Project").getProjectLanguage() in |
|
264 self.syntaxCheckService.getLanguages() |
|
265 ): |
|
266 self.__projectBrowserMenu = menu |
|
267 if self.__projectBrowserAct is None: |
|
268 self.__projectBrowserAct = EricAction( |
|
269 self.tr('Check Syntax'), |
|
270 self.tr('&Syntax...'), 0, 0, |
|
271 self, "") |
|
272 self.__projectBrowserAct.setWhatsThis(self.tr( |
|
273 """<b>Check Syntax...</b>""" |
|
274 """<p>This checks Python files for syntax errors.</p>""" |
|
275 )) |
|
276 self.__projectBrowserAct.triggered.connect( |
|
277 self.__projectBrowserSyntaxCheck) |
|
278 if self.__projectBrowserAct not in menu.actions(): |
|
279 menu.addAction(self.__projectBrowserAct) |
|
280 |
|
281 def __projectSyntaxCheck(self): |
|
282 """ |
|
283 Private slot used to check the project files for syntax errors. |
|
284 """ |
|
285 project = ericApp().getObject("Project") |
|
286 project.saveAllScripts() |
|
287 ppath = project.getProjectPath() |
|
288 extensions = tuple(self.syntaxCheckService.getExtensions()) |
|
289 files = [os.path.join(ppath, file) |
|
290 for file in project.pdata["SOURCES"] |
|
291 if file.endswith(extensions)] |
|
292 |
|
293 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import ( |
|
294 SyntaxCheckerDialog |
|
295 ) |
|
296 self.__projectSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
297 self.__projectSyntaxCheckerDialog.show() |
|
298 self.__projectSyntaxCheckerDialog.prepare(files, project) |
|
299 |
|
300 def __projectBrowserSyntaxCheck(self): |
|
301 """ |
|
302 Private method to handle the syntax check context menu action of the |
|
303 project sources browser. |
|
304 """ |
|
305 browser = ericApp().getObject("ProjectBrowser").getProjectBrowser( |
|
306 "sources") |
|
307 if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: |
|
308 fn = [] |
|
309 for itm in browser.getSelectedItems([ProjectBrowserFileItem]): |
|
310 fn.append(itm.fileName()) |
|
311 else: |
|
312 itm = browser.model().item(browser.currentIndex()) |
|
313 try: |
|
314 fn = itm.fileName() |
|
315 except AttributeError: |
|
316 fn = itm.dirName() |
|
317 |
|
318 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import ( |
|
319 SyntaxCheckerDialog |
|
320 ) |
|
321 self.__projectBrowserSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
322 self.__projectBrowserSyntaxCheckerDialog.show() |
|
323 self.__projectBrowserSyntaxCheckerDialog.start(fn) |
|
324 |
|
325 def __editorOpened(self, editor): |
|
326 """ |
|
327 Private slot called, when a new editor was opened. |
|
328 |
|
329 @param editor reference to the new editor (QScintilla.Editor) |
|
330 """ |
|
331 menu = editor.getMenu("Checks") |
|
332 if menu is not None: |
|
333 menu.addAction(self.__editorAct) |
|
334 editor.showMenu.connect(self.__editorShowMenu) |
|
335 self.__editors.append(editor) |
|
336 |
|
337 def __editorClosed(self, editor): |
|
338 """ |
|
339 Private slot called, when an editor was closed. |
|
340 |
|
341 @param editor reference to the editor (QScintilla.Editor) |
|
342 """ |
|
343 with contextlib.suppress(ValueError): |
|
344 self.__editors.remove(editor) |
|
345 |
|
346 def __editorShowMenu(self, menuName, menu, editor): |
|
347 """ |
|
348 Private slot called, when the the editor context menu or a submenu is |
|
349 about to be shown. |
|
350 |
|
351 @param menuName name of the menu to be shown (string) |
|
352 @param menu reference to the menu (QMenu) |
|
353 @param editor reference to the editor |
|
354 """ |
|
355 if menuName == "Checks": |
|
356 if self.__editorAct not in menu.actions(): |
|
357 menu.addAction(self.__editorAct) |
|
358 self.__editorAct.setEnabled( |
|
359 editor.getLanguage() in self.syntaxCheckService.getLanguages()) |
|
360 |
|
361 def __editorSyntaxCheck(self): |
|
362 """ |
|
363 Private slot to handle the syntax check context menu action of the |
|
364 editors. |
|
365 """ |
|
366 editor = ericApp().getObject("ViewManager").activeWindow() |
|
367 if editor is not None: |
|
368 from CheckerPlugins.SyntaxChecker.SyntaxCheckerDialog import ( |
|
369 SyntaxCheckerDialog |
|
370 ) |
|
371 self.__editorSyntaxCheckerDialog = SyntaxCheckerDialog() |
|
372 self.__editorSyntaxCheckerDialog.show() |
|
373 if editor.isJavascriptFile(): |
|
374 unnamed = "Unnamed.js" |
|
375 else: |
|
376 unnamed = "Unnamed.py" |
|
377 self.__editorSyntaxCheckerDialog.start( |
|
378 editor.getFileName() or unnamed, editor.text()) |