Script to split JS comma-separated var declaration into multiple var declarations - javascript

I have a large codebase with a ton of stuff declared like this:
var x = 1,
y = {
//some object
},
z = function(arg) {
// some function
};
I'd like to run a node script to convert all this to
var x = 1;
var y = {
//some object
};
var z = function(arg) {
// some function
};
It's not as simple as running a regex on it, because as soon as an object or function appears, you can't just look for commas and semicolons anymore.
Is there an existing library or tool which can do this conversion for me? Not looking to minify or uglify the code, I'm just looking to modify existing, human-readable code to get rid of the comma-separated var declarations.

Are there situations where the character sequence , followed by an identifier and then a single = can exist outside of a multiple var declaration? I'm having trouble thinking of one outside of a string literal with those characters, because a single = is used for assignment, and I'm not sure why you'd have a comma before an assignment statement except in the initialization form you're trying to replace.
Granted, it's always risky to use regex in situations where a parser is more appropriate. The pattern would look something like:
,\s*([\$a-z_][\$a-z_0-9]*)(?=\s*=[^=])
Note: The Visual Studio plugin Resharper has a refactoring for this very operation. However, unlike many other refactorings, Resharper does not provide the option to apply this globally.

Maybe I'm missing something, but if all your comma separators lie at the end of a line, you could just use the regex:
replace(/,\n/g, ';\nvar ');
Here's a browser example:
// in node, this would come straight from a file
var string = 'var x = 1,\ny = {\n //some object \n},\nz = function(arg) {\n // some function\n};';
// heres an element we can use for results
var code = document.getElementById('code');
// lets show original string
code.innerHTML = string;
// lets show the new string in a couple of seconds
setTimeout( function () {
// the regex replace
var updated = string.replace(/,\n/g, ';\nvar ');
// updating the code element
code.innerHTML = updated;
// change color to signify finished
code.className = 'done';
}, 2000);
code {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
.done {
background-color: #C0D9AF;
}
<code id="code"></code>

This seems to do the trick:
jsfmt --rewrite "a=b,c=d -> var a=b; var c=d;" input.js > output.js
input.js:
var x = 1,
y = {
//some object
},
z = function(arg) {
// some function
};
output.js:
var x = 1;
var y = {
//some object
};
var z = function(arg) {
// some function
};
using jsfmt

Related

Wrap all variable declarations in a function

I have a Javascript function declartions as a string (gotten from Function.toString), and I want to wrap all variable declarations with a function (also in Javascript), E.g.
const value = 42 to const value = wrapper(42).
First I thought of using RegEx to get the original values and location and then replace them with the wrapped value, but the RegEx got too complex very fast because of needing to think about things like multiline strings and objects. Using RegEx would also impact the ease of other people contributing to the project.
After that I looked into using a module for this, I found Acorn (used by Babel, Svelte. Parses the Javascript into an ESTree, the spec for Javascript Abstract Syntax Trees): https://github.com/acornjs/acorn, but I couldn't find a way of parsing the ESTree back to a Javascript function declaration after making the modifications.
Is there a way of parsing the ESTree back to a function, or another better solution?
You don't really need a function to stringify the tree back into code. Instead, take note of the offsets where the change should occur, and then don't apply the change in the tree, but to the original string.
Here is a demo with the acorn API:
function test () { // The function we want to tamper with
const value = 42, prefix = "prefix";
let x = 3;
for (let i = 0; i < 10; i++) {
x = (x * 997 + value) % 1000;
}
return prefix + " " + x;
}
function addInitWrappers(str) { // Returns an updated string
let ast = acorn.parse(str, {ecmaVersion: 2020});
function* iter(node) {
if (Object(node) !== node) return; // Primitive
if (node.type == "VariableDeclaration" && node.kind == "const") {
for (let {init} of node.declarations) {
yield init; // yield the offset where this initialisation occurs
}
}
for (let value of Object.values(node)) {
yield* iter(value);
}
}
// Inject the wrapper -- starting at the back
for (let {start, end} of [...iter(ast)].reverse()) {
str = str.slice(0, start) + "wrapper(" + str.slice(start, end) + ")" + str.slice(end);
}
return str;
}
function wrapper(value) { // A wrapper function to demo with
return value + 1;
}
console.log("before wrapping test() returns:", test());
let str = test.toString();
str = addInitWrappers(str);
eval(str); // Override the test function with its new definition
console.log("after wrapping test() returns:", test());
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.7.1/acorn.min.js"></script>

Eval vs IF statements (many IF statements)

This fiddle pretty much explains what I'm looking for. I'm trying to find the simplest way to go about coding something WITHOUT using eval. I can do it without eval but I think I will have to write 1000s of IF statements. Or is there another way?
http://jsfiddle.net/243rz8eq/9/
HTML
Eval way...<br>
Window-A<br>
Window-B<br>
Window-C<br><br>
Non-Eval way. Requires if statement for each window (there will be thousands). Or is there a simpler way I'm not seeing?<br>
Window-A<br>
Window-B<br>
Window-C<br><br>
JavaScript
window.core = {
launch: function(obj_string) {
//we init a blank dhtmlxwindow and do some other things here, then...
var x = eval("new " + obj_string); //fill dhtmlxwindow with proper content
},
launch_no_eval: function(id) {
//we init a blank dhtmlxwindow and do some other things here, then...
if (id==="window_a") var x = wins.a({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_b") var x = wins.b({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_c") var x = wins.c({x:1}); //fill dhtmlxwindow with proper content
//and so on for literally thousands of items.
}
};
window.wins = {
a: function(args) {
//this.myName = 'wins.a'; is used for the help topic.
//DB contains columns: [item] / [helpurl]
//Example Data: [wins.a] / [/help/gettingstarted.html]
//That is why in this previous post (http://stackoverflow.com/q/28096922/3112803)
//I was wanting to know if a function could know it's own name so I wouldn't
//have to type this line below for 1000s of items.
this.myName = 'wins.a';
console.log('Window-A is now displayed. Use "'+this.myName+'" to make link to help topic.');
},
b: function(args) {
this.myName = 'wins.b';
console.log('Window-B is now displayed. Use "'+this.myName+'" to make link to help topic.');
},
c: function(args) {
this.myName = 'wins.c';
console.log('Window-C is now displayed. Use "'+this.myName+'" to make link to help topic.');
}
};
Another approach would be to re-write the current usage of accessing object properties via the dot notation and use the bracket notation instead.
launch_no_eval:function(id) {
//we init a blank dhtmlxwindow and do some other things here, then...
if (id==="window_a") var x = wins.a({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_b") var x = wins.b({x:1}); //fill dhtmlxwindow with proper content
else if (id==="window_c") var x = wins.c({x:1}); //fill dhtmlxwindow with proper content
//and so on for literally thousands of items.
}
could be re-written as follows,
launch_no_eval:function(id) {
var x = wins[id]({x:1});
}
This would mean your HTML markup can change from,
Window-A<br>
Window-B<br>
Window-C<br><br>
to,
Window-A<br>
Window-B<br>
Window-C<br><br>
As Daniel commented below, assuming you have no control over the HTML markup and must use the window_# value being passed in then you could perform the following,
launch_no_eval:function(id) {
// replace 'window_' with empty string to retrieve unique id
var x = wins[id.replace('window_', '')]({x:1});
}

Last evaluated expression in Javascript

Is it possible in Javascript to get the result of the last evaluated expression? For example:
var a = 3;
var b = 5;
a * b;
console.log(lastEvaluatedExpression); // should print 15
So it would be something like eval() where it returns the last evaluated expression, but I cannot use eval().
There is no standard, reified concept of "the result of the last evaluated expression" in JavaScript. There are actually not too many languages that do have such a thing. Various JavaScript REPLs may provide some facility along these lines, but that's specific to those REPLs. Therei s no general "JavaScript" way.
-- package.json --
"dependencies": {
"stream-buffers": "^3.0.1"
},
-- main.js --
const streamBuffers = require('stream-buffers');
const repl = require('repl');
const reader = new streamBuffers.ReadableStreamBuffer();
const writer = new streamBuffers.WritableStreamBuffer();
const r = repl.start({
input: reader,
output: writer,
writer: function (output) {
console.log(output)
return output;
}
});
reader.push(`
var a = 3;
var b = 5;
a * b;`);
reader.stop();
-- output --
undefined
undefined
15
see: https://nodejs.org/api/repl.html
This is not possible in javascript. The only way I can think of right now (that doesn't involve writing a new interpreter) is to use Coffeescript. Coffeescript automatically returns the last expression.
http://coffeescript.org/
http://coffeescript.org/extras/coffee-script.js
That includes functions such as Coffeescript.compile and Coffeescript.eval.
There is no standard call for the last evaluated expression. No you would need to store values. For example you could do it like this:
var a = 3;
var b = 5;
var c = a * b;
var consoleResult = c.toString();
console.log(consoleResult); // should print 15
//Then make your program logic change the value of consoleResult, as needed.

Trying to avoid eval getting array element from window object

I've got a function that wants to access a global variable, the name of which arrives as a string argument. This is how it looks now, using eval:
function echoVar(whichone){
document.write(eval(whichone));
}
I thought I'd just use the window[] syntax and have this:
function echoVar(whichone) {
document.write(window[whichone]);
}
If I create a var and call it like this, it doc writes ABC as expected:
var abc = "ABC";
echoVar("abc");
If the var I want to access is an array element though, it doesn't work:
var def = ["DEF"];
echoVar("def[0]"); //fails with undefined
Obviously that's actually executing window[def[0]] which rightly gives undefined (because there's no variable called DEF). What I actually want to happen is that it executes window["def"][0].
The only way I know to achieve this, is to do a split on the whichone parameter with "[" as the delimiter and then use the split [0] as the window index and a parseInt on split [1] to get the index, like this:
function echoVar(whichone){
if(whichone.indexOf("[")==-1){
document.write(window[whichone]);
}
else{
var s = whichone.split("[");
var nam = s[0];
var idx = parseInt(s[1]);
document.write( window[nam][idx] );
}
}
Am I overlooking something obvious? I'd rather keep the eval than have to do all that.
If you dislike using eval in your code, you can always do this:
function echoVar(whichone) {
document.write(Function("return " + whichone)());
}
Unless this is some sick experiment, you should never be writing Javascript code that looks like this. This is terrible; you need to re-think your design. I know this isn't what you're looking for, but it's the right answer.
The fact is you've got a piece of a javscript expression in a string so you either have to parse it yourself or use eval to parse it for you unless you change the way it's passed like this:
function echoVar(a,b) {
var x = window[a];
if (b) {
x = x[b];
}
document.write(x);
}
And, then you can pass it differently like this:
var def = ["DEF"];
echoVar("def", 0); // def[0]
You could even make this support multiple dimensions if you needed to.
function echoVar(a) {
var x = window[a];
for (var i = 1; i < arguments.length; i++) {
x = x[arguments[i]];
}
document.write(x);
}
var def = {myObject: {length: 3}}
echoVar("def", "myObject", "length"); // def["myObject"]["length"] or def.myObject.length
You can see it work here: http://jsfiddle.net/jfriend00/dANwq/
It would be simpler to lose the brackets and call the item with dot notation
function reval(s, O){
s= String(s);
O= O || window;
var N= s.split(".");
while(O && N.length) O= O[N.shift()];
return O || s;
}
window.def= ['definition'];
alert(reval('def.0'))
/* returned value: (String)
definition
*/

Why does this function work for literals but not for more complex expressions?

I am having some insidious JavaScript problem that I need help with. I am generating HTML from a JSON structure. The idea is that I should be able to pass a list like:
['b',{'class':'${class_name}'}, ['i', {}, 'Some text goes here']]
...and get (if class_name = 'foo')...
<b class='foo'><i>Some text goes here.</i></b>
I use the following functions:
function replaceVariableSequences(str, vars) {
/* #TODO Compiling two regexes is probably suboptimal. */
var patIdent = /(\$\{\w+\})/; // For identification.
var patExtr = /\$\{(\w+)\}/; // For extraction.
var pieces = str.split(patIdent);
for(var i = 0; i < pieces.length; i++) {
if (matches = pieces[i].match(patExtr)) {
pieces[i] = vars[matches[1]];
}
}
return pieces.join('');
}
function renderLogicalElement(vars, doc) {
if (typeof(doc[0]) == 'string') {
/* Arg represents an element. */
/* First, perform variable substitution on the attribute values. */
if (doc[1] != {}) {
for(var i in doc[1]) {
doc[1][i] = replaceVariableSequences(doc[1][i], vars);
}
}
/* Create element and store in a placeholder variable so you can
append text or nodes later. */
var elementToReturn = createDOM(doc[0], doc[1]);
} else if (isArrayLike(doc[0])) {
/* Arg is a list of elements. */
return map(partial(renderLogicalElement, vars), doc);
}
if (typeof(doc[2]) == 'string') {
/* Arg is literal text used as innerHTML. */
elementToReturn.innerHTML = doc[2];
} else if (isArrayLike(doc[2])) {
/* Arg either (a) represents an element
or (b) represents a list of elements. */
appendChildNodes(elementToReturn, renderLogicalElement(vars, doc[2]));
}
return elementToReturn;
}
This works beautifully sometimes, but not others. Example from the calling code:
/* Correct; Works as expected. */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = 4;
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
/* Incorrect; Substitutes "0" for the expression instead of the value of
`siblings.length` . */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = siblings.length; // Notice change here!
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
When I trap out the first argument of renderLogicalElement() using alert(), I see a zero. Why is this?? I feel like it's some JavaScript type thing, possibly having to do with object literals, that I'm not aware of.
Edit: I have this code hooked up to the click event for a button on my page. Each click adds a new row to the <tbody> element whose ID is kv_body. The first time this function is called, siblings is indeed zero. However, once we add a <tr> to the mix, siblings.length evaluates to the proper count, increasing each time we add a <tr>. Sorry for not being clearer!! :)
Thanks in advance for any advice you can give.
If new_id is 0, doesn't it mean that siblings.length is 0? Maybe there is really no sibling.
Perhaps siblings.length is actually 0? Try debugging further (e.g. with Firebug)
OK, I fixed it. As it turns out, I was modifying my source JSON object with the first function call (because in JS you are basically just passing pointers around). I needed to write a copy function that would make a new copy of the relevant data.
http://my.opera.com/GreyWyvern/blog/show.dml/1725165
I ended up removing this as a prototype function and just making a regular old function.

Categories

Resources