/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Oct 25, 2004 * * @author Fabio Zadrozny */ package org.python.pydev.builder.pylint; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.ui.console.IOConsoleOutputStream; import org.python.pydev.consoles.MessageConsoles; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.IModule; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.core.log.Log; import org.python.pydev.editor.correctionassist.CheckAnalysisErrors; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.runners.SimplePythonRunner; import org.python.pydev.runners.SimpleRunner; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.io.ThreadStreamReader; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_ui.UIConstants; import org.python.pydev.shared_ui.utils.PyMarkerUtils; import org.python.pydev.shared_ui.utils.PyMarkerUtils.MarkerInfo; /** * Check lint.py for options. * * @author Fabio Zadrozny */ /*default*/ final class PyLintVisitor extends OnlyRemoveMarkersPyLintVisitor { private static Object lock = new Object(); private IDocument document; private IProgressMonitor monitor; /*default*/ PyLintVisitor(IResource resource, IDocument document, ICallback<IModule, Integer> module, IProgressMonitor monitor) { super(resource); this.document = document; this.monitor = monitor; } /** * Helper class which will start a process to collect PyLint information and process it. */ private static final class PyLintAnalysis { private IResource resource; private IDocument document; private IPath location; List<PyMarkerUtils.MarkerInfo> markers = new ArrayList<PyMarkerUtils.MarkerInfo>(); private IProgressMonitor monitor; private Process process; private Thread processWatchDoc; public PyLintAnalysis(IResource resource, IDocument document, IPath location, IProgressMonitor monitor) { this.resource = resource; this.document = document; this.location = location; this.monitor = monitor; } /** * Creates the pylint process and starts getting its output. */ private void createPyLintProcess(IOConsoleOutputStream out) throws CoreException, MisconfigurationException, PythonNatureWithoutProjectException { String script = FileUtils.getFileAbsolutePath(new File(PyLintPrefPage.getPyLintLocation())); String target = FileUtils.getFileAbsolutePath(new File(location.toOSString())); // check whether lint.py module or pylint executable has been specified boolean isPyScript = script.endsWith(".py") || script.endsWith(".pyw"); ArrayList<String> cmdList = new ArrayList<String>(); // pylint executable if (!isPyScript) { cmdList.add(script); } //user args String userArgs = StringUtils.replaceNewLines( PyLintPrefPage.getPyLintArgs(), " "); StringTokenizer tokenizer2 = new StringTokenizer(userArgs); while (tokenizer2.hasMoreTokens()) { String token = tokenizer2.nextToken(); if (token.equals("--output-format=parseable")) { continue; } if (token.startsWith("--msg-template=")) { continue; } if (token.startsWith("--output-format=")) { continue; } cmdList.add(token); } cmdList.add("--output-format=text"); cmdList.add("--msg-template='{C}:{line:3d},{column:2d}: {msg} ({symbol})'"); // target file to be linted cmdList.add(target); String[] args = cmdList.toArray(new String[0]); // run pylint in project location IProject project = resource.getProject(); File workingDir = project.getLocation().toFile(); if (isPyScript) { // run Python script (lint.py) with the interpreter of current project PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { Throwable e = new RuntimeException("PyLint ERROR: Nature not configured for: " + project); Log.log(e); return; } String interpreter = nature.getProjectInterpreter().getExecutableOrJar(); write("PyLint: Executing command line:", out, script, args); SimplePythonRunner runner = new SimplePythonRunner(); String[] parameters = SimplePythonRunner.preparePythonCallParameters(interpreter, script, args); Tuple<Process, String> r = runner.run(parameters, workingDir, nature, monitor); this.process = r.o1; } else { // run executable command (pylint or pylint.bat or pylint.exe) write("PyLint: Executing command line:", out, (Object) args); SimpleRunner simpleRunner = new SimpleRunner(); Tuple<Process, String> r = simpleRunner.run(args, workingDir, PythonNature.getPythonNature(project), null); this.process = r.o1; } this.processWatchDoc = new Thread() { @Override public void run() { //No need to synchronize as we'll waitFor() the process before getting the contents. ThreadStreamReader std = new ThreadStreamReader(process.getInputStream(), false, null); ThreadStreamReader err = new ThreadStreamReader(process.getErrorStream(), false, null); std.start(); err.start(); while (process.isAlive()) { if (monitor.isCanceled()) { std.stopGettingOutput(); err.stopGettingOutput(); return; } synchronized (this) { try { this.wait(20); } catch (InterruptedException e) { // Just proceed to another check. } } } if (monitor.isCanceled()) { std.stopGettingOutput(); err.stopGettingOutput(); return; } // Wait for the other threads to finish getting the output try { std.join(); } catch (InterruptedException e) { } try { err.join(); } catch (InterruptedException e) { } if (monitor.isCanceled()) { std.stopGettingOutput(); err.stopGettingOutput(); return; } String output = std.getAndClearContents(); String errors = err.getAndClearContents(); afterRunProcess(output, errors, out); } }; this.processWatchDoc.start(); } public void afterRunProcess(String output, String errors, IOConsoleOutputStream out) { write("PyLint: The stdout of the command line is:", out, output); write("PyLint: The stderr of the command line is:", out, errors); StringTokenizer tokenizer = new StringTokenizer(output, "\r\n"); //Set up local values for severity int wSeverity = PyLintPrefPage.wSeverity(); int eSeverity = PyLintPrefPage.eSeverity(); int fSeverity = PyLintPrefPage.fSeverity(); int cSeverity = PyLintPrefPage.cSeverity(); int rSeverity = PyLintPrefPage.rSeverity(); if (monitor.isCanceled()) { return; } if (output.indexOf("Traceback (most recent call last):") != -1) { Throwable e = new RuntimeException("PyLint ERROR: \n" + output); Log.log(e); return; } if (errors.indexOf("Traceback (most recent call last):") != -1) { Throwable e = new RuntimeException("PyLint ERROR: \n" + errors); Log.log(e); return; } while (tokenizer.hasMoreTokens()) { String tok = tokenizer.nextToken(); if (monitor.isCanceled()) { return; } try { int priority = 0; int indexOfDoublePoints = tok.indexOf(":"); if (indexOfDoublePoints != 1) { continue; } char c = tok.charAt(0); switch (c) { case 'C': priority = cSeverity; break; case 'R': priority = rSeverity; break; case 'W': priority = wSeverity; break; case 'E': priority = eSeverity; break; case 'F': priority = fSeverity; break; } if (priority > -1) { // priority == -1: ignore, 0=info, 1=warning, 2=error. try { int line = -1; int column = -1; String messageId = ""; Matcher m = PYLINT_MATCH_PATTERN.matcher(tok); if (m.matches()) { line = Integer.parseInt(tok.substring(m.start(1), m.end(1))); column = Integer.parseInt(tok.substring(m.start(2), m.end(2))); messageId = tok.substring(m.start(4), m.end(4)).trim(); tok = tok.substring(m.start(3), m.end(3)).trim(); } else { continue; } IRegion region = null; try { region = document.getLineInformation(line - 1); } catch (Exception e) { region = document.getLineInformation(line); } String lineContents = document.get(region.getOffset(), region.getLength()); if (CheckAnalysisErrors.isPyLintErrorHandledAtLine(lineContents, messageId)) { continue; } addToMarkers(tok, priority, messageId, line - 1, column, lineContents); } catch (RuntimeException e2) { Log.log(e2); } } } catch (Exception e1) { Log.log(e1); } } } private static Pattern PYLINT_MATCH_PATTERN = Pattern .compile("\\A[CRWEF]:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*:(.*)\\((.*)\\)\\s*\\Z"); private void addToMarkers(String tok, int priority, String id, int line, int column, String lineContents) { Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put(PYLINT_MESSAGE_ID, id); markers.add(new PyMarkerUtils.MarkerInfo(document, "PyLint: " + tok, PYLINT_PROBLEM_MARKER, priority, false, false, line, column, line, lineContents.length(), additionalInfo)); } /** * Waits for the PyLint processing to finish (note that canceling the monitor should also * stop the analysis/kill the related process). */ public void join() { if (processWatchDoc != null) { try { processWatchDoc.join(); } catch (InterruptedException e) { // If interrrupted, log and got through with it. Log.log(e); } } } } private static void write(String cmdLineToExe, IOConsoleOutputStream out, Object... args) { try { if (out != null) { synchronized (lock) { if (args != null) { for (Object arg : args) { if (arg instanceof String) { cmdLineToExe += " " + arg; } else if (arg instanceof String[]) { String[] strings = (String[]) arg; for (String string : strings) { cmdLineToExe += " " + string; } } } } out.write(cmdLineToExe + "\n"); } } } catch (IOException e) { Log.log(e); } } /** * Helper class to monitor the cancel state of another monitor. */ private static class NullProgressMonitorWrapper extends NullProgressMonitor { private IProgressMonitor wrap; public NullProgressMonitorWrapper(IProgressMonitor monitor) { this.wrap = monitor; } @Override public boolean isCanceled() { return super.isCanceled() || this.wrap.isCanceled(); } } private PyLintAnalysis pyLintRunnable; /** * When we start visiting some resource, we create the process which will do the PyLint analysis. */ @Override public void startVisit() { if (document == null || resource == null || PyLintPrefPage.usePyLint() == false) { deleteMarkers(); return; } IProject project = resource.getProject(); PythonNature pythonNature = PythonNature.getPythonNature(project); try { // PyLint can only be used for python projects if (pythonNature.getInterpreterType() != IInterpreterManager.INTERPRETER_TYPE_PYTHON) { deleteMarkers(); return; } } catch (Exception e) { deleteMarkers(); return; } if (project != null && resource instanceof IFile) { IFile file = (IFile) resource; IPath location = file.getRawLocation(); if (location != null) { pyLintRunnable = new PyLintAnalysis(resource, document, location, new NullProgressMonitorWrapper(monitor)); try { IOConsoleOutputStream out = getConsoleOutputStream(); pyLintRunnable.createPyLintProcess(out); } catch (final Exception e) { Log.log(e); } } } } @Override public void join() { if (pyLintRunnable != null) { pyLintRunnable.join(); } } @Override public List<MarkerInfo> getMarkers() { if (pyLintRunnable == null) { return null; } return pyLintRunnable.markers; } private static IOConsoleOutputStream getConsoleOutputStream() throws MalformedURLException { if (PyLintPrefPage.useConsole()) { return MessageConsoles.getConsoleOutputStream("PyLint", UIConstants.PY_LINT_ICON); } else { return null; } } }