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
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
},
},
};
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.
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);
});
}
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
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