Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EnC: Implements support for #line mappings #53735

Merged
merged 12 commits into from
Jun 4, 2021
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
20 changes: 18 additions & 2 deletions Roslyn.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28503.202
# Visual Studio Version 17
VisualStudioVersion = 17.0.31319.15
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynDeployment", "src\Deployment\RoslynDeployment.csproj", "{600AF682-E097-407B-AD85-EE3CED37E680}"
EndProject
Expand Down Expand Up @@ -1274,6 +1274,22 @@ Global
{21B49277-E55A-45EF-8818-744BCD6CB732}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21B49277-E55A-45EF-8818-744BCD6CB732}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21B49277-E55A-45EF-8818-744BCD6CB732}.Release|Any CPU.Build.0 = Release|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB987FFC-B758-4F73-96A3-923DE8DCFF1A}.Release|Any CPU.Build.0 = Release|Any CPU
{1B73FB08-9A17-497E-97C5-FA312867D51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B73FB08-9A17-497E-97C5-FA312867D51B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B73FB08-9A17-497E-97C5-FA312867D51B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B73FB08-9A17-497E-97C5-FA312867D51B}.Release|Any CPU.Build.0 = Release|Any CPU
{AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE976DE9-811D-4C86-AEBB-DCDC1226D754}.Release|Any CPU.Build.0 = Release|Any CPU
{3829F774-33F2-41E9-B568-AE555004FC62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3829F774-33F2-41E9-B568-AE555004FC62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3829F774-33F2-41E9-B568-AE555004FC62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3829F774-33F2-41E9-B568-AE555004FC62}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.GlobalKeyword.get -> M
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken globalKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken staticKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameEqualsSyntax alias, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.WithGlobalKeyword(Microsoft.CodeAnalysis.SyntaxToken globalKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax
*REMOVED*Microsoft.CodeAnalysis.CSharp.SyntaxKind.DataKeyword = 8441 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.GetLineMappings(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.LineMapping>
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public override LineVisibility GetLineVisibility(SourceText sourceText, int posi
}
}

// C# does not have unknown visibility state
protected override LineVisibility GetUnknownStateVisibility(int index)
=> throw ExceptionUtilities.Unreachable;

internal override FileLinePositionSpan TranslateSpanAndVisibility(SourceText sourceText, string treeFilePath, TextSpan span, out bool isHiddenPosition)
{
var lines = sourceText.Lines;
Expand Down
67 changes: 26 additions & 41 deletions src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -607,6 +608,17 @@ public override IList<TextChange> GetChanges(SyntaxTree oldTree)

#region LinePositions and Locations

private CSharpLineDirectiveMap GetDirectiveMap()
{
if (_lazyLineDirectiveMap == null)
{
// Create the line directive map on demand.
Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
}

return _lazyLineDirectiveMap;
}

/// <summary>
/// Gets the location in terms of path, line and column for a given span.
/// </summary>
Expand All @@ -617,9 +629,7 @@ public override IList<TextChange> GetChanges(SyntaxTree oldTree)
/// </returns>
/// <remarks>The values are not affected by line mapping directives (<c>#line</c>).</remarks>
public override FileLinePositionSpan GetLineSpan(TextSpan span, CancellationToken cancellationToken = default)
{
return new FileLinePositionSpan(this.FilePath, GetLinePosition(span.Start), GetLinePosition(span.End));
}
=> new(FilePath, GetLinePosition(span.Start, cancellationToken), GetLinePosition(span.End, cancellationToken));
tmat marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets the location in terms of path, line and column after applying source line mapping directives (<c>#line</c>).
Expand All @@ -638,25 +648,18 @@ public override FileLinePositionSpan GetLineSpan(TextSpan span, CancellationToke
/// </para>
/// </returns>
public override FileLinePositionSpan GetMappedLineSpan(TextSpan span, CancellationToken cancellationToken = default)
{
if (_lazyLineDirectiveMap == null)
{
// Create the line directive map on demand.
Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
}

return _lazyLineDirectiveMap.TranslateSpan(this.GetText(cancellationToken), this.FilePath, span);
}
=> GetDirectiveMap().TranslateSpan(GetText(cancellationToken), this.FilePath, span);

/// <inheritdoc/>
public override LineVisibility GetLineVisibility(int position, CancellationToken cancellationToken = default)
{
if (_lazyLineDirectiveMap == null)
{
// Create the line directive map on demand.
Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
}
=> GetDirectiveMap().GetLineVisibility(GetText(cancellationToken), position);

return _lazyLineDirectiveMap.GetLineVisibility(this.GetText(cancellationToken), position);
/// <inheritdoc/>
public override IEnumerable<LineMapping> GetLineMappings(CancellationToken cancellationToken = default)
{
var map = GetDirectiveMap();
Debug.Assert(map.Entries.Length >= 1);
return (map.Entries.Length == 1) ? Array.Empty<LineMapping>() : map.GetLineMappings(GetText(cancellationToken).Lines);
}

/// <summary>
Expand All @@ -667,30 +670,14 @@ public override LineVisibility GetLineVisibility(int position, CancellationToken
/// <param name="isHiddenPosition">When the method returns, contains a boolean value indicating whether this span is considered hidden or not.</param>
/// <returns>A resulting <see cref="FileLinePositionSpan"/>.</returns>
internal override FileLinePositionSpan GetMappedLineSpanAndVisibility(TextSpan span, out bool isHiddenPosition)
{
if (_lazyLineDirectiveMap == null)
{
// Create the line directive map on demand.
Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
}

return _lazyLineDirectiveMap.TranslateSpanAndVisibility(this.GetText(), this.FilePath, span, out isHiddenPosition);
}
=> GetDirectiveMap().TranslateSpanAndVisibility(GetText(), FilePath, span, out isHiddenPosition);

/// <summary>
/// Gets a boolean value indicating whether there are any hidden regions in the tree.
/// </summary>
/// <returns>True if there is at least one hidden region.</returns>
public override bool HasHiddenRegions()
{
if (_lazyLineDirectiveMap == null)
{
// Create the line directive map on demand.
Interlocked.CompareExchange(ref _lazyLineDirectiveMap, new CSharpLineDirectiveMap(this), null);
}

return _lazyLineDirectiveMap.HasAnyHiddenRegions();
}
=> GetDirectiveMap().HasAnyHiddenRegions();

/// <summary>
/// Given the error code and the source location, get the warning state based on <c>#pragma warning</c> directives.
Expand Down Expand Up @@ -756,10 +743,8 @@ bool isGeneratedHeuristic()

private GeneratedKind _lazyIsGeneratedCode = GeneratedKind.Unknown;

private LinePosition GetLinePosition(int position)
{
return this.GetText().Lines.GetLinePosition(position);
}
private LinePosition GetLinePosition(int position, CancellationToken cancellationToken)
=> GetText(cancellationToken).Lines.GetLinePosition(position);

/// <summary>
/// Gets a <see cref="Location"/> for the specified text <paramref name="span"/>.
Expand Down
93 changes: 81 additions & 12 deletions src/Compilers/CSharp/Test/Syntax/Diagnostics/LocationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
Expand Down Expand Up @@ -71,6 +71,12 @@ private TextSpan GetSpanIn(SyntaxTree syntaxTree, string textToFind)
return new TextSpan(index, textToFind.Length);
}

private static IEnumerable<string> InspectLineMapping(SyntaxTree tree)
{
var text = tree.GetText();
return tree.GetLineMappings().Select(mapping => $"[|{text.GetSubText(text.Lines.GetTextSpan(mapping.Span))}|] -> {(mapping.IsHidden ? "<hidden>" : mapping.MappedSpan)}");
}

[ClrOnlyFact]
public void TestGetSourceLocationInFile()
{
Expand Down Expand Up @@ -120,7 +126,7 @@ public void TestLineMapping1()
string sampleProgram = @"using System;
class X {
#line 20 ""banana.cs""
int x;
int x;
int y;
#line 44
int z;
Expand All @@ -134,7 +140,6 @@ class X {
#endif
int a;
}".NormalizeLineEndings();
var resolver = new TestSourceResolver();

SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "goo.cs");

Expand All @@ -147,6 +152,15 @@ class X {
AssertMappedSpanEqual(syntaxTree, "w;", "goo.cs", 8, 4, 8, 6, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, "q;\r\nin", "goo.cs", 10, 4, 11, 2, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, "a;", "goo.cs", 15, 4, 15, 6, hasMappedPath: false);

AssertEx.Equal(new[]
{
"[|using System;\r\nclass X {\r\n|] -> : (0,0)-(1,11)",
"[|int x;\r\nint y;\r\n|] -> banana.cs: (19,0)-(20,8)",
"[|int z;\r\n|] -> banana.cs: (43,0)-(43,8)",
"[|int w;\r\n|] -> : (8,0)-(8,8)",
"[|int q;\r\nint f;\r\n#if false\r\n#line 17 \"d:\\twing.cs\"\r\n#endif\r\nint a;\r\n}|] -> <hidden>"
}, InspectLineMapping(syntaxTree));
}

[Fact]
Expand All @@ -165,8 +179,6 @@ class X {
#line 40
int v;
}";
var resolver = new TestSourceResolver();

SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs");

AssertMappedSpanEqual(syntaxTree, "int x;", "c:\\goo.cs", 19, 0, 19, 6, hasMappedPath: false);
Expand All @@ -183,8 +195,6 @@ public void TestLineMapping_NoSyntaxTreePath()
#line 20
class X {}
";
var resolver = new TestSourceResolver();

AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path: ""), "class X {}", "", 19, 0, 19, 10, hasMappedPath: false);
AssertMappedSpanEqual(SyntaxFactory.ParseSyntaxTree(sampleProgram, path: " "), "class X {}", " ", 19, 0, 19, 10, hasMappedPath: false);
}
Expand All @@ -194,32 +204,91 @@ public void TestInvalidLineMapping()
{
string sampleProgram = @"using System;
class X {
int q;
int q;
#line 0 ""firstdirective""
int r;
int r;
#line 20 ""seconddirective""
int s;
}";
int s;
}".NormalizeLineEndings();

SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "filename.cs");

AssertMappedSpanEqual(syntaxTree, "int q", "filename.cs", 2, 4, 2, 9, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, "int r", "filename.cs", 4, 4, 4, 9, hasMappedPath: false); // invalid #line args
AssertMappedSpanEqual(syntaxTree, "int s", "seconddirective", 19, 4, 19, 9, hasMappedPath: true);

AssertEx.Equal(new[]
{
"[|using System;\r\nclass X {\r\n int q;\r\n|] -> : (0,0)-(2,12)",
"[| int r;\r\n|] -> : (4,0)-(4,12)",
"[| int s;\r\n}|] -> seconddirective: (19,0)-(20,1)"
}, InspectLineMapping(syntaxTree));
}

[Fact]
public void TestLineMappingNoDirectives()
{
string sampleProgram = @"using System;
class X {
int x;
int x;
}";
SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs");

AssertMappedSpanEqual(syntaxTree, "ing Sy", "c:\\goo.cs", 0, 2, 0, 8, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, "class X", "c:\\goo.cs", 1, 0, 1, 7, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, $"System;{Environment.NewLine}class X", "c:\\goo.cs", 0, 6, 1, 7, hasMappedPath: false);
AssertMappedSpanEqual(syntaxTree, "x;", "c:\\goo.cs", 2, 4, 2, 6, hasMappedPath: false);

Assert.Empty(InspectLineMapping(syntaxTree));
}

[Fact]
public void TestLineMappingFirstAndLastLineDirectives()
{
string sampleProgram = @"#line 20
class X {}
#line 30".NormalizeLineEndings();
var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs");

AssertEx.Equal(new[]
{
"[|class X {}\r\n|] -> : (19,0)-(19,12)",
}, InspectLineMapping(syntaxTree));
}

[Fact]
public void TestLineMappingLastLineDirectiveFollowedByEmptyLine()
{
string sampleProgram = @"#line 30
".NormalizeLineEndings();

var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs");

AssertEx.Equal(new[]
{
"[||] -> : (29,0)-(29,0)",
}, InspectLineMapping(syntaxTree));
}

[Fact]
public void TestLineMappingConsecutiveDirectives()
{
string sampleProgram =
@"#line hidden
#line default
class C {}
#line 5
#line 10
class D {}
".NormalizeLineEndings();

var syntaxTree = SyntaxFactory.ParseSyntaxTree(sampleProgram, path: "c:\\goo.cs");

AssertEx.Equal(new[]
{
"[|class C {}\r\n|] -> : (2,0)-(2,12)",
"[|class D {}\r\n|] -> : (9,0)-(10,0)",
}, InspectLineMapping(syntaxTree));
}

[WorkItem(537005, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/537005")]
Expand Down
Loading