Permalink
Browse files

Intial JsHint plugin for JavaScript static analysis.

  • Loading branch information...
1 parent ac656ce commit 636a4783ec64c8fd6b465bc1be587e20b9c21f81 @alkemist alkemist committed May 21, 2012
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.javascript.jshint
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.addGradlePublicJsRepoScript
+
+class JsHintPluginIntegrationTest extends WellBehavedPluginTest {
+
+ def setup() {
+ applyPlugin()
+ addGradlePublicJsRepoScript(buildFile)
+ buildFile << """
+ repositories.mavenCentral()
+ """
+ }
+
+ def "can analyse javascript"() {
+ given:
+ file("src/main/js/dir1/f1.js") << """
+ "a" == null
+ """
+
+ when:
+ buildFile << """
+ task jsHint(type: ${JsHint.name}) {
+ source fileTree("src/main/js")
+ }
+ """
+
+ then:
+ succeeds "jsHint"
+
+ and:
+ ":jsHint" in nonSkippedTasks
+ }
+
+}
@@ -16,8 +16,7 @@
package org.gradle.plugins.javascript.coffeescript.compile.internal.rhino;
-import org.apache.commons.io.FileUtils;
-import org.gradle.api.UncheckedIOException;
+import org.gradle.api.Action;
import org.gradle.api.internal.file.RelativeFile;
import org.gradle.plugins.javascript.coffeescript.compile.internal.CoffeeScriptCompileDestinationCalculator;
import org.gradle.plugins.javascript.coffeescript.compile.internal.SerializableCoffeeScriptCompileSpec;
@@ -26,12 +25,17 @@
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Scriptable;
-import java.io.*;
+import static org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils.*;
public class CoffeeScriptCompilerWorker implements RhinoWorker<Boolean, SerializableCoffeeScriptCompileSpec> {
public Boolean process(SerializableCoffeeScriptCompileSpec spec) {
- Scriptable coffeeScriptScope = createCoffeeScriptScope(spec.getCoffeeScriptJs());
+ Scriptable coffeeScriptScope = parse(spec.getCoffeeScriptJs(), "UTF-8", new Action<Context>() {
+ public void execute(Context context) {
+ context.setOptimizationLevel(-1);
+ }
+ });
+
String encoding = spec.getOptions().getEncoding();
CoffeeScriptCompileDestinationCalculator destinationCalculator = new CoffeeScriptCompileDestinationCalculator(spec.getDestinationDir());
@@ -50,53 +54,12 @@ public Exception convertException(RhinoException rhinoException) {
return rhinoException;
}
- private String readFile(File file, String encoding) {
- try {
- return FileUtils.readFileToString(file, encoding);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private void writeFile(String content, File destination, String encoding) {
- try {
- FileUtils.writeStringToFile(destination, content, encoding);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private String compile(Scriptable rootScope, String source, String sourceName) {
- Context context = Context.enter();
- try {
- Scriptable compileScope = context.newObject(rootScope);
- compileScope.setParentScope(rootScope);
- compileScope.put("coffeeScriptSource", compileScope, source);
- return (String)context.evaluateString(compileScope, "CoffeeScript.compile(coffeeScriptSource, {});", sourceName, 0, null);
- } finally {
- Context.exit();
- }
- }
-
- private static Scriptable createCoffeeScriptScope(File coffeeScriptJs) {
- Context context = Context.enter();
- context.setOptimizationLevel(-1);
-
- Scriptable scope = context.initStandardObjects();
- try {
- // TODO we aren't considering the case where coffee-script.js is in a different encoding here
- Reader reader = new InputStreamReader(new FileInputStream(coffeeScriptJs), "UTF-8");
- try {
- context.evaluateReader(scope, reader, coffeeScriptJs.getName(), 0, null);
- } finally {
- reader.close();
+ private String compile(Scriptable rootScope, final String source, final String sourceName) {
+ return childScope(rootScope, new DefaultScopeOperation<String>() {
+ public String action(Scriptable compileScope, Context context) {
+ compileScope.put("coffeeScriptSource", compileScope, source);
+ return (String)context.evaluateString(compileScope, "CoffeeScript.compile(coffeeScriptSource, {});", sourceName, 0, null);
}
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- } finally {
- Context.exit();
- }
-
- return scope;
+ });
}
}
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.javascript.jshint;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.*;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.javascript.jshint.internal.JsHintResult;
+import org.gradle.plugins.javascript.jshint.internal.JsHintSpec;
+import org.gradle.plugins.javascript.jshint.internal.JsHintWorker;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+public class JsHint extends SourceTask {
+
+ private Object rhinoClasspath;
+ private Object jsHint;
+ private String encoding = "UTF-8";
+
+ @InputFiles
+ public FileCollection getRhinoClasspath() {
+ return getProject().files(rhinoClasspath);
+ }
+
+ public void setRhinoClasspath(Object rhinoClasspath) {
+ this.rhinoClasspath = rhinoClasspath;
+ }
+
+ @InputFiles
+ public FileCollection getJsHint() {
+ return getProject().files(jsHint);
+ }
+
+ public void setJsHint(Object jsHint) {
+ this.jsHint = jsHint;
+ }
+
+ @Input
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ @TaskAction
+ public void doJsHint() {
+ ProjectInternal projectInternal = (ProjectInternal)getProject();
+ Factory<WorkerProcessBuilder> workerProcessBuilderFactory = projectInternal.getServices().getFactory(WorkerProcessBuilder.class);
+ RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
+
+ RhinoWorkerHandle<JsHintResult, JsHintSpec> rhinoHandle = handleFactory.create(getRhinoClasspath(), createWorkerSpec(), getLogging().getLevel(), new Action<JavaExecSpec>() {
+ public void execute(JavaExecSpec javaExecSpec) {
+ javaExecSpec.setWorkingDir(getProject().getProjectDir());
+ }
+ });
+
+ JsHintSpec spec = new JsHintSpec();
+ spec.setSource(getSource().getFiles()); // flatten because we need to serialize
+ spec.setEncoding(getEncoding());
+ spec.setJsHint(getJsHint().getSingleFile());
+
+ JsHintResult result = rhinoHandle.process(spec);
+ setDidWork(true);
+
+ // TODO - this is all terribly lame. We need some proper reporting here.
+
+ Logger logger = getLogger();
+ boolean anyErrors = false;
+
+ for (Map.Entry<File, Map<String, Object>> fileEntry: result.getResults().entrySet()) {
+ File file = fileEntry.getKey();
+ Map<String, Object> data = fileEntry.getValue();
+
+ if (data.containsKey("errors")) {
+ anyErrors = true;
+
+ URI projectDirUri = getProject().getProjectDir().toURI();
+ @SuppressWarnings("unchecked") Map<String, Object> errors = (Map<String, Object>) data.get("errors");
+ if (!errors.isEmpty()) {
+ URI relativePath = projectDirUri.relativize(file.toURI());
+ logger.warn("JsHint errors for file: {}", relativePath.getPath());
+ for (Map.Entry<String, Object> errorEntry : errors.entrySet()) {
+ @SuppressWarnings("unchecked") Map<String, Object> error = (Map<String, Object>) errorEntry.getValue();
+ int line = Float.valueOf(error.get("line").toString()).intValue();
+ int character = Float.valueOf(error.get("character").toString()).intValue();
+ String reason = error.get("reason").toString();
+
+ logger.warn(" {}:{} > {}", new Object[] {line, character, reason});
+ }
+ }
+ }
+ }
+
+ if (anyErrors) {
+ throw new TaskExecutionException(this, new GradleException("JsHint detected errors"));
+ }
+ }
+
+ private RhinoWorkerSpec<JsHintResult, JsHintSpec> createWorkerSpec() {
+ return new RhinoWorkerSpec<JsHintResult, JsHintSpec>(JsHintResult.class, JsHintSpec.class, JsHintWorker.class);
+ }
+
+}
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.javascript.jshint;
+
+import org.gradle.api.file.FileCollection;
+
+public class JsHintExtension {
+
+ public static final String NAME = "jsHint";
+
+ public static final String DEFAULT_DEPENDENCY_VERSION = "r07";
+ public static final String DEFAULT_DEPENDENCY_GROUP = "com.jshint";
+ public static final String DEFAULT_DEPENDENCY_MODULE = "jshint";
+
+ public static final String CONFIGURATION_NAME = "jsHintPlugin";
+
+ private String version;
+ private FileCollection js;
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public FileCollection getJs() {
+ return js;
+ }
+
+ public void setJs(FileCollection js) {
+ this.js = js;
+ }
+}
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.javascript.jshint
+
+import org.gradle.api.Action
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.plugins.javascript.base.JavaScriptExtension
+import org.gradle.plugins.javascript.rhino.RhinoExtension
+import org.gradle.plugins.javascript.rhino.RhinoPlugin
+
+import static org.gradle.plugins.javascript.jshint.JsHintExtension.*
+
+class JsHintPlugin implements Plugin<Project> {
+
+ void apply(Project project) {
+ project.plugins.apply(RhinoPlugin)
+
+ JavaScriptExtension jsExtension = project.extensions.getByType(JavaScriptExtension)
+ JsHintExtension jsHintExtension = jsExtension.extensions.create(JsHintExtension.NAME, JsHintExtension)
+ Configuration configuration = addConfiguration(project.configurations, project.dependencies, jsHintExtension)
+
+ jsHintExtension.conventionMapping.with {
+ map("js") { configuration }
+ map("version") { DEFAULT_DEPENDENCY_VERSION }
+ }
+
+ RhinoExtension rhinoExtension = jsExtension.extensions.getByType(RhinoExtension)
+
+ project.tasks.withType(JsHint) { JsHint task ->
+ task.conventionMapping.map("rhinoClasspath") { rhinoExtension.classpath }
+ task.conventionMapping.map("jsHint") { jsHintExtension.js }
+ }
+ }
+
+ Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, JsHintExtension extension) {
+ Configuration configuration = configurations.add(JsHintExtension.CONFIGURATION_NAME)
+ configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
+ void execute(ResolvableDependencies resolvableDependencies) {
+ if (configuration.dependencies.empty) {
+ String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
+ Dependency dependency = dependencies.create(notation)
+ configuration.dependencies.add(dependency)
+ }
+ }
+ })
+ configuration
+
+ }
+}
Oops, something went wrong.

0 comments on commit 636a478

Please sign in to comment.