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>
Related
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
I got used to using bind to remember the last result of function and to keep track to be able to use the last result for the next result. For instance to concat or join last string to a new string without using outer variables:
function remStr(outStr){
return function c(lastStr,newStr){
if(!newStr)return lastStr;
var all = lastStr+newStr;
return c.bind(null,all);
}.bind(null,outStr);
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
The problem is that I want to call remStr several times and so bind came into play. But can it be done better or just differently, maybe it turns out that for one case an approach fulfills a task better than remStr?
If I understand your intention correctly, how about just using the closure?
function remStr(outStr) {
return function c(newStr) {
if (!newStr) return outStr;
outStr += newStr;
return c;
}
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
As mentioned by Tomalak in the comments, JavaScript strings are immutable, so if you intend to use large or many strings, you will probably want to buffer them in an array.
function remStr(outStr) {
var buffer = [outStr || ''];
return function c(newStr) {
if (!newStr) return buffer.join('');
buffer.push(newStr);
return c;
}
}
var str = remStr('stack');
str = str('over');
str = str('flow');
str(); // stackoverflow
You shouldn't be using Function.bind here at all. You can cache the arguments. And then join it.
This approach is widely known as functions are also objects and can have properties. Function.bind is used to change the context of the given function and that isn't what we want.
function concat(word){
return function fn(anWord){
if(!anWord) return fn.words.join("");
(fn.words || (fn.words = [word])).push(anWord);
}
}
Now you can use it like below:
var str = concat("stack");
str("over");
str("flow");
console.log(str()); // "stackoverflow"
In a Codecademy assignment, I would like to add a case to the parameter of my function, so whenever a person searches on the name "JoNes", the parameter gets "translated" to "Jones". (this is just to play with Javascript, not mandatory)
According to this website, there is a case called Sentence Case (he also calls it Title Case), however, whenever I use .toSentenceCase, it returns a syntax error on Codecademy. I would like to know if the following code would normally work and that it's Codecademy that doesn't support it in this assignment (in other words: it doesn't expect it, so anything different than the expected is wrong) OR if it's not possible to add a case to the parameter of a function like this.
Bonus: If it's the latter, how would you fix the input coming in through search();, so it always corresponds to the demand of the first letter being upper-case, while the rest is lower case?
The function:
var search = function(lastName.toSentenceCase) {
var contactsLength = contacts.length;
for (var i = 0; i < contactsLength; i++) {
if (lastName === contacts[i].lastName) {
printPerson(contacts[i]);
}
}
}
search("JoNes");
Here you go:
String.prototype.toSentenceCase= function() {
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase()
}
Then:
"JoNes".toSentenceCase();
becomes: Jones
I would try this:
String.prototype.toSentenceCase = function () {
var stringArray = this.split(" ");
for (var index = 0; index < stringArray.length; index++) {
stringArray[index] = stringArray[index].substr(0,1).toUpperCase() + stringArray[index].substr(1).toLowerCase();
}
return stringArray.join(" ");
}
//test:
var testString = new String("JoNe Is A NaMe");
console.log(testString.toSentenceCase());
I am trying to get all combination of a number. For example, input "123" should return ["123", "231", "213", "312", "321", "132"].
Here is my function:
function swapDigits(input) {
for (var i = 0; i++; i < input.length - 1) {
var output = [];
var inter = input.slice(i, i + 1);
var left = (input.slice(0, i) + input.slice(i + 1, input)).split("");
for (var j = 0; j++; j <= left.length) {
var result = left.splice(j, 0, inter).join("");
output.push(result);
}
}
console.log(output);
return output;
}
However this function returns undefined, could anyone tell me what's going wrong?
The errors with the for loop and scope have already been mentioned. Besides that, the splice method will change the string that it operates on. This means that the inner loop will never terminate because left keeps on growing, so j never reaches left.length.
If you are new to a language, I would suggest starting with an implementation that is close to the algorithm that you want to implement. Then, once you are comfortable with it, use more advanced language constructs.
See this fiddle for an example. This is the algorithm code:
function getPermutations(input)
{
if(input.length <= 1)
{
return [input];
}
var character = input[0];
var returnArray = [];
var subPermutes = getPermutations(input.slice(1));
debugOutput('Returned array: ' + subPermutes);
for(var subPermuteIndex = 0; subPermuteIndex < subPermutes.length; subPermuteIndex++ )
{
var subPermute = subPermutes[subPermuteIndex];
for(var charIndex = 0; charIndex <= subPermute.length; charIndex++)
{
var pre = subPermute.slice( 0, charIndex );
var post = subPermute.slice( charIndex );
returnArray.push(pre+character+post);
debugOutput(pre + '_' + character + '_' + post );
}
}
return returnArray;
}
Basically, this will walk to the end of the string and work its way back constructing all permutations of sub-strings. It is easiest to see this from the debug output for 1234. Note that 'Returned array' refers to the array that was created by the permutations of the sub-string. Also note that the current character is placed in every position in that array. The current character is shown between _ such as the 1 in 432_1_.
Returned array: 4
_3_4
4_3_
Returned array: 34,43
_2_34
3_2_4
34_2_
_2_43
4_2_3
43_2_
Returned array: 234,324,342,243,423,432
_1_234
2_1_34
23_1_4
234_1_
_1_324
3_1_24
32_1_4
324_1_
_1_342
3_1_42
34_1_2
342_1_
_1_243
2_1_43
24_1_3
243_1_
_1_423
4_1_23
42_1_3
423_1_
_1_432
4_1_32
43_1_2
432_1_
This algorithm doesn't enforce uniqueness. So, if you have a string 22 then you will get two results - 22,22. Also, this algorithm uses recursion which I think is quite intuitive in this case, however there are pure iterative implementations if you look for them.
There are several errors in that code.
You have the order of the parts of the for statement incorrect. The order is initialization, test, increment. So for (/* init */ ; /* test */ ; /* increment */)
You're creating a new array for each iteration of your outer loop.
I'm making this a CW because I haven't checked for further errors than the above.
I'm trying to create a generic i18n solution for a HTML app I'm working in. I'm looking for alternatives to use eval() to call deeply nested Javascript objects:
Suppose the following HTML example:
<div id="page1">
<h1 data-i18n="html.pageOne.pageTitle"></h1>
</div>
and it's companion Javascript (using jQuery):
var i18n;
i18n = {
html: {
pageOne: {
pageTitle: 'Lorem Ipsum!'
}
}
};
$(document).ready(function () {
$('[data-18n]').each(function () {
var q;
q = eval('i18n.' + $(this).attr('data-i18n'));
if (q) {
$(this).text(q);
}
});
});
Any advices on how to access the "pageTitle" property inside the i18n object without using eval()? I need to keep the object's structure, so changing its layout to a "flat" solution is not feasible.
Thanks!!!
You can use bracket syntax, as others have hinted at. But, you'll need to split and iterate at .:
function lookup(obj, path) {
var keys = path.split('.'),
result = obj;
for (var i = 0, l = keys.length; i < l; i++) {
result = result[keys[i]];
// exit early if `null` or `undefined`
if (result == null)
return result;
}
return result;
}
Then:
q = lookup(i18n, $(this).attr('data-i18n'));
if (q) {
$(this).text(q);
}
The dot syntax (object.field) is really just syntactic sugar for object['field']. If you find yourself writing eval('object.'+field), you should simply write object['field'] instead. In your example above, you probably want: i18n[$(this).attr('data-i18n')].
Since you're encoding your attribute in a way that has dots in it, try splitting it by the dots, and iterating over the fields. For example (this can probably be improved):
var fields = $(this).attr('i18n').split('.');
fieldCount = fields.length;
fieldIdx = 0;
var cur = i18n;
while(cur != undefined && fieldIdx > fieldCount) {
cur = cur[fields[fieldIdx++]];
}
You'll want to do additional checking to make sure all of the fields were handled, nulls weren't encountered, etc.
You can split the string on the periods and traverse the object:
var q = i18n;
$.each($(this).attr('data-i18n').split('.'), function(index, key){
if (q) q = q[key];
});
Demo: http://jsfiddle.net/GsVsr/