Skip to content
This repository was archived by the owner on Dec 25, 2023. It is now read-only.

Commit 4160d29

Browse files
committed
Allow organizing imports on save
1 parent 02e9762 commit 4160d29

File tree

7 files changed

+183
-4
lines changed

7 files changed

+183
-4
lines changed

.nova/Configuration.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"apexskier.typescript.config.organizeImportsOnSave": "true",
23
"editor.default_syntax": "typescript",
34
"workspace.name": "TypeScript Extension"
45
}

src/commands/organizeImports.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ export function registerOrganizeImports(client: LanguageClient) {
1414
wrapCommand(organizeImports)
1515
);
1616

17-
async function organizeImports(editor: TextEditor) {
17+
async function organizeImports(editor: TextEditor): Promise<void>;
18+
async function organizeImports(
19+
workspace: Workspace,
20+
editor: TextEditor
21+
): Promise<void>;
22+
async function organizeImports(
23+
editorOrWorkspace: TextEditor | Workspace,
24+
maybeEditor?: TextEditor
25+
) {
26+
const editor: TextEditor = maybeEditor ?? (editorOrWorkspace as TextEditor);
27+
1828
const originalSelections = editor.selectedRanges;
1929
const originalLength = editor.document.length;
2030

src/main.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ jest.mock("./tsLibPath", () => ({
77
jest.mock("./isEnabledForJavascript", () => ({
88
isEnabledForJavascript: () => true,
99
}));
10+
jest.mock("./shouldOrganizeImportsOnSave", () => ({
11+
shouldOrganizeImportsOnSave: jest.fn(() => true),
12+
}));
1013
jest.mock("nova-extension-utils");
1114

1215
jest.useFakeTimers();
@@ -18,6 +21,7 @@ jest.useFakeTimers();
1821
},
1922
workspace: {
2023
path: "/workspace",
24+
onDidAddTextEditor: jest.fn(),
2125
},
2226
extension: {
2327
path: "/extension",
@@ -69,6 +73,7 @@ describe("test suite", () => {
6973
.mockImplementation(() => Promise.resolve());
7074
nova.fs.access = jest.fn().mockReturnValue(true);
7175
(nova.commands.register as jest.Mock).mockReset();
76+
(nova.commands.invoke as jest.Mock).mockReset();
7277
LanguageClientMock.mockReset().mockImplementation(() => ({
7378
onRequest: jest.fn(),
7479
onNotification: jest.fn(),
@@ -86,6 +91,7 @@ describe("test suite", () => {
8691
start: jest.fn(),
8792
}));
8893
(informationViewModule.InformationView as jest.Mock).mockReset();
94+
(nova.workspace.onDidAddTextEditor as jest.Mock).mockReset();
8995
}
9096

9197
const reload = (nova.commands.register as jest.Mock).mock.calls.find(
@@ -291,5 +297,44 @@ describe("test suite", () => {
291297

292298
assertActivationBehavior();
293299
});
300+
301+
test("watches files for import organization", async () => {
302+
resetMocks();
303+
304+
await activate();
305+
306+
expect(nova.workspace.onDidAddTextEditor).toBeCalledTimes(1);
307+
const setupWatcher = (nova.workspace.onDidAddTextEditor as jest.Mock).mock
308+
.calls[0][0];
309+
const mockEditor = {
310+
onWillSave: jest.fn(),
311+
onDidDestroy: jest.fn(),
312+
document: {
313+
syntax: "typescript",
314+
},
315+
};
316+
setupWatcher(mockEditor);
317+
expect(mockEditor.onWillSave).toBeCalledTimes(1);
318+
const saveHandler = (mockEditor.onWillSave as jest.Mock).mock.calls[0][0];
319+
await saveHandler(mockEditor);
320+
expect(nova.commands.invoke).toHaveBeenNthCalledWith(
321+
1,
322+
"apexskier.typescript.commands.organizeImports",
323+
mockEditor
324+
);
325+
326+
(nova.commands.invoke as jest.Mock).mockReset();
327+
mockEditor.document.syntax = "__something_else__";
328+
await saveHandler(mockEditor);
329+
expect(nova.commands.invoke).not.toBeCalled();
330+
331+
(nova.commands.invoke as jest.Mock).mockReset();
332+
mockEditor.document.syntax = "typescript";
333+
require("./shouldOrganizeImportsOnSave").shouldOrganizeImportsOnSave.mockReturnValue(
334+
false
335+
);
336+
await saveHandler(mockEditor);
337+
expect(nova.commands.invoke).not.toBeCalled();
338+
});
294339
});
295340
});

src/main.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { dependencyManagement } from "nova-extension-utils";
22
import { registerFindReferences } from "./commands/findReferences";
33
import { registerFindSymbol } from "./commands/findSymbol";
4-
import { registerRename } from "./commands/rename";
54
import { registerOrganizeImports } from "./commands/organizeImports";
5+
import { registerRename } from "./commands/rename";
66
import { registerSignatureHelp } from "./commands/signatureHelp";
7-
import { wrapCommand } from "./novaUtils";
87
import { InformationView } from "./informationView";
9-
import { getTsLibPath } from "./tsLibPath";
108
import { isEnabledForJavascript } from "./isEnabledForJavascript";
9+
import { wrapCommand } from "./novaUtils";
10+
import { shouldOrganizeImportsOnSave } from "./shouldOrganizeImportsOnSave";
11+
import { getTsLibPath } from "./tsLibPath";
1112

1213
nova.commands.register(
1314
"apexskier.typescript.openWorkspaceConfig",
@@ -188,6 +189,24 @@ async function asyncActivate() {
188189

189190
client.start();
190191

192+
compositeDisposable.add(
193+
nova.workspace.onDidAddTextEditor((editor) => {
194+
const listener = editor.onWillSave(async (editor) => {
195+
if (
196+
editor.document.syntax &&
197+
syntaxes.includes(editor.document.syntax) &&
198+
shouldOrganizeImportsOnSave()
199+
) {
200+
await nova.commands.invoke(
201+
"apexskier.typescript.commands.organizeImports",
202+
editor
203+
);
204+
}
205+
});
206+
compositeDisposable.add(editor.onDidDestroy(() => listener.dispose()));
207+
})
208+
);
209+
191210
getTsVersion(tslibPath).then((version) => {
192211
informationView.tsVersion = version;
193212
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
(global as any).nova = Object.assign(nova, {
2+
commands: {
3+
invoke: jest.fn(),
4+
},
5+
config: {
6+
onDidChange: jest.fn(),
7+
["get"]: jest.fn(),
8+
},
9+
workspace: {
10+
config: { onDidChange: jest.fn(), ["get"]: jest.fn() },
11+
},
12+
});
13+
14+
jest.mock("nova-extension-utils", () => ({
15+
preferences: {
16+
getOverridableBoolean: jest.fn(),
17+
},
18+
}));
19+
20+
describe("shouldOrganizeImportsOnSave", () => {
21+
beforeEach(() => {
22+
(nova.workspace.config.get as jest.Mock).mockReset();
23+
(nova.config.get as jest.Mock).mockReset();
24+
});
25+
26+
const {
27+
shouldOrganizeImportsOnSave,
28+
} = require("./shouldOrganizeImportsOnSave");
29+
30+
describe("reloads extension when it changes", () => {
31+
it("globally and for the workspace", () => {
32+
expect(nova.config.onDidChange).toBeCalledTimes(1);
33+
expect(nova.config.onDidChange).toBeCalledWith(
34+
"apexskier.typescript.config.organizeImportsOnSave",
35+
expect.any(Function)
36+
);
37+
expect(nova.workspace.config.onDidChange).toBeCalledTimes(1);
38+
expect(nova.workspace.config.onDidChange).toBeCalledWith(
39+
"apexskier.typescript.config.organizeImportsOnSave",
40+
expect.any(Function)
41+
);
42+
// same function
43+
const onWorkspaceChange = (nova.workspace.config.onDidChange as jest.Mock)
44+
.mock.calls[0][1];
45+
const onGlobalChange = (nova.config.onDidChange as jest.Mock).mock
46+
.calls[0][1];
47+
expect(onWorkspaceChange).toBe(onGlobalChange);
48+
});
49+
50+
it("by calling the reload command", () => {
51+
const reload = (nova.config.onDidChange as jest.Mock).mock.calls[0][1];
52+
reload();
53+
expect(nova.commands.invoke).toBeCalledTimes(1);
54+
expect(nova.commands.invoke).toBeCalledWith(
55+
"apexskier.typescript.reload"
56+
);
57+
});
58+
});
59+
60+
it("is false by default", () => {
61+
expect(shouldOrganizeImportsOnSave()).toBe(false);
62+
});
63+
64+
it("can be enabled", () => {
65+
const {
66+
preferences: { getOverridableBoolean },
67+
} = require("nova-extension-utils");
68+
getOverridableBoolean.mockReturnValue(true);
69+
expect(shouldOrganizeImportsOnSave()).toBe(true);
70+
});
71+
});

src/shouldOrganizeImportsOnSave.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { preferences } from "nova-extension-utils";
2+
3+
const organizeImportsOnSaveKey =
4+
"apexskier.typescript.config.organizeImportsOnSave";
5+
6+
function reload() {
7+
nova.commands.invoke("apexskier.typescript.reload");
8+
}
9+
nova.config.onDidChange(organizeImportsOnSaveKey, reload);
10+
nova.workspace.config.onDidChange(organizeImportsOnSaveKey, reload);
11+
12+
export function shouldOrganizeImportsOnSave(): boolean {
13+
return preferences.getOverridableBoolean(organizeImportsOnSaveKey) ?? false;
14+
}

typescript.novaextension/extension.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
"link": "nova://extension/?id=apexskier.typescript",
3434
"type": "string"
3535
},
36+
{
37+
"key": "apexskier.typescript.config.organizeImportsOnSave",
38+
"title": "Organize imports on save",
39+
"description": "Run Organize Imports command on file save.",
40+
"type": "boolean",
41+
"default": false
42+
},
3643
{
3744
"key": "apexskier.typescript.config.isEnabledForJavascript",
3845
"title": "Enable on Javascript Files",
@@ -50,6 +57,18 @@
5057
"link": "nova://extension/?id=apexskier.typescript",
5158
"type": "string"
5259
},
60+
{
61+
"key": "apexskier.typescript.config.organizeImportsOnSave",
62+
"title": "Organize imports on save",
63+
"description": "Run Organize Imports command on file save.",
64+
"type": "enum",
65+
"values": [
66+
["null", "Inherit from Global Settings"],
67+
["false", "Disable"],
68+
["true", "Enable"]
69+
],
70+
"default": "null"
71+
},
5372
{
5473
"key": "apexskier.typescript.config.isEnabledForJavascript",
5574
"title": "Enable on Javascript Files",

0 commit comments

Comments
 (0)