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

Thrown "error TS4022: 'extends' clause of exported interface '...' has or is using private name ..." with --declaration flag and mixing imported export name with equal export interface name #16440

Closed
Manusan42 opened this issue Jun 11, 2017 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@Manusan42
Copy link

Manusan42 commented Jun 11, 2017

TypeScript Version: 2.3.4 and nightly (2.4.0-dev.20170610)

Trying to build up a hierarchical namespace structure with modules.
All seems fine till setting --declaration flag to true.

Code

index.ts

import * as Service from './Service';

class MySuccessMessage implements Service.Response.Message {
  public type = 'successMessage'
}

class MyResponse implements Service.Response {
  public message: MySuccessMessage = new MySuccessMessage();
}

class MyTypedResponse implements Service.TypedResponse {
  public type = 'successResponse';
  public message: MySuccessMessage = new MySuccessMessage();
}

console.log((new MyResponse()).message.type);
console.log((new MyTypedResponse()).type);

Service.ts

import * as Response from './Response';
export { Response };

export interface Response {
  message: Response.Message;
}

export interface TypedResponse extends Response {
  type: string;
  message: Response.Message;
}

Response.ts

export interface Message {
  key?: string;
  type: string;
}

Compiling with:

{
  "compilerOptions": {
    "baseUrl": "",
    "outDir": "../dist/app",
    "target": "es2015",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true
  }
}

Expected behavior:
No errors while compiling and printing out:

successMessage
successResponse

just like it does without setting --declaration to true or by removing the extends Response from export interface TypedResponse extends Response.

Actual behavior:
Getting error:

src/Service.ts(8,40): error TS4022: 'extends' clause of exported interface 'TypedResponse' has or is using private name 'Response'.

sample project with code from above:
export-error-test.zip

@Manusan42
Copy link
Author

Any suggestions yet? I really need this for my project and would appreciate any tips to overcome this problem. Is this a bug or have I just forgotten something?

@DanielRosenwasser
Copy link
Member

Can you try changing import * as Response from './Response'; to import * as R from './Response'; and seeing if that changes things?

In other words, rename Response to R

@Manusan42
Copy link
Author

Manusan42 commented Jun 16, 2017

Thank you for quick response but if you mean this:

import * as R from './Response';
export { R as Response };

export interface Response {
  message: R.Message;
}

export interface TypedResponse extends Response {
  type: string;
  message: R.Message;
}

sadly not resolves the problem.

If you meant this:

import * as R from './Response';
export { R };

export interface Response {
  message: R.Message;
}

export interface TypedResponse extends Response {
  type: string;
  message: R.Message;
}

and using it that way:

import * as Service from './Service';
class MySuccessMessage implements Service.R.Message ...
class MyResponse implements Service.Response ...
...

bypasses the problem but thats the whole point here, that I try to merge the reexported "Response"-module-namespace with the equal named interface to get a nice hierarchical structure which I can use like this:

import * as Service from './Service';
class MySuccessMessage implements Service.Response.Message ...
class MyResponse implements Service.Response ...

@Manusan42
Copy link
Author

Ok, till this (hopefully) gets done, my workaround is to not generate the declarations and instead pointing the typings to source in my package.json:

...
  "main": "dist/app/index.js",
  "typings": "src/index",
...

Because it's a private module, I'm publishing the source anyways, so it seems totally fine for now.

@mhegazy
Copy link
Contributor

mhegazy commented Aug 24, 2017

the interface Response does not really merge the way you expect. if you want to augment the module you should instead do something like:

import * as Response from './Response';
export { Response };

declare module  './Response' {
     interface Response {
         message: Response.Message;
    }
}

export interface TypedResponse extends Response.Response {
    type: string;
    message: Response.Message;
}

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 24, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Sep 7, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed Sep 7, 2017
@Manusan42
Copy link
Author

Manusan42 commented Sep 7, 2017

Finally I had some time to continue my project and suddenly realized there is just no solution to my problem.

So the conclusion of this: At this time (v. 2.5.2) it is just not possible (yet) to merge a module namespace with an interface of the same name. Even worse, the workaround with pointing to the source for the "typings" in package.json and stay with "declaration"-flag equals false (which worked fine with typescript 2.4.x) is not working anymore with v. 2.5.x.

So there are mainly three possiblities:

  1. Put the interface below the module-namespace (like @mhegazy suggested but without augmentation, cause I own the Response-module and thus just write the Response-interface into it) which results in:
import * as Service from './Service';
class MySuccessMessage implements Service.Response.Message ...
class MyResponse implements Service.Response.Response ...

or to keep the hierarchical structure the way I initially thought of:

  1. Rename the module-namespaces to lower-case which gives the needed distinction for the compiler and results in something like:
import * as service from './service';
class MySuccessMessage implements service.response.Message ...
class MyResponse implements service.Response ...

which is acceptable.

  1. Or even simpler: Using a pre-/suffix like IResponse resulting in:
import * as Service from './Service';
class MySuccessMessage implements Service.Response.Message ...
class MyResponse implements Service.IResponse ...

but there is a good reason that naming conventions of typescript point out to omit the prefixes, because if you base on abstraction your "interfaces" are the main thing you handle with for e.g. types, castings and dependency injection. You really won't be cluttered with silly prefixes literally everywhere.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants