-1

Edit: For clarification, I'm looking for a JavaScript or TypeScript refactoring transpiler. I want to do the rename at build time, not at runtime.

I would like to use a particular library, but its API does not fit with the naming style of my own codebase (sound familiar?) so I want to override the API calls with identical calls that use the coding conventions of my own code.

But this introduces a lot of runtime overhead, with over a hundred methods introduced into prototypes to change the signature of the objects.

Object.defineProperty(Room.prototype, 'getController', {get: function () {return this.controller}});

Instead, if I could introduce a compilation step that executed refactoring rules on the API calls, my code could us my preferred API but the result code could use the published API. I have not been able to find a JavaScript refactoring tool that has a DSL for executing repetitive refactoring requests, something like:

rename(Room.prototype.controller, 'getController')

Searching for 'automatic refactoring' brings up dozens of pie-in-the-sky projects for making code better with no human input. That's not what I want; I just want to introduce manually-entered, scripted refactoring into my build pipeline to shield the working code from the differing naming conventions of external APIs.

Myrddin Emrys
  • 42,126
  • 11
  • 38
  • 51
  • 1
    Wrap all your code in an IIFE and insulate it from global namespace?? Not completely clear where collisions are occurring – charlietfl Jun 26 '16 at 17:52
  • This is exactly what I'm doing right now, but the additional function calls are overhead I want to avoid, hence my request for a way to do rename-on-transpile. – Myrddin Emrys Jun 26 '16 at 19:45

2 Answers2

1

What you want is a source-to-source program transformation system (PTS).

Such tools parse source code into ASTs, offer you the ability to transform those ASTs into other ASTs with surface-syntax patterns, and then spit out the source code for the correspondingly revised AST. If you transform from ASTs in one language to ASTs in another you get what you are calling a transpiler (not my favorite term; PTS was just fine).

Source to source transformation syntax varies, but in essence you write something like this using the surface (actual) language syntax of the source and target languages:

  if you see *this*, replace it by *that* if some_condition(*this*) 

The this and that element are patterns expressed in the language syntax; The optional conditional ("semantic constraint") allows the tool to take into account context information (such as symbol properties, etc.) Often it takes several or many rules to accomplish a complex transformation; the rules can usually be sequenced to achieve a compound effect of interest. The expression of the rulles and their sequencing the is "scripting" part of interest to OP.

You need to either get one that will accept new language definitions (the really general ones will) and define JavaScript to it, or get one that already has a JavaScript parser available and simply use that.

One of these genera, PTS is our DMS Software Reengineering Toolkit. It has an available Javascript front end.

OP's renaming problem can be specified as as a DMS Rewrite Rule as follows:

rule rename_controller(): IDENTIFIER
    "controller" -> "getController";

The quote marks are meta quotes used to distinguish the syntax of the pattern language, from the syntax of the source/target langauges, which is written inside the meta quotes.

To rename all the API calls, OP would need rules for each unique API entry.

While easy to write, this is rather heavyhanded; any identifier with that name in the files processed by the will be so-renamed. That may rename "controller" in scopes that OP does not intend. If there is no danger of multiple declarations with the same name then this will be safe to execute using DMS's "apply rules everywhere" built-in tactic as a command-line scriptable task.

(It might be easier to rename the identifiers used in OP's code to the API identifiers; it is likely easier to avoid name collisions in code he writes than name collisions in the source files the APIs he did not write).

To do this right, OP has to somehow qualify which declaration of "controller" he wants renamed. That that he will have to define a custom constraint that checks he is renaming the "right" one, something like:

rule rename_controller(i:IDENTIFIER): IDENTIFIER
    "\i" -> "getController"
          if (match(i,"controller") & declaration_at_line(i,1219);

Here we we match any identifier with the intention of obtaining access to the AST node in which it is found (\i inside surface syntax text, i outside that text.). The match predicate is built-into DMS already; it is used to insist we found the right name. The declaration_at_line predicate is used to check that the matched AST node corresponds to one with the same name declared at a specific place in the file. This requires what amounts to a scoped name lookup that follows ECMAScript rules, so some custom work is required. (At present, this DMS front end does not provide that; other DMS front ends for other languages sometimes provide this capability out of the box). The choice of how to constrain which declaration is the target is rather arbitrary; one might define by the path from the root of the global namespace to get to get to it as OP hints in his example:

     ... declaration_in(i,"Room.prototype") ...

One will still need scope lookup rules to process such a path.

We remark that no matter which PTS is chosen by OP, scoping lookup will be required for accurate renaming. Many of the other PTS (TXL, Stratego, ...) provide no support whatever for implementing this.

Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • I was familiar with the term AST, and had been looking into ways to do this myself from the AST output of a tool. I had not heard of the term PTS, so thank you for that (another tool to Google with). A quick question of capability (since your documentation is not public as far as I can find): can I constrain a declaration to a file, rather than a line? – Myrddin Emrys Jun 30 '16 at 14:25
  • You can't do this with "just" an AST; something has to be able to regenerate *valid* souce code from the AST. That's harder than it looks (See http://stackoverflow.com/a/5834775/120163 ) This is just one of many reasons that a pure parser is really not enough (see "Life After Parsing" http://www.semdesigns.com/Products/DMS/LifeAfterParsing.html for lots more detail) – Ira Baxter Jun 30 '16 at 15:05
  • Regarding constraints: DMS is a customizable PTS.; it has to be that way to meet the widely varied set of things that people want to do to source code. You can configure to read as many or as few source files of interest; clearly any file it doesn't read isn't going to get modified :-} You can implement such a restrict-to-file constraint pretty easily (each AST node is stamped as to the source file from which it came). The semanitc predicate has to inspect the AST node for the file indication and check that the file is the "one" of interest. – Ira Baxter Jun 30 '16 at 15:06
0

Not certain if js at approach returns expected results, though should be able to adjust the name, set context of function by creating a separate object, using Function.prototype.bind(). Implementation could probably be improved, to meet exact requirement.

function Mirror() {
  var ctx = Object.create(null);
  this.rename = function(obj, name, context, args) {
    ctx[name] = obj.bind(context || ctx, args || null);
    return ctx
  }
}

// `Room`
function Room() {

}

Room.prototype.controller = function() {
  console.log(this)
}

var mirror = new Mirror();
let _room = mirror.rename(
              Room.prototype.controller
              , "getController"
              , new Room());
_room.getController()

An alternative approach could be to adjust names of function one at a time at text editor.

guest271314
  • 1
  • 15
  • 104
  • 177
  • Perhaps I'm misunderstanding, but this appears to be a DSL to make the Object.defineProperty() rename at runtime. My goal is to have the deployed code use the published API directly, but the internal code use a private API, with a rename-on-transpile step. – Myrddin Emrys Jun 26 '16 at 19:44
  • @MyrddinEmrys _"My goal is to have the deployed code use the published API directly, but the internal code use a private API, with a rename-on-transpile step"_ See alternative approach. The effort placed into creating such a process could be used to re-write the entire existing API. – guest271314 Jun 26 '16 at 20:05
  • I don't have access to re-write the existing API. Or I would. I know this is JavaScript, but in this case the API is unavailable for modification. – Myrddin Emrys Jun 26 '16 at 22:15
  • If you know the names of the functions you should be able to rename the functions another name while preserving the context: `this`? – guest271314 Jun 27 '16 at 01:02
  • Indeed I can. You did notice my example in the question, where I do exactly that, right? I create an alternate function that functionally renames the existing one. – Myrddin Emrys Jun 27 '16 at 03:37
  • @MyrddinEmrys _"Indeed I can. You did notice my example in the question, where I do exactly that, right? I create an alternate function that functionally renames the existing one."_ This approach resolves Question, yes? – guest271314 Jun 27 '16 at 03:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/115672/discussion-between-myrddin-emrys-and-guest271314). – Myrddin Emrys Jun 27 '16 at 03:40