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

org.spiffyui.maven.plugins.JSLintMojo Maven / Gradle / Ivy

There is a newer version: 1.7
Show newest version
package org.spiffyui.maven.plugins;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import com.googlecode.jslint4java.JSLint;
import com.googlecode.jslint4java.JSLintBuilder;
import com.googlecode.jslint4java.JSLintResult;
import com.googlecode.jslint4java.Option;
import com.googlecode.jslint4java.UnicodeBomInputStream;
import com.googlecode.jslint4java.formatter.JSLintResultFormatter;
import com.googlecode.jslint4java.formatter.JSLintXmlFormatter;
import com.googlecode.jslint4java.formatter.PlainFormatter;
import com.googlecode.jslint4java.maven.FileLister;
import com.googlecode.jslint4java.maven.ReportWriter;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject;

/**
 * Validates JavaScript using jslint4java.  Initially copied from: 
 * https://github.com/happygiraffe/jslint4java/blob/master/jslint4java-maven-plugin/src/main/java/com/googlecode/jslint4java/maven/JSLintMojo.java 
 *
 * @goal spiffy-jslint
 * @phase verify
 */
public class JSLintMojo extends AbstractMojo 
{

    private static final String DEFAULT_INCLUDES = "**/*.js";

    private static final String JSLINT_XML = "jslint.xml";
    
    private static final String JSLINT_FILE = "org/spiffyui/maven/plugins/jslint.js";
    
    private static final Charset UTF8 = Charset.forName("UTF-8");
    
    private ContextFactory contextFactory = new ContextFactory();

    /**
     * Specifies the the source files to be excluded for JSLint (relative to
     * {@link #defaultSourceFolder}). Maven applies its own defaults.
     *
     * @parameter expression="${jslint.excludes}" 
     *            property="excludes"
     */
    private final List excludes = new ArrayList();

    /**
     * Specifies the the source files to be used for JSLint (relative to
     * {@link #defaultSourceFolder}). If none are given, defaults to
     * **/*.js.
     *
     * @parameter expression="${jslint.includes}" 
     *            property="includes"
     */
    private final List includes = new ArrayList();

    /**
     * Specifies the location of the default source folder to be used for
     * JSLint. Note that this is just used for filling in the default, as it
     * resolves the default value correctly. Anything you specify will override
     * it.
     *
     * @parameter expression="${spiffyui.src.js}" 
     *            default-value="${basedir}/src/main/js" 
     * @required
     */
    private File defaultSourceFolder;

    /**
     * Which locations should JSLint look for JavaScript files in? Defaults to
     * ${basedir}/src/main/js.
     *
     * @parameter
     */
    private File[] sourceFolders = new File[] {};

    /**
     * Which JSLint {@link Option}s to set.
     *
     * @parameter
     */
    private final Map options = new HashMap();

    /**
     * What encoding should we use to read the JavaScript files?  Defaults to UTF-8.
     *
     * @parameter expression="${encoding}"
     *            default-value="${project.build.sourceEncoding}"
     */
    private String encoding = "UTF-8";

    /**
     * Base folder for report output.
     *
     * @parameter expression="${jslint.outputFolder}"
     *            default-value="${project.build.directory}"
     */
    private File outputFolder = new File("target");

    /**
     * Fail the build if JSLint detects any problems.
     *
     * @parameter expression="${jslint.failOnError}" default-value="true"
     */
    private boolean failOnError = true;

    /**
     * An alternative JSLint to use.
     *
     * @parameter expression="${jslint.source}"
     */
    private File jslintSource;

    /**
     * How many seconds JSLint is allowed to run.
     *
     * @parameter expression="${jslint.timeout}"
     */
    private long timeout;

    /** Add a single option.  For testing only. */
    void addOption(Option sloppy, String value)
    {
        options.put(sloppy.name().toLowerCase(Locale.ENGLISH), value);
    }

    /**
     * Set the default includes.
     *
     * @param includes
     */
    private void applyDefaults()
    {
        if (includes.isEmpty()) {
            includes.add(DEFAULT_INCLUDES);
        }
        if (sourceFolders.length == 0) {
            sourceFolders = new File[] { 
                defaultSourceFolder
            };
        }
    }

    private void applyOptions(JSLint jsLint) throws MojoExecutionException
    {
        for (Entry entry : options.entrySet()) {
            if (entry.getValue() == null || entry.getValue().equals("")) {
                continue;
            }
            Option option;
            try {
                option = Option.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
            } catch (IllegalArgumentException e) {
                throw new MojoExecutionException("unknown option: " + entry.getKey());
            }
            jsLint.addOption(option, entry.getValue());
        }
        
        if (options.size() == 0) {
            jsLint.addOption(Option.UNDEF, "true");
            jsLint.addOption(Option.VARS, "true");
            jsLint.addOption(Option.REGEXP, "true");
            jsLint.addOption(Option.WHITE, "true");
            jsLint.addOption(Option.SLOPPY, "true");
        }
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        JSLint jsLint = applyJSlintSource();
        applyDefaults();
        applyOptions(jsLint);
        List files = getFilesToProcess();
        int failures = 0;
        ReportWriter reporter = new ReportWriter(new File(outputFolder, JSLINT_XML), new JSLintXmlFormatter());
        try {
            reporter.open();
            for (File file : files) {
                JSLintResult result = lintFile(jsLint, file);
                failures += result.getIssues().size();
                logIssues(result, reporter);
            }
        } finally {
            reporter.close();
        }
        if (failures > 0) {
            String message = "JSLint found " + failures + " problems in " + files.size() + " files";
            if (failOnError) {
                throw new MojoFailureException(message);
            } else {
                getLog().info(message);
            }
        }
    }

    private JSLint applyJSlintSource() throws MojoExecutionException
    {
        JSLintBuilder builder = new JSLintBuilder();
        if (timeout > 0) {
            builder.timeout(timeout);
        }
        if (jslintSource != null) {
            try {
                return builder.fromFile(jslintSource, Charset.forName(encoding));
            } catch (IOException e) {
                throw new MojoExecutionException("Cant' load jslint.js", e);
            }
        } else {
            try {
                Context cx = contextFactory.enter();
                ScriptableObject scope = cx.initStandardObjects();
                Reader r = new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(JSLINT_FILE), UTF8);
                cx.evaluateReader(scope, r, "jslint.js", 1, null);
                return new JSLint(contextFactory, scope);
            } catch (IOException ioe) {
                throw new MojoExecutionException("Cant' load jslint.js", ioe);
            } finally {
                Context.exit();
            }
        }
    }

    // Visible for testing only.
    String getEncoding()
    {
        return encoding;
    }

    /**
     * Process includes and excludes to work out which files we are interested
     * in. Originally nicked from CheckstyleReport, now looks nothing like it.
     *
     * @return a {@link List} of {@link File}s.
     */
    private List getFilesToProcess() throws MojoExecutionException
    {
        // Defaults.
        getLog().debug("includes=" + includes);
        getLog().debug("excludes=" + excludes);

        List files = new ArrayList();
        for (File folder : sourceFolders) {
            getLog().debug("searching " + folder);
            try {
                files.addAll(new FileLister(folder, includes, excludes).files());
            } catch (IOException e) {
                // Looking in FileUtils, this is a "can never happen". *sigh*
                throw new MojoExecutionException("Error listing files", e);
            }
        }

        return files;
    }

    // Visible for testing only.
    Map getOptions()
    {
        return options;
    }

    private JSLintResult lintFile(JSLint jsLint, File file) throws MojoExecutionException
    {
        getLog().debug("lint " + file);
        BufferedReader reader = null;
        try {
            InputStream stream = new UnicodeBomInputStream(new FileInputStream(file));
            reader = new BufferedReader(new InputStreamReader(stream, getEncoding()));
            return jsLint.lint(file.toString(), reader);
        } catch (FileNotFoundException e) {
            throw new MojoExecutionException("file not found: " + file, e);
        } catch (UnsupportedEncodingException e) {
            // Can never happen.
            throw new MojoExecutionException("unsupported character encoding UTF-8", e);
        } catch (IOException e) {
            throw new MojoExecutionException("problem whilst linting " + file, e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    throw new MojoExecutionException("Error closing reader", e);
                }
            }
        }
    }

    private void logIssues(JSLintResult result, ReportWriter reporter)
    {
        reporter.report(result);
        if (result.getIssues().isEmpty()) {
            return;
        }
        logIssuesToConsole(result);
    }

    private void logIssuesToConsole(JSLintResult result)
    {
        JSLintResultFormatter formatter = new PlainFormatter();
        String report = formatter.format(result);
        for (String line : report.split("\n")) {
            getLog().info(line);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy