-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Discriminant property type guard not applied with bracket notation #10530
Comments
👍 Bracketed property access should work the same as dotted property access for the purposes of type guards when the indexed name matches a known property |
Fix is up at #10565 |
Update from the PR at #10530: narrowing on element access, even just for strings and not numbers, adds 6.5% time to the checker, so this is not likely to go into 2.1 unless we come up with a way to reduce its cost. |
@sandersn thanks for the update. That's unfortunate, but understandable. :( |
Then I can rest assured that we can still continue to use unmatched index names? |
Adds a generator that produces TypeScript definition files for a JavaScript client. As with JavaScript, there are two generators: `tsd_types` and `tsd_client`. The produced definition files require TypeScript 2.0 at the minimum, as they rely on TypeScript tagged unions. Below, I will summarize how we map Stone types to TypeScript. TypeScript's basic types match JSDoc, so there is no difference from the `js_types` generator. Aliases are emitted as `type`s: ``` typescript type AliasName = ReferencedType; ``` Structs are emitted as `interface`s, which support inheritance. Thus, if a struct `A` extends struct `B`, it will be emitted as: ``` typescript interface A extends B { // fields go here } ``` Nullable fields and fields with default values are emitted as _optional_ fields. In addition, the generator adds a field description with the default field value, if the field has one: ``` typescript interface A { // Defaults to False recur?: boolean; } ``` Unions are emitted as a `type` that is the disjunction of all possible union variants (including those from parent types!). Each variant is emitted as an individual `interface`. ``` union Shape point square Float64 "The value is the length of a side." circle Float64 "The value is the radius." ``` ``` typescript interface ShapeCircle { .tag: 'circle'; circle: number; } interface ShapeSquare { .tag: 'square'; square: number; } interface ShapePoint { .tag: 'point'; } type Shape = ShapePoint | ShapeSquare | ShapeCircle; ``` TypeScript 2.0 supports [tagged union types](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types) like these ones, so the compiler should automatically infer the type of a shape when the developer writes code like so (and statically check that all cases are covered!): ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': // Compiler knows this is a ShapeSquare, so .square field is visible. console.log('square ' + shape.square); break; // No 'circle' case! If developer enables the relevant compiler option, compilation will fail. } ``` Unfortunately, [there is a bug that prevents this from happening](microsoft/TypeScript#10530) when you use bracket notation to access a field. It will be fixed in TypeScript 2.1. Until then, developers will need to cast: ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': console.log('square ' + (<ShapeSquare> shape).square); break; } ``` When a struct explicitly enumerates its subtypes, direct references to the struct will have a `.tag` field to indicate which subtype it is. Direct references to the struct's subtypes will omit this field. To capture this subtlety, the generator emits an interface that represents a direct reference to a struct with enumerated subtypes: ``` struct Resource union file File folder Folder path String struct File extends Resource ... struct Folder extends Resource ... ``` ``` typescript interface Resource { path: string; } interface File extends Resource { } interface Folder extends Resource { } interface ResourceReference extends Resource { '.tag': 'file' | 'folder'; } interface FileReference extends File { '.tag': 'file'; } interface FolderReference extends Folder { '.tag': 'folder'; } ``` Direct references to `Resource` will be typed as `FileReference | FolderReference | ResourceReference` if the union is open, or `FileReference | FolderReference` if the union is closed. A direct reference to `File` will be typed as `File`, since the `.tag` field will not be present. TypeScript 2.0's tagged union support should work on these types once the previously-discussed bug is fixed. Nullable types are emitted as optional fields when referenced from structs. Routes are emitted in the same manner as the JavaScript generators, **except** that TypeScript's type system is unable to type `Promise`-based errors. The generator adds text to the route's documentation that explicitly mentions the data type the developer should expect when an error occurs. Example: ``` typescript type DropboxError = DropboxTypes.Error; db.filesListFolder({path: ''}).then((response) => { // TypeScript knows the type of response, so no type annotation is needed. }).catch( // Add explicit annotation on err. (err: DropboxError<DropboxTypes.files.ListFolderError>) => { }); ``` Stone namespaces are mapped directly to TypeScript namespaces: ``` namespace files; import common; struct Metadata parent_shared_folder_id common.SharedFolderId? ``` ``` typescript namespace files { interface Metadata { parent_shared_folder_id?: common.SharedFolderId; } } ``` Both `tsd_types` and `tsd_client` consume a template file, which contains a skeleton around the types they omit. This skeleton is unavoidable, as SDKs may augment SDK classes (like `Dropbox` or `DropboxTeam`) with additional methods not described in stone. The "templates" simply have a comment string that marks where the generator should insert code. For example, the following template has markers for route definitions and type definitions: ``` typescript class Dropbox { // This is an SDK-specific method which isn't described in stone. getClientId(): string; // All of the routes go here: /*ROUTES*/ } // All of the stone data types are defined here: /*TYPES*/ ``` In the above template, the developer would need to run the `tsd_types` generator to produce an output file, and then run the `tsd_client` generator on that output to insert the routes (or vice-versa). The developer may also choose to have separate template files for types and routes: ``` typescript // in types.d.ts namespace DropboxTypes { /*TYPES*/ } ``` ``` typescript /// <reference path="./types.d.ts" /> // ^ this will "import" the types from the other file. // in dropbox.d.ts namespace DropboxTypes { class Dropbox { /*ROUTES*/ } } ``` Developers can customize the template string used for `tsd_client` with a command line parameter, in case they have multiple independent sets of routes: ``` typescript namespace DropboxTypes { class Dropbox { /*ROUTES*/ } class DropboxTeam { /*TEAM_ROUTES*/ } } ``` For Dropbox's JavaScript SDK, I've defined the following templates. **dropbox.d.tstemplate**: Contains a template for the `Dropbox` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> declare module DropboxTypes { class Dropbox extends DropboxBase { /** * The Dropbox SDK class. */ constructor(options: DropboxOptions); /*ROUTES*/ } } ``` **dropbox_team.d.tstemplate**: Contains a template for the `DropboxTeam` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> /// <reference path="./dropbox.d.ts" /> declare module DropboxTypes { class DropboxTeam extends DropboxBase { /** * The DropboxTeam SDK class. */ constructor(options: DropboxOptions); /** * Returns an instance of Dropbox that can make calls to user api endpoints on * behalf of the passed user id, using the team access token. Only relevant for * team endpoints. */ actAsUser(userId: string): Dropbox; /*ROUTES*/ } } ``` **dropbox_types.d.ts**: Contains a template for the Stone data types, as well as the `DropboxBase` class (which is shared by both `Dropbox` and `DropboxTeam`). ``` typescript declare module DropboxTypes { interface DropboxOptions { // An access token for making authenticated requests. accessToken?: string; // The client id for your app. Used to create authentication URL. clientId?: string; // Select user is only used by team endpoints. It specifies which user the team access token should be acting as. selectUser?: string; } class DropboxBase { /** * Get the access token. */ getAccessToken(): string; /** * Get a URL that can be used to authenticate users for the Dropbox API. * @param redirectUri A URL to redirect the user to after authenticating. * This must be added to your app through the admin interface. * @param state State that will be returned in the redirect URL to help * prevent cross site scripting attacks. */ getAuthenticationUrl(redirectUri: string, state?: string): string; /** * Get the client id */ getClientId(): string; /** * Set the access token used to authenticate requests to the API. * @param accessToken An access token. */ setAccessToken(accessToken: string): void; /** * Set the client id, which is used to help gain an access token. * @param clientId Your app's client ID. */ setClientId(clientId: string): void; } /*TYPES*/ } ``` Then, I defined simple definition files for each of the ways you package up the SDK, which references these types. These can be readily distributed alongside your libraries. `DropboxTeam-sdk.min.d.ts` (`DropboxTeam` class in a UMD module): ``` typescript /// <reference path="./dropbox_team.d.ts" /> export = DropboxTypes.DropboxTeam; export as namespace DropboxTeam; ``` `Dropbox-sdk.min.d.ts` (`Dropbox` class in a UMD module): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; export as namespace Dropbox; ``` `dropbox-sdk.js` (`Dropbox` class in a CommonJS module -- not sure why you distribute this when you have a UMD version!): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; ``` Finally, for your Node module, there's `src/index.d.ts` which goes alongside `src/index.js` and defines all of your Node modules together. After adding a `typings` field to `package.json` that points to `src/index`, the TypeScript compiler _automatically_ picks up the definitions from the NPM module: ``` typescript /// <reference path="../dist/dropbox.d.ts" /> /// <reference path="../dist/dropbox_team.d.ts" /> declare module "dropbox/team" { export = DropboxTypes.DropboxTeam; } declare module "dropbox" { export = DropboxTypes.Dropbox; } ``` To properly bundle things, I added a `typescript-copy.js` script that NPM calls when you run `npm run build`. The script simply copies the TypeScript typings to the `dist` folder. These are the files that must be maintained to provide complete TypeScript typings for all of your distribution methods. The files that are likely to change in the future are the templates, as you add/modify/remove SDK-specific interfaces.
Adds a generator that produces TypeScript definition files for a JavaScript client. As with JavaScript, there are two generators: `tsd_types` and `tsd_client`. The definition files require TypeScript 2.0, as they rely upon TypeScript tagged unions. Overview: Mapping Stone Types to TypeScript =========================================== Below, I will summarize how we map Stone types to TypeScript. Basic Types ----------- TypeScript's basic types match JSDoc, so there is no difference from the `js_types` generator. Alias ----- Aliases are emitted as `type`s: ``` typescript type AliasName = ReferencedType; ``` Struct ------ Structs are emitted as `interface`s, which support inheritance. Thus, if a struct `A` extends struct `B`, it will be emitted as: ``` typescript interface A extends B { // fields go here } ``` Nullable fields and fields with default values are emitted as _optional_ fields. In addition, the generator adds a field description with the default field value, if the field has one: ``` typescript interface A { // Defaults to False recur?: boolean; } ``` Unions ------ Unions are emitted as a `type` that is the disjunction of all possible union variants (including those from parent types!). Each variant is emitted as an individual `interface`. ``` union Shape point square Float64 "The value is the length of a side." circle Float64 "The value is the radius." ``` ``` typescript interface ShapeCircle { .tag: 'circle'; circle: number; } interface ShapeSquare { .tag: 'square'; square: number; } interface ShapePoint { .tag: 'point'; } type Shape = ShapePoint | ShapeSquare | ShapeCircle; ``` TypeScript 2.0 supports [tagged union types](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types) like these ones, so the compiler should automatically infer the type of a shape when the developer writes code like so (and statically check that all cases are covered!): ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': // Compiler knows this is a ShapeSquare, so .square field is visible. console.log('square ' + shape.square); break; // No 'circle' case! If developer enables the relevant compiler option, compilation will fail. } ``` Unfortunately, [there is a bug that prevents this from happening](microsoft/TypeScript#10530) when you use bracket notation to access a field. It will be fixed in a future version of TypeScript. Until then, developers will need to cast: ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': console.log('square ' + (<ShapeSquare> shape).square); break; } ``` Struct Polymorphism ------------------- When a struct explicitly enumerates its subtypes, direct references to the struct will have a `.tag` field to indicate which subtype it is. Direct references to the struct's subtypes will omit this field. To capture this subtlety, the generator emits an interface that represents a direct reference to a struct with enumerated subtypes: ``` struct Resource union file File folder Folder path String struct File extends Resource ... struct Folder extends Resource ... ``` ``` typescript interface Resource { path: string; } interface File extends Resource { } interface Folder extends Resource { } interface ResourceReference extends Resource { '.tag': 'file' | 'folder'; } interface FileReference extends File { '.tag': 'file'; } interface FolderReference extends Folder { '.tag': 'folder'; } ``` Direct references to `Resource` will be typed as `FileReference | FolderReference | ResourceReference` if the union is open, or `FileReference | FolderReference` if the union is closed. A direct reference to `File` will be typed as `File`, since the `.tag` field will not be present. TypeScript 2.0's tagged union support should work on these types once the previously-discussed bug is fixed. Nullable Types -------------- Nullable types are emitted as optional fields when referenced from structs. Routes ------ Routes are emitted in the same manner as the JavaScript generators, **except** that TypeScript's type system is unable to type `Promise`-based errors. The generator adds text to the route's documentation that explicitly mentions the data type the developer should expect when an error occurs. Example: ``` typescript type DropboxError = DropboxTypes.Error; db.filesListFolder({path: ''}).then((response) => { // TypeScript knows the type of response, so no type annotation is needed. }).catch( // Add explicit annotation on err. (err: DropboxError<DropboxTypes.files.ListFolderError>) => { }); ``` Import / Namespaces ------------------- Stone namespaces are mapped directly to TypeScript namespaces: ``` namespace files; import common; struct Metadata parent_shared_folder_id common.SharedFolderId? ``` ``` typescript namespace files { interface Metadata { parent_shared_folder_id?: common.SharedFolderId; } } ``` Using the Generator =================== Both `tsd_types` and `tsd_client` consume a template file, which contains a skeleton around the types they omit. This skeleton is unavoidable, as SDKs may augment SDK classes (like `Dropbox` or `DropboxTeam`) with additional methods not described in stone. The "templates" simply have a comment string that marks where the generator should insert code. For example, the following template has markers for route definitions and type definitions: ``` typescript class Dropbox { // This is an SDK-specific method which isn't described in stone. getClientId(): string; // All of the routes go here: /*ROUTES*/ } // All of the stone data types are defined here: /*TYPES*/ ``` In the above template, the developer would need to run the `tsd_types` generator to produce an output file, and then run the `tsd_client` generator on that output to insert the routes (or vice-versa). The developer may also choose to have separate template files for types and routes: ``` typescript // in types.d.ts namespace DropboxTypes { /*TYPES*/ } ``` ``` typescript /// <reference path="./types.d.ts" /> // ^ this will "import" the types from the other file. // in dropbox.d.ts namespace DropboxTypes { class Dropbox { /*ROUTES*/ } } ``` Developers can customize the template string used for `tsd_client` with a command line parameter, in case they have multiple independent sets of routes: ``` typescript namespace DropboxTypes { class Dropbox { /*ROUTES*/ } class DropboxTeam { /*TEAM_ROUTES*/ } } ``` Generator Usage in Dropbox SDK ============================== For Dropbox's JavaScript SDK, I've defined the following templates. **dropbox.d.tstemplate**: Contains a template for the `Dropbox` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> declare module DropboxTypes { class Dropbox extends DropboxBase { /** * The Dropbox SDK class. */ constructor(options: DropboxOptions); /*ROUTES*/ } } ``` **dropbox_team.d.tstemplate**: Contains a template for the `DropboxTeam` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> /// <reference path="./dropbox.d.ts" /> declare module DropboxTypes { class DropboxTeam extends DropboxBase { /** * The DropboxTeam SDK class. */ constructor(options: DropboxOptions); /** * Returns an instance of Dropbox that can make calls to user api endpoints on * behalf of the passed user id, using the team access token. Only relevant for * team endpoints. */ actAsUser(userId: string): Dropbox; /*ROUTES*/ } } ``` **dropbox_types.d.ts**: Contains a template for the Stone data types, as well as the `DropboxBase` class (which is shared by both `Dropbox` and `DropboxTeam`). ``` typescript declare module DropboxTypes { interface DropboxOptions { // An access token for making authenticated requests. accessToken?: string; // The client id for your app. Used to create authentication URL. clientId?: string; // Select user is only used by team endpoints. It specifies which user the team access token should be acting as. selectUser?: string; } class DropboxBase { /** * Get the access token. */ getAccessToken(): string; /** * Get a URL that can be used to authenticate users for the Dropbox API. * @param redirectUri A URL to redirect the user to after authenticating. * This must be added to your app through the admin interface. * @param state State that will be returned in the redirect URL to help * prevent cross site scripting attacks. */ getAuthenticationUrl(redirectUri: string, state?: string): string; /** * Get the client id */ getClientId(): string; /** * Set the access token used to authenticate requests to the API. * @param accessToken An access token. */ setAccessToken(accessToken: string): void; /** * Set the client id, which is used to help gain an access token. * @param clientId Your app's client ID. */ setClientId(clientId: string): void; } /*TYPES*/ } ``` Then, I defined simple definition files for each of the ways you package up the SDK, which references these types. These can be readily distributed alongside your libraries. `DropboxTeam-sdk.min.d.ts` (`DropboxTeam` class in a UMD module): ``` typescript /// <reference path="./dropbox_team.d.ts" /> export = DropboxTypes.DropboxTeam; export as namespace DropboxTeam; ``` `Dropbox-sdk.min.d.ts` (`Dropbox` class in a UMD module): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; export as namespace Dropbox; ``` `dropbox-sdk.js` (`Dropbox` class in a CommonJS module -- not sure why you distribute this when you have a UMD version!): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; ``` Finally, for your Node module, there's `src/index.d.ts` which goes alongside `src/index.js` and defines all of your Node modules together. After adding a `typings` field to `package.json` that points to `src/index`, the TypeScript compiler _automatically_ picks up the definitions from the NPM module: ``` typescript /// <reference path="../dist/dropbox.d.ts" /> /// <reference path="../dist/dropbox_team.d.ts" /> declare module "dropbox/team" { export = DropboxTypes.DropboxTeam; } declare module "dropbox" { export = DropboxTypes.Dropbox; } ``` To properly bundle things, I added a `typescript-copy.js` script that NPM calls when you run `npm run build`. The script simply copies the TypeScript typings to the `dist` folder. These are the files that must be maintained to provide complete TypeScript typings for all of your distribution methods. The files that are likely to change in the future are the templates, as you add/modify/remove SDK-specific interfaces.
Adds a generator that produces TypeScript definition files for a JavaScript client. As with JavaScript, there are two generators: `tsd_types` and `tsd_client`. The definition files require TypeScript 2.0, as they rely upon TypeScript tagged unions. Overview: Mapping Stone Types to TypeScript =========================================== Below, I will summarize how we map Stone types to TypeScript. Basic Types ----------- TypeScript's basic types match JSDoc, so there is no difference from the `js_types` generator. Alias ----- Aliases are emitted as `type`s: ``` typescript type AliasName = ReferencedType; ``` Struct ------ Structs are emitted as `interface`s, which support inheritance. Thus, if a struct `A` extends struct `B`, it will be emitted as: ``` typescript interface A extends B { // fields go here } ``` Nullable fields and fields with default values are emitted as _optional_ fields. In addition, the generator adds a field description with the default field value, if the field has one: ``` typescript interface A { // Defaults to False recur?: boolean; } ``` Unions ------ Unions are emitted as a `type` that is the disjunction of all possible union variants (including those from parent types!). Each variant is emitted as an individual `interface`. ``` union Shape point square Float64 "The value is the length of a side." circle Float64 "The value is the radius." ``` ``` typescript interface ShapeCircle { .tag: 'circle'; circle: number; } interface ShapeSquare { .tag: 'square'; square: number; } interface ShapePoint { .tag: 'point'; } type Shape = ShapePoint | ShapeSquare | ShapeCircle; ``` TypeScript 2.0 supports [tagged union types](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types) like these ones, so the compiler should automatically infer the type of a shape when the developer writes code like so (and statically check that all cases are covered!): ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': // Compiler knows this is a ShapeSquare, so .square field is visible. console.log('square ' + shape.square); break; // No 'circle' case! If developer enables the relevant compiler option, compilation will fail. } ``` Unfortunately, [there is a bug that prevents this from happening](microsoft/TypeScript#10530) when you use bracket notation to access a field. It will be fixed in a future version of TypeScript. Until then, developers will need to cast: ``` typescript var shape: Shape = getShape(); switch (shape['.tag']) { case 'point': console.log('point'); break; case 'square': console.log('square ' + (<ShapeSquare> shape).square); break; } ``` Struct Polymorphism ------------------- When a struct explicitly enumerates its subtypes, direct references to the struct will have a `.tag` field to indicate which subtype it is. Direct references to the struct's subtypes will omit this field. To capture this subtlety, the generator emits an interface that represents a direct reference to a struct with enumerated subtypes: ``` struct Resource union file File folder Folder path String struct File extends Resource ... struct Folder extends Resource ... ``` ``` typescript interface Resource { path: string; } interface File extends Resource { } interface Folder extends Resource { } interface ResourceReference extends Resource { '.tag': 'file' | 'folder'; } interface FileReference extends File { '.tag': 'file'; } interface FolderReference extends Folder { '.tag': 'folder'; } ``` Direct references to `Resource` will be typed as `FileReference | FolderReference | ResourceReference` if the union is open, or `FileReference | FolderReference` if the union is closed. A direct reference to `File` will be typed as `File`, since the `.tag` field will not be present. TypeScript 2.0's tagged union support should work on these types once the previously-discussed bug is fixed. Nullable Types -------------- Nullable types are emitted as optional fields when referenced from structs. Routes ------ Routes are emitted in the same manner as the JavaScript generators, **except** that TypeScript's type system is unable to type `Promise`-based errors. The generator adds text to the route's documentation that explicitly mentions the data type the developer should expect when an error occurs. Example: ``` typescript type DropboxError = DropboxTypes.Error; db.filesListFolder({path: ''}).then((response) => { // TypeScript knows the type of response, so no type annotation is needed. }).catch( // Add explicit annotation on err. (err: DropboxError<DropboxTypes.files.ListFolderError>) => { }); ``` Import / Namespaces ------------------- Stone namespaces are mapped directly to TypeScript namespaces: ``` namespace files; import common; struct Metadata parent_shared_folder_id common.SharedFolderId? ``` ``` typescript namespace files { interface Metadata { parent_shared_folder_id?: common.SharedFolderId; } } ``` Using the Generator =================== Both `tsd_types` and `tsd_client` consume a template file, which contains a skeleton around the types they omit. This skeleton is unavoidable, as SDKs may augment SDK classes (like `Dropbox` or `DropboxTeam`) with additional methods not described in stone. The "templates" simply have a comment string that marks where the generator should insert code. For example, the following template has markers for route definitions and type definitions: ``` typescript class Dropbox { // This is an SDK-specific method which isn't described in stone. getClientId(): string; // All of the routes go here: /*ROUTES*/ } // All of the stone data types are defined here: /*TYPES*/ ``` In the above template, the developer would need to run the `tsd_types` generator to produce an output file, and then run the `tsd_client` generator on that output to insert the routes (or vice-versa). The developer may also choose to have separate template files for types and routes: ``` typescript // in types.d.ts namespace DropboxTypes { /*TYPES*/ } ``` ``` typescript /// <reference path="./types.d.ts" /> // ^ this will "import" the types from the other file. // in dropbox.d.ts namespace DropboxTypes { class Dropbox { /*ROUTES*/ } } ``` Developers can customize the template string used for `tsd_client` with a command line parameter, in case they have multiple independent sets of routes: ``` typescript namespace DropboxTypes { class Dropbox { /*ROUTES*/ } class DropboxTeam { /*TEAM_ROUTES*/ } } ``` Generator Usage in Dropbox SDK ============================== For Dropbox's JavaScript SDK, I've defined the following templates. **dropbox.d.tstemplate**: Contains a template for the `Dropbox` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> declare module DropboxTypes { class Dropbox extends DropboxBase { /** * The Dropbox SDK class. */ constructor(options: DropboxOptions); /*ROUTES*/ } } ``` **dropbox_team.d.tstemplate**: Contains a template for the `DropboxTeam` class. ``` typescript /// <reference path="./dropbox_types.d.ts" /> /// <reference path="./dropbox.d.ts" /> declare module DropboxTypes { class DropboxTeam extends DropboxBase { /** * The DropboxTeam SDK class. */ constructor(options: DropboxOptions); /** * Returns an instance of Dropbox that can make calls to user api endpoints on * behalf of the passed user id, using the team access token. Only relevant for * team endpoints. */ actAsUser(userId: string): Dropbox; /*ROUTES*/ } } ``` **dropbox_types.d.ts**: Contains a template for the Stone data types, as well as the `DropboxBase` class (which is shared by both `Dropbox` and `DropboxTeam`). ``` typescript declare module DropboxTypes { interface DropboxOptions { // An access token for making authenticated requests. accessToken?: string; // The client id for your app. Used to create authentication URL. clientId?: string; // Select user is only used by team endpoints. It specifies which user the team access token should be acting as. selectUser?: string; } class DropboxBase { /** * Get the access token. */ getAccessToken(): string; /** * Get a URL that can be used to authenticate users for the Dropbox API. * @param redirectUri A URL to redirect the user to after authenticating. * This must be added to your app through the admin interface. * @param state State that will be returned in the redirect URL to help * prevent cross site scripting attacks. */ getAuthenticationUrl(redirectUri: string, state?: string): string; /** * Get the client id */ getClientId(): string; /** * Set the access token used to authenticate requests to the API. * @param accessToken An access token. */ setAccessToken(accessToken: string): void; /** * Set the client id, which is used to help gain an access token. * @param clientId Your app's client ID. */ setClientId(clientId: string): void; } /*TYPES*/ } ``` Then, I defined simple definition files for each of the ways you package up the SDK, which references these types. These can be readily distributed alongside your libraries. `DropboxTeam-sdk.min.d.ts` (`DropboxTeam` class in a UMD module): ``` typescript /// <reference path="./dropbox_team.d.ts" /> export = DropboxTypes.DropboxTeam; export as namespace DropboxTeam; ``` `Dropbox-sdk.min.d.ts` (`Dropbox` class in a UMD module): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; export as namespace Dropbox; ``` `dropbox-sdk.js` (`Dropbox` class in a CommonJS module -- not sure why you distribute this when you have a UMD version!): ``` typescript /// <reference path="./dropbox.d.ts" /> export = DropboxTypes.Dropbox; ``` Finally, for your Node module, there's `src/index.d.ts` which goes alongside `src/index.js` and defines all of your Node modules together. After adding a `typings` field to `package.json` that points to `src/index`, the TypeScript compiler _automatically_ picks up the definitions from the NPM module: ``` typescript /// <reference path="../dist/dropbox.d.ts" /> /// <reference path="../dist/dropbox_team.d.ts" /> declare module "dropbox/team" { export = DropboxTypes.DropboxTeam; } declare module "dropbox" { export = DropboxTypes.Dropbox; } ``` To properly bundle things, I added a `typescript-copy.js` script that NPM calls when you run `npm run build`. The script simply copies the TypeScript typings to the `dist` folder. These are the files that must be maintained to provide complete TypeScript typings for all of your distribution methods. The files that are likely to change in the future are the templates, as you add/modify/remove SDK-specific interfaces.
If we're not going to close this, could someone edit the title and description to reflect the current purpose of the issue? Right now it's just generating noise in every issue closed as a duplicate of this one. |
As others have mentioned, this issue title/description isn't particularly clear, but from the issues marked as duplicate I'm guessing this issue is the same root cause as the following? declare const apple: { [key: string]: boolean | undefined }
const banana: Record<string, boolean> = apple // error
// Type '{ [key: string]: boolean | undefined; }' is not assignable to type 'Record<string, boolean>'.
// 'string' index signatures are incompatible.
// Type 'boolean | undefined' is not assignable to type 'boolean'.
// Type 'undefined' is not assignable to type 'boolean'.(2322) |
@MicahZoltu I don't think that's the same issue, and I don't think the example that you shared would be considered an issue in TypeScript. Although a |
Closing since the examples cited here work as expected |
@RyanCavanaugh in that case can any of the issues that were closed as a duplicate of this one be re-opened? Eg. #51368 |
@DetachHead let's just get a fresh issue with a clear statement of the problem for clarity |
Well, I opened #56389... not sure if it covers all the loose ends, though. |
To anybody following this issue and confused like me, also the more complicated topic of variable index access type guards was fixed this June by ahejlsberg himself with #57847 in TS 5.5 :) |
the issue i raised (#51368) which was closed as a duplicate of this one still doesn't work: interface Data {
a?: number
}
declare const data: Data
declare let key: 'a'
if (data.a !== undefined) {
key // "a"
const a = data[key] // number | undefined
const b = data['a'] // number
} the difference being that
or is there something i'm missing? |
You're missing that the |
i see, thanks. it also seems like attempting it to narrow it with + export {}
+
interface Data {
a?: number
}
declare const data: Data
declare let key: 'a'
- if (data.a !== undefined) {
+ if (data[key] !== undefined) {
key // "a"
const a = data[key] // number | undefined
const b = data['a'] // number
} |
TypeScript Version: 2.0.0
Code
Expected behavior:
The code compiles without errors.
Actual behavior:
Why this is bad:
I am trying to work with Dropbox's new 2.0 SDK, which heavily uses tagged union types (especially for API errors). The discriminant property is named
.tag
, so it can only be accessed via bracket notation. I generated TypeScript typings for their new JavaScript SDK, and discovered this bug the hard way. :(The text was updated successfully, but these errors were encountered: