Skip to content
Closed
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
11 changes: 2 additions & 9 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,10 @@ declare namespace PluginError {

/**
* @param plugin Plugin name
* @param message Error message
* @param error Base error / Error message
* @param options Error options
*/
new (plugin: string, message: string, options?: Options): PluginError;

/**
* @param plugin Plugin name
* @param error Base error
* @param options Error options
*/
new <E extends Error>(plugin: string, error: E, options?: Options): PluginError<E>;
new <E extends Error>(plugin: string, error: E | string, options?: Options): PluginError<E>;

/**
* @param plugin Plugin name
Expand Down
41 changes: 41 additions & 0 deletions test/types/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,44 @@ import PluginError = require("plugin-error");
}
}

{
const PLUGIN_NAME = 'test';
function createPluginError(err: Error | string) {
return new PluginError(PLUGIN_NAME, err);
}
}

{
const PLUGIN_NAME = 'test';

interface IFooError extends Error {
foo: number;
}

function createPluginError(err: IFooError | string) {
return new PluginError(PLUGIN_NAME, err);
}

const fooError: IFooError = Object.assign(new Error('something broke'), {foo: 1});
const pluginError = createPluginError(fooError);
const foo: number = pluginError.foo;
Copy link
Member

@demurgos demurgos Jan 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for the change and sorry for the back-and-forth.
At this point, foo should be number | undefined: if you invoke createPluginError("message"), then foo won't be defined. This was the reason why in my first message I said that an additional signature was required.

I then got confused in my reply two hours ago when I told you to merge the signatures: I should have stuck with my initial reply consisting in just updating the return type of your initial commit.

  export interface Constructor {
    /**
     * @param options Options with plugin name and message
     */
    new(options: Options & {plugin: string, message: string}): PluginError;

    /**
     * @param plugin Plugin name
     * @param message Error message
     * @param options Error options
     */
    new (plugin: string, message: string, options?: Options): PluginError;

    /**
     * @param plugin Plugin name
     * @param error Base error
     * @param options Error options
     */
    new <E extends Error>(plugin: string, error: E, options?: Options): PluginError<E>;

    /**
     * @param plugin Plugin name
     * @param error Base error or error message
     * @param options Error options
     */
    new <E extends Error = Error>(plugin: string, error: E | string, options?: Options): PluginError<E | {[K in keyof E]: undefined}>;

    /**
     * @param plugin Plugin name
     * @param options Options with message
     */
    new(plugin: string, options: Options & {message: string}): PluginError;
  }

Here is the explanation:

// Nitpick: The convention in TS is to avoid the `I` prefix for interfaces.
interface FooError extendsError {
  foo: number;
}

function createError1(error: FooError) {
  const pluginError = new PluginError(PLUGIN_NAME, error);
  // In this case, we know that the second parameter is an error with a custom property `foo`
  // So the result will also have a property `foo`
  const foo: number = pluginError.foo;
}


function createError2(error: string) {
  const pluginError = new PluginError(PLUGIN_NAME, error);
  // In this case, we know that the second parameter is a string. It means that the result is a simple `PluginError`
  // and does not have `foo`. The following code would cause a compilation error:
  // const foo: any = pluginError.foo;
}

function createError3(error: FooError | string) {
  const pluginError = new PluginError(PLUGIN_NAME, error);
  // In this case we don't know if we received an error instance or string: we cannot use the type-checker
  // to prove that `foo` is defined. The correct type for the `foo` property is `number | undefined`.
  const foo: number | undefined = pluginError.foo;
  // It will be defined if we call `createError3(Object.assign(new Error("msg"), {foo: 1}));`
  // It will be undefined if we call `createError3("msg");`
}

The FooBar case is not yet properly supported by the engine so it's better to not test it. Here is a better illustration of the problem:

interface Named {
  name: string;
  foo: undefined;
  bar: undefined
}

interface NamedFooBar {
  name: string;
  foo: number;
  bar: number;
}

type Union = Named | NamedFooBar;

function exec(union: Union) {
    if (union.foo !== undefined) {
        // This fails with TS2322 when `strictNullChecks` is enabled.
        const bar: number = union.bar;
    }
}

}

{
// Inference with union type on second parameter and dependent properties
const PLUGIN_NAME = 'test';

interface IFooBarError extends Error {
foo: number;
bar: number;
}

function createPluginError(err: IFooBarError | string) {
return new PluginError(PLUGIN_NAME, err);
}

const fooError: IFooBarError = Object.assign(new Error('something broke'), {foo: 1, bar: 2});
const pluginError = createPluginError(fooError);
const foo: number = pluginError.foo;
const bar: number = pluginError.bar;
}