Babel: Replacing ArrowFunctionExpression vs FunctionDeclaration in ExportDefaultDeclaration - babeljs

Say I have the following code I want to transform:
export default () => {};
The following visitor code works:
export default function ({ types: t }) {
return {
visitor: {
ArrowFunctionExpression(path) {
if (!path.node.body.directives.some(d => d.value.value === 'inject')) return;
path.node.body.directives = path.node.body.directives.filter(d => d.value.value !== 'inject');
path.replaceWith(t.arrayExpression([path.node]));
}
}
};
}
That results in the following output:
export default [() => {}];
Great! Now change the input:
export default function () {
'inject';
};
And the visitor:
export default function ({types: t}) {
return {
visitor: {
FunctionDeclaration(path) {
if (!path.node.body.directives.some(d => d.value.value === 'inject')) return;
path.node.body.directives = path.node.body.directives.filter(d => d.value.value !== 'inject');
path.replaceWith(t.arrayExpression([path.node]));
}
}
};
}
That produces the following error:
TypeError: unknown: Property elements[0] of ArrayExpression expected node to be of a type ["null","Expression","SpreadElement"] but instead got "FunctionDeclaration"
Okay, so convert the FunctionDeclaration to a FunctionExpression:
export default function ({types: t}) {
return {
visitor: {
FunctionDeclaration(path) {
if (!path.node.body.directives.some(d => d.value.value === 'inject')) return;
path.node.body.directives = path.node.body.directives.filter(d => d.value.value !== 'inject');
path.replaceWith(
t.arrayExpression([
t.functionExpression(path.node.id, path.node.params, path.node.body, path.node.generator, path.node.async),
])
);
}
}
};
}
And I get the following error:
TypeError: unknown: Property declaration of ExportDefaultDeclaration expected node to be of a type ["FunctionDeclaration","ClassDeclaration","Expression"] but instead got "ExpressionStatement"
And that is where I'm lost. I'm creating an ArrayExpression just as I was with the default-exported arrow function. Why is it complaining about receiving an ExpressionStatement?
Note, the desired output is as such:
export default [function () {
}];

Related

Unable to parse class method decorator in a Babel plugin

I'm writing a Babel plugin that manipulates the AST node related to a specific decorator. I'm traversing the AST but for some reason, my plugin doesn't detect the method decorator - node.decorators is always null when the visitor visits a node.
This is the plugin:
import { ClassMethod, Decorator } from '#babel/types';
import { get } from 'lodash';
import { NodePath, PluginObj } from '#babel/core';
const providerArgumentsTransformer = (): PluginObj => ({
visitor: {
ClassMethod({ node, parent }: NodePath<ClassMethod>) {
const decorator = getProviderDecorator(node.decorators); // <- node.decorators is always null
if (getDecoratorName(decorator) === 'Provides') {
console.log('Success');
}
},
},
});
function getProviderDecorator(decorators: Array<Decorator> | undefined | null): Decorator | undefined {
return decorators?.find((decorator) => get(decorator, 'expression.callee.name') === 'Provides');
}
function getDecoratorName(decorator?: Decorator): string | undefined {
return get(decorator, 'expression.callee.name');
}
export default providerArgumentsTransformer;
I'm testing the decorator as follows:
import { PluginObj } from '#babel/core';
import * as babel from '#babel/core';
import providerArgumentsTransformer from './providerArgumentsTransformer';
const code = `class MainGraph {
Provides(clazz, propertyKey, descriptor) { }
#Provides()
someString(stringProvider) {
return stringProvider.theString;
}
}`;
describe('Provider Arguments Transformer', () => {
const uut: PluginObj = providerArgumentsTransformer();
it('Exposes transformer', () => {
babel.transformSync(code, {
plugins: [
['#babel/plugin-proposal-decorators', { legacy: true }],
['#babel/plugin-proposal-class-properties', { legacy: true }],
[uut, { legacy: true }],
],
configFile: false,
});
});
});
I wonder if the issue is related to how babel.transformSync is used or perhaps the visitor is not configured properly.
Turns out the decorators were missing because #babel/plugin-proposal-decorators clears the decorators when it traverses the AST.
In order to visit the node before #babel/plugin-proposal-decorators I had to modify my visitor a bit. This approach should probably be optimized by visiting ClassBody or ClassExpression instead of Program.
const providerArgumentsTransformer: PluginObj = {
visitor: {
Program(path: NodePath<Program>) {
path.traverse(internalVisitor);
},
},
};
const internalVisitor = {
ClassMethod: {
enter({ node }: NodePath<ClassMethod>) {
// node.decorators are not null anymore
},
},
};

How to use Grid.js with data being updated every second

setInterval(() => {
// lets update the config
grid.updateConfig({
server: {
url: document.location.href + 'api.json/',
then: data => data.map(obj => {
return [obj.name, obj.value]
}),
handle: (res) => {
// no matching records found
if (res.status === 404) return { data: [] }
if (res.ok) return res.json()
throw Error('oh no :(')
}
}
}).forceRender()
}, 2000)
This snippet make the work, but loading message and flicking/redraw all table are ruining UX.

How to dynamically add parameters to firestore query and map to object

What is the recommended way to map the data to an object and return it as a promise/observable while being able to add dynamic/conditional parameters to the query.
In getCompanies2 I can dynamically add parameters to the query but I can't figure out how to map the data returned to my object and return it as a promise/observable.
In getCompanies everything works as I want it but I have to duplicate the code (as below) if I have dynamic query parameters to add.
Note: convertDocTimeStampsToDate just does what it says. I have excluded it to reduce the size of the code section.
getCompanies(searchText: string, useWhereActive: boolean): Observable<Company[]> {
if (useWhereActive) {
return this.db.collection('companies', ref => ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff')
.where('active', '==', true)
)
.snapshotChanges()
.pipe(
map(snaps => convertSnaps<Company>(snaps)),
first()
);
} else {
return this.db.collection('companies', ref => ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff')
)
.snapshotChanges()
.pipe(
map(snaps => convertSnaps<Company>(snaps)),
first()
);
}
}
​
getCompanies2(searchText: string, useWhereActive: boolean) {
let query = this.db.collection('companies').ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff');
​
if (useWhereActive) {
query.where('active', '==', true);
}
​
query.get().then(querySnapshot => {
const results = this.convertDocuments<Company>(querySnapshot.docs);
console.log(results);
});
}
convertDocuments<T>(docs) {
return <T[]>docs.map(doc => {
return {
id: doc.id,
...doc.data()
};
});
}
export function convertSnaps<T>(snaps) {
return <T[]>snaps.map(snap => {
const data = convertDocTimeStampsToDate(snap.payload.doc.data());
return {
id: snap.payload.doc.id,
...data
};
});
}
I got it to work like below, I guess I am still getting my head around promises.
Any better solutions will be accepted though as I am still learning and don't know if this is the best method.
getCompanies2(searchText: string, useWhereActive: boolean) {
let query = this.db.collection('companies').ref
.orderBy('name').startAt(searchText).endAt(searchText + '\uf8ff');
if (useWhereActive) {
query.where('active', '==', true);
}
return query.get().then(querySnapshot => {
return this.convertDocuments<Company>(querySnapshot.docs);
});
}

Unit Test with Jest , Class constructor

How to test the following Class that has validation in construction using set.
const BaseParameter = class BaseParameter {
constructor(addr, fullname, value) {
this.addr = addr;
this.fullname = fullname;
this.value = value;
}
get value() {
return this._value;
}
set value(value) {
if (typeof value !== "number") {
throw new TypeError(`Parameter ${this.fullname} should be a number`);
}
this._value = value;
}
};
I have tried this following method of Jest.
test("BaseParameter with invalid constructor", () => {
expect(new BaseParameter("test", "test fullname", "a")).toThrowError(
TypeError
);
});
but throws the error and the pass fails.
The docs have clear example, I just has stuck
test("BaseParameter with invalid constructor", () => {
expect(() => new BaseParameter("test", "test fullname", "a")).toThrowError(
TypeError
);
});
https://jestjs.io/docs/en/es6-class-mocks

If use async/await, it transpiles code above my "imports" and makes my func undefined

I currently have this code:
// Imports
const {utils: Cu, Constructor: CC} = Components;
Cu.import('resource://gre/modules/Services.jsm');
Services.scriptloader.loadSubScript('chrome://trigger/content/webextension/scripts/3rd/polyfill.min.js');
function install() {}
function uninstall() {}
function shutdown() {}
function startup() { // this gets automatically called by the environment i am designing for
Services.prompt.alert(Services.wm.getMostRecentWindow('navigator:browser'), 'title', 'body');
}
async function doAsync() {
return await new Promise(() => setTimeout(()=>resolve('done'), 5000));
}
However after compiling with babel with this .babelrc:
{
"presets": ["es2015", "es2017"],
"plugins": ["transform-object-rest-spread"],
"ignore": [
"3rd/**/*",
]
}
It moves my doAsync function to the top. Before the imports. Which is a problem. Because the import needs to happen right away. As startup gets called right away.
The compiled code becomes this:
'use strict';
var doAsync = function () {
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return new Promise(function () {
return setTimeout(function () {
return resolve('done');
}, 5000);
});
case 2:
return _context.abrupt('return', _context.sent);
case 3:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function doAsync() {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
// Imports
var _Components = Components,
Cu = _Components.utils,
CC = _Components.Constructor;
Cu.import('resource://gre/modules/Services.jsm');
Services.scriptloader.loadSubScript('chrome://trigger/content/webextension/scripts/3rd/polyfill.min.js');
function install() {}
function uninstall() {}
function shutdown() {}
function startup() {
// this gets automatically called by the environment i am designing for Services.prompt.alert(Services.wm.getMostRecentWindow('navigator:browser'), 'title', 'body');
}
Is there anyway so that if I use async/await that it doesn't move stuff to above my imports? If my imports at the top of the file, it fails to run. Because the imports brings in the polyfill.min.js