Skip to content
This repository has been archived by the owner on Oct 10, 2018. It is now read-only.

Commit

Permalink
feat(imports): Add organize on save via workspace hook (#291)
Browse files Browse the repository at this point in the history
Closes #150. Adds a hook to actually organize the imports on the save event of a document. Needs editor.formatOnSave setting to be active as well.
  • Loading branch information
buehler authored Sep 15, 2017
1 parent efb57d8 commit 46ebeb9
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 22 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ The following settings do have the prefix `resolver`. So an example setting coul
| importGroups | The groups that are used for sorting the imports (description below) |
| ignoreImportsForOrganize | Imports that are never removed during organize import (e.g. react) |
| resolverMode | Which files should be considered to index for TypeScript Hero |
| organizeOnSave | Enable or disable the `organizeImports` action on a save of a document |

### Code outline view

Expand All @@ -109,6 +110,8 @@ TypeScript Hero can manage your imports. It is capable of:
- Import something that is beneath your current cursor position (and ask you if it's not sure which one)
- Import all missing identifiers of the current file
- Remove unused imports and sort the remaining ones by alphabet
- Do organize the imports when a document is saved
- Note that this feature is only enabled if the vscode setting `editor.formatOnSave` is enabled as well!

#### Import groups

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@
"default": false,
"description": "Defines if sorting is disable during organize imports."
},
"typescriptHero.resolver.organizeOnSave": {
"type": "boolean",
"default": true,
"description": "Defines if the imports should be organized on save."
},
"typescriptHero.resolver.ignoreImportsForOrganize": {
"type": "array",
"items": {
Expand Down
9 changes: 9 additions & 0 deletions src/common/config/ResolverConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,13 @@ export interface ResolverConfig {
* @memberof ResolverConfig
*/
resolverModeLanguages: string[];

/**
* Defines if typescript hero tries to organize your imports of a
* file as soon as the file would be saved.
*
* @type {boolean}
* @memberof ResolverConfig
*/
organizeOnSave: boolean;
}
2 changes: 2 additions & 0 deletions src/extension/IoC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CodeActionExtension } from './extensions/CodeActionExtension';
import { CodeCompletionExtension } from './extensions/CodeCompletionExtension';
import { DocumentSymbolStructureExtension } from './extensions/DocumentSymbolStructureExtension';
import { ImportResolveExtension } from './extensions/ImportResolveExtension';
import { OrganizeImportsOnSaveExtension } from './extensions/OrganizeImportsOnSaveExtension';
import { iocSymbols } from './IoCSymbols';
import { TypeScriptHero } from './TypeScriptHero';
import { VscodeLogger } from './utilities/VscodeLogger';
Expand Down Expand Up @@ -50,6 +51,7 @@ container.bind<BaseExtension>(iocSymbols.extensions).to(ImportResolveExtension).
container.bind<BaseExtension>(iocSymbols.extensions).to(CodeCompletionExtension).inSingletonScope();
container.bind<BaseExtension>(iocSymbols.extensions).to(DocumentSymbolStructureExtension).inSingletonScope();
container.bind<BaseExtension>(iocSymbols.extensions).to(CodeActionExtension).inSingletonScope();
container.bind<BaseExtension>(iocSymbols.extensions).to(OrganizeImportsOnSaveExtension).inSingletonScope();

// Logging
container
Expand Down
55 changes: 35 additions & 20 deletions src/extension/VscodeExtensionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const sectionKey = 'typescriptHero';
/**
* Configuration class for TypeScript Hero
* Contains all exposed config endpoints.
*
*
* @export
* @class VscodeExtensionConfig
*/
Expand All @@ -27,7 +27,7 @@ export class VscodeExtensionConfig implements ExtensionConfig {

/**
* The actual log level.
*
*
* @readonly
* @type {string}
* @memberof VscodeExtensionConfig
Expand All @@ -38,7 +38,7 @@ export class VscodeExtensionConfig implements ExtensionConfig {

/**
* Configuration object for the resolver extension.
*
*
* @readonly
* @type {ResolverConfig}
* @memberof VscodeExtensionConfig
Expand All @@ -49,7 +49,7 @@ export class VscodeExtensionConfig implements ExtensionConfig {

/**
* Configuration object for the code outline extension.
*
*
* @readonly
* @type {CodeOutlineConfig}
* @memberof VscodeExtensionConfig
Expand All @@ -72,7 +72,7 @@ export class VscodeExtensionConfig implements ExtensionConfig {

/**
* Configuration class for the resolver extension.
*
*
* @class VscodeResolverConfig
*/
class VscodeResolverConfig implements ResolverConfig {
Expand All @@ -83,7 +83,7 @@ class VscodeResolverConfig implements ResolverConfig {
/**
* Defines, if there should be a space between the brace and the import specifiers.
* {Symbol} vs { Symbol }
*
*
* @readonly
* @type {boolean}
* @memberof VscodeResolverConfig
Expand All @@ -96,7 +96,7 @@ class VscodeResolverConfig implements ResolverConfig {
/**
* Defines, if there should be a semicolon at the end of a statement.
* import Symbol from 'symbol' vs import Symbol from 'symbol';
*
*
* @readonly
* @type {boolean}
* @memberof VscodeResolverConfig
Expand All @@ -108,7 +108,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Defines the quote style (' or ").
*
*
* @readonly
* @type {string}
* @memberof VscodeResolverConfig
Expand All @@ -120,7 +120,7 @@ class VscodeResolverConfig implements ResolverConfig {
/**
* Array of string that are excluded from indexing (e.g. build, out, node_modules).
* If those parts are found after the workspace path is striped away, the file is ignored.
*
*
* @readonly
* @type {string[]}
* @memberof VscodeResolverConfig
Expand All @@ -135,7 +135,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* A length number after which the import is transformed into a multiline import.
*
*
* @readonly
* @type {number}
* @memberof VscodeResolverConfig
Expand Down Expand Up @@ -164,7 +164,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Defines, if sorting is obligatory during organize imports
*
*
* @readonly
* @type {boolean}
* @memberof ResolverConfig
Expand All @@ -176,7 +176,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Returns the tab size that is configured in vscode.
*
*
* @readonly
* @type {number}
* @memberof VscodeResolverConfig
Expand All @@ -187,7 +187,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Returns the list of imports that should be ignored during organize import feature.
*
*
* @readonly
* @type {string[]}
* @memberof VscodeResolverConfig
Expand All @@ -198,7 +198,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Returns the configured import groups. On a parsing error, the default is used.
*
*
* @type {ImportGroup[]}
* @memberof VscodeResolverConfig
*/
Expand All @@ -224,7 +224,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* All information that are needed to print an import.
*
*
* @readonly
* @type {TypescriptGenerationOptions}
* @memberof VscodeResolverConfig
Expand All @@ -242,7 +242,7 @@ class VscodeResolverConfig implements ResolverConfig {

/**
* Current mode of the resolver.
*
*
* @readonly
* @type {ResolverMode}
* @memberof VscodeResolverConfig
Expand All @@ -260,7 +260,7 @@ class VscodeResolverConfig implements ResolverConfig {
*
* @example `ES6`
* Will return: ['\*\*\/\*.js', '\*\*\/\*.jsx']
*
*
* @type {string[]}
* @memberof VscodeResolverConfig
*/
Expand All @@ -286,7 +286,7 @@ class VscodeResolverConfig implements ResolverConfig {
*
* @example `TypeScript`
* Will return: ['typescript', 'typescriptreact']
*
*
* @readonly
* @type {string[]}
* @memberof VscodeResolverConfig
Expand All @@ -307,11 +307,26 @@ class VscodeResolverConfig implements ResolverConfig {

return languages;
}

/**
* Defines if typescript hero tries to organize your imports of a
* file as soon as the file would be saved.
* Is a combination between the editor.formatOnSave and the resolver settings.
*
* @readonly
* @type {boolean}
* @memberof VscodeResolverConfig
*/
public get organizeOnSave(): boolean {
const typescriptHeroValue = this.workspaceSection.get<boolean>('resolver.organizeOnSave', true);
const editorValue = workspace.getConfiguration().get('editor.formatOnSave', false);
return typescriptHeroValue && editorValue;
}
}

/**
* Configuration interface for the code outline feature.
*
*
* @class VscodeCodeOutlineConfig
* @implements {CodeOutlineConfig}
*/
Expand All @@ -322,7 +337,7 @@ class VscodeCodeOutlineConfig implements CodeOutlineConfig {

/**
* Defined if the code outline feature is enabled or not.
*
*
* @readonly
* @type {boolean}
* @memberof VscodeCodeOutlineConfig
Expand Down
61 changes: 61 additions & 0 deletions src/extension/extensions/OrganizeImportsOnSaveExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { inject, injectable } from 'inversify';
import { ExtensionContext, workspace } from 'vscode';

import { ExtensionConfig } from '../../common/config';
import { Logger, LoggerFactory } from '../../common/utilities';
import { iocSymbols } from '../IoCSymbols';
import { ImportManager } from '../managers';
import { BaseExtension } from './BaseExtension';

/**
* Extension that does sort and organize the imports as soon as a document will be saved.
*
* @export
* @class OrganizeImportsOnSaveExtension
* @extends {BaseExtension}
*/
@injectable()
export class OrganizeImportsOnSaveExtension extends BaseExtension {
private logger: Logger;

constructor(
@inject(iocSymbols.extensionContext) context: ExtensionContext,
@inject(iocSymbols.loggerFactory) loggerFactory: LoggerFactory,
@inject(iocSymbols.configuration) private config: ExtensionConfig,
) {
super(context);
this.logger = loggerFactory('OrganizeImportsOnSaveExtension');
}

/**
* Initialized the extension. Registers the commands and other disposables to the context.
*
* @memberof OrganizeImportsOnSaveExtension
*/
public initialize(): void {
this.context.subscriptions.push(workspace.onWillSaveTextDocument((event) => {
if (!this.config.resolver.organizeOnSave) {
this.logger.info('Organize on save is deactivated through config.');
return;
}

this.logger.info(`Organize on save for document "${event.document.fileName}".`);
event.waitUntil(
ImportManager
.create(event.document)
.then(manager => manager.organizeImports().calculateTextEdits()),
);
}));

this.logger.info('Initialized');
}

/**
* Disposes the extension.
*
* @memberof OrganizeImportsOnSaveExtension
*/
public dispose(): void {
this.logger.info('Disposed');
}
}
5 changes: 3 additions & 2 deletions test/_workspace/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
],
"typescriptHero.verbosity": "Warnings",
"tslint.enable": false,
"typescript.check.tscVersion": false,
"typescriptHero.codeOutline.enabled": true
"typescriptHero.codeOutline.enabled": true,
"typescriptHero.resolver.organizeOnSave": false,
"editor.formatOnSave": true
}
Loading

7 comments on commit 46ebeb9

@cbrevik
Copy link

Choose a reason for hiding this comment

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

@buehler This is a nice feature but it should not be true by default in my opinion. It took me quite a bit of time understanding why my prettier settings were being overridden.

@buehler
Copy link
Owner Author

Choose a reason for hiding this comment

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

@cbrevik fair point. Gonna change that in a future release.

@cbrevik
Copy link

@cbrevik cbrevik commented on 46ebeb9 Sep 21, 2017

Choose a reason for hiding this comment

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

I like the sorting feature. But is there some way to turn off organizeImports removing unused imports? At the moment it is also removing React imports from my React Native-app. Which breaks rendering since it is a required import.

(Optionally whitelisting imports?)

@buehler
Copy link
Owner Author

Choose a reason for hiding this comment

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

Yea there is an option (setting) to whitelist certain imports so that they aren't removed:
typescriptHero.resolver.ignoreImportsForOrganize

@cbrevik
Copy link

Choose a reason for hiding this comment

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

Thanks! Seems like that feature is bugged though, it ignores:

import React from "react"

But it does not ignore:

import React, { Component } from "react"

Which becomes:

import { Component } from "react"

I can open an issue?

@buehler
Copy link
Owner Author

Choose a reason for hiding this comment

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

Of course you can open an issue ;-)
import React, { Component } from "react" this syntax does not work though. You can write it as:
import { default as React, Component } from "react" which should work.

Thats a bug in the syntax ;-)

in TypeScript, the following syntax works and is not removed: import * as React from 'react';

@cbrevik
Copy link

Choose a reason for hiding this comment

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

It is allowed syntax if "allowSyntheticDefaultImports": true is set in tsconfig.json :)

Please sign in to comment.