Skip to content

Commit

Permalink
fix: type-widening bug when handling plugins. (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximDevoir authored Mar 24, 2020
1 parent 61a6d30 commit ef3c4ce
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 47 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ See [before-after-hook](https://github.com/gr2m/before-after-hook#readme) for

## Plugins

Octokit’s functionality can be extended using plugins. THe `Octokit.plugin()` method accepts a function or an array of functions and returns a new constructor.
Octokit’s functionality can be extended using plugins. The `Octokit.plugin()` method accepts a plugin (or many) and returns a new constructor.

A plugin is a function which gets two arguments:

Expand All @@ -353,10 +353,10 @@ In order to extend `octokit`'s API, the plugin must return an object with the ne

```js
// index.js
const MyOctokit = require("@octokit/core").plugin([
const MyOctokit = require("@octokit/core").plugin(
require("./lib/my-plugin"),
require("octokit-plugin-example")
]);
);

const octokit = new MyOctokit({ greeting: "Moin moin" });
octokit.helloWorld(); // logs "Moin moin, world!"
Expand Down Expand Up @@ -388,11 +388,11 @@ You can build your own Octokit class with preset default options and plugins. In

```js
const MyActionOctokit = require("@octokit/core")
.plugin([
.plugin(
require("@octokit/plugin-paginate"),
require("@octokit/plugin-throttle"),
require("@octokit/plugin-retry"),
])
require("@octokit/plugin-retry")
)
.defaults({
authStrategy: require("@octokit/auth-action"),
userAgent: `my-octokit-action/v1.2.3`,
Expand Down
36 changes: 29 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
OctokitPlugin,
RequestParameters,
ReturnTypeOf,
UnionToIntersection,
} from "./types";
import { VERSION } from "./version";

Expand Down Expand Up @@ -42,22 +43,43 @@ export class Octokit {
}

static plugins: OctokitPlugin[] = [];
/**
* Attach a plugin (or many) to your Octokit instance.
*
* @example
* const API = Octokit.plugin(plugin1, plugin2, plugin3, ...)
*/
static plugin<
S extends Constructor<any> & { plugins: any[] },
T extends OctokitPlugin | OctokitPlugin[]
>(this: S, pluginOrPlugins: T) {
T1 extends OctokitPlugin | OctokitPlugin[],
T2 extends OctokitPlugin[]
>(this: S, p1: T1, ...p2: T2) {
if (p1 instanceof Array) {
console.warn(
[
"Passing an array of plugins to Octokit.plugin() has been deprecated.",
"Instead of:",
" Octokit.plugin([plugin1, plugin2, ...])",
"Use:",
" Octokit.plugin(plugin1, plugin2, ...)",
].join("\n")
);
}
const currentPlugins = this.plugins;
const newPlugins = Array.isArray(pluginOrPlugins)
? pluginOrPlugins
: [pluginOrPlugins];

let newPlugins: (OctokitPlugin | undefined)[] = [
...(p1 instanceof Array
? (p1 as OctokitPlugin[])
: [p1 as OctokitPlugin]),
...p2,
];
const NewOctokit = class extends this {
static plugins = currentPlugins.concat(
newPlugins.filter((plugin) => !currentPlugins.includes(plugin))
);
};

return NewOctokit as typeof NewOctokit & Constructor<ReturnTypeOf<T>>;
return NewOctokit as typeof NewOctokit &
Constructor<UnionToIntersection<ReturnTypeOf<T1> & ReturnTypeOf<T2>>>;
}

constructor(options: OctokitOptions = {}) {
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type ReturnTypeOf<
* @author https://stackoverflow.com/users/2887218/jcalz
* @see https://stackoverflow.com/a/50375286/10325032
*/
type UnionToIntersection<Union> = (
export type UnionToIntersection<Union> = (
Union extends any ? (argument: Union) => void : never
) extends (argument: infer Intersection) => void // tslint:disable-line: no-unused
? Intersection
Expand Down
21 changes: 21 additions & 0 deletions test/__snapshots__/plugin.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Octokit.plugin() supports array of plugins and warns of deprecated usage 1`] = `
[MockFunction] {
"calls": Array [
Array [
"Passing an array of plugins to Octokit.plugin() has been deprecated.
Instead of:
Octokit.plugin([plugin1, plugin2, ...])
Use:
Octokit.plugin(plugin1, plugin2, ...)",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;
60 changes: 27 additions & 33 deletions test/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import { Octokit } from "../src";

const pluginFoo = () => {
return { foo: "bar" };
};
const pluginBaz = () => {
return { baz: "daz" };
};
const pluginQaz = () => {
return { qaz: "naz" };
};

describe("Octokit.plugin()", () => {
it("gets called in constructor", () => {
const MyOctokit = Octokit.plugin(() => {
return {
foo: "bar",
};
});
const MyOctokit = Octokit.plugin(pluginFoo);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
});

it("supports array of plugins", () => {
const MyOctokit = Octokit.plugin([
() => {
return {
foo: "bar",
};
},
() => {
return { baz: "daz" };
},
]);
it("supports array of plugins and warns of deprecated usage", () => {
const warningSpy = jest.spyOn(console, "warn").mockImplementation();
const MyOctokit = Octokit.plugin([pluginFoo, pluginBaz]);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
expect(myClient.baz).toEqual("daz");
expect(warningSpy).toMatchSnapshot();
warningSpy.mockClear();
});

it("supports multiple plugins", () => {
const MyOctokit = Octokit.plugin(pluginFoo, pluginBaz, pluginQaz);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
expect(myClient.baz).toEqual("daz");
expect(myClient.qaz).toEqual("naz");
});
it("does not override plugins of original constructor", () => {
const MyOctokit = Octokit.plugin((octokit) => {
return {
foo: "bar",
};
});
const MyOctokit = Octokit.plugin(pluginFoo);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");

const octokit = new Octokit();
expect(octokit).not.toHaveProperty("foo");
});
Expand Down Expand Up @@ -64,17 +66,9 @@ describe("Octokit.plugin()", () => {
});

it("supports chaining", () => {
const MyOctokit = Octokit.plugin(() => {
return {
foo: "bar",
};
})
.plugin(() => {
return { baz: "daz" };
})
.plugin(() => {
return { qaz: "naz" };
});
const MyOctokit = Octokit.plugin(pluginFoo)
.plugin(pluginBaz)
.plugin(pluginQaz);

const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
Expand Down

0 comments on commit ef3c4ce

Please sign in to comment.