Skip to content

Commit

Permalink
Resolve interceptor file path relative to containing file
Browse files Browse the repository at this point in the history
  • Loading branch information
RikkiGibson committed Feb 12, 2024
1 parent a3c0cbd commit 7f9cce9
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 82 deletions.
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/CommandLine/CSharpCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,9 @@ protected override void ResolveEmbeddedFilesFromExternalSourceDirectives(
}
}

private protected override GeneratorDriver CreateGeneratorDriver(ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, ImmutableArray<AdditionalText> additionalTexts)
private protected override GeneratorDriver CreateGeneratorDriver(string baseDirectory, ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, ImmutableArray<AdditionalText> additionalTexts)
{
return CSharpGeneratorDriver.Create(generators, additionalTexts, (CSharpParseOptions)parseOptions, analyzerConfigOptionsProvider);
return CSharpGeneratorDriver.Create(generators, additionalTexts, (CSharpParseOptions)parseOptions, analyzerConfigOptionsProvider, driverOptions: new GeneratorDriverOptions() { BaseDirectory = baseDirectory });
}

private protected override void DiagnoseBadAccesses(TextWriter consoleOutput, ErrorLogger? errorLogger, Compilation compilation, ImmutableArray<Diagnostic> diagnostics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1057,10 +1057,10 @@ internal OneOrMany<SyntaxTree> GetSyntaxTreesByMappedPath(string mappedPath)
ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> computeMappedPathToSyntaxTree()
{
var builder = ImmutableSegmentedDictionary.CreateBuilder<string, OneOrMany<SyntaxTree>>();
var resolver = Options.SourceReferenceResolver;
var resolver = Options.SourceReferenceResolver ?? SourceFileResolver.Default;
foreach (var tree in SyntaxTrees)
{
var path = resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
var path = resolver.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
builder[path] = builder.ContainsKey(path) ? builder[path].Add(tree) : OneOrMany.Create(tree);
}
return builder.ToImmutable();
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2202,7 +2202,7 @@ internal enum ErrorCode
ERR_InterceptorLineOutOfRange = 9142,
ERR_InterceptorCharacterOutOfRange = 9143,
ERR_InterceptorSignatureMismatch = 9144,
ERR_InterceptorPathNotInCompilationWithUnmappedCandidate = 9145,
// ERR_InterceptorPathNotInCompilationWithUnmappedCandidate = 9145,
ERR_InterceptorMethodMustBeOrdinary = 9146,
ERR_InterceptorMustReferToStartOfTokenPosition = 9147,
ERR_InterceptorMustHaveMatchingThisParameter = 9148,
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2348,7 +2348,6 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_InterceptorContainingTypeCannotBeGeneric:
case ErrorCode.ERR_InterceptorPathNotInCompilation:
case ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate:
case ErrorCode.ERR_InterceptorPathNotInCompilationWithUnmappedCandidate:
case ErrorCode.ERR_InterceptorPositionBadToken:
case ErrorCode.ERR_InterceptorLineOutOfRange:
case ErrorCode.ERR_InterceptorCharacterOutOfRange:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -1000,29 +1002,31 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
return;
}

var syntaxTrees = DeclaringCompilation.SyntaxTrees;
var referenceResolver = DeclaringCompilation.Options.SourceReferenceResolver ?? SourceFileResolver.Default;

// First, the attributeFilePath might be a mapped path of one of the trees in the compilation.
// In this case, it's not intended to be resolved relative to anything, and we won't know if this is the case until we look it up.
var matchingTrees = DeclaringCompilation.GetSyntaxTreesByMappedPath(attributeFilePath);
if (matchingTrees.Count == 0
&& referenceResolver.NormalizePath(attributeFilePath, baseFilePath: SyntaxTree.FilePath) is { } normalizedFilePath)
{
// Try again but normalize the given file path relative to the containing file of the attribute application
matchingTrees = DeclaringCompilation.GetSyntaxTreesByMappedPath(normalizedFilePath);

// Use the normalized path in the following diagnostic messages so the user has a hint of where we are searching.
// Relative paths are the happy path so we want them to know how the compiler has resolved the path they gave us.
attributeFilePath = normalizedFilePath;
}

if (matchingTrees.Count == 0)
{
var referenceResolver = DeclaringCompilation.Options.SourceReferenceResolver;
// if we expect '/_/Program.cs':

// we might get: 'C:\Project\Program.cs' <-- path not mapped
var unmappedMatch = syntaxTrees.FirstOrDefault(static (tree, filePath) => tree.FilePath == filePath, attributeFilePath);
if (unmappedMatch != null)
{
diagnostics.Add(
ErrorCode.ERR_InterceptorPathNotInCompilationWithUnmappedCandidate,
attributeData.GetAttributeArgumentLocation(filePathParameterIndex),
attributeFilePath,
mapPath(referenceResolver, unmappedMatch));
return;
}

// we might get: '\_\Program.cs' <-- slashes not normalized
// we might get: '\_/Program.cs' <-- slashes don't match
// we might get: 'Program.cs' <-- suffix match
// Force normalization of all '\' to '/', but when we recommend a path in the diagnostic message, ensure it will match what we expect if the user decides to use it.
var syntaxTrees = DeclaringCompilation.SyntaxTrees;
var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, pair)
=> mapPath(pair.referenceResolver, tree)
.Replace('\\', '/')
Expand Down
24 changes: 12 additions & 12 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13773,7 +13773,7 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);

var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(dir.Path, generator);
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, generatorPrefix, $"generatedSource.cs"), generatedSource } }, dir, true);

// Clean up temp files
Expand Down Expand Up @@ -13814,7 +13814,7 @@ class C
generators: new[] { generator });

// Verify source generator was executed, regardless of the value of 'skipAnalyzers'.
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(dir.Path, generator);
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, generatorPrefix, "generatedSource.cs"), generatedSource } }, dir, true);

if (expectedAnalyzerExecution)
Expand Down Expand Up @@ -13853,8 +13853,8 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator, generator2 }, analyzers: null);

var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator2);
var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(dir.Path, generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(dir.Path, generator2);

ValidateEmbeddedSources_Portable(new Dictionary<string, string>
{
Expand Down Expand Up @@ -13939,7 +13939,7 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);

var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator);
ValidateWrittenSources(new()
{
{ Path.Combine(generatedDir.Path, generatorPrefix, expectedDir), new() { { expectedFileName, generatedSource } } }
Expand All @@ -13965,7 +13965,7 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator1 }, analyzers: null);

var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator1);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator1);
ValidateWrittenSources(new() { { Path.Combine(generatedDir.Path, generatorPrefix), new() { { "generatedSource.cs", generatedSource1 } } } });

var generatedSource2 = "public class D { }";
Expand Down Expand Up @@ -14000,13 +14000,13 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator, generator2 }, analyzers: null);

var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator2);
var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator2);

ValidateWrittenSources(new()
{
{ Path.Combine(generatedDir.Path, generator1Prefix), new() { { source1Name, source1 } } },
{ Path.Combine(generatedDir.Path, generator2Prefix), new() { { source2Name, source2 } } }
{ generator1Prefix, new() { { source1Name, source1 } } },
{ generator2Prefix, new() { { source2Name, source2 } } }
});

// Clean up temp files
Expand Down Expand Up @@ -14041,7 +14041,7 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);

var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator);
ValidateWrittenSources(new()
{
{ Path.Combine(generatedDir.Path, generatorPrefix, expectedDir), new() { { generatedFileName, generatedSource } } }
Expand Down Expand Up @@ -14158,7 +14158,7 @@ class C

VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);

var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generatedDir.Path, generator);
ValidateWrittenSources(new() { { Path.Combine(generatedDir.Path, generatorPrefix), new() { { "generatedSource.cs", generatedSource } } } });

// Clean up temp files
Expand Down
Loading

0 comments on commit 7f9cce9

Please sign in to comment.