Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic property name in interface definition and ambient external module problem #5644

Closed
ericlu88 opened this issue Nov 12, 2015 · 7 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@ericlu88
Copy link

Hi,

I am trying to write the type definition file(.d.ts) for bunyan-middleware but encounter two problems that I couldn't figure out how to resolve.

A little bit of context: bunyan-middleware is an express middleware that attaches a bunyan logger object to each express incoming req object as req.<propertyName>, where <propertyName> is controlled by the bunyan-middleware option.propertyName.

Now the .d.ts file I tried:

/// <reference path="../bunyan/bunyan.d.ts" />
/// <reference path="../express/express.d.ts" />

declare module Express {
    // Problem1: Error get from tsc 1.6.2
    // error TS1147: Import declarations in a namespace cannot reference a module.
    import * as bunyan from 'bunyan';
    export interface Request {
        // Problem2: Have not found a way to dynamic create the property(e.g. propertyName)
        // NOTE: 'log' is the default property name when propertyName option is omitted
        log: bunyan.Logger;
    }
}

declare module "bunyan-middleware" {
    import * as bunyan from 'bunyan';
    import * as express from 'express';

    module e {
        interface Options {
            logger: bunyan.Logger;
            propertyName?: string;
        }
    }

    function e(options: e.Options): express.RequestHandler;
    export = e;
}

So two problems(also inline in the source above):

  1. How do I type the log property as bunyan.Logger? How can I reference an ambient external module while doing declaration merging for express?
  2. How can I correctly add the property name to express.Request based on an input option(option.propertyName)?

Thanks

@RyanCavanaugh
Copy link
Member

Unfortunately there aren't solutions to either question right now.

The first problem is #4166. We're looking at solutions here.

The second problem is not solvable in the current type system; I don't think we have any proposal for solving this either. It would be a big change -- there's no concept of a function call altering the type system like that. We don't see this pattern very often -- it seems like a bad one, honestly. Just in practical terms, what happens when two people call the function twice with multiple property names? Or a property that already exists?

@ericlu88
Copy link
Author

@RyanCavanaugh Thanks for the quick reply. I will keep an eye on the first issue you mentioned there. For the second problem, I totally agree that it is beyond the static typing realm. It might be ok here for bunyan-middleware cause it is designed to be called only once(think it as constructor). I will omit the propertyName option in the .d.ts file now so user can only use the default property name(log).

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Nov 13, 2015
@mhegazy mhegazy closed this as completed Nov 13, 2015
@tkrotoff
Copy link

I'm trying to add a custom matcher for Jasmine and I have the same problem with tsc 1.7.5:

declare module jasmine {
  // FIXME error TS1147: Import declarations in a namespace cannot reference a module.
  import * as errorHandler from 'api-error-handler';

  interface Matchers {
    toEqualErrorResponse(expected: errorHandler.Response): boolean;
  }
}

An ugly solution is to use any instead of a specific type (errorHandler.Response in this example) or re-declare the specific type inside the module (only feasible in simple cases).

@scharf
Copy link

scharf commented Jun 2, 2016

I have a similar problem with an ambient definition:

declare module MyModule {
  // ERROR TS1147: Import declarations in a namespace cannot reference a module.
  import {AnInterface} from 'some-module';

  export var foo: AnInterface;
}

I do not understand why this is causing an error. As I understand that in typescript (according to @basarat), there is a type declaration space and a variable declaration space.

The import problematic above is a pure import into the declaration space, so why is that causing an error?????

@scharf
Copy link

scharf commented Jun 2, 2016

If I have a module: foo.js:

export interface Foo {
}
export function foo () {
}

and a module bar.ts:

import {Foo} from "./foo";
var x: Foo;

In this case no require is generated, because it imports into the declaration space.

Only if I import a variable:

import {foo} from "./foo";
foo();

then the a require statement is generated.

Therefore I think this error is invalid and it makes writing ambient definitions difficult.

I would be happy, if I could write an ignore error comment for that statement (like eslint, eshint etc support)

@scharf
Copy link

scharf commented Jul 22, 2016

There seems to be a subtle difference on how I declare my module. If the module name is in quotes, then the semantics is different:

declare module "foo" {
    export interface Foo {
        foo():void;
    }
}

// when I quote the module name, everything is fine
declare module "bar" {
    import foo = require("foo");
    import http = require("http");
    export const bar:foo.Foo;
    export const server:http.Server;
}

// when I do not quote the module name, then TypeScript complains
declare module Bar {
    import foo = require("foo");  // TS1147: Import declarations in a namespace cannot reference a module.
    import http = require("http"); // TS1147: Import declarations in a namespace cannot reference a module.
    export const bar:foo.Foo;
    export const server:http.Server;
}

I am not sure where that comes form and where this is explained...

@mhegazy
Copy link
Contributor

mhegazy commented Jul 22, 2016

TypeScript uses the keyword "module" for two concepts "external modules" ( i.e. modules that need a module loader to load) and "namespace" (i.e. a named object literal). in hindsight this overloading of concepts was not a good idea.

so the one with quoted name is an "external modules" the one with unquoted name is a "namespace".

you can find more documenation about this in http://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

5 participants