From 15198197dd7584d21e6bbe669b21894a6b318a70 Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Sun, 15 Oct 2023 16:00:20 +0100 Subject: [PATCH 1/6] feat(lsp): add sort imports command --- packages/core/src/language-server/binding.ts | 11 +++++- .../language-server/glint-language-server.ts | 39 +++++++++++++++++++ .../core/src/language-server/messages.cts | 11 ++++-- packages/vscode/package.json | 6 ++- packages/vscode/src/extension.ts | 31 ++++++++++++++- 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/packages/core/src/language-server/binding.ts b/packages/core/src/language-server/binding.ts index b6a43f516..8e03fb976 100644 --- a/packages/core/src/language-server/binding.ts +++ b/packages/core/src/language-server/binding.ts @@ -12,7 +12,7 @@ import { import { TextDocument } from 'vscode-languageserver-textdocument'; import { GlintCompletionItem } from './glint-language-server.js'; import { LanguageServerPool } from './pool.js'; -import { GetIRRequest } from './messages.cjs'; +import { GetIRRequest, SortImportsRequest } from './messages.cjs'; import { ConfigManager } from './config-manager.js'; import type * as ts from 'typescript'; @@ -219,6 +219,15 @@ export function bindLanguageServerPool({ return pool.withServerForURI(uri, ({ server }) => server.getTransformedContents(uri)); }); + connection.onRequest(SortImportsRequest.type, ({ uri }) => { + return pool.withServerForURI(uri, ({ server }) => { + const language = server.getLanguageType(uri); + const formatting = configManager.getFormatCodeSettingsFor(language); + const preferences = configManager.getUserSettingsFor(language); + return server.organizeImports(uri, formatting, preferences); + }); + }); + connection.onDidChangeWatchedFiles(({ changes }) => { pool.forEachServer(({ server, scheduleDiagnostics }) => { for (let change of changes) { diff --git a/packages/core/src/language-server/glint-language-server.ts b/packages/core/src/language-server/glint-language-server.ts index 038662e3d..fd2e72e0f 100644 --- a/packages/core/src/language-server/glint-language-server.ts +++ b/packages/core/src/language-server/glint-language-server.ts @@ -416,6 +416,45 @@ export default class GlintLanguageServer { return this.glintConfig.environment.isTypedScript(file) ? 'typescript' : 'javascript'; } + public organizeImports( + uri: string, + formatOptions: ts.FormatCodeSettings = {}, + preferences: ts.UserPreferences = {} + ): TextEdit[] { + const transformInfo = this.transformManager.findTransformInfoForOriginalFile( + uriToFilePath(uri) + ); + + if (!transformInfo) { + return []; + } + + const fileTextChanges = this.service.organizeImports( + { + type: 'file', + fileName: transformInfo.transformedFileName, + skipDestructiveCodeActions: true, + }, + formatOptions, + preferences + ); + const edits: TextEdit[] = []; + + for (const fileTextChange of fileTextChanges) { + for (const textChange of fileTextChange.textChanges) { + const location = this.textSpanToLocation(fileTextChange.fileName, textChange.span); + if (location) { + edits.push({ + range: location.range, + newText: textChange.newText, + }); + } + } + } + + return edits; + } + private applyCodeAction( uri: string, range: Range, diff --git a/packages/core/src/language-server/messages.cts b/packages/core/src/language-server/messages.cts index df83f3e81..b5fdaab1d 100644 --- a/packages/core/src/language-server/messages.cts +++ b/packages/core/src/language-server/messages.cts @@ -1,4 +1,4 @@ -import { ProtocolRequestType } from 'vscode-languageserver'; +import { ProtocolRequestType, TextEdit } from 'vscode-languageserver'; export type Request = { name: Name; @@ -7,10 +7,15 @@ export type Request = { export const GetIRRequest = makeRequestType( 'glint/getIR', - ProtocolRequestType + ProtocolRequestType ); -export interface GetIRParams { +export const SortImportsRequest = makeRequestType( + 'glint/sortImports', + ProtocolRequestType +); + +export interface RequestParams { uri: string; } diff --git a/packages/vscode/package.json b/packages/vscode/package.json index a254e492b..cef92f059 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -50,6 +50,10 @@ "title": "Glint: Show IR for Debugging", "command": "glint.show-debug-ir", "enablement": "config.glint.debug == true" + }, + { + "title": "Glint: Sort Imports", + "command": "glint.sort-imports" } ], "configuration": [ @@ -111,4 +115,4 @@ "publisherId": "b79e9b30-918d-42b5-9460-27287aca13c4", "isPreReleaseVersion": false } -} +} \ No newline at end of file diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 78ecb3e07..d8b301fd5 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -11,9 +11,11 @@ import { commands, workspace, WorkspaceConfiguration, + WorkspaceEdit, + Position, } from 'vscode'; import { Disposable, LanguageClient, ServerOptions } from 'vscode-languageclient/node.js'; -import type { Request, GetIRRequest } from '@glint/core/lsp-messages'; +import type { Request, GetIRRequest, SortImportsRequest } from '@glint/core/lsp-messages'; /////////////////////////////////////////////////////////////////////////////// // Setup and extension lifecycle @@ -29,6 +31,7 @@ export function activate(context: ExtensionContext): void { context.subscriptions.push(fileWatcher, createConfigWatcher()); context.subscriptions.push( commands.registerCommand('glint.restart-language-server', restartClients), + commands.registerTextEditorCommand('glint.sort-imports', sortImports), commands.registerTextEditorCommand('glint.show-debug-ir', showDebugIR) ); @@ -57,6 +60,32 @@ async function restartClients(): Promise { await Promise.all([...clients.values()].map((client) => client.restart())); } +async function sortImports(editor: TextEditor): Promise { + let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri); + if (!workspaceFolder) { + return; + } + + let client = clients.get(workspaceFolder.uri.fsPath); + let request = requestKey('glint/sortImports'); + const edits = await client?.sendRequest(request, { uri: editor.document.uri.toString() }); + + if (!edits) { + return; + } + + const workspaceEdit = new WorkspaceEdit(); + + for (const edit of edits) { + const start = new Position(edit.range.start.line, edit.range.start.character); + const end = new Position(edit.range.end.line, edit.range.end.character); + const range = new Range(start, end); + workspaceEdit.replace(editor.document.uri, range, edit.newText); + } + + workspace.applyEdit(workspaceEdit); +} + async function showDebugIR(editor: TextEditor): Promise { let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri); if (!workspaceFolder) { From f01c1769f105ff4251c0892490ac95bc32a42999 Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Mon, 16 Oct 2023 11:22:57 +0100 Subject: [PATCH 2/6] test(lsp): add suite for sort imports command --- .../language-server/requests.test.ts | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 packages/core/__tests__/language-server/requests.test.ts diff --git a/packages/core/__tests__/language-server/requests.test.ts b/packages/core/__tests__/language-server/requests.test.ts new file mode 100644 index 000000000..adb268c37 --- /dev/null +++ b/packages/core/__tests__/language-server/requests.test.ts @@ -0,0 +1,328 @@ +import { Project } from 'glint-monorepo-test-utils'; +import { describe, beforeEach, afterEach, test, expect } from 'vitest'; +import { stripIndent } from 'common-tags'; +import * as ts from 'typescript'; + +describe('Language Server: Requests', () => { + let project!: Project; + + beforeEach(async () => { + project = await Project.create(); + }); + + afterEach(async () => { + await project.destroy(); + }); + + test('no imports', () => { + project.write({ + 'index.ts': stripIndent` + + export default class Application extends Component { + static template = hbs\` + Hello, world! + \`; + } + `, + }); + + let server = project.startLanguageServer(); + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([]); + }); + + test('single import', () => { + project.write({ + 'index.ts': stripIndent` + import Component, { hbs } from '@glimmerx/component'; + + export default class Application extends Component { + static template = hbs\` + Hello, world! + \`; + } + `, + }); + + let server = project.startLanguageServer(); + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([]); + }); + + test('many imports, unsorted', () => { + project.write({ + 'index.ts': stripIndent` + import './App.css'; + import EmberComponent from './ember-component'; + import Component from '@ember/component'; + import logo from './logo.svg'; + import { ComponentLike, WithBoundArgs } from '@glint/template'; + + interface WrapperComponentSignature { + Blocks: { + default: [ + { + InnerComponent: WithBoundArgs; + MaybeComponent?: ComponentLike<{ Args: { key: string } }>; + } + ]; + }; + } + + export default class WrapperComponent extends Component { + logo = logo + } + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 2 }, + end: { character: 0, line: 3 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 3 }, + end: { character: 0, line: 4 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 4 }, + end: { character: 0, line: 5 }, + }, + }, + ]); + + test('many imports, sorted', () => { + project.write({ + 'index.ts': stripIndent` + import Component from '@ember/component'; + import { ComponentLike, WithBoundArgs } from '@glint/template'; + import EmberComponent from './ember-component'; + import './App.css'; + import logo from './logo.svg'; + + interface WrapperComponentSignature { + Blocks: { + default: [ + { + InnerComponent: WithBoundArgs; + MaybeComponent?: ComponentLike<{ Args: { key: string } }>; + } + ]; + }; + } + + export default class WrapperComponent extends Component { + logo = logo + } + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 2 }, + end: { character: 0, line: 3 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 3 }, + end: { character: 0, line: 4 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 4 }, + end: { character: 0, line: 5 }, + }, + }, + ]); + }); + }); + + test('many imports, already sorted', () => { + project.write({ + 'index.ts': stripIndent` + import Component from '@ember/component'; + import { ComponentLike, WithBoundArgs } from '@glint/template'; + import './App.css'; + import EmberComponent from './ember-component'; + import logo from './logo.svg'; + + interface WrapperComponentSignature { + Blocks: { + default: [ + { + InnerComponent: WithBoundArgs; + MaybeComponent?: ComponentLike<{ Args: { key: string } }>; + } + ]; + }; + } + + export default class WrapperComponent extends Component { + logo = logo + } + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 2 }, + end: { character: 0, line: 3 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 3 }, + end: { character: 0, line: 4 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 4 }, + end: { character: 0, line: 5 }, + }, + }, + ]); + }); + + test('gts import sorting', () => { + project.setGlintConfig({ environment: 'ember-template-imports' }); + project.write({ + 'index.gts': stripIndent` + import Component from '@glimmer/component'; + import { TOC } from '@ember/component/template-only'; + import { hash } from '@ember/helper'; + + const MaybeComponent: undefined as TOC<{ Args: { arg: string } }> | undefined; + + class List extends Component { + + } + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.gts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import { TOC } from '@ember/component/template-only';\nimport { hash } from '@ember/helper';\nimport Component from '@glimmer/component';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 2 }, + end: { character: 0, line: 3 }, + }, + }, + ]); + }); +}); From 9e2e6ef6752a151f96129c18cddcf7eb500b463f Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Sun, 22 Oct 2023 12:59:52 +0100 Subject: [PATCH 3/6] refactor(lsp): create request-specific types --- packages/core/src/language-server/messages.cts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/core/src/language-server/messages.cts b/packages/core/src/language-server/messages.cts index b5fdaab1d..439b68191 100644 --- a/packages/core/src/language-server/messages.cts +++ b/packages/core/src/language-server/messages.cts @@ -7,15 +7,15 @@ export type Request = { export const GetIRRequest = makeRequestType( 'glint/getIR', - ProtocolRequestType + ProtocolRequestType ); export const SortImportsRequest = makeRequestType( 'glint/sortImports', - ProtocolRequestType + ProtocolRequestType ); -export interface RequestParams { +export interface GetIRParams { uri: string; } @@ -24,6 +24,12 @@ export interface GetIRResult { uri: string; } +export interface SortImportsParams { + uri: string; +} + +export type SortImportsResult = TextEdit[]; + // This utility allows us to encode type information to enforce that we're using // a valid request name along with its associated param/response types without // actually requring the runtime code here to be imported elsewhere. From 83937e400358e26043d0c452fbc9966db85b37c6 Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Sun, 22 Oct 2023 13:00:37 +0100 Subject: [PATCH 4/6] feat(lsp): limit SortImports to relevant files --- packages/vscode/package.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index cef92f059..da2e63d1b 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -56,6 +56,14 @@ "command": "glint.sort-imports" } ], + "menus": { + "commandPalette": [ + { + "command": "glint.sort-imports", + "when": "editorLangId =~ /javascript|typescript|glimmer-js|glimmer-ts/" + } + ] + }, "configuration": [ { "title": "Glint", @@ -115,4 +123,4 @@ "publisherId": "b79e9b30-918d-42b5-9460-27287aca13c4", "isPreReleaseVersion": false } -} \ No newline at end of file +} From e9354bd28d84ac218a8a353dd6b38e7d6b9f78a1 Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Sun, 22 Oct 2023 13:07:45 +0100 Subject: [PATCH 5/6] refactor(vscode): simplify Range creation --- packages/vscode/src/extension.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index d8b301fd5..d5a7e3ea1 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -12,7 +12,6 @@ import { workspace, WorkspaceConfiguration, WorkspaceEdit, - Position, } from 'vscode'; import { Disposable, LanguageClient, ServerOptions } from 'vscode-languageclient/node.js'; import type { Request, GetIRRequest, SortImportsRequest } from '@glint/core/lsp-messages'; @@ -61,7 +60,7 @@ async function restartClients(): Promise { } async function sortImports(editor: TextEditor): Promise { - let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri); + const workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri); if (!workspaceFolder) { return; } @@ -77,9 +76,12 @@ async function sortImports(editor: TextEditor): Promise { const workspaceEdit = new WorkspaceEdit(); for (const edit of edits) { - const start = new Position(edit.range.start.line, edit.range.start.character); - const end = new Position(edit.range.end.line, edit.range.end.character); - const range = new Range(start, end); + const range = new Range( + edit.range.start.line, + edit.range.start.character, + edit.range.end.line, + edit.range.end.character + ); workspaceEdit.replace(editor.document.uri, range, edit.newText); } From 52a51d6f1f685c758177ac0e83dbfdb0ed4f1828 Mon Sep 17 00:00:00 2001 From: Cameron Dubas Date: Sun, 22 Oct 2023 14:59:01 +0100 Subject: [PATCH 6/6] test(lsp): sorting multiple import blocks --- .../language-server/organize-imports.test.ts | 178 ++++++++++ .../language-server/requests.test.ts | 328 ------------------ 2 files changed, 178 insertions(+), 328 deletions(-) create mode 100644 packages/core/__tests__/language-server/organize-imports.test.ts delete mode 100644 packages/core/__tests__/language-server/requests.test.ts diff --git a/packages/core/__tests__/language-server/organize-imports.test.ts b/packages/core/__tests__/language-server/organize-imports.test.ts new file mode 100644 index 000000000..08a90d6a8 --- /dev/null +++ b/packages/core/__tests__/language-server/organize-imports.test.ts @@ -0,0 +1,178 @@ +import { Project } from 'glint-monorepo-test-utils'; +import { describe, beforeEach, afterEach, test, expect } from 'vitest'; +import { stripIndent } from 'common-tags'; +import * as ts from 'typescript'; + +describe('Language Server: Organize Imports', () => { + let project!: Project; + + beforeEach(async () => { + project = await Project.create(); + }); + + afterEach(async () => { + await project.destroy(); + }); + + test('no imports', () => { + project.write({ + 'index.ts': stripIndent` + + export default class Application extends Component { + static template = hbs\` + Hello, world! + \`; + } + `, + }); + + let server = project.startLanguageServer(); + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([]); + }); + + test('ts: handles sorting imports', () => { + project.write({ + 'index.ts': stripIndent` + import './App.css'; + import EmberComponent from './ember-component'; + import Component from '@ember/component'; + + interface WrapperComponentSignature { + Blocks: { + default: [ + { + InnerComponent: WithBoundArgs; + MaybeComponent?: ComponentLike<{ Args: { key: string } }>; + } + ]; + }; + } + + // Second Import Block + import logo from './logo.svg'; + import { ComponentLike, WithBoundArgs } from '@glint/template'; + + export default class WrapperComponent extends Component { + logo = logo + } + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import Component from '@ember/component';\nimport './App.css';\nimport EmberComponent from './ember-component';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 2 }, + end: { character: 0, line: 3 }, + }, + }, + { + newText: + "import { ComponentLike, WithBoundArgs } from '@glint/template';\nimport logo from './logo.svg';\n", + range: { + start: { character: 0, line: 16 }, + end: { character: 0, line: 17 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 17 }, + end: { character: 0, line: 18 }, + }, + }, + ]); + }); + + test('gts: handles sorting imports', () => { + project.setGlintConfig({ environment: 'ember-template-imports' }); + project.write({ + 'index.gts': stripIndent` + import Component from '@glimmer/component'; + import { hash } from '@ember/helper'; + + class List extends Component { + + } + + // Second Import Block + import { ComponentLike, ModifierLike, HelperLike } from '@glint/template'; + import { TOC } from '@ember/component/template-only'; + + const MaybeComponent: undefined as TOC<{ Args: { arg: string } }> | undefined; + declare const CanvasThing: ComponentLike<{ Args: { str: string }; Element: HTMLCanvasElement }>; + `, + }); + + let server = project.startLanguageServer(); + + let formatting = ts.getDefaultFormatCodeSettings(); + let preferences = {}; + let edits = server.organizeImports(project.fileURI('index.gts'), formatting, preferences); + + expect(edits).toEqual([ + { + newText: + "import { hash } from '@ember/helper';\nimport Component from '@glimmer/component';\n", + range: { + start: { character: 0, line: 0 }, + end: { character: 0, line: 1 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 1 }, + end: { character: 0, line: 2 }, + }, + }, + { + newText: + "import { TOC } from '@ember/component/template-only';\nimport { ComponentLike, HelperLike, ModifierLike } from '@glint/template';\n", + range: { + start: { character: 0, line: 15 }, + end: { character: 0, line: 16 }, + }, + }, + { + newText: '', + range: { + start: { character: 0, line: 16 }, + end: { character: 0, line: 17 }, + }, + }, + ]); + }); +}); diff --git a/packages/core/__tests__/language-server/requests.test.ts b/packages/core/__tests__/language-server/requests.test.ts deleted file mode 100644 index adb268c37..000000000 --- a/packages/core/__tests__/language-server/requests.test.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { Project } from 'glint-monorepo-test-utils'; -import { describe, beforeEach, afterEach, test, expect } from 'vitest'; -import { stripIndent } from 'common-tags'; -import * as ts from 'typescript'; - -describe('Language Server: Requests', () => { - let project!: Project; - - beforeEach(async () => { - project = await Project.create(); - }); - - afterEach(async () => { - await project.destroy(); - }); - - test('no imports', () => { - project.write({ - 'index.ts': stripIndent` - - export default class Application extends Component { - static template = hbs\` - Hello, world! - \`; - } - `, - }); - - let server = project.startLanguageServer(); - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); - - expect(edits).toEqual([]); - }); - - test('single import', () => { - project.write({ - 'index.ts': stripIndent` - import Component, { hbs } from '@glimmerx/component'; - - export default class Application extends Component { - static template = hbs\` - Hello, world! - \`; - } - `, - }); - - let server = project.startLanguageServer(); - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); - - expect(edits).toEqual([]); - }); - - test('many imports, unsorted', () => { - project.write({ - 'index.ts': stripIndent` - import './App.css'; - import EmberComponent from './ember-component'; - import Component from '@ember/component'; - import logo from './logo.svg'; - import { ComponentLike, WithBoundArgs } from '@glint/template'; - - interface WrapperComponentSignature { - Blocks: { - default: [ - { - InnerComponent: WithBoundArgs; - MaybeComponent?: ComponentLike<{ Args: { key: string } }>; - } - ]; - }; - } - - export default class WrapperComponent extends Component { - logo = logo - } - `, - }); - - let server = project.startLanguageServer(); - - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); - - expect(edits).toEqual([ - { - newText: - "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", - range: { - start: { character: 0, line: 0 }, - end: { character: 0, line: 1 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 1 }, - end: { character: 0, line: 2 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 2 }, - end: { character: 0, line: 3 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 3 }, - end: { character: 0, line: 4 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 4 }, - end: { character: 0, line: 5 }, - }, - }, - ]); - - test('many imports, sorted', () => { - project.write({ - 'index.ts': stripIndent` - import Component from '@ember/component'; - import { ComponentLike, WithBoundArgs } from '@glint/template'; - import EmberComponent from './ember-component'; - import './App.css'; - import logo from './logo.svg'; - - interface WrapperComponentSignature { - Blocks: { - default: [ - { - InnerComponent: WithBoundArgs; - MaybeComponent?: ComponentLike<{ Args: { key: string } }>; - } - ]; - }; - } - - export default class WrapperComponent extends Component { - logo = logo - } - `, - }); - - let server = project.startLanguageServer(); - - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); - - expect(edits).toEqual([ - { - newText: - "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", - range: { - start: { character: 0, line: 0 }, - end: { character: 0, line: 1 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 1 }, - end: { character: 0, line: 2 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 2 }, - end: { character: 0, line: 3 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 3 }, - end: { character: 0, line: 4 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 4 }, - end: { character: 0, line: 5 }, - }, - }, - ]); - }); - }); - - test('many imports, already sorted', () => { - project.write({ - 'index.ts': stripIndent` - import Component from '@ember/component'; - import { ComponentLike, WithBoundArgs } from '@glint/template'; - import './App.css'; - import EmberComponent from './ember-component'; - import logo from './logo.svg'; - - interface WrapperComponentSignature { - Blocks: { - default: [ - { - InnerComponent: WithBoundArgs; - MaybeComponent?: ComponentLike<{ Args: { key: string } }>; - } - ]; - }; - } - - export default class WrapperComponent extends Component { - logo = logo - } - `, - }); - - let server = project.startLanguageServer(); - - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.ts'), formatting, preferences); - - expect(edits).toEqual([ - { - newText: - "import Component from '@ember/component';\nimport { ComponentLike, WithBoundArgs } from '@glint/template';\nimport './App.css';\nimport EmberComponent from './ember-component';\nimport logo from './logo.svg';\n", - range: { - start: { character: 0, line: 0 }, - end: { character: 0, line: 1 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 1 }, - end: { character: 0, line: 2 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 2 }, - end: { character: 0, line: 3 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 3 }, - end: { character: 0, line: 4 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 4 }, - end: { character: 0, line: 5 }, - }, - }, - ]); - }); - - test('gts import sorting', () => { - project.setGlintConfig({ environment: 'ember-template-imports' }); - project.write({ - 'index.gts': stripIndent` - import Component from '@glimmer/component'; - import { TOC } from '@ember/component/template-only'; - import { hash } from '@ember/helper'; - - const MaybeComponent: undefined as TOC<{ Args: { arg: string } }> | undefined; - - class List extends Component { - - } - `, - }); - - let server = project.startLanguageServer(); - - let formatting = ts.getDefaultFormatCodeSettings(); - let preferences = {}; - let edits = server.organizeImports(project.fileURI('index.gts'), formatting, preferences); - - expect(edits).toEqual([ - { - newText: - "import { TOC } from '@ember/component/template-only';\nimport { hash } from '@ember/helper';\nimport Component from '@glimmer/component';\n", - range: { - start: { character: 0, line: 0 }, - end: { character: 0, line: 1 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 1 }, - end: { character: 0, line: 2 }, - }, - }, - { - newText: '', - range: { - start: { character: 0, line: 2 }, - end: { character: 0, line: 3 }, - }, - }, - ]); - }); -});