Say you have a file with:
AddReactImport();
And the plugin:
export default function ({types: t }) {
return {
visitor: {
CallExpression(p) {
if (p.node.callee.name === "AddReactImport") {
// add import if it's not there
}
}
}
};
}
How do you add import React from 'react'; at the top of the file/tree if it's not there already.
I think more important than the answer is how you find out how to do it. Please tell me because I'm having a hard time finding info sources on how to develop Babel plugins. My sources right now are: Plugin Handbook,Babel Types, AST Spec, this blog post, and the AST explorer. It feels like using an English-German dictionary to try to speak German.
export default function ({types: t }) {
return {
visitor: {
Program(path) {
const identifier = t.identifier('React');
const importDefaultSpecifier = t.importDefaultSpecifier(identifier);
const importDeclaration = t.importDeclaration([importDefaultSpecifier], t.stringLiteral('react'));
path.unshiftContainer('body', importDeclaration);
}
}
};
}
If you want to inject code, just use #babel/template to generate the AST node for it; then inject it as you need to.
Preamble: Babel documentation is not the best
I also agree that, even in 2020, information is sparse. I am getting most of my info by actually working through the babel source code, looking at all the tools (types, traverse, path, code-frame etc...), the helpers they use, existing plugins (e.g. istanbul to learn a bit about basic instrumentation in JS), the webpack babel-loader and more...
For example: unshiftContainer (and actually, babel-traverse in general) has no official documentation, but you can find it's source code here (fascinatingly enough, it accepts either a single node or an array of nodes!)
Strategy #1 (updated version)
In this particular case, I would:
Create a #babel/template
prepare that AST once at the start of my plugin
inject it into Program (i.e. the root path) once, only if the particular function call has been found
NOTE: Templates also support variables. Very useful if you want to wrap existing nodes or want to produce slight variations of the same code, depending on context.
Code (using Strategy #1)
import template from "#babel/template";
// template
const buildImport = template(`
import React from 'react';
`);
// plugin
const plugin = function () {
const importDeclaration = buildImport();
let imported = false;
let root;
return {
visitor: {
Program(path) {
root = path;
},
CallExpression(path) {
if (!imported && path.node.callee.name === "AddMyImport") {
// add import if it's not there
imported = true;
root.unshiftContainer('body', importDeclaration);
}
}
}
};
};
Strategy #2 (old version)
An alternative is:
use a utility function to generate an AST from source (parseSource)
prepare that AST once at the start of my plugin
inject it into Program (i.e. the root path) once, only if the particular function call has been found
Code (using Strategy #2)
Same as above but with your own compiler function (not as efficient as #babel/template):
/**
* Helper: Generate AST from source through `#babel/parser`.
* Copied from somewhere... I think it was `#babel/traverse`
* #param {*} source
*/
export function parseSource(source) {
let ast;
try {
source = `${source}`;
ast = parse(source);
} catch (err) {
const loc = err.loc;
if (loc) {
err.message +=
"\n" +
codeFrameColumns(source, {
start: {
line: loc.line,
column: loc.column + 1,
},
});
}
throw err;
}
const nodes = ast.program.body;
nodes.forEach(n => traverse.removeProperties(n));
return nodes;
}
Possible Pitfalls
When a new node is injected/replaced etc, babel will run all plugins on them again. This is why your first instrumentation plugin is likely to encounter an infinite loop right of the bet: you want to remember and not re-visit previously visited nodes (I'm using a Set for that).
It gets worse when wrapping nodes. Nodes wrapped (e.g. with #babel/template) are actually copies, not the original node. In that case, you want to remember that it is instrumented and skip it in case you come across it again, or, again: infinite loop 💥!
If you don't want to instrument nodes that have been emitted by any plugin (not just yours), that is you want to only operate on the original source code, you can skip them by checking whether they have a loc property (injected nodes usually do not have a loc property).
In your case, you are trying to add an import statement which won't always work without the right plugins enabled or without program-type set to module.
I believe there's an even better way now: babel-helper-module-imports
For you the code would be
import { addDefault } from "#babel/helper-module-imports";
addDefault(path, 'React', { nameHint: "React" })
Related
private fun shareOperation(file: File) {
val uri = Uri.fromFile(file)
val storage = FirebaseStorage.getInstance()
val pdfRef = storage.reference.child("pdf/${uri.lastPathSegment}")
pdfRef.putFile(uri).addOnFailureListener { e ->
Log.e(TAG, "Couldn't share " + e.message)
}.addOnCompleteListener{
it.addOnCompleteListener {
pdfRef.downloadUrl.addOnSuccessListener { e ->
run {
link = e.toString()
Log.i(TAG,link!!) // Here i get the link to file in firebase storage
}
}
}
}
// Here link gets null
}
i was expecting somehow i can get the link to the file and can use it for sharing intent
You are performing an asynchronous call to upload the file, that is correct since any UI blocking action must be performed in background. The variable link will be null until the run code is executed in the background thread.
You need to code inside the run block whatever you want to happen when the link is available.
BTW looks weird what you are doing with the nested addOnCompleteListener, there should be an easier way to code that. You should probably spend time learning how to code with listeners and background threads.
I am working on a solution where I am using vertx 3.8.4 and vertx-mysql-client 3.9.0 for asynchronous database calls.
Here is the scenario that I have been trying to resolve, in a proper reactive manner.
I have some mastertable records which are in inactive state.
I run a query and get the list of records from the database.
This I did like this :
Future<List<Master>> locationMasters = getInactiveMasterTableRecords ();
locationMasters.onSuccess (locationMasterList -> {
if (locationMasterList.size () > 0) {
uploadTargetingDataForAllInactiveLocations(vertx, amazonS3Utility,
locationMasterList);
}
});
Now in uploadTargetingDataForAllInactiveLocations method, i have a list of items.
What I have to do is, I need to iterate over this list, for each item, I need to download a file from aws, parse the file and insert those data to db.
I understand the way to do it using CompositeFuture.
Can someone from vertx dev community help me with this or with some documentation available ?
I did not find good contents on this by googling.
I'm answering this as I was searching for something similar and I ended up spending some time before finding an answer and hopefully this might be useful to someone else in future.
I believe you want to use CompositeFuture in vertx only if you want to synchronize multiple actions. That means that you either want an action to execute in the case that either all your other actions on which your composite future is built upon succeed or at least one of the action on which your composite future is built upon succeed.
In the first case I would use CompositeFuture.all(List<Future> futures) and in the second case I would use CompositeFuture.any(List<Future> futures).
As per your question, below is a sample code where a list of item, for each item we run an asynchronous operation (namely downloadAnProcessFile()) which returns a Future and we want to execute an action doAction() in the case that all the async actions succeeded:
List<Future> futures = new ArrayList<>();
locationMasterList.forEach(elem -> {
Promise<Void> promise = Promise.promise();
futures.add(promise.future());
Future<Boolean> processStatus = downloadAndProcessFile(); // doesn't need to be boolean
processStatus.onComplete(asyncProcessStatus -> {
if (asyncProcessStatus.succeeded()){
// eventually do stuff with the result
promise.complete();
} else {
promise.fail("Error while processing file whatever");
}
});
});
CompositeFuture.all(futures).onComplete(compositeAsync -> {
if (compositeAsync.succeeded()){
doAction(); // <-- here do what you want to do when all future complete
} else {
// at least 1 future failed
}
});
This solution is probably not perfect and I suppose can be improved but this is what I found works for me. Hopefully will work for someone else.
So, this is the problem I am stuck at for a few weeks.
I am developing an Eclipse plugin which fills in a View with custom values depending on a particular StackFrame selection in the Debug View.
In particular, I want to listen to the stackframe selected and would like to get the underlying IStackFrame object.
However, I have tried more than a dozen things and all of them have failed. So I tried adding DebugContextListener to get the DebugContextEvent and finally the selection. However, the main issue is that ISelection doesn't return the underlying IStackFrame object. It instead returns an object of type AbstractDMVMNode.DMVMContext. I tried getting an adapter but that didn't work out too. I posted this question sometime back also:
Eclipse Plugin Dev- Extracting IStackFrame object from selection in Debug View
Since then, I have tried out many different approaches. I tried adding IDebugEventSetListener (but this failed as it cannot identify stack frame selection in the debug view).
I tried adding an object contribution action but this too was pointless as it ultimately returned me an ISelection which is useless as it only returns me an object of class AbstractDMVMNode.DMVMContext and not IStackframe.
Moreover, I checked out the implementation of the VariablesView source code itself in the org.eclipse.debug.ui plugin. It looks like a few versions back (VariablesView source code in version 3.2), the underlying logic was to use the ISelection and get the IStackFrame. All the other resources on the internet also advocate the same. However, now, this scheme no longer works as ISelection doesn't return you an IStackFrame. Also, the source code for the latest eclipse Debug plugin (which doesn't use this scheme) was not very intuitive for me.
Can anyone tell how I should proceed ? Is hacking the latest Eclipse source code for VariablesView my only option ? This doesn't look like a good design practice and I believe there should be a much more elegant way of doing this.
PS: I have tried all the techniques and all of them return an ISelection. So, if your approach too return the same thing, then it is most likely incorrect.
Edit (Code snippet for trying to adapt the ISelection):
// Following is the listener implemnetation
IDebugContextListener flistener = new IDebugContextListener() {
#Override
public void debugContextChanged(DebugContextEvent event) {
if ((event.getFlags() & DebugContextEvent.ACTIVATED) > 0) {
contextActivated(event.getContext());
}
};
};
// Few things I tried in the contextActivated Method
//Attempt 1 (Getting the Adapter):
private void contextActivated(ISelection context) {
if (context instanceof StructuredSelection) {
Object data = ((StructuredSelection) context).getFirstElement();
if( data instanceof IAdaptable){
System.out.println("check1");
IStackFrame model = (IStackFrame)((IAdaptable)data).getAdapter(IStackFrame.class);
if(model != null){
System.out.println("success" + model.getName());
}
}
}
}
// Attemp2 (Directly getting it from ISelection):
private void contextActivated(ISelection context) {
if (context instanceof StructuredSelection) {
System.out.println("a");
Object data = ((StructuredSelection) context).getFirstElement();
if (data instanceof IStackFrame) {
System.out.println("yes");
} else {
System.out.println("no" + data.getClass().getName());
}
}
// This always execute the else and it prints: org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMNode.DMVMContext
}
// Attemp3 (Trying to obtain it from the viewer (similiar to object action binding in some ways):
private void contextActivated(ISelection context) {
VariablesView variablesView = (VariablesView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(IDebugUIConstants.ID_VARIABLE_VIEW);
if (variablesView != null) {
Object input = ((TreeViewer) variablesView.getViewer()).getInput();
if(input != null) System.out.println(input.getClass().getName());
if (input instanceof IStackFrame) {
System.out.println("success");
} else if (input instanceof IThread) {
System.out.println("success");
try {
IStackFrame[] stackFrames = ((IThread) input).getStackFrames();
for (IStackFrame iStackFrame : stackFrames) {
printVariables(iStackFrame);
}
} catch (DebugException e) {
e.printStackTrace();
}
}
}
}
While I am building this view to work with both JDT & CDT, I am testing it out on the C project. So, this might be the reason why I always get the returned object type as AbstractDMVMNode.DMVMContext. Should my implementation be different to handle both these cases ? I believe I should be building a generic view. Also, if AbstractDMVMNode.DMVMContext is CDT specific, I should I go about implementing it for the CDT case?
If I started a process using Scala Process/ProcessBuilder. How can I get the pid of the process that was created?
I could not find any mention of the pid in the official docs:
http://www.scala-lang.org/api/2.10.4/index.html#scala.sys.process.Process
http://www.scala-lang.org/api/2.10.4/index.html#scala.sys.process.ProcessBuilder
http://www.scala-lang.org/api/2.10.4/index.html#scala.sys.process.package
2016: same question; I've been clicking through related questions for a few minutes, but still couldn't find any solution that is generally agreed upon. Here is a Scala version inspired by LRBH10's Java code in the answer linked by wingedsubmariner:
import scala.sys.process.Process
def pid(p: Process): Long = {
val procField = p.getClass.getDeclaredField("p")
procField.synchronized {
procField.setAccessible(true)
val proc = procField.get(p)
try {
proc match {
case unixProc
if unixProc.getClass.getName == "java.lang.UNIXProcess" => {
val pidField = unixProc.getClass.getDeclaredField("pid")
pidField.synchronized {
pidField.setAccessible(true)
try {
pidField.getLong(unixProc)
} finally {
pidField.setAccessible(false)
}
}
}
// If someone wants to add support for Windows processes,
// this would be the right place to do it:
case _ => throw new RuntimeException(
"Cannot get PID of a " + proc.getClass.getName)
}
} finally {
procField.setAccessible(false)
}
}
}
// little demo
val proc = Process("echo 'blah blah blaaah'").run()
println(pid(proc))
WARNING: scala code runner is essentially just a bash script, so when you use it to launch scala programs, it will do thousand things before actually starting the java process. Therefore, the PID of the java-process that you are actually interested in will be much larger than what the above code snippet returns. So this method is essentially useless if you start your processes with scala. Use java directly, and explicitly add Scala library to the classpath.
The scala.sys.io.process classes are wrappers around the Java classes for starting processes, and unfortunately it is difficult to obtain the PID from this API. See the stackoverlow question for this, How to get PID of process I've just started within java program?.
I'm writing a Chrome extension that injects scripts to the Google's search result page and modified all the results' anchor elements.
My problem is that the results are rendered asynchronously and are not shown in the page on document load/ready.
I had 2 initial solutions which don't work:
Set a timeout: Bad practice but it works. Nevertheless, might show inconsistent results so I prefer to avoid this solution.
Bind to 'DOMNodeInserted'. Generally works, but more complicated in my case because I insert new nodes my self before the anchors, which triggers a recursion. I can insert code to avoid it if the anchor is already 'tagged', but again, this solution is bad since I need to traverse all the anchors each time a node is inserted - from what I checked this happens more than 140 times in the search result page.
Is there any kind of custom event Google trigger on the search results page? Is there any other DOM event that can work in this case?
You are right in that using "DOMNodeInserted" is not a good approach. If nothing else, it is part of the obsolete Mutation Events API, which has been deprecated (among other reasons) for being notoriously inefficient.
It has been replaced by the MutationObserver API, so this is what you should use instead. You can utilize a MutationObserver to observe "childList" DOM mutations on a root node and its descendants.
(If you choose this approach the mutation-summary library might also come in handy.)
After a (really shallow) search, I found out that (at least for me) Google places its results in a div with id search. Below is the code of a sample extension that does the following:
Registers a MutationObserver to detect the insertion of div#search into the DOM.
Registers a MutationObserver to detect "childList" changes in div#search and its descendants.
Whenever a <a> node is added, a function traverses the relevant nodes and modifies the links. (The script ignores <script> elements for obvious reasons.)
This sample extension just encloses the link's text in ~~, but you can easily change it to do whatever you need.
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"content_scripts": [{
"matches": [
...
"*://www.google.gr/*",
"*://www.google.com/*"
],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": false
}],
}
content.js:
console.log("Injected...");
/* MutationObserver configuration data: Listen for "childList"
* mutations in the specified element and its descendants */
var config = {
childList: true,
subtree: true
};
var regex = /<a.*?>[^<]*<\/a>/;
/* Traverse 'rootNode' and its descendants and modify '<a>' tags */
function modifyLinks(rootNode) {
var nodes = [rootNode];
while (nodes.length > 0) {
var node = nodes.shift();
if (node.tagName == "A") {
/* Modify the '<a>' element */
node.innerHTML = "~~" + node.innerHTML + "~~";
} else {
/* If the current node has children, queue them for further
* processing, ignoring any '<script>' tags. */
[].slice.call(node.children).forEach(function(childNode) {
if (childNode.tagName != "SCRIPT") {
nodes.push(childNode);
}
});
}
}
}
/* Observer1: Looks for 'div.search' */
var observer1 = new MutationObserver(function(mutations) {
/* For each MutationRecord in 'mutations'... */
mutations.some(function(mutation) {
/* ...if nodes have beed added... */
if (mutation.addedNodes && (mutation.addedNodes.length > 0)) {
/* ...look for 'div#search' */
var node = mutation.target.querySelector("div#search");
if (node) {
/* 'div#search' found; stop observer 1 and start observer 2 */
observer1.disconnect();
observer2.observe(node, config);
if (regex.test(node.innerHTML)) {
/* Modify any '<a>' elements already in the current node */
modifyLinks(node);
}
return true;
}
}
});
});
/* Observer2: Listens for '<a>' elements insertion */
var observer2 = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes) {
[].slice.call(mutation.addedNodes).forEach(function(node) {
/* If 'node' or any of its desctants are '<a>'... */
if (regex.test(node.outerHTML)) {
/* ...do something with them */
modifyLinks(node);
}
});
}
});
});
/* Start observing 'body' for 'div#search' */
observer1.observe(document.body, config);
In general, you can use Mutation Observers to listen for document changes. To avoid recursion, simply disconnect the mutation observer before changing the document, then enable it again.
Conceptually, it is not much different from the DOMNodeInserted event, so you can also remove the event listener, insert your nodes, then rebind the event listener. However, Mutation observers are more efficient, so you should use these instead of the DOM mutation events.
In this specific case (Google's search results), you can also use the hashchange event to detect when Google has rendered new search results. This method is only useful because there's a correlation between the location fragment, the search terms and the search results:
The user enters a search term Enter
The search results are updated.
The location fragment is changed (https://www.google.com/search?q=old#q=<new term>).
Example:
// On document load
printResult();
// Whenever the search term is changed
window.addEventListener('hashchange', function(event) {
printResult();
});
function printResult() {
// Example: Print first search result
console.log(document.querySelector('h3 a').href);
}