Package com.googlecode.jslint4java

Source Code of com.googlecode.jslint4java.JSLint$JSFunctionConverter

package com.googlecode.jslint4java;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.UniqueTag;

import com.googlecode.jslint4java.Issue.IssueBuilder;
import com.googlecode.jslint4java.JSFunction.Builder;
import com.googlecode.jslint4java.JSLintResult.ResultBuilder;

/**
* A utility class to check JavaScript source code for potential problems.
*
* @author dom
* @see JSLintBuilder Construction of JSLint
*/
public class JSLint {

    // Uncomment to enable the rhino debugger.
    // static {
    // org.mozilla.javascript.tools.debugger.Main.mainEmbedded(null);
    // }

    /** What should {@code indent} be set to by default? */
    private static final String DEFAULT_INDENT = "4";

    /** What should {@code maxerr} be set to by default? */
    private static final String DEFAULT_MAXERR = "50";

    /**
     * A helper class for interpreting the output of {@code JSLINT.data()}.
     */
    private static final class JSFunctionConverter implements Util.Converter<JSFunction> {
        public JSFunction convert(Object obj) {
            Scriptable scope = (Scriptable) obj;
            String name = Util.stringValue("name", scope);
            int line = Util.intValue("line", scope);
            Builder b = new JSFunction.Builder(name, line);
            b.last(Util.intValue("last", scope));
            for (String param : Util.listValueOfType("parameter", String.class, scope)) {
                b.addParam(param);
            }
            for (String closure : Util.listValueOfType("closure", String.class, scope)) {
                b.addClosure(closure);
            }
            for (String var : Util.listValueOfType("var", String.class, scope)) {
                b.addVar(var);
            }
            for (String exception : Util.listValueOfType("exception", String.class, scope)) {
                b.addException(exception);
            }
            for (String outer : Util.listValueOfType("outer", String.class, scope)) {
                b.addOuter(outer);
            }
            for (String unused : Util.listValueOfType("unused", String.class, scope)) {
                b.addUnused(unused);
            }
            for (String global : Util.listValueOfType("global", String.class, scope)) {
                b.addGlobal(global);
            }
            for (String label : Util.listValueOfType("label", String.class, scope)) {
                b.addLabel(label);
            }
            return b.build();
        }
    }

    private final Map<Option, Object> options = new EnumMap<Option, Object>(Option.class);

    private final ContextFactory contextFactory;

    private final Function lintFunc;

    /**
     * Create a new {@link JSLint} object. You must pass in a {@link Function}, which is the JSLINT
     * function defined by jslint.js. You are expected to use {@link JSLintBuilder} rather than
     * calling this constructor.
     */
    JSLint(ContextFactory contextFactory, Function lintFunc) {
        this.contextFactory = contextFactory;
        this.lintFunc = lintFunc;
    }

    /**
     * Add an option to change the behaviour of the lint. This will be passed in
     * with a value of "true".
     *
     * @param o
     *            Any {@link Option}.
     */
    public void addOption(Option o) {
        options.put(o, Boolean.TRUE);
    }

    /**
     * Add an option to change the behaviour of the lint. The option will be
     * parsed as appropriate using an {@link OptionParser}.
     *
     * @param o
     *            Any {@link Option}.
     * @param arg
     *            The value to associate with <i>o</i>.
     */
    public void addOption(Option o, String arg) {
        OptionParser optionParser = new OptionParser();
        options.put(o, optionParser.parse(o.getType(), arg));
    }

    /**
     * Set options that should always be present. This mirrors what jslint.com
     * does.
     */
    private void applyDefaultOptions() {
        if (!options.containsKey(Option.INDENT)) {
            addOption(Option.INDENT, DEFAULT_INDENT);
        }
        if (!options.containsKey(Option.MAXERR)) {
            addOption(Option.MAXERR, DEFAULT_MAXERR);
        }
    }

    /**
     * Assemble the {@link JSLintResult} object.
     */
    @NeedsContext
    private JSLintResult buildResults(final String systemId, final long startNanos, final long endNanos) {
        return (JSLintResult) contextFactory.call(new ContextAction() {
            public Object run(Context cx) {
                ResultBuilder b = new JSLintResult.ResultBuilder(systemId);
                b.duration(TimeUnit.NANOSECONDS.toMillis(endNanos - startNanos));
                for (Issue issue : readErrors(systemId)) {
                    b.addIssue(issue);
                }

                // Collect a report on what we've just linted.
                b.report(callReport(false));

                // Extract JSLINT.data() output and set it on the result.
                Object o = lintFunc.get("data", lintFunc);
                // Real JSLINT will always have this, but some of my test stubs don't.
                if (o != UniqueTag.NOT_FOUND) {
                    Function reportFunc = (Function) o;
                    Scriptable data = (Scriptable) reportFunc.call(cx, lintFunc, null,
                            Context.emptyArgs);
                    for (String global : Util.listValueOfType("global", String.class, data)) {
                        b.addGlobal(global);
                    }
                    b.json(Util.booleanValue("json", data));
                    for (JSFunction f : Util.listValue("functions", data, new JSFunctionConverter())) {
                        b.addFunction(f);
                    }
                }

                // Extract the list of properties. Note that we don't expose the counts, as it
                // doesn't seem that useful.
                Object properties = lintFunc.get("property", lintFunc);
                if (properties != UniqueTag.NOT_FOUND) {
                    for (Object id: ScriptableObject.getPropertyIds((Scriptable) properties)) {
                        b.addProperty(id.toString());
                    }
                }

                return b.build();
            }
        });
    }

    /**
     * Construct a JSLint error report. This is in two parts: a list of errors, and an optional
     * function report.
     *
     * @param errorsOnly
     *            if the function report should be omitted.
     * @return the report, an HTML string.
     */
    @NeedsContext
    private String callReport(final boolean errorsOnly) {
        return (String) contextFactory.call(new ContextAction() {
            // TODO: This would probably benefit from injecting an API to manage JSLint.
            public Object run(Context cx) {
                Function fn = null;
                Object value = null;
                StringBuilder sb = new StringBuilder();

                // Look up JSLINT.data.
                value = lintFunc.get("data", lintFunc);
                if (value == UniqueTag.NOT_FOUND) {
                    return "";
                }
                fn = (Function) value;
                // Call JSLINT.data().  This returns a JS data structure that we need below.
                Object data = fn.call(cx, lintFunc, null, Context.emptyArgs);

                // Look up JSLINT.error_report.
                value = lintFunc.get("error_report", lintFunc);
                // Shouldn't happen ordinarily, but some of my tests don't have it.
                if (value != UniqueTag.NOT_FOUND) {
                    fn = (Function) value;
                    // Call JSLint.report().
                    sb.append(fn.call(cx, lintFunc, null, new Object[] { data }));
                }

                if (!errorsOnly) {
                    // Look up JSLINT.report.
                    value = lintFunc.get("report", lintFunc);
                    // Shouldn't happen ordinarily, but some of my tests don't have it.
                    if (value != UniqueTag.NOT_FOUND) {
                        fn = (Function) value;
                        // Call JSLint.report().
                        sb.append(fn.call(cx, lintFunc, null, new Object[] { data }));
                    }
                }
                return sb.toString();
            }
        });
    }

    @NeedsContext
    private void doLint(final String javaScript) {
        contextFactory.call(new ContextAction() {
            public Object run(Context cx) {
                String src = javaScript == null ? "" : javaScript;
                Object[] args = new Object[] { src, optionsAsJavaScriptObject() };
                // JSLINT actually returns a boolean, but we ignore it as we always go
                // and look at the errors in more detail.
                lintFunc.call(cx, lintFunc, null, args);
                return null;
            }
        });
    }

    /**
     * Return the version of jslint in use.
     */
    public String getEdition() {
        return (String) lintFunc.get("edition", lintFunc);
    }

    /**
     * Check for problems in a {@link Reader} which contains JavaScript source.
     *
     * @param systemId
     *            a filename
     * @param reader
     *            a {@link Reader} over JavaScript source code.
     *
     * @return a {@link JSLintResult}.
     */
    public JSLintResult lint(String systemId, Reader reader) throws IOException {
        return lint(systemId, Util.readerToString(reader));
    }

    /**
     * Check for problems in JavaScript source.
     *
     * @param systemId
     *            a filename
     * @param javaScript
     *            a String of JavaScript source code.
     *
     * @return a {@link JSLintResult}.
     */
    public JSLintResult lint(String systemId, String javaScript) {
        // This is synchronized, even though Rhino is thread safe, because we have multiple
        // accesses to the scope, which store state in between them.  This synchronized block
        // is slightly larger than I would like, but in practical terms, it doesn't make much
        // difference.  The cost of running lint is larger than the cost of pulling out the
        // results.
        synchronized (this) {
            long before = System.nanoTime();
            doLint(javaScript);
            long after = System.nanoTime();
            return buildResults(systemId, before, after);
        }
    }

    /**
     * Turn the set of options into a JavaScript object, where the key is the
     * name of the option and the value is true.
     */
    @NeedsContext
    private Scriptable optionsAsJavaScriptObject() {
        return (Scriptable) contextFactory.call(new ContextAction() {
            public Object run(Context cx) {
                applyDefaultOptions();
                Scriptable opts = cx.newObject(lintFunc);
                for (Entry<Option, Object> entry : options.entrySet()) {
                    String key = entry.getKey().getLowerName();
                    // Use our "custom" version in order to get native arrays.
                    Object value = Util.javaToJS(entry.getValue(), opts);
                    opts.put(key, opts, value);
                }
                return opts;
            }
        });
    }

    private List<Issue> readErrors(String systemId) {
        ArrayList<Issue> issues = new ArrayList<Issue>();
        Scriptable errors = (Scriptable) lintFunc.get("errors", lintFunc);
        int count = Util.intValue("length", errors);
        for (int i = 0; i < count; i++) {
            Scriptable err = (Scriptable) errors.get(i, errors);
            // JSLINT spits out a null when it cannot proceed.
            // TODO Should probably turn i-1th issue into a "fatal".
            if (err != null) {
                issues.add(IssueBuilder.fromJavaScript(systemId, err));
            }
        }
        return issues;
    }

    /**
     * Report on what variables / functions are in use by this code.
     *
     * @param javaScript
     * @return an HTML report.
     */
    public String report(String javaScript) {
        return report(javaScript, false);
    }

    /**
     * Report on what variables / functions are in use by this code.
     *
     * @param javaScript
     * @param errorsOnly
     *            If a report consisting solely of the problems is desired.
     * @return an HTML report.
     */
    public String report(String javaScript, boolean errorsOnly) {
        // Run the lint function itself as prep.
        doLint(javaScript);

        // The run the reporter.
        return callReport(errorsOnly);
    }

    /**
     * Clear out all options that have been set with {@link #addOption(Option)}.
     */
    public void resetOptions() {
        options.clear();
    }
}
TOP

Related Classes of com.googlecode.jslint4java.JSLint$JSFunctionConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.