Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ protected override Compilation CreateCompilation(
{
var syntaxTreeFactory = services.GetRequiredLanguageService<ISyntaxTreeFactoryService>(LanguageNames.CSharp);

var globalUsingsTree = syntaxTreeFactory.ParseSyntaxTree(
var globalUsingsAndToolsTree = syntaxTreeFactory.ParseSyntaxTree(
filePath: null,
CSharpSemanticSearchUtilities.ParseOptions,
SemanticSearchUtilities.CreateSourceText(CSharpSemanticSearchUtilities.Configuration.GlobalUsings),
SemanticSearchUtilities.CreateSourceText(CSharpSemanticSearchUtilities.Configuration.GlobalUsingsAndTools),
cancellationToken);

queryTree = syntaxTreeFactory.ParseSyntaxTree(
Expand All @@ -43,7 +43,7 @@ protected override Compilation CreateCompilation(

return CSharpCompilation.Create(
assemblyName: SemanticSearchUtilities.QueryProjectName,
[queryTree, globalUsingsTree],
[queryTree, globalUsingsAndToolsTree],
references,
CSharpSemanticSearchUtilities.CompilationOptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,32 @@ static IEnumerable<ISymbol> Find(Compilation compilation)
return compilation.Assembly.GlobalNamespace.GetMembers("C");
}
""",
GlobalUsings = """
GlobalUsingsAndTools = $$"""
global using System;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;

public static class {{SemanticSearchUtilities.ToolsTypeName}}
{
private static Func<ISymbol, IEnumerable<SyntaxNode>> {{SemanticSearchUtilities.FindReferencingSyntaxNodesImplName}};
private static Func<SyntaxTree, Task<SemanticModel>> {{SemanticSearchUtilities.GetSemanticModelImplName}};

/// <summary>
/// Returns all syntax nodes that reference the given <paramref name="symbol" />.
/// </summary>
public static IEnumerable<SyntaxNode> FindReferencingSyntaxNodes(this ISymbol symbol)
=> {{SemanticSearchUtilities.FindReferencingSyntaxNodesImplName}}(symbol);

/// <summary>
/// Returns the semantic model for the given <paramref name="tree" />.
/// </summary>
public static Task<SemanticModel> GetSemanticModelAsync(this SyntaxTree tree)
=> {{SemanticSearchUtilities.GetSemanticModelImplName}}(tree);
}
""",
EditorConfig = """
is_global = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,92 @@ static IEnumerable<ISymbol> Find(IEventSymbol e)
AssertEx.Equal(["event Action C.E"], results.Select(Inspect));
}

[ConditionalFact(typeof(CoreClrOnly))]
public async Task FindReferencingSyntaxNodes()
{
using var workspace = TestWorkspace.Create("""
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document FilePath="File1.cs">
class C
{
void F()
{
}
}

class D
{
void R1() => new C().F();
void R2() => new C().F();
}
</Document>
</Project>
</Workspace>
""", composition: FeaturesTestCompositions.Features);

var solution = workspace.CurrentSolution;

var service = solution.Services.GetRequiredLanguageService<ISemanticSearchService>(LanguageNames.CSharp);

var query = """
static async IAsyncEnumerable<ISymbol> Find(IMethodSymbol e)
{
if (e.Name != "F")
{
yield break;
}

foreach (var node in e.FindReferencingSyntaxNodes())
{
var model = await node.SyntaxTree.GetSemanticModelAsync();
yield return model.GetEnclosingSymbol(node.SpanStart);
}
}
""";

var results = new List<DefinitionItem>();
var observer = new MockSemanticSearchResultsObserver() { OnDefinitionFoundImpl = results.Add };
var traceSource = new TraceSource("test");

var options = workspace.GlobalOptions.GetClassificationOptionsProvider();
var result = await service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, CancellationToken.None);

Assert.Null(result.ErrorMessage);
AssertEx.Equal(
[
"void D.R1()",
"void D.R2()"
], results.Select(Inspect));
}

[ConditionalFact(typeof(CoreClrOnly))]
public async Task NullReturn()
{
using var workspace = TestWorkspace.Create(DefaultWorkspaceXml, composition: FeaturesTestCompositions.Features);

var solution = workspace.CurrentSolution;

var service = solution.Services.GetRequiredLanguageService<ISemanticSearchService>(LanguageNames.CSharp);

var query = """
static IEnumerable<ISymbol> Find(Compilation compilation)
{
return null;
}
""";

var results = new List<DefinitionItem>();
var observer = new MockSemanticSearchResultsObserver() { OnDefinitionFoundImpl = results.Add };
var traceSource = new TraceSource("test");

var options = workspace.GlobalOptions.GetClassificationOptionsProvider();
var result = await service.ExecuteQueryAsync(solution, query, s_referenceAssembliesDir, observer, options, traceSource, CancellationToken.None);

Assert.Null(result.ErrorMessage);
Assert.Empty(results);
}

[ConditionalFact(typeof(CoreClrOnly))]
public async Task ForcedCancellation()
{
Expand Down Expand Up @@ -351,11 +437,19 @@ void F(long x)
var exception = exceptions.Single();
AssertEx.Equal($"CSharpAssembly1: [179..179) 'F(x + 1);': InsufficientExecutionStackException: '{expectedMessage}'", Inspect(exception, query));

AssertEx.Equal(
" ..." + Environment.NewLine +
string.Join(Environment.NewLine, Enumerable.Repeat($" at Program.<<Main>$>g__F|0_1(Int64 x) in {FeaturesResources.Query}:line 7", 31)) + Environment.NewLine +
$" at Program.<<Main>$>g__Find|0_0(Compilation compilation)+MoveNext() in Query:line 4" + Environment.NewLine,
exception.StackTrace.JoinText());
var actualTrace = exception.StackTrace.JoinText().Split([Environment.NewLine], StringSplitOptions.None).AsSpan();

AssertEx.SequenceEqual(
[
" ...",
.. Enumerable.Repeat($" at Program.<<Main>$>g__F|0_1(Int64 x) in {FeaturesResources.Query}:line 7", 10),
], actualTrace[0..11].ToArray());

AssertEx.SequenceEqual(
[
.. Enumerable.Repeat($" at Program.<<Main>$>g__F|0_1(Int64 x) in {FeaturesResources.Query}:line 7", 10),
$" at Program.<<Main>$>g__Find|0_0(Compilation compilation)+MoveNext() in Query:line 4",
], actualTrace[^14..^3].ToArray());
}

[ConditionalFact(typeof(CoreClrOnly))]
Expand Down Expand Up @@ -409,11 +503,13 @@ static ISymbol F(ISymbol s)
var exception = exceptions.Single();
AssertEx.Equal($"CSharpAssembly1: [190..190) 'var x = s.ToString();': NullReferenceException: '{expectedMessage}'", Inspect(exception, query));

AssertEx.Equal(
$" at Program.<<Main>$>g__F|0_1(ISymbol s) in {FeaturesResources.Query}:line 11" + Environment.NewLine +
$" at Program.<>c.<<Main>$>b__0_2(ISymbol x) in {FeaturesResources.Query}:line 5" + Environment.NewLine +
$" at <Select Iterator>()" + Environment.NewLine,
Regex.Replace(exception.StackTrace.JoinText(), @"System\.Linq\.Enumerable\..*\.MoveNext", "<Select Iterator>"));
var actualTrace = exception.StackTrace.JoinText().Split([Environment.NewLine], StringSplitOptions.None).AsSpan()[0..2].ToArray();

AssertEx.SequenceEqual(
[
$" at Program.<<Main>$>g__F|0_1(ISymbol s) in {FeaturesResources.Query}:line 11",
$" at Program.<>c.<<Main>$>b__0_2(ISymbol x) in {FeaturesResources.Query}:line 5",
], actualTrace);
}

/// <summary>
Expand Down
10 changes: 8 additions & 2 deletions src/Features/Core/Portable/FeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3153,8 +3153,14 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="The_query_specifies_multiple_top_level_functions_1" xml:space="preserve">
<value>The query specifies multiple top-level functions '{1}'</value>
</data>
<data name="Type_0_is_not_among_supported_types_1" xml:space="preserve">
<value>Type '{0}' is not among supported types: {1}</value>
<data name="Top_level_function_0_must_have_a_single_parameter" xml:space="preserve">
<value>Top-level function '{0}' must have a single parameter</value>
</data>
<data name="Parameter_type_0_is_not_among_supported_types_1" xml:space="preserve">
<value>Parameter type '{0}' is not among supported types: {1}</value>
</data>
<data name="Return_type_0_is_not_among_supported_types_1" xml:space="preserve">
<value>Return type '{0}' is not among supported types: {1}</value>
</data>
<data name="Unable_to_load_type_0_1" xml:space="preserve">
<value>Unable to load type '{0}': '{1}'</value>
Expand Down
Loading
Loading