Repository: sveltejs/language-tools
Pull request #1320 (2022-01-06 03:00:09)

(fix) prevent comment being removed in organizes imports
#1310 

Side effort, this also allows the `@ts-ignore` comment to silence import diagnostics. 


Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
new file mode 100644
index 00000000..258f7ca3
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/handleImportDeclaration.ts
@@ -0,0 +1,27 @@
+import MagicString from 'magic-string';
+import ts from 'typescript';
+
+/**
+ * move imports to top of script so they appear outside our render function
+ */
+export function handleImportDeclaration(
+    node: ts.ImportDeclaration,
+    str: MagicString,
+    astOffset: number,
+    scriptStart: number
+) {
+    const comments = ts.getLeadingCommentRanges(node.getFullText(), 0) ?? [];
+    for (const comment of comments) {
+        const commentEnd = node.pos + comment.end + astOffset;
+        str.move(node.pos + comment.pos + astOffset, commentEnd, scriptStart + 1);
+
+        if (comment.hasTrailingNewLine) {
+            str.overwrite(commentEnd - 1, commentEnd, str.original[commentEnd - 1] + '\n');
+        }
+    }
+
+    str.move(node.getStart() + astOffset, node.end + astOffset, scriptStart + 1);
+    //add in a \n
+    const originalEndChar = str.original[node.end + astOffset - 1];
+    str.overwrite(node.end + astOffset - 1, node.end + astOffset, originalEndChar + '\n');
+}
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 7fd86f76..75583f3b 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -15,6 +15,7 @@ import { ImplicitStoreValues } from './nodes/ImplicitStoreValues';
 import { Generics } from './nodes/Generics';
 import { is$$SlotsDeclaration } from './nodes/slot';
 import { preprendStr } from '../utils/magic-string';
+import { handleImportDeclaration } from './nodes/handleImportDeclaration';
 
 export interface InstanceScriptProcessResult {
     exportedNames: ExportedNames;
@@ -283,11 +284,8 @@ export function processInstanceScriptContent(
         }
 
         if (ts.isImportDeclaration(node)) {
-            //move imports to top of script so they appear outside our render function
-            str.move(node.getStart() + astOffset, node.end + astOffset, script.start + 1);
-            //add in a \n
-            const originalEndChar = str.original[node.end + astOffset - 1];
-            str.overwrite(node.end + astOffset - 1, node.end + astOffset, originalEndChar + '\n');
+            handleImportDeclaration(node, str, astOffset, script.start);
+
             // Check if import is the event dispatcher
             events.checkIfImportIsEventDispatcher(node);
         }


Test patch:
diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
index 158dad5d..51da7b55 100644
--- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
+++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts
@@ -695,6 +695,70 @@ describe('CodeActionsProvider', () => {
         ]);
     });
 
+    it('organizes imports and not remove the leading comment', async () => {
+        const { provider, document } = setup('organize-imports-leading-comment.svelte');
+
+        const codeActions = await provider.getCodeActions(
+            document,
+            Range.create(Position.create(1, 4), Position.create(1, 5)), // irrelevant
+            {
+                diagnostics: [],
+                only: [CodeActionKind.SourceOrganizeImports]
+            }
+        );
+        (<TextDocumentEdit>codeActions[0]?.edit?.documentChanges?.[0])?.edits.forEach(
+            (edit) => (edit.newText = harmonizeNewLines(edit.newText))
+        );
+
+        assert.deepStrictEqual(codeActions, [
+            {
+                edit: {
+                    documentChanges: [
+                        {
+                            edits: [
+                                {
+                                    newText:
+                                        '// @ts-ignore\n' +
+                                        "    import { } from './somepng.png';\n" +
+                                        "    import { } from './t.png';\n",
+                                    range: {
+                                        end: {
+                                            character: 4,
+                                            line: 2
+                                        },
+                                        start: {
+                                            character: 4,
+                                            line: 1
+                                        }
+                                    }
+                                },
+                                {
+                                    newText: '',
+                                    range: {
+                                        end: {
+                                            character: 0,
+                                            line: 4
+                                        },
+                                        start: {
+                                            character: 4,
+                                            line: 2
+                                        }
+                                    }
+                                }
+                            ],
+                            textDocument: {
+                                uri: getUri('organize-imports-leading-comment.svelte'),
+                                version: null
+                            }
+                        }
+                    ]
+                },
+                kind: 'source.organizeImports',
+                title: 'Organize Imports'
+            }
+        ]);
+    });
+
     it('should do extract into const refactor', async () => {
         const { provider, document } = setup('codeactions.svelte');
 
diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte
new file mode 100644
index 00000000..27ae0f3e
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/organize-imports-leading-comment.svelte
@@ -0,0 +1,5 @@
+<script>
+    import { } from './t.png';
+    // @ts-ignore
+    import { } from './somepng.png';
+</script>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx
new file mode 100644
index 00000000..5f3f21cc
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/expected.tsx
@@ -0,0 +1,18 @@
+///<reference types="svelte" />
+<></>;
+import A from './a.svelte';
+// @ts-ignore
+import B from './b.svelte';
+/*hi*/import C from './c.svelte';
+function render() {
+
+    
+    
+    
+    
+;
+() => (<></>);
+return { props: {}, slots: {}, getters: {}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte
new file mode 100644
index 00000000..faeafc35
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/import-leading-comment/input.svelte
@@ -0,0 +1,6 @@
+<script lang="ts">
+    import A from './a.svelte';
+    // @ts-ignore
+    import B from './b.svelte';
+    /*hi*/import C from './c.svelte';
+</script>