Webpack plugin parser hook doesn't tap for expression - javascript

I'm writing a plugin for webpack for extracting values from MyObject.myProperty = 'myValue'; expressions, and my hook doesn't tap for my expression. Here is the example code:
var zzz = (function () {
function zzz() {
return this;
}
zzz.MY_VAR = "My value";
return zzz;
});
Here is how my code for the hook looks like:
parser.hooks.expression.for("zzz.MY_VAR").tap("MyPlugin", expr => {
console.log(expr);
}
I also tried:
parser.hooks.evaluate.for("AssignmentExpression").tap("MyPlugin", expr => {
console.log(expr);
}
Also without success.
I did some debugging, and find out that for some reason when JavascriptParser is calling getFreeInfoFromVariable() in getMemberExpressionInfo() it returns undefined. Because getVariableInfo() method return some scope details instead of VariableInfo instance or 'string'.
Am I missing something ? Is it possible to get value of the object's property via parser somehow ? Or maybe there is another way to do it ?

Related

Pass an object that has an optional property to a function that guarantee it will not be undefined but editor/compiler still think it can be undefined

function f1(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
) {
if (!toolbox.tool1) {
toolbox.tool1 = fetchTool1();
}
if (!toolbox.tool2) {
toolbox.tool2 = fetchTool2();
}
// Do something else
}
function f2(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
) {
f1(toolbox);
// tool1 and tool2 are no longer undefined.
const x = toolbox.tool1.value // Editor shows error, tool1 may be undefined.
}
The design above passes a toolbox object to different functions, so the program doesn't need to fetch the tools that are already fetched by other functions. The problem is, even if I know for sure toolbox.tool1 and toolbox.tool2 will not be undefined after calling f1, the editor still shows error.
I know I can use exclamation mark like const x = toolbox.tool1!.value, or maybe just turn off the warning. But I'm looking for a way that is more friendly to the default type checking.
I have tried let f1 return the toolbox that has no question mark in it, and call toolbox = f1(toolbox). It doesn't work, the line const x = toolbox.tool1.value still shows error.
You're looking for asserts.
f1 now looks like this:
function f1(
toolbox: {
tool1?: Tool1,
tool2?: Tool2,
}
): asserts toolbox is { tool1: Tool1; tool2: Tool2 } {
You can think of this as "changing" the type in the scope it was called:
f1(toolbox);
toolbox // now { tool1: Tool1; tool2: Tool2 }
const x = toolbox.tool1.value; // fine
Playground

Returning Boolean from Cypress Page Object

I'm trying to understand how do i return a boolean from cypress page object?
Use case:
I am trying to check if an element is present on the page. If so,
return a boolean.
Code:
class DataTable {
constructor() {
return this;
}
has(fieldName) {
// HOW DO I RETURN A BOOLEAN HERE?
return cy.get('.view-and-field-name').contains(fieldName)
}
}
Mainpage.spec.js
const dataTable = new DataTable();
expect(dataTable.has(field.fieldName)).to.be.true;
I'd appreciate some insights on what I am doing wrong.
Thank you for the time.
According to this post, and according to cypress suggestions for custom commands and page objects I suggest the next custom command:
Cypress.Commands.add("validateIfElementExistsInDom", (selector) => {
cy.get('body')
.then($body => {
expect($body.find(selector).length > 0).to.be.true
})
})
And you can use it in the test like :
cy.validateIfElementExistsInDom(fieldName)
And a general return boolean function example:
Cypress.Commands.add("validateIfElementExistsInDomAsBoolean", (selector) => {
return cy.get('body')
.then($body => {
return cy.wrap($body.find(selector).length > 0) //Cy wrap is needed so the function is chainable
})
})
//in test code
cy.validateIfElementExistsInDomAsBoolean(fieldName)
.then(boolean => expect(boolean).to.be.true)
The expect() on Mainpage has already been executed internally in cy.get('.view-and-field-name').contains(fieldName), so it is redundant.
If dataTable.has(field.fieldName) fails, the test stops there - it will never pass out a false value.
If you want has(fieldName) to pass out a boolean, you need to switch to jquery inside, e.g
has(fieldName) {
const jquerySelector = `.view-and-field-name:contains(${fieldName})`;
return Cypress.$(jquerySelector);
}
But it is much simpler to go with Cypress commands and use
const dataTable = new DataTable();
dataTable.has(field.fieldName);
Personally, I'd ditch the page object and just use the command directly in the test.
cy.get is not a synchronous function due to the retry-ability strategy,
so there is no way to return Boolean directly.
If necessary, you can use variant of should with callback.
In your case, a simpler variant with exist is suitable (or be.visible, possibly more accurate for your case):
class DataTable {
has(fieldName) {
return cy.get('.view-and-field-name').contains(fieldName);
}
}
const dataTable = new DataTable();
dataTable.getField(field.fieldName).should('exist');

Assign and Query Javascript Arrow Function for Metadata

The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.

Why use parentheses when returning in JavaScript?

In the Restify framework code I found this function:
function queryParser(options) {
function parseQueryString(req, res, next) {
// Some code goes there
return (next());
}
return (parseQueryString);
}
Why would the author write return (next()); and return (parseQueryString);? Does it need parentheses there and if so, why?
Using parentheses when returning is necessary if you want to write your return statement over several lines.
React.js offers a useful example. In the return statement of the render property in a component you usually want to spread the JSX you return over several lines for readability reasons, e.g.:
render: function() {
return (
<div className="foo">
<h1>Headline</h1>
<MyComponent data={this.state.data} />
</div>
);
}
Without parentheses it results in an error!
More generally, not using parentheses when spreading a return statement over several lines will result in an error. The following example will execute properly:
var foo = function() {
var x = 3;
return (
x
+
1
);
};
console.log(foo());
Whereas the following (without the parentheses) will throw errors:
var foo = function() {
var x = 3;
return
x
+
1
;
};
console.log(foo());
It doesn't need to be that way, but it's valid JavaScript code. Actually it's quite uncommon to see such syntax. I guess it's a personal preference of the author.
Parenthesis are used for two purposes in a return statement.
To support multi-line expression as mentioned in #Andru Answer.
To allow returning object in arrow function like the below:
() => ({ name: 'Amanda' }) // Shorthand to return an object
This is equivalent to
() => {
return { name : 'Amanda' }
}
For more information, please check this article.
https://medium.com/#leannezhang/curly-braces-versus-parenthesis-in-reactjs-4d3ffd33128f
// Create a component named MessageComponent
var MessageComponent = React.createClass({
render: function() {
return (
<div>{this.props.message}</div>
);
}
});
NOTE Why do we need the parentheses around the return statement (line
3)? This is because of JavaScript's automatic semicolon insertion.
Without the parentheses, JavaScript would ignore the following lines
and return without a value. If the JSX starts on the same line as the
return, then parentheses are not needed.
Taken from here.
Just to add to what others have said.
Using brackets around the return value is valid JavaScript, but mostly a bad thing to do.
Mostly bad because it doesn't add anything yet increases the size of the JavaScript which means that there is more to download to the browser. Yes most people have fast broadband connections, but don't lose sight of the fact that everything you put in the JavaScript file needs to be downloaded so avoid unnecessary code bloat. This probably doesn't matter if you use a tool to compact your code (minifier has already been mentioned), but not everyone does.
Sometimes it might aid readability. Hard pressed to think of an example in this case, but if the use of brackets makes your JavaScript clearer to you as the developer and thus easier to maintain then use them - even if it goes against what I said about code bloat.
Why would the author write return (next()); ... ?
Regarding next():
Probably because his function is something like this:
function next()
{
var i=0;
return function (){
// Do something with that closured i....
}
}
Regarding (xxx);:
It is unnecessary. Every minifier will remove it.
Example (uglifyJS):
becomes:
I tried:
var a = function() {
return true || true
}
console.log(a());
//return: true
var a = function() {
return
true || true
}
console.log(a());
//return: undefined
var a = function() {
return (
true || true
)
}
console.log(a());
//return: true
While Andru's answer is popular, it is wrong that parantheses are required for multiline return statement. Here, you can see an object of foo and bar is returned with no parantheses needed.
function foo () {
return {
foo: 'foo',
bar: 'bar',
}
}
console.log(foo())
As long as the return line is not just empty space or linebreak, you can have a multiline return just fine. Otherwise Automatic Semicolon Insertion takeover and break your return statement as demonstrated by Andru.
Regarding your question, I am onboard with Darin's answer.
This may be old but the return () can be used in this way:
function parseQueryString(req, res, next) {
var id = req.param('id');
return (id ? "Foo" : "Bar");
}
Less code, easy to read :)

in jaydata: pass variables to filter, only global works

I have this function:
function db_borrarServer(idABorrar){
serversDB.servidores
.filter(function(elementoEncontrado) {
return elementoEncontrado.id_local == this.idABorrar;
})
.forEach(function(elementoEncontrado){
console.log('Starting to remove ' + elementoEncontrado.nombre);
serversDB.servidores.remove(elementoEncontrado);
serversDB.saveChanges();
});
}
does not work, but it does if I replace the variable "this.idABorrar" with a number, it does
return elementoEncontrado.id_local == 3;
or if I declare idABorrar as a global, works to.
I need to pass idABorrar as variable. How can I do this?
The EntitySet filter() function (as any other predicate functions) are not real closure blocks, rather an expression tree written as a function. To resolve variables in this scope you can only rely on the Global and the this which represents the param context. This follows HTML5 Array.filter syntax. To access closure variables you need to pass them via the param. Some examples
inside an event handler, the longest syntax is:
$('#myelement').click(function() {
var element = this;
context.set.filter(function(it) { return it.id_local == this.id; },
{ id: element.id});
});
you can also however omit the this to reference the params as of JayData 1.2 and also use string predicates
$('#myelement').click(function() {
var element = this;
context.set.filter("it.id_local == id", { id: element.id});
});
Note that in the string syntax the use of it to denote the lambda argument is mandatory.
In JayData 1.3 we will have an even simplex calling syntax
$('#myelement').click(function() {
var element = this;
context.set.filter("it.id_local", "==", element.id);
});
In the filter you should pass an object which is the this object, like this:
.filter(function(){},{idABorrar: foo})
foo can be const or any variable which is in scope.
The .filter() function takes an optional 2nd parameter which is assigned to this inside of the first parameter function.
So you can modify your code like so :
function db_borrarServer(idABorrar){
serversDB.servidores
.filter(function(elementoEncontrado) {
return elementoEncontrado.id_local == this;
}, idABorrar)
.forEach(function(elementoEncontrado){
console.log('Starting to remove ' + elementoEncontrado.nombre);
serversDB.servidores.remove(elementoEncontrado);
serversDB.saveChanges();
});
}
Let me know how you go - I'm very new to jaydata too and I've also been going a bit crazy trying to get my head into this paradigm.
But I came across your question trying to solve the same issue, and this is how I resolved it for me.

Categories

Resources