Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console;
import com.facebook.sonar.core.SonarConnection;
import com.facebook.sonar.core.SonarPlugin;
import com.facebook.sonar.plugins.console.iface.ConsoleCommandReceiver;
public class ConsoleSonarPlugin implements SonarPlugin {
private final JavascriptEnvironment mJavascriptEnvironment;
private JavascriptSession mJavascriptSession;
public ConsoleSonarPlugin(JavascriptEnvironment jsEnvironment) {
this.mJavascriptEnvironment = jsEnvironment;
}
@Override
public String getId() {
return "Console";
}
@Override
public void onConnect(SonarConnection connection) throws Exception {
ConsoleCommandReceiver.listenForCommands(connection, mJavascriptEnvironment);
}
public void onDisconnect() throws Exception {
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console;
import com.facebook.sonar.plugins.console.iface.ScriptingEnvironment;
import java.util.HashMap;
import java.util.Map;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
public class JavascriptEnvironment implements ScriptingEnvironment {
private final Map<String, Object> mBoundVariables;
private final ContextFactory mContextFactory;
public JavascriptEnvironment() {
mBoundVariables = new HashMap<>();
mContextFactory =
new ContextFactory() {
@Override
public boolean hasFeature(Context cx, int featureIndex) {
return featureIndex == Context.FEATURE_ENHANCED_JAVA_ACCESS;
}
};
}
@Override
public JavascriptSession startSession() {
return new JavascriptSession(mContextFactory, mBoundVariables);
}
/**
* Method for other plugins to register objects to a name, so that they can be accessed in all
* console sessions.
*
* @param name The variable name to bind the object to.
* @param object The reference to bind.
*/
@Override
public void registerGlobalObject(String name, Object object) {
if (mBoundVariables.containsKey(name)) {
throw new IllegalStateException(
String.format(
"Variable %s is already reserved for %s", name, mBoundVariables.get(name)));
}
mBoundVariables.put(name, object);
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console;
import com.facebook.sonar.plugins.console.iface.ScriptingSession;
import java.io.Closeable;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeJSON;
import org.mozilla.javascript.NativeJavaMethod;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
public class JavascriptSession implements Closeable, ScriptingSession {
private static final String TYPE = "type";
private static final String VALUE = "value";
public static final String JSON = "json";
private final Context mContext;
private final ContextFactory mContextFactory;
private final Scriptable mScope;
private final AtomicInteger lineNumber = new AtomicInteger(0);
JavascriptSession(ContextFactory contextFactory, Map<String, Object> globals) {
mContextFactory = contextFactory;
mContext = contextFactory.enterContext();
// Interpreted mode, or it will produce Dalvik incompatible bytecode.
mContext.setOptimizationLevel(-1);
mScope = mContext.initStandardObjects();
for (Map.Entry<String, Object> entry : globals.entrySet()) {
final Object value = entry.getValue();
if (value instanceof Number || value instanceof String) {
ScriptableObject.putConstProperty(mScope, entry.getKey(), entry.getValue());
} else {
// Calling java methods in the VM produces objects wrapped in NativeJava*.
// So passing in wrapped objects keeps them consistent.
ScriptableObject.putConstProperty(
mScope,
entry.getKey(),
new NativeJavaObject(mScope, entry.getValue(), entry.getValue().getClass()));
}
}
}
@Override
public JSONObject evaluateCommand(String userScript) throws JSONException {
return evaluateCommand(userScript, mScope);
}
@Override
public JSONObject evaluateCommand(String userScript, Object context) throws JSONException {
Scriptable scope = new NativeJavaObject(mScope, context, context.getClass());
return evaluateCommand(userScript, scope);
}
private JSONObject evaluateCommand(String command, Scriptable scope) throws JSONException {
try {
// This may be called by any thread, and contexts have to be entered in the current thread
// before being used, so enter/exit every time.
mContextFactory.enterContext();
return toJson(
mContext.evaluateString(
scope, command, "sonar-console", lineNumber.incrementAndGet(), null));
} finally {
Context.exit();
}
}
private JSONObject toJson(Object result) throws JSONException {
if (result instanceof String) {
return new JSONObject().put(TYPE, JSON).put(VALUE, result);
}
if (result instanceof Class) {
return new JSONObject().put(TYPE, "class").put(VALUE, ((Class) result).getName());
}
if (result instanceof NativeJavaObject
&& ((NativeJavaObject) result).unwrap() instanceof String) {
return new JSONObject().put(TYPE, JSON).put(VALUE, ((NativeJavaObject) result).unwrap());
}
if (result instanceof NativeJavaObject
&& ((NativeJavaObject) result).unwrap() instanceof Class) {
return new JSONObject()
.put(TYPE, "class")
.put(VALUE, ((NativeJavaObject) result).unwrap().toString());
}
if (result instanceof NativeJavaObject) {
final JSONObject o = new JSONObject();
o.put("toString", ((NativeJavaObject) result).unwrap().toString());
for (Object id : ((NativeJavaObject) result).getIds()) {
if (id instanceof String) {
final String name = (String) id;
final Object value = ((NativeJavaObject) result).get(name, (NativeJavaObject) result);
if (value != null && value instanceof NativeJavaMethod) {
continue;
}
final String valueString = value == null ? null : safeUnwrap(value).toString();
o.put(name, valueString);
}
}
return new JSONObject().put(TYPE, "javaObject").put(VALUE, o);
}
if (result instanceof NativeJavaMethod) {
final JSONObject o = new JSONObject();
o.put(TYPE, "method");
o.put("name", ((NativeJavaMethod) result).getFunctionName());
return o;
}
if (result == null || result instanceof Undefined) {
return new JSONObject().put(TYPE, "null");
}
if (result instanceof Function) {
final JSONObject o = new JSONObject();
o.put(TYPE, "function");
o.put(VALUE, Context.toString(result));
return o;
}
if (result instanceof ScriptableObject) {
return new JSONObject()
.put(TYPE, JSON)
.put(
VALUE,
new JSONTokener(NativeJSON.stringify(mContext, mScope, result, null, null).toString())
.nextValue());
}
if (result instanceof Number) {
return new JSONObject().put(TYPE, JSON).put(VALUE, result);
}
return new JSONObject().put(TYPE, "unknown").put(VALUE, result.toString());
}
@Override
public void close() {
Context.exit();
}
private static Object safeUnwrap(Object o) {
if (o instanceof NativeJavaObject) {
return ((NativeJavaObject) o).unwrap();
}
return o;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console.iface;
import android.support.annotation.Nullable;
import com.facebook.sonar.core.SonarConnection;
import com.facebook.sonar.core.SonarObject;
import com.facebook.sonar.core.SonarReceiver;
import com.facebook.sonar.core.SonarResponder;
import com.facebook.sonar.plugins.common.MainThreadSonarReceiver;
import org.json.JSONObject;
/**
* Convenience class for adding console execution to a Sonar Plugin. Calling {@link
* ConsoleCommandReceiver#listenForCommands(SonarConnection, ScriptingEnvironment, ContextProvider)}
* will add the necessary listeners for responding to command execution calls.
*/
public class ConsoleCommandReceiver {
/**
* Incoming command execution calls may reference a context ID that means something to your
* plugin. Implement {@link ContextProvider} to provide a mapping from context ID to java object.
* This will allow your sonar plugin to control the execution context of the command.
*/
public interface ContextProvider {
@Nullable
Object getObjectForId(String id);
}
public static void listenForCommands(
final SonarConnection connection,
final ScriptingEnvironment scriptingEnvironment,
final ContextProvider contextProvider) {
final ScriptingSession session = scriptingEnvironment.startSession();
final SonarReceiver executeCommandReceiver =
new MainThreadSonarReceiver(connection) {
@Override
public void onReceiveOnMainThread(SonarObject params, SonarResponder responder)
throws Exception {
final String command = params.getString("command");
final String contextObjectId = params.getString("context");
final Object contextObject = contextProvider.getObjectForId(contextObjectId);
try {
JSONObject o =
contextObject == null
? session.evaluateCommand(command)
: session.evaluateCommand(command, contextObject);
responder.success(new SonarObject(o));
} catch (Exception e) {
responder.error(new SonarObject.Builder().put("message", e.getMessage()).build());
}
}
};
final SonarReceiver isEnabledReceiver =
new SonarReceiver() {
@Override
public void onReceive(SonarObject params, SonarResponder responder) throws Exception {
responder.success(
new SonarObject.Builder()
.put("isEnabled", scriptingEnvironment.isEnabled())
.build());
}
};
connection.receive("executeCommand", executeCommandReceiver);
connection.receive("isConsoleEnabled", isEnabledReceiver);
}
public static void listenForCommands(
SonarConnection connection, ScriptingEnvironment scriptingEnvironment) {
listenForCommands(connection, scriptingEnvironment, nullContextProvider);
}
private static final ContextProvider nullContextProvider =
new ContextProvider() {
@Override
@Nullable
public Object getObjectForId(String id) {
return null;
}
};
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console.iface;
import org.json.JSONException;
import org.json.JSONObject;
public class NullScriptingEnvironment implements ScriptingEnvironment {
@Override
public ScriptingSession startSession() {
return new NoOpScriptingSession();
}
@Override
public void registerGlobalObject(String name, Object object) {}
static class NoOpScriptingSession implements ScriptingSession {
@Override
public JSONObject evaluateCommand(String userScript) throws JSONException {
throw new UnsupportedOperationException("Console plugin not enabled in this app");
}
@Override
public JSONObject evaluateCommand(String userScript, Object context) throws JSONException {
return evaluateCommand(userScript);
}
@Override
public void close() {}
}
@Override
public boolean isEnabled() {
return false;
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console.iface;
public interface ScriptingEnvironment {
ScriptingSession startSession();
void registerGlobalObject(String name, Object object);
boolean isEnabled();
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
package com.facebook.sonar.plugins.console.iface;
import org.json.JSONException;
import org.json.JSONObject;
public interface ScriptingSession {
JSONObject evaluateCommand(String userScript) throws JSONException;
JSONObject evaluateCommand(String userScript, Object context) throws JSONException;
void close();
}