Repository: sveltejs/language-tools
Pull request #1313 (2022-01-05 04:41:50)

Fix language server Yarn PnP resolution crash
**Summary:** The Svelte Language Server crashes while being used with [Yarn PnP VS Code support](https://yarnpkg.com/sdks/cli/default) because it [uses the package `vscode-languageserver-protocol`](https://github.com/sveltejs/language-tools/blob/cde40da02a3934ff90b78aa44d42a616fb2f18d1/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts#L1) [without declaring it in its dependencies](https://github.com/sveltejs/language-tools/blob/cde40da02a3934ff90b78aa44d42a616fb2f18d1/packages/language-server/package.json#L65). This pull request adds the package to the server's dependencies so it no longer crashes.

**Steps to reproduce:**
1. Install [VSCode](https://code.visualstudio.com/) and the [Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)
2. Go to Settings and set the following settings:
a. `svelte.language-server.ls-path`: `.yarn/sdks/svelte-language-server/bin/server.js`
b. `svelte.trace.server`: `verbose`
3. Clone the [PoC](https://github.com/NoelTautges/svelte-language-server-pnp)
4. Open the PoC folder in VS Code
5. Open `App.svelte`
6. View the following error message in Output > Svelte:
```
[snip]\svelte-language-server-pnp-crash\.pnp.cjs:11508
    throw firstError;
    ^

Error: svelte-language-server tried to access vscode-languageserver-protocol, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.

Required package: vscode-languageserver-protocol
Required by: svelte-language-server@npm:0.14.16 (via [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\plugins\typescript\features\)

Require stack:
- [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\plugins\typescript\features\ImplementationProvider.js
- [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\plugins\typescript\TypeScriptPlugin.js
- [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\plugins\index.js
- [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\server.js
- [snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\bin\server.js
- [snip]\svelte-language-server-pnp-crash\.pnp.cjs
    at Function.external_module_.Module._resolveFilename ([snip]\svelte-language-server-pnp-crash\.pnp.cjs:11507:55)
    at Function.external_module_.Module._load ([snip]\svelte-language-server-pnp-crash\.pnp.cjs:11306:48)
    at Module.require (internal/modules/cjs/loader.js:1006:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> ([snip]\svelte-language-server-pnp-crash\.yarn\cache\svelte-language-server-npm-0.14.16-ad271df40a-4826ff5df6.zip\node_modules\svelte-language-server\dist\src\plugins\typescript\features\ImplementationProvider.js:4:42)
    at Module._compile (internal/modules/cjs/loader.js:1125:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1155:10)
    at Module.load (internal/modules/cjs/loader.js:982:32)
    at Function.external_module_.Module._load ([snip]\svelte-language-server-pnp-crash\.pnp.cjs:11356:14)
    at Module.require (internal/modules/cjs/loader.js:1006:19)
[Error - 8:19:27 PM] Connection to server got closed. Server will not be restarted.
```
7. Run `yarn remove` and `yarn link [path to built language-server]`
8. Restart VS Code and open `App.svelte` again
9. View no error messages
10. Unset the settings above

**Temporary workaround:** Add the following lines to [`.yarnrc.yml`](https://yarnpkg.com/configuration/yarnrc#packageExtensions):
```yml
packageExtensions:
  svelte-language-server@*:
    dependencies:
      vscode-languageserver-protocol: "*"
```

**Notes:**
- The `vscode-languageserver-protocol` version added is the same as `vscode-languageserver-types` so they can be updated togther.

Thank you for your time! Let me know if you need more information.


Patch:
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index df7ffa29..bd5d9973 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -63,6 +63,7 @@
         "vscode-emmet-helper": "~2.6.0",
         "vscode-html-languageservice": "~4.1.0",
         "vscode-languageserver": "7.1.0-next.4",
+        "vscode-languageserver-protocol": "3.16.0",
         "vscode-languageserver-types": "3.16.0",
         "vscode-uri": "~3.0.0"
     }
diff --git a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
index 5a7ebc4f..cbe96da8 100644
--- a/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
+++ b/packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml
@@ -13,22 +13,23 @@ injections:
   #     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
   #     contentName: source.<lang>, patterns: [{ include: source.<lang> }]}]
 
-  # Script Languages
+  # Style/Script Languages
   # JavaScript | 'javascript' | 'source.js'
-  'L:meta.script.svelte meta.lang.javascript - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) (meta.lang.js | meta.lang.javascript) - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.js, patterns: [{ include: source.js }]}]
 
   # TypeScript | 'ts' 'typescript' | 'source.ts'
-  'L:meta.script.svelte (meta.lang.ts | meta.lang.typescript) - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) (meta.lang.ts | meta.lang.typescript) - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.ts, patterns: [{ include: source.ts }]}]
 
   # CoffeeScript | 'coffee' | 'source.coffee'
-  'L:meta.script.svelte meta.lang.coffee - (meta source)':
+  'L:(meta.script.svelte | meta.style.svelte) meta.lang.coffee - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
     contentName: source.coffee, patterns: [{ include: source.coffee }]}]
 
+  # Script Languages
   # Default (JavaScript)
   'L:meta.script.svelte - meta.lang - (meta source)':
     patterns: [{begin: '(?<=>)(?!</)', end: '(?=</)', name: meta.embedded.block.svelte,
@@ -339,24 +340,36 @@ repository:
   # Matches attribute keyvalues. (and boolean attributes as well)
   # e.g. `class="my-class"`
   attributes-keyvalue:
-    begin: ([_$[:alpha:]][_\-$[:alnum:]]*)
-    beginCaptures: { 1: { name: entity.other.attribute-name.svelte } }
+    begin: ((?:--)?[_$[:alpha:]][_\-$[:alnum:]]*)
+    beginCaptures:
+      0:
+        patterns:
+        # Matches if the key is a `--css-variable` attribute.
+        - match: --.*
+          name: support.type.property-name.svelte
+        # Matches everything else.
+        - match: .*
+          name: entity.other.attribute-name.svelte
     end: (?=\s*+[^=\s])
     name: 'meta.attribute.$1.svelte'
-    patterns: [ include: '#attributes-value' ]
+    patterns:
+    - begin: '='
+      beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
+      end: (?<=[^\s=])(?!\s*=)|(?=/?>)
+      patterns: [include: '#attributes-value']
 
-  # The value part of attribute keyvalues. e.g. `="my-class"` in `class="my-class"`
+  # The value part of attribute keyvalues. e.g. `"my-class"` in `class="my-class"`
   attributes-value:
-    begin: '='
-    beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
-    end: (?<=[^\s=])(?!\s*=)|(?=/?>)
     patterns:
     # No quotes - just an interpolation expression.
     - include: '#interpolation'
     # Units, meaning digit characters and an optional unit string. e.g. `15px`
-    - match: ([0-9._]+[\w]{,4})(?=\s|/?>)
-      name: constant.numeric.decimal.svelte
-      patterns: [ include: '#interpolation' ]
+    - match: (?:(['"])([0-9._]+[\w%]{,4})(\1))|(?:([0-9._]+[\w%]{,4})(?=\s|/?>))
+      captures:
+        1: { name: punctuation.definition.string.begin.svelte }
+        2: { name: constant.numeric.decimal.svelte }
+        3: { name: punctuation.definition.string.end.svelte }
+        4: { name: constant.numeric.decimal.svelte }
     # Unquoted strings.
     - match: ([^\s"'=<>`/]|/(?!>))+
       name: string.unquoted.svelte
@@ -365,7 +378,7 @@ repository:
     - begin: (['"])
       end: \1
       beginCaptures: { 0: { name: punctuation.definition.string.begin.svelte } }
-      endCaptures: { 0: { name: punctuation.definition.string.end.svelte } }
+      endCaptures:   { 0: { name: punctuation.definition.string.end.svelte } }
       name: string.quoted.svelte
       patterns: [ include: '#interpolation' ]
 
@@ -419,7 +432,10 @@ repository:
     end: (?=\s*+[^=\s])
     name: meta.directive.$1.svelte
     patterns:
-    - include: '#attributes-value'
+    - begin: '='
+      beginCaptures: { 0: { name: punctuation.separator.key-value.svelte } }
+      end: (?<=[^\s=])(?!\s*=)|(?=/?>)
+      patterns: [include: '#attributes-value']
 
   # ------
   #  TAGS
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);
         }
diff --git a/packages/svelte2tsx/svelte-jsx.d.ts b/packages/svelte2tsx/svelte-jsx.d.ts
index 2f9742bc..889a6d69 100644
--- a/packages/svelte2tsx/svelte-jsx.d.ts
+++ b/packages/svelte2tsx/svelte-jsx.d.ts
@@ -581,6 +581,7 @@ declare namespace svelte.JSX {
       autocorrect?: string | undefined | null;
       autosave?: string | undefined | null;
       color?: string | undefined | null;
+      controlslist?: 'nodownload' | 'nofullscreen' | 'noplaybackrate' | 'noremoteplayback';
       itemprop?: string | undefined | null;
       itemscope?: boolean | undefined | null;
       itemtype?: string | undefined | null;


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>