Refactor variable names in JavaScript code

7

Your challenge today is to refactor JavaScript code. You will take three strings as input; a JavaScript string, a old variable name, and the name you want to refactor it to. For example, an input of

"var x = 100; alert(x);", "x", "num"

Will output

"var num = 100; alert(num);"

Here's a more advanced test case:

"var gb = 1; var g = bg = 2; gb = bg = g = function(gg) { alert(gb); };
var str = 'g gb bg gg'; var regexp = /gb gb gb/g; //comment gb gb g g
/*gb gb g g*/", "gb", "aaa"

It should output

var aaa = 1; var g = bg = 2; aaa = bg = g = function(gg) { alert(aaa); };
var str = 'g gb bg gg'; var regexp = /gb gb g g/g; //comment gb gb g g
/*gb gb g g*/

And the same JS input with the other arguments "g", "aaa" should output

var gb = 1; var aaa = bg = 2; gb = bg = aaa = function(gg) { alert(gb); };
var str = 'g gb bg gg'; var regexp = /gb gb gb/g; //comment gb gb g g
/*gb gb g g*/

Here is one last much more complicated test case:

var g = 1; "g\"g"; "g'"; g; '"g\'g"g'; /g"\/*\//g; g; "// /*"; g; "*/"

It should output, with other parameters "g" and "a", this:

var a = 1; "g\"g"; "g'"; a; '"g\'g"g'; /g"\/*\//g; a; "// /*"; a; "*/"

(sidenote: I really like /g"\/*\//g; I think that's just evil :D)


Full specification:

  • For simplicity, you may ignore scope. Ex. "var a=1; function(){var a=2;}; var o={a: 3}", "a", "b" can output var b=1; function(){var b=2;}; var o={b: 3}.
  • Text in strings ('...' and "..."), regexps and their modifiers (/.../...), and comments (//...\n and /*...*/) must not be modified.
  • Also for simplicity, a valid identifier is one that starts with a letter, dollar sign, or underscore, and the rest of the characters are letters, numbers, dollar signs, or underscores.
  • You may assume that the input contains no syntax errors.
  • This is , so the shortest code in bytes will win.
  • If you use network access, all bytes downloaded from the network count towards your score.
  • You must use an actual programming language. No IDE solutions, like emacs or vim keystrokes. ;)

Doorknob

Posted 2014-02-05T03:38:57.113

Reputation: 68 138

3wait, what's wrong with vim keystrokes? :-) – John Dvorak – 2014-02-05T06:34:44.833

do we have to support /[/]/ (character classes containing unescaped slash characters)? Ruby doesn't. – John Dvorak – 2014-02-05T08:46:00.627

Asking the same as @JanDvorak above: do we need to support /[/]/ (character classes containing unescaped slash characters)? Most languages with built-in support for regular expressions don't; however, JavaScript does. – Toothbrush – 2014-02-17T10:56:00.693

@toothbrush No, you do not. (Sorry for forgetting to reply ;-)) – Doorknob – 2014-02-17T14:12:57.293

Answers

3

Ruby, 134 (113?) characters

*c,o,n=*$<;puts (c*?\n).gsub(/('|")((\\?+.)*?)\1|\/\*[\s\S]*?\*\/|\/\/.*|\/(\[\g<2>\]|\g<2>)+?\/\w*|([\w$]+)/){$5==o.chop&&n.chop||$&}

Unicode is not supported. If Unicode identifiers are to be supported, replace \w with [[:word:]] and [\w$] with ([[:word:]]|\$) (and adjust the backreferences).

If unescaped slashes inside regexp literals (such as /[/]/) don't need to be supported, we can handle them together with string literals (116 characters). This also gives us string flags:

*c,o,n=*$<;puts (c*?\n).gsub(/\/\*[\s\S]*?\*\/|\/\/.*|('|"|\/)((\\?+.)*?)\1\w*|([\w$]+)/){$4==o.chop&&n.chop||$&}

Ungolfed version:

*code, old, new = *$<

puts code.join("\n").gsub (/
     ('|")((\\?+.)*?)\1           #strings
   | \/\*[\s\S]*?\*\/             #multiline comments
   | \/\/.*                       #single-line comments
   | \/(\[\g<2>\]|\g<2>)+?\/\w*   #regexes
   | ([\w$]+)                     #identifiers and keywords
/x){
  if $5 == old.chop then new.chop else $& end
}

John Dvorak

Posted 2014-02-05T03:38:57.113

Reputation: 9 048

2

Perl, 122

@a=<>;chomp(($y,$x)=(pop@a,pop@a));$_=join'',@a;s!//.*?\n|/\*.*?\*/|("|'|/)(\\?+.)*?\1\w*|([\w\$]+)!$3eq$x?$y:$&!sge;print

i.e. (semi-ungolfed)

@a=<>;
chomp(($y,$x)=(pop@a,pop@a));
$_=join'',@a;
s!//.*?\n|/\*.*?\*/|("|'|/)(\\?+.)*?\1\w*|([\w\$]+)!$3eq$x?$y:$&!sge;
print

practically the same as Ruby's short version answer (except I didn't knew about possessive quantifiers trick and my first take was slightly longer). Regexp is shorter, but 'I/O' part spoils everything (Perl can't do (@a,$x,$y)=<>), and my assumption (the same I see in Ruby's, if I'm not mistaken) was we feed JS string line by line through STDIN, followed by 'old name' and 'new name' on separate lines.

user2846289

Posted 2014-02-05T03:38:57.113

Reputation: 1 541

0

EcmaScript 6 (138 bytes):

x=(s,o,n)=>s.replace(/\/\/.*?\n|\/\*[\s\S]+?\*\/|('|")(\\\1|[^\1])+?\1|\/(\\u[a-f\d]{4}|\\.|[^\/])+\/[angi]*|[a-z_$][\w$]*/gi,x=>x==o?n:x)

Runs fine on any recent version of Firefox/Google Chrome.


It creates a function called x with three parameters:

  1. The code
  2. The old variable name
  3. The new variable name

See this JSFiddle for an example.

Toothbrush

Posted 2014-02-05T03:38:57.113

Reputation: 3 197