Add shim for ES Promise

This commit is contained in:
Ron Buckton
2019-10-07 16:41:46 -07:00
parent 01b3d41124
commit 4d0d010376
17 changed files with 465 additions and 26 deletions

View File

@@ -49,6 +49,7 @@
"allowDeclarations": true
}],
"no-double-space": "error",
"async-type": "error",
"boolean-trivia": "error",
"no-in-operator": "error",
"simple-indent": "error",

View File

@@ -0,0 +1,40 @@
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "async-type",
meta: {
docs: {
description: ``,
category: "Possible Errors",
recommended: "error",
},
messages: {
asyncTypeError: `Async functions must have an explicit return type`,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
const checkAsyncFunction = (node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression) => {
if (node.async && !node.returnType) {
context.report({ messageId: "asyncTypeError", node });
}
};
const checkAsyncMethod = (node: TSESTree.MethodDefinition) => {
if (node.value.type === AST_NODE_TYPES.FunctionExpression) {
checkAsyncFunction(node.value);
}
};
return {
[AST_NODE_TYPES.FunctionDeclaration]: checkAsyncFunction,
[AST_NODE_TYPES.FunctionExpression]: checkAsyncFunction,
[AST_NODE_TYPES.ArrowFunctionExpression]: checkAsyncFunction,
[AST_NODE_TYPES.MethodDefinition]: checkAsyncMethod
};
},
});

View File

@@ -0,0 +1,55 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/async-type");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("async-type", rule, {
valid: [
{
code: `
async function foo(): Promise<void> {}
`,
},
{
code: `
const fn = async function(): Promise<void> {}
`,
},
{
code: `
const fn = async (): Promise<void> => {}
`,
},
{
code: `
class C {
async method(): Promise<void> {}
}
`,
},
],
invalid: [
{
code: `async function foo() {}`,
errors: [{ messageId: "asyncTypeError" }]
},
{
code: `const fn = async function() {}`,
errors: [{ messageId: "asyncTypeError" }]
},
{
code: `const fn = async () => {}`,
errors: [{ messageId: "asyncTypeError" }]
},
{
code: `class C { async method() {} }`,
errors: [{ messageId: "asyncTypeError" }]
},
]
});

View File

@@ -1,7 +1,9 @@
/* eslint-disable async-type */
/// <reference lib="esnext.asynciterable" />
// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
/// <reference types="node" />
import Octokit = require("@octokit/rest");
const {runSequence} = require("./run-sequence");
import fs = require("fs");

View File

@@ -1,5 +1,7 @@
/* eslint-disable async-type */
/// <reference lib="esnext.asynciterable" />
/// <reference lib="es2015.promise" />
// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
import Octokit = require("@octokit/rest");
import {runSequence} from "./run-sequence";

View File

@@ -1,3 +1,4 @@
/* eslint-disable async-type */
/// <reference types="node" />
import childProcess = require("child_process");

View File

@@ -58,6 +58,23 @@ namespace ts {
push(...values: T[]): void;
}
/* @internal */
export interface PromiseConstructor extends PromiseConstructorLike {
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason: unknown) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
resolve(): Promise<void>;
reject<T = never>(reason: unknown): Promise<T>;
all<T>(promises: (T | PromiseLike<T>)[]): Promise<T[]>;
race<T>(promises: (T | PromiseLike<T>)[]): Promise<T>;
}
/* @internal */
export interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
/* @internal */
export type EqualityComparer<T> = (a: T, b: T) => boolean;
@@ -75,8 +92,9 @@ namespace ts {
/* @internal */
namespace ts {
// Natives
// NOTE: This must be declared in a separate block from the one below so that we don't collide with the exported definition of `Map`.
declare const Map: (new <T>() => Map<T>) | undefined;
// NOTE: These must be declared in a separate block from the one below so that we don't collide with the exported definitions of `Map` and `Promise`.
declare const Map: MapConstructor | undefined;
declare const Promise: PromiseConstructor | undefined;
/**
* Returns the native Map implementation if it is available and compatible (i.e. supports iteration).
@@ -86,6 +104,10 @@ namespace ts {
// eslint-disable-next-line no-in-operator
return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined;
}
export function tryGetNativePromise(): PromiseConstructor | undefined {
return typeof Promise === "function" ? Promise : undefined;
}
}
/* @internal */
@@ -100,6 +122,14 @@ namespace ts {
throw new Error("TypeScript requires an environment that provides a compatible native Map implementation.");
})();
export const Promise: PromiseConstructor = tryGetNativePromise() || (() => {
// NOTE: createPromiseShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it.
if (typeof createPromiseShim === "function") {
return createPromiseShim();
}
throw new Error("TypeScript requires an environment that provides a compatible native Promise implementation.");
})();
/** Create a new map. */
export function createMap<T>(): Map<T> {
return new Map<T>();

175
src/shims/promiseShim.ts Normal file
View File

@@ -0,0 +1,175 @@
/* @internal */
namespace ts {
declare function setTimeout<A extends any[]>(handler: (...args: A) => void, timeout: number, ...args: A): any;
export interface PromiseConstructor extends PromiseConstructorLike {
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason: unknown) => void) => void): Promise<T>;
prototype: Promise<any>;
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
resolve(): Promise<void>;
reject<T = never>(reason: unknown): Promise<T>;
all<T>(promises: (T | PromiseLike<T>)[]): Promise<T[]>;
race<T>(promises: (T | PromiseLike<T>)[]): Promise<T>;
}
export interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
export function createPromiseShim(): PromiseConstructor {
class Promise<T> {
private _state: "pending" | "fulfilled" | "rejected" = "pending";
private _result: unknown;
private _reactions: PromiseReaction[] = [];
constructor(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason: any) => void) => void) {
const { resolve, reject } = createResolvingFunctions(this);
try {
executor(resolve, reject);
}
catch (e) {
reject(e);
}
}
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> {
return new Promise<TResult1 | TResult2>((resolve, reject) => {
const reaction: PromiseReaction = { resolve, reject, onfulfilled, onrejected };
if (this._state === "pending") {
this._reactions.push(reaction);
}
else {
setTimeout(promiseReactionJob, 0, reaction, this._state === "fulfilled" ? "fulfill" : "reject", this._result);
}
});
}
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
return this.then(/*onfulfilled*/ undefined, onrejected);
}
static resolve(): Promise<void>;
static resolve<T>(value: T | PromiseLike<T>): Promise<T>;
static resolve<T>(value?: T | PromiseLike<T>): Promise<T> {
return value instanceof this && value.constructor === this ? value : new Promise<T>(resolve => resolve(value));
}
static reject<T = never>(reason: any): Promise<T> {
return new Promise<T>((_, reject) => reject(reason));
}
static all<T>(promises: (T | PromiseLike<T>)[]): Promise<T[]> {
return new Promise<T[]>((resolve, reject) => {
let count = promises.length;
const values: T[] = Array<T>(count);
for (let i = 0; i < promises.length; i++) {
let called = false;
this.resolve(promises[i]).then(
value => {
if (!called) {
called = true;
values[i] = value;
count--;
if (count === 0) {
resolve(values);
}
}
},
reject);
}
});
}
static race<T>(promises: (T | PromiseLike<T>)[]): Promise<T> {
return new Promise<T>((resolve, reject) => {
for (const promise of promises) {
this.resolve(promise).then(resolve, reject);
}
});
}
}
interface PromiseReaction {
resolve: (value: unknown) => void;
reject: (reason: unknown) => void;
onfulfilled?: ((value: unknown) => unknown) | null;
onrejected?: ((reason: unknown) => unknown) | null;
}
function createResolvingFunctions<T>(promise: Promise<T>) {
let called = false;
return {
resolve: (value: T | Promise<T>) => {
if (!called) {
called = true;
try {
if (promise === value) throw new TypeError();
// eslint-disable-next-line no-null/no-null
const then = typeof value === "object" && value !== null && (<Promise<unknown>>value).then;
if (typeof then !== "function") {
settlePromise(promise, "fulfill", value);
}
else {
setTimeout(resolveThenableJob, 0, promise, value, then);
}
}
catch (e) {
settlePromise(promise, "reject", e);
}
}
},
reject: (reason: any) => {
if (!called) {
called = true;
settlePromise(promise, "reject", reason);
}
}
};
}
function settlePromise(promise: Promise<unknown>, verb: "fulfill" | "reject", value: unknown) {
/* eslint-disable dot-notation */
const reactions = promise["_reactions"];
promise["_result"] = value;
promise["_reactions"] = undefined!;
promise["_state"] = verb === "fulfill" ? "fulfilled" : "rejected";
for (const reaction of reactions) {
setTimeout(promiseReactionJob, 0, reaction, verb, value);
}
/* eslint-enable dot-notation */
}
function resolveThenableJob<T>(promiseToResolve: Promise<T>, thenable: T, thenAction: Promise<T>["then"]) {
const { resolve, reject } = createResolvingFunctions(promiseToResolve);
try {
thenAction.call(thenable, resolve, reject);
}
catch (e) {
reject(e);
}
}
function promiseReactionJob(reaction: PromiseReaction, verb: "fulfill" | "reject", argument: unknown) {
const handler = verb === "fulfill" ? reaction.onfulfilled : reaction.onrejected;
if (handler) {
try {
argument = handler(argument);
verb = "fulfill";
}
catch (e) {
argument = e;
verb = "reject";
}
}
const action = verb === "fulfill" ? reaction.resolve : reaction.reject;
action(argument);
}
return Promise;
}
}

View File

@@ -4,6 +4,7 @@
"outFile": "../../built/local/shims.js"
},
"files": [
"mapShim.ts"
"mapShim.ts",
"promiseShim.ts"
]
}

View File

@@ -62,6 +62,7 @@
"unittests/reuseProgramStructure.ts",
"unittests/semver.ts",
"unittests/createMapShim.ts",
"unittests/createPromiseShim.ts",
"unittests/transform.ts",
"unittests/config/commandLineParsing.ts",
"unittests/config/configurationExtension.ts",

View File

@@ -0,0 +1,131 @@
describe("unittests:: createPromiseShim", () => {
function expectFulfilled<T>(done: Mocha.Done, value: T) {
return (_value: T) => {
try {
assert.strictEqual(_value, value);
}
catch (e) {
return done(e);
}
done();
};
}
function expectNotFulfilled(done: Mocha.Done) {
return () => done(new Error("expected promise to be rejected"));
}
function expectNotRejected(done: Mocha.Done) {
return (_reason: any) => done(_reason);
}
function expectRejected(done: Mocha.Done, reason: any) {
return (_reason: any) => {
try {
assert.strictEqual(_reason, reason);
}
catch (e) {
return done(e);
}
done();
};
}
it("executor resolves correctly with non-promise", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = {};
new PromiseShim(resolve => resolve(value)).then(
expectFulfilled(done, value),
expectNotRejected(done)
);
}).timeout(500);
it("executor resolves correctly with promise", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = { };
const p = { then(cb: (v: {}) => void) { cb(value); } };
new PromiseShim(resolve => resolve(p)).then(
expectFulfilled(done, value),
expectNotRejected(done)
);
}).timeout(500);
it("executor rejects correctly", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = { };
new PromiseShim((_, reject) => reject(value)).then(
expectNotFulfilled(done),
expectRejected(done, value)
);
}).timeout(500);
it("catches error in executor", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = {};
new PromiseShim(() => { throw value; }).then(
expectNotFulfilled(done),
expectRejected(done, value)
);
}).timeout(500);
it("catches error in onfulfilled of 'then'", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = {};
new PromiseShim(resolve => resolve(/*value*/ undefined)).then(
() => { throw value; },
expectNotRejected(done)
).then(
expectNotFulfilled(done),
expectRejected(done, value)
);
}).timeout(500);
it("catches error in onrejected of 'then'", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const first = {};
const second = {};
new PromiseShim((_, reject) => reject(first)).then(
expectNotFulfilled(done),
_ => { throw second; }
).then(
expectNotFulfilled(done),
expectRejected(done, second)
);
}).timeout(500);
it("Promise.resolve resolves correctly with non-promise", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = {};
PromiseShim.resolve(value).then(
expectFulfilled(done, value),
expectNotRejected(done)
);
}).timeout(500);
it("Promsie.resolve resolves correctly with promise", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = { };
const p = { then(cb: (v: {}) => void) { cb(value); } };
PromiseShim.resolve(p).then(
expectFulfilled(done, value),
expectNotRejected(done)
);
}).timeout(500);
it("Promsie.resolve returns same promise if instance", () => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = { };
const first = PromiseShim.resolve(value);
const second = PromiseShim.resolve(first);
assert.strictEqual(first, second);
});
it("Promise.reject rejects correctly", done => {
const PromiseShim = ts.createPromiseShim(); // tslint:disable-line variable-name
const value = { };
PromiseShim.reject(value).then(
expectNotFulfilled(done),
expectRejected(done, value)
);
}).timeout(500);
});

View File

@@ -1,6 +1,6 @@
describe("unittests:: evaluation:: asyncArrowEvaluation", () => {
// https://github.com/Microsoft/TypeScript/issues/24722
it("this capture (es5)", async () => {
it("this capture (es5)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export class A {
b = async (...args: any[]) => {

View File

@@ -1,5 +1,5 @@
describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => {
it("return (es5)", async () => {
it("return (es5)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
async function * g() {
return Promise.resolve(0);
@@ -13,7 +13,7 @@ describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => {
{ value: 0, done: true }
]);
});
it("return (es2015)", async () => {
it("return (es2015)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
async function * g() {
return Promise.resolve(0);

View File

@@ -1,6 +1,6 @@
describe("unittests:: evaluation:: awaiter", () => {
// NOTE: This could break if the ECMAScript spec ever changes the timing behavior for Promises (again)
it("await (es5)", async () => {
it("await (es5)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
async function a(msg: string) {
await Promise.resolve();

View File

@@ -1,5 +1,5 @@
describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
it("sync (es5)", async () => {
it("sync (es5)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
let i = 0;
const iterator: IterableIterator<any> = {
@@ -25,7 +25,7 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
assert.strictEqual(result.output[2], 3);
});
it("sync (es2015)", async () => {
it("sync (es2015)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
let i = 0;
const iterator: IterableIterator<any> = {
@@ -51,7 +51,7 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
assert.strictEqual(result.output[2], 3);
});
it("async (es5)", async () => {
it("async (es5)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
let i = 0;
const iterator = {
@@ -77,7 +77,7 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
assert.instanceOf(result.output[2], Promise);
});
it("async (es2015)", async () => {
it("async (es2015)", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
let i = 0;
const iterator = {

View File

@@ -1,6 +1,6 @@
describe("unittests:: evaluation:: objectRest", () => {
// https://github.com/microsoft/TypeScript/issues/31469
it("side effects in property assignment", async () => {
it("side effects in property assignment", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
const k = { a: 1, b: 2 };
const o = { a: 3, ...k, b: k.a++ };
@@ -8,7 +8,7 @@ describe("unittests:: evaluation:: objectRest", () => {
`);
assert.deepEqual(result.output, { a: 1, b: 1 });
});
it("side effects in during spread", async () => {
it("side effects in during spread", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
const k = { a: 1, get b() { l = { c: 9 }; return 2; } };
let l = { c: 3 };
@@ -17,7 +17,7 @@ describe("unittests:: evaluation:: objectRest", () => {
`);
assert.deepEqual(result.output, { a: 1, b: 2, c: 9 });
});
it("trailing literal-valued object-literal", async () => {
it("trailing literal-valued object-literal", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
const k = { a: 1 }
const o = { ...k, ...{ b: 2 } };

View File

@@ -1,5 +1,5 @@
describe("unittests:: evaluation:: optionalCall", () => {
it("f?.()", async () => {
it("f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
function f(a) {
output.push(a);
@@ -11,7 +11,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.isUndefined(result.output[1]);
});
it("o.f?.()", async () => {
it("o.f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
@@ -25,7 +25,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x.f?.()", async () => {
it("o.x.f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
@@ -41,7 +41,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.f()", async () => {
it("o?.f()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
@@ -55,7 +55,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o?.f?.()", async () => {
it("o?.f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
@@ -69,7 +69,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x?.f()", async () => {
it("o.x?.f()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
@@ -85,7 +85,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x.f()", async () => {
it("o?.x.f()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
@@ -101,7 +101,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f()", async () => {
it("o?.x?.f()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
@@ -117,7 +117,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f?.()", async () => {
it("o?.x?.f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
@@ -133,7 +133,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("f?.()?.()", async () => {
it("f?.()?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
function g(a) {
output.push(a);
@@ -150,7 +150,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[1], 2);
assert.isUndefined(result.output[2]);
});
it("f?.().f?.()", async () => {
it("f?.().f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
@@ -169,7 +169,7 @@ describe("unittests:: evaluation:: optionalCall", () => {
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
it("f?.()?.f?.()", async () => {
it("f?.()?.f?.()", async (): Promise<void> => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {