diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 31f2c14b3ddad..42f0663819b97 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -185,12 +185,12 @@ "vsBranch": "master", "vsMajorVersion": 16 }, - "features/dotnetFormat": { + "features/UsedAssemblyReferences": { "nugetKind": [ "Shipping", "NonShipping" ], - "version": "3.3.*", + "version": "3.8.*", "nuget": [ "https://dotnet.myget.org/F/roslyn/api/v2/package" ], "vsix": [ "https://dotnet.myget.org/F/roslyn/vsix/upload" ], - "channels": [ "dotnetFormat" ], + "channels": [ "UsedAssemblyReferences" ], "vsBranch": "master", "vsMajorVersion": 16 }, diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index 25a6556ddb684..f0e8e33a86227 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -696,7 +696,8 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) } private bool IsEntryPoint(IMethodSymbol methodSymbol) - => (methodSymbol.Name == WellKnownMemberNames.EntryPointMethodName || methodSymbol.Name == "$Main") && + => (methodSymbol.Name == WellKnownMemberNames.EntryPointMethodName || methodSymbol.Name == "
$") && // https://github.com/dotnet/roslyn/issues/45110 Switch to using WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + // once src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj is able to use the latest version of the type. methodSymbol.IsStatic && (methodSymbol.ReturnsVoid || methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 || diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_QueryErrors.cs b/src/Compilers/CSharp/Portable/Binder/Binder_QueryErrors.cs index c3551bc1fe4ba..cf52de33e42a2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_QueryErrors.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_QueryErrors.cs @@ -49,7 +49,7 @@ internal void ReportQueryLookupFailed( } else if (ImplementsStandardQueryInterface(instanceArgument.Type, name, ref useSiteDiagnostics)) { - // Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + // Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? diagnostics.Add(new DiagnosticInfoWithSymbols( ErrorCode.ERR_QueryNoProviderStandard, new object[] { instanceArgument.Type, name }, diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index fbbdf1ba95725..5c1a54f78d7d6 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -3299,7 +3299,7 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Consider explicitly specifying the type of the range variable '{2}'. - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. diff --git a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs index 2c086a194e96d..6e7181f4b4742 100644 --- a/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs +++ b/src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs @@ -131,7 +131,7 @@ private static SingleNamespaceOrTypeDeclaration CreateSimpleProgram(GlobalStatem { return new SingleTypeDeclaration( kind: DeclarationKind.SimpleProgram, - name: SimpleProgramNamedTypeSymbol.UnspeakableName, + name: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, arity: 0, modifiers: DeclarationModifiers.Internal | DeclarationModifiers.Partial | DeclarationModifiers.Static, declFlags: (hasAwaitExpressions ? SingleTypeDeclaration.TypeDeclarationFlags.HasAwaitExpressions : SingleTypeDeclaration.TypeDeclarationFlags.None) | diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SimpleProgramNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SimpleProgramNamedTypeSymbol.cs index 08f30a2c087e4..75f396bfffbde 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SimpleProgramNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SimpleProgramNamedTypeSymbol.cs @@ -23,14 +23,12 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class SimpleProgramNamedTypeSymbol : SourceMemberContainerTypeSymbol { - internal const string UnspeakableName = "$Program"; - internal SimpleProgramNamedTypeSymbol(NamespaceSymbol globalNamespace, MergedTypeDeclaration declaration, DiagnosticBag diagnostics) : base(globalNamespace, declaration, diagnostics) { Debug.Assert(globalNamespace.IsGlobalNamespace); Debug.Assert(declaration.Kind == DeclarationKind.SimpleProgram); - Debug.Assert(declaration.Name == UnspeakableName); + Debug.Assert(declaration.Name == WellKnownMemberNames.TopLevelStatementsEntryPointTypeName); state.NotePartComplete(CompletionPart.EnumUnderlyingType); // No work to do for this. } @@ -42,7 +40,7 @@ internal SimpleProgramNamedTypeSymbol(NamespaceSymbol globalNamespace, MergedTyp private static SimpleProgramNamedTypeSymbol? GetSimpleProgramNamedTypeSymbol(CSharpCompilation compilation) { - return compilation.SourceModule.GlobalNamespace.GetTypeMembers(UnspeakableName).OfType().SingleOrDefault(); + return compilation.SourceModule.GlobalNamespace.GetTypeMembers(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName).OfType().SingleOrDefault(); } internal static SynthesizedSimpleProgramEntryPointSymbol? GetSimpleProgramEntryPoint(CSharpCompilation compilation, CompilationUnitSyntax compilationUnit, bool fallbackToMainEntryPoint) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs index b12e4839f50e8..ef17789df2390 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs @@ -18,8 +18,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SynthesizedSimpleProgramEntryPointSymbol : SourceMemberMethodSymbol { - internal const string UnspeakableName = "$Main"; - /// /// The corresponding . /// @@ -73,7 +71,7 @@ public override string Name { get { - return UnspeakableName; + return WellKnownMemberNames.TopLevelStatementsEntryPointMethodName; } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index cc8a5fe3604fa..dca9296dd7c59 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -7263,8 +7263,8 @@ Blok catch() po bloku catch (System.Exception e) může zachytit výjimky, kter - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Nenašla se implementace vzorku dotazu pro typ zdroje {0}. Nenašel se prvek {1}. Nechybí odkaz na System.Core.dll nebo na direktivu using pro System.Linq? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Nenašla se implementace vzorku dotazu pro typ zdroje {0}. Nenašel se prvek {1}. Nechybí odkaz na System.Core.dll nebo na direktivu using pro System.Linq? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index faf3e7f16e5a6..817c1b893c610 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -7263,8 +7263,8 @@ Ein catch()-Block nach einem catch (System.Exception e)-Block kann nicht-CLS-Aus - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Es konnte keine Implementierung des Abfragemusters für den Quelltyp "{0}" gefunden werden. "{1}" wurde nicht gefunden. Fehlt möglicherweise ein Verweis auf "System.Core.dll" oder eine Using-Direktive für "System.Linq"? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Es konnte keine Implementierung des Abfragemusters für den Quelltyp "{0}" gefunden werden. "{1}" wurde nicht gefunden. Fehlt möglicherweise ein Verweis auf "System.Core.dll" oder eine Using-Direktive für "System.Linq"? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index b762ed92bf08c..809febd3b42d4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -7263,8 +7263,8 @@ Un bloque catch() después de un bloque catch (System.Exception e) puede abarcar - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - No se encontró ninguna implementación del patrón de consulta para el tipo de origen '{0}'. No se encontró '{1}'. ¿Falta alguna referencia a 'System.Core.dll' o alguna directiva using para 'System.Linq'? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + No se encontró ninguna implementación del patrón de consulta para el tipo de origen '{0}'. No se encontró '{1}'. ¿Falta alguna referencia a 'System.Core.dll' o alguna directiva using para 'System.Linq'? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 8a062087b58e1..8af2b8c8374af 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -7263,8 +7263,8 @@ Un bloc catch() après un bloc catch (System.Exception e) peut intercepter des e - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Impossible de trouver une implémentation du modèle de requête pour le type source '{0}'. '{1}' introuvable. Vous manque-t-il une référence à 'System.Core.dll' ou une directive using pour 'System.Linq' ? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Impossible de trouver une implémentation du modèle de requête pour le type source '{0}'. '{1}' introuvable. Vous manque-t-il une référence à 'System.Core.dll' ou une directive using pour 'System.Linq' ? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 4adf7a1d54b56..bd9fc7aa63427 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -7263,8 +7263,8 @@ Un blocco catch() dopo un blocco catch (System.Exception e) può rilevare eccezi - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Non è stata trovata un'implementazione del modello di query per il tipo di origine '{0}'. '{1}' non è presente. Probabilmente manca un riferimento a 'System.Core.dll' o una direttiva using per 'System.Linq'. + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Non è stata trovata un'implementazione del modello di query per il tipo di origine '{0}'. '{1}' non è presente. Probabilmente manca un riferimento a 'System.Core.dll' o una direttiva using per 'System.Linq'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 0980e29f39b3a..03c049daa6a0a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -7263,8 +7263,8 @@ AssemblyInfo.cs ファイルで RuntimeCompatibilityAttribute が false に設 - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - ソース型 '{0}' のクエリ パターンの実装が見つかりませんでした。'{1}' が見つかりません。'System.Core.dll' の参照または 'System.Linq' のディレクティブの使用が指定されていることを確認してください。 + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + ソース型 '{0}' のクエリ パターンの実装が見つかりませんでした。'{1}' が見つかりません。'System.Core.dll' の参照または 'System.Linq' のディレクティブの使用が指定されていることを確認してください。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index fde5ee69fcba3..dbe67070fbdb3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -7263,8 +7263,8 @@ catch (System.Exception e) 블록 뒤의 catch() 블록은 RuntimeCompatibilityA - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - 소스 형식 '{0}'에 대해 구현된 쿼리 패턴을 찾을 수 없습니다. '{1}'을(를) 찾을 수 없습니다. 'System.Core.dll'에 대한 참조 또는 'System.Linq'에 대한 using 지시문이 있는지 확인하세요. + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + 소스 형식 '{0}'에 대해 구현된 쿼리 패턴을 찾을 수 없습니다. '{1}'을(를) 찾을 수 없습니다. 'System.Core.dll'에 대한 참조 또는 'System.Linq'에 대한 using 지시문이 있는지 확인하세요. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 2442c56f3cd49..a9b03f84fce50 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -7263,8 +7263,8 @@ Blok catch() po bloku catch (System.Exception e) może przechwytywać wyjątki n - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Nie można znaleźć implementacji wzorca zapytania dla typu źródłowego „{0}”. Nie znaleziono elementu „{1}”. Być może brakuje odwołania do biblioteki „System.Core.dll” lub użycia dyrektywy dla przestrzeni nazw „System.Linq”. + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Nie można znaleźć implementacji wzorca zapytania dla typu źródłowego „{0}”. Nie znaleziono elementu „{1}”. Być może brakuje odwołania do biblioteki „System.Core.dll” lub użycia dyrektywy dla przestrzeni nazw „System.Linq”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 2bccd8c168387..b1054e0b4af48 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -7261,8 +7261,8 @@ Um bloco catch() depois de um bloco catch (System.Exception e) poderá capturar - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Não foi possível encontrar uma implementação do padrão de consulta para o tipo de origem "{0}". "{1}" não encontrado. Está faltando uma referência a "System.Core.dll" ou uma diretiva using para "System.Linq"? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Não foi possível encontrar uma implementação do padrão de consulta para o tipo de origem "{0}". "{1}" não encontrado. Está faltando uma referência a "System.Core.dll" ou uma diretiva using para "System.Linq"? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index ebebd547b5629..f058c44101fdc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -7263,8 +7263,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - Не удалось найти реализацию шаблона запроса для исходного типа "{0}". "{1}" не найден. Возможно, не хватает ссылки на "System.Core.dll" или директивы using для "System.Linq". + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + Не удалось найти реализацию шаблона запроса для исходного типа "{0}". "{1}" не найден. Возможно, не хватает ссылки на "System.Core.dll" или директивы using для "System.Linq". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 85c14d9172c8c..851c2d342e33c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -7263,8 +7263,8 @@ RuntimeCompatibilityAttribute AssemblyInfo.cs dosyasında false olarak ayarlanm - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - {0}' sorgu türü için sorgu deseninin bir uygulaması bulunamadı. '{1}' bulunamadı. Bir 'System.Core.dll' başvurusunu mu unuttunuz veya bir 'System.Linq' yönergesi mi kullanıyorsunuz? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + {0}' sorgu türü için sorgu deseninin bir uygulaması bulunamadı. '{1}' bulunamadı. Bir 'System.Core.dll' başvurusunu mu unuttunuz veya bir 'System.Linq' yönergesi mi kullanıyorsunuz? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index e63b85e974fa1..da142521d45a7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -7263,8 +7263,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - 未能找到源类型“{0}”的查询模式的实现。未找到“{1}”。是否缺少对“System.Core.dll”的引用,或者缺少针对“System.Linq”的 using 指令? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + 未能找到源类型“{0}”的查询模式的实现。未找到“{1}”。是否缺少对“System.Core.dll”的引用,或者缺少针对“System.Linq”的 using 指令? diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index c9adc0ddaaebc..6d985deb143d9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -7263,8 +7263,8 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep - Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? - 找不到來源類型 '{0}' 的查詢模式實作。找不到 '{1}'。是否遺漏了 'System.Core.dll' 的參考或 'System.Linq' 的 using 指示詞? + Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. Are you missing required assembly references or a using directive for 'System.Linq'? + 找不到來源類型 '{0}' 的查詢模式實作。找不到 '{1}'。是否遺漏了 'System.Core.dll' 的參考或 'System.Linq' 的 using 指示詞? diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 170d4cd8008e9..bc759aef2da18 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -12594,6 +12594,30 @@ class C Assert.True(options.TryGetValue("key3", out val)); Assert.Equal("value3", val); } + + [Theory, CombinatorialData] + public void TestAdditionalFileAnalyzer(bool registerFromInitialize) + { + var srcDirectory = Temp.CreateDirectory(); + + var source = "class C { }"; + var srcFile = srcDirectory.CreateFile("a.cs"); + srcFile.WriteAllText(source); + + var additionalText = "Additional Text"; + var additionalFile = srcDirectory.CreateFile("b.txt"); + additionalFile.WriteAllText(additionalText); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan); + + var output = VerifyOutput(srcDirectory, srcFile, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, + additionalFlags: new[] { "/additionalfile:" + additionalFile.Path }, + analyzers: analyzer); + Assert.Contains("b.txt(1,3): warning ID0001", output, StringComparison.Ordinal); + + CleanupAllGeneratedFiles(srcDirectory.Path); + } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs index db9983259404f..550fccb14cf90 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs @@ -160,7 +160,7 @@ public void DeeplyNestedGeneric() _ when ExecutionConditionUtil.IsCoreClrUnix => 1200, // 1200 _ when ExecutionConditionUtil.IsMonoDesktop => 730, // 730 (ExecutionArchitecture.x86, ExecutionConfiguration.Debug) => 460, // 270 - (ExecutionArchitecture.x86, ExecutionConfiguration.Release) => 1310, // 1290 + (ExecutionArchitecture.x86, ExecutionConfiguration.Release) => 1290, // 1290 (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) => 260, // 170 (ExecutionArchitecture.x64, ExecutionConfiguration.Release) => 730, // 730 _ => throw new Exception($"Unexpected configuration {ExecutionConditionUtil.Architecture} {ExecutionConditionUtil.Configuration}") diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs index 3d33e6d626ea2..af03e0d23f713 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs @@ -34,7 +34,8 @@ public void DiagnosticAnalyzerAllInOne() missingSyntaxKinds.Add(SyntaxKind.FunctionPointerType); var analyzer = new CSharpTrackingDiagnosticAnalyzer(); - CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }, options); analyzer.VerifyAllAnalyzerMembersWereCalled(); analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds(); analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxKinds); @@ -94,8 +95,10 @@ public class C public void AnalyzerDriverIsSafeAgainstAnalyzerExceptions() { var compilation = CreateCompilationWithMscorlib45(TestResource.AllInOneCSharpCode); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + ThrowingDiagnosticAnalyzer.VerifyAnalyzerEngineIsSafeAgainstExceptions(analyzer => - compilation.GetAnalyzerDiagnostics(new[] { analyzer }, null)); + compilation.GetAnalyzerDiagnostics(new[] { analyzer }, options)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index dad841e5c0b4b..304fcbd950d67 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -3586,5 +3587,163 @@ await compilationWithAnalyzers.GetAnalysisResultAsync(tree1, CancellationToken.N } } } + + [Theory, CombinatorialData] + public async Task TestAdditionalFileAnalyzer(bool registerFromInitialize) + { + var tree = CSharpSyntaxTree.ParseText(string.Empty); + var compilation = CreateCompilation(new[] { tree }); + compilation.VerifyDiagnostics(); + + AdditionalText additionalFile = new TestAdditionalText("Additional File Text"); + var options = new AnalyzerOptions(ImmutableArray.Create(additionalFile)); + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan); + var analyzers = ImmutableArray.Create(analyzer); + + var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None); + verifyDiagnostics(diagnostics); + + var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile, CancellationToken.None); + verifyDiagnostics(analysisResult.GetAllDiagnostics()); + verifyDiagnostics(analysisResult.AdditionalFileDiagnostics[additionalFile][analyzer]); + + analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); + verifyDiagnostics(analysisResult.GetAllDiagnostics()); + verifyDiagnostics(analysisResult.AdditionalFileDiagnostics[additionalFile][analyzer]); + + void verifyDiagnostics(ImmutableArray diagnostics) + { + var diagnostic = Assert.Single(diagnostics); + Assert.Equal(analyzer.Descriptor.Id, diagnostic.Id); + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind); + var location = (ExternalFileLocation)diagnostic.Location; + Assert.Equal(additionalFile.Path, location.FilePath); + Assert.Equal(diagnosticSpan, location.SourceSpan); + } + } + + [Theory, CombinatorialData] + public async Task TestMultipleAdditionalFileAnalyzers(bool registerFromInitialize, bool additionalFilesHaveSamePaths, bool firstAdditionalFileHasNullPath) + { + var tree = CSharpSyntaxTree.ParseText(string.Empty); + var compilation = CreateCompilationWithMscorlib45(new[] { tree }); + compilation.VerifyDiagnostics(); + + var path1 = firstAdditionalFileHasNullPath ? null : @"c:\file.txt"; + var path2 = additionalFilesHaveSamePaths ? path1 : @"file2.txt"; + + AdditionalText additionalFile1 = new TestAdditionalText("Additional File1 Text", path: path1); + AdditionalText additionalFile2 = new TestAdditionalText("Additional File2 Text", path: path2); + var additionalFiles = ImmutableArray.Create(additionalFile1, additionalFile2); + var options = new AnalyzerOptions(additionalFiles); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer1 = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001"); + var analyzer2 = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0002"); + var analyzers = ImmutableArray.Create(analyzer1, analyzer2); + + var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None); + verifyDiagnostics(diagnostics, analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths); + + var analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, CancellationToken.None); + verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); + analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, CancellationToken.None); + verifyAnalysisResult(analysisResult, analyzers, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths); + + var singleAnalyzerArray = ImmutableArray.Create(analyzer1); + analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile1, singleAnalyzerArray, CancellationToken.None); + verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile1), diagnosticSpan, additionalFilesHaveSamePaths); + analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile2, singleAnalyzerArray, CancellationToken.None); + verifyAnalysisResult(analysisResult, singleAnalyzerArray, ImmutableArray.Create(additionalFile2), diagnosticSpan, additionalFilesHaveSamePaths); + + analysisResult = await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None); + verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths); + + if (!additionalFilesHaveSamePaths) + { + verifyAnalysisResult(analysisResult, analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths, verifyGetAllDiagnostics: false); + } + + return; + + static void verifyDiagnostics( + ImmutableArray diagnostics, + ImmutableArray analyzers, + ImmutableArray additionalFiles, + TextSpan diagnosticSpan, + bool additionalFilesHaveSamePaths) + { + foreach (AdditionalFileAnalyzer analyzer in analyzers) + { + var fileIndex = 0; + foreach (var additionalFile in additionalFiles) + { + var applicableDiagnostics = diagnostics.WhereAsArray( + d => d.Id == analyzer.Descriptor.Id && PathUtilities.Comparer.Equals(d.Location.GetLineSpan().Path, additionalFile.Path)); + if (additionalFile.Path == null) + { + Assert.Empty(applicableDiagnostics); + continue; + } + + var expectedCount = additionalFilesHaveSamePaths ? additionalFiles.Length : 1; + Assert.Equal(expectedCount, applicableDiagnostics.Length); + + foreach (var diagnostic in applicableDiagnostics) + { + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind); + var location = (ExternalFileLocation)diagnostic.Location; + Assert.Equal(diagnosticSpan, location.SourceSpan); + } + + fileIndex++; + if (!additionalFilesHaveSamePaths || fileIndex == additionalFiles.Length) + { + diagnostics = diagnostics.RemoveRange(applicableDiagnostics); + } + } + } + + Assert.Empty(diagnostics); + } + + static void verifyAnalysisResult( + AnalysisResult analysisResult, + ImmutableArray analyzers, + ImmutableArray additionalFiles, + TextSpan diagnosticSpan, + bool additionalFilesHaveSamePaths, + bool verifyGetAllDiagnostics = true) + { + if (verifyGetAllDiagnostics) + { + verifyDiagnostics(analysisResult.GetAllDiagnostics(), analyzers, additionalFiles, diagnosticSpan, additionalFilesHaveSamePaths); + } + + foreach (var analyzer in analyzers) + { + var singleAnalyzerArray = ImmutableArray.Create(analyzer); + foreach (var additionalFile in additionalFiles) + { + var reportedDiagnostics = getReportedDiagnostics(analysisResult, analyzer, additionalFile); + verifyDiagnostics(reportedDiagnostics, singleAnalyzerArray, ImmutableArray.Create(additionalFile), diagnosticSpan, additionalFilesHaveSamePaths); + } + } + + return; + + static ImmutableArray getReportedDiagnostics(AnalysisResult analysisResult, DiagnosticAnalyzer analyzer, AdditionalText additionalFile) + { + if (analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFile, out var diagnosticsMap) && + diagnosticsMap.TryGetValue(analyzer, out var diagnostics)) + { + return diagnostics; + } + + return ImmutableArray.Empty; + } + } + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/AnonymousFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/AnonymousFunctionTests.cs index 2ef9fe06b06d4..44a1b22585a66 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/AnonymousFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/AnonymousFunctionTests.cs @@ -1241,7 +1241,7 @@ void local(Func fn) { Console.WriteLine(fn(0)); }"; - VerifyInPreview(source, expectedOutput: "1", metadataName: "$Program.<>c.<$Main>b__0_0", expectedIL: @" + VerifyInPreview(source, expectedOutput: "1", metadataName: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + ".<>c.<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">b__0_0", expectedIL: @" { // Code size 5 (0x5) .maxstack 2 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs index ef034d279d19e..d71df226fa18f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs @@ -2173,7 +2173,7 @@ void M() { } var cMembers = comp.GlobalNamespace.GetMember("C").GetMembers(); AssertEx.SetEqual(new[] { - "C C.<>Clone()", + "C C." + WellKnownMemberNames.CloneMethodName + "()", "System.Type C.EqualityContract.get", "System.Type C.EqualityContract { get; }", "C..ctor(System.Int32 i)", diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 121c41b652a5f..1e40af9d33a22 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -20233,7 +20233,7 @@ where n < 5 }" }, options: WithNonNullTypesTrue()); c.VerifyDiagnostics( - // (7,33): error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Where' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + // (7,33): error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Where' not found. Are you missing required assembly references or a using directive for 'System.Linq'? // var lowNums = from n in numbers Diagnostic(ErrorCode.ERR_QueryNoProviderStandard, "numbers").WithArguments("int[]", "Where").WithLocation(7, 33) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs index fb6c3a337e994..49cbffbc3acb4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs @@ -2520,7 +2520,7 @@ static void Main(string[] args) var semanticModel = compilation.GetSemanticModel(tree); semanticModel.GetDiagnostics().Verify( - // (21,30): error CS1935: Could not find an implementation of the query pattern for source type 'System.Collections.Generic.IEnumerable'. 'Select' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + // (21,30): error CS1935: Could not find an implementation of the query pattern for source type 'System.Collections.Generic.IEnumerable'. 'Select' not found. Are you missing required assembly references or a using directive for 'System.Linq'? // var q1 = from num in System.Linq.Enumerable.Range(4, 5).Where(n => n > 10) Diagnostic(ErrorCode.ERR_QueryNoProviderStandard, "System.Linq.Enumerable.Range(4, 5).Where(n => n > 10)").WithArguments("System.Collections.Generic.IEnumerable", "Select")); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 98bb862cebc41..287385c0e4b96 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -336,7 +336,7 @@ public void set_X() { } var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "C C.<>Clone()", + "C C." + WellKnownMemberNames.CloneMethodName + "()", "System.Type C.EqualityContract.get", "System.Type C.EqualityContract { get; }", "C..ctor(System.Int32 X, System.Int32 Y)", @@ -720,7 +720,7 @@ public static void Main() Left: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') Right: - IInvocationOperation (virtual C C.<>Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { }') + IInvocationOperation (virtual C C." + WellKnownMemberNames.CloneMethodName + @"()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { }') Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') Arguments(0) @@ -895,7 +895,7 @@ .maxstack 3 IL_0006: dup IL_0007: callvirt ""int C.X.get"" IL_000c: call ""void System.Console.WriteLine(int)"" - IL_0011: callvirt ""C C.<>Clone()"" + IL_0011: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0016: dup IL_0017: ldc.i4.5 IL_0018: callvirt ""void C.X.init"" @@ -933,7 +933,7 @@ .maxstack 3 IL_0002: newobj ""C..ctor(int, int)"" IL_0007: dup IL_0008: call ""void System.Console.WriteLine(object)"" - IL_000d: callvirt ""C C.<>Clone()"" + IL_000d: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0012: dup IL_0013: ldc.i4.5 IL_0014: callvirt ""void C.X.init"" @@ -973,13 +973,13 @@ .maxstack 3 IL_0002: newobj ""C..ctor(int, int)"" IL_0007: dup IL_0008: call ""void System.Console.WriteLine(object)"" - IL_000d: callvirt ""C C.<>Clone()"" + IL_000d: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0012: dup IL_0013: ldc.i4.5 IL_0014: callvirt ""void C.X.init"" IL_0019: dup IL_001a: call ""void System.Console.WriteLine(object)"" - IL_001f: callvirt ""C C.<>Clone()"" + IL_001f: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0024: dup IL_0025: ldc.i4.2 IL_0026: callvirt ""void C.Y.init"" @@ -1293,7 +1293,7 @@ implements class [mscorlib]System.IEquatable`1 .field private initonly int32 'k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) - .method public hidebysig specialname newslot virtual instance class Base '<>Clone' () cil managed + .method public hidebysig specialname newslot virtual instance class Base '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { IL_0000: ldarg.0 IL_0001: newobj instance void Base::.ctor(class Base) @@ -1471,7 +1471,7 @@ protected C(C other) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1480,6 +1480,9 @@ .maxstack 1 IL_0006: ret } "); + + var clone = verifier.Compilation.GetMember("C." + WellKnownMemberNames.CloneMethodName); + Assert.Equal("$", clone.Name); } [Fact] @@ -1510,7 +1513,7 @@ protected C(C other) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1550,7 +1553,7 @@ protected C(C other) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1584,7 +1587,7 @@ protected C(ref C other) : this(-1) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1618,7 +1621,7 @@ protected C(in C other) : this(-1) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1653,7 +1656,7 @@ protected C(out C other) : this(-1) var verifier = CompileAndVerify(source, expectedOutput: @"1 11"); - verifier.VerifyIL("C.<>Clone", @" + verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @" { // Code size 7 (0x7) .maxstack 1 @@ -1895,7 +1898,7 @@ .maxstack 3 IL_0001: ldc.i4.1 IL_0002: ldc.i4.2 IL_0003: newobj ""C..ctor(int, int, int)"" - IL_0008: callvirt ""C C.<>Clone()"" + IL_0008: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_000d: dup IL_000e: ldstr ""Y"" IL_0013: call ""int C.W(string)"" @@ -1918,7 +1921,7 @@ .maxstack 3 IWithOperation (OperationKind.With, Type: C) (Syntax: 'c with { Y ... = W(""X"") }') Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - CloneMethod: C C.<>Clone() + CloneMethod: C C." + WellKnownMemberNames.CloneMethodName + @"() Initializer: IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C) (Syntax: '{ Y = W(""Y"" ... = W(""X"") }') Initializers(2): @@ -1998,7 +2001,7 @@ .maxstack 3 ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') Value: - IInvocationOperation (virtual C C.<>Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + IInvocationOperation (virtual C C." + WellKnownMemberNames.CloneMethodName + @"()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') Arguments(0) @@ -2068,7 +2071,7 @@ .maxstack 3 IL_0000: ldc.i4.0 IL_0001: conv.i8 IL_0002: newobj ""C..ctor(long)"" - IL_0007: callvirt ""C C.<>Clone()"" + IL_0007: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_000c: dup IL_000d: ldc.i4.s 11 IL_000f: conv.i8 @@ -2120,7 +2123,7 @@ .locals init (S V_0) //s IL_0007: ldloca.s V_0 IL_0009: ldc.i4.s 11 IL_000b: call ""S..ctor(int)"" - IL_0010: callvirt ""C C.<>Clone()"" + IL_0010: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0015: dup IL_0016: ldloc.0 IL_0017: call ""long S.op_Implicit(S)"" @@ -2253,7 +2256,7 @@ .locals init (S V_0) //s IL_0005: ldloca.s V_0 IL_0007: ldc.i4.s 11 IL_0009: call ""S..ctor(int)"" - IL_000e: callvirt ""C C.<>Clone()"" + IL_000e: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0013: dup IL_0014: ldloc.0 IL_0015: call ""int S.op_Implicit(S)"" @@ -2439,7 +2442,7 @@ public static void Main() IWithOperation (OperationKind.With, Type: C) (Syntax: 'c with { X = 2 }') Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - CloneMethod: C C.<>Clone() + CloneMethod: C C." + WellKnownMemberNames.CloneMethodName + @"() Initializer: IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C) (Syntax: '{ X = 2 }') Initializers(1): @@ -2493,7 +2496,7 @@ public static void Main() ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c with { X = 2 }') Value: - IInvocationOperation (virtual C C.<>Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { X = 2 }') + IInvocationOperation (virtual C C." + WellKnownMemberNames.CloneMethodName + @"()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { X = 2 }') Instance Receiver: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') Arguments(0) @@ -2667,7 +2670,7 @@ public static void Main() IWithOperation (OperationKind.With, Type: C, IsInvalid) (Syntax: 'c with { 5 }') Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - CloneMethod: C C.<>Clone() + CloneMethod: C C." + WellKnownMemberNames.CloneMethodName + @"() Initializer: IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C, IsInvalid) (Syntax: '{ 5 }') Initializers(1): @@ -2680,7 +2683,7 @@ public static void Main() IWithOperation (OperationKind.With, Type: C, IsInvalid) (Syntax: 'c with { ') Operand: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - CloneMethod: C C.<>Clone() + CloneMethod: C C." + WellKnownMemberNames.CloneMethodName + @"() Initializer: IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C, IsInvalid) (Syntax: '{ ') Initializers(0)"); @@ -3329,7 +3332,7 @@ public C M(C c) => c with // Code size 33 (0x21) .maxstack 3 IL_0000: ldarg.1 - IL_0001: callvirt ""C C.<>Clone()"" + IL_0001: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0006: dup IL_0007: ldc.i4.5 IL_0008: callvirt ""void C.X.set"" @@ -3379,7 +3382,7 @@ .maxstack 3 IL_000f: callvirt ""ref int C.X.get"" IL_0014: ldind.i4 IL_0015: call ""void System.Console.WriteLine(int)"" - IL_001a: callvirt ""C C.<>Clone()"" + IL_001a: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_001f: dup IL_0020: callvirt ""ref int C.X.get"" IL_0025: ldc.i4.1 @@ -3704,7 +3707,7 @@ public void Inheritance_09() var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "C C.<>Clone()", + "C C." + WellKnownMemberNames.CloneMethodName + "()", "System.Type C.EqualityContract.get", "System.Type C.EqualityContract { get; }", "C..ctor(System.Int32 X, System.Int32 Y)", @@ -4215,7 +4218,7 @@ record C(object P) var expectedMembers = new[] { - "A B.<>Clone()", + "A B." + WellKnownMemberNames.CloneMethodName + "()", "System.Type B.EqualityContract.get", "System.Type B.EqualityContract { get; }", "B..ctor(System.Object P, System.Object Q)", @@ -4238,7 +4241,7 @@ record C(object P) expectedMembers = new[] { - "C C.<>Clone()", + "C C." + WellKnownMemberNames.CloneMethodName + "()", "System.Type C.EqualityContract.get", "System.Type C.EqualityContract { get; }", "C..ctor(System.Object P)", @@ -5466,7 +5469,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class A A_1) { ldnull throw } - .method public hidebysig newslot specialname abstract virtual instance class A '<>Clone'() { } + .method public hidebysig newslot specialname abstract virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { } .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::get_EqualityContract() @@ -5500,7 +5503,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class B A_1) { ldnull throw } - .method public hidebysig specialname abstract virtual instance class A '<>Clone'() { } + .method public hidebysig specialname abstract virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { } .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type B::get_EqualityContract() @@ -5560,7 +5563,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class A A_1) { ldnull throw } - .method public hidebysig newslot specialname abstract virtual instance class A '<>Clone'() { } + .method public hidebysig newslot specialname abstract virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { } .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::GetProperty1() @@ -5634,7 +5637,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class A A_1) { ldnull throw } - .method public hidebysig newslot specialname abstract virtual instance class A '<>Clone'() { } + .method public hidebysig newslot specialname abstract virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { } .property instance class [mscorlib]System.Type EqualityContract() { .get instance class [mscorlib]System.Type A::'EqualityContract<>get'() @@ -5728,7 +5731,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class A A_1) { ldnull throw } - .method public hidebysig newslot specialname abstract virtual instance class A '<>Clone'() { } + .method public hidebysig newslot specialname abstract virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { } .property instance class [mscorlib]System.Type modopt(int32) EqualityContract() { .get instance class [mscorlib]System.Type modopt(int32) A::get_EqualityContract() @@ -5851,7 +5854,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor() ret } .method family hidebysig specialname rtspecialname instance void .ctor(class A A_1) { ldnull throw } - .method public hidebysig newslot specialname virtual instance class A '<>Clone'() { ldnull throw } + .method public hidebysig newslot specialname virtual instance class A '" + WellKnownMemberNames.CloneMethodName + @"'() { ldnull throw } .property instance object P() { .get instance object A::get_P() @@ -6795,7 +6798,7 @@ public void CopyCtor_MissingInMetadata() var ilSource = @" .class public auto ansi beforefieldinit B extends [mscorlib]System.Object { - .method public hidebysig specialname newslot virtual instance class B '<>Clone' () cil managed + .method public hidebysig specialname newslot virtual instance class B '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { IL_0000: ldnull IL_0001: throw @@ -6870,7 +6873,7 @@ public void CopyCtor_InaccessibleInMetadata() var ilSource = @" .class public auto ansi beforefieldinit B extends [mscorlib]System.Object { - .method public hidebysig specialname newslot virtual instance class B '<>Clone' () cil managed + .method public hidebysig specialname newslot virtual instance class B '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { IL_0000: ldnull IL_0001: throw @@ -6939,7 +6942,7 @@ .class public auto ansi beforefieldinit B extends [mscorlib]System.Object { INJECT - .method public hidebysig specialname newslot virtual instance class B '<>Clone' () cil managed + .method public hidebysig specialname newslot virtual instance class B '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { IL_0000: ldarg.0 IL_0001: newobj instance void B::.ctor(class B) @@ -7126,7 +7129,7 @@ .method family hidebysig specialname rtspecialname instance void .ctor ( class B IL_000a: ret } - .method public hidebysig specialname newslot virtual instance class B`1 '<>Clone' () cil managed + .method public hidebysig specialname newslot virtual instance class B`1 '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { IL_0000: ldarg.0 IL_0001: newobj instance void class B`1::.ctor(class B`1) @@ -8449,7 +8452,7 @@ record B(int X, int Y) : A var actualMembers = comp.GetMember("B").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "A B.<>Clone()", + "A B." + WellKnownMemberNames.CloneMethodName + "()", "System.Type B.EqualityContract.get", "System.Type B.EqualityContract { get; }", "B..ctor(System.Int32 X, System.Int32 Y)", @@ -8500,7 +8503,7 @@ record B(int X, int Y) : A var actualMembers = comp.GetMember("B").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "A B.<>Clone()", + "A B." + WellKnownMemberNames.CloneMethodName + "()", "System.Type B.EqualityContract.get", "System.Type B.EqualityContract { get; }", "B..ctor(System.Int32 X, System.Int32 Y)", @@ -8531,13 +8534,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public final hidebysig virtual instance bool Equals ( @@ -8625,13 +8628,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public newslot hidebysig virtual instance bool Equals ( @@ -8719,13 +8722,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public newslot hidebysig instance bool Equals ( @@ -8813,13 +8816,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public newslot hidebysig virtual instance int32 Equals ( @@ -8984,13 +8987,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -9156,13 +9159,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -9267,13 +9270,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -9519,13 +9522,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -9624,13 +9627,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -9722,13 +9725,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -10053,13 +10056,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -10147,13 +10150,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -10241,13 +10244,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -10335,13 +10338,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -10425,13 +10428,13 @@ extends A { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -11711,13 +11714,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -11873,13 +11876,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -12000,13 +12003,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -12113,13 +12116,13 @@ extends System.Object { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -12192,13 +12195,13 @@ extends A { // Methods .method public hidebysig specialname newslot virtual - instance class A '<>Clone' () cil managed + instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed { .maxstack 8 IL_0000: ldnull IL_0001: throw - } // end of method A::'<>Clone' + } // end of method A::'" + WellKnownMemberNames.CloneMethodName + @"' .method public hidebysig virtual instance bool Equals ( @@ -12493,7 +12496,7 @@ .locals init (C V_0, //c IL_0007: callvirt ""void C.X.init"" IL_000c: stloc.0 IL_000d: ldloc.0 - IL_000e: callvirt ""C C.<>Clone()"" + IL_000e: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0013: dup IL_0014: ldc.i4.2 IL_0015: callvirt ""void C.X.init"" @@ -12509,7 +12512,7 @@ .locals init (C V_0, //c IL_0037: callvirt ""void C.X.init"" IL_003c: stloc.1 IL_003d: ldloc.1 - IL_003e: callvirt ""C C.<>Clone()"" + IL_003e: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0043: castclass ""D"" IL_0048: dup IL_0049: ldc.i4.2 @@ -12549,13 +12552,13 @@ .locals init (C V_0, //c IL_00b3: ldloc.2 IL_00b4: stloc.s V_4 IL_00b6: ldloc.3 - IL_00b7: callvirt ""C C.<>Clone()"" + IL_00b7: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_00bc: dup IL_00bd: ldc.i4.3 IL_00be: callvirt ""void C.X.init"" IL_00c3: stloc.3 IL_00c4: ldloc.s V_4 - IL_00c6: callvirt ""C C.<>Clone()"" + IL_00c6: callvirt ""C C." + WellKnownMemberNames.CloneMethodName + @"()"" IL_00cb: dup IL_00cc: ldc.i4.4 IL_00cd: callvirt ""void C.X.init"" @@ -14343,9 +14346,9 @@ .maxstack 2 VerifyVirtualMethod(comp.GetMember("B.get_EqualityContract"), isOverride: true); VerifyVirtualMethod(comp.GetMember("C.get_EqualityContract"), isOverride: true); - VerifyVirtualMethod(comp.GetMember("A.<>Clone"), isOverride: false); - VerifyVirtualMethod(comp.GetMember("B.<>Clone"), isOverride: true); - VerifyVirtualMethod(comp.GetMember("C.<>Clone"), isOverride: true); + VerifyVirtualMethod(comp.GetMember("A." + WellKnownMemberNames.CloneMethodName), isOverride: false); + VerifyVirtualMethod(comp.GetMember("B." + WellKnownMemberNames.CloneMethodName), isOverride: true); + VerifyVirtualMethod(comp.GetMember("C." + WellKnownMemberNames.CloneMethodName), isOverride: true); VerifyVirtualMethod(comp.GetMember("A.GetHashCode"), isOverride: true); VerifyVirtualMethod(comp.GetMember("B.GetHashCode"), isOverride: true); @@ -14787,7 +14790,7 @@ record C : B; var actualMembers = comp.GetMember("B").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "A B.<>Clone()", + "A B." + WellKnownMemberNames.CloneMethodName + "()", "System.Type B.EqualityContract { get; }", "System.Type B.EqualityContract.get", "System.Int32 B.GetHashCode()", @@ -14913,7 +14916,7 @@ static void Main() var actualMembers = comp.GetMember("B1").GetMembers().ToTestDisplayStrings(); var expectedMembers = new[] { - "A B1.<>Clone()", + "A B1." + WellKnownMemberNames.CloneMethodName + "()", "System.Type B1.EqualityContract.get", "System.Type B1.EqualityContract { get; }", "B1..ctor(System.Int32 P)", diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index c27901c0e3f25..23f35e9db2bac 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -16845,7 +16845,7 @@ where n > 3 } } ").VerifyDiagnostics( - // (8,40): error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Where' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + // (8,40): error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Where' not found. Are you missing required assembly references or a using directive for 'System.Linq'? // nums Diagnostic(ErrorCode.ERR_QueryNoProviderStandard, "nums").WithArguments("int[]", "Where")); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs index 8b5212d81f704..4c8f0e9c3f4e2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs @@ -39,6 +39,8 @@ public void Simple_01() Assert.Same(entryPoint, comp.GetEntryPoint(default)); Assert.False(entryPoint.CanBeReferencedByName); Assert.False(entryPoint.ContainingType.CanBeReferencedByName); + Assert.Equal("
$", entryPoint.Name); + Assert.Equal("$", entryPoint.ContainingType.Name); } private static void AssertEntryPointParameter(SynthesizedSimpleProgramEntryPointSymbol entryPoint) @@ -6053,13 +6055,13 @@ void validateAssembly(PEAssembly assembly) var methodName = peReader.GetString(methodDef.Name); var expectedFlags = methodName switch { - "<$Main>g__forwardRef|0_0" => MethodImplAttributes.ForwardRef, - "<$Main>g__noInlining|0_1" => MethodImplAttributes.NoInlining, - "<$Main>g__noOptimization|0_2" => MethodImplAttributes.NoOptimization, - "<$Main>g__synchronized|0_3" => MethodImplAttributes.Synchronized, - "<$Main>g__internalCallStatic|0_4" => MethodImplAttributes.InternalCall, + "<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__forwardRef|0_0" => MethodImplAttributes.ForwardRef, + "<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__noInlining|0_1" => MethodImplAttributes.NoInlining, + "<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__noOptimization|0_2" => MethodImplAttributes.NoOptimization, + "<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__synchronized|0_3" => MethodImplAttributes.Synchronized, + "<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__internalCallStatic|0_4" => MethodImplAttributes.InternalCall, ".ctor" => MethodImplAttributes.IL, - "$Main" => MethodImplAttributes.IL, + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName => MethodImplAttributes.IL, _ => throw TestExceptionUtilities.UnexpectedValue(methodName) }; @@ -6108,12 +6110,12 @@ public void Attributes_03() void validate(ModuleSymbol module) { - var cClass = module.GlobalNamespace.GetMember(SimpleProgramNamedTypeSymbol.UnspeakableName); + var cClass = module.GlobalNamespace.GetMember(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName); Assert.Equal(new[] { "CompilerGeneratedAttribute" }, GetAttributeNames(cClass.GetAttributes().As())); - Assert.Empty(cClass.GetMethod(SynthesizedSimpleProgramEntryPointSymbol.UnspeakableName).GetAttributes()); + Assert.Empty(cClass.GetMethod(WellKnownMemberNames.TopLevelStatementsEntryPointMethodName).GetAttributes()); - var localFn1 = cClass.GetMethod("<$Main>g__local1|0_0"); + var localFn1 = cClass.GetMethod("<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">g__local1|0_0"); Assert.Empty(localFn1.GetAttributes()); validateLocalFunction(localFn1); @@ -6572,7 +6574,7 @@ private void Handle1(SymbolStartAnalysisContext context) private void Handle2(SymbolStartAnalysisContext context) { - Assert.Equal(SimpleProgramNamedTypeSymbol.UnspeakableName, context.Symbol.ToTestDisplayString()); + Assert.Equal(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, context.Symbol.ToTestDisplayString()); Interlocked.Increment(ref FireCount3); context.RegisterSymbolEndAction(Handle5); @@ -6609,7 +6611,7 @@ private void Handle4(SymbolAnalysisContext context) private void Handle5(SymbolAnalysisContext context) { - Assert.Equal(SimpleProgramNamedTypeSymbol.UnspeakableName, context.Symbol.ToTestDisplayString()); + Assert.Equal(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, context.Symbol.ToTestDisplayString()); Interlocked.Increment(ref FireCount8); } } @@ -7256,7 +7258,7 @@ private void Handle3(SymbolAnalysisContext context) case "C1": Interlocked.Increment(ref FireCount3); break; - case SimpleProgramNamedTypeSymbol.UnspeakableName: + case WellKnownMemberNames.TopLevelStatementsEntryPointTypeName: Interlocked.Increment(ref FireCount4); break; default: @@ -7593,14 +7595,14 @@ public void Return_01() { _ = ConditionalSkipReason.NativePdbRequiresDesktop; - comp.VerifyPdb("$Program.$Main", -@" + comp.VerifyPdb(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "." + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, +@$" - + - + @@ -7616,6 +7618,11 @@ public void Return_01() } } + private static string EscapeForXML(string toEscape) + { + return toEscape.Replace("<", "<").Replace(">", ">"); + } + [Fact] public void Return_02() { @@ -7635,14 +7642,14 @@ public void Return_02() { _ = ConditionalSkipReason.NativePdbRequiresDesktop; - comp.VerifyPdb("$Program.$Main", -@" + comp.VerifyPdb(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "." + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, +@$" - + - + @@ -7682,16 +7689,16 @@ public void Return_03() { _ = ConditionalSkipReason.NativePdbRequiresDesktop; - comp.VerifyPdb("$Program+<$Main>d__0.MoveNext", -@" + comp.VerifyPdb(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "+<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">d__0.MoveNext", +@$" - + - + - + @@ -7712,8 +7719,8 @@ public void Return_03() - - + + @@ -7745,16 +7752,16 @@ public void Return_04() { _ = ConditionalSkipReason.NativePdbRequiresDesktop; - comp.VerifyPdb("$Program+<$Main>d__0.MoveNext", -@" + comp.VerifyPdb(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "+<" + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName + ">d__0.MoveNext", +@$" - + - + - + @@ -7776,8 +7783,8 @@ public void Return_04() - - + + @@ -8214,7 +8221,7 @@ public void ThrowStatement_02() var comp = CreateCompilation(text, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp).VerifyIL("", sequencePoints: "$Program.$Main", source: text, expectedIL: + CompileAndVerify(comp).VerifyIL("", sequencePoints: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "." + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, source: text, expectedIL: @" { // Code size 2 (0x2) @@ -8292,7 +8299,7 @@ public void CheckedStatement_01() "; var comp = CreateCompilation(text, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "3").VerifyIL("", sequencePoints: "$Program.$Main", source: text, expectedIL: + CompileAndVerify(comp, expectedOutput: "3").VerifyIL("", sequencePoints: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "." + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, source: text, expectedIL: @" { // Code size 20 (0x14) @@ -8339,7 +8346,7 @@ public void UncheckedStatement_01() "; var comp = CreateCompilation(text, options: TestOptions.DebugExe.WithOverflowChecks(true), parseOptions: DefaultParseOptions); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "3").VerifyIL("", sequencePoints: "$Program.$Main", source: text, expectedIL: + CompileAndVerify(comp, expectedOutput: "3").VerifyIL("", sequencePoints: WellKnownMemberNames.TopLevelStatementsEntryPointTypeName + "." + WellKnownMemberNames.TopLevelStatementsEntryPointMethodName, source: text, expectedIL: @" { // Code size 20 (0x14) diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs index cb5c9417a9c96..fe13548b9a2c9 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelAPITests.cs @@ -813,7 +813,7 @@ public class A var typeA = mems.Where(s => s.Name == "A").Select(s => s); Assert.Equal(1, typeA.Count()); - var invalid = mems.Where(s => s.Name == SimpleProgramNamedTypeSymbol.UnspeakableName).Select(s => s); + var invalid = mems.Where(s => s.Name == WellKnownMemberNames.TopLevelStatementsEntryPointTypeName).Select(s => s); Assert.Equal(1, invalid.Count()); } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs index be448d6fe80de..4a5ab7c5cced1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/RecordTests.cs @@ -1056,7 +1056,7 @@ record C }"); var members = comp.GlobalNamespace.GetTypeMember("C").GetMembers(); AssertEx.Equal(new[] { - "C! C.<>Clone()", + "C! C." + WellKnownMemberNames.CloneMethodName + "()", "System.Type! C.EqualityContract.get", "System.Type! C.EqualityContract { get; }", "System.Int32 C.k__BackingField", @@ -1429,7 +1429,7 @@ public static void Main() Assert.True(clone.IsAbstract); Assert.Equal(0, clone.ParameterCount); Assert.Equal(0, clone.Arity); - Assert.Equal("R R.<>Clone()", clone.ToTestDisplayString()); + Assert.Equal("R R." + WellKnownMemberNames.CloneMethodName + "()", clone.ToTestDisplayString()); var r2 = comp.GlobalNamespace.GetTypeMember("R2"); var clone2 = (MethodSymbol)r2.GetMembers(WellKnownMemberNames.CloneMethodName).Single(); @@ -1439,7 +1439,7 @@ public static void Main() Assert.Equal(0, clone2.ParameterCount); Assert.Equal(0, clone2.Arity); Assert.True(clone2.OverriddenMethod.Equals(clone, TypeCompareKind.ConsiderEverything)); - Assert.Equal("R R2.<>Clone()", clone2.ToTestDisplayString()); + Assert.Equal("R R2." + WellKnownMemberNames.CloneMethodName + "()", clone2.ToTestDisplayString()); var r3 = comp.GlobalNamespace.GetTypeMember("R3"); var clone3 = (MethodSymbol)r3.GetMembers(WellKnownMemberNames.CloneMethodName).Single(); @@ -1449,7 +1449,7 @@ public static void Main() Assert.Equal(0, clone3.ParameterCount); Assert.Equal(0, clone3.Arity); Assert.True(clone3.OverriddenMethod.Equals(clone2, TypeCompareKind.ConsiderEverything)); - Assert.Equal("R R3.<>Clone()", clone3.ToTestDisplayString()); + Assert.Equal("R R3." + WellKnownMemberNames.CloneMethodName + "()", clone3.ToTestDisplayString()); var r4 = comp.GlobalNamespace.GetTypeMember("R4"); var clone4 = (MethodSymbol)r4.GetMembers(WellKnownMemberNames.CloneMethodName).Single(); @@ -1459,7 +1459,7 @@ public static void Main() Assert.Equal(0, clone4.ParameterCount); Assert.Equal(0, clone4.Arity); Assert.True(clone4.OverriddenMethod.Equals(clone3, TypeCompareKind.ConsiderEverything)); - Assert.Equal("R R4.<>Clone()", clone4.ToTestDisplayString()); + Assert.Equal("R R4." + WellKnownMemberNames.CloneMethodName + "()", clone4.ToTestDisplayString()); var r5 = comp.GlobalNamespace.GetTypeMember("R5"); var clone5 = (MethodSymbol)r5.GetMembers(WellKnownMemberNames.CloneMethodName).Single(); @@ -1469,7 +1469,7 @@ public static void Main() Assert.Equal(0, clone5.ParameterCount); Assert.Equal(0, clone5.Arity); Assert.True(clone5.OverriddenMethod.Equals(clone4, TypeCompareKind.ConsiderEverything)); - Assert.Equal("R R5.<>Clone()", clone5.ToTestDisplayString()); + Assert.Equal("R R5." + WellKnownMemberNames.CloneMethodName + "()", clone5.ToTestDisplayString()); var verifier = CompileAndVerify(comp, expectedOutput: "", verify: Verification.Passes); verifier.VerifyIL("C.Main", @" @@ -1477,10 +1477,10 @@ public static void Main() // Code size 28 (0x1c) .maxstack 1 IL_0000: newobj ""R3..ctor()"" - IL_0005: callvirt ""R R.<>Clone()"" + IL_0005: callvirt ""R R." + WellKnownMemberNames.CloneMethodName + @"()"" IL_000a: pop IL_000b: newobj ""R5..ctor()"" - IL_0010: callvirt ""R R.<>Clone()"" + IL_0010: callvirt ""R R." + WellKnownMemberNames.CloneMethodName + @"()"" IL_0015: castclass ""R4"" IL_001a: pop IL_001b: ret diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/TypeTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/TypeTests.cs index 978c52fa45df2..ccd3d0fa64368 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/TypeTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/TypeTests.cs @@ -1450,7 +1450,7 @@ public void ErrorTypeTest01() var errSymbol = comp.SourceModule.GlobalNamespace.GetMembers().FirstOrDefault() as NamedTypeSymbol; Assert.NotNull(errSymbol); - Assert.Equal(SimpleProgramNamedTypeSymbol.UnspeakableName, errSymbol.Name); + Assert.Equal(WellKnownMemberNames.TopLevelStatementsEntryPointTypeName, errSymbol.Name); Assert.False(errSymbol.IsErrorType(), "ErrorType"); Assert.False(errSymbol.IsImplicitClass, "ImplicitClass"); } diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs index ccd581021f27a..74ef0bf56f1e7 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/AnalysisContextInfoTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -23,23 +24,25 @@ public void InitializeTest() var parseOptions = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.None) .WithFeatures(new[] { new KeyValuePair("IOperation", "true") }); var compilation = CreateCompilation(code, parseOptions: parseOptions); - - Verify(compilation, nameof(AnalysisContext.RegisterCodeBlockAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCodeBlockStartAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCompilationAction)); - Verify(compilation, nameof(AnalysisContext.RegisterCompilationStartAction)); - Verify(compilation, nameof(AnalysisContext.RegisterOperationAction)); - Verify(compilation, nameof(AnalysisContext.RegisterOperationBlockAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSemanticModelAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSymbolAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSyntaxNodeAction)); - Verify(compilation, nameof(AnalysisContext.RegisterSyntaxTreeAction)); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + + Verify(compilation, options, nameof(AnalysisContext.RegisterCodeBlockAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCodeBlockStartAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCompilationAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterCompilationStartAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterOperationAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterOperationBlockAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSemanticModelAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSymbolAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSyntaxNodeAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterSyntaxTreeAction)); + Verify(compilation, options, nameof(AnalysisContext.RegisterAdditionalFileAction)); } - private static void Verify(Compilation compilation, string context) + private static void Verify(Compilation compilation, AnalyzerOptions options, string context) { var analyzer = new Analyzer(s => context == s); - var diagnostics = compilation.GetAnalyzerDiagnostics(new DiagnosticAnalyzer[] { analyzer }); + var diagnostics = compilation.GetAnalyzerDiagnostics(new DiagnosticAnalyzer[] { analyzer }, options); Assert.Equal(1, diagnostics.Length); Assert.True(diagnostics[0].Descriptor.Description.ToString().IndexOf(analyzer.Info.GetContext()) >= 0); @@ -73,7 +76,8 @@ public override void Initialize(AnalysisContext c) c.RegisterSemanticModelAction(b => ThrowIfMatch(nameof(c.RegisterSemanticModelAction), new AnalysisContextInfo(b.SemanticModel))); c.RegisterSymbolAction(b => ThrowIfMatch(nameof(c.RegisterSymbolAction), new AnalysisContextInfo(b.Compilation, b.Symbol)), SymbolKind.NamedType); c.RegisterSyntaxNodeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxNodeAction), new AnalysisContextInfo(b.SemanticModel.Compilation, b.Node)), SyntaxKind.ReturnStatement); - c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, b.Tree))); + c.RegisterSyntaxTreeAction(b => ThrowIfMatch(nameof(c.RegisterSyntaxTreeAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.Tree)))); + c.RegisterAdditionalFileAction(b => ThrowIfMatch(nameof(c.RegisterAdditionalFileAction), new AnalysisContextInfo(b.Compilation, new SourceOrAdditionalFile(b.AdditionalFile)))); } private void ThrowIfMatch(string context, AnalysisContextInfo info) diff --git a/src/Compilers/Core/CommandLine/BuildProtocol.cs b/src/Compilers/Core/CommandLine/BuildProtocol.cs index 0a20e7f6db16b..f0389f538128d 100644 --- a/src/Compilers/Core/CommandLine/BuildProtocol.cs +++ b/src/Compilers/Core/CommandLine/BuildProtocol.cs @@ -82,11 +82,12 @@ public static BuildRequest Create(RequestLanguage language, { Debug.Assert(!string.IsNullOrWhiteSpace(compilerHash), "CompilerHash is required to send request to the build server"); - Log("Creating BuildRequest"); - Log($"Working directory: {workingDirectory}"); - Log($"Temp directory: {tempDirectory}"); - Log($"Lib directory: {libDirectory ?? "null"}"); - Log($"Compiler hash: {compilerHash}"); + Log($@" +Creating BuildRequest + Working directory: {workingDirectory} + Temp directory: {tempDirectory} + Lib directory: {libDirectory ?? null} + Compiler hash: {compilerHash}"); var requestLength = args.Count + 1 + (libDirectory == null ? 0 : 1); var requestArgs = new List(requestLength); @@ -108,7 +109,6 @@ public static BuildRequest Create(RequestLanguage language, for (int i = 0; i < args.Count; ++i) { var arg = args[i]; - Log($"argument[{i}] = {arg}"); requestArgs.Add(new Argument(ArgumentId.CommandLineArgument, i, arg)); } @@ -118,7 +118,7 @@ public static BuildRequest Create(RequestLanguage language, public static BuildRequest CreateShutdown() { var requestArgs = new[] { new Argument(ArgumentId.Shutdown, argumentIndex: 0, value: "") }; - return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, GetCommitHash(), requestArgs); + return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, GetCommitHash() ?? "", requestArgs); } /// @@ -127,19 +127,17 @@ public static BuildRequest CreateShutdown() /// The total request size must be less than 1MB. /// /// null if the Request was too large, the Request otherwise. - public static async Task ReadAsync(Stream inStream, CancellationToken cancellationToken) + public static async Task ReadAsync(Stream inStream, CancellationToken cancellationToken) { // Read the length of the request var lengthBuffer = new byte[4]; - Log("Reading length of request"); await ReadAllAsync(inStream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToInt32(lengthBuffer, 0); // Back out if the request is > 1MB if (length > 0x100000) { - Log("Request is over 1MB in length, cancelling read."); - return null; + throw new ArgumentException("Request is over 1MB in length"); } cancellationToken.ThrowIfCancellationRequested(); @@ -150,7 +148,6 @@ public static BuildRequest CreateShutdown() cancellationToken.ThrowIfCancellationRequested(); - Log("Parsing request"); // Parse the request into the Request data structure. using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode)) { @@ -182,8 +179,6 @@ public static BuildRequest CreateShutdown() using (var memoryStream = new MemoryStream()) using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) { - // Format the request. - Log("Formatting request"); writer.Write(ProtocolVersion); writer.Write((uint)Language); writer.Write(CompilerHash); @@ -203,17 +198,12 @@ public static BuildRequest CreateShutdown() // Back out if the request is > 1 MB if (memoryStream.Length > 0x100000) { - Log("Request is over 1MB in length, cancelling write"); - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("Request is over 1MB in length"); } - // Send the request to the server - Log("Writing length of request."); await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, cancellationToken).ConfigureAwait(false); - Log("Writing request of size {0}", length); - // Write the request memoryStream.Position = 0; await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -311,8 +301,6 @@ public async Task WriteAsync(Stream outStream, using (var memoryStream = new MemoryStream()) using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) { - // Format the response - Log("Formatting Response"); writer.Write((int)Type); AddResponseBody(writer); @@ -325,7 +313,6 @@ public async Task WriteAsync(Stream outStream, // Write the length of the response int length = checked((int)memoryStream.Length); - Log("Writing response length"); // There is no way to know the number of bytes written to // the pipe stream. We just have to assume all of them are written. await outStream.WriteAsync(BitConverter.GetBytes(length), @@ -333,8 +320,6 @@ await outStream.WriteAsync(BitConverter.GetBytes(length), 4, cancellationToken).ConfigureAwait(false); - // Write the response - Log("Writing response of size {0}", length); memoryStream.Position = 0; await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -350,14 +335,12 @@ await outStream.WriteAsync(BitConverter.GetBytes(length), /// public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { - Log("Reading response length"); // Read the response length var lengthBuffer = new byte[4]; await ReadAllAsync(stream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToUInt32(lengthBuffer, 0); // Read the response - Log("Reading response of length {0}", length); var responseBuffer = new byte[length]; await ReadAllAsync(stream, responseBuffer, @@ -381,7 +364,7 @@ await ReadAllAsync(stream, case ResponseType.Shutdown: return ShutdownBuildResponse.Create(reader); case ResponseType.Rejected: - return new RejectedBuildResponse(); + return RejectedBuildResponse.Create(reader); default: throw new InvalidOperationException("Received invalid response type from server."); } @@ -505,13 +488,30 @@ protected override void AddResponseBody(BinaryWriter writer) { } internal sealed class RejectedBuildResponse : BuildResponse { + public string Reason; + public override ResponseType Type => ResponseType.Rejected; + public RejectedBuildResponse(string reason) + { + Reason = reason; + } + /// /// AnalyzerInconsistency has no body. /// /// - protected override void AddResponseBody(BinaryWriter writer) { } + protected override void AddResponseBody(BinaryWriter writer) + { + WriteLengthPrefixedString(writer, Reason); + } + + public static RejectedBuildResponse Create(BinaryReader reader) + { + var reason = ReadLengthPrefixedString(reader); + Debug.Assert(reason is object); + return new RejectedBuildResponse(reason); + } } // The id numbers below are just random. It's useful to use id numbers @@ -592,14 +592,13 @@ public static void WriteLengthPrefixedString(BinaryWriter writer, string? value) /// Reads the value of of the assembly is defined in /// /// The hash value of the current assembly or an empty string - public static string GetCommitHash() + public static string? GetCommitHash() { var hashAttributes = typeof(BuildRequest).Assembly.GetCustomAttributes(); var hashAttributeCount = hashAttributes.Count(); if (hashAttributeCount != 1) { - Log($"Error reading CommitHashAttribute. Exactly 1 attribute is required, found {hashAttributeCount}"); - return string.Empty; + return null; } return hashAttributes.Single().Hash; } @@ -616,21 +615,16 @@ internal static async Task ReadAllAsync( int totalBytesRead = 0; do { - Log("Attempting to read {0} bytes from the stream", - count - totalBytesRead); int bytesRead = await stream.ReadAsync(buffer, totalBytesRead, count - totalBytesRead, cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { - Log("Unexpected -- read 0 bytes from the stream."); throw new EndOfStreamException("Reached end of stream before end of read."); } - Log("Read {0} bytes", bytesRead); totalBytesRead += bytesRead; } while (totalBytesRead < count); - Log("Finished read"); } } } diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 518e8d23ca413..d261b625c3c9d 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -556,6 +556,9 @@ Syntax tree doesn't belong to the underlying 'Compilation'. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Resource stream ended at {0} bytes, expected {1} bytes. diff --git a/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs b/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs index 2e96c659b577b..21401827908ae 100644 --- a/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs +++ b/src/Compilers/Core/Portable/Diagnostic/ExternalFileLocation.cs @@ -32,6 +32,8 @@ public override TextSpan SourceSpan } } + public string FilePath => _lineSpan.Path; + public override FileLinePositionSpan GetLineSpan() { return _lineSpan; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs index 51ade1a134f6f..1043481b34c93 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs @@ -2,68 +2,74 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + +using System.Diagnostics; using System.Text; -using Microsoft.CodeAnalysis.Operations; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { /// /// this hold onto analyzer executor context which will be used later to put context information in analyzer exception if it occurs. /// - internal struct AnalysisContextInfo + internal readonly struct AnalysisContextInfo { - private readonly Compilation _compilation; - private readonly IOperation _operation; - private readonly ISymbol _symbol; - private readonly SyntaxTree _tree; - private readonly SyntaxNode _node; + private readonly Compilation? _compilation; + private readonly IOperation? _operation; + private readonly ISymbol? _symbol; + private readonly SourceOrAdditionalFile? _file; + private readonly SyntaxNode? _node; public AnalysisContextInfo(Compilation compilation) : - this(compilation: compilation, operation: null, symbol: null, tree: null, node: null) + this(compilation: compilation, operation: null, symbol: null, file: null, node: null) { } public AnalysisContextInfo(SemanticModel model) : - this(model.Compilation, model.SyntaxTree) + this(model.Compilation, new SourceOrAdditionalFile(model.SyntaxTree)) { } public AnalysisContextInfo(Compilation compilation, ISymbol symbol) : - this(compilation: compilation, operation: null, symbol: symbol, tree: null, node: null) + this(compilation: compilation, operation: null, symbol: symbol, file: null, node: null) { } - public AnalysisContextInfo(Compilation compilation, SyntaxTree tree) : - this(compilation: compilation, operation: null, symbol: null, tree: tree, node: null) + public AnalysisContextInfo(Compilation compilation, SourceOrAdditionalFile file) : + this(compilation: compilation, operation: null, symbol: null, file: file, node: null) { } public AnalysisContextInfo(Compilation compilation, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: null, tree: node.SyntaxTree, node: node) + this(compilation: compilation, operation: null, symbol: null, file: new SourceOrAdditionalFile(node.SyntaxTree), node) { } public AnalysisContextInfo(Compilation compilation, IOperation operation) : - this(compilation: compilation, operation: operation, symbol: null, tree: operation.Syntax.SyntaxTree, node: operation.Syntax) + this(compilation: compilation, operation: operation, symbol: null, file: new SourceOrAdditionalFile(operation.Syntax.SyntaxTree), node: operation.Syntax) { } public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) : - this(compilation: compilation, operation: null, symbol: symbol, tree: node.SyntaxTree, node: node) + this(compilation: compilation, operation: null, symbol: symbol, file: new SourceOrAdditionalFile(node.SyntaxTree), node) { } - public AnalysisContextInfo( - Compilation compilation, - IOperation operation, - ISymbol symbol, - SyntaxTree tree, - SyntaxNode node) + private AnalysisContextInfo( + Compilation? compilation, + IOperation? operation, + ISymbol? symbol, + SourceOrAdditionalFile? file, + SyntaxNode? node) { + Debug.Assert(node == null || file?.SourceTree != null); + Debug.Assert(operation == null || file?.SourceTree != null); + _compilation = compilation; _operation = operation; _symbol = symbol; - _tree = tree; + _file = file; _node = node; } @@ -86,18 +92,29 @@ public string GetContext() sb.AppendLine($"{nameof(ISymbol)}: {_symbol.Name} ({_symbol.Kind})"); } - if (_tree?.FilePath != null) + if (_file.HasValue) { - sb.AppendLine($"{nameof(SyntaxTree)}: {_tree.FilePath}"); + if (_file.Value.SourceTree != null) + { + sb.AppendLine($"{nameof(SyntaxTree)}: {_file.Value.SourceTree.FilePath}"); + } + else + { + RoslynDebug.Assert(_file.Value.AdditionalFile != null); + sb.AppendLine($"{nameof(AdditionalText)}: {_file.Value.AdditionalFile.Path}"); + } } if (_node != null) { - var text = _tree?.GetText(); + RoslynDebug.Assert(_file.HasValue); + RoslynDebug.Assert(_file.Value.SourceTree != null); + + var text = _file.Value.SourceTree.GetText(); var lineSpan = text?.Lines?.GetLinePositionSpan(_node.Span); // can't use Kind since that is language specific. instead will output typename. - sb.AppendLine($"{nameof(SyntaxNode)}: {GetFlattenedNodeText(_node)} [{_node.GetType().Name}]@{_node.Span.ToString()} {(lineSpan.HasValue ? lineSpan.Value.ToString() : string.Empty)}"); + sb.AppendLine($"{nameof(SyntaxNode)}: {GetFlattenedNodeText(_node)} [{_node.GetType().Name}]@{_node.Span} {(lineSpan.HasValue ? lineSpan.Value.ToString() : string.Empty)}"); } return sb.ToString(); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs index 8e7f6a12b8510..80869bba51319 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs @@ -24,12 +24,14 @@ internal AnalysisResult( ImmutableArray analyzers, ImmutableDictionary>> localSyntaxDiagnostics, ImmutableDictionary>> localSemanticDiagnostics, + ImmutableDictionary>> localAdditionalFileDiagnostics, ImmutableDictionary> nonLocalDiagnostics, ImmutableDictionary analyzerTelemetryInfo) { Analyzers = analyzers; SyntaxDiagnostics = localSyntaxDiagnostics; SemanticDiagnostics = localSemanticDiagnostics; + AdditionalFileDiagnostics = localAdditionalFileDiagnostics; CompilationDiagnostics = nonLocalDiagnostics; AnalyzerTelemetryInfo = analyzerTelemetryInfo; } @@ -37,27 +39,32 @@ internal AnalysisResult( /// /// Analyzers corresponding to this analysis result. /// - public ImmutableArray Analyzers { get; private set; } + public ImmutableArray Analyzers { get; } /// /// Syntax diagnostics reported by the . /// - public ImmutableDictionary>> SyntaxDiagnostics { get; private set; } + public ImmutableDictionary>> SyntaxDiagnostics { get; } /// /// Semantic diagnostics reported by the . /// - public ImmutableDictionary>> SemanticDiagnostics { get; private set; } + public ImmutableDictionary>> SemanticDiagnostics { get; } + + /// + /// Diagnostics in additional files reported by the . + /// + public ImmutableDictionary>> AdditionalFileDiagnostics { get; } /// /// Compilation diagnostics reported by the . /// - public ImmutableDictionary> CompilationDiagnostics { get; private set; } + public ImmutableDictionary> CompilationDiagnostics { get; } /// /// Analyzer telemetry info (register action counts and execution times). /// - public ImmutableDictionary AnalyzerTelemetryInfo { get; private set; } + public ImmutableDictionary AnalyzerTelemetryInfo { get; } /// /// Gets all the diagnostics reported by the given . @@ -69,7 +76,7 @@ public ImmutableArray GetAllDiagnostics(DiagnosticAnalyzer analyzer) throw new ArgumentException(CodeAnalysisResources.UnsupportedAnalyzerInstance, nameof(analyzer)); } - return GetDiagnostics(SpecializedCollections.SingletonEnumerable(analyzer), getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: true); + return GetDiagnostics(SpecializedCollections.SingletonEnumerable(analyzer)); } /// @@ -77,45 +84,25 @@ public ImmutableArray GetAllDiagnostics(DiagnosticAnalyzer analyzer) /// public ImmutableArray GetAllDiagnostics() { - return GetDiagnostics(Analyzers, getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: true); - } - - internal ImmutableArray GetSyntaxDiagnostics(ImmutableArray analyzers) - { - return GetDiagnostics(analyzers, getLocalSyntaxDiagnostics: true, getLocalSemanticDiagnostics: false, getNonLocalDiagnostics: false); - } - - internal ImmutableArray GetSemanticDiagnostics(ImmutableArray analyzers) - { - return GetDiagnostics(analyzers, getLocalSyntaxDiagnostics: false, getLocalSemanticDiagnostics: true, getNonLocalDiagnostics: false); + return GetDiagnostics(Analyzers); } - internal ImmutableArray GetDiagnostics(IEnumerable analyzers, bool getLocalSyntaxDiagnostics, bool getLocalSemanticDiagnostics, bool getNonLocalDiagnostics) + private ImmutableArray GetDiagnostics(IEnumerable analyzers) { var excludedAnalyzers = Analyzers.Except(analyzers); var excludedAnalyzersSet = excludedAnalyzers.Any() ? excludedAnalyzers.ToImmutableHashSet() : ImmutableHashSet.Empty; - return GetDiagnostics(excludedAnalyzersSet, getLocalSyntaxDiagnostics, getLocalSemanticDiagnostics, getNonLocalDiagnostics); + return GetDiagnostics(excludedAnalyzersSet); } - private ImmutableArray GetDiagnostics(ImmutableHashSet excludedAnalyzers, bool getLocalSyntaxDiagnostics, bool getLocalSemanticDiagnostics, bool getNonLocalDiagnostics) + private ImmutableArray GetDiagnostics(ImmutableHashSet excludedAnalyzers) { - if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) + if (SyntaxDiagnostics.Count > 0 || SemanticDiagnostics.Count > 0 || AdditionalFileDiagnostics.Count > 0 || CompilationDiagnostics.Count > 0) { var builder = ImmutableArray.CreateBuilder(); - if (getLocalSyntaxDiagnostics) - { - AddLocalDiagnostics(SyntaxDiagnostics, excludedAnalyzers, builder); - } - - if (getLocalSemanticDiagnostics) - { - AddLocalDiagnostics(SemanticDiagnostics, excludedAnalyzers, builder); - } - - if (getNonLocalDiagnostics) - { - AddNonLocalDiagnostics(CompilationDiagnostics, excludedAnalyzers, builder); - } + AddLocalDiagnostics(SyntaxDiagnostics, excludedAnalyzers, builder); + AddLocalDiagnostics(SemanticDiagnostics, excludedAnalyzers, builder); + AddLocalDiagnostics(AdditionalFileDiagnostics, excludedAnalyzers, builder); + AddNonLocalDiagnostics(CompilationDiagnostics, excludedAnalyzers, builder); return builder.ToImmutable(); } @@ -123,8 +110,8 @@ private ImmutableArray GetDiagnostics(ImmutableHashSet.Empty; } - private static void AddLocalDiagnostics( - ImmutableDictionary>> localDiagnostics, + private static void AddLocalDiagnostics( + ImmutableDictionary>> localDiagnostics, ImmutableHashSet excludedAnalyzers, ImmutableArray.Builder builder) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs index 44dc3177a2941..36259ff7d37bf 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResultBuilder.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -22,20 +23,26 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class AnalysisResultBuilder { + private static readonly ImmutableDictionary> s_emptyPathToAdditionalTextMap = + ImmutableDictionary>.Empty.WithComparers(PathUtilities.Comparer); + private readonly object _gate = new object(); private readonly Dictionary? _analyzerExecutionTimeOpt; private readonly HashSet _completedAnalyzers; private readonly Dictionary _analyzerActionCounts; + private readonly ImmutableDictionary> _pathToAdditionalTextMap; private Dictionary.Builder>>? _localSemanticDiagnosticsOpt = null; private Dictionary.Builder>>? _localSyntaxDiagnosticsOpt = null; + private Dictionary.Builder>>? _localAdditionalFileDiagnosticsOpt = null; private Dictionary.Builder>? _nonLocalDiagnosticsOpt = null; - internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers) + internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray analyzers, ImmutableArray additionalFiles) { _analyzerExecutionTimeOpt = logAnalyzerExecutionTime ? CreateAnalyzerExecutionTimeMap(analyzers) : null; _completedAnalyzers = new HashSet(); _analyzerActionCounts = new Dictionary(analyzers.Length); + _pathToAdditionalTextMap = CreatePathToAdditionalTextMap(additionalFiles); } private static Dictionary CreateAnalyzerExecutionTimeMap(ImmutableArray analyzers) @@ -49,6 +56,37 @@ private static Dictionary CreateAnalyzerExecutionT return map; } + private static ImmutableDictionary> CreatePathToAdditionalTextMap(ImmutableArray additionalFiles) + { + if (additionalFiles.IsEmpty) + { + return s_emptyPathToAdditionalTextMap; + } + + var builder = ImmutableDictionary.CreateBuilder>(PathUtilities.Comparer); + foreach (var file in additionalFiles) + { + // Null file path for additional files is not possible from IDE or command line compiler host. + // However, it is possible from custom third party analysis hosts. + // Ensure we handle it gracefully + var path = file.Path ?? string.Empty; + + // Handle multiple additional files with same path. + if (builder.TryGetValue(path, out var value)) + { + value = value.Add(file); + } + else + { + value = new OneOrMany(file); + } + + builder[path] = value; + } + + return builder.ToImmutable(); + } + public TimeSpan GetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) { Debug.Assert(_analyzerExecutionTimeOpt != null); @@ -79,7 +117,7 @@ internal ImmutableArray GetPendingAnalyzers(ImmutableArray getAnalyzerActionCounts, bool fullAnalysisResultForAnalyzersInScope) { - Debug.Assert(!fullAnalysisResultForAnalyzersInScope || analysisScope.FilterTreeOpt == null, "Full analysis result cannot come from partial (tree) analysis."); + Debug.Assert(!fullAnalysisResultForAnalyzersInScope || analysisScope.FilterFileOpt == null, "Full analysis result cannot come from partial (tree) analysis."); foreach (var analyzer in analysisScope.Analyzers) { @@ -98,8 +136,9 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop if (syntaxDiagnostics.Length > 0 || semanticDiagnostics.Length > 0 || compilationDiagnostics.Length > 0 || fullAnalysisResultForAnalyzersInScope) { - UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, ref _localSyntaxDiagnosticsOpt); - UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullAnalysisResultForAnalyzersInScope, ref _localSemanticDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSyntaxDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullAnalysisResultForAnalyzersInScope, getAdditionalTextKey, ref _localAdditionalFileDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullAnalysisResultForAnalyzersInScope, getSourceTree, ref _localSemanticDiagnosticsOpt); UpdateNonLocalDiagnostics_NoLock(analyzer, compilationDiagnostics, fullAnalysisResultForAnalyzersInScope); } @@ -122,31 +161,59 @@ internal void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScop } } } + + static SyntaxTree? getSourceTree(Diagnostic diagnostic) + => diagnostic.Location.SourceTree; + + AdditionalText? getAdditionalTextKey(Diagnostic diagnostic) + { + // Fetch the first additional file that matches diagnostic location. + if (diagnostic.Location is ExternalFileLocation externalFileLocation) + { + if (_pathToAdditionalTextMap.TryGetValue(externalFileLocation.FilePath, out var additionalTexts)) + { + foreach (var additionalText in additionalTexts) + { + if (analysisScope.AdditionalFiles.Contains(additionalText)) + { + return additionalText; + } + } + } + } + + return null; + } } - private void UpdateLocalDiagnostics_NoLock( + private void UpdateLocalDiagnostics_NoLock( DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, bool overwrite, - ref Dictionary.Builder>>? lazyLocalDiagnostics) + Func getKeyFunc, + ref Dictionary.Builder>>? lazyLocalDiagnostics) + where TKey : class { if (diagnostics.IsEmpty) { return; } - lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary.Builder>>(); + lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary.Builder>>(); - foreach (var diagsByTree in diagnostics.GroupBy(d => d.Location.SourceTree)) + foreach (var diagsByKey in diagnostics.GroupBy(getKeyFunc)) { - var tree = diagsByTree.Key; - Debug.Assert(tree is object); + var key = diagsByKey.Key; + if (key is null) + { + continue; + } Dictionary.Builder>? allDiagnostics; - if (!lazyLocalDiagnostics.TryGetValue(tree, out allDiagnostics)) + if (!lazyLocalDiagnostics.TryGetValue(key, out allDiagnostics)) { allDiagnostics = new Dictionary.Builder>(); - lazyLocalDiagnostics[tree] = allDiagnostics; + lazyLocalDiagnostics[key] = allDiagnostics; } ImmutableArray.Builder? analyzerDiagnostics; @@ -161,7 +228,7 @@ private void UpdateLocalDiagnostics_NoLock( analyzerDiagnostics.Clear(); } - analyzerDiagnostics.AddRange(diagsByTree); + analyzerDiagnostics.AddRange(diagsByKey); } } @@ -204,14 +271,16 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS var builder = ImmutableArray.CreateBuilder(); if (getLocalDiagnostics) { - if (!analysisScope.IsTreeAnalysis) + if (!analysisScope.IsSingleFileAnalysis) { AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); + AddAllLocalDiagnostics_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); } - else if (analysisScope.IsSyntaxOnlyTreeAnalysis) + else if (analysisScope.IsSyntacticSingleFileAnalysis) { AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); + AddLocalDiagnosticsForPartialAnalysis_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder); } else { @@ -221,22 +290,23 @@ private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisS if (getNonLocalDiagnostics && _nonLocalDiagnosticsOpt != null) { - AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope, builder); + AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope.Analyzers, builder); } return builder.ToImmutableArray(); } - private static void AddAllLocalDiagnostics_NoLock( - Dictionary.Builder>>? localDiagnostics, + private static void AddAllLocalDiagnostics_NoLock( + Dictionary.Builder>>? lazyLocalDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) + where TKey : class { - if (localDiagnostics != null) + if (lazyLocalDiagnostics != null) { - foreach (var localDiagsByTree in localDiagnostics.Values) + foreach (var localDiagsByTree in lazyLocalDiagnostics.Values) { - AddDiagnostics_NoLock(localDiagsByTree, analysisScope, builder); + AddDiagnostics_NoLock(localDiagsByTree, analysisScope.Analyzers, builder); } } } @@ -245,22 +315,36 @@ private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( Dictionary.Builder>>? localDiagnostics, AnalysisScope analysisScope, ImmutableArray.Builder builder) + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.SourceTree, analysisScope.Analyzers, builder); + + private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( + Dictionary.Builder>>? localDiagnostics, + AnalysisScope analysisScope, + ImmutableArray.Builder builder) + => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.AdditionalFile, analysisScope.Analyzers, builder); + + private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( + Dictionary.Builder>>? localDiagnostics, + TKey? key, + ImmutableArray analyzers, + ImmutableArray.Builder builder) + where TKey : class { Dictionary.Builder>? diagnosticsForTree; - if (localDiagnostics != null && localDiagnostics.TryGetValue(analysisScope.FilterTreeOpt, out diagnosticsForTree)) + if (key != null && localDiagnostics != null && localDiagnostics.TryGetValue(key, out diagnosticsForTree)) { - AddDiagnostics_NoLock(diagnosticsForTree, analysisScope, builder); + AddDiagnostics_NoLock(diagnosticsForTree, analyzers, builder); } } private static void AddDiagnostics_NoLock( Dictionary.Builder> diagnostics, - AnalysisScope analysisScope, + ImmutableArray analyzers, ImmutableArray.Builder builder) { Debug.Assert(diagnostics != null); - foreach (var analyzer in analysisScope.Analyzers) + foreach (var analyzer in analyzers) { ImmutableArray.Builder? diagnosticsByAnalyzer; if (diagnostics.TryGetValue(analyzer, out diagnosticsByAnalyzer)) @@ -276,6 +360,7 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal ImmutableDictionary>> localSyntaxDiagnostics; ImmutableDictionary>> localSemanticDiagnostics; + ImmutableDictionary>> localAdditionalFileDiagnostics; ImmutableDictionary> nonLocalDiagnostics; var analyzersSet = analyzers.ToImmutableHashSet(); @@ -283,29 +368,31 @@ internal AnalysisResult ToAnalysisResult(ImmutableArray anal { localSyntaxDiagnostics = GetImmutable(analyzersSet, _localSyntaxDiagnosticsOpt); localSemanticDiagnostics = GetImmutable(analyzersSet, _localSemanticDiagnosticsOpt); + localAdditionalFileDiagnostics = GetImmutable(analyzersSet, _localAdditionalFileDiagnosticsOpt); nonLocalDiagnostics = GetImmutable(analyzersSet, _nonLocalDiagnosticsOpt); } cancellationToken.ThrowIfCancellationRequested(); var analyzerTelemetryInfo = GetTelemetryInfo(analyzers); - return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); + return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, localAdditionalFileDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo); } - private static ImmutableDictionary>> GetImmutable( + private static ImmutableDictionary>> GetImmutable( ImmutableHashSet analyzers, - Dictionary.Builder>>? localDiagnosticsOpt) + Dictionary.Builder>>? localDiagnosticsOpt) + where TKey : class { if (localDiagnosticsOpt == null) { - return ImmutableDictionary>>.Empty; + return ImmutableDictionary>>.Empty; } - var builder = ImmutableDictionary.CreateBuilder>>(); + var builder = ImmutableDictionary.CreateBuilder>>(); var perTreeBuilder = ImmutableDictionary.CreateBuilder>(); foreach (var diagnosticsByTree in localDiagnosticsOpt) { - var tree = diagnosticsByTree.Key; + var key = diagnosticsByTree.Key; foreach (var diagnosticsByAnalyzer in diagnosticsByTree.Value) { if (analyzers.Contains(diagnosticsByAnalyzer.Key)) @@ -314,7 +401,7 @@ private static ImmutableDictionary> _lazyAnalyzersSet; - public SyntaxTree FilterTreeOpt { get; } + public SourceOrAdditionalFile? FilterFileOpt { get; } public TextSpan? FilterSpanOpt { get; } public ImmutableArray Analyzers { get; } @@ -32,6 +33,11 @@ internal class AnalysisScope /// public IEnumerable SyntaxTrees { get; } + /// + /// Non-source files on which we need to perform analysis. + /// + public IEnumerable AdditionalFiles { get; } + public bool ConcurrentAnalysis { get; } /// @@ -40,44 +46,47 @@ internal class AnalysisScope public bool CategorizeDiagnostics { get; } /// - /// True if we need to perform only syntax analysis for a single tree. + /// True if we need to perform only syntax analysis for a single source or additional file. /// - public bool IsSyntaxOnlyTreeAnalysis { get; } + public bool IsSyntacticSingleFileAnalysis { get; } /// - /// True if we need to perform analysis for a single tree. + /// True if we need to perform analysis for a single source or additional file. /// - public bool IsTreeAnalysis => FilterTreeOpt != null; + public bool IsSingleFileAnalysis => FilterFileOpt != null; /// /// Flag indicating if this is a partial analysis for the corresponding , - /// i.e. is true and/or is a subset of . + /// i.e. is true and/or is a subset of . /// public bool IsPartialAnalysis { get; } - public AnalysisScope(Compilation compilation, ImmutableArray analyzers, bool hasAllAnalyzers, bool concurrentAnalysis, bool categorizeDiagnostics) - : this(compilation.SyntaxTrees, analyzers, isPartialAnalysis: !hasAllAnalyzers, filterTreeOpt: null, filterSpanOpt: null, isSyntaxOnlyTreeAnalysis: false, concurrentAnalysis: concurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics) + public AnalysisScope(Compilation compilation, AnalyzerOptions? analyzerOptions, ImmutableArray analyzers, bool hasAllAnalyzers, bool concurrentAnalysis, bool categorizeDiagnostics) + : this(compilation.SyntaxTrees, analyzerOptions?.AdditionalFiles ?? ImmutableArray.Empty, + analyzers, isPartialAnalysis: !hasAllAnalyzers, filterFile: null, filterSpanOpt: null, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: concurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics) { } - public AnalysisScope(ImmutableArray analyzers, SyntaxTree filterTree, TextSpan? filterSpan, bool syntaxAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) - : this(SpecializedCollections.SingletonEnumerable(filterTree), analyzers, isPartialAnalysis: true, filterTree, filterSpan, syntaxAnalysis, concurrentAnalysis, categorizeDiagnostics) + public AnalysisScope(ImmutableArray analyzers, SourceOrAdditionalFile filterFile, TextSpan? filterSpan, bool isSyntacticSingleFileAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + : this(filterFile.SourceTree != null ? SpecializedCollections.SingletonEnumerable(filterFile.SourceTree) : SpecializedCollections.EmptyEnumerable(), + filterFile.AdditionalFile != null ? SpecializedCollections.SingletonEnumerable(filterFile.AdditionalFile) : SpecializedCollections.EmptyEnumerable(), + analyzers, isPartialAnalysis: true, filterFile, filterSpan, isSyntacticSingleFileAnalysis, concurrentAnalysis, categorizeDiagnostics) { - Debug.Assert(filterTree != null); } - private AnalysisScope(IEnumerable trees, ImmutableArray analyzers, bool isPartialAnalysis, SyntaxTree filterTreeOpt, TextSpan? filterSpanOpt, bool isSyntaxOnlyTreeAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + private AnalysisScope(IEnumerable trees, IEnumerable additionalFiles, ImmutableArray analyzers, bool isPartialAnalysis, SourceOrAdditionalFile? filterFile, TextSpan? filterSpanOpt, bool isSyntacticSingleFileAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) { - Debug.Assert(isPartialAnalysis || FilterTreeOpt == null); + Debug.Assert(isPartialAnalysis || FilterFileOpt == null); Debug.Assert(isPartialAnalysis || FilterSpanOpt == null); - Debug.Assert(isPartialAnalysis || !isSyntaxOnlyTreeAnalysis); + Debug.Assert(isPartialAnalysis || !isSyntacticSingleFileAnalysis); SyntaxTrees = trees; + AdditionalFiles = additionalFiles; Analyzers = analyzers; IsPartialAnalysis = isPartialAnalysis; - FilterTreeOpt = filterTreeOpt; + FilterFileOpt = filterFile; FilterSpanOpt = filterSpanOpt; - IsSyntaxOnlyTreeAnalysis = isSyntaxOnlyTreeAnalysis; + IsSyntacticSingleFileAnalysis = isSyntacticSingleFileAnalysis; ConcurrentAnalysis = concurrentAnalysis; CategorizeDiagnostics = categorizeDiagnostics; @@ -99,8 +108,8 @@ public bool Contains(DiagnosticAnalyzer analyzer) public AnalysisScope WithAnalyzers(ImmutableArray analyzers, bool hasAllAnalyzers) { - var isPartialAnalysis = IsTreeAnalysis || !hasAllAnalyzers; - return new AnalysisScope(SyntaxTrees, analyzers, isPartialAnalysis, FilterTreeOpt, FilterSpanOpt, IsSyntaxOnlyTreeAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); + var isPartialAnalysis = IsSingleFileAnalysis || !hasAllAnalyzers; + return new AnalysisScope(SyntaxTrees, AdditionalFiles, analyzers, isPartialAnalysis, FilterFileOpt, FilterSpanOpt, IsSyntacticSingleFileAnalysis, ConcurrentAnalysis, CategorizeDiagnostics); } public static bool ShouldSkipSymbolAnalysis(SymbolDeclaredCompilationEvent symbolEvent) @@ -118,19 +127,29 @@ public static bool ShouldSkipDeclarationAnalysis(ISymbol symbol) public bool ShouldAnalyze(SyntaxTree tree) { - return FilterTreeOpt == null || FilterTreeOpt == tree; + return !FilterFileOpt.HasValue || FilterFileOpt.Value.SourceTree == tree; + } + + public bool ShouldAnalyze(AdditionalText file) + { + return !FilterFileOpt.HasValue || FilterFileOpt.Value.AdditionalFile == file; } public bool ShouldAnalyze(ISymbol symbol) { - if (FilterTreeOpt == null) + if (!FilterFileOpt.HasValue) { return true; } + if (FilterFileOpt.Value.SourceTree == null) + { + return false; + } + foreach (var location in symbol.Locations) { - if (location.SourceTree != null && FilterTreeOpt == location.SourceTree && ShouldInclude(location.SourceSpan)) + if (FilterFileOpt.Value.SourceTree == location.SourceTree && ShouldInclude(location.SourceSpan)) { return true; } @@ -141,11 +160,16 @@ public bool ShouldAnalyze(ISymbol symbol) public bool ShouldAnalyze(SyntaxNode node) { - if (FilterTreeOpt == null) + if (!FilterFileOpt.HasValue) { return true; } + if (FilterFileOpt.Value.SourceTree == null) + { + return false; + } + return ShouldInclude(node.FullSpan); } @@ -161,14 +185,25 @@ public bool ContainsSpan(TextSpan filterSpan) public bool ShouldInclude(Diagnostic diagnostic) { - if (FilterTreeOpt == null) + if (!FilterFileOpt.HasValue) { return true; } - if (!diagnostic.Location.IsInSource || diagnostic.Location.SourceTree != FilterTreeOpt) + if (diagnostic.Location.IsInSource) { - return false; + if (diagnostic.Location.SourceTree != FilterFileOpt.Value.SourceTree) + { + return false; + } + } + else if (diagnostic.Location is ExternalFileLocation externalFileLocation) + { + if (FilterFileOpt.Value.AdditionalFile == null || + !PathUtilities.Comparer.Equals(externalFileLocation.FilePath, FilterFileOpt.Value.AdditionalFile.Path)) + { + return false; + } } return ShouldInclude(diagnostic.Location.SourceSpan); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs index 9d7319088fb54..c702bcd66d514 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs @@ -23,8 +23,8 @@ private class PerAnalyzerState private readonly Dictionary _pendingSymbols = new Dictionary(); private readonly Dictionary> _pendingDeclarations = new Dictionary>(); - private Dictionary _lazySyntaxTreesWithAnalysisData = null; - private int _pendingSyntaxAnalysisTreesCount = 0; + private Dictionary _lazyFilesWithAnalysisData = null; + private int _pendingSyntaxAnalysisFilesCount = 0; private Dictionary _lazyPendingSymbolEndAnalyses = null; private readonly ObjectPool _analyzerStateDataPool; @@ -52,31 +52,31 @@ public void AddPendingEvents(HashSet uniqueEvents) } } - public bool HasPendingSyntaxAnalysis(SyntaxTree treeOpt) + public bool HasPendingSyntaxAnalysis(SourceOrAdditionalFile? fileOpt) { lock (_gate) { - if (_pendingSyntaxAnalysisTreesCount == 0) + if (_pendingSyntaxAnalysisFilesCount == 0) { return false; } - Debug.Assert(_lazySyntaxTreesWithAnalysisData != null); + Debug.Assert(_lazyFilesWithAnalysisData != null); - if (treeOpt == null) + if (!fileOpt.HasValue) { - // We have syntax analysis pending for at least one tree. + // We have syntax analysis pending for at least one file. return true; } AnalyzerStateData state; - if (!_lazySyntaxTreesWithAnalysisData.TryGetValue(treeOpt, out state)) + if (!_lazyFilesWithAnalysisData.TryGetValue(fileOpt.Value, out state)) { - // We haven't even started analysis for this tree. + // We haven't even started analysis for this file. return true; } - // See if we have completed analysis for this tree. + // See if we have completed analysis for this file. return state.StateKind == StateKind.FullyProcessed; } } @@ -143,15 +143,15 @@ private static bool MarkEntityProcessed_NoLock EnsureDeclarationDataMap_NoLock(ISymbol symbol, Dictionary declarationDataMap) @@ -408,20 +408,20 @@ public void MarkDeclarationsComplete(ISymbol symbol) } } - public bool TryStartSyntaxAnalysis(SyntaxTree tree, out AnalyzerStateData state) + public bool TryStartSyntaxAnalysis(SourceOrAdditionalFile tree, out AnalyzerStateData state) { lock (_gate) { - Debug.Assert(_lazySyntaxTreesWithAnalysisData != null); + Debug.Assert(_lazyFilesWithAnalysisData != null); return TryStartSyntaxAnalysis_NoLock(tree, out state); } } - public void MarkSyntaxAnalysisComplete(SyntaxTree tree) + public void MarkSyntaxAnalysisComplete(SourceOrAdditionalFile file) { lock (_gate) { - MarkSyntaxTreeProcessed_NoLock(tree); + MarkSyntaxAnalysisComplete_NoLock(file); } } @@ -461,12 +461,14 @@ public void OnCompilationEventGenerated(CompilationEvent compilationEvent, Analy return; } } - else if (compilationEvent is CompilationStartedEvent) + else if (compilationEvent is CompilationStartedEvent compilationStartedEvent) { - if (actionCounts.SyntaxTreeActionsCount > 0) + var fileCount = actionCounts.SyntaxTreeActionsCount > 0 ? compilationEvent.Compilation.SyntaxTrees.Count() : 0; + fileCount += actionCounts.AdditionalFileActionsCount > 0 ? compilationStartedEvent.AdditionalFiles.Length : 0; + if (fileCount > 0) { - _lazySyntaxTreesWithAnalysisData = new Dictionary(); - _pendingSyntaxAnalysisTreesCount = compilationEvent.Compilation.SyntaxTrees.Count(); + _lazyFilesWithAnalysisData = new Dictionary(); + _pendingSyntaxAnalysisFilesCount = fileCount; } if (actionCounts.CompilationActionsCount == 0) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs index 2a9c5452e5d24..93502c8871036 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -107,7 +107,7 @@ public async Task OnCompilationEventsGeneratedAsync(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt) + private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents) { Debug.Assert(_lazyAnalyzerActionCountsMap != null); // Add the events to our global pending events map. - AddToEventsMap_NoLock(compilationEvents, filterTreeOpt); + AddToEventsMap_NoLock(compilationEvents); // Mark the events for analysis for each analyzer. ArrayBuilder newPartialSymbols = null; @@ -174,14 +174,8 @@ private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt) + private void AddToEventsMap_NoLock(ImmutableArray compilationEvents) { - if (filterTreeOpt != null) - { - AddPendingSourceEvents_NoLock(compilationEvents, filterTreeOpt); - return; - } - foreach (var compilationEvent in compilationEvents) { UpdateEventsMap_NoLock(compilationEvent, add: true); @@ -245,20 +239,6 @@ private void UpdateEventsMap_NoLock(CompilationEvent compilationEvent, bool add) } } - private void AddPendingSourceEvents_NoLock(ImmutableArray compilationEvents, SyntaxTree tree) - { - HashSet currentEvents; - if (!_pendingSourceEvents.TryGetValue(tree, out currentEvents)) - { - currentEvents = new HashSet(compilationEvents); - _pendingSourceEvents[tree] = currentEvents; - _compilationData.RemoveCachedSemanticModel(tree); - return; - } - - currentEvents.AddAll(compilationEvents); - } - private void AddPendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) { HashSet currentEvents; @@ -317,7 +297,8 @@ private static bool HasActionsForEvent(CompilationEvent compilationEvent, Analyz if (compilationEvent is CompilationStartedEvent) { return actionCounts.CompilationActionsCount > 0 || - actionCounts.SyntaxTreeActionsCount > 0; + actionCounts.SyntaxTreeActionsCount > 0 || + actionCounts.AdditionalFileActionsCount > 0; } else if (compilationEvent is CompilationCompletedEvent) { @@ -506,7 +487,7 @@ private void Free(HashSet events) /// public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) { - if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) { return false; } @@ -514,7 +495,7 @@ public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) foreach (var analyzer in analysisScope.Analyzers) { var analyzerState = GetAnalyzerState(analyzer); - if (analyzerState.HasPendingSyntaxAnalysis(analysisScope.FilterTreeOpt)) + if (analyzerState.HasPendingSyntaxAnalysis(analysisScope.FilterFileOpt)) { return true; } @@ -528,9 +509,10 @@ public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) /// public bool HasPendingSymbolAnalysis(AnalysisScope analysisScope, CancellationToken cancellationToken) { - Debug.Assert(analysisScope.FilterTreeOpt != null); + RoslynDebug.Assert(analysisScope.FilterFileOpt.HasValue); + RoslynDebug.Assert(analysisScope.FilterFileOpt.Value.SourceTree != null); - var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterTreeOpt, cancellationToken); + var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterFileOpt.Value.SourceTree, cancellationToken); foreach (var symbolDeclaredEvent in symbolDeclaredEvents) { if (analysisScope.ShouldAnalyze(symbolDeclaredEvent.Symbol)) @@ -762,44 +744,44 @@ public void MarkDeclarationsComplete(ISymbol symbol, IEnumerable - /// Attempts to start processing a syntax tree for the given analyzer's syntax tree actions. + /// Attempts to start processing a syntax tree or additional file for the given analyzer's syntax tree or additional file actions respectively. /// /// - /// Returns false if the tree has already been processed for the analyzer OR is currently being processed by another task. + /// Returns false if the file has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial syntax analysis state for the given tree for the given analyzer. /// - public bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + public bool TryStartSyntaxAnalysis(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { - return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(tree, out state); + return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(file, out state); } /// - /// Marks the given tree as fully syntactically analyzed for the given analyzer. + /// Marks the given file as fully syntactically analyzed for the given analyzer. /// - public void MarkSyntaxAnalysisComplete(SyntaxTree tree, DiagnosticAnalyzer analyzer) + public void MarkSyntaxAnalysisComplete(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer) { - GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); + GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(file); } /// - /// Marks the given tree as fully syntactically analyzed for the given analyzers. + /// Marks the given file as fully syntactically analyzed for the given analyzers. /// - public void MarkSyntaxAnalysisComplete(SyntaxTree tree, IEnumerable analyzers) + public void MarkSyntaxAnalysisComplete(SourceOrAdditionalFile file, IEnumerable analyzers) { foreach (var analyzer in analyzers) { - GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); + GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(file); } } /// - /// Marks the given tree as fully syntactically analyzed for the unprocessed analyzers in the given analysisScope. + /// Marks the given file as fully syntactically analyzed for the unprocessed analyzers in the given analysisScope. /// public void MarkSyntaxAnalysisCompleteForUnprocessedAnalyzers( - SyntaxTree tree, + SourceOrAdditionalFile file, AnalysisScope analysisScope, HashSet processedAnalyzers) - => MarkAnalysisCompleteForUnprocessedAnalyzers(analysisScope, processedAnalyzers, MarkSyntaxAnalysisComplete, tree); + => MarkAnalysisCompleteForUnprocessedAnalyzers(analysisScope, processedAnalyzers, MarkSyntaxAnalysisComplete, file); private static void MarkAnalysisCompleteForUnprocessedAnalyzers( AnalysisScope analysisScope, diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs index 718855c120515..86821565806d5 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerActionCounts.cs @@ -17,6 +17,7 @@ internal AnalyzerActionCounts(in AnalyzerActions analyzerActions) : analyzerActions.CompilationEndActionsCount, analyzerActions.CompilationActionsCount, analyzerActions.SyntaxTreeActionsCount, + analyzerActions.AdditionalFileActionsCount, analyzerActions.SemanticModelActionsCount, analyzerActions.SymbolActionsCount, analyzerActions.SymbolStartActionsCount, @@ -38,6 +39,7 @@ internal AnalyzerActionCounts( int compilationEndActionsCount, int compilationActionsCount, int syntaxTreeActionsCount, + int additionalFileActionsCount, int semanticModelActionsCount, int symbolActionsCount, int symbolStartActionsCount, @@ -56,6 +58,7 @@ internal AnalyzerActionCounts( CompilationEndActionsCount = compilationEndActionsCount; CompilationActionsCount = compilationActionsCount; SyntaxTreeActionsCount = syntaxTreeActionsCount; + AdditionalFileActionsCount = additionalFileActionsCount; SemanticModelActionsCount = semanticModelActionsCount; SymbolActionsCount = symbolActionsCount; SymbolStartActionsCount = symbolStartActionsCount; @@ -99,6 +102,11 @@ internal AnalyzerActionCounts( /// public int SyntaxTreeActionsCount { get; } + /// + /// Count of registered additional file actions. + /// + public int AdditionalFileActionsCount { get; } + /// /// Count of registered semantic model actions. /// diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 4e7ea50bdec59..3ed956d7c6ee1 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -79,6 +79,7 @@ internal abstract partial class AnalyzerDriver : IDisposable private ImmutableArray<(DiagnosticAnalyzer, ImmutableArray>)> _symbolActionsByKind; private ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> _semanticModelActions; private ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> _syntaxTreeActions; + private ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> _additionalFileActions; // Compilation actions and compilation end actions have separate maps so that it is easy to // execute the compilation actions before the compilation end actions. private ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> _compilationActions; @@ -245,10 +246,11 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn _generatedCodeAttribute = analyzerExecutor.Compilation?.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute"); _symbolActionsByKind = MakeSymbolActionsByKind(in AnalyzerActions); - _semanticModelActions = MakeSemanticModelActionsByAnalyzer(in AnalyzerActions); - _syntaxTreeActions = MakeSyntaxTreeActionsByAnalyzer(in AnalyzerActions); - _compilationActions = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationActions); - _compilationEndActions = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); + _semanticModelActions = MakeActionsByAnalyzer(AnalyzerActions.SemanticModelActions); + _syntaxTreeActions = MakeActionsByAnalyzer(AnalyzerActions.SyntaxTreeActions); + _additionalFileActions = MakeActionsByAnalyzer(AnalyzerActions.AdditionalFileActions); + _compilationActions = MakeActionsByAnalyzer(this.AnalyzerActions.CompilationActions); + _compilationEndActions = MakeActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); _compilationEndAnalyzers = MakeCompilationEndAnalyzers(_compilationEndActions); if (this.AnalyzerActions.SymbolStartActionsCount > 0) @@ -558,7 +560,7 @@ private static void OnDriverException(Task faultedTask, AnalyzerExecutor analyze private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { - if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) { // For partial analysis, only execute syntax tree actions if performing syntax analysis. return; @@ -567,9 +569,10 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState foreach (var tree in analysisScope.SyntaxTrees) { var isGeneratedCode = IsGeneratedCode(tree); + var file = new SourceOrAdditionalFile(tree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { - analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analysisScope.Analyzers); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analysisScope.Analyzers); continue; } @@ -586,12 +589,51 @@ private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState cancellationToken.ThrowIfCancellationRequested(); // Execute actions for a given analyzer sequentially. - AnalyzerExecutor.TryExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, tree, analysisScope, analysisStateOpt, isGeneratedCode); + AnalyzerExecutor.TryExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, file, analysisScope, analysisStateOpt, isGeneratedCode); processedAnalyzers?.Add(analyzer); } - analysisStateOpt?.MarkSyntaxAnalysisCompleteForUnprocessedAnalyzers(tree, analysisScope, processedAnalyzers); + analysisStateOpt?.MarkSyntaxAnalysisCompleteForUnprocessedAnalyzers(file, analysisScope, processedAnalyzers); + } + finally + { + processedAnalyzers?.Free(); + } + } + } + + private void ExecuteAdditionalFileActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) + { + if (analysisScope.IsSingleFileAnalysis && !analysisScope.IsSyntacticSingleFileAnalysis) + { + // For partial analysis, only execute additional file actions if performing syntactic single file analysis. + return; + } + + foreach (var additionalFile in analysisScope.AdditionalFiles) + { + var file = new SourceOrAdditionalFile(additionalFile); + + var processedAnalyzers = analysisStateOpt != null ? PooledHashSet.GetInstance() : null; + try + { + foreach (var (analyzer, additionalFileActions) in _additionalFileActions) + { + if (!analysisScope.Contains(analyzer)) + { + continue; + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Execute actions for a given analyzer sequentially. + AnalyzerExecutor.TryExecuteAdditionalFileActions(additionalFileActions, analyzer, file, analysisScope, analysisStateOpt); + + processedAnalyzers?.Add(analyzer); + } + + analysisStateOpt?.MarkSyntaxAnalysisCompleteForUnprocessedAnalyzers(file, analysisScope, processedAnalyzers); } finally { @@ -655,7 +697,7 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( var analysisOptions = new CompilationWithAnalyzersOptions(options, onAnalyzerException, analyzerExceptionFilter: analyzerExceptionFilter, concurrentAnalysis: true, logAnalyzerExecutionTime: reportAnalyzer, reportSuppressedDiagnostics: false); analyzerDriver.Initialize(newCompilation, analysisOptions, new CompilationData(newCompilation), categorizeDiagnostics, cancellationToken); - var analysisScope = new AnalysisScope(newCompilation, analyzers, hasAllAnalyzers: true, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(newCompilation, options, analyzers, hasAllAnalyzers: true, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); analyzerDriver.AttachQueueAndStartProcessingEvents(newCompilation.EventQueue, analysisScope, cancellationToken: cancellationToken); return analyzerDriver; } @@ -1115,34 +1157,11 @@ private ImmutableHashSet ComputeSuppressedAnalyzersForTree(S return builder.ToImmutableAndFree(); } - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeSyntaxTreeActionsByAnalyzer(in AnalyzerActions analyzerActions) + private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeActionsByAnalyzer(in ImmutableArray analyzerActions) + where TAnalyzerAction : AnalyzerAction { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) - { - builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); - } - - return builder.ToImmutableAndFree(); - } - - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeSemanticModelActionsByAnalyzer(in AnalyzerActions analyzerActions) - { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = analyzerActions.SemanticModelActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) - { - builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); - } - - return builder.ToImmutableAndFree(); - } - - private static ImmutableArray<(DiagnosticAnalyzer, ImmutableArray)> MakeCompilationActionsByAnalyzer(ImmutableArray compilationActions) - { - var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); - var actionsByAnalyzers = compilationActions.GroupBy(action => action.Analyzer); + var builder = ArrayBuilder<(DiagnosticAnalyzer, ImmutableArray)>.GetInstance(); + var actionsByAnalyzers = analyzerActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { builder.Add((analyzerAndActions.Key, analyzerAndActions.ToImmutableArray())); @@ -1187,8 +1206,11 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An // Kick off tasks to execute syntax tree actions. var syntaxTreeActionsTask = Task.Run(() => ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken), cancellationToken); + // Kick off tasks to execute additional file actions. + var additionalFileActionsTask = Task.Run(() => ExecuteAdditionalFileActions(analysisScope, analysisStateOpt, cancellationToken), cancellationToken); + // Wait for all worker threads to complete processing events. - await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask)).ConfigureAwait(false); + await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask).Concat(additionalFileActionsTask)).ConfigureAwait(false); for (int i = 0; i < workerCount; i++) { @@ -1204,6 +1226,7 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An completedEvent = await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false); ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken); + ExecuteAdditionalFileActions(analysisScope, analysisStateOpt, cancellationToken); } // Finally process the compilation completed event, if any. @@ -2261,7 +2284,7 @@ protected override bool TryExecuteDeclaringReferenceActions( cancellationToken.ThrowIfCancellationRequested(); var decl = declaringReferences[i]; - if (analysisScope.FilterTreeOpt != null && analysisScope.FilterTreeOpt != decl.SyntaxTree) + if (analysisScope.FilterFileOpt != null && analysisScope.FilterFileOpt?.SourceTree != decl.SyntaxTree) { continue; } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs index 65fc204402701..1d5eb85cb8a27 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.AnalyzerDiagnosticReporter.cs @@ -5,9 +5,7 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Text; using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; @@ -29,7 +27,7 @@ private sealed class AnalyzerDiagnosticReporter new ObjectPool(() => new AnalyzerDiagnosticReporter(), 10); public static AnalyzerDiagnosticReporter GetInstance( - SyntaxTree contextTree, + SourceOrAdditionalFile contextFile, TextSpan? span, Compilation compilation, DiagnosticAnalyzer analyzer, @@ -41,7 +39,7 @@ public static AnalyzerDiagnosticReporter GetInstance( CancellationToken cancellationToken) { var item = s_objectPool.Allocate(); - item._contextTree = contextTree; + item._contextFile = contextFile; item._span = span; item._compilation = compilation; item._analyzer = analyzer; @@ -56,7 +54,7 @@ public static AnalyzerDiagnosticReporter GetInstance( public void Free() { - _contextTree = null!; + _contextFile = null!; _span = null; _compilation = null!; _analyzer = null!; @@ -69,7 +67,7 @@ public void Free() s_objectPool.Free(this); } - private SyntaxTree _contextTree; + private SourceOrAdditionalFile? _contextFile; private TextSpan? _span; private Compilation _compilation; private DiagnosticAnalyzer _analyzer; @@ -105,8 +103,7 @@ private void AddDiagnostic(Diagnostic diagnostic) Debug.Assert(_addNonCategorizedDiagnosticOpt == null); RoslynDebug.Assert(_addCategorizedNonLocalDiagnosticOpt != null); - if (diagnostic.Location.IsInSource && - _contextTree == diagnostic.Location.SourceTree && + if (isLocalDiagnostic(diagnostic) && (!_span.HasValue || _span.Value.IntersectsWith(diagnostic.Location.SourceSpan))) { _addCategorizedLocalDiagnosticOpt(diagnostic, _analyzer, _isSyntaxDiagnostic); @@ -115,6 +112,25 @@ private void AddDiagnostic(Diagnostic diagnostic) { _addCategorizedNonLocalDiagnosticOpt(diagnostic, _analyzer); } + + return; + + bool isLocalDiagnostic(Diagnostic diagnostic) + { + if (diagnostic.Location.IsInSource) + { + return _contextFile?.SourceTree != null && + _contextFile.Value.SourceTree == diagnostic.Location.SourceTree; + } + + if (_contextFile?.AdditionalFile != null && + diagnostic.Location is ExternalFileLocation externalFileLocation) + { + return PathUtilities.Comparer.Equals(_contextFile.Value.AdditionalFile.Path, externalFileLocation.FilePath); + } + + return false; + } } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index c3bb3b143093b..1a0856b8abfc3 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -674,7 +674,7 @@ private void ExecuteSemanticModelActionsCore( return; } - var diagReporter = GetAddDiagnostic(semanticModel.SyntaxTree, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(semanticModel.SyntaxTree, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); @@ -706,7 +706,7 @@ private void ExecuteSemanticModelActionsCore( /// /// Syntax tree actions to be executed. /// Analyzer whose actions are to be executed. - /// Syntax tree to analyze. + /// Syntax tree to analyze. /// Scope for analyzer execution. /// An optional object to track analysis state. /// Flag indicating if the syntax tree being analyzed is generated code. @@ -717,19 +717,20 @@ private void ExecuteSemanticModelActionsCore( public bool TryExecuteSyntaxTreeActions( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SyntaxTree tree, + SourceOrAdditionalFile file, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCode) { + RoslynDebug.Assert(file.SourceTree != null); AnalyzerStateData analyzerStateOpt = null; try { - if (TryStartSyntaxAnalysis(tree, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + if (TryStartSyntaxAnalysis(file, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) { - ExecuteSyntaxTreeActionsCore(syntaxTreeActions, analyzer, tree, analyzerStateOpt, isGeneratedCode); - analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); + ExecuteSyntaxTreeActionsCore(syntaxTreeActions, analyzer, file, analyzerStateOpt, isGeneratedCode); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); return true; } @@ -744,17 +745,20 @@ public bool TryExecuteSyntaxTreeActions( private void ExecuteSyntaxTreeActionsCore( ImmutableArray syntaxTreeActions, DiagnosticAnalyzer analyzer, - SyntaxTree tree, + SourceOrAdditionalFile file, AnalyzerStateData analyzerStateOpt, bool isGeneratedCode) { + RoslynDebug.Assert(file.SourceTree != null); + + var tree = file.SourceTree; if (isGeneratedCode && _shouldSkipAnalysisOnGeneratedCode(analyzer) || _isAnalyzerSuppressedForTree(analyzer, tree)) { return; } - var diagReporter = GetAddDiagnostic(tree, analyzer, isSyntaxDiagnostic: true); + var diagReporter = GetAddSyntaxDiagnostic(file, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); @@ -770,8 +774,8 @@ private void ExecuteSyntaxTreeActionsCore( ExecuteAndCatchIfThrows( syntaxTreeAction.Analyzer, data => data.action(data.context), - (action: syntaxTreeAction.Action, context: context), - new AnalysisContextInfo(_compilation, tree)); + (action: syntaxTreeAction.Action, context), + new AnalysisContextInfo(_compilation, file)); analyzerStateOpt?.ProcessedActions.Add(syntaxTreeAction); } @@ -780,6 +784,79 @@ private void ExecuteSyntaxTreeActionsCore( diagReporter.Free(); } + /// + /// Tries to execute the additional file actions. + /// + /// Actions to be executed. + /// Analyzer whose actions are to be executed. + /// Additional file to analyze. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + /// + /// True, if successfully executed the actions for the given analysis scope OR all the actions have already been executed for the given analysis scope. + /// False, if there are some pending actions that are currently being executed on another thread. + /// + public bool TryExecuteAdditionalFileActions( + ImmutableArray additionalFileActions, + DiagnosticAnalyzer analyzer, + SourceOrAdditionalFile file, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + RoslynDebug.Assert(file.AdditionalFile != null); + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartSyntaxAnalysis(file, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteAdditionalFileActionsCore(additionalFileActions, analyzer, file, analyzerStateOpt); + analysisStateOpt?.MarkSyntaxAnalysisComplete(file, analyzer); + return true; + } + + return analysisStateOpt == null || !analysisStateOpt.HasPendingSyntaxAnalysis(analysisScope); + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteAdditionalFileActionsCore( + ImmutableArray additionalFileActions, + DiagnosticAnalyzer analyzer, + SourceOrAdditionalFile file, + AnalyzerStateData analyzerStateOpt) + { + RoslynDebug.Assert(file.AdditionalFile != null); + var additionalFile = file.AdditionalFile; + + var diagReporter = GetAddSyntaxDiagnostic(file, analyzer); + + using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); + foreach (var additionalFileAction in additionalFileActions) + { + if (ShouldExecuteAction(analyzerStateOpt, additionalFileAction)) + { + _cancellationToken.ThrowIfCancellationRequested(); + + var context = new AdditionalFileAnalysisContext(additionalFile, _analyzerOptions, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, _compilation, _cancellationToken); + + // Catch Exception from action. + ExecuteAndCatchIfThrows( + additionalFileAction.Analyzer, + data => data.action(data.context), + (action: additionalFileAction.Action, context), + new AnalysisContextInfo(_compilation, file)); + + analyzerStateOpt?.ProcessedActions.Add(additionalFileAction); + } + } + + diagReporter.Free(); + } + private void ExecuteSyntaxNodeAction( SyntaxNodeAnalyzerAction syntaxNodeAction, SyntaxNode node, @@ -979,7 +1056,7 @@ private void ExecuteBlockActionsCore( return; } - var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(model.SyntaxTree, filterSpan, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, containingSymbol, model, getKind, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt); @@ -1352,7 +1429,7 @@ private void ExecuteOperationActionsCore( return; } - var diagReporter = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); + var diagReporter = GetAddSemanticDiagnostic(model.SyntaxTree, filterSpan, analyzer); using var _ = PooledDelegates.GetPooledFunction((d, arg) => arg.self.IsSupportedDiagnostic(arg.analyzer, d), (self: this, analyzer), out Func isSupportedDiagnostic); ExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, containingSymbol, model, diagReporter.AddDiagnosticAction, isSupportedDiagnostic, analyzerStateOpt); @@ -1717,60 +1794,25 @@ private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyz }; } - private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(tree, null, _compilation, analyzer, isSyntaxDiagnostic, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrAdditionalFile(tree), null, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private AnalyzerDiagnosticReporter GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + private AnalyzerDiagnosticReporter GetAddSemanticDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer) { - return AnalyzerDiagnosticReporter.GetInstance(tree, span, _compilation, analyzer, false, + return AnalyzerDiagnosticReporter.GetInstance(new SourceOrAdditionalFile(tree), span, _compilation, analyzer, isSyntaxDiagnostic: false, _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } - private static Action GetAddDiagnostic( - SyntaxTree contextTree, - TextSpan? span, - Compilation compilation, - DiagnosticAnalyzer analyzer, - bool isSyntaxDiagnostic, - Action addNonCategorizedDiagnosticOpt, - Action addCategorizedLocalDiagnosticOpt, - Action addCategorizedNonLocalDiagnosticOpt, - Func shouldSuppressGeneratedCodeDiagnostic, - CancellationToken cancellationToken) + private AnalyzerDiagnosticReporter GetAddSyntaxDiagnostic(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer) { - return diagnostic => - { - if (shouldSuppressGeneratedCodeDiagnostic(diagnostic, analyzer, compilation, cancellationToken)) - { - return; - } - - if (addCategorizedLocalDiagnosticOpt == null) - { - Debug.Assert(addNonCategorizedDiagnosticOpt != null); - addNonCategorizedDiagnosticOpt(diagnostic); - return; - } - - Debug.Assert(addNonCategorizedDiagnosticOpt == null); - Debug.Assert(addCategorizedNonLocalDiagnosticOpt != null); - - if (diagnostic.Location.IsInSource && - contextTree == diagnostic.Location.SourceTree && - (!span.HasValue || span.Value.IntersectsWith(diagnostic.Location.SourceSpan))) - { - addCategorizedLocalDiagnosticOpt(diagnostic, analyzer, isSyntaxDiagnostic); - } - else - { - addCategorizedNonLocalDiagnosticOpt(diagnostic, analyzer); - } - }; + return AnalyzerDiagnosticReporter.GetInstance(file, null, _compilation, analyzer, isSyntaxDiagnostic: true, + _addNonCategorizedDiagnosticOpt, _addCategorizedLocalDiagnosticOpt, _addCategorizedNonLocalDiagnosticOpt, + _shouldSuppressGeneratedCodeDiagnostic, _cancellationToken); } private static bool ShouldExecuteAction(AnalyzerStateData analyzerStateOpt, AnalyzerAction action) @@ -1841,12 +1883,12 @@ private static bool TryStartProcessingEvent(CompilationEvent nonSymbolCompilatio return analysisStateOpt == null || analysisStateOpt.TryStartProcessingEvent(nonSymbolCompilationEvent, analyzer, out analyzerStateOpt); } - private static bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + private static bool TryStartSyntaxAnalysis(SourceOrAdditionalFile file, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) { Debug.Assert(analysisScope.Contains(analyzer)); analyzerStateOpt = null; - return analysisStateOpt == null || analysisStateOpt.TryStartSyntaxAnalysis(tree, analyzer, out analyzerStateOpt); + return analysisStateOpt == null || analysisStateOpt.TryStartSyntaxAnalysis(file, analyzer, out analyzerStateOpt); } private static bool TryStartAnalyzingSymbol(ISymbol symbol, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs index 515d1bbd37d9c..1e4968e80cc1d 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs @@ -33,6 +33,11 @@ public sealed class AnalyzerTelemetryInfo /// public int SyntaxTreeActionsCount { get; set; } = 0; + /// + /// Count of registered additional file actions. + /// + public int AdditionalFileActionsCount { get; set; } = 0; + /// /// Count of registered semantic model actions. /// @@ -117,6 +122,7 @@ internal AnalyzerTelemetryInfo(AnalyzerActionCounts actionCounts, int suppressio CompilationActionsCount = actionCounts.CompilationActionsCount; SyntaxTreeActionsCount = actionCounts.SyntaxTreeActionsCount; + AdditionalFileActionsCount = actionCounts.AdditionalFileActionsCount; SemanticModelActionsCount = actionCounts.SemanticModelActionsCount; SymbolActionsCount = actionCounts.SymbolActionsCount; SymbolStartActionsCount = actionCounts.SymbolStartActionsCount; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs index 9f65a30c47ba0..035343206e14a 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationStartedEvent.cs @@ -2,6 +2,8 @@ // 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; + namespace Microsoft.CodeAnalysis.Diagnostics { /// @@ -9,10 +11,25 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal sealed class CompilationStartedEvent : CompilationEvent { - public CompilationStartedEvent(Compilation compilation) : base(compilation) { } + public ImmutableArray AdditionalFiles { get; } + + private CompilationStartedEvent(Compilation compilation, ImmutableArray additionalFiles) + : base(compilation) + { + AdditionalFiles = additionalFiles; + } + + public CompilationStartedEvent(Compilation compilation) + : this(compilation, ImmutableArray.Empty) + { + } + public override string ToString() { return "CompilationStartedEvent"; } + + public CompilationStartedEvent WithAdditionalFiles(ImmutableArray additionalFiles) + => new CompilationStartedEvent(Compilation, additionalFiles); } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index bc6e2870bf2ca..67340996ab413 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -56,7 +56,7 @@ public class CompilationWithAnalyzers /// Lock to track the set of active tasks computing tree diagnostics and task computing compilation diagnostics. /// private readonly object _executingTasksLock = new object(); - private readonly Dictionary>? _executingConcurrentTreeTasksOpt; + private readonly Dictionary>? _executingConcurrentTreeTasksOpt; private Tuple? _executingCompilationOrNonConcurrentTreeTask; /// @@ -136,10 +136,10 @@ private CompilationWithAnalyzers(Compilation compilation, ImmutableArray.Empty); _analyzerManager = new AnalyzerManager(analyzers); _driverPool = new ObjectPool(() => _compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None)); - _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; + _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; _concurrentTreeTaskTokensOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary() : null; _executingCompilationOrNonConcurrentTreeTask = null; } @@ -245,6 +245,19 @@ private void VerifyTree(SyntaxTree tree) } } + private void VerifyAdditionalFile(AdditionalText file) + { + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + if (_analysisOptions.Options == null || !_analysisOptions.Options.AdditionalFiles.Contains(file)) + { + throw new ArgumentException(CodeAnalysisResources.InvalidAdditionalFile, nameof(file)); + } + } + #endregion /// @@ -341,7 +354,7 @@ private async Task> GetAnalyzerCompilationDiagnostics var diagnostics = ImmutableArray.Empty; var hasAllAnalyzers = analyzers.Length == Analyzers.Length; - var analysisScope = new AnalysisScope(_compilation, analyzers, hasAllAnalyzers, _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(_compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); Func> getPendingEvents = () => _analysisState.GetPendingEvents(analyzers, includeSourceEvents: true, includeNonSourceEvents: true, cancellationToken); @@ -367,7 +380,7 @@ private async Task> GetAnalyzerDiagnosticsWithoutStat // Get analyzer diagnostics for the given analysis scope. var hasAllAnalyzers = analyzers.Length == Analyzers.Length; - var analysisScope = new AnalysisScope(_compilation, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(_compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: true); } @@ -394,7 +407,7 @@ private async Task ComputeAnalyzerDiagnosticsWithoutStateTrackingAsync(Cancellat driver = compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None); driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); var hasAllAnalyzers = analyzers.Length == Analyzers.Length; - var analysisScope = new AnalysisScope(compilation, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue, analysisScope, cancellationToken); // Force compilation diagnostics and wait for analyzer execution to complete. @@ -435,7 +448,7 @@ private async Task> GetAllDiagnosticsWithoutStateTrac driver = compilation.CreateAnalyzerDriver(analyzers, _analyzerManager, severityFilter: SeverityFilter.None); driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); var hasAllAnalyzers = analyzers.Length == Analyzers.Length; - var analysisScope = new AnalysisScope(compilation, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); + var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue, analysisScope, cancellationToken); // Force compilation diagnostics and wait for analyzer execution to complete. @@ -493,7 +506,7 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, Cancellation { VerifyTree(tree); - return GetAnalysisResultCoreAsync(tree, Analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(tree), Analyzers, cancellationToken); } /// @@ -508,19 +521,49 @@ public Task GetAnalysisResultAsync(SyntaxTree tree, ImmutableArr VerifyTree(tree); VerifyExistingAnalyzersArgument(analyzers); - return GetAnalysisResultCoreAsync(tree, analyzers, cancellationToken); + return GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(tree), analyzers, cancellationToken); } - private async Task GetAnalysisResultCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) + /// + /// Returns an populated with produced by all from analyzing the given additional . + /// The given must be part of for the for this CompilationWithAnalyzers instance. + /// Depending on analyzers' behavior, some diagnostics that would be reported for the file by an analysis of the complete compilation can be absent. + /// + /// Additional file to analyze. + /// Cancellation token. + public async Task GetAnalysisResultAsync(AdditionalText file, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, tree, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + VerifyAdditionalFile(file); + + return await GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(file), Analyzers, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns an populated with produced by given from analyzing the given additional . + /// The given must be part of for the for this CompilationWithAnalyzers instance. + /// Depending on analyzers' behavior, some diagnostics that would be reported for the file by an analysis of the complete compilation can be absent. + /// + /// Additional file to analyze. + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task GetAnalysisResultAsync(AdditionalText file, ImmutableArray analyzers, CancellationToken cancellationToken) + { + VerifyAdditionalFile(file); + VerifyExistingAnalyzersArgument(analyzers); + + return await GetAnalysisResultCoreAsync(new SourceOrAdditionalFile(file), analyzers, cancellationToken).ConfigureAwait(false); + } + + private async Task GetAnalysisResultCoreAsync(SourceOrAdditionalFile file, ImmutableArray analyzers, CancellationToken cancellationToken) + { + var analysisScope = new AnalysisScope(analyzers, file, filterSpan: null, isSyntacticSingleFileAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSyntaxDiagnosticsAsync(analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.ToAnalysisResult(analyzers, cancellationToken); } private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, tree, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSyntaxDiagnosticsAsync(analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -608,14 +651,14 @@ public Task GetAnalysisResultAsync(SemanticModel model, TextSpan private async Task GetAnalysisResultCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, model.SyntaxTree, filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(model.SyntaxTree), filterSpan, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.ToAnalysisResult(analyzers, cancellationToken); } private async Task> GetAnalyzerSemanticDiagnosticsCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - var analysisScope = new AnalysisScope(analyzers, model.SyntaxTree, filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + var analysisScope = new AnalysisScope(analyzers, new SourceOrAdditionalFile(model.SyntaxTree), filterSpan, isSyntacticSingleFileAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); await ComputeAnalyzerSemanticDiagnosticsAsync(model, analysisScope, cancellationToken).ConfigureAwait(false); return _analysisResultBuilder.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); } @@ -698,7 +741,7 @@ await Task.WhenAll(partialTrees.Select(tree => Task.Run(() => { var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, tree, filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); return ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false); }, cancellationToken))).ConfigureAwait(false); } @@ -708,7 +751,7 @@ await Task.WhenAll(partialTrees.Select(tree => { cancellationToken.ThrowIfCancellationRequested(); var treeModel = _compilationData.GetOrCreateCachedSemanticModel(tree, _compilation, cancellationToken); - analysisScope = new AnalysisScope(analysisScope.Analyzers, tree, filterSpan: null, syntaxAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); + analysisScope = new AnalysisScope(analysisScope.Analyzers, new SourceOrAdditionalFile(tree), filterSpan: null, isSyntacticSingleFileAnalysis: false, analysisScope.ConcurrentAnalysis, analysisScope.CategorizeDiagnostics); await ComputeAnalyzerSemanticDiagnosticsAsync(treeModel, analysisScope, cancellationToken, forceCompletePartialTrees: false).ConfigureAwait(false); } } @@ -792,7 +835,7 @@ await Task.WhenAll(partialTrees.Select(tree => cancellationSource); // Wait for higher priority tree document tasks to complete. - computeTask = await SetActiveAnalysisTaskAsync(getComputeTask, analysisScope.FilterTreeOpt, newTaskToken, cancellationToken).ConfigureAwait(false); + computeTask = await SetActiveAnalysisTaskAsync(getComputeTask, analysisScope.FilterFileOpt, newTaskToken, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -810,7 +853,7 @@ await Task.WhenAll(partialTrees.Select(tree => } finally { - ClearExecutingTask(computeTask, analysisScope.FilterTreeOpt); + ClearExecutingTask(computeTask, analysisScope.FilterFileOpt); computeTask = null; } } @@ -833,13 +876,13 @@ private void GenerateCompilationEvents(AnalysisScope analysisScope, Cancellation { // Invoke GetDiagnostics to populate CompilationEvent queue for the given analysis scope. // Discard the returned diagnostics. - if (analysisScope.FilterTreeOpt == null) + if (analysisScope.FilterFileOpt == null) { _ = _compilation.GetDiagnostics(cancellationToken); } - else if (!analysisScope.IsSyntaxOnlyTreeAnalysis) + else if (!analysisScope.IsSyntacticSingleFileAnalysis) { - var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterTreeOpt, _compilation, cancellationToken); + var mappedModel = _compilationData.GetOrCreateCachedSemanticModel(analysisScope.FilterFileOpt!.Value.SourceTree!, _compilation, cancellationToken); _ = mappedModel.GetDiagnostics(cancellationToken: cancellationToken); } } @@ -869,6 +912,12 @@ ImmutableArray dequeueGeneratedCompilationEvents() while (_compilation.EventQueue.TryDequeue(out CompilationEvent compilationEvent)) { + if (compilationEvent is CompilationStartedEvent compilationStartedEvent && + _analysisOptions.Options?.AdditionalFiles.Length > 0) + { + compilationEvent = compilationStartedEvent.WithAdditionalFiles(_analysisOptions.Options.AdditionalFiles); + } + builder.Add(compilationEvent); } @@ -961,11 +1010,11 @@ private async Task ComputeAnalyzerDiagnosticsCoreAsync(AnalyzerDriver driver, As } } - private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SyntaxTree? treeOpt, int newTaskToken, CancellationToken cancellationToken) + private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SourceOrAdditionalFile? fileOpt, int newTaskToken, CancellationToken cancellationToken) { - if (treeOpt != null) + if (fileOpt.HasValue) { - return SetActiveTreeAnalysisTaskAsync(getNewAnalysisTask, treeOpt, newTaskToken, cancellationToken); + return SetActiveTreeAnalysisTaskAsync(getNewAnalysisTask, fileOpt.Value, newTaskToken, cancellationToken); } else { @@ -1031,7 +1080,7 @@ private async Task WaitForActiveAnalysisTasksAsync(bool waitForTreeTasks, bool w } } - private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SyntaxTree tree, int newTaskToken, CancellationToken cancellationToken) + private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SourceOrAdditionalFile tree, int newTaskToken, CancellationToken cancellationToken) { try { @@ -1098,22 +1147,22 @@ private void SuspendAnalysis_NoLock(Task computeTask, CancellationTokenSource ct } } - private void ClearExecutingTask(Task? computeTask, SyntaxTree? treeOpt) + private void ClearExecutingTask(Task? computeTask, SourceOrAdditionalFile? fileOpt) { if (computeTask != null) { lock (_executingTasksLock) { Tuple? executingTask; - if (treeOpt != null && _analysisOptions.ConcurrentAnalysis) + if (fileOpt.HasValue && _analysisOptions.ConcurrentAnalysis) { Debug.Assert(_executingConcurrentTreeTasksOpt != null); Debug.Assert(_concurrentTreeTaskTokensOpt != null); - if (_executingConcurrentTreeTasksOpt.TryGetValue(treeOpt, out executingTask) && + if (_executingConcurrentTreeTasksOpt.TryGetValue(fileOpt.Value, out executingTask) && executingTask.Item1 == computeTask) { - _executingConcurrentTreeTasksOpt.Remove(treeOpt); + _executingConcurrentTreeTasksOpt.Remove(fileOpt.Value); } if (_concurrentTreeTaskTokensOpt.ContainsKey(computeTask)) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs index 31b58d26a3612..b0c476cf5af44 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs @@ -120,6 +120,16 @@ public virtual void RegisterSymbolStartAction(Action /// Action to be executed at completion of parsing of a document. public abstract void RegisterSyntaxTreeAction(Action action); + /// + /// Register an action to be executed for each non-code document. + /// An additional file action reports s about the of a document. + /// + /// Action to be executed for each non-code document. + public virtual void RegisterAdditionalFileAction(Action action) + { + throw new NotImplementedException(); + } + /// /// Register an action to be executed at completion of semantic analysis of a with an appropriate Kind. /// A syntax node action can report s about s, and can also collect @@ -405,6 +415,16 @@ public virtual void RegisterOperationBlockAction(ActionAction to be executed at completion of parsing of a document. public abstract void RegisterSyntaxTreeAction(Action action); + /// + /// Register an action to be executed for each non-code document. + /// An additional file action reports s about the of a document. + /// + /// Action to be executed for each non-code document. + public virtual void RegisterAdditionalFileAction(Action action) + { + throw new NotImplementedException(); + } + /// /// Register an action to be executed at completion of semantic analysis of a with an appropriate Kind. /// A syntax node action can report s about s, and can also collect @@ -1285,6 +1305,66 @@ public void ReportDiagnostic(Diagnostic diagnostic) } } + /// + /// Context for an additional file action. + /// An additional file action can use an to report s about a non-source document. + /// + public readonly struct AdditionalFileAnalysisContext + { + private readonly Action _reportDiagnostic; + private readonly Func _isSupportedDiagnostic; + + /// + /// that is the subject of the analysis. + /// + public AdditionalText AdditionalFile { get; } + + /// + /// Options specified for the analysis. + /// + public AnalyzerOptions Options { get; } + + /// + /// Token to check for requested cancellation of the analysis. + /// + public CancellationToken CancellationToken { get; } + + /// + /// Compilation being analyzed. + /// + public Compilation Compilation { get; } + + internal AdditionalFileAnalysisContext( + AdditionalText additionalFile, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + Compilation compilation, + CancellationToken cancellationToken) + { + AdditionalFile = additionalFile; + Options = options; + _reportDiagnostic = reportDiagnostic; + _isSupportedDiagnostic = isSupportedDiagnostic; + Compilation = compilation; + CancellationToken = cancellationToken; + } + + /// + /// Report a diagnostic for the given . + /// A diagnostic in a non-source document should be created with a non-source , + /// which can be created using API. + /// + public void ReportDiagnostic(Diagnostic diagnostic) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(diagnostic, Compilation, _isSupportedDiagnostic); + lock (_reportDiagnostic) + { + _reportDiagnostic(diagnostic); + } + } + } + /// /// Context for a syntax node action. /// A syntax node action can use a to report s for a . diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs index 5514fff6cf96e..46593837d0ceb 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs @@ -169,6 +169,17 @@ public SyntaxTreeAnalyzerAction(Action action, Diagno public Action Action { get { return _action; } } } + internal sealed class AdditionalFileAnalyzerAction : AnalyzerAction + { + public AdditionalFileAnalyzerAction(Action action, DiagnosticAnalyzer analyzer) + : base(analyzer) + { + Action = action; + } + + public Action Action { get; } + } + internal sealed class CodeBlockStartAnalyzerAction : AnalyzerAction where TLanguageKindEnum : struct { private readonly Action> _action; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs index 98015e0aa2d4f..71e465685ee9f 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs @@ -114,7 +114,7 @@ public override void Enqueue(Diagnostic diagnostic) public override void EnqueueLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) { - Debug.Assert(diagnostic.Location.IsInSource); + Debug.Assert(diagnostic.Location.Kind == LocationKind.SourceFile || diagnostic.Location.Kind == LocationKind.ExternalFile); if (isSyntaxDiagnostic) { EnqueueCore(ref _lazyLocalSyntaxDiagnostics, diagnostic, analyzer); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index e009f49f68915..ee52efd4a8e77 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -47,6 +47,12 @@ public override void RegisterSyntaxTreeAction(Action _scope.RegisterSyntaxTreeAction(_analyzer, action); } + public override void RegisterAdditionalFileAction(Action action) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(action); + _scope.RegisterAdditionalFileAction(_analyzer, action); + } + public override void RegisterSemanticModelAction(Action action) { DiagnosticAnalysisContextHelpers.VerifyArguments(action); @@ -147,6 +153,12 @@ public override void RegisterSyntaxTreeAction(Action _scope.RegisterSyntaxTreeAction(_analyzer, action); } + public override void RegisterAdditionalFileAction(Action action) + { + DiagnosticAnalysisContextHelpers.VerifyArguments(action); + _scope.RegisterAdditionalFileAction(_analyzer, action); + } + public override void RegisterSemanticModelAction(Action action) { DiagnosticAnalysisContextHelpers.VerifyArguments(action); @@ -511,6 +523,12 @@ public void RegisterSyntaxTreeAction(DiagnosticAnalyzer analyzer, Action action) + { + var analyzerAction = new AdditionalFileAnalyzerAction(action, analyzer); + this.GetOrCreateAnalyzerActions(analyzer).Value.AddAdditionalFileAction(analyzerAction); + } + public void RegisterSymbolAction(DiagnosticAnalyzer analyzer, Action action, ImmutableArray symbolKinds) { SymbolAnalyzerAction analyzerAction = new SymbolAnalyzerAction(action, symbolKinds, analyzer); @@ -645,6 +663,7 @@ internal struct AnalyzerActions private ImmutableArray _compilationEndActions; private ImmutableArray _compilationActions; private ImmutableArray _syntaxTreeActions; + private ImmutableArray _additionalFileActions; private ImmutableArray _semanticModelActions; private ImmutableArray _symbolActions; private ImmutableArray _symbolStartActions; @@ -665,6 +684,7 @@ internal AnalyzerActions(bool concurrent) _compilationEndActions = ImmutableArray.Empty; _compilationActions = ImmutableArray.Empty; _syntaxTreeActions = ImmutableArray.Empty; + _additionalFileActions = ImmutableArray.Empty; _semanticModelActions = ImmutableArray.Empty; _symbolActions = ImmutableArray.Empty; _symbolStartActions = ImmutableArray.Empty; @@ -687,6 +707,7 @@ public AnalyzerActions( ImmutableArray compilationEndActions, ImmutableArray compilationActions, ImmutableArray syntaxTreeActions, + ImmutableArray additionalFileActions, ImmutableArray semanticModelActions, ImmutableArray symbolActions, ImmutableArray symbolStartActions, @@ -706,6 +727,7 @@ public AnalyzerActions( _compilationEndActions = compilationEndActions; _compilationActions = compilationActions; _syntaxTreeActions = syntaxTreeActions; + _additionalFileActions = additionalFileActions; _semanticModelActions = semanticModelActions; _symbolActions = symbolActions; _symbolStartActions = symbolStartActions; @@ -726,6 +748,7 @@ public AnalyzerActions( public readonly int CompilationEndActionsCount { get { return _compilationEndActions.Length; } } public readonly int CompilationActionsCount { get { return _compilationActions.Length; } } public readonly int SyntaxTreeActionsCount { get { return _syntaxTreeActions.Length; } } + public readonly int AdditionalFileActionsCount { get { return _additionalFileActions.Length; } } public readonly int SemanticModelActionsCount { get { return _semanticModelActions.Length; } } public readonly int SymbolActionsCount { get { return _symbolActions.Length; } } public readonly int SymbolStartActionsCount { get { return _symbolStartActions.Length; } } @@ -762,6 +785,11 @@ internal readonly ImmutableArray SyntaxTreeActions get { return _syntaxTreeActions; } } + internal readonly ImmutableArray AdditionalFileActions + { + get { return _additionalFileActions; } + } + internal readonly ImmutableArray SemanticModelActions { get { return _semanticModelActions; } @@ -861,6 +889,12 @@ internal void AddSyntaxTreeAction(SyntaxTreeAnalyzerAction action) IsEmpty = false; } + internal void AddAdditionalFileAction(AdditionalFileAnalyzerAction action) + { + _additionalFileActions = _additionalFileActions.Add(action); + IsEmpty = false; + } + internal void AddSemanticModelAction(SemanticModelAnalyzerAction action) { _semanticModelActions = _semanticModelActions.Add(action); @@ -954,6 +988,7 @@ public readonly AnalyzerActions Append(in AnalyzerActions otherActions, bool app actions._compilationEndActions = _compilationEndActions.AddRange(otherActions._compilationEndActions); actions._compilationActions = _compilationActions.AddRange(otherActions._compilationActions); actions._syntaxTreeActions = _syntaxTreeActions.AddRange(otherActions._syntaxTreeActions); + actions._additionalFileActions = _additionalFileActions.AddRange(otherActions._additionalFileActions); actions._semanticModelActions = _semanticModelActions.AddRange(otherActions._semanticModelActions); actions._symbolActions = _symbolActions.AddRange(otherActions._symbolActions); actions._symbolStartActions = appendSymbolStartAndSymbolEndActions ? _symbolStartActions.AddRange(otherActions._symbolStartActions) : _symbolStartActions; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs new file mode 100644 index 0000000000000..4ee354b34ba2a --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SourceOrAdditionalFile.cs @@ -0,0 +1,60 @@ +// 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. + +#nullable enable + +using System; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Represents a source file or an additional file. + /// For source files, is non-null and is null. + /// For additional files, is non-null and is null. + /// + internal readonly struct SourceOrAdditionalFile + : IEquatable + { + public SyntaxTree? SourceTree { get; } + public AdditionalText? AdditionalFile { get; } + + public SourceOrAdditionalFile(SyntaxTree tree) + { + SourceTree = tree; + AdditionalFile = null; + } + + public SourceOrAdditionalFile(AdditionalText file) + { + AdditionalFile = file; + SourceTree = null; + } + + public override bool Equals(object? obj) + => obj is SourceOrAdditionalFile file && Equals(file); + + public bool Equals(SourceOrAdditionalFile other) + => SourceTree == other.SourceTree && AdditionalFile == other.AdditionalFile; + + public static bool operator ==(SourceOrAdditionalFile left, SourceOrAdditionalFile right) + => Equals(left, right); + + public static bool operator !=(SourceOrAdditionalFile left, SourceOrAdditionalFile right) + => !Equals(left, right); + + public override int GetHashCode() + { + if (SourceTree != null) + { + return Hash.Combine(true, SourceTree.GetHashCode()); + } + else + { + RoslynDebug.Assert(AdditionalFile != null); + return Hash.Combine(false, AdditionalFile.GetHashCode()); + } + } + } +} diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index c6ba5e3fec3b9..001ea1d7b9c3a 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1 +1,16 @@ -Microsoft.CodeAnalysis.Operations.IPatternOperation.NarrowedType.get -> Microsoft.CodeAnalysis.ITypeSymbol \ No newline at end of file +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.AdditionalFile.get -> Microsoft.CodeAnalysis.AdditionalText +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions +Microsoft.CodeAnalysis.Diagnostics.AdditionalFileAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void +Microsoft.CodeAnalysis.Diagnostics.AnalysisResult.AdditionalFileDiagnostics.get -> System.Collections.Immutable.ImmutableDictionary>> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalysisResultAsync(Microsoft.CodeAnalysis.AdditionalText file, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AdditionalFileActionsCount.set -> void +const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointMethodName = "
$" -> string +const Microsoft.CodeAnalysis.WellKnownMemberNames.TopLevelStatementsEntryPointTypeName = "$" -> string +Microsoft.CodeAnalysis.Operations.IPatternOperation.NarrowedType.get -> Microsoft.CodeAnalysis.ITypeSymbol +virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void +virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterAdditionalFileAction(System.Action action) -> void \ No newline at end of file diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 7e9eabd7ac23e..692b38b5febf5 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -344,6 +344,16 @@ public static class WellKnownMemberNames public const string SliceMethodName = "Slice"; // internal until we settle on this long-term - internal const string CloneMethodName = "<>Clone"; + internal const string CloneMethodName = "$"; + + /// + /// The name of an entry point method synthesized for top-level statements. + /// + public const string TopLevelStatementsEntryPointMethodName = "
$"; + + /// + /// The name of a type synthesized for a top-level statements entry point method. + /// + public const string TopLevelStatementsEntryPointTypeName = "$"; } } diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf index 40d5dbf3936fd..e142530153807 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Potlačené ID diagnostiky {0} neodpovídá potlačitelnému ID {1} pro daný popisovač potlačení. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf index a16264b79ce1a..f5060dd8cbb9b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Die unterdrückte Diagnose-ID "{0}" entspricht nicht der unterdrückbaren ID "{1}" für den angegebenen Deskriptor zur Unterdrückung. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf index 7a3cb437dc2a6..d07e6c88d818b 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. El id. de diagnóstico "{0}" suprimido no coincide con el id. "{1}" que se puede suprimir para el descriptor de supresión dado. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf index f0774057d8213..4f52067068ea0 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. L'ID de diagnostic supprimé '{0}' ne correspond pas à l'ID supprimable '{1}' pour le descripteur de suppression spécifié. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf index 515a5a4974757..a49824fa3310f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. L'ID diagnostica '{0}' eliminato non corrisponde all'ID eliminabile '{1}' per il descrittore di eliminazione specificato. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf index 1a2135a09b612..47f38ebb4170d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 抑制された診断 ID '{0}' が、指定された抑制記述子の抑制可能な ID '{1}' と一致しません。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf index 2faaacfd41e40..721ae2818be61 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 표시되지 않는 진단 ID '{0}'이(가) 지정된 비표시 설명자의 표시하지 않을 수 있는 ID '{1}'과(와) 일치하지 않습니다. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf index bf39e02888770..d0fc007db556f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Pominięty identyfikator diagnostyki „{0}” nie pasuje do możliwego do pominięcia identyfikatora „{1}” dla danego deskryptora pomijania. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf index 4201e76d383e9..a9dde446a1232 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. A ID de diagnóstico suprimida '{0}' não corresponde à ID suprimível '{1}' para o descritor de supressão fornecido. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf index 7195026b5db8c..5b6b82a24f1db 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. Подавленный идентификатор диагностики "{0}" не соответствует подавленному идентификатору "{1}" для заданного дескриптора подавления. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf index a016f43e492f0..2b48c77614d3d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. '{0}' gizlenmiş tanılama kimliği, belirtilen gizleme tanımlayıcısı için gizlenebilir '{1}' kimliği ile eşleşmiyor. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf index 6396c5ef50add..952caebc8bd89 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 对于给定的禁止显示描述符,禁止显示的诊断 ID“{0}”与可禁止显示的 ID“{1}”不匹配。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf index e153d6fd575d1..8aa6e210dbd5f 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf @@ -39,6 +39,11 @@ The hintName of the added source file must be unique within a generator. + + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + Additional file doesn't belong to the underlying 'CompilationWithAnalyzers'. + + Suppressed diagnostic ID '{0}' does not match suppressable ID '{1}' for the given suppression descriptor. 隱藏的診斷識別碼 '{0}' 不符合指定隱藏描述項的可隱藏識別碼 '{1}'。 diff --git a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs index 559c97032114d..c8865427d463f 100644 --- a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs +++ b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs @@ -96,27 +96,22 @@ public bool TryCreateCompiler(RunRequest request, out CommonCompiler compiler) public BuildResponse RunCompilation(RunRequest request, CancellationToken cancellationToken) { - Log($"CurrentDirectory = '{request.CurrentDirectory}'"); - Log($"LIB = '{request.LibDirectory}'"); - for (int i = 0; i < request.Arguments.Length; ++i) - { - Log($"Argument[{i}] = '{request.Arguments[i]}'"); - } + Log($@" +Run Compilation + CurrentDirectory = '{request.CurrentDirectory} + LIB = '{request.LibDirectory}'"); // Compiler server must be provided with a valid temporary directory in order to correctly // isolate signing between compilations. if (string.IsNullOrEmpty(request.TempDirectory)) { - Log($"Rejecting build due to missing temp directory"); - return new RejectedBuildResponse(); + return new RejectedBuildResponse("Missing temp directory"); } CommonCompiler compiler; if (!TryCreateCompiler(request, out compiler)) { - // We can't do anything with a request we don't know about. - Log($"Got request with id '{request.Language}'"); - return new RejectedBuildResponse(); + return new RejectedBuildResponse($"Cannot create compiler for language id {request.Language}"); } bool utf8output = compiler.Arguments.Utf8Output; @@ -125,11 +120,16 @@ public BuildResponse RunCompilation(RunRequest request, CancellationToken cancel return new AnalyzerInconsistencyBuildResponse(); } - Log($"****Running {request.Language} compiler..."); + Log($"Begin {request.Language} compiler run"); TextWriter output = new StringWriter(CultureInfo.InvariantCulture); int returnCode = compiler.Run(output, cancellationToken); - Log($"****{request.Language} Compilation complete.\r\n****Return code: {returnCode}\r\n****Output:\r\n{output.ToString()}\r\n"); - return new CompletedBuildResponse(returnCode, utf8output, output.ToString()); + var outputString = output.ToString(); + Log(@$" +End {request.Language} Compilation complete. +Return code: {returnCode} +Output: +{outputString}"); + return new CompletedBuildResponse(returnCode, utf8output, outputString); } } } diff --git a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs index 35e1a5597e1b6..1605bf4624905 100644 --- a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs +++ b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs @@ -112,8 +112,7 @@ public void Close() // The client connection failing to close isn't fatal to the server process. It is simply a client // for which we can no longer communicate and that's okay because the Close method indicates we are // done with the client already. - var msg = string.Format($"Pipe {LoggingIdentifier}: Error closing pipe."); - CompilerServerLogger.LogException(e, msg); + CompilerServerLogger.LogException(e, $"Pipe {LoggingIdentifier}: Error closing pipe."); } } @@ -124,14 +123,12 @@ public void Close() BuildRequest request; try { - Log("Begin reading request."); request = await BuildRequest.ReadAsync(_stream, cancellationToken).ConfigureAwait(false); ValidateBuildRequest(request); - Log("End reading request."); } catch (Exception e) { - LogException(e, "Error reading build request."); + CompilerServerLogger.LogException(e, "Error reading build request."); return new ConnectionData(CompletionReason.CompilationNotStarted); } @@ -149,7 +146,7 @@ public void Close() } else if (!allowCompilationRequests) { - return await HandleRejectedRequestAsync(cancellationToken).ConfigureAwait(false); + return await HandleRejectedRequestAsync("Compilation requests not allowed at this time", cancellationToken).ConfigureAwait(false); } else { @@ -181,10 +178,8 @@ private async Task HandleCompilationRequestAsync(BuildRequest re try { - Log("Begin writing response."); await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); reason = CompletionReason.CompilationCompleted; - Log("End writing response."); } catch { @@ -216,9 +211,9 @@ private async Task HandleIncorrectHashRequestAsync(CancellationT return new ConnectionData(CompletionReason.CompilationNotStarted); } - private async Task HandleRejectedRequestAsync(CancellationToken cancellationToken) + private async Task HandleRejectedRequestAsync(string reason, CancellationToken cancellationToken) { - var response = new RejectedBuildResponse(); + var response = new RejectedBuildResponse(reason); await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); return new ConnectionData(CompletionReason.CompilationNotStarted); } @@ -266,13 +261,8 @@ private Task ServeBuildRequestAsync(BuildRequest buildRequest, Ca { Func func = () => { - // Do the compilation - Log("Begin compilation"); - var request = BuildProtocolUtil.GetRunRequest(buildRequest); var response = _compilerServerHost.RunCompilation(request, cancellationToken); - - Log("End compilation"); return response; }; @@ -280,15 +270,5 @@ private Task ServeBuildRequestAsync(BuildRequest buildRequest, Ca task.Start(); return task; } - - private void Log(string message) - { - CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message); - } - - private void LogException(Exception e, string message) - { - CompilerServerLogger.LogException(e, string.Format("Client {0}: {1}", _loggingIdentifier, message)); - } } } diff --git a/src/Compilers/Server/VBCSCompiler/ServerDispatcher.cs b/src/Compilers/Server/VBCSCompiler/ServerDispatcher.cs index 762a7457559fa..4a7039c97e69d 100644 --- a/src/Compilers/Server/VBCSCompiler/ServerDispatcher.cs +++ b/src/Compilers/Server/VBCSCompiler/ServerDispatcher.cs @@ -229,6 +229,7 @@ private void HandleCompletedListenTask(CancellationToken cancellationToken) private void HandleCompletedTimeoutTask() { + CompilerServerLogger.Log("Timeout triggered. Shutting down server."); _diagnosticListener.KeepAliveReached(); _listenCancellationTokenSource.Cancel(); _timeoutTask = null; @@ -298,10 +299,12 @@ private void HandleCompletedConnections() case CompletionReason.ClientDisconnect: // Have to assume the worst here which is user pressing Ctrl+C at the command line and // hence wanting all compilation to end. + CompilerServerLogger.Log("Unexpected client disconnect. Shutting down server"); shutdown = true; break; case CompletionReason.ClientException: case CompletionReason.ClientShutdownRequest: + CompilerServerLogger.Log($"Unexpected client completion: {connectionData.CompletionReason}. Shutting down server"); shutdown = true; break; default: diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs index 1299275b4c5a7..e101d9bdd63ea 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs @@ -512,7 +512,7 @@ public async Task CancelWillCancelCompilation() } cancellationToken.WaitHandle.WaitOne(); - return new RejectedBuildResponse(); + return new RejectedBuildResponse(""); }; var list = new List>(); diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index a6081eb936bd6..17d308b92b369 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -113,14 +113,14 @@ internal static async Task RunServerCompilationCoreAsync( CreateServerFunc createServerFunc, CancellationToken cancellationToken) { - if (pipeName == null) + if (pipeName is null) { - return new RejectedBuildResponse(); + throw new ArgumentException(nameof(pipeName)); } if (buildPaths.TempDirectory == null) { - return new RejectedBuildResponse(); + throw new ArgumentException(nameof(buildPaths)); } // early check for the build hash. If we can't find it something is wrong; no point even trying to go to the server @@ -132,21 +132,21 @@ internal static async Task RunServerCompilationCoreAsync( var pipeTask = tryConnectToServer(pipeName, buildPaths, timeoutOverride, createServerFunc, cancellationToken); if (pipeTask is null) { - return new RejectedBuildResponse(); + return new RejectedBuildResponse("Failed to connect to server"); } else { var pipe = await pipeTask.ConfigureAwait(false); if (pipe is null) { - return new RejectedBuildResponse(); + return new RejectedBuildResponse("Failed to connect to server"); } else { var request = BuildRequest.Create(language, buildPaths.WorkingDirectory, buildPaths.TempDirectory, - BuildProtocolConstants.GetCommitHash(), + BuildProtocolConstants.GetCommitHash() ?? "", arguments, keepAlive, libEnvVariable); @@ -259,7 +259,7 @@ private static async Task TryCompileAsync(NamedPipeClientStream p catch (Exception e) { LogException(e, "Error writing build request."); - return new RejectedBuildResponse(); + return new RejectedBuildResponse($"Error writing build request: {e.Message}"); } // Wait for the compilation and a monitor to detect if the server disconnects @@ -283,13 +283,13 @@ private static async Task TryCompileAsync(NamedPipeClientStream p catch (Exception e) { LogException(e, "Error reading response"); - response = new RejectedBuildResponse(); + response = new RejectedBuildResponse($"Error reading response: {e.Message}"); } } else { - Log("Server disconnect"); - response = new RejectedBuildResponse(); + Log("Client disconnect"); + response = new RejectedBuildResponse($"Client disconnected"); } // Cancel whatever task is still around diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index e1e1e408b5fd7..3ea5b96d88a36 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -9960,6 +9960,31 @@ End Class") Assert.Contains("warning AD0001: Analyzer 'Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers+NamedTypeAnalyzerWithConfigurableEnabledByDefault' threw an exception of type 'System.NotImplementedException'", output, StringComparison.Ordinal) End If End Sub + + + Public Sub TestAdditionalFileAnalyzer(registerFromInitialize As Boolean) + Dim srcDirectory = Temp.CreateDirectory() + + Dim source = " +Class C +End Class" + Dim srcFile = srcDirectory.CreateFile("a.vb") + srcFile.WriteAllText(source) + + Dim additionalText = "Additional Text" + Dim additionalFile = srcDirectory.CreateFile("b.txt") + additionalFile.WriteAllText(additionalText) + + Dim diagnosticSpan = New TextSpan(2, 2) + Dim analyzer As DiagnosticAnalyzer = New AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan) + + Dim output = VerifyOutput(srcDirectory, srcFile, expectedWarningCount:=1, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags:={"/additionalfile:" & additionalFile.Path}, + analyzers:=ImmutableArray.Create(analyzer)) + Assert.Contains("b.txt(1) : warning ID0001", output, StringComparison.Ordinal) + CleanupAllGeneratedFiles(srcDirectory.Path) + End Sub End Class diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb index 2f7057b55d14e..d246cc377540d 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb @@ -19,7 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics Public Sub DiagnosticAnalyzerAllInOne() Dim source = TestResource.AllInOneVisualBasicBaseline Dim analyzer = New BasicTrackingDiagnosticAnalyzer() - CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}) + Dim options = New AnalyzerOptions({DirectCast(New TestAdditionalText(), AdditionalText)}.ToImmutableArray()) + CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}, options) analyzer.VerifyAllAnalyzerMembersWereCalled() analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds() analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(New HashSet(Of SyntaxKind)()) @@ -44,8 +45,9 @@ End Enum Public Sub AnalyzerDriverIsSafeAgainstAnalyzerExceptions() Dim compilation = CreateCompilationWithMscorlib40({TestResource.AllInOneVisualBasicCode}) + Dim options = New AnalyzerOptions({CType(new TestAdditionalText(), AdditionalText)}.ToImmutableArray()) ThrowingDiagnosticAnalyzer(Of SyntaxKind).VerifyAnalyzerEngineIsSafeAgainstExceptions( - Function(analyzer) compilation.GetAnalyzerDiagnostics({analyzer})) + Function(analyzer) compilation.GetAnalyzerDiagnostics({analyzer}, options)) End Sub diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb index d904e32d6d954..ad31393e47e0a 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.vb @@ -4,12 +4,14 @@ Imports System.Collections.Immutable Imports System.Runtime.Serialization +Imports System.Threading Imports Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Diagnostics.VisualBasic Imports Microsoft.CodeAnalysis.FlowAnalysis Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities @@ -1560,5 +1562,41 @@ End Namespace compilation.VerifyAnalyzerDiagnostics(analyzers, Nothing, Nothing, Diagnostic("SymbolStartRuleId").WithArguments("MyApplication", "Analyzer1").WithLocation(1, 1)) End Sub + + + Public Async Function TestAdditionalFileAnalyzer(registerFromInitialize As Boolean) As Task + Dim tree = VisualBasicSyntaxTree.ParseText(String.Empty) + Dim compilation = CreateCompilationWithMscorlib45({tree}) + compilation.VerifyDiagnostics() + + Dim additionalFile As AdditionalText = New TestAdditionalText("Additional File Text") + Dim options = New AnalyzerOptions(ImmutableArray.Create(additionalFile)) + Dim diagnosticSpan = New TextSpan(2, 2) + Dim analyzer = New AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan) + Dim analyzers As ImmutableArray(Of DiagnosticAnalyzer) = ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer) + + Dim diagnostics = Await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics, diagnosticSpan, analyzer, additionalFile) + + Dim analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(additionalFile, CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.AdditionalFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) + + analysisResult = Await compilation.WithAnalyzers(analyzers, options).GetAnalysisResultAsync(CancellationToken.None) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.GetAllDiagnostics(), diagnosticSpan, analyzer, additionalFile) + TestAdditionalFileAnalyzer_VerifyDiagnostics(analysisResult.AdditionalFileDiagnostics(additionalFile)(analyzer), diagnosticSpan, analyzer, additionalFile) + End Function + + Private Shared Sub TestAdditionalFileAnalyzer_VerifyDiagnostics(diagnostics As ImmutableArray(Of Diagnostic), + expectedDiagnosticSpan As TextSpan, + Analyzer As AdditionalFileAnalyzer, + additionalFile As AdditionalText) + Dim diagnostic = Assert.Single(diagnostics) + Assert.Equal(Analyzer.Descriptor.Id, diagnostic.Id) + Assert.Equal(LocationKind.ExternalFile, diagnostic.Location.Kind) + Dim location = DirectCast(diagnostic.Location, ExternalFileLocation) + Assert.Equal(additionalFile.Path, location.FilePath) + Assert.Equal(expectedDiagnosticSpan, location.SourceSpan) + End Sub End Class End Namespace diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs index dd11cb0b338c1..dab5328071def 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs @@ -25,6 +25,8 @@ public ExtensionMethodImportCompletionProviderTests(CSharpTestWorkspaceFixture w { } + private const string NonBreakingSpaceString = "\x00A0"; + private bool? ShowImportCompletionItemsOptionValue { get; set; } = true; private bool IsExpandedCompletion { get; set; } = true; @@ -1667,6 +1669,61 @@ await VerifyImportItemExistsAsync( expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod()"); } + [InlineData(ReferenceType.Project)] + [InlineData(ReferenceType.Metadata)] + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestDescriptionOfOverloads(ReferenceType refType) + { + var refDoc = @" +using System; + +namespace NS2 +{ + public static class Extensions + { + public static bool ExtentionMethod(this int t) => false; + public static bool ExtentionMethod(this int t, int a) => false; + public static bool ExtentionMethod(this int t, int a, int b) => false; + public static bool ExtentionMethod(this int t, T a) => false; + public static bool ExtentionMethod(this int t, T a, T b) => false; + public static bool ExtentionMethod(this int t, T1 a, T2 b) => false; + } +}"; + var srcDoc = @" +namespace NS1 +{ + public class C + { + public void M(int x) + { + x.$$ + } + } +}"; + + var markup = refType switch + { + ReferenceType.Project => CreateMarkupForProjectWithProjectReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), + ReferenceType.Metadata => CreateMarkupForProjectWithMetadataReference(srcDoc, refDoc, LanguageNames.CSharp, LanguageNames.CSharp), + _ => null, + }; + + await VerifyImportItemExistsAsync( + markup, + "ExtentionMethod", + glyph: (int)Glyph.ExtensionMethodPublic, + inlineDescription: "NS2", + expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod() (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})"); + + await VerifyImportItemExistsAsync( + markup, + "ExtentionMethod", + displayTextSuffix: "<>", + glyph: (int)Glyph.ExtensionMethodPublic, + inlineDescription: "NS2", + expectedDescriptionOrNull: $"({CSharpFeaturesResources.extension}) bool int.ExtentionMethod(T a) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.generic_overloads})"); + } + private Task VerifyImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null) => VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PropertySubPatternCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PropertySubPatternCompletionProviderTests.cs index cc8831baeac7f..e8737a079dbc1 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PropertySubPatternCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PropertySubPatternCompletionProviderTests.cs @@ -528,7 +528,7 @@ class D await VerifyNoItemsExistAsync(markup); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015")] + [Fact] public async Task PropertiesInRecursivePattern_InPositional_Incomplete() { var markup = @@ -539,16 +539,16 @@ public class Program void M() { - _ = this is ({ $$ }) // no deconstruction into 1 element + _ = this is ({ $$ }) // Can deconstruct into a parenthesized property pattern } public void Deconstruct(out Program x, out Program y) => throw null; } "; - await VerifyNoItemsExistAsync(markup); + await VerifyItemExistsAsync(markup, "P1", displayTextSuffix: ":"); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015")] + [Fact] public async Task PropertiesInRecursivePattern_InPositional_Incomplete_WithoutClosingBrace() { var markup = @@ -559,13 +559,13 @@ public class Program void M() { - _ = this is ({ $$ // no deconstruction into 1 element + _ = this is ({ $$ // Can deconstruct into a parenthesized property pattern } public void Deconstruct(out Program x, out Program y) => throw null; } "; - await VerifyNoItemsExistAsync(markup); + await VerifyItemExistsAsync(markup, "P1", displayTextSuffix: ":"); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 2dcb3fc4beeb7..ccf366bb9d539 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -50,7 +50,9 @@ public async Task DiagnosticAnalyzerDriverAllInOne() using var workspace = TestWorkspace.CreateCSharp(source, TestOptions.Regular); var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer)); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); + var newSolution = workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference }) + .Projects.Single().AddAdditionalDocument(name: "dummy.txt", text: "", filePath: "dummy.txt").Project.Solution; + workspace.TryApplyChanges(newSolution); var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); AccessSupportedDiagnostics(analyzer); diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/WhenKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/WhenKeywordRecommenderTests.cs index 58e3f1fbe848e..46865ac0276e0 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/WhenKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/WhenKeywordRecommenderTests.cs @@ -193,33 +193,33 @@ public async Task TestForSwitchCase_NotAfterColon_BeforeBreak() => public async Task TestForSwitchCase_NotInEmptySwitchStatement() => await VerifyAbsenceAsync(AddInsideMethod(@"switch (1) { $$ }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case int $$ }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType_BeforeBreak() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case int $$ break; }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ break; }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterPredefinedType_BeforeWhen() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case int $$ when }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case int $$ when }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType_BeforeBreak() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ break; }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ break; }")); - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/40015"), Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterGenericType_BeforeWhen() => - await VerifyAbsenceAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ when }")); + await VerifyKeywordAsync(AddInsideMethod(@"switch (new object()) { case Dictionary $$ when }")); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class SyntaxNode { } class C { @@ -228,7 +228,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType_BeforeBreak() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class SyntaxNode { } class C { @@ -237,7 +237,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterCustomType_BeforeWhen() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class SyntaxNode { } class C { @@ -246,7 +246,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" using Type = System.String; class C { @@ -255,7 +255,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias_BeforeBreak() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" using Type = System.String; class C { @@ -264,7 +264,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterTypeAlias_BeforeWhen() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" using Type = System.String; class C { @@ -273,7 +273,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class ValueTuple { } class ValueTuple { } class C @@ -283,7 +283,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName_BeforeBreak() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class ValueTuple { } class ValueTuple { } class C @@ -293,7 +293,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_NotAfterOverloadedTypeName_BeforeWhen() => - await VerifyAbsenceAsync(@" + await VerifyKeywordAsync(@" class ValueTuple { } class ValueTuple { } class C @@ -462,19 +462,19 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar() => - await VerifyKeywordAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ }")); + await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ }")); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar_BeforeBreak() => - await VerifyKeywordAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ break; }")); + await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ break; }")); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterLocalConstantVar_BeforeWhen() => - await VerifyKeywordAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ when }")); + await VerifyAbsenceAsync(AddInsideMethod(@"const object var = null; switch (new object()) { case var $$ when }")); [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar() => - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" class var { } class C { @@ -483,7 +483,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar_BeforeBreak() => - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" class var { } class C { @@ -492,7 +492,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterClassAndLocalConstantVar_BeforeWhen() => - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" class var { } class C { @@ -502,7 +502,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar() { - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" using var = System.String; class C { @@ -514,7 +514,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar_BeforeBreak() { - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" using var = System.String; class C { @@ -526,7 +526,7 @@ class C [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] public async Task TestForSwitchCase_SemanticCheck_AfterTypeAliasAndFieldConstantVar_BeforeWhen() { - await VerifyKeywordAsync(@" + await VerifyAbsenceAsync(@" using var = System.String; class C { diff --git a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs index 3c36cacd47290..a97b478eacc71 100644 --- a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs +++ b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; @@ -73,7 +74,7 @@ public void Register(Workspace workspace) private async Task AnalyzeAsync() { var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); - var diagnosticAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); + var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); var solution = _workspace.CurrentSolution; var documentIds = _workspace.GetOpenDocumentIds().ToImmutableArray(); @@ -82,8 +83,8 @@ private async Task AnalyzeAsync() { foreach (var documentId in documentIds) { - var document = solution.GetDocument(documentId); - if (document == null) + var textDocument = solution.GetTextDocument(documentId); + if (textDocument == null) { continue; } @@ -92,8 +93,15 @@ private async Task AnalyzeAsync() await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false); // do actual analysis - await diagnosticAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); - await diagnosticAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + if (textDocument is Document document) + { + await incrementalAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + await incrementalAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + } + else if (incrementalAnalyzer is IIncrementalAnalyzer2 incrementalAnalyzer2) + { + await incrementalAnalyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + } // don't call project one. } diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index bb63687255d6b..54838a0d589b4 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -566,6 +567,105 @@ private static async Task TestFullSolutionAnalysisForProjectAsync(Project projec Assert.Equal(expectAnalyzerExecuted, called); } + [Theory, CombinatorialData] + internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool testMultiple, BackgroundAnalysisScope analysisScope) + { + using var workspace = new AdhocWorkspace(); + var options = workspace.Options.WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); + workspace.SetOptions(options); + + var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "CSharpProject", "CSharpProject", LanguageNames.CSharp); + var project = workspace.AddProject(projectInfo); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001"); + var analyzers = ImmutableArray.Create(analyzer); + if (testMultiple) + { + analyzer = new AdditionalFileAnalyzer2(registerFromInitialize, diagnosticSpan, id: "ID0002"); + analyzers = analyzers.Add(analyzer); + } + + var analyzerReference = new AnalyzerImageReference(analyzers); + project = project.WithAnalyzerReferences(new[] { analyzerReference }) + .AddAdditionalDocument(name: "dummy.txt", text: "Additional File Text", filePath: "dummy.txt").Project; + if (testMultiple) + { + project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project; + } + + var applied = workspace.TryApplyChanges(project.Solution); + Assert.True(applied); + + // create listener/service/analyzer + var listener = new AsynchronousOperationListener(); + var service = new MyDiagnosticAnalyzerService(listener); + + var diagnostics = new ConcurrentSet(); + service.DiagnosticsUpdated += (s, e) => + { + diagnostics.AddRange(e.Diagnostics); + }; + + var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace); + var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); + + switch (analysisScope) + { + case BackgroundAnalysisScope.ActiveFile: + case BackgroundAnalysisScope.OpenFilesAndProjects: + workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); + await incrementalAnalyzer.AnalyzeNonSourceDocumentAsync(firstAdditionalDocument, InvocationReasons.SyntaxChanged, CancellationToken.None); + break; + + case BackgroundAnalysisScope.FullSolution: + await incrementalAnalyzer.AnalyzeProjectAsync(project, semanticsChanged: true, InvocationReasons.Reanalyze, CancellationToken.None); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(analysisScope); + } + + await listener.ExpeditedWaitAsync(); + + var expectedCount = !testMultiple + ? 1 + : analysisScope == BackgroundAnalysisScope.FullSolution ? 4 : 2; + Assert.Equal(expectedCount, diagnostics.Count); + + for (var i = 0; i < analyzers.Length; i++) + { + analyzer = (AdditionalFileAnalyzer)analyzers[i]; + foreach (var additionalDoc in project.AdditionalDocuments) + { + var applicableDiagnostics = diagnostics.Where( + d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath); + + if (analysisScope != BackgroundAnalysisScope.FullSolution && + firstAdditionalDocument != additionalDoc) + { + Assert.Empty(applicableDiagnostics); + } + else + { + var diagnostic = Assert.Single(applicableDiagnostics); + Assert.Equal(diagnosticSpan, diagnostic.GetTextSpan()); + diagnostics.Remove(diagnostic); + } + } + } + + Assert.Empty(diagnostics); + } + + private class AdditionalFileAnalyzer2 : AdditionalFileAnalyzer + { + public AdditionalFileAnalyzer2(bool registerFromInitialize, TextSpan diagnosticSpan, string id) + : base(registerFromInitialize, diagnosticSpan, id) + { + } + } + [Theory, CombinatorialData] internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeSuppressor, BackgroundAnalysisScope analysisScope) { @@ -785,11 +885,19 @@ private static (bool, bool) CompilerAnalyzerResultSetter(bool syntax, bool seman return (syntax, semantic); } - private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, Document document) + private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument) { - await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + } + + await analyzer.AnalyzeProjectAsync(textDocument.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); } private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index b10ba5e067263..8adc1546c3e30 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -5223,7 +5223,7 @@ class C state.SendBackspace() state.SendTypeChars("w") - Await state.AssertSelectedCompletionItem(displayText:="with", isHardSelected:=False) + Await state.AssertSelectedCompletionItem(displayText:="when", isHardSelected:=False) End Using End Function diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 6a1db70f65a3f..cfb3968a41db5 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -307,8 +307,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var hostProject = this.GetTestProject(info.Id.ProjectId); var hostDocument = new TestHostDocument( text.ToString(), info.Name, info.SourceCodeKind, - info.Id, folders: info.Folders, - exportProvider: ExportProvider); + info.Id, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddDocument(hostDocument); this.OnDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -330,7 +329,7 @@ protected override void ApplyAdditionalDocumentTextChanged(DocumentId document, protected override void ApplyAdditionalDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, exportProvider: ExportProvider); hostProject.AddAdditionalDocument(hostDocument); this.OnAdditionalDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -352,7 +351,7 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume protected override void ApplyAnalyzerConfigDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddAnalyzerConfigDocument(hostDocument); this.OnAnalyzerConfigDocumentAdded(hostDocument.ToDocumentInfo()); } diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb index 46e55f86ecc9e..054c3525dc426 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb @@ -17,7 +17,9 @@ Public Class DiagnosticAnalyzerDriverTests Dim analyzer = New BasicTrackingDiagnosticAnalyzer() Using workspace = TestWorkspace.CreateVisualBasic(source) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) + Dim newSolution = workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}). + Projects.Single().AddAdditionalDocument(name:="dummy.txt", text:="", filePath:="dummy.txt").Project.Solution + workspace.TryApplyChanges(newSolution) Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single() AccessSupportedDiagnostics(analyzer) diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs index 85c6527c83592..baff01171c0cc 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs @@ -5285,7 +5285,7 @@ static void M() EnsureEnglishUICulture.PreferredOrNull, testData); Assert.Equal(new AssemblyIdentity("System.Core"), missingAssemblyIdentities.Single()); - Assert.Equal("error CS1935: Could not find an implementation of the query pattern for source type 'string'. 'Select' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'?", error); + Assert.Equal("error CS1935: Could not find an implementation of the query pattern for source type 'string'. 'Select' not found. Are you missing required assembly references or a using directive for 'System.Linq'?", error); }); } diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs index e9950e6349bd3..3a7cf3c988e57 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MissingAssemblyTests.cs @@ -130,7 +130,7 @@ public void M(int[] array) { var context = CreateMethodContext(runtime, "C.M"); - var expectedError = "error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Select' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'?"; + var expectedError = "error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Select' not found. Are you missing required assembly references or a using directive for 'System.Linq'?"; var expectedMissingAssemblyIdentity = EvaluationContextBase.SystemCoreIdentity; ResultProperties resultProperties; diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhenKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhenKeywordRecommender.cs index f7c27f9902352..19dec455dc3d8 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhenKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/WhenKeywordRecommender.cs @@ -22,20 +22,14 @@ public WhenKeywordRecommender() protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { return context.IsCatchFilterContext || - (IsAfterCompleteExpressionOrPatternInCaseLabel(context, out var expressionOrPattern) && - !IsTypeName(expressionOrPattern, context.SemanticModel, cancellationToken)); + IsAfterCompleteExpressionOrPatternInCaseLabel(context); } - private static bool IsAfterCompleteExpressionOrPatternInCaseLabel(CSharpSyntaxContext context, - out SyntaxNodeOrToken nodeOrToken) + private static bool IsAfterCompleteExpressionOrPatternInCaseLabel(CSharpSyntaxContext context) { - nodeOrToken = null; - var switchLabel = context.TargetToken.GetAncestor(); if (switchLabel == null) - { return false; - } var expressionOrPattern = switchLabel.ChildNodes().FirstOrDefault(); if (expressionOrPattern == null) @@ -44,6 +38,13 @@ private static bool IsAfterCompleteExpressionOrPatternInCaseLabel(CSharpSyntaxCo return false; } + // Never show `when` after `var` in a pattern. It's virtually always going to be unhelpful as the user is + // far more likely to be writing `case var someName...` rather than typing `cae var when...` (in the case + // that `var` is a constant). In other words, it's fine to make that rare case have to manually type out + // `when` rather than risk having `when` pop up when it's not desired. + if (context.TargetToken.Text == "var") + return false; + // If the last token is missing, the expression is incomplete - possibly because of missing parentheses, // but not necessarily. We don't want to offer 'when' in those cases. Here are some examples that illustrate this: // case | @@ -55,122 +56,27 @@ private static bool IsAfterCompleteExpressionOrPatternInCaseLabel(CSharpSyntaxCo // case (1 + ) | if (expressionOrPattern.GetLastToken(includeZeroWidth: true).IsMissing) - { return false; - } - - // There are zero width tokens that are not "missing" (inserted by the parser) because they are optional, - // such as the identifier in a recursive pattern. We want to ignore those now, so we exclude all zero width. var lastToken = expressionOrPattern.GetLastToken(includeZeroWidth: false); + + // We're writing past the end of a complete pattern. This is a place where 'when' could be added to add + // restrictions on the pattern. if (lastToken == context.TargetToken) - { - nodeOrToken = expressionOrPattern; return true; - } + // We're writing the last token of a pattern. In this case, we might either be writing a name for the pattern + // (like `case Wolf w:`) or we might be starting to write `when` (like `case Wolf when ...:`). if (lastToken == context.LeftToken) { - // The user is typing a new word (might be a partially written 'when' keyword), - // which is part of the pattern as opposed to appearing outside of it. In a few special cases, - // this word can actually be replaced with 'when' and the resulting pattern would still be valid. - - if (expressionOrPattern is DeclarationPatternSyntax declarationPattern) - { - // The new token causes this to be parsed as a declaration pattern: - // case constant w| ('w' = LeftToken, 'constant' = TargetToken) - - // However 'constant' itself might end up being a valid constant pattern. - // We will pretend as if 'w' didn't exist so that the later check - // for whether 'constant' is actually a type can still work properly. - nodeOrToken = declarationPattern.Type; + if (expressionOrPattern is DeclarationPatternSyntax) return true; - } - - if (expressionOrPattern is VarPatternSyntax varPattern) - { - // The new token causes this to be parsed as a var pattern: - // case var w| ('w' = LeftToken, 'var' = TargetToken) - // However 'var' itself might end up being a valid constant pattern. - nodeOrToken = varPattern.VarKeyword; + if (expressionOrPattern is RecursivePatternSyntax) return true; - } - - if (expressionOrPattern is RecursivePatternSyntax recursivePattern) - { - // The new token is consumed as the identifier in a recursive pattern: - // case { } w| ('w' = LeftToken, '}' = TargetToken) - - // However the identifier is optional and can be replaced by 'when'. - nodeOrToken = recursivePattern.Type; - return true; - } - - // In other cases, this would not be true because the pattern would be incomplete without this word: - // case 1 + w| } return false; } - - private static bool IsTypeName( - SyntaxNodeOrToken nodeOrToken, - SemanticModel semanticModel, - CancellationToken cancellationToken) - { - // Syntactically, everything works out. We're in a pretty good spot to show 'when' now. - // But let's not do it just yet... Consider these cases: - // case SyntaxNode | - // case SyntaxNode w| - // If what we have here is known to be a type, we don't want to clutter the variable name suggestion list - // with 'when' since we know that the resulting code would be semantically invalid. - - bool isVar; - ImmutableArray symbols; - - if (nodeOrToken.IsNode) - { - var node = nodeOrToken.AsNode(); - var expression = node as ExpressionSyntax - ?? (node as ConstantPatternSyntax)?.Expression; - - if (!(expression is TypeSyntax typeSyntax)) - { - return false; - } - - // We don't pass in the semantic model - let IsPotentialTypeName handle the cases where it's clear - // from the syntax, but other than that, we need to do our own logic here. - if (typeSyntax.IsPotentialTypeName(semanticModelOpt: null, cancellationToken)) - { - return true; - } - - isVar = typeSyntax.IsVar; - symbols = semanticModel.LookupName(typeSyntax, cancellationToken); - } - else - { - // In a var pattern, the 'var' keyword is not wrapped in a type syntax, so we might just have a naked token. - var token = nodeOrToken.AsToken(); - - isVar = token.Text == SyntaxFacts.GetText(SyntaxKind.VarKeyword); - symbols = semanticModel.LookupSymbols(nodeOrToken.AsToken().SpanStart, null, token.Text); - } - - if (symbols.Length == 0) - { - // For all unknown identifiers except var, we return false (therefore 'when' will be offered), - // because the user could later create a type with this name OR a constant. We give them both options. - // But with var, when there is no type or constant with this name, we instead make the reasonable - // assumption that the user didn't just type 'var' to then create a constant named 'var', but really - // is about to declare a variable. Therefore we don't want to interfere with the declaration. - // However note that if such a constant already exists, we do the right thing and do offer 'when'. - return isVar; - } - - return symbols.All(symbol => symbol is IAliasSymbol || symbol is ITypeSymbol); - } } } diff --git a/src/Features/Core/Portable/Completion/Log/CompletionProvidersLogger.cs b/src/Features/Core/Portable/Completion/Log/CompletionProvidersLogger.cs index 6dc877aac3219..95487399de634 100644 --- a/src/Features/Core/Portable/Completion/Log/CompletionProvidersLogger.cs +++ b/src/Features/Core/Portable/Completion/Log/CompletionProvidersLogger.cs @@ -29,15 +29,12 @@ internal enum ActionInfo TargetTypeCompletionTicks, - ExtensionMethodCompletionSuccessCount, - // following are only reported when successful (i.e. filter is available) ExtensionMethodCompletionTicks, ExtensionMethodCompletionMethodsProvided, - ExtensionMethodCompletionGetFilterTicks, - ExtensionMethodCompletionGetSymbolTicks, - ExtensionMethodCompletionTypesChecked, - ExtensionMethodCompletionMethodsChecked, + ExtensionMethodCompletionGetSymbolsTicks, + ExtensionMethodCompletionCreateItemsTicks, CommitsOfExtensionMethodImportCompletionItem, + ExtensionMethodCompletionPartialResultCount, } internal static void LogTypeImportCompletionTicksDataPoint(int count) @@ -61,9 +58,6 @@ internal static void LogCommitOfTypeImportCompletionItem() => internal static void LogTargetTypeCompletionTicksDataPoint(int count) => s_statisticLogAggregator.AddDataPoint((int)ActionInfo.TargetTypeCompletionTicks, count); - internal static void LogExtensionMethodCompletionSuccess() => - s_logAggregator.IncreaseCount((int)ActionInfo.ExtensionMethodCompletionSuccessCount); - internal static void LogExtensionMethodCompletionTicksDataPoint(int count) { s_histogramLogAggregator.IncreaseCount((int)ActionInfo.ExtensionMethodCompletionTicks, count); @@ -73,21 +67,18 @@ internal static void LogExtensionMethodCompletionTicksDataPoint(int count) internal static void LogExtensionMethodCompletionMethodsProvidedDataPoint(int count) => s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionMethodsProvided, count); - internal static void LogExtensionMethodCompletionGetFilterTicksDataPoint(int count) => - s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionGetFilterTicks, count); - - internal static void LogExtensionMethodCompletionGetSymbolTicksDataPoint(int count) => - s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionGetSymbolTicks, count); + internal static void LogExtensionMethodCompletionGetSymbolsTicksDataPoint(int count) => + s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionGetSymbolsTicks, count); - internal static void LogExtensionMethodCompletionTypesCheckedDataPoint(int count) => - s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionTypesChecked, count); - - internal static void LogExtensionMethodCompletionMethodsCheckedDataPoint(int count) => - s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionMethodsChecked, count); + internal static void LogExtensionMethodCompletionCreateItemsTicksDataPoint(int count) => + s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionCreateItemsTicks, count); internal static void LogCommitOfExtensionMethodImportCompletionItem() => s_logAggregator.IncreaseCount((int)ActionInfo.CommitsOfExtensionMethodImportCompletionItem); + internal static void LogExtensionMethodCompletionPartialResultCount() => + s_logAggregator.IncreaseCount((int)ActionInfo.ExtensionMethodCompletionPartialResultCount); + internal static void ReportTelemetry() { Logger.Log(FunctionId.Intellisense_CompletionProviders_Data, KeyValueLogMessage.Create(m => diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs index 14d2ce13d81d7..257ab4a2999fc 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs @@ -39,7 +39,7 @@ protected override async Task AddCompletionItemsAsync( var syntaxFacts = completionContext.Document.GetRequiredLanguageService(); if (TryGetReceiverTypeSymbol(syntaxContext, syntaxFacts, cancellationToken, out var receiverTypeSymbol)) { - var items = await ExtensionMethodImportCompletionHelper.GetUnimportedExtensionMethodsAsync( + var (items, counter) = await ExtensionMethodImportCompletionHelper.GetUnimportedExtensionMethodsAsync( completionContext.Document, completionContext.Position, receiverTypeSymbol, @@ -113,6 +113,6 @@ private CompletionItem Convert(SerializableImportCompletionItem serializableItem serializableItem.Glyph, GenericSuffix, CompletionItemFlags.Expanded, - (serializableItem.SymbolKeyData, receiverTypeSymbolKey)); + (serializableItem.SymbolKeyData, receiverTypeSymbolKey, serializableItem.AdditionalOverloadCount)); } } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.CacheEntry.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.CacheEntry.cs index 57daa7df3a212..0c2f025f118e7 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.CacheEntry.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.CacheEntry.cs @@ -30,6 +30,8 @@ private readonly struct CacheEntry ///
public readonly MultiDictionary ReceiverTypeNameToExtensionMethodMap { get; } + public bool ContainsExtensionMethod => !ReceiverTypeNameToExtensionMethodMap.IsEmpty; + private CacheEntry( Checksum checksum, string language, diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 16e203431c3c0..74dec8b0b7eb5 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -5,14 +5,15 @@ #nullable enable using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion.Log; +using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; @@ -30,7 +31,7 @@ internal static partial class ExtensionMethodImportCompletionHelper private static readonly object s_gate = new object(); private static Task s_indexingTask = Task.CompletedTask; - public static async Task> GetUnimportedExtensionMethodsAsync( + public static async Task<(ImmutableArray, StatisticCounter)> GetUnimportedExtensionMethodsAsync( Document document, int position, ITypeSymbol receiverTypeSymbol, @@ -67,194 +68,197 @@ public static async Task> GetUn counter.TotalExtensionMethodsProvided = items.Length; counter.Report(); - return items; + return (items, counter); } - /// - /// Get the metadata name of all the base types and interfaces this type derived from. - /// - private static ImmutableArray GetReceiverTypeNames(ITypeSymbol receiverTypeSymbol) + public static async Task<(ImmutableArray, StatisticCounter)> GetUnimportedExtensionMethodsInCurrentProcessAsync( + Document document, + int position, + ITypeSymbol receiverTypeSymbol, + ISet namespaceInScope, + bool forceIndexCreation, + CancellationToken cancellationToken) { - using var _ = PooledHashSet.GetInstance(out var allTypeNamesBuilder); - AddNamesForTypeWorker(receiverTypeSymbol, allTypeNamesBuilder); - return allTypeNamesBuilder.ToImmutableArray(); + var counter = new StatisticCounter(); + var ticks = Environment.TickCount; - static void AddNamesForTypeWorker(ITypeSymbol receiverTypeSymbol, PooledHashSet builder) + // First find symbols of all applicable extension methods. + // Workspace's syntax/symbol index is used to avoid iterating every method symbols in the solution. + var results = await GetExtensionMethodSymbolsAsync( + document, position, receiverTypeSymbol, namespaceInScope, forceIndexCreation, cancellationToken).ConfigureAwait(false); + + counter.GetSymbolsTicks = Environment.TickCount - ticks; + ticks = Environment.TickCount; + + var indicesComplete = true; + using var _1 = PooledDictionary.GetInstance(out var namespaceNameCache); + using var _2 = PooledDictionary<(string containingNamespace, string methodName, bool isGeneric), (IMethodSymbol bestSymbol, int overloadCount)>.GetInstance(out var overloadMap); + + // Aggregate overloads + foreach (var result in results) { - if (receiverTypeSymbol is ITypeParameterSymbol typeParameter) + // `null` indicates we don't have the index ready for the corresponding project/PE, + // we will queue a background task to force creating them. Meanwhile, returns + // what we do have even it means we only show partial results. + if (result == null) { - foreach (var constraintType in typeParameter.ConstraintTypes) - { - AddNamesForTypeWorker(constraintType, builder); - } + indicesComplete = false; + continue; } - else + + foreach (var symbol in result) { - builder.Add(GetReceiverTypeName(receiverTypeSymbol)); - builder.AddRange(receiverTypeSymbol.GetBaseTypes().Select(t => t.MetadataName)); - builder.AddRange(receiverTypeSymbol.GetAllInterfacesIncludingThis().Select(t => t.MetadataName)); + IMethodSymbol bestSymbol; + int overloadCount; - // interface doesn't inherit from object, but is implicitly convertible to object type. - if (receiverTypeSymbol.IsInterfaceType()) + var containingNamespacename = GetFullyQualifiedNamespaceName(symbol.ContainingNamespace, namespaceNameCache); + var overloadKey = (containingNamespacename, symbol.Name, isGeneric: symbol.Arity > 0); + + // Select the overload with minimum number of parameters to display + if (overloadMap.TryGetValue(overloadKey, out var currentValue)) { - builder.Add(nameof(Object)); + bestSymbol = currentValue.bestSymbol.Parameters.Length > symbol.Parameters.Length ? symbol : currentValue.bestSymbol; + overloadCount = currentValue.overloadCount + 1; } - } - } - } - - private static string GetReceiverTypeName(ITypeSymbol typeSymbol) - { - switch (typeSymbol) - { - case INamedTypeSymbol namedType: - return namedType.MetadataName; - - case IArrayTypeSymbol arrayType: - var elementType = arrayType.ElementType; - while (elementType is IArrayTypeSymbol symbol) + else { - elementType = symbol.ElementType; + bestSymbol = symbol; + overloadCount = 1; } - var elementTypeName = GetReceiverTypeName(elementType); - - // We do not differentiate array of different kinds sicne they are all represented in the indices as "NonArrayElementTypeName[]" - // e.g. int[], int[][], int[,], etc. are all represented as "int[]", whereas array of complex type such as T[] is "[]". - return elementTypeName + FindSymbols.Extensions.ArrayReceiverTypeNameSuffix; - - default: - // Complex types are represented by ""; - return FindSymbols.Extensions.ComplexReceiverTypeName; + overloadMap[overloadKey] = (bestSymbol, overloadCount); + } } - } - public static async Task<(ImmutableArray, StatisticCounter)> GetUnimportedExtensionMethodsInCurrentProcessAsync( - Document document, - int position, - ITypeSymbol receiverTypeSymbol, - ISet namespaceInScope, - bool forceIndexCreation, - CancellationToken cancellationToken) - { - var counter = new StatisticCounter(); - var ticks = Environment.TickCount; + // Then convert symbols into completion items + using var _3 = ArrayBuilder.GetInstance(out var itemsBuilder); - var indicesResult = await TryGetIndicesAsync( - document.Project, forceIndexCreation, cancellationToken).ConfigureAwait(false); + foreach (var ((containingNamespace, _, _), (bestSymbol, overloadCount)) in overloadMap) + { + // To display the count of of additional overloads, we need to substract total by 1. + var item = CreateItem(bestSymbol, containingNamespace, additionalOverloadCount: overloadCount - 1, cancellationToken); + itemsBuilder.Add(item); + } - // Don't show unimported extension methods if the index isn't ready. - if (!indicesResult.HasResult) + // If we don't have all the indices available already, queue a backgrounds task to create them. + if (!indicesComplete) { - // We use a very simple approach to build the cache in the background: - // queue a new task only if the previous task is completed, regardless of what - // that task is. lock (s_gate) { + // We use a very simple approach to build the cache in the background: + // queue a new task only if the previous task is completed. This is to avoid + // queueing calculation for the same set of references repeatedly while + // index is being constrcuted, which might take some time. if (s_indexingTask.IsCompleted) { - s_indexingTask = Task.Run(() => TryGetIndicesAsync(document.Project, forceIndexCreation: true, CancellationToken.None)); + s_indexingTask = PopulateIndicesAsync(document.Project, CancellationToken.None); } } - return (ImmutableArray.Empty, counter); + counter.PartialResult = true; } - counter.GetFilterTicks = Environment.TickCount - ticks; - counter.NoFilter = !indicesResult.HasResult; + counter.CreateItemsTicks = Environment.TickCount - ticks; - ticks = Environment.TickCount; + return (itemsBuilder.ToImmutable(), counter); + + static SerializableImportCompletionItem CreateItem(IMethodSymbol methodSymbol, string containingNamespace, int additionalOverloadCount, CancellationToken cancellationToken) + => new SerializableImportCompletionItem( + SymbolKey.CreateString(methodSymbol, cancellationToken), + methodSymbol.Name, + methodSymbol.Arity, + methodSymbol.GetGlyph(), + containingNamespace, + additionalOverloadCount); - var items = await GetExtensionMethodItemsAsync( - document, receiverTypeSymbol, GetReceiverTypeNames(receiverTypeSymbol), indicesResult, position, - namespaceInScope, counter, cancellationToken).ConfigureAwait(false); + // Force create all relevant indices + static async Task PopulateIndicesAsync(Project currentProject, CancellationToken cancellationToken) + { + var solution = currentProject.Solution; + var cacheService = GetCacheService(solution.Workspace); - counter.GetSymbolTicks = Environment.TickCount - ticks; + using var _ = ArrayBuilder.GetInstance(out var tasks); - return (items, counter); + foreach (var project in GetAllRelevantProjects(currentProject)) + { + tasks.Add(Task.Run(() + => GetCacheEntryAsync(project, loadOnly: false, cacheService, cancellationToken), cancellationToken)); + } + + foreach (var peReference in GetAllRelevantPeReferences(currentProject)) + { + tasks.Add(Task.Run(() + => SymbolTreeInfo.GetInfoForMetadataReferenceAsync(solution, peReference, loadOnly: false, cancellationToken), cancellationToken)); + } + + await Task.WhenAll(tasks.ToImmutable()).ConfigureAwait(false); + } + } + + private static ImmutableArray GetAllRelevantProjects(Project project) + { + var solution = project.Solution; + var graph = solution.GetProjectDependencyGraph(); + var relevantProjectIds = graph.GetProjectsThatThisProjectTransitivelyDependsOn(project.Id).Concat(project.Id); + return relevantProjectIds.Select(id => solution.GetRequiredProject(id)).Where(p => p.SupportsCompilation).ToImmutableArray(); } - private static async Task> GetExtensionMethodItemsAsync( + private static ImmutableArray GetAllRelevantPeReferences(Project project) + => project.MetadataReferences.OfType().ToImmutableArray(); + + private static async Task?[]> GetExtensionMethodSymbolsAsync( Document document, - ITypeSymbol receiverTypeSymbol, - ImmutableArray receiverTypeNames, - GetIndicesResult indices, int position, - ISet namespaceFilter, - StatisticCounter counter, + ITypeSymbol receiverTypeSymbol, + ISet namespaceInScope, + bool forceIndexCreation, CancellationToken cancellationToken) { - if (!indices.HasResult) - { - return ImmutableArray.Empty; - } - var currentProject = document.Project; + var solution = currentProject.Solution; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var currentCompilation = semanticModel.Compilation; var currentAssembly = currentCompilation.Assembly; - using var _1 = ArrayBuilder.GetInstance(out var completionItemsbuilder); - using var _2 = PooledDictionary.GetInstance(out var namespaceNameCache); - // This dictionary is used as cache among all projects and PE references. // The key is the receiver type as in the extension method declaration (symbol retrived from current compilation). // The value indicates if we can reduce an extension method with this receiver type given receiver type. - using var _3 = PooledDictionary.GetInstance(out var checkedReceiverTypes); - - receiverTypeNames = AttachComplexTypes(receiverTypeNames); - - // Get extension method items from source - foreach (var (project, syntaxIndex) in indices.SyntaxIndices!) - { - var filter = CreateAggregatedFilter(receiverTypeNames, syntaxIndex); - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var assembly = compilation.Assembly; + var checkedReceiverTypes = new ConcurrentDictionary(); + var receiverTypeNames = GetReceiverTypeNames(receiverTypeSymbol); + receiverTypeNames = AddComplexTypes(receiverTypeNames); + var cacheService = GetCacheService(solution.Workspace); - var internalsVisible = currentAssembly.IsSameAssemblyOrHasFriendAccessTo(assembly); + var parameters = new SymbolComputationParameters( + semanticModel, solution, receiverTypeSymbol, receiverTypeNames, position, namespaceInScope, checkedReceiverTypes, cacheService); - var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( - compilation.Assembly, filter, namespaceFilter, internalsVisible, - counter, cancellationToken); + using var _ = ArrayBuilder?>>.GetInstance(out var tasks); - if (project == currentProject) - { - GetExtensionMethodItemsForSymbolsFromSameCompilation( - position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, - completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); - } - else - { - GetExtensionMethodItemsForSymbolsFromDifferentCompilation( - position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, - completionItemsbuilder, namespaceNameCache, checkedReceiverTypes, cancellationToken); - } + foreach (var peReference in GetAllRelevantPeReferences(currentProject)) + { + tasks.Add(Task.Run(() => GetExtensionMethodSymbolsFromPeReferenceAsync( + peReference, + forceIndexCreation, + parameters, + cancellationToken), cancellationToken)); } - // Get extension method items from PE - foreach (var (peReference, symbolInfo) in indices.SymbolInfos!) + foreach (var project in GetAllRelevantProjects(currentProject)) { - var filter = CreateAggregatedFilter(receiverTypeNames, symbolInfo); - if (currentCompilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assembly) - { - var internalsVisible = currentAssembly.IsSameAssemblyOrHasFriendAccessTo(assembly); - - var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( - assembly, filter, namespaceFilter, internalsVisible, - counter, cancellationToken); - - GetExtensionMethodItemsForSymbolsFromSameCompilation( - position, semanticModel, receiverTypeSymbol, matchingMethodSymbols, - completionItemsbuilder, namespaceNameCache, - checkedReceiverTypes, cancellationToken); - } + // By default, don't trigger index creation except for documents in current project. + var isCurrentProject = project == currentProject; + tasks.Add(Task.Run(() => GetExtensionMethodSymbolsFromProjectAsync( + project, + isCurrentProject, + forceIndexCreation: forceIndexCreation || isCurrentProject, + parameters, + cancellationToken), cancellationToken)); } - return completionItemsbuilder.ToImmutable(); + return await Task.WhenAll(tasks).ConfigureAwait(false); // Add strings represent complex types (i.e. "" for non-array types and "[]" for array types) to the receiver type, // so we would include in the filter info about extension methods with complex receiver type. - static ImmutableArray AttachComplexTypes(ImmutableArray receiverTypeNames) + static ImmutableArray AddComplexTypes(ImmutableArray receiverTypeNames) { using var _ = ArrayBuilder.GetInstance(receiverTypeNames.Length + 2, out var receiverTypeNamesBuilder); receiverTypeNamesBuilder.AddRange(receiverTypeNames); @@ -265,22 +269,86 @@ static ImmutableArray AttachComplexTypes(ImmutableArray receiver } } - private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( - int position, - SemanticModel semanticModel, - ITypeSymbol receiverTypeSymbol, + private static async Task?> GetExtensionMethodSymbolsFromProjectAsync( + Project project, + bool isCurrentProject, + bool forceIndexCreation, + SymbolComputationParameters parameters, + CancellationToken cancellationToken) + { + var cacheEntry = await GetCacheEntryAsync( + project, loadOnly: !forceIndexCreation, parameters.CacheService, cancellationToken).ConfigureAwait(false); + + if (!cacheEntry.HasValue) + { + // Returns null to indicate index not ready + return null; + } + + if (!cacheEntry.Value.ContainsExtensionMethod) + { + return ImmutableArray.Empty; + } + + var currentAssembly = parameters.SemanticModel.Compilation.Assembly; + var filter = CreateAggregatedFilter(parameters.ReceiverTypeNames, cacheEntry.Value); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var assembly = compilation.Assembly; + var internalsVisible = currentAssembly.IsSameAssemblyOrHasFriendAccessTo(assembly); + + var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( + compilation.Assembly, filter, parameters.NamespaceInScope, internalsVisible, cancellationToken); + + return isCurrentProject + ? GetExtensionMethodsForSymbolsFromSameCompilation( + matchingMethodSymbols, parameters, cancellationToken) + : GetExtensionMethodsForSymbolsFromDifferentCompilation( + matchingMethodSymbols, parameters, cancellationToken); + } + + private static async Task?> GetExtensionMethodSymbolsFromPeReferenceAsync( + PortableExecutableReference peReference, + bool forceIndexCreation, + SymbolComputationParameters parameters, + CancellationToken cancellationToken) + { + var index = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + parameters.Solution, peReference, loadOnly: !forceIndexCreation, cancellationToken).ConfigureAwait(false); + + if (index == null) + { + // Returns null to indicate index not ready + return null; + } + + if (index.ContainsExtensionMethod && parameters.SemanticModel.Compilation.GetAssemblyOrModuleSymbol(peReference) is IAssemblySymbol assembly) + { + var filter = CreateAggregatedFilter(parameters.ReceiverTypeNames, index); + var internalsVisible = parameters.SemanticModel.Compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(assembly); + + var matchingMethodSymbols = GetPotentialMatchingSymbolsFromAssembly( + assembly, filter, parameters.NamespaceInScope, internalsVisible, cancellationToken); + + return GetExtensionMethodsForSymbolsFromSameCompilation( + matchingMethodSymbols, parameters, cancellationToken); + } + + return ImmutableArray.Empty; + } + + private static ImmutableArray GetExtensionMethodsForSymbolsFromDifferentCompilation( MultiDictionary matchingMethodSymbols, - ArrayBuilder builder, - Dictionary stringCache, - Dictionary checkedReceiverTypes, + SymbolComputationParameters parameters, CancellationToken cancellationToken) { + using var _ = ArrayBuilder.GetInstance(out var builder); + // Matching extension method symbols are grouped based on their receiver type. foreach (var (declaredReceiverType, methodSymbols) in matchingMethodSymbols) { cancellationToken.ThrowIfCancellationRequested(); - var declaredReceiverTypeInCurrentCompilation = SymbolFinder.FindSimilarSymbols(declaredReceiverType, semanticModel.Compilation).FirstOrDefault(); + var declaredReceiverTypeInCurrentCompilation = SymbolFinder.FindSimilarSymbols(declaredReceiverType, parameters.SemanticModel.Compilation).FirstOrDefault(); if (declaredReceiverTypeInCurrentCompilation == null) { // Bug: https://github.com/dotnet/roslyn/issues/45404 @@ -294,7 +362,7 @@ private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( continue; } - if (checkedReceiverTypes.TryGetValue(declaredReceiverTypeInCurrentCompilation, out var cachedResult) && !cachedResult) + if (parameters.CheckedReceiverTypes.TryGetValue(declaredReceiverTypeInCurrentCompilation, out var cachedResult) && !cachedResult) { // If we already checked an extension method with same receiver type before, and we know it can't be applied // to the receiverTypeSymbol, then no need to proceed methods from this group.. @@ -304,7 +372,7 @@ private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( // This is also affected by the symbol resolving issue mentioned above, which means in case referenced projects // are targeting different framework, we will miss extension methods with any framework type in their signature from those projects. var isFirstMethod = true; - foreach (var methodInCurrentCompilation in methodSymbols.Select(s => SymbolFinder.FindSimilarSymbols(s, semanticModel.Compilation).FirstOrDefault()).WhereNotNull()) + foreach (var methodInCurrentCompilation in methodSymbols.Select(s => SymbolFinder.FindSimilarSymbols(s, parameters.SemanticModel.Compilation).FirstOrDefault()).WhereNotNull()) { if (isFirstMethod) { @@ -318,9 +386,9 @@ private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( // try to check if we can apply it to given receiver type, and save result to our cache. // Since method symbols are grouped by their declared receiver type, they are either all matches to the receiver type // or all mismatches. So we only need to call ReduceExtensionMethod on one of them. - var reducedMethodSymbol = methodInCurrentCompilation.ReduceExtensionMethod(receiverTypeSymbol); + var reducedMethodSymbol = methodInCurrentCompilation.ReduceExtensionMethod(parameters.ReceiverTypeSymbol); cachedResult = reducedMethodSymbol != null; - checkedReceiverTypes[declaredReceiverTypeInCurrentCompilation] = cachedResult; + parameters.CheckedReceiverTypes[declaredReceiverTypeInCurrentCompilation] = cachedResult; // Now, cachedResult being false means method doesn't match the receiver type, // stop processing methods from this group. @@ -331,24 +399,23 @@ private static void GetExtensionMethodItemsForSymbolsFromDifferentCompilation( } } - if (semanticModel.IsAccessible(position, methodInCurrentCompilation)) + if (parameters.SemanticModel.IsAccessible(parameters.Position, methodInCurrentCompilation)) { - CreateAndAddItem(methodInCurrentCompilation, builder, stringCache); + builder.Add(methodInCurrentCompilation); } } } + + return builder.ToImmutable(); } - private static void GetExtensionMethodItemsForSymbolsFromSameCompilation( - int position, - SemanticModel semanticModel, - ITypeSymbol receiverTypeSymbol, + private static ImmutableArray GetExtensionMethodsForSymbolsFromSameCompilation( MultiDictionary matchingMethodSymbols, - ArrayBuilder builder, - Dictionary stringCache, - Dictionary checkedReceiverTypes, + SymbolComputationParameters data, CancellationToken cancellationToken) { + using var _ = ArrayBuilder.GetInstance(out var builder); + // Matching extension method symbols are grouped based on their receiver type. foreach (var (receiverType, methodSymbols) in matchingMethodSymbols) { @@ -356,7 +423,7 @@ private static void GetExtensionMethodItemsForSymbolsFromSameCompilation( // If we already checked an extension method with same receiver type before, and we know it can't be applied // to the receiverTypeSymbol, then no need to proceed further. - if (checkedReceiverTypes.TryGetValue(receiverType, out var cachedResult) && !cachedResult) + if (data.CheckedReceiverTypes.TryGetValue(receiverType, out var cachedResult) && !cachedResult) { continue; } @@ -365,9 +432,9 @@ private static void GetExtensionMethodItemsForSymbolsFromSameCompilation( // to the given receiver type and save the result. if (!cachedResult) { - var reducedMethodSymbol = methodSymbols.First().ReduceExtensionMethod(receiverTypeSymbol); + var reducedMethodSymbol = methodSymbols.First().ReduceExtensionMethod(data.ReceiverTypeSymbol); cachedResult = reducedMethodSymbol != null; - checkedReceiverTypes[receiverType] = cachedResult; + data.CheckedReceiverTypes[receiverType] = cachedResult; } // Receiver type matches the receiver type of the extension method declaration. @@ -376,38 +443,15 @@ private static void GetExtensionMethodItemsForSymbolsFromSameCompilation( { foreach (var methodSymbol in methodSymbols) { - if (semanticModel.IsAccessible(position, methodSymbol)) + if (data.SemanticModel.IsAccessible(data.Position, methodSymbol)) { - CreateAndAddItem(methodSymbol, builder, stringCache); + builder.Add(methodSymbol); } } } } - } - private static void CreateAndAddItem(IMethodSymbol methodSymbol, ArrayBuilder builder, Dictionary stringCache) - => builder.Add(new SerializableImportCompletionItem( - SymbolKey.CreateString(methodSymbol), - methodSymbol.Name, - methodSymbol.Arity, - methodSymbol.GetGlyph(), - GetFullyQualifiedNamespaceName(methodSymbol.ContainingNamespace, stringCache))); - - private static string GetFullyQualifiedNamespaceName(INamespaceSymbol symbol, Dictionary stringCache) - { - if (symbol.ContainingNamespace == null || symbol.ContainingNamespace.IsGlobalNamespace) - { - return symbol.Name; - } - - if (stringCache.TryGetValue(symbol, out var name)) - { - return name; - } - - name = GetFullyQualifiedNamespaceName(symbol.ContainingNamespace, stringCache) + "." + symbol.Name; - stringCache[symbol] = name; - return name; + return builder.ToImmutable(); } private static MultiDictionary GetPotentialMatchingSymbolsFromAssembly( @@ -415,12 +459,11 @@ private static MultiDictionary GetPotentialMatchingS MultiDictionary extensionMethodFilter, ISet namespaceFilter, bool internalsVisible, - StatisticCounter counter, CancellationToken cancellationToken) { var builder = new MultiDictionary(); - foreach (var (fullyQualifiedContainerName, methodNames) in extensionMethodFilter) + foreach (var (fullyQualifiedContainerName, methodInfo) in extensionMethodFilter) { // First try to filter out types from already imported namespaces var indexOfLastDot = fullyQualifiedContainerName.LastIndexOf('.'); @@ -431,8 +474,6 @@ private static MultiDictionary GetPotentialMatchingS continue; } - counter.TotalTypesChecked++; - // Container of extension method (static class in C# and Module in VB) can't be generic or nested. var containerSymbol = assembly.GetTypeByMetadataName(fullyQualifiedContainerName); @@ -443,7 +484,7 @@ private static MultiDictionary GetPotentialMatchingS continue; } - foreach (var (methodName, receiverTypeName) in methodNames) + foreach (var (methodName, receiverTypeName) in methodInfo) { cancellationToken.ThrowIfCancellationRequested(); @@ -451,7 +492,6 @@ private static MultiDictionary GetPotentialMatchingS foreach (var methodSymbol in methodSymbols) { - counter.TotalExtensionMethodsChecked++; if (MatchExtensionMethod(methodSymbol, receiverTypeName, internalsVisible, out var receiverType)) { @@ -492,67 +532,10 @@ static bool IsAccessible(ISymbol symbol, bool internalsVisible) => (symbol.DeclaredAccessibility == Accessibility.Internal && internalsVisible); } - private static async Task TryGetIndicesAsync( - Project currentProject, - bool forceIndexCreation, - CancellationToken cancellationToken) - { - var solution = currentProject.Solution; - var cacheService = GetCacheService(solution.Workspace); - var graph = currentProject.Solution.GetProjectDependencyGraph(); - var relevantProjectIds = graph.GetProjectsThatThisProjectTransitivelyDependsOn(currentProject.Id) - .Concat(currentProject.Id); - - var syntaxIndices = new Dictionary(); - var symbolInfos = new Dictionary(); - - foreach (var projectId in relevantProjectIds) - { - cancellationToken.ThrowIfCancellationRequested(); - - var project = solution.GetProject(projectId); - if (project == null || !project.SupportsCompilation) - { - continue; - } - - // By default, don't trigger index creation except for documents in current project. - var loadOnly = !forceIndexCreation && projectId != currentProject.Id; - var cacheEntry = await GetCacheEntryAsync(project, loadOnly, cacheService, cancellationToken).ConfigureAwait(false); - - if (cacheEntry == null) - { - // Don't provide anything if we don't have all the required SyntaxTreeIndex created. - return GetIndicesResult.NoneResult; - } - - syntaxIndices.Add(project, cacheEntry.Value); - } - - // Search through all direct PE references. - foreach (var peReference in currentProject.MetadataReferences.OfType()) - { - cancellationToken.ThrowIfCancellationRequested(); - - var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - solution, peReference, loadOnly: !forceIndexCreation, cancellationToken).ConfigureAwait(false); - - if (info == null) - { - // Don't provide anything if we don't have all the required SymbolTreeInfo created. - return GetIndicesResult.NoneResult; - } - - if (info.ContainsExtensionMethod) - { - symbolInfos.Add(peReference, info); - } - } - - return new GetIndicesResult(syntaxIndices, symbolInfos); - } - - // Create filter for extension methods from source. + /// + /// Create a filter for extension methods from source. + /// The filter is a map from fully qualified type name to info of extension methods it contains. + /// private static MultiDictionary CreateAggregatedFilter(ImmutableArray receiverTypeNames, CacheEntry syntaxIndex) { var results = new MultiDictionary(); @@ -574,7 +557,10 @@ private static async Task TryGetIndicesAsync( return results; } - // Create filter for extension methods from metadata + /// + /// Create filter for extension methods from metadata + /// The filter is a map from fully qualified type name to info of extension methods it contains. + /// private static MultiDictionary CreateAggregatedFilter(ImmutableArray receiverTypeNames, SymbolTreeInfo symbolInfo) { var results = new MultiDictionary(); @@ -596,47 +582,133 @@ private static async Task TryGetIndicesAsync( return results; } - private readonly struct GetIndicesResult + private static string GetFullyQualifiedNamespaceName(INamespaceSymbol symbol, Dictionary stringCache) { - public bool HasResult { get; } - public Dictionary? SyntaxIndices { get; } - public Dictionary? SymbolInfos { get; } + if (symbol.ContainingNamespace == null || symbol.ContainingNamespace.IsGlobalNamespace) + { + return symbol.Name; + } - public GetIndicesResult(Dictionary syntaxIndices, Dictionary symbolInfos) + if (stringCache.TryGetValue(symbol, out var name)) + { + return name; + } + + name = GetFullyQualifiedNamespaceName(symbol.ContainingNamespace, stringCache) + "." + symbol.Name; + stringCache[symbol] = name; + return name; + } + + /// + /// Get the metadata name of all the base types and interfaces this type derived from. + /// + private static ImmutableArray GetReceiverTypeNames(ITypeSymbol receiverTypeSymbol) + { + using var _ = PooledHashSet.GetInstance(out var allTypeNamesBuilder); + AddNamesForTypeWorker(receiverTypeSymbol, allTypeNamesBuilder); + return allTypeNamesBuilder.ToImmutableArray(); + + static void AddNamesForTypeWorker(ITypeSymbol receiverTypeSymbol, PooledHashSet builder) + { + if (receiverTypeSymbol is ITypeParameterSymbol typeParameter) + { + foreach (var constraintType in typeParameter.ConstraintTypes) + { + AddNamesForTypeWorker(constraintType, builder); + } + } + else + { + builder.Add(GetReceiverTypeName(receiverTypeSymbol)); + builder.AddRange(receiverTypeSymbol.GetBaseTypes().Select(t => t.MetadataName)); + builder.AddRange(receiverTypeSymbol.GetAllInterfacesIncludingThis().Select(t => t.MetadataName)); + + // interface doesn't inherit from object, but is implicitly convertible to object type. + if (receiverTypeSymbol.IsInterfaceType()) + { + builder.Add(nameof(Object)); + } + } + } + } + + private static string GetReceiverTypeName(ITypeSymbol typeSymbol) + { + switch (typeSymbol) { - HasResult = true; - SyntaxIndices = syntaxIndices; - SymbolInfos = symbolInfos; + case INamedTypeSymbol namedType: + return namedType.MetadataName; + + case IArrayTypeSymbol arrayType: + var elementType = arrayType.ElementType; + while (elementType is IArrayTypeSymbol symbol) + { + elementType = symbol.ElementType; + } + + var elementTypeName = GetReceiverTypeName(elementType); + + // We do not differentiate array of different kinds sicne they are all represented in the indices as "NonArrayElementTypeName[]" + // e.g. int[], int[][], int[,], etc. are all represented as "int[]", whereas array of complex type such as T[] is "[]". + return elementTypeName + FindSymbols.Extensions.ArrayReceiverTypeNameSuffix; + + default: + // Complex types are represented by ""; + return FindSymbols.Extensions.ComplexReceiverTypeName; } + } - public static GetIndicesResult NoneResult => default; + private class SymbolComputationParameters + { + public SemanticModel SemanticModel { get; } + public Solution Solution { get; } + public ITypeSymbol ReceiverTypeSymbol { get; } + public ImmutableArray ReceiverTypeNames { get; } + public int Position { get; } + public ISet NamespaceInScope { get; } + public ConcurrentDictionary CheckedReceiverTypes { get; } + public IImportCompletionCacheService CacheService { get; } + + public SymbolComputationParameters( + SemanticModel semanticModel, + Solution solution, + ITypeSymbol receiverTypeSymbol, + ImmutableArray receiverTypeNames, + int position, + ISet namespaceInScope, + ConcurrentDictionary checkedReceiverTypes, + IImportCompletionCacheService cacheService) + { + SemanticModel = semanticModel; + Solution = solution; + ReceiverTypeSymbol = receiverTypeSymbol; + ReceiverTypeNames = receiverTypeNames; + Position = position; + NamespaceInScope = namespaceInScope; + CheckedReceiverTypes = checkedReceiverTypes; + CacheService = cacheService; + } } } internal sealed class StatisticCounter { - public bool NoFilter; - public int TotalTicks; - public int TotalExtensionMethodsProvided; - public int GetFilterTicks; - public int GetSymbolTicks; - public int TotalTypesChecked; - public int TotalExtensionMethodsChecked; + public bool PartialResult { get; set; } + public int TotalTicks { get; set; } + public int TotalExtensionMethodsProvided { get; set; } + public int GetSymbolsTicks { get; set; } + public int CreateItemsTicks { get; set; } public void Report() { - if (NoFilter) - { - CompletionProvidersLogger.LogExtensionMethodCompletionSuccess(); - } - else + CompletionProvidersLogger.LogExtensionMethodCompletionTicksDataPoint(TotalTicks); + CompletionProvidersLogger.LogExtensionMethodCompletionMethodsProvidedDataPoint(TotalExtensionMethodsProvided); + CompletionProvidersLogger.LogExtensionMethodCompletionGetSymbolsTicksDataPoint(GetSymbolsTicks); + CompletionProvidersLogger.LogExtensionMethodCompletionCreateItemsTicksDataPoint(CreateItemsTicks); + + if (PartialResult) { - CompletionProvidersLogger.LogExtensionMethodCompletionTicksDataPoint(TotalTicks); - CompletionProvidersLogger.LogExtensionMethodCompletionMethodsProvidedDataPoint(TotalExtensionMethodsProvided); - CompletionProvidersLogger.LogExtensionMethodCompletionGetFilterTicksDataPoint(GetFilterTicks); - CompletionProvidersLogger.LogExtensionMethodCompletionGetSymbolTicksDataPoint(GetSymbolTicks); - CompletionProvidersLogger.LogExtensionMethodCompletionTypesCheckedDataPoint(TotalTypesChecked); - CompletionProvidersLogger.LogExtensionMethodCompletionMethodsCheckedDataPoint(TotalExtensionMethodsChecked); + CompletionProvidersLogger.LogExtensionMethodCompletionPartialResultCount(); } } } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index fb54a37c9bc65..12482acbb6c97 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -22,11 +22,19 @@ internal static class ImportCompletionItem private const string AttributeFullName = nameof(AttributeFullName); private const string MethodKey = nameof(MethodKey); private const string ReceiverKey = nameof(ReceiverKey); + private const string OverloadCountKey = nameof(OverloadCountKey); public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace, string genericTypeSuffix) => Create(typeSymbol.Name, typeSymbol.Arity, containingNamespace, typeSymbol.GetGlyph(), genericTypeSuffix, CompletionItemFlags.CachedAndExpanded, extensionMethodData: null); - public static CompletionItem Create(string name, int arity, string containingNamespace, Glyph glyph, string genericTypeSuffix, CompletionItemFlags flags, (string methodSymbolKey, string receiverTypeSymbolKey)? extensionMethodData) + public static CompletionItem Create( + string name, + int arity, + string containingNamespace, + Glyph glyph, + string genericTypeSuffix, + CompletionItemFlags flags, + (string methodSymbolKey, string receiverTypeSymbolKey, int overloadCount)? extensionMethodData) { ImmutableDictionary? properties = null; @@ -38,6 +46,11 @@ public static CompletionItem Create(string name, int arity, string containingNam { builder.Add(MethodKey, extensionMethodData.Value.methodSymbolKey); builder.Add(ReceiverKey, extensionMethodData.Value.receiverTypeSymbolKey); + + if (extensionMethodData.Value.overloadCount > 0) + { + builder.Add(OverloadCountKey, extensionMethodData.Value.overloadCount.ToString()); + } } else { @@ -102,21 +115,18 @@ public static string GetContainingNamespace(CompletionItem item) public static async Task GetCompletionDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) { var compilation = (await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false)); - var symbol = GetSymbol(item, compilation); + var (symbol, overloadCount) = GetSymbolAndOverloadCount(item, compilation); if (symbol != null) { var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - // We choose not to display the number of "type overloads" for simplicity. - // Otherwise, we need additional logic to track internal and public visible - // types separately, and cache both completion items. return await CommonCompletionUtilities.CreateDescriptionAsync( document.Project.Solution.Workspace, semanticModel, position: 0, symbol, - overloadCount: 0, + overloadCount, supportedPlatforms: null, cancellationToken).ConfigureAwait(false); } @@ -127,7 +137,7 @@ public static async Task GetCompletionDescriptionAsync(Do private static string GetFullyQualifiedName(string namespaceName, string typeName) => namespaceName.Length == 0 ? typeName : namespaceName + "." + typeName; - private static ISymbol? GetSymbol(CompletionItem item, Compilation compilation) + private static (ISymbol? symbol, int overloadCount) GetSymbolAndOverloadCount(CompletionItem item, Compilation compilation) { // If we have SymbolKey data (i.e. this is an extension method item), use it to recover symbol if (item.Properties.TryGetValue(MethodKey, out var methodSymbolKey)) @@ -136,17 +146,21 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam if (methodSymbol != null) { + var overloadCount = item.Properties.TryGetValue(OverloadCountKey, out var overloadCountString) && int.TryParse(overloadCountString, out var count) ? count : 0; + // Get reduced extension method symbol for the given receiver type. if (item.Properties.TryGetValue(ReceiverKey, out var receiverTypeKey)) { if (SymbolKey.ResolveString(receiverTypeKey, compilation).GetAnySymbol() is ITypeSymbol receiverTypeSymbol) { - return methodSymbol.ReduceExtensionMethod(receiverTypeSymbol) ?? methodSymbol; + return (methodSymbol.ReduceExtensionMethod(receiverTypeSymbol) ?? methodSymbol, overloadCount); } } + + return (methodSymbol, overloadCount); } - return methodSymbol; + return default; } // Otherwise, this is a type item, so we don't have SymbolKey data. But we should still have all @@ -154,12 +168,16 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam var containingNamespace = GetContainingNamespace(item); var typeName = item.Properties.TryGetValue(AttributeFullName, out var attributeFullName) ? attributeFullName : item.DisplayText; var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, typeName); + + // We choose not to display the number of "type overloads" for simplicity. + // Otherwise, we need additional logic to track internal and public visible + // types separately, and cache both completion items. if (item.Properties.TryGetValue(TypeAritySuffixName, out var aritySuffix)) { - return compilation.GetTypeByMetadataName(fullyQualifiedName + aritySuffix); + return (compilation.GetTypeByMetadataName(fullyQualifiedName + aritySuffix), 0); } - return compilation.GetTypeByMetadataName(fullyQualifiedName); + return (compilation.GetTypeByMetadataName(fullyQualifiedName), 0); } } } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/SerializableImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/SerializableImportCompletionItem.cs index 98d789521e9b6..a6586b3ad1fc3 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/SerializableImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/SerializableImportCompletionItem.cs @@ -13,14 +13,16 @@ internal readonly struct SerializableImportCompletionItem public readonly string Name; public readonly Glyph Glyph; public readonly string ContainingNamespace; + public readonly int AdditionalOverloadCount; - public SerializableImportCompletionItem(string symbolKeyData, string name, int arity, Glyph glyph, string containingNamespace) + public SerializableImportCompletionItem(string symbolKeyData, string name, int arity, Glyph glyph, string containingNamespace, int additionalOverloadCount) { SymbolKeyData = symbolKeyData; Arity = arity; Name = name; Glyph = glyph; ContainingNamespace = containingNamespace; + AdditionalOverloadCount = additionalOverloadCount; } } } diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 9c2ff06cccd2d..c43e8510794e4 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -459,60 +459,38 @@ async Task VerifyDiagnosticLocationAsync(string id, Location location) } #endif - public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, Document targetDocument, TextSpan? span = null) + public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, TextDocument targetTextDocument, TextSpan? span = null) { - var project = targetDocument.Project; - - if (project.SupportsCompilation) - { - return ConvertToLocalDiagnosticsWithCompilation(); - } - - return ConvertToLocalDiagnosticsWithoutCompilation(); - - IEnumerable ConvertToLocalDiagnosticsWithoutCompilation() + foreach (var diagnostic in diagnostics) { - Contract.ThrowIfTrue(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (!IsReportedInDocument(diagnostic, targetTextDocument)) { - var location = diagnostic.Location; - if (location.Kind != LocationKind.ExternalFile) - { - continue; - } - - var lineSpan = location.GetLineSpan(); - - var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); - if (documentIds.IsEmpty || documentIds.All(id => id != targetDocument.Id)) - { - continue; - } + continue; + } - yield return DiagnosticData.Create(diagnostic, targetDocument); + if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) + { + continue; } + + yield return DiagnosticData.Create(diagnostic, targetTextDocument); } - IEnumerable ConvertToLocalDiagnosticsWithCompilation() + static bool IsReportedInDocument(Diagnostic diagnostic, TextDocument targetTextDocument) { - Contract.ThrowIfFalse(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (diagnostic.Location.SourceTree != null) { - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null || document != targetDocument) - { - continue; - } - - if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) - { - continue; - } + return targetTextDocument.Project.GetDocument(diagnostic.Location.SourceTree) == targetTextDocument; + } + else if (diagnostic.Location.Kind == LocationKind.ExternalFile) + { + var lineSpan = diagnostic.Location.GetLineSpan(); - yield return DiagnosticData.Create(diagnostic, document); + var documentIds = targetTextDocument.Project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); + return documentIds.Any(id => id == targetTextDocument.Id); } + + return false; } } diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index ffcc5cfebd7f7..91076762f0b49 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -58,7 +58,7 @@ public ImmutableArray GetDiagnostics(Workspace workspace, Projec internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs state) => DiagnosticsUpdated?.Invoke(this, state); - private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly DefaultDiagnosticAnalyzerService _service; private readonly Workspace _workspace; @@ -81,18 +81,24 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs return false; } - public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + + public Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(textDocument, cancellationToken); + + private async Task AnalyzeSyntaxOrNonSourceDocumentAsync(TextDocument textDocument, CancellationToken cancellationToken) { - Debug.Assert(document.Project.Solution.Workspace == _workspace); + Debug.Assert(textDocument.Project.Solution.Workspace == _workspace); // right now, there is no way to observe diagnostics for closed file. - if (!_workspace.IsDocumentOpen(document.Id) || + if (!_workspace.IsDocumentOpen(textDocument.Id) || !_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Syntax)) { return; } - await AnalyzeForKindAsync(document, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); + await AnalyzeForKindAsync(textDocument, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); } public async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) @@ -123,7 +129,7 @@ bool IsSemanticAnalysisOn() } } - private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + private async Task AnalyzeForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var diagnosticData = await GetDiagnosticsAsync(document, kind, cancellationToken).ConfigureAwait(false); @@ -145,7 +151,7 @@ private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, Can /// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity. ///
private async Task> GetDiagnosticsAsync( - Document document, AnalysisKind kind, CancellationToken cancellationToken) + TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (loadDiagnostic != null) @@ -207,9 +213,18 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return RemoveDocumentAsync(document.Id, cancellationToken); } + public Task NonSourceDocumentResetAsync(TextDocument textDocument, CancellationToken cancellationToken) + { + // no closed file diagnostic and file is not opened, remove any existing diagnostics + return RemoveDocumentAsync(textDocument.Id, cancellationToken); + } + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) => DocumentResetAsync(document, cancellationToken); + public Task NonSourceDocumentCloseAsync(TextDocument textDocument, CancellationToken cancellationToken) + => NonSourceDocumentResetAsync(textDocument, cancellationToken); + private void RaiseEmptyDiagnosticUpdated(AnalysisKind kind, DocumentId documentId) { _service.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( @@ -222,6 +237,9 @@ public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, Invocati public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) => Task.CompletedTask; + public Task NonSourceDocumentOpenAsync(TextDocument textDocument, CancellationToken cancellationToken) + => Task.CompletedTask; + public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs index 246697b293e36..c7b55e5a38110 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs @@ -23,6 +23,7 @@ private readonly struct Data public readonly int CompilationEndActionsCount; public readonly int CompilationActionsCount; public readonly int SyntaxTreeActionsCount; + public readonly int AdditionalFileActionsCount; public readonly int SemanticModelActionsCount; public readonly int SymbolActionsCount; public readonly int SymbolStartActionsCount; @@ -51,6 +52,7 @@ public Data(AnalyzerTelemetryInfo analyzerTelemetryInfo, bool isTelemetryCollect SymbolActionsCount = analyzerTelemetryInfo.SymbolActionsCount; SyntaxNodeActionsCount = analyzerTelemetryInfo.SyntaxNodeActionsCount; SyntaxTreeActionsCount = analyzerTelemetryInfo.SyntaxTreeActionsCount; + AdditionalFileActionsCount = analyzerTelemetryInfo.AdditionalFileActionsCount; OperationActionsCount = analyzerTelemetryInfo.OperationActionsCount; OperationBlockActionsCount = analyzerTelemetryInfo.OperationBlockActionsCount; OperationBlockEndActionsCount = analyzerTelemetryInfo.OperationBlockEndActionsCount; @@ -114,6 +116,7 @@ public void ReportAndClear(int correlationId) m["Analyzer.SemanticModel"] = analyzerInfo.SemanticModelActionsCount; m["Analyzer.SyntaxNode"] = analyzerInfo.SyntaxNodeActionsCount; m["Analyzer.SyntaxTree"] = analyzerInfo.SyntaxTreeActionsCount; + m["Analyzer.AdditionalFile"] = analyzerInfo.AdditionalFileActionsCount; m["Analyzer.Operation"] = analyzerInfo.OperationActionsCount; m["Analyzer.OperationBlock"] = analyzerInfo.OperationBlockActionsCount; m["Analyzer.OperationBlockStart"] = analyzerInfo.OperationBlockStartActionsCount; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs index af68a61c56533..7b00eba9f8ff2 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs @@ -148,6 +148,7 @@ private static void WriteTelemetry(ObjectWriter writer, AnalyzerTelemetryInfo te writer.WriteInt32(telemetryInfo.CompilationEndActionsCount); writer.WriteInt32(telemetryInfo.CompilationActionsCount); writer.WriteInt32(telemetryInfo.SyntaxTreeActionsCount); + writer.WriteInt32(telemetryInfo.AdditionalFileActionsCount); writer.WriteInt32(telemetryInfo.SemanticModelActionsCount); writer.WriteInt32(telemetryInfo.SymbolActionsCount); writer.WriteInt32(telemetryInfo.SymbolStartActionsCount); @@ -173,6 +174,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella var compilationEndActionsCount = reader.ReadInt32(); var compilationActionsCount = reader.ReadInt32(); var syntaxTreeActionsCount = reader.ReadInt32(); + var additionalFileActionsCount = reader.ReadInt32(); var semanticModelActionsCount = reader.ReadInt32(); var symbolActionsCount = reader.ReadInt32(); var symbolStartActionsCount = reader.ReadInt32(); @@ -196,6 +198,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella CompilationActionsCount = compilationActionsCount, SyntaxTreeActionsCount = syntaxTreeActionsCount, + AdditionalFileActionsCount = additionalFileActionsCount, SemanticModelActionsCount = semanticModelActionsCount, SymbolActionsCount = symbolActionsCount, SymbolStartActionsCount = symbolStartActionsCount, diff --git a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs index 1e8554a1def8d..dbe961450d9e5 100644 --- a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics { /// - /// Executes analyzers on a document for computing local syntax/semantic diagnostics for a specific . + /// Executes analyzers on a document for computing local syntax/semantic/additional file diagnostics for a specific . /// internal sealed class DocumentAnalysisExecutor { @@ -32,6 +32,7 @@ internal sealed class DocumentAnalysisExecutor private ImmutableDictionary>? _lazySyntaxDiagnostics; private ImmutableDictionary>? _lazySemanticDiagnostics; + private ImmutableDictionary>? _lazyAdditionalDocumentDiagnostics; public DocumentAnalysisExecutor( DocumentAnalysisScope analysisScope, @@ -57,16 +58,19 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti { Contract.ThrowIfFalse(AnalysisScope.Analyzers.Contains(analyzer)); - var document = AnalysisScope.Document; + var textDocument = AnalysisScope.TextDocument; var span = AnalysisScope.Span; var kind = AnalysisScope.Kind; - var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + var document = textDocument as Document; + RoslynDebug.Assert(document != null || kind == AnalysisKind.Syntax, "We only support syntactic analysis for non-source documents"); + + var loadDiagnostic = await textDocument.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (analyzer == FileContentLoadAnalyzer.Instance) { return loadDiagnostic != null ? - SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) : + SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, textDocument)) : SpecializedCollections.EmptyEnumerable(); } @@ -75,12 +79,18 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti return SpecializedCollections.EmptyEnumerable(); } + ImmutableArray diagnostics; if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { - var diagnostics = await AnalyzerHelper.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( - documentAnalyzer, document, kind, _compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return SpecializedCollections.EmptyEnumerable(); + } + + diagnostics = await AnalyzerHelper.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + documentAnalyzer, document, kind, _compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); - return diagnostics.ConvertToLocalDiagnostics(document, span); + return diagnostics.ConvertToLocalDiagnostics(textDocument, span); } // quick optimization to reduce allocations. @@ -89,7 +99,7 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti if (kind == AnalysisKind.Syntax) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, - (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", _compilationWithAnalyzers, document, analyzer, kind); + (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", _compilationWithAnalyzers, textDocument, analyzer, kind); } return SpecializedCollections.EmptyEnumerable(); @@ -99,9 +109,9 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti var isCompilerAnalyzer = analyzer.IsCompilerAnalyzer(); if (kind != AnalysisKind.Syntax && isCompilerAnalyzer) { - var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); + var isEnabled = await textDocument.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); - Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, document, isEnabled); + Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, textDocument, isEnabled); if (!isEnabled) { @@ -109,52 +119,65 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti } } - var skippedAnalyzerInfo = document.Project.GetSkippedAnalyzersInfo(_analyzerInfoCache); - ImmutableArray filteredIds; - switch (kind) { case AnalysisKind.Syntax: - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - if (tree == null) + if (document != null) { - return SpecializedCollections.EmptyEnumerable(); - } - - var diagnostics = await GetSyntaxDiagnosticsAsync(tree, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); - - if (diagnostics.IsDefaultOrEmpty) - { - Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree == null) + { + return SpecializedCollections.EmptyEnumerable(); + } + + diagnostics = await GetSyntaxDiagnosticsAsync(tree, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); + + if (diagnostics.IsDefaultOrEmpty) + { + Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree); + return SpecializedCollections.EmptyEnumerable(); + } } - else if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) + else { - diagnostics = diagnostics.Filter(filteredIds); + // Currently, we only support analysis for additional documents. In future, we may support analyzer config documents. + if (textDocument.Kind != TextDocumentKind.AdditionalDocument) + { + return SpecializedCollections.EmptyEnumerable(); + } + + diagnostics = await GetAdditionalDocumentDiagnosticsAsync((AdditionalDocument)textDocument, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); } - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document, span); + break; case AnalysisKind.Semantic: - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var model = await document!.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model == null) { return SpecializedCollections.EmptyEnumerable(); } diagnostics = await GetSemanticDiagnosticsAsync(model, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); - - if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) - { - diagnostics = diagnostics.Filter(filteredIds); - } - - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document, span); + break; default: throw ExceptionUtilities.UnexpectedValue(kind); } + + if (diagnostics.IsEmpty) + { + return SpecializedCollections.EmptyEnumerable(); + } + + var skippedAnalyzerInfo = textDocument.Project.GetSkippedAnalyzersInfo(_analyzerInfoCache); + if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out var filteredIds)) + { + diagnostics = diagnostics.Filter(filteredIds); + } + + Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); + return diagnostics.ConvertToLocalDiagnostics(textDocument, span); } private async Task> GetSyntaxDiagnosticsAsync(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) @@ -187,6 +210,43 @@ private async Task> GetSyntaxDiagnosticsAsync(SyntaxT ImmutableArray.Empty; } + private async Task> GetAdditionalDocumentDiagnosticsAsync(AdditionalDocument document, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) + { + // PERF: Compute diagnostics for all analyzers with a single invocation into CompilationWithAnalyzers. + // This is critical for better analyzer execution performance. + + RoslynDebug.Assert(_compilationWithAnalyzers != null); + RoslynDebug.Assert(_compilationBasedAnalyzersInAnalysisScope.Contains(analyzer)); + + if (isCompilerAnalyzer) + { + return ImmutableArray.Empty; + } + + if (_lazyAdditionalDocumentDiagnostics == null) + { + var filePath = document.FilePath ?? document.Name; + var additionalFile = _compilationWithAnalyzers.AnalysisOptions.Options?.AdditionalFiles.FirstOrDefault(a => PathUtilities.Comparer.Equals(a.Path, filePath)); + ImmutableDictionary> diagnosticsMap; + if (additionalFile == null) + { + diagnosticsMap = ImmutableDictionary>.Empty; + } + else + { + // TODO: Move this invocation to OOP + var analysisResult = await _compilationWithAnalyzers.GetAnalysisResultAsync(additionalFile, _compilationBasedAnalyzersInAnalysisScope, cancellationToken).ConfigureAwait(false); + diagnosticsMap = analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFile, out var value) ? value : ImmutableDictionary>.Empty; + } + + Interlocked.CompareExchange(ref _lazyAdditionalDocumentDiagnostics, diagnosticsMap, null); + } + + return _lazyAdditionalDocumentDiagnostics.TryGetValue(analyzer, out var diagnostics) ? + diagnostics : + ImmutableArray.Empty; + } + private async Task> GetSemanticDiagnosticsAsync(SemanticModel model, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) { // PERF: @@ -238,7 +298,7 @@ await suppressionsAnalyzer.AnalyzeAsync(model, span, _compilationWithAnalyzers, bool IsCompilationEndAnalyzer(DiagnosticAnalyzer analyzer) { RoslynDebug.AssertNotNull(_compilationWithAnalyzers); - return _analyzerInfoCache.IsCompilationEndAnalyzer(analyzer, AnalysisScope.Document.Project, _compilationWithAnalyzers.Compilation) ?? true; + return _analyzerInfoCache.IsCompilationEndAnalyzer(analyzer, AnalysisScope.TextDocument.Project, _compilationWithAnalyzers.Compilation) ?? true; } async Task GetAdjustedSpanForCompilerAnalyzerAsync() @@ -253,7 +313,8 @@ bool IsCompilationEndAnalyzer(DiagnosticAnalyzer analyzer) return null; } - var service = AnalysisScope.Document.GetRequiredLanguageService(); + var document = (Document)AnalysisScope.TextDocument; + var service = document.GetRequiredLanguageService(); var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var startNode = service.GetContainingMemberDeclaration(root, span.Value.Start); var endNode = service.GetContainingMemberDeclaration(root, span.Value.End); diff --git a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisScope.cs b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisScope.cs index 41d9f2cd7b092..63596d7afa154 100644 --- a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisScope.cs +++ b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisScope.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics internal sealed class DocumentAnalysisScope { public DocumentAnalysisScope( - Document document, + TextDocument document, TextSpan? span, ImmutableArray analyzers, AnalysisKind kind) @@ -25,13 +25,13 @@ public DocumentAnalysisScope( Debug.Assert(kind == AnalysisKind.Syntax || kind == AnalysisKind.Semantic); Debug.Assert(!analyzers.IsDefaultOrEmpty); - Document = document; + TextDocument = document; Span = span; Analyzers = analyzers; Kind = kind; } - public Document Document { get; } + public TextDocument TextDocument { get; } public TextSpan? Span { get; } public ImmutableArray Analyzers { get; } public AnalysisKind Kind { get; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index f026174ba9279..d1758768c015f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -28,7 +28,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// Returns null if the diagnostics need to be computed. ///
private async Task TryGetCachedDocumentAnalysisDataAsync( - Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + TextDocument document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) { try { @@ -62,7 +62,7 @@ private static async Task ComputeDocumentAnalysisDataAsync DocumentAnalysisExecutor executor, StateSet stateSet, CancellationToken cancellationToken) { var kind = executor.AnalysisScope.Kind; - var document = executor.AnalysisScope.Document; + var document = executor.AnalysisScope.TextDocument; // get log title and functionId GetLogFunctionIdAndTitle(kind, out var functionId, out var title); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index cf79282a1934e..7dcd99ec13024 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -111,7 +112,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor /// /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document /// - public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, Document document, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -202,7 +203,7 @@ public async Task SaveAsync(IPersistentStorageService persistentService, Project var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); foreach (var documentId in result.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -229,7 +230,7 @@ public void ResetVersion() _lastResult = _lastResult.Reset(); } - public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, Document document) + public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, TextDocument document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); @@ -312,7 +313,7 @@ private async Task LoadInitialAnalysisDataAsync(IPersi return builder.ToResult(); } - private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, Document document, CancellationToken cancellationToken) + private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, CancellationToken cancellationToken) { // loading data can be cancelled any time. var project = document.Project; @@ -344,7 +345,7 @@ private async Task LoadInitialProjectAnalysisDataAsync return builder.ToResult(); } - private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, ImmutableArray diagnostics) + private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) { Contract.ThrowIfFalse(document == null || document.Project == project); @@ -360,7 +361,7 @@ private async Task SerializeAsync(IPersistentStorageService persistentService, D InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializer.Version, diagnostics)); } - private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) + private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, TextDocument document, Builder builder, CancellationToken cancellationToken) { var success = true; var project = document.Project; @@ -411,7 +412,7 @@ private async Task TryDeserializeProjectDiagnosticsAsync(IPersistentStorag return false; } - private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, CancellationToken cancellationToken) + private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document == null || document.Project == project); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 3b174d098f4d4..a0d338bc57271 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -192,7 +192,7 @@ public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) return stateSets.ToImmutable(); } - public static bool OnDocumentReset(IEnumerable stateSets, Document document) + public static bool OnDocumentReset(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; @@ -204,7 +204,7 @@ public static bool OnDocumentReset(IEnumerable stateSets, Document doc return removed; } - public async Task OnDocumentOpenedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentOpenedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var opened = false; @@ -216,7 +216,7 @@ public async Task OnDocumentOpenedAsync(IEnumerable stateSets, D return opened; } - public async Task OnDocumentClosedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentClosedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index 4c6032c61138e..ef5aaa3e57982 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -134,7 +134,7 @@ public ActiveFileState GetOrCreateActiveFileState(DocumentId documentId) public ProjectState GetOrCreateProjectState(ProjectId projectId) => _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id)); - public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled if (!TryGetProjectState(document.Project.Id, out var projectState) || @@ -155,7 +155,7 @@ public async Task OnDocumentOpenedAsync(IPersistentStorageService persiste return true; } - public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled // remove active file state and put it in project state @@ -170,7 +170,7 @@ public async Task OnDocumentClosedAsync(IPersistentStorageService persiste return true; } - public bool OnDocumentReset(Document document) + public bool OnDocumentReset(TextDocument document) { var changed = false; // can not be cancelled diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 754d177a12f99..f61987ef377b5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -29,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 /// /// This one follows pattern compiler has set for diagnostic analyzer. ///
- internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly int _correlationId; private readonly DiagnosticAnalyzerTelemetry _telemetry; @@ -184,7 +184,7 @@ private void RaiseDiagnosticsRemoved( } private void RaiseDiagnosticsCreated( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) { Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); @@ -235,16 +235,16 @@ public void LogAnalyzerCountSummary() internal IEnumerable GetAnalyzersTestOnly(Project project) => _stateManager.GetOrCreateStateSets(project).Select(s => s.Analyzer); - private static string GetDocumentLogMessage(string title, Document document, DiagnosticAnalyzer analyzer) + private static string GetDocumentLogMessage(string title, TextDocument document, DiagnosticAnalyzer analyzer) => $"{title}: ({document.Id}, {document.Project.Id}), ({analyzer})"; private static string GetProjectLogMessage(Project project, IEnumerable stateSets) => $"project: ({project.Id}), ({string.Join(Environment.NewLine, stateSets.Select(s => s.Analyzer.ToString()))})"; - private static string GetResetLogMessage(Document document) + private static string GetResetLogMessage(TextDocument document) => $"document close/reset: ({document.FilePath ?? document.Name})"; - private static string GetOpenLogMessage(Document document) + private static string GetOpenLogMessage(TextDocument document) => $"document open: ({document.FilePath ?? document.Name})"; private static string GetRemoveLogMessage(DocumentId id) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 36bc350d5d623..bcc7d944d1187 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -33,7 +33,10 @@ public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, Can public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) => AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken); - private async Task AnalyzeDocumentForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + public Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeDocumentForKindAsync(textDocument, AnalysisKind.Syntax, cancellationToken); + + private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { try { @@ -165,7 +168,13 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C } } - public async Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + private async Task TextDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) { @@ -177,7 +186,13 @@ public async Task DocumentOpenAsync(Document document, CancellationToken cancell } } - public async Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + private async Task TextDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentClose, GetResetLogMessage, document, cancellationToken)) { @@ -191,6 +206,12 @@ public async Task DocumentCloseAsync(Document document, CancellationToken cancel } public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + private Task TextDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentReset, GetResetLogMessage, document, cancellationToken)) { @@ -205,7 +226,7 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return Task.CompletedTask; } - private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(Document document, IEnumerable stateSets, bool documentHadDiagnostics) + private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(TextDocument document, IEnumerable stateSets, bool documentHadDiagnostics) { // if there was no diagnostic reported for this document OR Full solution analysis is enabled, nothing to clean up if (!documentHadDiagnostics || @@ -298,7 +319,7 @@ public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancel return Task.CompletedTask; } - private static bool AnalysisEnabled(Document document) + private static bool AnalysisEnabled(TextDocument document) { if (document.Services.GetService()?.DiagnosticsLspClientName != null) { @@ -439,17 +460,17 @@ private void RaiseProjectDiagnosticsIfNeeded( }); } - private void RaiseDocumentDiagnosticsIfNeeded(Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) + private void RaiseDocumentDiagnosticsIfNeeded(TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) => RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray.Empty, items); private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) { RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, AnalyzerService.RaiseDiagnosticsUpdated, forceUpdate: false); } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, DiagnosticAnalysisResult oldResult, DiagnosticAnalysisResult newResult, Action raiseEvents) { @@ -470,7 +491,7 @@ private void RaiseDocumentDiagnosticsIfNeeded( } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems, Action raiseEvents, bool forceUpdate) @@ -490,7 +511,7 @@ private void RaiseProjectDiagnosticsCreated(Project project, StateSet stateSet, foreach (var documentId in newAnalysisResult.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -537,7 +558,7 @@ private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId project RaiseDiagnosticsRemoved(projectId, solution: null, stateSet, raiseEvents); } - private async Task ReportAnalyzerPerformanceAsync(Document document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) + private async Task ReportAnalyzerPerformanceAsync(TextDocument document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) { try { diff --git a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs index 5c4aef52807d2..8d94fcf2853d9 100644 --- a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { - internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer + internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer2 { public readonly ImmutableDictionary> Analyzers; @@ -126,5 +126,41 @@ public async Task RemoveProjectAsync(ProjectId projectId, CancellationToken canc } } } + + public async Task NonSourceDocumentOpenAsync(TextDocument textDocument, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentCloseAsync(TextDocument textDocument, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentResetAsync(TextDocument textDocument, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + public async Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(textDocument.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index 54910f9581b5a..84217fe489888 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -237,13 +237,19 @@ private void ReportPendingWorkItemCount() } private async Task ProcessDocumentAnalyzersAsync( - Document document, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) + TextDocument textDocument, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) { // process all analyzers for each categories in this order - syntax, body, document var reasons = workItem.InvocationReasons; if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await RunAnalyzersAsync(analyzers, textDocument, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); + } + + if (!(textDocument is Document document)) + { + // Semantic analysis is not supported for non-source documents. + return; } if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SemanticChanged)) @@ -255,6 +261,20 @@ private async Task ProcessDocumentAnalyzersAsync( // if we don't need to re-analyze whole body, see whether we need to at least re-analyze one method. await RunBodyAnalyzersAsync(analyzers, workItem, document, cancellationToken).ConfigureAwait(false); } + + return; + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private async Task RunAnalyzersAsync( diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 5844f27fa6fbe..6d2b6b04cce8e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -334,25 +334,25 @@ private async Task ProcessDocumentAsync(ImmutableArray ana { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) { - var document = solution.GetDocument(documentId); + var textDocument = solution.GetTextDocument(documentId); - if (document != null) + if (textDocument != null) { // if we are called because a document is opened, we invalidate the document so that // it can be re-analyzed. otherwise, since newly opened document has same version as before // analyzer will simply return same data back if (workItem.MustRefresh && !workItem.IsRetry) { - var isOpen = document.IsOpen(); + var isOpen = textDocument.IsOpen(); - await ProcessOpenDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); - await ProcessCloseDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); + await ProcessOpenDocumentIfNeededAsync(analyzers, workItem, textDocument, isOpen, cancellationToken).ConfigureAwait(false); + await ProcessCloseDocumentIfNeededAsync(analyzers, workItem, textDocument, isOpen, cancellationToken).ConfigureAwait(false); } // check whether we are having special reanalyze request - await ProcessReanalyzeDocumentAsync(workItem, document, cancellationToken).ConfigureAwait(false); + await ProcessReanalyzeDocumentAsync(workItem, textDocument, cancellationToken).ConfigureAwait(false); - await Processor.ProcessDocumentAnalyzersAsync(document, analyzers, workItem, cancellationToken).ConfigureAwait(false); + await Processor.ProcessDocumentAnalyzersAsync(textDocument, analyzers, workItem, cancellationToken).ConfigureAwait(false); } else { @@ -389,31 +389,57 @@ private async Task ProcessDocumentAsync(ImmutableArray ana } } - private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument textDocument, bool isOpen, CancellationToken cancellationToken) { if (!isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened)) { return; } - SolutionCrawlerLogger.LogProcessOpenDocument(Processor._logAggregator, document.Id.Id); + SolutionCrawlerLogger.LogProcessOpenDocument(Processor._logAggregator, textDocument.Id.Id); - await Processor.RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.DocumentOpenAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, textDocument, workItem, DocumentOpenAsync, cancellationToken).ConfigureAwait(false); + return; + + static async Task DocumentOpenAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument textDocument, bool isOpen, CancellationToken cancellationToken) { if (isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentClosed)) { return; } - SolutionCrawlerLogger.LogProcessCloseDocument(Processor._logAggregator, document.Id.Id); + SolutionCrawlerLogger.LogProcessCloseDocument(Processor._logAggregator, textDocument.Id.Id); + + await Processor.RunAnalyzersAsync(analyzers, textDocument, workItem, DocumentCloseAsync, cancellationToken).ConfigureAwait(false); + return; - await Processor.RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.DocumentCloseAsync(d, c), cancellationToken).ConfigureAwait(false); + static async Task DocumentCloseAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document document, CancellationToken cancellationToken) + private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, TextDocument document, CancellationToken cancellationToken) { try { @@ -421,7 +447,7 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc Debug.Assert(!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze) || workItem.SpecificAnalyzers.Count > 0); #endif - // no-reanalyze request or we already have a request to re-analyze every thing + // No-reanalyze request or we already have a request to re-analyze every thing if (workItem.MustRefresh || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze)) { return; @@ -429,25 +455,53 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc // First reset the document state in analyzers. var reanalyzers = workItem.SpecificAnalyzers.ToImmutableArray(); - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.DocumentResetAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, DocumentResetAsync, cancellationToken).ConfigureAwait(false); - // no request to re-run syntax change analysis. run it here + // No request to re-run syntax change analysis. run it here var reasons = workItem.InvocationReasons; if (!reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); } - // no request to re-run semantic change analysis. run it here - if (!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) + // No request to re-run semantic change analysis. run it here + // Note: Semantic analysis is not supported for non-source documents. + if (document is Document sourceDocument && + !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, sourceDocument, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } + + return; + + static async Task DocumentResetAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync((Document)document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private Task RemoveDocumentAsync(DocumentId documentId, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 2caf7033b1381..9451b0ff04500 100644 --- a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -8,7 +8,7 @@ NU1701;$(NoWarn) - net472;$(AssetTargetFallback) + net472;$(AssetTargetFallback) true diff --git a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs index 6c23b5ed58fbd..348693e14ff3f 100644 --- a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs @@ -2186,5 +2186,54 @@ public override void Initialize(AnalysisContext context) } } } + + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public class AdditionalFileAnalyzer : DiagnosticAnalyzer + { + private readonly bool _registerFromInitialize; + private readonly TextSpan _diagnosticSpan; + + public AdditionalFileAnalyzer(bool registerFromInitialize, TextSpan diagnosticSpan, string id = "ID0001") + { + _registerFromInitialize = registerFromInitialize; + _diagnosticSpan = diagnosticSpan; + + Descriptor = new DiagnosticDescriptor( + id, + "Title1", + "Message1", + "Category1", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } + + public DiagnosticDescriptor Descriptor { get; } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor); + public override void Initialize(AnalysisContext context) + { + if (_registerFromInitialize) + { + context.RegisterAdditionalFileAction(AnalyzeAdditionalFile); + } + else + { + context.RegisterCompilationStartAction(context => + context.RegisterAdditionalFileAction(AnalyzeAdditionalFile)); + } + } + + private void AnalyzeAdditionalFile(AdditionalFileAnalysisContext context) + { + if (context.AdditionalFile.Path == null) + { + return; + } + + var text = context.AdditionalFile.GetText(); + var location = Location.Create(context.AdditionalFile.Path, _diagnosticSpan, text.Lines.GetLinePositionSpan(_diagnosticSpan)); + context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); + } + } } } diff --git a/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs b/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs index 383abf3dca980..351f7d7172fa4 100644 --- a/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs +++ b/src/Test/Utilities/Portable/Diagnostics/TestDiagnosticAnalyzer.cs @@ -3,14 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Test.Utilities @@ -21,8 +16,7 @@ public abstract class TestDiagnosticAnalyzer : DiagnosticAnal protected static readonly ImmutableArray AllSyntaxKinds = GetAllEnumValues(); - protected static readonly ImmutableArray AllAnalyzerMemberNames = new string[] { "AnalyzeCodeBlock", "AnalyzeCompilation", "AnalyzeNode", "AnalyzeSemanticModel", "AnalyzeSymbol", "AnalyzeSyntaxTree", "Initialize", "SupportedDiagnostics" }.ToImmutableArray(); - // protected static readonly ImmutableArray AllAbstractMemberNames = ImmutableArray.Empty.AddRange(GetAbstractMemberNames(typeof(CompilationStartAnalysisScope)).Distinct()); + protected static readonly ImmutableArray AllAnalyzerMemberNames = new string[] { "AnalyzeCodeBlock", "AnalyzeCompilation", "AnalyzeNode", "AnalyzeSemanticModel", "AnalyzeSymbol", "AnalyzeSyntaxTree", "AnalyzeAdditionalFile", "Initialize", "SupportedDiagnostics" }.ToImmutableArray(); protected static readonly DiagnosticDescriptor DefaultDiagnostic = #pragma warning disable RS1029 // Do not use reserved diagnostic IDs. @@ -50,6 +44,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCodeBlockAction(this.AnalyzeCodeBlock); context.RegisterSymbolAction(this.AnalyzeSymbol, AllSymbolKinds.ToArray()); context.RegisterSyntaxTreeAction(this.AnalyzeSyntaxTree); + context.RegisterAdditionalFileAction(this.AnalyzeAdditionalFile); context.RegisterSyntaxNodeAction(this.AnalyzeNode, AllSyntaxKinds.ToArray()); } @@ -92,6 +87,12 @@ private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context) OnOptions(context.Options); } + private void AnalyzeAdditionalFile(AdditionalFileAnalysisContext context) + { + OnAbstractMember("AdditionalFile"); + OnOptions(context.Options); + } + private void AnalyzeNode(SyntaxNodeAnalysisContext context) { OnAbstractMember("SyntaxNode", context.Node); diff --git a/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs b/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs index 0cc04fbe0c190..e5861fbee4e73 100644 --- a/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs +++ b/src/Test/Utilities/Portable/Mocks/TestAdditionalText.cs @@ -4,6 +4,7 @@ #nullable enable +using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -20,6 +21,11 @@ public TestAdditionalText(string path, SourceText text) _text = text; } + public TestAdditionalText(string text = "", Encoding? encoding = null, string path = "dummy") + : this(path, new StringText(text, encoding)) + { + } + public override string Path { get; } public override SourceText GetText(CancellationToken cancellationToken = default) => _text; diff --git a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs index 20ff953ba976e..817b030f828f3 100644 --- a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs @@ -464,6 +464,7 @@ private static void WriteTelemetry(string analyzerName, AnalyzerTelemetryInfo te WriteLine($"Symbol End Actions: {telemetry.SymbolEndActionsCount}", ConsoleColor.White); WriteLine($"Syntax Node Actions: {telemetry.SyntaxNodeActionsCount}", ConsoleColor.White); WriteLine($"Syntax Tree Actions: {telemetry.SyntaxTreeActionsCount}", ConsoleColor.White); + WriteLine($"Additional File Actions: {telemetry.AdditionalFileActionsCount}", ConsoleColor.White); WriteLine($"Suppression Actions: {telemetry.SuppressionActionsCount}", ConsoleColor.White); } diff --git a/src/Tools/AnalyzerRunner/Extensions.cs b/src/Tools/AnalyzerRunner/Extensions.cs index 80cf66d64818a..d4156887be9b3 100644 --- a/src/Tools/AnalyzerRunner/Extensions.cs +++ b/src/Tools/AnalyzerRunner/Extensions.cs @@ -26,6 +26,7 @@ internal static void Add(this AnalyzerTelemetryInfo analyzerTelemetryInfo, Analy analyzerTelemetryInfo.SymbolEndActionsCount += addendum.SymbolEndActionsCount; analyzerTelemetryInfo.SyntaxNodeActionsCount += addendum.SyntaxNodeActionsCount; analyzerTelemetryInfo.SyntaxTreeActionsCount += addendum.SyntaxTreeActionsCount; + analyzerTelemetryInfo.AdditionalFileActionsCount += addendum.AdditionalFileActionsCount; analyzerTelemetryInfo.SuppressionActionsCount += addendum.SuppressionActionsCount; } } diff --git a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml index 4d0a13b43a266..1b965fa05f5d1 100644 --- a/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml +++ b/src/VisualStudio/Core/Def/Implementation/ChangeSignature/ChangeSignatureDialog.xaml @@ -203,6 +203,11 @@ + + +