Repository: denoland/deno
Pull request: fix(lsp): handle pathless untitled URIs

Fixes https://github.com/denoland/vscode_deno/issues/1207. `untitled:Untitled-1` was being converted to `file://Untitled-1/` instead of `file:///Untitled-1`.

Changed files:
filename: cli/lsp/language_server.rs
patch: @@ -1419,18 +1419,16 @@ impl Inner {
             // the file path is only used to determine what formatter should
             // be used to format the file, so give the filepath an extension
             // that matches what the user selected as the language
-            let file_path = document
+            let ext = document
               .maybe_language_id()
-              .and_then(|id| id.as_extension())
-              .map(|ext| file_path.with_extension(ext))
-              .unwrap_or(file_path);
+              .and_then(|id| id.as_extension().map(|s| s.to_string()));
             // it's not a js/ts file, so attempt to format its contents
             format_file(
               &file_path,
               document.content(),
               &fmt_options,
               &unstable_options,
-              None,
+              ext,
             )
           }
         };

filename: cli/lsp/urls.rs
patch: @@ -282,24 +282,26 @@ impl LspUrlMap {
   }
 }
 
-/// Convert a e.g. `deno-notebook-cell:` specifier to a `file:` specifier.
+/// Convert a e.g. `vscode-notebook-cell:` specifier to a `file:` specifier.
 /// ```rust
 /// assert_eq!(
 ///   file_like_to_file_specifier(
-///     &Url::parse("deno-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
+///     &Url::parse("vscode-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
 ///   ),
-///   Some(Url::parse("file:///path/to/file.ipynb.ts?scheme=deno-notebook-cell#abc").unwrap()),
+///   Some(Url::parse("file:///path/to/file.ipynb?scheme=untitled#abc").unwrap()),
 /// );
 fn file_like_to_file_specifier(specifier: &Url) -> Option<Url> {
-  if matches!(specifier.scheme(), "untitled" | "deno-notebook-cell") {
+  if matches!(
+    specifier.scheme(),
+    "untitled" | "vscode-notebook-cell" | "deno-notebook-cell"
+  ) {
     if let Ok(mut s) = ModuleSpecifier::parse(&format!(
-      "file://{}",
+      "file:///{}",
       &specifier.as_str()[deno_core::url::quirks::internal_components(specifier)
-        .host_end as usize..],
+        .host_end as usize..].trim_start_matches('/'),
     )) {
       s.query_pairs_mut()
         .append_pair("scheme", specifier.scheme());
-      s.set_path(&format!("{}.ts", s.path()));
       return Some(s);
     }
   }
@@ -432,11 +434,11 @@ mod tests {
   fn test_file_like_to_file_specifier() {
     assert_eq!(
       file_like_to_file_specifier(
-        &Url::parse("deno-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
+        &Url::parse("vscode-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
       ),
       Some(
         Url::parse(
-          "file:///path/to/file.ipynb.ts?scheme=deno-notebook-cell#abc"
+          "file:///path/to/file.ipynb?scheme=vscode-notebook-cell#abc"
         )
         .unwrap()
       ),
@@ -446,8 +448,7 @@ mod tests {
         &Url::parse("untitled:/path/to/file.ipynb#123").unwrap(),
       ),
       Some(
-        Url::parse("file:///path/to/file.ipynb.ts?scheme=untitled#123")
-          .unwrap()
+        Url::parse("file:///path/to/file.ipynb?scheme=untitled#123").unwrap()
       ),
     );
   }

filename: tests/integration/lsp_tests.rs
patch: @@ -11690,6 +11690,46 @@ fn lsp_format_exclude_default_config() {
   client.shutdown();
 }
 
+#[test]
+fn lsp_format_untitled() {
+  let context = TestContextBuilder::new().use_temp_cwd().build();
+  let mut client = context.new_lsp_command().build();
+  client.initialize_default();
+  client.did_open(json!({
+    "textDocument": {
+      "uri": "untitled:Untitled-1",
+      "languageId": "typescript",
+      "version": 1,
+      "text": "  console.log();\n",
+    },
+  }));
+  let res = client.write_request(
+    "textDocument/formatting",
+    json!({
+      "textDocument": {
+        "uri": "untitled:Untitled-1",
+      },
+      "options": {
+        "tabSize": 2,
+        "insertSpaces": true,
+      },
+    }),
+  );
+  assert_eq!(
+    res,
+    json!([
+      {
+        "range": {
+          "start": { "line": 0, "character": 0 },
+          "end": { "line": 0, "character": 2 },
+        },
+        "newText": "",
+      },
+    ])
+  );
+  client.shutdown();
+}
+
 #[test]
 fn lsp_format_json() {
   let context = TestContextBuilder::new().use_temp_cwd().build();