Function or method as value in collection - hacklang

Is it possible to pass a function or method call as value in a collection of any type?
$collection = Vector {
function(){}
};
The code above throws Fatal error: syntax error, unexpected T_FUNCTION, expecting '}'

Oops, this looks like a limitation of the HHVM parser. Below are a few options for working around it using locals. Feel free to file an issue on GitHub to support the literal syntax in your original question if it's important to you.
Option 1:
$v = Vector {};
$f = function () {};
$v[] = $f;
Option 2:
$f = function () {};
$v = Vector {$f};
etc.

Related

How can I read a file in node js, find all instances of a function and then extract each function's argument?

I'm trying to write a node script that identifies unused translation strings in my React project.
First, I want to get a list of all the translations that are used. To do this, I am getting a list of each JS file in my /src/components folder and then reading the file.
My translation strings look like this: t('some.translation.key'), so basically, I want to identify each instance of t('...') using RegEx and then get the key in between those parentheses (i.e. "some.translation.key"). From there, I should be able to compare the keys to the ones in my translation JSON file and remove the ones that aren't being used.
unused.js
const path = require('path');
const fs = require('fs');
let files = [];
// https://stackoverflow.com/a/63111390/2262604
function getFiles(dir) {
fs.readdirSync(dir).forEach(file => {
const absolute = path.join(dir, file);
if (fs.statSync(absolute).isDirectory()) {
getFiles(absolute);
} else {
if (absolute.includes('.js')) {
files.push(absolute);
}
}
});
return files;
}
function getTranslations() {
const pathComponents = path.join(__dirname, '../../src/components');
// get all js files in components directory
const files = getFiles(pathComponents);
const translationKeys = [];
// for each js file
for(let i = 0; i < files.length; i++) {
// read contents of file
const contents = fs.readFileSync(files[i]).toString();
// search contents for all instances of t('...')
// and get the key between the parentheses
}
}
getTranslations();
How can I use RegEx to find all instances of t('...') in contents and then extract the ... string between the parentheses?
Yes, you could use a regular expression:
for (const [, str] of contents.matchAll(/\bt\(['"](.*?)['"]\)/g)) {
console.log('t called with string argument:', str)
}
However, with regular expressions the problem will be that they don't understand the code and would cause trouble with matching strings that contain ( ) or \' themselves, have issues with concatenated strings or extra whitespace, etc., and you'd then also get the contents literally, including possible escape sequences.
A more robust way would be to create an AST (abstract syntax tree) from the code and look for calls to t in it.
A popular AST parser would be acorn. There is also the supplementary module acorn-walk that helps walking through the whole syntax tree without building your own recursive algorithm.
import acorn from 'acorn'
import walk from 'acorn-walk'
// Example
const contents = "function a () { if (123) { t('hello') } return t('world') }"
// The arguments to acorn.parse would have to be adjusted based
// on what kind of syntax your files can use.
const result = acorn.parse(contents, {ecmaVersion: 2020})
walk.full(result, node => {
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 't') {
if (node.arguments.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
// This is for the case `t` is called with a single string
// literal as argument.
console.log('t called with string argument:', node.arguments[0].value)
} else {
// In case you have things like template literals as well,
// or multiple arguments, you'd need to handle them here too.
console.log('t called with unknown arguments:', node.arguments)
}
}
})
// Will output:
// t called with string argument: hello
// t called with string argument: world

TS/Node.js: Getting the absolute path of the class instance rather than the class itself

Is there a way to get the path (__dirname) of the file where an instance of a class was made without passing that into the constructor?
For example,
// src/classes/A.ts
export class A {
private instanceDirname: string;
constructor() {
this.instanceDirname = ??
}
}
// src/index.ts
import { A } from "./classes/A"
const a = new A();
// a.instanceDirname === __dirname ✓
I tried callsite, it worked but I had to do some regex that I'm not happy with to get what I need, I also tried a module called caller-callsite, but that ended up returning the module path, not the path of the file where the instance was made.
Is there a workaround for this?
I would have callers pass in the location information. Sniffing this stuff seems like a code smell to me (pardon the pun). ;-)
But you can do it, by using regular expressions on the V8 call stack from an Error instance, but it still involves doing regular expressions (which you didn't like with callsite), though it's doing them on V8's own stacks, which aren't likely to change in a breaking way (and certainly won't except when you do upgrades of Node.js, so it's easy to test). See comments:
// A regular expression to look for lines in this file (A.ts / A.js)
const rexThisFile = /\bA\.[tj]s:/i;
// Your class
export class A {
constructor() {
// Get a stack trace, break into lines -- this is V8, we can rely on the format
const stackLines = (new Error().stack).split(/\r\n|\r|\n/);
// Find the first line that doesn't reference this file
const line = stackLines.find((line, index) => index > 0 && !rexThisFile.test(line));
if (line) {
// Found it, extract the directory from it
const instanceOfDirName = line.replace(/^\s*at\s*/, "")
.replace(/\w+\.[tj]s[:\d]+$/, "")
.replace(/^file:\/\//, "");
console.log(`instanceOfDirName = "${instanceOfDirName}"`);
}
}
}
Those three replaces can be combined:
const instanceOfDirName = line.replace(/(?:^\s*at\s*(?:file:\/\/)?)|(?:\w+\.[tj]s[:\d]+$)/g, "");
...but I left them separate for clarity; not going to make any performance difference to care about.

Argument of type '{}' is not assignable to parameter of type 'Uint8Array'

I'm trying to clone and run an opensource project repo and having hard time fixing this issue, npm start fails with "compile failed error' and its states the following reason.
Argument of type '{}' is not assignable to parameter of type 'Uint8Array'
const [encChallenge] = await waitEvent(socket, 'data')
const challenge = decrypt(encChallenge) //This line causes the error
Following in the decrypt function
/** Decrypt data used shared key */
function decrypt(data: Uint8Array) {
if (!sharedKey) return null
const nonce = data.slice(0, sodium.crypto_box_NONCEBYTES)
const box = data.slice(sodium.crypto_box_NONCEBYTES, data.length)
const msg = crypto.decrypt(box, nonce, sharedKey)
return msg
}
Changing the parameter to any solves it but I can't do that,
How can I convert my parameter to Unit8Array?
As long as encChallenge is a typed array represents an array of 8-bit unsigned integers then you should just be able to do:
const [encChallenge] = await waitEvent(socket, 'data')
const challenge = decrypt(new Uint8Array(encChallenge);)
really i would make waitEvent encChallenge be of strongly type Uint8Array in waitEvent method, then it's abstracted away and always that type if you reuse it again.

Require() from string into object

Assuming I have the content of a js file in a string. Furthermore, assume it has an exports['default'] = function() {...} and/or other exported properties or functions. Is there any way to "require" it (compile it) from that string into an object, such that I can use it? (Also, I don't want to cache it like require() does.)
Here's a very simple example using vm.runInThisContext():
const vm = require('vm');
let code = `
exports['default'] = function() {
console.log('hello world');
}
`
global.exports = {}; // this is what `exports` in the code will refer to
vm.runInThisContext(code);
global.exports.default(); // "hello world"
Or, if you don't want to use globals, you can achieve something similar using eval:
let sandbox = {};
let wrappedCode = `void function(exports) { ${ code } }(sandbox)`;
eval(wrappedCode);
sandbox.default(); // "hello world"
Both methods assume that the code you're feeding to it is "safe", because they will both allow running arbitrary code.

pass optional parameters to require()

so, I have this problem - and when I have a problem with JavaScript or node inevitably it is my coding that is the problem ;)
So at the risk of ridicule, this is the problem:
I have a module that has an optional parameter for config
Using the standard pattern, this is what I have:
module.exports = function(opts){
return {
// module instance
};
}
and in the calling code there is this
var foo = require('bar')({option: value})
if there are no options to pass, the code looks like this
var foo = require('bar')({})
which kinda looks ugly
so, I wanted to do this
var foo = require('bar')
which doesn't work, as the exports is a function call
so, to the meat of the issue
a) is there any way of achieving this lofty goal ?
b) is there a better pattern of passing parameters to a module ?
many thanks - and I hope that once the laughter has passed you will be able to send some help my way :)
Instead of removing the function call completely, you could make the options argument options to remove the need for an empty object:
module.exports = function(opts) {
opts = opts || {};
return {
// module instance
};
}
It doesn't completely remove the need for () but is better than ({}).
tldr: stick with require('foo')('bar');
There's no way to pass additional parameters to require. Here's the source code, notice how it only takes a single argument:
Module.prototype.require = function(path) {
assert(util.isString(path), 'path must be a string');
assert(path, 'missing path');
return Module._load(path, this);
};
If you really really really want to avoid ()(), you could try something like this:
b.js
'use strict';
module.exports = {
x: 'default',
configure: function (x) {
this.x = x;
},
doStuff: function () {
return 'x is ' + this.x;
}
};
a.js
'use strict';
var b = require('./b');
// Default config:
console.log(b.doStuff()); // 'x is default'
// Reconfigure:
b.configure(42);
console.log(b.doStuff()); // 'x is 42'
But I think it's uglier... stick with the original idea.

Resources