Repository: rust-lang/rust-analyzer
Pull request #12652 (2022-06-28 19:18:37)

internal: Try to publish releases to OpenVSX


Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index a99894dc44..9205d86f93 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -246,13 +246,24 @@ jobs:
       - run: npm ci
         working-directory: ./editors/code
 
-      - name: Publish Extension (release)
+      - name: Publish Extension (Code Marketplace, release)
         if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
         working-directory: ./editors/code
         # token from https://dev.azure.com/rust-analyzer/
         run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
 
-      - name: Publish Extension (nightly)
+      - name: Publish Extension (OpenVSX, release)
+        if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
+        working-directory: ./editors/code
+        # token from https://dev.azure.com/rust-analyzer/
+        run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
+
+      - name: Publish Extension (Code Marketplace, nightly)
         if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
         working-directory: ./editors/code
         run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release
+
+      - name: Publish Extension (OpenVSX, nightly)
+        if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
+        working-directory: ./editors/code
+        run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index c7566b06d0..57dd3a3502 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -312,7 +312,7 @@ pub struct SyntheticSyntax;
 pub enum BodyDiagnostic {
     InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
     MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
-    UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>>, krate: CrateId },
+    UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
     UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
 }
 
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 31e3877ea9..5d7a1100cd 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -572,10 +572,9 @@ impl ExprCollector<'_> {
 
         if record_diagnostics {
             match &res.err {
-                Some(ExpandError::UnresolvedProcMacro(krate)) => {
+                Some(ExpandError::UnresolvedProcMacro) => {
                     self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
                         node: InFile::new(outer_file, syntax_ptr),
-                        krate: *krate,
                     });
                 }
                 Some(err) => {
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index ca0cb99b37..9901df2af0 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -22,7 +22,6 @@ use itertools::Itertools;
 use la_arena::Idx;
 use limit::Limit;
 use rustc_hash::{FxHashMap, FxHashSet};
-use stdx::always;
 use syntax::{ast, SmolStr};
 
 use crate::{
@@ -1235,7 +1234,7 @@ impl DefCollector<'_> {
                         self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
                             directive.module_id,
                             loc.kind,
-                            loc.def.krate,
+                            Some(loc.def.krate),
                         ));
                         return recollect_without(self);
                     }
@@ -1259,7 +1258,7 @@ impl DefCollector<'_> {
                             self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
                                 directive.module_id,
                                 loc.kind,
-                                loc.def.krate,
+                                Some(loc.def.krate),
                             ));
 
                             return recollect_without(self);
@@ -1309,10 +1308,13 @@ impl DefCollector<'_> {
         let err = self.db.macro_expand_error(macro_call_id);
         if let Some(err) = err {
             let diag = match err {
-                hir_expand::ExpandError::UnresolvedProcMacro(krate) => {
-                    always!(krate == loc.def.krate);
+                hir_expand::ExpandError::UnresolvedProcMacro => {
                     // Missing proc macros are non-fatal, so they are handled specially.
-                    DefDiagnostic::unresolved_proc_macro(module_id, loc.kind.clone(), loc.def.krate)
+                    DefDiagnostic::unresolved_proc_macro(
+                        module_id,
+                        loc.kind.clone(),
+                        Some(loc.def.krate),
+                    )
                 }
                 _ => DefDiagnostic::macro_error(module_id, loc.kind.clone(), err.to_string()),
             };
diff --git a/crates/hir-def/src/nameres/diagnostics.rs b/crates/hir-def/src/nameres/diagnostics.rs
index 0d01f6d0ab..3ebc5629d7 100644
--- a/crates/hir-def/src/nameres/diagnostics.rs
+++ b/crates/hir-def/src/nameres/diagnostics.rs
@@ -24,7 +24,7 @@ pub enum DefDiagnosticKind {
 
     UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
 
-    UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId },
+    UnresolvedProcMacro { ast: MacroCallKind, krate: Option<CrateId> },
 
     UnresolvedMacroCall { ast: MacroCallKind, path: ModPath },
 
@@ -85,7 +85,7 @@ impl DefDiagnostic {
     pub(super) fn unresolved_proc_macro(
         container: LocalModuleId,
         ast: MacroCallKind,
-        krate: CrateId,
+        krate: Option<CrateId>,
     ) -> Self {
         Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } }
     }
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 0844e1f625..bc5372754d 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -43,7 +43,7 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>;
 
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub enum ExpandError {
-    UnresolvedProcMacro(CrateId),
+    UnresolvedProcMacro,
     Mbe(mbe::ExpandError),
     Other(Box<str>),
 }
@@ -57,7 +57,7 @@ impl From<mbe::ExpandError> for ExpandError {
 impl fmt::Display for ExpandError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
+            ExpandError::UnresolvedProcMacro => f.write_str("unresolved proc-macro"),
             ExpandError::Mbe(it) => it.fmt(f),
             ExpandError::Other(it) => f.write_str(it),
         }
diff --git a/crates/hir-expand/src/proc_macro.rs b/crates/hir-expand/src/proc_macro.rs
index 5afdcc0e66..29d78e21ba 100644
--- a/crates/hir-expand/src/proc_macro.rs
+++ b/crates/hir-expand/src/proc_macro.rs
@@ -75,7 +75,7 @@ impl ProcMacroExpander {
                     },
                 }
             }
-            None => ExpandResult::only_err(ExpandError::UnresolvedProcMacro(self.krate)),
+            None => ExpandResult::only_err(ExpandError::UnresolvedProcMacro),
         }
     }
 }
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 7ecfb0cd6f..6a06c6b10f 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -90,7 +90,7 @@ pub struct UnresolvedProcMacro {
     pub macro_name: Option<String>,
     pub kind: MacroKind,
     /// The crate id of the proc-macro this macro belongs to, or `None` if the proc-macro can't be found.
-    pub krate: CrateId,
+    pub krate: Option<CrateId>,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index f24edebfd0..7a31a8a417 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1159,13 +1159,13 @@ impl DefWithBody {
                     }
                     .into(),
                 ),
-                BodyDiagnostic::UnresolvedProcMacro { node, krate } => acc.push(
+                BodyDiagnostic::UnresolvedProcMacro { node } => acc.push(
                     UnresolvedProcMacro {
                         node: node.clone().map(|it| it.into()),
                         precise_location: None,
                         macro_name: None,
                         kind: MacroKind::ProcMacro,
-                        krate: *krate,
+                        krate: None,
                     }
                     .into(),
                 ),
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 9bdde5e6e0..d624d37577 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -67,7 +67,10 @@ impl SourceAnalyzer {
         let scopes = db.expr_scopes(def);
         let scope = match offset {
             None => scope_for(&scopes, &source_map, node),
-            Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
+            Some(offset) => {
+                let file_id = node.file_id.original_file(db.upcast());
+                scope_for_offset(db, &scopes, &source_map, InFile::new(file_id.into(), offset))
+            }
         };
         let resolver = resolver_for_scope(db.upcast(), def, scope);
         SourceAnalyzer {
@@ -88,7 +91,10 @@ impl SourceAnalyzer {
         let scopes = db.expr_scopes(def);
         let scope = match offset {
             None => scope_for(&scopes, &source_map, node),
-            Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
+            Some(offset) => {
+                let file_id = node.file_id.original_file(db.upcast());
+                scope_for_offset(db, &scopes, &source_map, InFile::new(file_id.into(), offset))
+            }
         };
         let resolver = resolver_for_scope(db.upcast(), def, scope);
         SourceAnalyzer { resolver, def: Some((def, body, source_map)), infer: None, file_id }
@@ -579,15 +585,14 @@ fn scope_for_offset(
     db: &dyn HirDatabase,
     scopes: &ExprScopes,
     source_map: &BodySourceMap,
-    from_file: HirFileId,
-    offset: TextSize,
+    offset: InFile<TextSize>,
 ) -> Option<ScopeId> {
     scopes
         .scope_by_expr()
         .iter()
         .filter_map(|(id, scope)| {
             let InFile { file_id, value } = source_map.expr_syntax(*id).ok()?;
-            if from_file == file_id {
+            if offset.file_id == file_id {
                 let root = db.parse_or_expand(file_id)?;
                 let node = value.to_node(&root);
                 return Some((node.syntax().text_range(), scope));
@@ -597,15 +602,17 @@ fn scope_for_offset(
             let source = iter::successors(file_id.call_node(db.upcast()), |it| {
                 it.file_id.call_node(db.upcast())
             })
-            .find(|it| it.file_id == from_file)
+            .find(|it| it.file_id == offset.file_id)
             .filter(|it| it.value.kind() == SyntaxKind::MACRO_CALL)?;
             Some((source.value.text_range(), scope))
         })
-        .filter(|(expr_range, _scope)| expr_range.start() <= offset && offset <= expr_range.end())
+        .filter(|(expr_range, _scope)| {
+            expr_range.start() <= offset.value && offset.value <= expr_range.end()
+        })
         // find containing scope
         .min_by_key(|(expr_range, _scope)| expr_range.len())
         .map(|(expr_range, scope)| {
-            adjust(db, scopes, source_map, expr_range, from_file, offset).unwrap_or(*scope)
+            adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
         })
 }
 
@@ -616,8 +623,7 @@ fn adjust(
     scopes: &ExprScopes,
     source_map: &BodySourceMap,
     expr_range: TextRange,
-    from_file: HirFileId,
-    offset: TextSize,
+    offset: InFile<TextSize>,
 ) -> Option<ScopeId> {
     let child_scopes = scopes
         .scope_by_expr()
@@ -625,7 +631,7 @@ fn adjust(
         .filter_map(|(id, scope)| {
             let source = source_map.expr_syntax(*id).ok()?;
             // FIXME: correctly handle macro expansion
-            if source.file_id != from_file {
+            if source.file_id != offset.file_id {
                 return None;
             }
             let root = source.file_syntax(db.upcast());
@@ -633,7 +639,7 @@ fn adjust(
             Some((node.syntax().text_range(), scope))
         })
         .filter(|&(range, _)| {
-            range.start() <= offset && expr_range.contains_range(range) && range != expr_range
+            range.start() <= offset.value && expr_range.contains_range(range) && range != expr_range
         });
 
     child_scopes
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index 2de33a7362..154c096f4d 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -330,9 +330,9 @@ const ATTRIBUTES: &[AttrCompletion] = &[
     attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
     attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
     attr(
-        r#"target_feature(enable = "…")"#,
+        r#"target_feature = "…""#,
         Some("target_feature"),
-        Some(r#"target_feature(enable = "${0:feature}")"#),
+        Some(r#"target_feature = "${0:feature}""#),
     ),
     attr("test", None, None),
     attr("track_caller", None, None),
diff --git a/crates/ide-completion/src/completions/attribute/cfg.rs b/crates/ide-completion/src/completions/attribute/cfg.rs
index b0a9fc8d43..b7e69d7b43 100644
--- a/crates/ide-completion/src/completions/attribute/cfg.rs
+++ b/crates/ide-completion/src/completions/attribute/cfg.rs
@@ -3,7 +3,6 @@
 use std::iter;
 
 use ide_db::SymbolKind;
-use itertools::Itertools;
 use syntax::SyntaxKind;
 
 use crate::{completions::Completions, context::CompletionContext, CompletionItem};
@@ -35,7 +34,7 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext) {
 
             acc.add(item.build());
         }),
-        None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| {
+        None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().for_each(|s| {
             let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
             acc.add(item.build());
         }),
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index b371c97880..01a2f96fd1 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -506,11 +506,7 @@ impl<'a> CompletionContext<'a> {
 
         let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
         let token = sema.descend_into_macros_single(original_token.clone());
-
-        // adjust for macro input, this still fails if there is no token written yet
-        let scope_offset = if original_token == token { offset } else { token.text_range().end() };
-        let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?;
-
+        let scope = sema.scope_at_offset(&token.parent()?, offset)?;
         let krate = scope.krate();
         let module = scope.module();
 
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
index fde3901a73..5ebfe33dab 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
@@ -32,13 +32,13 @@ pub(crate) fn unresolved_proc_macro(
         None => "proc macro not expanded".to_string(),
     };
     let severity = if config_enabled { Severity::Error } else { Severity::WeakWarning };
-    let def_map = ctx.sema.db.crate_def_map(d.krate);
+    let def_map = d.krate.map(|krate| ctx.sema.db.crate_def_map(krate));
     let message = format!(
         "{message}: {}",
         if config_enabled {
-            match def_map.proc_macro_loading_error() {
+            match def_map.as_ref().and_then(|def_map| def_map.proc_macro_loading_error()) {
                 Some(e) => e,
-                None => "proc macro not found in the built dylib",
+                None => "proc macro not found",
             }
         } else {
             match d.kind {
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index de2db0e1da..18c9c2c810 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -873,7 +873,7 @@ fn add_target_crate_root(
     let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) {
         Some(Some(it)) => load_proc_macro(it),
         Some(None) => Err("no proc macro dylib present".into()),
-        None => Err("crate has not (yet) been built".into()),
+        None => Err("no build data".into()),
     };
 
     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
@@ -929,7 +929,7 @@ fn sysroot_to_crate_graph(
                 cfg_options.clone(),
                 cfg_options.clone(),
                 env,
-                Err("no proc macro loaded for sysroot crate".into()),
+                Ok(Vec::new()),
                 false,
                 CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
             );
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 13bcb7dfa2..3f129efd95 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -540,18 +540,17 @@ pub(crate) fn load_proc_macro(
     path: &AbsPath,
     dummy_replace: &[Box<str>],
 ) -> ProcMacroLoadResult {
-    let res: Result<Vec<_>, String> = (|| {
+    let res: Result<_, String> = (|| {
         let dylib = MacroDylib::new(path.to_path_buf())
             .map_err(|io| format!("Proc-macro dylib loading failed: {io}"))?;
-        let server = server.ok_or_else(|| format!("Proc-macro server not started"))?;
-        let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
-        if vec.is_empty() {
-            return Err("proc macro library returned no proc macros".to_string());
-        }
-        Ok(vec
-            .into_iter()
-            .map(|expander| expander_to_proc_macro(expander, dummy_replace))
-            .collect())
+        Ok(if let Some(it) = server {
+            let vec = it.load_dylib(dylib).map_err(|e| format!("{e}"))?;
+            vec.into_iter()
+                .map(|expander| expander_to_proc_macro(expander, dummy_replace))
+                .collect()
+        } else {
+            Vec::new()
+        })
     })();
     return match res {
         Ok(proc_macros) => {
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 20d3a304f8..86dad6bb37 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -23,6 +23,7 @@
                 "esbuild": "^0.14.27",
                 "eslint": "^8.11.0",
                 "eslint-config-prettier": "^8.5.0",
+                "ovsx": "^0.5.1",
                 "prettier": "^2.6.2",
                 "tslib": "^2.3.0",
                 "typescript": "^4.6.3",
@@ -705,6 +706,12 @@
             "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
             "dev": true
         },
+        "node_modules/ci-info": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+            "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+            "dev": true
+        },
         "node_modules/cliui": {
             "version": "7.0.4",
             "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -2210,6 +2217,26 @@
             "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
             "dev": true
         },
+        "node_modules/follow-redirects": {
+            "version": "1.15.1",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+            "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://github.com/sponsors/RubenVerborgh"
+                }
+            ],
+            "engines": {
+                "node": ">=4.0"
+            },
+            "peerDependenciesMeta": {
+                "debug": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/fs-constants": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -2591,6 +2618,18 @@
                 "node": ">=12"
             }
         },
+        "node_modules/is-ci": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+            "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+            "dev": true,
+            "dependencies": {
+                "ci-info": "^2.0.0"
+            },
+            "bin": {
+                "is-ci": "bin.js"
+            }
+        },
         "node_modules/is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2965,6 +3004,35 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/ovsx": {
+            "version": "0.5.1",
+            "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.5.1.tgz",
+            "integrity": "sha512-3OWq0l7DuVHi2bd2aQe5+QVQlFIqvrcw3/2vGXL404L6Tr+R4QHtzfnYYghv8CCa85xJHjU0RhcaC7pyXkAUbg==",
+            "dev": true,
+            "dependencies": {
+                "commander": "^6.1.0",
+                "follow-redirects": "^1.14.6",
+                "is-ci": "^2.0.0",
+                "leven": "^3.1.0",
+                "tmp": "^0.2.1",
+                "vsce": "^2.6.3"
+            },
+            "bin": {
+                "ovsx": "lib/ovsx"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/ovsx/node_modules/commander": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+            "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+            "dev": true,
+            "engines": {
+                "node": ">= 6"
+            }
+        },
         "node_modules/parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -4553,6 +4621,12 @@
             "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
             "dev": true
         },
+        "ci-info": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+            "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+            "dev": true
+        },
         "cliui": {
             "version": "7.0.4",
             "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -5603,6 +5677,12 @@
             "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
             "dev": true
         },
+        "follow-redirects": {
+            "version": "1.15.1",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
+            "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
+            "dev": true
+        },
         "fs-constants": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -5889,6 +5969,15 @@
             "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
             "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="
         },
+        "is-ci": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+            "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+            "dev": true,
+            "requires": {
+                "ci-info": "^2.0.0"
+            }
+        },
         "is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6192,6 +6281,28 @@
                 "word-wrap": "^1.2.3"
             }
         },
+        "ovsx": {
+            "version": "0.5.1",
+            "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.5.1.tgz",
+            "integrity": "sha512-3OWq0l7DuVHi2bd2aQe5+QVQlFIqvrcw3/2vGXL404L6Tr+R4QHtzfnYYghv8CCa85xJHjU0RhcaC7pyXkAUbg==",
+            "dev": true,
+            "requires": {
+                "commander": "^6.1.0",
+                "follow-redirects": "^1.14.6",
+                "is-ci": "^2.0.0",
+                "leven": "^3.1.0",
+                "tmp": "^0.2.1",
+                "vsce": "^2.6.3"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "6.2.1",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
+                    "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
+                    "dev": true
+                }
+            }
+        },
         "parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
diff --git a/editors/code/package.json b/editors/code/package.json
index a40ca6e2df..ac7dcfac64 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -49,6 +49,7 @@
         "esbuild": "^0.14.27",
         "eslint": "^8.11.0",
         "eslint-config-prettier": "^8.5.0",
+        "ovsx": "^0.5.1",
         "prettier": "^2.6.2",
         "tslib": "^2.3.0",
         "typescript": "^4.6.3",


Test patch:
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 3b366c94bf..52c69f84b6 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -568,7 +568,7 @@ fn attr_on_fn() {
             at proc_macro_attribute
             at proc_macro_derive(…)
             at should_panic
-            at target_feature(enable = "…")
+            at target_feature = "…"
             at test
             at track_caller
             at warn(…)
@@ -615,7 +615,7 @@ fn attr_in_source_file_end() {
             at proc_macro_derive(…)
             at repr(…)
             at should_panic
-            at target_feature(enable = "…")
+            at target_feature = "…"
             at test
             at track_caller
             at used
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 39cb41485b..f1557107e0 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -782,31 +782,3 @@ fn main() {
         "#]],
     )
 }
-
-#[test]
-fn completes_locals_from_macros() {
-    check(
-        r#"
-
-macro_rules! x {
-    ($x:ident, $expr:expr) => {
-        let $x = 0;
-        $expr
-    };
-}
-fn main() {
-    x! {
-        foobar, {
-            f$0
-        }
-    };
-}
-"#,
-        expect![[r#"
-            fn main() fn()
-            lc foobar i32
-            ma x!(…)  macro_rules! x
-            bt u32
-        "#]],
-    )
-}
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index ddfea0ce4c..2e8e93c926 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -173,7 +173,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -248,7 +248,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -313,7 +313,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                         },
                         dependencies: [],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: Some(
@@ -390,7 +390,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -465,7 +465,7 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -555,7 +555,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -632,7 +632,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -697,7 +697,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                         },
                         dependencies: [],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: Some(
@@ -776,7 +776,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -853,7 +853,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -934,7 +934,7 @@ fn cargo_hello_world_project_model() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -1011,7 +1011,7 @@ fn cargo_hello_world_project_model() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -1076,7 +1076,7 @@ fn cargo_hello_world_project_model() {
                         },
                         dependencies: [],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: Some(
@@ -1155,7 +1155,7 @@ fn cargo_hello_world_project_model() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -1232,7 +1232,7 @@ fn cargo_hello_world_project_model() {
                             },
                         ],
                         proc_macro: Err(
-                            "crate has not (yet) been built",
+                            "no build data",
                         ),
                         origin: CratesIo {
                             repo: None,
@@ -1288,8 +1288,8 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Alloc,
@@ -1322,8 +1322,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1356,8 +1356,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1400,8 +1400,8 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1434,8 +1434,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Core,
@@ -1539,8 +1539,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1573,8 +1573,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1607,8 +1607,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,
@@ -1641,8 +1641,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Test,
@@ -1757,8 +1757,8 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Std,
@@ -1791,8 +1791,8 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
+                        proc_macro: Ok(
+                            [],
                         ),
                         origin: Lang(
                             Other,