All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.rsta.ac.js.JsHinter Maven / Gradle / Ivy

/*
 * 01/30/2014
 *
 * Copyright (C) 2014 Robert Futrell
 * robert_futrell at users.sourceforge.net
 * http://fifesoft.com/rsyntaxtextarea
 *
 * This library is distributed under a modified BSD license.  See the included
 * RSTALanguageSupport.License.txt file for details.
 */
package org.fife.rsta.ac.js;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.text.BadLocationException;

import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult;
import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice;
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;


/**
 * Launches jshint as an external process to parse JavaScript in an
 * {@link RSyntaxTextArea}.  Note that this is pretty inefficient, and was
 * mainly done as a test of jshint integration.  In the future, the external
 * process should be launched in a separate thread.
 *
 * @author Robert Futrell
 * @version 1.0
 */
class JsHinter {

	private JavaScriptParser parser;
//	private RSyntaxDocument doc;
	private DefaultParseResult result;

	private static final Map MARK_STRATEGIES;
	static {
		MARK_STRATEGIES = new HashMap();
		MARK_STRATEGIES.put("E015", MarkStrategy.MARK_CUR_TOKEN); // Unclosed regular expression.
		MARK_STRATEGIES.put("E019", MarkStrategy.MARK_CUR_TOKEN); // Unmatched '{a}'
		MARK_STRATEGIES.put("E030", MarkStrategy.MARK_CUR_TOKEN); // Expected an identifier and instead saw '{a}'.
		MARK_STRATEGIES.put("E041", MarkStrategy.STOP_PARSING); // Unrecoverable syntax error.
		MARK_STRATEGIES.put("E042", MarkStrategy.STOP_PARSING); // Stopping.
		MARK_STRATEGIES.put("E043", MarkStrategy.STOP_PARSING); // Too many errors.
		MARK_STRATEGIES.put("W004", MarkStrategy.MARK_PREV_NON_WS_TOKEN); // '{a}' is already defined.
		MARK_STRATEGIES.put("W015", MarkStrategy.MARK_CUR_TOKEN); // Expected {a} to have an indentation of {b} instead at {c}.
		MARK_STRATEGIES.put("W032", MarkStrategy.MARK_PREV_TOKEN); // Unnecessary semicolon.
		MARK_STRATEGIES.put("W033", MarkStrategy.MARK_PREV_TOKEN); // Missing semicolon.
		MARK_STRATEGIES.put("W060", MarkStrategy.MARK_CUR_TOKEN); // document.write can be a form of eval.
		MARK_STRATEGIES.put("W098", MarkStrategy.MARK_PREV_TOKEN); // '{a}' is defined but never used.
		MARK_STRATEGIES.put("W116", MarkStrategy.MARK_PREV_TOKEN); // Expected '{a}' and instead saw '{b}'.
		MARK_STRATEGIES.put("W117", MarkStrategy.MARK_CUR_TOKEN); // '{a}' is not defined.
	}


	private JsHinter(JavaScriptParser parser, RSyntaxDocument doc,
			DefaultParseResult result) {
		this.parser = parser;
//		this.doc = doc;
		this.result = result;
	}


	public static void parse(JavaScriptParser parser, RSyntaxTextArea textArea,
			DefaultParseResult result) throws IOException {

		String stdout = null;
		RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();

		List command = new ArrayList();
		if (File.separatorChar=='\\') {
			command.add("cmd.exe");
			command.add("/c");
		}
		else {
			command.add("/bin/sh");
			command.add("-c");
		}
		command.add("jshint");
		File jshintrc = parser.getJsHintRCFile(textArea);
		if (jshintrc!=null) {
			command.add("--config");
			command.add(jshintrc.getAbsolutePath());
		}
		command.add("--verbose"); // Allows to decide error vs. warning
		command.add("-");

		ProcessBuilder pb = new ProcessBuilder(command);
		pb.redirectErrorStream();

		Process p = pb.start();
		PrintWriter w = new PrintWriter(p.getOutputStream());

		// Create threads to read the stdout and stderr of the external
		// process.  If we do not do it this way, the process may
		// deadlock.
		InputStream outStream = p.getInputStream();
		InputStream errStream = p.getErrorStream();
		StreamReaderThread stdoutThread = new StreamReaderThread(outStream);
		StreamReaderThread stderrThread = new StreamReaderThread(errStream);
		stdoutThread.start();

		try {

			String text = doc.getText(0, doc.getLength());
			w.print(text);
			w.flush();
			w.close();

			/*rc = */p.waitFor();
			p = null;

			// Save the stdout and stderr. Don't interrupt reader threads;
			// just wait for them to terminate normally.
			//stdoutThread.interrupt();
			//stderrThread.interrupt();
			stdoutThread.join();
			stderrThread.join();
			stdout = stdoutThread.getStreamOutput();

		} catch (InterruptedException ie) {
			//ie.printStackTrace();
			stdoutThread.interrupt();
			//lastError = ie;
		} catch (BadLocationException ble) {
			ble.printStackTrace(); // Never happens
		} finally {
			if (outStream!=null) {
				outStream.close();
			}
			w.close();
			if (p!=null) {
				p.destroy();
			}
		}

		JsHinter hinter = new JsHinter(parser, doc, result);
		hinter.parseOutput(doc, stdout);

	}


	private void parseOutput(RSyntaxDocument doc, String output) {

		// Line format:
		// stdin: line xx, col yy, error-or-warning-text. (Ennn)

//		Element root = doc.getDefaultRootElement();
//		int indent = parser.getJsHintIndent();

		String[] lines = output.split("\r?\n");
//		OUTER:
		for (String line : lines) {

			String origLine = line;
//			System.out.println(line);
			if (line.startsWith("stdin: line ")) {
				line = line.substring("stdin: line ".length());
				int end = 0;
				while (Character.isDigit(line.charAt(end))) {
					end++;
				}
				int lineNum = Integer.parseInt(line.substring(0, end)) - 1;
				if (lineNum==-1) {
					// Probably bad option to jshint, e.g.
					// stdin: line 0, col 0, Bad option: 'ender'. (E001)
					// Just give them the entire error
					DefaultParserNotice dpn = new DefaultParserNotice(parser,
							origLine, 0);
					result.addNotice(dpn);
					continue;
				}
				line = line.substring(end);
				if (line.startsWith(", col ")) {
					line = line.substring(", col ".length());
					end = 0;
					while (Character.isDigit(line.charAt(end))) {
						end++;
					}
//					int col = Integer.parseInt(line.substring(0, end)) - 1;
					line = line.substring(end);
					if (line.startsWith(", ")) {

//						int lineOffs = getLineOffset(lineNum);
//						int offs = lineOffs + col;

						String msg = line.substring(", ".length());
						String errorCode = null;

						// Ends in "(E0xxx)" or "(W0xxx)"
						ParserNotice.Level noticeType= ParserNotice.Level.ERROR;
						if (msg.charAt(msg.length()-1)==')') {
							int openParen = msg.lastIndexOf('(');
							errorCode = msg.substring(openParen+1,
									msg.length()-1);
							if (msg.charAt(openParen+1)=='W') {
								noticeType = ParserNotice.Level.WARNING;
							}
							msg = msg.substring(0, openParen-1);
						}

						DefaultParserNotice dpn = null;
						MarkStrategy markStrategy = getMarkStrategy(errorCode);
						switch (markStrategy) {
//							case MARK_PREV_TOKEN:
//								offs--;
//								// Fall through
//							case MARK_CUR_TOKEN:
//								lineNum = root.getElementIndex(offs);
//								offs = adjustOffset(doc, lineNum, offs, indent);
//								Token t = RSyntaxUtilities.getTokenAtOffset(doc,
//										offs);
//								if (t!=null) {
//									dpn = createNotice(doc, msg, lineNum,
//											t.getOffset(), t.length(), indent);
//								}
//								else if (offs==doc.getLength()) {
//									dpn = createNotice(doc, msg, lineNum,
//											offs, 1, indent);
//								}
//								break;
//							case MARK_PREV_NON_WS_TOKEN:
//								t = RSyntaxUtilities.
//									getPreviousImportantTokenFromOffs(doc, offs-1);
//								lineNum = root.getElementIndex(offs-1);
//								dpn = createNotice(doc, msg, lineNum,
//										t.getOffset(), t.length(), indent);
//								break;
//							case IGNORE:
//								break; // No ParserNotice
//							case STOP_PARSING:
//								break OUTER;
							default:
							case MARK_LINE:
								// Just mark the whole line, as the offset returned
								// by JSHint can vary.
								dpn = new DefaultParserNotice(parser, msg, lineNum);
								break;
						}
//						if (dpn!=null) {
							dpn.setLevel(noticeType);
							result.addNotice(dpn);
//						}

					}
				}
			}

		}

	}


//	private final int adjustOffset(RSyntaxDocument doc, int line, int offs,
//			int indent) {
//		if (indent>-1) {
//			Element root = doc.getDefaultRootElement();
//			Element elem = root.getElement(line);
//			if (elem!=null) {
//				int start = elem.getStartOffset();
//				int cur = start;
//				try {
//					while ((start-cur)
	 * this JavaWorld article.
	 * 
	 * @author Robert Futrell
	 */
	static class StreamReaderThread extends Thread {

		private BufferedReader r;
		private StringBuilder buffer;

		/**
		 * Constructor.
		 * 
		 * @param in The stream (stdout or stderr) to read from.
		 */
		public StreamReaderThread(InputStream in) {
			r = new BufferedReader(new InputStreamReader(in));
			this.buffer = new StringBuilder();
		}

		/**
		 * Returns the output read from the stream.
		 * 
		 * @return The stream's output, as a String.
		 */
		public String getStreamOutput() {
			return buffer.toString();
		}

		/**
		 * Continually reads from the output stream until this thread is
		 * interrupted.
		 */
		@Override
		public void run() {
			String line;
			try {
				while ((line=r.readLine())!=null) {
					buffer.append(line).append('\n');
					//System.out.println(line);
				}
			} catch (IOException ioe) {
				buffer.append("IOException occurred: " + ioe.getMessage());
			}
		}

	}


	/**
	 * What exactly to mark as the error in the document, based on an error
	 * code from JSHint.
	 */
	private enum MarkStrategy {
		MARK_LINE, MARK_CUR_TOKEN, MARK_PREV_TOKEN, MARK_PREV_NON_WS_TOKEN,
		IGNORE, STOP_PARSING
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy