JS variable names - plain source vs as keys - javascript

I am looking for information on the difference between acceptable variable names in JavaScript, when they are key names in an object versus when the variables are referenced declaratively.
For example:
in an object, I can do this:
var obj = {
'##A': 1,
'#B': 2,
'C*': 3
};
however, we cannot reference those variables in the code, as this will be some form of syntax error
of course, we cannot do this:
obj.##A++; //syntax error
obj.#B++; //syntax error
obj.C*++; //syntax error
and we definitely cannot do this either:
var ##A = 1; //syntax error
var #B = 2; //syntax error
var C* = 3; //syntax error
I believe the primary difference is that the key of object can take any value of a JS Symbol. Whereas a variable name cannot take any value of Symbol.
So this is a two part question:
How is this disparity described in JavaScript? What was the motivation to have the disparity between Symbol's being valid key names but not valid variable names?
Is there a publicly available regex that can validate a JS variable name? I want to run this regex against keys of a particular object. The reason is that users of a library that I am writing will define variable names with the keys from an object, and they could easily use Symbol characters that would be invalid JS variable names.
To be more specific, this is for a dependency injection facility.
The end-user defines dependencies like so:
const deps = {
'request': function(){
return require('request'); //useless, but as a simple example
},
'users': function(){ //no problems here
return db.users.find({});
},
'users-older-than-13': function(){ //invalid because of "-" chars
return db.users.find({age: {gt: 13}});
}
};

Related

How to extract variable names and property names from string of JavaScript code?

Given a JavaScript expression as a string, how can I extract the names of variables used inside the expression, and going beyond that, extract the names of any properties accessed on these variables?
These expressions are written by another author using their own personalized names like cabbage and elephant.weight.
The expression could be simple, like: firstName, name.charAt(0), c.radius * Math.PI
But could also be something complex like:
(function(){
var fruit = apple || banana.length;
if (fruit > citrus.lime) {
fruitSalad += "," + citrus.lemon;
// ...
return lemon.split(/[0-9]+/);
}
return fruitBowl.width * fruitBowl['amount'] * Math.SQRT2;
})();
Running the above expressions will throw reference errors unless variables like apple are defined. If I have a way of parsing the expression and extracting the undefined variable names, I can take note of them, and run the expression at a later time without any issues, with values for each variable.
Additionally, if I can take note of which variables have properties and the names of those properties, then I can initialize these also.
fruit = apple = banana = fruitSalad = "";
citrus = { lemon: "0", lime: "1" };
fruitBowl = { width: "10", amount: "30" };
// run the expression
Stuff I've Tried
Simply running the expression and using try/catch to catch each reference error, parsing the exception text for the variable name. This is perilous as exception text is different in each browser. This also doesn't account for parts of the expression which aren't immediately evaluated such as in isTrue? yesText : noText. As accessing undefined properties of objects does not yield an error, these can't be noted.
Parsing the expression into an AST using a library such as Esprima. This is my first time using AST (Abstract Syntax Tree). I think I can access the identifiers used in the code but there doesn't seem to be any clear distinction between variable names and property names. As the output is quite verbose, it would be easy to let something slip. I have looked into AST walkers, but nothing stands out as being a solution.
Note: Assume that eval will not be used. Variables will always be strings or objects whose properties are strings, so knowing the type of variable is not an issue, only the names used. Names of functions used in the expressions can be ignored.

Conditional grammar rule in PEGjs

I'm trying to implement a simple DSL that parses basic arithmetic expressions. This needs to be done in the browser, so I'm using PEGjs to generate the parser.
Terms in the expression can be numbers (integers or real), variables (variables are properties on a context object passed to the parser), conditionals or properties accessed via dot notation.
I want the conditionals to look like this condition?value, where if condition is true, the term equates to value. The variables on either side of the ? could also be dot notation accessed properties of an object like this object.property1?object.property2.
So if the parser is passed an object like this:
context = {
depth: 100,
material: {
thickness: 20
include: true
}
edge: {
face: 4.5
}
}
The expression:
500 + depth + material.include?edge.face + material.thickness should equate to 624.5.
I've been using the PEGjs online editor. I've tried lots of different approaches, but I can't seem to nail the conditional. Everything else works. Here are the relevant rules:
Variable "variable"
= variable:identifier accessor:("." identifier)* {
var result = context[variable], i
for (i = 0; i < accessor.length; i++) {
result = result[accessor[i][1]]
}
return result
}
identifier
= identifier:$([0-9a-zA-Z_\$]+)
Conditional
= condition:Variable "?" value:Variable {
return condition ? value : 0
}
I've looked at the example grammar for javascript in the PEGjs github repo, and the conditional rule looks a lot like what I've got here, but I still can't get it to work.
What would be the correct way to implement a conditional statement like the one I've described in a PEGjs rule?
I know that this is a bit late, but the issue is that your variable is a string evaluating to "material.include".
Look at this code:
var result = context[variable], i
You are trying to access a property named "material.include" from your context object, which would look like this:
{
"material.include": true
}
Rather than trying to access the object referenced by the "material" property, and then the "include" property off the resulting object, which would look like this:
{
"material": {
"include": true
}
}
The solution would be to split the variable string by "." characters and then recursively find your property:
Variable "variable"
= variable:identifier accessor:("." identifier)* {
var path = variable.split(".");
var result = path.reduce( (nextObject, propName) => nextObject[propName], context );
for (var i = 0; i < accessor.length; i++) {
result = result[accessor[i][1]]
}
return result
}
Note that this solution is not complete, as it will cause an error if you try to access material.include where material is never defined in your context. You may want to add additional error handling, but it does work for the given example.

Give eval a value in JavaScript

very basic JavaScript programmer here!
I was busy on some code with variables that look like this:
blocktype1;
blocktype2;
blocktype3;
blocktype4;
... //everything between blocktype4 and blocktype70, the three dots are not actual code!
blocktype70;
Now I was using eval() in a function where a value was given to one of the blocktype variables. The blocktype depended on the variable "number".
This is what I had for that part:
eval("blocktype" + number) = 3
What I want is, say "number" is 27, then I want the variable blocktype27 to get a value of 3.
When I check the console it says:
ReferenceError: Invalid left-hand side in assignment
Could anyone possibly help me?
I would prefer just vanilla JavaScript and still the use of eval.
Thank you for your time!
The 'correct' solution would probably be to use an Array which is ideal for sequences and are accessible by index.
var number = 1;
var val = 3;
var blocktype = []; // so clean
blocktype[number] = val;
However, properties can be accessed as with the bracket notation as well. This assumes the variables are in global scope and are thus properties of the global (window) object.
var blocktype1; // .. etc
window["blocktype" + number] = val;
The problem with the eval is that is effectively the same as doing f() = 3 which does not make sense: only variables/properties can be assigned to1.
However eval is a built-in function and the results of a function cannot be assigned to, per the error message. It could be written as
var blocktype1; // .. etc (see dandavis' comment)
eval("blocktype" + number + " = " + val);
// What is actually eval'd is:
// eval("blocktype1 = 3")
which quickly exposes a flaw with eval. If val was the string "Hello world!" with would result in eval("blocktype1 = Hello world!") which is clearly invalid.
1 For the gritty: the left-hand side of an assignment has to be a Reference Specification Type, which is a more wordy way of describining the above behavior. (It is not possible for a JavaScript function to return a RST, although it could technically be done for vendor host objects.)
Feel free not to accept this, since it's specifically not using eval(), but:
You can allocate an array of size 71 like so:
var blocktype = new Array(71);
(your number values apparently start at 1, so we'll have to ignore the first element, blocktype[0], and leave room for blocktype[70], the 71st)
You can now assign elements like this:
blocktype[number] = 3;
and use them like so:
alert( blocktype[number] );

javascript - String to real Object

I have a javascript objects var flower_1; var flower_2;
My question is if I have another variable for example a String var Name;
And lets say for example: Name = "flower_1";
How can I change the Name variable into an object "flower_1"
If I understand your question correctly, you have something like this:
function foo() {
var flower_1 = { /* ... */ };
var flower_2 = { /* ... */ };
var name = "flower_1";
var target = /* code here to get the correct object based on `name` */;
}
You can do that, but it should be avoided if at all possible:
var target = eval(name);
eval is a very big, and easily abused tool which should be, and can be, avoided. I've never had to use it in production code in several years of JavaScript development. Also note that eval is disallowed in the new "strict" mode of the language (one of many improvements strict mode brings).
In this particular case, it's pretty easy to avoid:
function foo() {
var objects = {
flower_1: { /* ... */ },
flower_2: { /* ... */ }
};
var name = "flower_1";
var target = objects[name];
}
Now, flower_1 and flower_2 are properties of an object, and you can use bracketed notation ([]) with a string name to access those properties. This is because in JavaScript objects, you can either access a property using dotted notation and a literal (e.g., obj.foo), or using bracketed notation and a string (e.g., obj["foo"]). In the second case, the string doesn't have to be a string literal, it can be the result of an expression, including (as in this case) retrieving the string from a variable.
Here's a live example of both techniques.
Note that if your var statements are globals, then those vars become properties of the global object, which on web browsers is window, so if they're globals you could access them via window[name] for exactly the same reason objects[name] works. But it's best practice to avoid global variables (entirely if you can, or exposing just one with a good unique name that then contains all of your public stuff if necessary — e.g., if external code needs to access your stuff).
You can use the eval() function:
var flower_1 = "abc";
var name = "flower_1";
var x = eval(name); // = "abc"
It's worth noting though that use of the eval() function is not best practise, and should be avoided at all costs.
A better (and much more recommended) solution would be to use an associative array:
var name = "flower_1";
var arr = new Array();
arr["flower_1"] = "abc";
var x = arr[name]; // = "abc"

method to convert form names to valid JavaScript names?

i am processing an object in JavaScript that has been returned from an API that may contain variable names that are not valid JavaScript variable names. (these names are valid in the naming convention of the system of the API i am using)
in one case, the name starts with a number, e.g. '5story'
what is the best way to convert this name to a valid JavaScript variable name?
(i am familiar with the form of legal JavaScript names. a useful answer to this question would be a (hopefully simple) algorithm e.g. prefix a $ to the name and then strip it when returning the form to the API)
(i note that it would be preferable if the JavaScript API did not create invalid names)
Note that I'm not sure if you're asking about html identifier names (you mention "form") or Javascript variable identifiers, but how about stripping any character that is not a word character (A-Z, a-z, 0-9, _) and prepending with an underscore?
var v = ' .-*&*$*$W 5 foo Bar';
v = '_' + v.replace(/\W/g, '');
v; // _W5fooBar
The .replace() will strip characters that are not legal in variable identifiers.
You could prepend with the $ symbol instead of underscore for a legal variable name too.
You could creat a wrapper object that also contained a back reference to the original name.
function MyApiClient()
{
var _self = this;
this.RegisterWrapper = function(strName, objValue, explicitName)
{
var newName = (explicitName != null) ? explicitName : '_' + strName.replace(/\W/g, '');
_self[newName] = {ApiName : strName, Value : objValue};
return _self[newName];
}
}
//implementation
var client = new MyApiClient();
client.RegisterWrapper('5story', [0,1,2,3,4,5]);
console.log(client._5story.Value); //output: Array() [0,1,2,3,4,5];
//or
var o = client.RegisterWrapper('5story', {a:'b'}, '_myName');
console.log(o.Value); //outpus Object a: 'b'
console.log(client._myName.Value); //outpus Object a: 'b'
This extends a couple additional benefits
Scope: when creating the new objects they will be encapsulated and not globals
Reference: if you have reference to this object - you can use it to hit your API (because it will contain the old name)
Flexibility: you can either register the wrappers dynamically through a loop as you parse a string returned from the api or explicitely like above.
Do you have any more information about the actual api?

Categories