Analyzers, converters, and cleaners (not indexers) are always passed a TargetInterface (in their analyze(), convert(), or clean() methods, respectively). The plugin then does some sort of work -- the specifics of which are, obviously, up to the plugin. Analyzers are expected to return any discovered issues; converters and cleaners are expected to save their changes to disk, but not return anything. (This is all documented in the various plugin interfaces.)

Standing between the plugin and the files on disk is something called the code manager, which is essentially a thin I/O wrapper. The code manager keeps references to all currently open syntax trees, and its responsible for saving any modifications to those trees back to disk. All indexers are bound to the code manager. Every Pharborist node in the target module is ultimately bound to the code manager. When you retrieve a node (a function, class, or whatever else) from an indexer, you will ultimately be retrieving a reference to a syntax tree that is being maintained by the code manager. If you want to save a modified syntax tree out to disk, you do it with the code manager. If you want to open a syntax tree, you do it with the code manager. Everything involving I/O with the target module’s files uses the code manager.

The code manager was originally written to prevent clobber conditions, which are extraordinarily easy to cause when you’re shuffling big objects around in memory. So, for the love of Cthulhu, don't circumvent the code manager. If you do, bugaboos may emerge from your closet in the dead of night and puppies may be shot into space.

You can always get ahold of the code manager by calling TargetInterface::getCodeManager().