Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/async-rewriter2/src/async-writer-babel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('AsyncWriter', () => {
let plainFn: sinon.SinonStub;
let implicitlyAsyncMethod: sinon.SinonStub;
let plainMethod: sinon.SinonStub;
let implicitlyAsyncValue: any;
let ctx: any;
let runTranspiledCode: (code: string, context?: any) => any;
let runUntranspiledCode: (code: string, context?: any) => any;
Expand All @@ -24,6 +25,7 @@ describe('AsyncWriter', () => {
plainFn = sinon.stub();
implicitlyAsyncMethod = sinon.stub();
plainMethod = sinon.stub();
implicitlyAsyncValue = undefined;

asyncWriter = new AsyncWriter();
ctx = vm.createContext({
Expand All @@ -42,6 +44,11 @@ describe('AsyncWriter', () => {
{ [Symbol.for('@@mongosh.syntheticPromise')]: true });
},
plainMethod
},
get implicitlyAsyncValue() {
return Object.assign(
Promise.resolve(implicitlyAsyncValue),
{ [Symbol.for('@@mongosh.syntheticPromise')]: true });
}
});
runTranspiledCode = (code: string, context?: any) => {
Expand Down Expand Up @@ -345,6 +352,20 @@ describe('AsyncWriter', () => {
expect(() => runTranspiledCode('const a = 42; const a = 43;'))
.to.throw(/has already been declared/);
});

it('supports typeof for un-defined variables', () => {
expect(runTranspiledCode('typeof nonexistent')).to.equal('undefined');
});

it('supports typeof for implicitly awaited function calls', async() => {
implicitlyAsyncFn.resolves(0);
expect(await runTranspiledCode('typeof implicitlyAsyncFn()')).to.equal('number');
});

it('supports typeof for implicitly awaited values', async() => {
implicitlyAsyncValue = 'abc';
expect(await runTranspiledCode('typeof implicitlyAsyncValue')).to.equal('string');
});
});

context('error handling', () => {
Expand Down
22 changes: 22 additions & 0 deletions packages/async-rewriter2/src/stages/transform-maybe-await.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default ({ types: t }: { types: typeof BabelTypes }): babel.PluginObj<{ f
const isGeneratedHelper = asNodeKey(Symbol('isGeneratedHelper'));
const isOriginalBody = asNodeKey(Symbol('isOriginalBody'));
const isAlwaysSyncFunction = asNodeKey(Symbol('isAlwaysSyncFunction'));
const isExpandedTypeof = asNodeKey(Symbol('isExpandedTypeof'));
// Using this key, we store data on Function nodes that contains the identifiers
// of helpers which are available inside the function.
const identifierGroupKey = '@@mongosh.identifierGroup';
Expand Down Expand Up @@ -318,6 +319,27 @@ export default ({ types: t }: { types: typeof BabelTypes }): babel.PluginObj<{ f
...wrapperFunction
]));
},
UnaryExpression: {
enter(path) {
if (path.node.operator === 'typeof' && path.node.argument.type === 'Identifier' && !path.node[isExpandedTypeof]) {
// 'typeof foo'-style checks have a double use; they not only report
// the 'type' of an expression, but when used on identifiers, check
// whether they have been declared, and if not, return 'undefined'.
// This is annoying. We replace `typeof foo` with
// `typeof foo === 'undefined' ? 'undefined' : typeof foo`.
// The first `typeof foo` is marked as a generated helper and not
// transformed any further, the second is transformed as usual.
path.replaceWith(t.conditionalExpression(
t.binaryExpression(
'===',
{ ...path.node, [isGeneratedHelper]: true, [isExpandedTypeof]: true },
t.stringLiteral('undefined')),
t.stringLiteral('undefined'),
{ ...path.node, [isExpandedTypeof]: true }
));
}
}
},
Expression: {
enter(path) {
// Minor adjustment: When we encounter a 'shorthand' arrow function,
Expand Down