diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs
index a538fb0385c45..c9a0a881e7b99 100644
--- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs
+++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs
@@ -642,7 +642,8 @@ class C
""",
testHost,
Namespace("System"),
- Class("Obsolete"));
+ Class("Obsolete"),
+ Obsolete("C"));
}
[Theory, CombinatorialData]
diff --git a/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs b/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs
new file mode 100644
index 0000000000000..52499ead36c21
--- /dev/null
+++ b/src/EditorFeatures/CSharpTest/ObsoleteSymbol/CSharpObsoleteSymbolTests.cs
@@ -0,0 +1,200 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Editor.UnitTests.ObsoleteSymbol;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ObsoleteSymbol;
+
+public class CSharpObsoleteSymbolTests : AbstractObsoleteSymbolTests
+{
+ protected override EditorTestWorkspace CreateWorkspace(string markup)
+ => EditorTestWorkspace.CreateCSharp(markup);
+
+ [Theory]
+ [InlineData("class")]
+ [InlineData("struct")]
+ [InlineData("record")]
+ [InlineData("record class")]
+ [InlineData("record struct")]
+ [InlineData("interface")]
+ [InlineData("enum")]
+ public async Task TestObsoleteTypeDefinition(string keyword)
+ {
+ await TestAsync(
+ $$"""
+ [System.Obsolete]
+ {{keyword}} [|ObsoleteType|]
+ {
+ }
+
+ {{keyword}} NonObsoleteType
+ {
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestObsoleteDelegateTypeDefinition()
+ {
+ await TestAsync(
+ """
+ [System.Obsolete]
+ delegate void [|ObsoleteType|]();
+
+ delegate void NonObsoleteType();
+ """);
+ }
+
+ [Fact]
+ public async Task TestDeclarationAndUseOfObsoleteAlias()
+ {
+ await TestAsync(
+ """
+ using [|ObsoleteAlias|] = [|ObsoleteType|];
+
+ [System.Obsolete]
+ class [|ObsoleteType|];
+
+ ///
+ ///
+ class NonObsoleteType
+ {
+ [|ObsoleteAlias|] field = new [|ObsoleteType|]();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestParametersAndReturnTypes()
+ {
+ await TestAsync(
+ """
+ [System.Obsolete]
+ class [|ObsoleteType|];
+
+ class NonObsoleteType([|ObsoleteType|] field2)
+ {
+ [|ObsoleteType|] Method([|ObsoleteType|] arg) => [|new|]();
+
+ System.Func<[|ObsoleteType|], [|ObsoleteType|]> field = [|ObsoleteType|] ([|ObsoleteType|] arg) => [|new|]();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestImplicitType()
+ {
+ await TestAsync(
+ """
+ [System.Obsolete]
+ class [|ObsoleteType|]
+ {
+ public ObsoleteType() { }
+
+ [System.Obsolete]
+ public [|ObsoleteType|](int x) { }
+ }
+
+ class ObsoleteCtor
+ {
+ public ObsoleteCtor() { }
+
+ [System.Obsolete]
+ public [|ObsoleteCtor|](int x) { }
+ }
+
+ class C
+ {
+ void Method()
+ {
+ [|var|] t1 = new [|ObsoleteType|]();
+ [|var|] t2 = [|new|] [|ObsoleteType|](3);
+ [|ObsoleteType|] t3 = [|new|]();
+ [|ObsoleteType|] t4 = [|new|](3);
+ [|var|] t5 = CreateObsoleteType();
+ var t6 = nameof([|ObsoleteType|]);
+
+ var u1 = new ObsoleteCtor();
+ var u2 = [|new|] ObsoleteCtor(3);
+ ObsoleteCtor u3 = new();
+ ObsoleteCtor u4 = [|new|](3);
+ var u6 = nameof(ObsoleteCtor);
+
+ [|ObsoleteType|] CreateObsoleteType() => [|new|]();
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestExtensionMethods()
+ {
+ await TestAsync(
+ """
+ [System.Obsolete]
+ static class [|ObsoleteType|]
+ {
+ public static void ObsoleteMember1(this C ignored) { }
+
+ [System.Obsolete]
+ public static void [|ObsoleteMember2|](this C ignored) { }
+ }
+
+ class C
+ {
+ void Method()
+ {
+ this.ObsoleteMember1();
+ this.[|ObsoleteMember2|]();
+ [|ObsoleteType|].ObsoleteMember1(this);
+ [|ObsoleteType|].[|ObsoleteMember2|](this);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestGenerics()
+ {
+ await TestAsync(
+ """
+ [System.Obsolete]
+ class [|ObsoleteType|];
+
+ [System.Obsolete]
+ struct [|ObsoleteValueType|];
+
+ class G
+ {
+ }
+
+ class C
+ {
+ void M() { }
+
+ ///
+ /// This looks like a reference to an obsolete type, but it's actually just an identifier alias for the
+ /// generic type parameter 'T'.
+ ///
+ ///
+ void Method()
+ {
+ _ = new G<[|ObsoleteType|]>();
+ _ = new G>();
+ M<[|ObsoleteType|]>();
+ M>();
+ M>>();
+
+ // Mark 'var' as obsolete even when it points to Nullable where T is obsolete
+ [|var|] nullableValue = CreateNullableValueType();
+
+ [|ObsoleteValueType|]? CreateNullableValueType() => new [|ObsoleteValueType|]();
+ }
+ }
+ """);
+ }
+}
diff --git a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs
index 9775e704ed1cf..654802b6f5c00 100644
--- a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs
+++ b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs
@@ -146,6 +146,25 @@ public ReassignedVariableFormatDefinition()
}
#endregion
+ #region Obsolete Symobl
+ [Export(typeof(EditorFormatDefinition))]
+ [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.ObsoleteSymbol)]
+ [Name(ClassificationTypeNames.ObsoleteSymbol)]
+ [Order(After = Priority.High)]
+ [UserVisible(false)]
+ [ExcludeFromCodeCoverage]
+ private class ObsoleteSymbolFormatDefinition : ClassificationFormatDefinition
+ {
+ [ImportingConstructor]
+ [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+ public ObsoleteSymbolFormatDefinition()
+ {
+ this.DisplayName = EditorFeaturesResources.Obsolete_symbol;
+ this.TextDecorations = System.Windows.TextDecorations.Strikethrough;
+ }
+ }
+ #endregion
+
#region Symbol - Static
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ClassificationTypeNames.StaticSymbol)]
diff --git a/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs b/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs
index f7829ff3920bf..b3e7160423526 100644
--- a/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs
+++ b/src/EditorFeatures/Core/Classification/ClassificationTypeDefinitions.cs
@@ -423,6 +423,13 @@ internal sealed class ClassificationTypeDefinitions
internal readonly ClassificationTypeDefinition ReassignedVariableTypeDefinition;
#endregion
+ #region Obsolete Symbol
+ [Export]
+ [Name(ClassificationTypeNames.ObsoleteSymbol)]
+ [BaseDefinition(PredefinedClassificationTypeNames.FormalLanguage)]
+ internal readonly ClassificationTypeDefinition ObsoleteSymbolTypeDefinition;
+ #endregion
+
#region Static Symbol
[Export]
[Name(ClassificationTypeNames.StaticSymbol)]
diff --git a/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs
index 88b1ff86d4d79..275ae3569a345 100644
--- a/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs
+++ b/src/EditorFeatures/Core/Classification/Semantic/AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs
@@ -63,7 +63,8 @@ protected sealed override ITaggerEventSource CreateEventSource(ITextView textVie
TaggerEventSources.OnViewSpanChanged(ThreadingContext, textView),
TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener),
TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer),
- TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyReassignedVariables));
+ TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyReassignedVariables),
+ TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyObsoleteSymbols));
}
protected sealed override Task ProduceTagsAsync(
diff --git a/src/EditorFeatures/Core/EditorFeaturesResources.resx b/src/EditorFeatures/Core/EditorFeaturesResources.resx
index 6b30388240e00..4ac3a6e33e98d 100644
--- a/src/EditorFeatures/Core/EditorFeaturesResources.resx
+++ b/src/EditorFeatures/Core/EditorFeaturesResources.resx
@@ -923,4 +923,7 @@ Do you want to proceed?
Roslyn Test Code Markup
+
+ Obsolete symbol
+
\ No newline at end of file
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf
index 296a70007ea64..056b55a7c22c6 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf
@@ -202,6 +202,11 @@
Ne
+
+
+ Obsolete symbol
+
+
Operátor – přetížení
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf
index 826dbc3dc0ec4..8d448ee1d8f3a 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf
@@ -202,6 +202,11 @@
Nein
+
+
+ Obsolete symbol
+
+
Operator - überladen
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf
index dd538dd8a5c67..077b691c264a0 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf
@@ -202,6 +202,11 @@
No
+
+
+ Obsolete symbol
+
+
Operador: sobrecargado
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf
index e7a49dbd61378..d636cc43f8042 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf
@@ -202,6 +202,11 @@
Non
+
+
+ Obsolete symbol
+
+
Opérateur - surchargé
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf
index f1fd95dd838d6..46de62c4f81ad 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf
@@ -202,6 +202,11 @@
No
+
+
+ Obsolete symbol
+
+
Operatore - Overload
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf
index 972db8cfab01a..400ab0cfb78f9 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf
@@ -202,6 +202,11 @@
いいえ
+
+
+ Obsolete symbol
+
+
演算子 - オーバーロード
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf
index 205f935e256fa..ade34d7bbd3fe 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf
@@ -202,6 +202,11 @@
아니요
+
+
+ Obsolete symbol
+
+
연산자 - 오버로드됨
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf
index 44844805ab126..375dbb079ce43 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf
@@ -202,6 +202,11 @@
Nie
+
+
+ Obsolete symbol
+
+
Operator — przeciążony
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf
index d4abbbcf97c5f..be6bca5e72436 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf
@@ -202,6 +202,11 @@
Não
+
+
+ Obsolete symbol
+
+
Operador – Sobrecarregado
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf
index e16baca18ecbe..49cfb8f71c190 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf
@@ -202,6 +202,11 @@
Нет
+
+
+ Obsolete symbol
+
+
Оператор — перегружен
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf
index da6365927fcd9..0d9ceef26fee7 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf
@@ -202,6 +202,11 @@
Hayır
+
+
+ Obsolete symbol
+
+
İşleç - Aşırı Yüklenmiş
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf
index 877b5031ed46b..4d094fa12f88b 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf
@@ -202,6 +202,11 @@
否
+
+
+ Obsolete symbol
+
+
运算符-重载
diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf
index 94cf68abd76d5..9e5cb84ddc787 100644
--- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf
+++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf
@@ -202,6 +202,11 @@
否
+
+
+ Obsolete symbol
+
+
運算子 - 多載
diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs
index b962707454438..717b35ad533ba 100644
--- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs
+++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs
@@ -90,6 +90,10 @@ public static FormattedClassification Property(string text)
public static FormattedClassification Event(string text)
=> New(text, ClassificationTypeNames.EventName);
+ [DebuggerStepThrough]
+ public static FormattedClassification Obsolete(string text)
+ => New(text, ClassificationTypeNames.ObsoleteSymbol);
+
[DebuggerStepThrough]
public static FormattedClassification Static(string text)
=> New(text, ClassificationTypeNames.StaticSymbol);
diff --git a/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.cs b/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.cs
new file mode 100644
index 0000000000000..05057c372ad75
--- /dev/null
+++ b/src/EditorFeatures/TestUtilities/ObsoleteSymbol/AbstractObsoleteSymbolTests.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.ObsoleteSymbol;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Test.Utilities;
+
+namespace Microsoft.CodeAnalysis.Editor.UnitTests.ObsoleteSymbol;
+
+[UseExportProvider]
+public abstract class AbstractObsoleteSymbolTests
+{
+ protected abstract EditorTestWorkspace CreateWorkspace(string markup);
+
+ protected async Task TestAsync(string markup)
+ {
+ using var workspace = CreateWorkspace(markup);
+
+ var project = workspace.CurrentSolution.Projects.Single();
+ var language = project.Language;
+ var documents = project.Documents.ToImmutableArray();
+
+ for (var i = 0; i < documents.Length; i++)
+ {
+ var document = documents[i];
+ var text = await document.GetTextAsync();
+
+ var service = document.GetRequiredLanguageService();
+ var textSpans = ImmutableArray.Create(new TextSpan(0, text.Length));
+ var result = await service.GetLocationsAsync(document, textSpans, CancellationToken.None);
+
+ var expectedSpans = workspace.Documents[i].SelectedSpans.OrderBy(s => s.Start);
+ var actualSpans = result.OrderBy(s => s.Start);
+
+ AssertEx.EqualOrDiff(
+ string.Join(Environment.NewLine, expectedSpans),
+ string.Join(Environment.NewLine, actualSpans));
+ }
+ }
+}
diff --git a/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb b/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb
new file mode 100644
index 0000000000000..fd6fee9bb0372
--- /dev/null
+++ b/src/EditorFeatures/VisualBasicTest/ObsoleteSymbol/VisualBasicObsoleteSymbolTests.vb
@@ -0,0 +1,218 @@
+' Licensed to the .NET Foundation under one or more agreements.
+' The .NET Foundation licenses this file to you under the MIT license.
+' See the LICENSE file in the project root for more information.
+
+Imports Microsoft.CodeAnalysis.Editor.UnitTests.ObsoleteSymbol
+
+Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ObsoleteSymbol
+ Public Class VisualBasicObsoleteSymbolTests
+ Inherits AbstractObsoleteSymbolTests
+
+ Protected Overrides Function CreateWorkspace(markup As String) As EditorTestWorkspace
+ Return EditorTestWorkspace.CreateVisualBasic(markup)
+ End Function
+
+
+
+
+
+
+
+ Public Async Function TestObsoleteTypeDefinition(keyword As String) As Task
+ Await TestAsync(
+ $"
+
+ {keyword} [|ObsoleteType|]
+ End {keyword}
+
+ {keyword} NonObsoleteType
+ End {keyword}
+ ")
+ End Function
+
+
+ Public Async Function TestObsoleteDelegateTypeDefinition() As Task
+ Await TestAsync(
+ "
+
+ Delegate Sub [|ObsoleteType|]()
+
+ Delegate Sub NonObsoleteType()
+ ")
+ End Function
+
+
+ Public Async Function TestDeclarationAndUseOfObsoleteAlias() As Task
+ Await TestAsync(
+ "
+ Imports [|ObsoleteAlias|] = [|ObsoleteType|]
+
+
+ Class [|ObsoleteType|]
+ End Class
+
+ '''
+ '''
+ Class NonObsoleteType
+ Dim field As [|ObsoleteAlias|] = New [|ObsoleteType|]()
+ End Class
+ ")
+ End Function
+
+
+ Public Async Function TestParametersAndReturnTypes() As Task
+ Await TestAsync(
+ "
+
+ Class [|ObsoleteType|]
+ End Class
+
+ Class NonObsoleteType
+ Function Method(arg As [|ObsoleteType|]) As [|ObsoleteType|]
+ Return New [|ObsoleteType|]()
+ End Function
+
+ Dim field As System.Func(Of [|ObsoleteType|], [|ObsoleteType|]) = Function(arg As [|ObsoleteType|]) New [|ObsoleteType|]()
+ End Class
+ ")
+ End Function
+
+
+ Public Async Function TestImplicitType() As Task
+ Await TestAsync(
+ "
+
+ Class [|ObsoleteType|]
+ Public Sub New()
+ End Sub
+
+
+ Public Sub New(x As Integer)
+ End Sub
+ End Class
+
+ Class ObsoleteCtor
+ Public Sub New()
+ End Sub
+
+
+ Public Sub New(x As Integer)
+ End Sub
+ End Class
+
+ Class NonObsoleteType
+ Sub Method()
+ Dim t1 As New [|ObsoleteType|]()
+ Dim t2 As [|New|] [|ObsoleteType|](3)
+ [|Dim|] t3 = New [|ObsoleteType|]()
+ [|Dim|] t4 = [|New|] [|ObsoleteType|](3)
+ Dim t5 As [|ObsoleteType|] = New [|ObsoleteType|]()
+ Dim t6 As [|ObsoleteType|] = [|New|] [|ObsoleteType|](3)
+ [|Dim|] t7 = CreateObsoleteType()
+ Dim t8 = NameOf([|ObsoleteType|])
+
+ Dim u1 As New ObsoleteCtor()
+ Dim u2 As [|New|] ObsoleteCtor(3)
+ Dim u3 = New ObsoleteCtor()
+ Dim u4 = [|New|] ObsoleteCtor(3)
+ Dim u5 As ObsoleteCtor = New ObsoleteCtor()
+ Dim u6 As ObsoleteCtor = [|New|] ObsoleteCtor(3)
+ Dim u8 = NameOf(ObsoleteCtor)
+ End Sub
+
+ Function CreateObsoleteType() As [|ObsoleteType|]
+ Return New [|ObsoleteType|]()
+ End Function
+ End Class
+ ")
+ End Function
+
+
+ Public Async Function TestDeclarators() As Task
+ Await TestAsync(
+ "
+
+ Class [|ObsoleteType|]
+ End Class
+
+ Class NonObsoleteType
+ Sub Method()
+ ' In this method, only t5 has an implicit type, but the Dim keyword applies to all declared
+ ' variables. Currently this feature does not analyze a Dim keyword when more than one variable
+ ' is declared.
+ Dim t1, t2 As New [|ObsoleteType|](), t3, t4 As [|ObsoleteType|], t5 = New [|ObsoleteType|]()
+ End Sub
+ End Class
+ ")
+ End Function
+
+
+ Public Async Function TestExtensionMethods() As Task
+ Await TestAsync(
+ "
+
+ Module [|ObsoleteType|]
+
+ Public Shared Sub ObsoleteMember1(ignored As C)
+ End Sub
+
+
+
+ Public Shared Sub [|ObsoleteMember2|](ignored As C)
+ End Sub
+ End Module
+
+ Class C
+ Sub Method()
+ Me.ObsoleteMember1()
+ Me.[|ObsoleteMember2|]()
+ [|ObsoleteType|].ObsoleteMember1(Me)
+ [|ObsoleteType|].[|ObsoleteMember2|](Me)
+ End Sub
+ End Class
+ ")
+ End Function
+
+
+ Public Async Function TestGenerics() As Task
+ Await TestAsync(
+ "
+
+ Class [|ObsoleteType|]
+ End Class
+
+
+ Structure [|ObsoleteValueType|]
+ End Structure
+
+ Class G(Of T)
+ End Class
+
+ Class C
+ Sub M(Of T)()
+ End Sub
+
+ '''
+ ''' Visual Basic, unlike C#, resolves concrete type names in generic argument positions in doc
+ ''' comment references.
+ '''
+ '''
+ Sub Method()
+ Dim x1 = New G(Of [|ObsoleteType|])()
+ Dim x2 = New G(Of G(Of [|ObsoleteType|]))()
+ M(Of [|ObsoleteType|])()
+ M(Of G(Of [|ObsoleteType|]))()
+ M(Of G(Of G(Of [|ObsoleteType|])))()
+
+ ' Mark 'Dim' as obsolete even when it points to Nullable(Of T) where T is obsolete
+ [|Dim|] nullableValue = CreateNullableValueType()
+ End Sub
+
+ Function CreateNullableValueType() As [|ObsoleteValueType|]?
+ Return New [|ObsoleteValueType|]()
+ End Function
+ End Class
+ ")
+ End Function
+ End Class
+End Namespace
diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
index 1f3c651245e43..0990ee2947f07 100644
--- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
+++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs
@@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
+using Microsoft.CodeAnalysis.Tags;
namespace Microsoft.CodeAnalysis.Completion.Providers;
@@ -56,6 +57,10 @@ private static CompletionItem CreateWorker(
AddSupportedPlatforms(builder, supportedPlatforms);
symbolEncoder(symbols, builder);
+ tags = tags.NullToEmpty();
+ if (symbols.All(symbol => symbol.IsObsolete()) && !tags.Contains(WellKnownTags.Deprecated))
+ tags = tags.Add(WellKnownTags.Deprecated);
+
var firstSymbol = symbols[0];
var item = CommonCompletionItem.Create(
displayText: displayText,
diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
index 3a9f045e2e595..c6a06c30f422c 100644
--- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
+++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
@@ -95,6 +95,17 @@ internal static partial class ProtocolConversions
{ WellKnownTags.NuGet, ImmutableArray.Create(LSP.CompletionItemKind.Text) }
}.ToImmutableDictionary();
+ ///
+ /// Mapping from tags to LSP completion item tags. The value lists the potential LSP tags from
+ /// least-preferred to most preferred. More preferred kinds will be chosen if the client states they support
+ /// it. This mapping allows values including extensions to the kinds defined by VS (but not in the core LSP
+ /// spec).
+ ///
+ public static readonly ImmutableDictionary> RoslynTagToCompletionItemTags = new Dictionary>()
+ {
+ { WellKnownTags.Deprecated, ImmutableArray.Create(LSP.CompletionItemTag.Deprecated) },
+ }.ToImmutableDictionary();
+
// TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds.
// https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726
public static async Task LSPToRoslynCompletionTriggerAsync(
diff --git a/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs
index 87d9faa3241fd..194765e18320e 100644
--- a/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs
+++ b/src/Features/LanguageServer/Protocol/Features/Options/ClassificationOptionsStorage.cs
@@ -12,6 +12,7 @@ public static ClassificationOptions GetClassificationOptions(this IOptionsReader
=> new()
{
ClassifyReassignedVariables = globalOptions.GetOption(ClassifyReassignedVariables, language),
+ ClassifyObsoleteSymbols = globalOptions.GetOption(ClassifyObsoleteSymbols, language),
ColorizeRegexPatterns = globalOptions.GetOption(ColorizeRegexPatterns, language),
ColorizeJsonPatterns = globalOptions.GetOption(ColorizeJsonPatterns, language),
// ForceFrozenPartialSemanticsForCrossProcessOperations not stored in global options
@@ -23,6 +24,9 @@ public static OptionsProvider GetClassificationOptionsPro
public static PerLanguageOption2 ClassifyReassignedVariables =
new("dotnet_classify_reassigned_variables", ClassificationOptions.Default.ClassifyReassignedVariables);
+ public static PerLanguageOption2 ClassifyObsoleteSymbols =
+ new("dotnet_classify_obsolete_symbols", ClassificationOptions.Default.ClassifyObsoleteSymbols);
+
public static PerLanguageOption2 ColorizeRegexPatterns =
new("dotnet_colorize_regex_patterns", ClassificationOptions.Default.ColorizeRegexPatterns);
diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs
index 8cdfd83df2e1f..7f3ceaa0a5795 100644
--- a/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/Completion/AbstractLspCompletionResultCreationService.cs
@@ -15,6 +15,7 @@
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
@@ -121,6 +122,7 @@ internal abstract class AbstractLspCompletionResultCreationService : ILspComplet
lspItem.FilterText = item.FilterText;
lspItem.Kind = GetCompletionKind(item.Tags, capabilityHelper.SupportedItemKinds);
+ lspItem.Tags = GetCompletionTags(item.Tags, capabilityHelper.SupportedItemTags);
lspItem.Preselect = item.Rules.MatchPriority == MatchPriority.Preselect;
if (lspVSClientCapability)
@@ -179,6 +181,37 @@ static LSP.CompletionItemKind GetCompletionKind(
return LSP.CompletionItemKind.Text;
}
+ static LSP.CompletionItemTag[]? GetCompletionTags(
+ ImmutableArray tags,
+ ISet supportedClientTags)
+ {
+ using var result = TemporaryArray.Empty;
+
+ foreach (var tag in tags)
+ {
+ if (ProtocolConversions.RoslynTagToCompletionItemTags.TryGetValue(tag, out var completionItemTags))
+ {
+ // Always at least pick the core tag provided.
+ var lspTag = completionItemTags[0];
+
+ // If better kinds are preferred, return them if the client supports them.
+ for (var i = 1; i < completionItemTags.Length; i++)
+ {
+ var preferredTag = completionItemTags[i];
+ if (supportedClientTags.Contains(preferredTag))
+ lspTag = preferredTag;
+ }
+
+ result.Add(lspTag);
+ }
+ }
+
+ if (result.Count == 0)
+ return null;
+
+ return [.. result.ToImmutableAndClear()];
+ }
+
static string[] GetCommitCharacters(
CompletionItem item,
Dictionary, string[]> currentRuleCache)
diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs
index bd565c39807ac..7bf5905039f3d 100644
--- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionCapabilityHelper.cs
@@ -30,6 +30,7 @@ internal sealed class CompletionCapabilityHelper
public bool SupportSnippets { get; }
public bool SupportsMarkdownDocumentation { get; }
public ISet SupportedItemKinds { get; }
+ public ISet SupportedItemTags { get; }
public CompletionCapabilityHelper(ClientCapabilities clientCapabilities)
{
@@ -42,6 +43,7 @@ public CompletionCapabilityHelper(ClientCapabilities clientCapabilities)
SupportCompletionListData = _completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(DataPropertyName) == true;
SupportDefaultCommitCharacters = _completionSetting?.CompletionListSetting?.ItemDefaults?.Contains(CommitCharactersPropertyName) == true;
SupportedItemKinds = _completionSetting?.CompletionItemKind?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet();
+ SupportedItemTags = _completionSetting?.CompletionItem?.TagSupport?.ValueSet?.ToSet() ?? SpecializedCollections.EmptySet();
// internal VS LSP
if (clientCapabilities.HasVisualStudioLspCapability())
diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
index a3d07daa66148..428806dc57f70 100644
--- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
@@ -320,16 +320,21 @@ private static int ComputeNextToken(
if (classificationType == ClassificationTypeNames.StaticSymbol)
{
// 4. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers
- modifierBits = TokenModifiers.Static;
+ modifierBits |= TokenModifiers.Static;
}
else if (classificationType == ClassificationTypeNames.ReassignedVariable)
{
// 5. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers
- modifierBits = TokenModifiers.ReassignedVariable;
+ modifierBits |= TokenModifiers.ReassignedVariable;
+ }
+ else if (classificationType == ClassificationTypeNames.ObsoleteSymbol)
+ {
+ // 6. Token modifiers - each set bit will be looked up in SemanticTokensLegend.tokenModifiers
+ modifierBits |= TokenModifiers.Deprecated;
}
else
{
- // 6. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping
+ // 7. Token type - looked up in SemanticTokensLegend.tokenTypes (language server defined mapping
// from integer to LSP token types).
tokenTypeIndex = GetTokenTypeIndex(classificationType);
}
diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs
index 94cdb33c0955e..752ec12df2eeb 100644
--- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs
@@ -130,7 +130,8 @@ public static SemanticTokensSchema LegacyTokensSchemaForLSIF
[
// This must be in the same order as SemanticTokens.TokenModifiers, but skip the "None" item
SemanticTokenModifiers.Static,
- nameof(SemanticTokens.TokenModifiers.ReassignedVariable)
+ nameof(SemanticTokens.TokenModifiers.ReassignedVariable),
+ SemanticTokenModifiers.Deprecated,
];
}
}
diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs
index c93cf9c6c35ce..b5f820e1d18a7 100644
--- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/TokenModifiers.cs
@@ -16,5 +16,6 @@ internal enum TokenModifiers
None = 0,
Static = 1,
ReassignedVariable = 2,
+ Deprecated = 4,
}
}
diff --git a/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs b/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs
index acd63d0657304..f416ae767d3d1 100644
--- a/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs
+++ b/src/Features/LanguageServer/Protocol/Protocol/CompletionItem.cs
@@ -52,6 +52,17 @@ public CompletionItemKind Kind
set;
} = CompletionItemKind.None;
+ ///
+ /// Tags for this completion item.
+ ///
+ [DataMember(Name = "tags")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public CompletionItemTag[]? Tags
+ {
+ get;
+ set;
+ }
+
///
/// Gets or sets the completion detail.
///
diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs
index 8369864bfd38f..2dbe0839d6d76 100644
--- a/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs
+++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Efficiency/OptimizedVSCompletionListJsonConverter.cs
@@ -176,6 +176,12 @@ private static void WriteCompletionItem(JsonWriter writer, CompletionItem comple
writer.WritePropertyName("kind");
writer.WriteValue(completionItem.Kind);
+ if (completionItem.Tags != null)
+ {
+ writer.WritePropertyName("tags");
+ serializer.Serialize(writer, completionItem.Tags);
+ }
+
if (completionItem.Detail != null)
{
writer.WritePropertyName("detail");
diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
index 97d60c6a0957c..00dfa0293382c 100644
--- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
+++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs
@@ -496,6 +496,79 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa
}
}
+ [Theory]
+ [CombinatorialData]
+ [WorkItem("https://github.com/dotnet/roslyn/issues/26488")]
+ public async Task TestCompletionForObsoleteSymbol(bool mutatingLspWorkspace)
+ {
+ var markup =
+ """
+ using System;
+
+ [Obsolete]
+ class ObsoleteType;
+
+ class A
+ {
+ void M()
+ {
+ ObsoleteType{|caret:|}
+ }
+ }
+ """;
+
+ await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace, DefaultClientCapabilities);
+ var completionParams = CreateCompletionParams(
+ testLspServer.GetLocations("caret").Single(),
+ invokeKind: LSP.VSInternalCompletionInvokeKind.Explicit,
+ triggerCharacter: "\0",
+ triggerKind: LSP.CompletionTriggerKind.Invoked);
+
+ var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First();
+
+ var completionResult = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionName, completionParams, CancellationToken.None).ConfigureAwait(false);
+ Assert.NotNull(completionResult.ItemDefaults.EditRange);
+ Assert.NotNull(completionResult.ItemDefaults.Data);
+ Assert.NotNull(completionResult.ItemDefaults.CommitCharacters);
+
+ var actualItem = completionResult.Items.First(i => i.Label == "ObsoleteType");
+ Assert.Null(actualItem.LabelDetails);
+ Assert.Null(actualItem.SortText);
+ Assert.Equal(CompletionItemKind.Class, actualItem.Kind);
+ Assert.Equal([CompletionItemTag.Deprecated], actualItem.Tags);
+ Assert.Null(actualItem.FilterText);
+ Assert.Null(actualItem.TextEdit);
+ Assert.Null(actualItem.TextEditText);
+ Assert.Null(actualItem.AdditionalTextEdits);
+ Assert.Null(actualItem.Command);
+ Assert.Null(actualItem.CommitCharacters);
+ Assert.Null(actualItem.Data);
+ Assert.Null(actualItem.Detail);
+ Assert.Null(actualItem.Documentation);
+
+ actualItem.Data = completionResult.ItemDefaults.Data;
+
+ var resolvedItem = await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionResolveName, actualItem, CancellationToken.None).ConfigureAwait(false);
+ Assert.Null(resolvedItem.LabelDetails);
+ Assert.Null(resolvedItem.SortText);
+ Assert.Equal(CompletionItemKind.Class, resolvedItem.Kind);
+ Assert.Equal([CompletionItemTag.Deprecated], resolvedItem.Tags);
+
+ Assert.Null(resolvedItem.AdditionalTextEdits);
+ Assert.Null(resolvedItem.FilterText);
+ Assert.Null(resolvedItem.TextEdit);
+ Assert.Null(resolvedItem.TextEditText);
+ Assert.Null(resolvedItem.Command);
+ Assert.Null(resolvedItem.Detail);
+
+ var expectedDocumentation = new MarkupContent()
+ {
+ Kind = LSP.MarkupKind.PlainText,
+ Value = "[deprecated] class ObsoleteType"
+ };
+ AssertJsonEquals(resolvedItem.Documentation, expectedDocumentation);
+ }
+
private sealed class CSharpLspMockCompletionService : CompletionService
{
private CSharpLspMockCompletionService(SolutionServices services, IAsynchronousOperationListenerProvider listenerProvider) : base(services, listenerProvider)
diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs
index 61929ec437f86..87db8cfbcc8f6 100644
--- a/src/Features/Lsif/Generator/Generator.cs
+++ b/src/Features/Lsif/Generator/Generator.cs
@@ -86,7 +86,7 @@ public static Generator CreateAndWriteCapabilitiesVertex(ILsifJsonWriter lsifJso
DocumentSymbolProvider,
FoldingRangeProvider,
DiagnosticProvider,
- new SemanticTokensCapabilities(SemanticTokensSchema.LegacyTokensSchemaForLSIF.AllTokenTypes, [SemanticTokenModifiers.Static]));
+ new SemanticTokensCapabilities(SemanticTokensSchema.LegacyTokensSchemaForLSIF.AllTokenTypes, [SemanticTokenModifiers.Static, SemanticTokenModifiers.Deprecated]));
generator._lsifJsonWriter.Write(capabilitiesVertex);
return generator;
}
diff --git a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb
index 2db321450b74f..5b01bbd5725c8 100644
--- a/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb
+++ b/src/Features/Lsif/GeneratorTest/OutputFormatTests.vb
@@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests
, openDocuments:=False, composition:=TestLsifOutput.TestComposition), jsonWriter)
AssertEx.EqualOrDiff(
-"{""hoverProvider"":true,""declarationProvider"":false,""definitionProvider"":true,""referencesProvider"":true,""typeDefinitionProvider"":false,""documentSymbolProvider"":true,""foldingRangeProvider"":true,""diagnosticProvider"":false,""semanticTokensProvider"":{""tokenTypes"":[""namespace"",""type"",""class"",""enum"",""interface"",""struct"",""typeParameter"",""parameter"",""variable"",""property"",""enumMember"",""event"",""function"",""method"",""macro"",""keyword"",""modifier"",""comment"",""string"",""number"",""regexp"",""operator"",""class name"",""constant name"",""delegate name"",""enum member name"",""enum name"",""event name"",""excluded code"",""extension method name"",""field name"",""interface name"",""json - array"",""json - comment"",""json - constructor name"",""json - keyword"",""json - number"",""json - object"",""json - operator"",""json - property name"",""json - punctuation"",""json - string"",""json - text"",""keyword - control"",""label name"",""local name"",""method name"",""module name"",""namespace name"",""operator - overloaded"",""parameter name"",""preprocessor keyword"",""preprocessor text"",""property name"",""punctuation"",""record class name"",""record struct name"",""regex - alternation"",""regex - anchor"",""regex - character class"",""regex - comment"",""regex - grouping"",""regex - other escape"",""regex - quantifier"",""regex - self escaped character"",""regex - text"",""roslyn test code markdown"",""string - escape character"",""string - verbatim"",""struct name"",""text"",""type parameter name"",""whitespace"",""xml doc comment - attribute name"",""xml doc comment - attribute quotes"",""xml doc comment - attribute value"",""xml doc comment - cdata section"",""xml doc comment - comment"",""xml doc comment - delimiter"",""xml doc comment - entity reference"",""xml doc comment - name"",""xml doc comment - processing instruction"",""xml doc comment - text"",""xml literal - attribute name"",""xml literal - attribute quotes"",""xml literal - attribute value"",""xml literal - cdata section"",""xml literal - comment"",""xml literal - delimiter"",""xml literal - embedded expression"",""xml literal - entity reference"",""xml literal - name"",""xml literal - processing instruction"",""xml literal - text""],""tokenModifiers"":[""static""]},""id"":1,""type"":""vertex"",""label"":""capabilities""}
+"{""hoverProvider"":true,""declarationProvider"":false,""definitionProvider"":true,""referencesProvider"":true,""typeDefinitionProvider"":false,""documentSymbolProvider"":true,""foldingRangeProvider"":true,""diagnosticProvider"":false,""semanticTokensProvider"":{""tokenTypes"":[""namespace"",""type"",""class"",""enum"",""interface"",""struct"",""typeParameter"",""parameter"",""variable"",""property"",""enumMember"",""event"",""function"",""method"",""macro"",""keyword"",""modifier"",""comment"",""string"",""number"",""regexp"",""operator"",""class name"",""constant name"",""delegate name"",""enum member name"",""enum name"",""event name"",""excluded code"",""extension method name"",""field name"",""interface name"",""json - array"",""json - comment"",""json - constructor name"",""json - keyword"",""json - number"",""json - object"",""json - operator"",""json - property name"",""json - punctuation"",""json - string"",""json - text"",""keyword - control"",""label name"",""local name"",""method name"",""module name"",""namespace name"",""operator - overloaded"",""parameter name"",""preprocessor keyword"",""preprocessor text"",""property name"",""punctuation"",""record class name"",""record struct name"",""regex - alternation"",""regex - anchor"",""regex - character class"",""regex - comment"",""regex - grouping"",""regex - other escape"",""regex - quantifier"",""regex - self escaped character"",""regex - text"",""roslyn test code markdown"",""string - escape character"",""string - verbatim"",""struct name"",""text"",""type parameter name"",""whitespace"",""xml doc comment - attribute name"",""xml doc comment - attribute quotes"",""xml doc comment - attribute value"",""xml doc comment - cdata section"",""xml doc comment - comment"",""xml doc comment - delimiter"",""xml doc comment - entity reference"",""xml doc comment - name"",""xml doc comment - processing instruction"",""xml doc comment - text"",""xml literal - attribute name"",""xml literal - attribute quotes"",""xml literal - attribute value"",""xml literal - cdata section"",""xml literal - comment"",""xml literal - delimiter"",""xml literal - embedded expression"",""xml literal - entity reference"",""xml literal - name"",""xml literal - processing instruction"",""xml literal - text""],""tokenModifiers"":[""static"",""deprecated""]},""id"":1,""type"":""vertex"",""label"":""capabilities""}
{""kind"":""csharp"",""resource"":""file:///Z:/%EE%89%9B/TestProject.csproj"",""name"":""TestProject"",""id"":2,""type"":""vertex"",""label"":""project""}
{""kind"":""begin"",""scope"":""project"",""data"":2,""id"":3,""type"":""vertex"",""label"":""$event""}
{""uri"":""file:///Z:/%EE%89%9B/a.cs"",""languageId"":""csharp"",""id"":4,""type"":""vertex"",""label"":""document""}
@@ -168,7 +168,8 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests
""xml literal - text""
],
""tokenModifiers"": [
- ""static""
+ ""static"",
+ ""deprecated""
]
},
""id"": 1,
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
index e1416d98bc310..d8ae01d553462 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml
@@ -218,6 +218,8 @@
Content="{x:Static local:AdvancedOptionPageStrings.Option_Report_invalid_placeholders_in_string_dot_format_calls}" />
+
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
index 859461ef020cb..9cedb77ba732c 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml.cs
@@ -137,6 +137,7 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon
BindToOption(Fix_text_pasted_into_string_literals_experimental, StringCopyPasteOptionsStorage.AutomaticallyFixStringContentsOnPaste, LanguageNames.CSharp);
BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, IdeAnalyzerOptionsStorage.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.CSharp);
BindToOption(Underline_reassigned_variables, ClassificationOptionsStorage.ClassifyReassignedVariables, LanguageNames.CSharp);
+ BindToOption(Strike_out_obsolete_symbols, ClassificationOptionsStorage.ClassifyObsoleteSymbols, LanguageNames.CSharp);
BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, () =>
{
// If the option has not been set by the user, check if the option is enabled from experimentation.
diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
index 2f53e9718508f..81950708cc7ce 100644
--- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
@@ -150,6 +150,9 @@ public static string Option_DisplayLineSeparators
public static string Option_Underline_reassigned_variables
=> ServicesVSResources.Underline_reassigned_variables;
+ public static string Option_Strike_out_obsolete_symbols
+ => ServicesVSResources.Strike_out_obsolete_symbols;
+
public static string Option_DontPutOutOrRefOnStruct
=> CSharpVSResources.Don_t_put_ref_or_out_on_custom_struct;
diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs
index 8b5162180b5d2..85f9166a2a8ad 100644
--- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs
+++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs
@@ -130,6 +130,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti
{"dotnet_show_outlining_for_declaration_level_constructs", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ShowOutliningForDeclarationLevelConstructs")},
{"csharp_format_on_close_brace", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.Auto Formatting On Close Brace")},
{"dotnet_classify_reassigned_variables", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ClassificationOptions.ClassifyReassignedVariables")},
+ {"dotnet_classify_obsolete_symbols", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.ClassificationOptions.ClassifyObsoleteSymbols")},
{"dotnet_prefer_system_hash_code", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.PreferSystemHashCode")},
{"visual_studio_color_scheme_name", new RoamingProfileStorage("TextEditor.Roslyn.ColorSchemeName")},
{"visual_studio_color_scheme_use_legacy_enhanced_colors", new RoamingProfileStorage("WindowManagement.Options.UseEnhancedColorsForManagedLanguages")},
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx
index aca53283fce6f..949f6dabad60c 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.resx
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx
@@ -1894,6 +1894,9 @@ Additional information: {1}
When types loosely match
+
+ Strike out obsolete symbols
+
{0} ({1})
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf
index d1e7bfbf1c0bb..b9c2853834d7b 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf
@@ -1377,6 +1377,11 @@
Trasování zásobníku: {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Návrh
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf
index 545fe7c9d2db4..17bb6edd8ed5d 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf
@@ -1377,6 +1377,11 @@
Stapelüberwachung {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Vorschlag
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf
index a096c54a69956..7a35b47219bd9 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf
@@ -1377,6 +1377,11 @@
Seguimiento de la pila {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Sugerencia
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf
index 6265ab847ad9d..b9f9e6550eb68 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf
@@ -1377,6 +1377,11 @@
Trace de la pile {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Suggestion
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf
index bac04d1a313bf..367ae1e8d6683 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf
@@ -1377,6 +1377,11 @@
Analisi dello stack {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Suggerimento
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf
index 6627fa184ad96..931841ff6d5d1 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf
@@ -1377,6 +1377,11 @@
スタック トレース {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
提案事項
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf
index 19f7d6be1920a..ef33d01d7750a 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf
@@ -1377,6 +1377,11 @@
스택 추적 {0}개
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
제안
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf
index 7f6c51ab3a166..e021f154164f8 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf
@@ -1377,6 +1377,11 @@
Ślad stosu {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Sugestia
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf
index 4ae3db17961c4..c32fe83ef17b2 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf
@@ -1377,6 +1377,11 @@
Rastreamento de Pilha{0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Sugestão
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf
index 7f92908b97a19..7cfe12ffe0459 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf
@@ -1377,6 +1377,11 @@
Трассировка стека {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Рекомендация
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf
index 88b236ef47313..38bac0145467f 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf
@@ -1377,6 +1377,11 @@
Yığın İzleme {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
Öneri
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf
index b84781af1c18d..d673741b2bf03 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf
@@ -1377,6 +1377,11 @@
堆栈跟踪: {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
建议
diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf
index 6acce6f8d8af4..889e73b2f0df3 100644
--- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf
+++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf
@@ -1377,6 +1377,11 @@
堆疊追蹤 {0}
Header for numbered stack trace view tabs
+
+
+ Strike out obsolete symbols
+
+
建議
diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml
index 4d762d6809e6f..4084ee0418f20 100644
--- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml
+++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml
@@ -205,6 +205,8 @@
Content="{x:Static local:AdvancedOptionPageStrings.Option_Report_invalid_placeholders_in_string_dot_format_calls}" />
+
diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb
index 899bb86301916..17375fb645916 100644
--- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb
+++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageControl.xaml.vb
@@ -127,6 +127,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
BindToOption(ShowRemarksInQuickInfo, QuickInfoOptionsStorage.ShowRemarksInQuickInfo, LanguageNames.VisualBasic)
BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, IdeAnalyzerOptionsStorage.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.VisualBasic)
BindToOption(Underline_reassigned_variables, ClassificationOptionsStorage.ClassifyReassignedVariables, LanguageNames.VisualBasic)
+ BindToOption(Strike_out_obsolete_symbols, ClassificationOptionsStorage.ClassifyObsoleteSymbols, LanguageNames.VisualBasic)
' Go To Definition
BindToOption(NavigateToObjectBrowser, VisualStudioNavigationOptionsStorage.NavigateToObjectBrowser, LanguageNames.VisualBasic)
diff --git a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb
index 545513dcf7094..d9a8e91fcdcbb 100644
--- a/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb
+++ b/src/VisualStudio/VisualBasic/Impl/Options/AdvancedOptionPageStrings.vb
@@ -85,6 +85,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
Public ReadOnly Property Option_Underline_reassigned_variables As String =
ServicesVSResources.Underline_reassigned_variables
+ Public ReadOnly Property Option_Strike_out_obsolete_symbols As String =
+ ServicesVSResources.Strike_out_obsolete_symbols
+
Public ReadOnly Property Option_Display_all_hints_while_pressing_Alt_F1 As String =
ServicesVSResources.Display_all_hints_while_pressing_Alt_F1
diff --git a/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs b/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs
new file mode 100644
index 0000000000000..db62ff996c5f7
--- /dev/null
+++ b/src/Workspaces/CSharp/Portable/ObsoleteSymbol/CSharpObsoleteSymbolService.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Composition;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.ObsoleteSymbol;
+
+namespace Microsoft.CodeAnalysis.CSharp.ObsoleteSymbol;
+
+[ExportLanguageService(typeof(IObsoleteSymbolService), LanguageNames.CSharp)]
+[Shared]
+[method: ImportingConstructor]
+[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
+internal sealed class CSharpObsoleteSymbolService() : AbstractObsoleteSymbolService(dimKeywordKind: null)
+{
+}
diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs
index f112e3dbb5cf9..176d0ebd7aac7 100644
--- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs
+++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs
@@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Extensions;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.ObsoleteSymbol;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.ReassignedVariable;
using Microsoft.CodeAnalysis.Remote;
@@ -139,6 +140,7 @@ public static async Task AddClassificationsInCurrentProcessAsync(
{
var classificationService = document.GetRequiredLanguageService();
var reassignedVariableService = document.GetRequiredLanguageService();
+ var obsoleteSymbolService = document.GetRequiredLanguageService();
var extensionManager = document.Project.Solution.Services.GetRequiredService();
var classifiers = classificationService.GetDefaultSyntaxClassifiers();
@@ -155,6 +157,13 @@ await classificationService.AddSemanticClassificationsAsync(
foreach (var span in reassignedVariableSpans)
result.Add(new ClassifiedSpan(span, ClassificationTypeNames.ReassignedVariable));
}
+
+ if (options.ClassifyObsoleteSymbols)
+ {
+ var obsoleteSymbolSpans = await obsoleteSymbolService.GetLocationsAsync(document, textSpans, cancellationToken).ConfigureAwait(false);
+ foreach (var span in obsoleteSymbolSpans)
+ result.Add(new ClassifiedSpan(span, ClassificationTypeNames.ObsoleteSymbol));
+ }
}
else if (type == ClassificationType.EmbeddedLanguage)
{
diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs b/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs
index ef812b4362d53..417ec439686f0 100644
--- a/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs
+++ b/src/Workspaces/Core/Portable/Classification/ClassificationOptions.cs
@@ -10,6 +10,7 @@ namespace Microsoft.CodeAnalysis.Classification;
internal readonly record struct ClassificationOptions
{
[DataMember] public bool ClassifyReassignedVariables { get; init; } = false;
+ [DataMember] public bool ClassifyObsoleteSymbols { get; init; } = true;
[DataMember] public bool ColorizeRegexPatterns { get; init; } = true;
[DataMember] public bool ColorizeJsonPatterns { get; init; } = true;
[DataMember] public bool ForceFrozenPartialSemanticsForCrossProcessOperations { get; init; } = false;
diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs
index 27bce3937550a..e8a52b10f219f 100644
--- a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs
+++ b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs
@@ -11,7 +11,7 @@ public static class ClassificationTypeNames
///
/// Additive classifications types supply additional context to other classifications.
///
- public static ImmutableArray AdditiveTypeNames { get; } = [StaticSymbol, ReassignedVariable, TestCode];
+ public static ImmutableArray AdditiveTypeNames { get; } = [StaticSymbol, ReassignedVariable, ObsoleteSymbol, TestCode];
public static ImmutableArray AllTypeNames { get; } =
[
@@ -28,6 +28,7 @@ public static class ClassificationTypeNames
WhiteSpace,
Text,
ReassignedVariable,
+ ObsoleteSymbol,
StaticSymbol,
PreprocessorText,
Punctuation,
@@ -112,6 +113,7 @@ public static class ClassificationTypeNames
public const string Text = "text";
internal const string ReassignedVariable = "reassigned variable";
+ internal const string ObsoleteSymbol = "obsolete symbol";
public const string StaticSymbol = "static symbol";
public const string PreprocessorText = "preprocessor text";
diff --git a/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs
new file mode 100644
index 0000000000000..d3349b8b75c30
--- /dev/null
+++ b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs
@@ -0,0 +1,218 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.LanguageService;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ObsoleteSymbol;
+
+internal abstract class AbstractObsoleteSymbolService(int? dimKeywordKind) : IObsoleteSymbolService
+{
+ ///
+ /// The of the keyword in Visual Basic, or
+ /// for C# scenarios. This value is used to improve performance in the token classification
+ /// fast-path by avoiding unnecessary calls to .
+ ///
+ private readonly int? _dimKeywordKind = dimKeywordKind;
+
+ protected virtual void ProcessDimKeyword(ref ArrayBuilder? result, SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken)
+ {
+ // Take no action by default
+ }
+
+ public async Task> GetLocationsAsync(Document document, ImmutableArray textSpans, CancellationToken cancellationToken)
+ {
+ var syntaxFacts = document.GetRequiredLanguageService();
+
+ var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
+ var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+
+ // Avoid taking a builder from the pool in the common case where there are no references to obsolete symbols
+ // currently on screen.
+ ArrayBuilder? result = null;
+ try
+ {
+ var semanticModel = compilation.GetSemanticModel(root.SyntaxTree);
+
+ foreach (var span in textSpans)
+ {
+ Recurse(span, semanticModel);
+ }
+
+ if (result is null)
+ return ImmutableArray.Empty;
+
+ result.RemoveDuplicates();
+ return result.ToImmutableAndClear();
+ }
+ finally
+ {
+ result?.Free();
+ }
+
+ void Recurse(TextSpan span, SemanticModel semanticModel)
+ {
+ using var _ = ArrayBuilder.GetInstance(out var stack);
+
+ // Walk through all the nodes in the provided span. Directly analyze local or parameter declaration. And
+ // also analyze any identifiers which might be reference to locals or parameters. Note that we might hit
+ // locals/parameters without any references in the span, or references that don't have the declarations in
+ // the span
+ stack.Add(root.FindNode(span));
+
+ // Use a stack so we don't blow out the stack with recursion.
+ while (stack.Count > 0)
+ {
+ var current = stack.Last();
+ stack.RemoveLast();
+
+ if (current.Span.IntersectsWith(span))
+ {
+ var tokenFromNode = ProcessNode(semanticModel, current);
+
+ foreach (var child in current.ChildNodesAndTokens())
+ {
+ if (child.IsNode)
+ {
+ stack.Add(child.AsNode()!);
+ }
+
+ var token = child.AsToken();
+ if (token != tokenFromNode)
+ ProcessToken(semanticModel, child.AsToken());
+
+ ExtractStructureFromTrivia(stack, token.LeadingTrivia);
+ ExtractStructureFromTrivia(stack, token.TrailingTrivia);
+ }
+ }
+ }
+ }
+
+ static void ExtractStructureFromTrivia(ArrayBuilder stack, SyntaxTriviaList triviaList)
+ {
+ foreach (var trivia in triviaList)
+ {
+ if (trivia.HasStructure)
+ {
+ stack.Add(trivia.GetStructure()!);
+ }
+ }
+ }
+
+ void AddResult(TextSpan span)
+ {
+ result ??= ArrayBuilder.GetInstance();
+ result.Add(span);
+ }
+
+ SyntaxToken ProcessNode(SemanticModel semanticModel, SyntaxNode node)
+ {
+ if (syntaxFacts.IsUsingAliasDirective(node))
+ {
+ syntaxFacts.GetPartsOfUsingAliasDirective(node, out _, out var aliasToken, out var name);
+ if (!aliasToken.Span.IsEmpty)
+ {
+ // Use 'name.Parent' because VB can't resolve the declared symbol directly from 'node'
+ var symbol = semanticModel.GetDeclaredSymbol(name.GetRequiredParent(), cancellationToken);
+ if (IsSymbolObsolete(symbol))
+ AddResult(aliasToken.Span);
+ }
+
+ return aliasToken;
+ }
+ else if (syntaxFacts.IsObjectCreationExpression(node))
+ {
+ syntaxFacts.GetPartsOfObjectCreationExpression(node, out var creationKeyword, out _, out _, out _);
+ if (!creationKeyword.Span.IsEmpty)
+ {
+ // For syntax like the following
+ //
+ // SomeType value = new SomeType();
+ //
+ // We classify 'new' as obsolete only if the specific constructor is obsolete. If the containing
+ // type is obsolete, the classification will be applied to 'SomeType' instead.
+ var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
+ if (IsSymbolObsolete(symbol))
+ AddResult(creationKeyword.Span);
+ }
+ }
+ else if (syntaxFacts.IsImplicitObjectCreationExpression(node))
+ {
+ syntaxFacts.GetPartsOfImplicitObjectCreationExpression(node, out var creationKeyword, out _, out _);
+ if (!creationKeyword.Span.IsEmpty)
+ {
+ // For syntax like the following
+ //
+ // SomeType value = new();
+ //
+ // We classify 'new' as obsolete if either the type or the specific constructor is obsolete.
+ var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
+ if (IsSymbolObsolete(symbol) || IsSymbolObsolete(symbol?.ContainingType))
+ AddResult(creationKeyword.Span);
+ }
+ }
+
+ return default;
+ }
+
+ void ProcessToken(SemanticModel semanticModel, SyntaxToken token)
+ {
+ if (syntaxFacts.IsIdentifier(token))
+ {
+ ProcessIdentifier(semanticModel, token);
+ }
+ else if (token.RawKind == _dimKeywordKind)
+ {
+ ProcessDimKeyword(ref result, semanticModel, token, cancellationToken);
+ }
+ }
+
+ void ProcessIdentifier(SemanticModel semanticModel, SyntaxToken token)
+ {
+ if (syntaxFacts.IsDeclaration(token.Parent))
+ {
+ var symbol = semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken);
+ if (IsSymbolObsolete(symbol))
+ AddResult(token.Span);
+ }
+ else
+ {
+ var symbol = semanticModel.GetSymbolInfo(token, cancellationToken).Symbol;
+ if (IsSymbolObsolete(symbol))
+ AddResult(token.Span);
+ }
+ }
+ }
+
+ protected static bool IsSymbolObsolete([NotNullWhen(true)] ISymbol? symbol)
+ {
+ // Avoid infinite recursion. Iteration limit chosen arbitrarily; cases are generally expected to complete on
+ // the first iteration or fail completely.
+ for (var i = 0; i < 5; i++)
+ {
+ if (symbol is IAliasSymbol alias)
+ {
+ symbol = alias.Target;
+ continue;
+ }
+
+ if (symbol is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments: [var valueType] })
+ {
+ symbol = valueType;
+ continue;
+ }
+
+ return symbol?.IsObsolete() ?? false;
+ }
+
+ // Unable to determine whether the symbol is considered obsolete
+ return false;
+ }
+}
diff --git a/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs b/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs
new file mode 100644
index 0000000000000..7e1d65363baff
--- /dev/null
+++ b/src/Workspaces/Core/Portable/ObsoleteSymbol/IObsoleteSymbolService.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.CodeAnalysis.ObsoleteSymbol;
+
+///
+/// Service which can analyze a span of a document and identify all locations of declarations or references to
+/// symbols which are marked .
+///
+internal interface IObsoleteSymbolService : ILanguageService
+{
+ Task> GetLocationsAsync(Document document, ImmutableArray textSpans, CancellationToken cancellationToken);
+}
diff --git a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs
index 13b86fe85581e..4552c97465f7d 100644
--- a/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs
+++ b/src/Workspaces/Core/Portable/Tags/WellKnownTags.cs
@@ -50,6 +50,8 @@ public static class WellKnownTags
public const string Error = nameof(Error);
public const string Warning = nameof(Warning);
+ internal const string Deprecated = nameof(Deprecated);
+
internal const string StatusInformation = nameof(StatusInformation);
internal const string AddReference = nameof(AddReference);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
index b03846c9f11a5..792289b5df9da 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
@@ -1681,14 +1681,23 @@ public void GetPartsOfNamedMemberInitializer(SyntaxNode node, out SyntaxNode ide
expression = assignment.Right;
}
- public void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer)
+ public void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer)
{
var objectCreationExpression = (ObjectCreationExpressionSyntax)node;
+ keyword = objectCreationExpression.NewKeyword;
type = objectCreationExpression.Type;
argumentList = objectCreationExpression.ArgumentList;
initializer = objectCreationExpression.Initializer;
}
+ public void GetPartsOfImplicitObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode argumentList, out SyntaxNode? initializer)
+ {
+ var implicitObjectCreationExpression = (ImplicitObjectCreationExpressionSyntax)node;
+ keyword = implicitObjectCreationExpression.NewKeyword;
+ argumentList = implicitObjectCreationExpression.ArgumentList;
+ initializer = implicitObjectCreationExpression.Initializer;
+ }
+
public void GetPartsOfParameter(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode? @default)
{
var parameter = (ParameterSyntax)node;
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
index 969b8f3878681..3fc36df6dda1b 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs
@@ -528,7 +528,8 @@ void GetPartsOfTupleExpression(SyntaxNode node,
void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right);
void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name);
void GetPartsOfNamedMemberInitializer(SyntaxNode node, out SyntaxNode name, out SyntaxNode expression);
- void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer);
+ void GetPartsOfObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode type, out SyntaxNode? argumentList, out SyntaxNode? initializer);
+ void GetPartsOfImplicitObjectCreationExpression(SyntaxNode node, out SyntaxToken keyword, out SyntaxNode argumentList, out SyntaxNode? initializer);
void GetPartsOfParameter(SyntaxNode node, out SyntaxToken identifier, out SyntaxNode? @default);
void GetPartsOfParenthesizedExpression(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode expression, out SyntaxToken closeParen);
void GetPartsOfPrefixUnaryExpression(SyntaxNode node, out SyntaxToken operatorToken, out SyntaxNode operand);
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
index 29deeaeb60fc6..3f2af4340e6e9 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs
@@ -595,7 +595,7 @@ public static SeparatedSyntaxList GetTypeArgumentsOfGenericName(this
public static SyntaxNode GetTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
{
- syntaxFacts.GetPartsOfObjectCreationExpression(node, out var type, out _, out _);
+ syntaxFacts.GetPartsOfObjectCreationExpression(node, out _, out var type, out _, out _);
return type;
}
@@ -648,7 +648,7 @@ public static bool IsTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFact
if (!syntaxFacts.IsObjectCreationExpression(parent))
return false;
- syntaxFacts.GetPartsOfObjectCreationExpression(parent, out var type, out _, out _);
+ syntaxFacts.GetPartsOfObjectCreationExpression(parent, out _, out var type, out _, out _);
return type == node;
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
index 0bb0d7c271698..a805b26656095 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb
@@ -1895,13 +1895,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService
expression = namedField.Expression
End Sub
- Public Sub GetPartsOfObjectCreationExpression(node As SyntaxNode, ByRef type As SyntaxNode, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfObjectCreationExpression
+ Public Sub GetPartsOfObjectCreationExpression(node As SyntaxNode, ByRef keyword As SyntaxToken, ByRef type As SyntaxNode, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfObjectCreationExpression
Dim objectCreationExpression = DirectCast(node, ObjectCreationExpressionSyntax)
+ keyword = objectCreationExpression.NewKeyword
type = objectCreationExpression.Type
argumentList = objectCreationExpression.ArgumentList
initializer = objectCreationExpression.Initializer
End Sub
+ Public Sub GetPartsOfImplicitObjectCreationExpression(node As SyntaxNode, ByRef keyword As SyntaxToken, ByRef argumentList As SyntaxNode, ByRef initializer As SyntaxNode) Implements ISyntaxFacts.GetPartsOfImplicitObjectCreationExpression
+ Throw New InvalidOperationException(DoesNotExistInVBErrorMessage)
+ End Sub
+
Public Sub GetPartsOfParameter(node As SyntaxNode, ByRef identifier As SyntaxToken, ByRef [default] As SyntaxNode) Implements ISyntaxFacts.GetPartsOfParameter
Dim parameter = DirectCast(node, ParameterSyntax)
identifier = parameter.Identifier.Identifier
diff --git a/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb b/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb
new file mode 100644
index 0000000000000..f20879b1fdce4
--- /dev/null
+++ b/src/Workspaces/VisualBasic/Portable/ObsoleteSymbol/VisualBasicObsoleteSymbolService.vb
@@ -0,0 +1,57 @@
+' Licensed to the .NET Foundation under one or more agreements.
+' The .NET Foundation licenses this file to you under the MIT license.
+' See the LICENSE file in the project root for more information.
+
+Imports System.Composition
+Imports System.Threading
+Imports Microsoft.CodeAnalysis.Host.Mef
+Imports Microsoft.CodeAnalysis.ObsoleteSymbol
+Imports Microsoft.CodeAnalysis.PooledObjects
+Imports Microsoft.CodeAnalysis.Text
+Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
+
+Namespace Microsoft.CodeAnalysis.VisualBasic.ObsoleteSymbol
+
+ <[Shared]>
+ Friend Class VisualBasicObsoleteSymbolService
+ Inherits AbstractObsoleteSymbolService
+
+
+
+ Public Sub New()
+ MyBase.New(SyntaxKind.DimKeyword)
+ End Sub
+
+ Protected Overrides Sub ProcessDimKeyword(ByRef result As ArrayBuilder(Of TextSpan), semanticModel As SemanticModel, token As SyntaxToken, cancellationToken As CancellationToken)
+ Dim localDeclaration = TryCast(token.Parent, LocalDeclarationStatementSyntax)
+ If localDeclaration Is Nothing Then
+ Return
+ End If
+
+ If localDeclaration.Declarators.Count <> 1 Then
+ Return
+ End If
+
+ Dim declarator = localDeclaration.Declarators(0)
+ If declarator.AsClause IsNot Nothing Then
+ ' This is an explicitly typed variable, so no need to mark 'Dim' obsolete
+ Return
+ End If
+
+ If declarator.Names.Count <> 1 Then
+ ' More than one variable is declared
+ Return
+ End If
+
+ ' Only one variable is declared
+ Dim localSymbol = TryCast(semanticModel.GetDeclaredSymbol(declarator.Names(0), cancellationToken), ILocalSymbol)
+ If IsSymbolObsolete(localSymbol?.Type) Then
+ If result Is Nothing Then
+ result = ArrayBuilder(Of TextSpan).GetInstance()
+ End If
+
+ result.Add(token.Span)
+ End If
+ End Sub
+ End Class
+End Namespace