Add diagnostics to the IR.

- Added a `Diagnostics` and `HasDiagnostics` properties to `RazorIRNode`. The `HasDiagnostics` property was necessary in order to traverse nodes without forcibly instantiating their diagnostic lists.
- Added `GetAllDiagnostics` extension method for `RazorIRNode` to provide a way to retrieve all diagnostics that exist on and under a `RazorIRNode`.
- Updated `RazorIRNodeWriter` to display any diagnostics that exist on IR nodes.
- Internal `RazorIRNode`s do not have mutable `Diagnostics` because we don't currently add diagnostics to these elements.
- Added `DefaultIRLoweringPhaseTest` to validate that errors flow from syntax tree to IR document. Also added a missing test.
- Updated the `CSharpLoweringPhaseTest`s to properly validate that errors flow from IR document => csharp document. This resulted in movement of code to the ir lowering phase tests.

#1412
This commit is contained in:
N. Taylor Mullen 2017-06-08 12:25:54 -07:00
parent 6860806213
commit e3287ae672
42 changed files with 967 additions and 92 deletions

View File

@ -20,9 +20,6 @@ namespace Microsoft.AspNetCore.Razor.Language
var irDocument = codeDocument.GetIRDocument();
ThrowForMissingDocumentDependency(irDocument);
var syntaxTree = codeDocument.GetSyntaxTree();
ThrowForMissingDocumentDependency(syntaxTree);
var target = irDocument.Target;
if (target == null)
{
@ -59,13 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language
documentWriter.WriteDocument(irDocument);
var diagnostics = new List<RazorDiagnostic>();
diagnostics.AddRange(syntaxTree.Diagnostics);
var importSyntaxTrees = codeDocument.GetImportSyntaxTrees();
for (var i = 0; i < importSyntaxTrees?.Count; i++)
{
diagnostics.AddRange(importSyntaxTrees[i].Diagnostics);
}
diagnostics.AddRange(irDocument.GetAllDiagnostics());
diagnostics.AddRange(renderingContext.Diagnostics);
var csharpDocument = RazorCSharpDocument.Create(

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// This might not have been set if there are no tag helpers.
var tagHelperContext = codeDocument.GetTagHelperContext();
var document = new DocumentIRNode();
var builder = RazorIRBuilder.Create(document);
@ -76,6 +76,26 @@ namespace Microsoft.AspNetCore.Razor.Language
builder.Insert(i++, @using);
}
// The document should contain all errors that currently exist in the system. This involves
// adding the errors from the primary and imported syntax trees.
for (i = 0; i < syntaxTree.Diagnostics.Count; i++)
{
document.Diagnostics.Add(syntaxTree.Diagnostics[i]);
}
if (imports != null)
{
for (i = 0; i < imports.Count; i++)
{
var import = imports[i];
for (var j = 0; j < import.Diagnostics.Count; j++)
{
document.Diagnostics.Add(import.Diagnostics[j]);
}
}
}
codeDocument.SetIRDocument(document);
}

View File

@ -8,10 +8,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal sealed class AddPreallocatedTagHelperHtmlAttributeIRNode : ExtensionIRNode
{
public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance;
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => false;
public string VariableName { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -8,12 +8,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class AddTagHelperHtmlAttributeIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Name { get; set; }
internal HtmlAttributeValueStyle ValueStyle { get; set; }

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class CSharpCodeAttributeValueIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Prefix { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class CSharpCodeIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -22,10 +23,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class CSharpExpressionAttributeValueIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Prefix { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class CSharpExpressionIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -22,10 +23,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class ChecksumIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Bytes { get; set; }
public string FilePath { get; set; }

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class ClassDeclarationIRNode : MemberDeclarationIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,10 +24,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string AccessModifier { get; set; }
public string Name { get; set; }

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class CreateTagHelperIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string TagHelperTypeName { get; set; }
public TagHelperDescriptor Descriptor { get; set; }

View File

@ -9,10 +9,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal sealed class DeclarePreallocatedTagHelperAttributeIRNode : ExtensionIRNode
{
public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance;
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => false;
public string VariableName { get; set; }
public string Name { get; set; }

View File

@ -9,10 +9,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal sealed class DeclarePreallocatedTagHelperHtmlAttributeIRNode : ExtensionIRNode
{
public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance;
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => false;
public string VariableName { get; set; }
public string Name { get; set; }

View File

@ -8,12 +8,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class DeclareTagHelperFieldsIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public ISet<string> UsedTagHelperTypeNames { get; set; } = new HashSet<string>(StringComparer.Ordinal);
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -0,0 +1,128 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class DefaultDiagnosticCollection : RazorDiagnosticCollection
{
private readonly List<RazorDiagnostic> _inner = new List<RazorDiagnostic>();
public override RazorDiagnostic this[int index]
{
get
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _inner[index];
}
set
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
_inner[index] = value;
}
}
public override int Count => _inner.Count;
public override bool IsReadOnly => false;
public override void Add(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
_inner.Add(item);
}
public override void Clear()
{
_inner.Clear();
}
public override bool Contains(RazorDiagnostic item)
{
return _inner.Contains(item);
}
public override void CopyTo(RazorDiagnostic[] array, int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
else if (array.Length - arrayIndex < Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
_inner.CopyTo(array, arrayIndex);
}
public override IEnumerator<RazorDiagnostic> GetEnumerator()
{
return _inner.GetEnumerator();
}
public override int IndexOf(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
return _inner.IndexOf(item);
}
public override void Insert(int index, RazorDiagnostic item)
{
if (index < 0 || index > Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
_inner.Insert(index, item);
}
public override bool Remove(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
return _inner.Remove(item);
}
public override void RemoveAt(int index)
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
_inner.RemoveAt(index);
}
}
}

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class DirectiveIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,10 +24,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Name { get; set; }
public IEnumerable<DirectiveTokenIRNode> Tokens => Children.OfType<DirectiveTokenIRNode>();

View File

@ -5,12 +5,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class DirectiveTokenIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Content { get; set; }
public DirectiveTokenDescriptor Descriptor { get; set; }

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class DocumentIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,6 +24,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public string DocumentKind { get; set; }
@ -31,6 +45,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public CodeTarget Target { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public abstract class ExtensionIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -22,9 +23,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public abstract void WriteNode(CodeTarget target, CSharpRenderingContext context);
protected static void AcceptExtensionNode<TNode>(TNode node, RazorIRNodeVisitor visitor)
protected static void AcceptExtensionNode<TNode>(TNode node, RazorIRNodeVisitor visitor)
where TNode : ExtensionIRNode
{
var typedVisitor = visitor as IExtensionIRNodeVisitor<TNode>;

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class FieldDeclarationIRNode : MemberDeclarationIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,12 +24,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public IList<string> Modifiers { get; set; } = new List<string>();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string AccessModifier { get; set; }
public string Name { get; set; }

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class HtmlAttributeIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Name { get; set; }
public string Prefix { get; set; }

View File

@ -8,12 +8,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class HtmlAttributeValueIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Prefix { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -8,12 +8,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class HtmlContentIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class MethodDeclarationIRNode : MemberDeclarationIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,10 +24,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string AccessModifier { get; set; }
public IList<string> Modifiers { get; set; } = new List<string>();
@ -44,5 +60,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
visitor.VisitMethodDeclaration(this);
}
}
}

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class NamespaceDeclarationIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -22,10 +23,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Content { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class PropertyDeclarationIRNode : MemberDeclarationIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -23,12 +24,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public IList<string> Modifiers { get; set; } = new List<string>();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string AccessModifier { get; set; }
public string Name { get; set; }

View File

@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public abstract class RazorDiagnosticCollection : IList<RazorDiagnostic>
{
public abstract RazorDiagnostic this[int index] { get; set; }
public abstract int Count { get; }
public abstract bool IsReadOnly { get; }
public abstract void Add(RazorDiagnostic item);
public abstract void Clear();
public abstract bool Contains(RazorDiagnostic item);
public abstract void CopyTo(RazorDiagnostic[] array, int arrayIndex);
public abstract IEnumerator<RazorDiagnostic> GetEnumerator();
public abstract int IndexOf(RazorDiagnostic item);
public abstract void Insert(int index, RazorDiagnostic item);
public abstract bool Remove(RazorDiagnostic item);
public abstract void RemoveAt(int index);
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -7,10 +7,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public abstract ItemCollection Annotations { get; }
public abstract RazorDiagnosticCollection Diagnostics { get; }
public abstract RazorIRNodeCollection Children { get; }
public abstract SourceSpan? Source { get; set; }
public abstract bool HasDiagnostics { get; }
public abstract void Accept(RazorIRNodeVisitor visitor);
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public static class RazorIRNodeExtensions
{
private static readonly IReadOnlyList<RazorDiagnostic> EmptyDiagnostics = Array.Empty<RazorDiagnostic>();
public static IReadOnlyList<RazorDiagnostic> GetAllDiagnostics(this RazorIRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
HashSet<RazorDiagnostic> diagnostics = null;
AddAllDiagnostics(node);
return diagnostics?.ToList() ?? EmptyDiagnostics;
void AddAllDiagnostics(RazorIRNode n)
{
if (n.HasDiagnostics)
{
if (diagnostics == null)
{
diagnostics = new HashSet<RazorDiagnostic>();
}
diagnostics.UnionWith(n.Diagnostics);
}
for (var i = 0; i < n.Children.Count; i++)
{
AddAllDiagnostics(n.Children[i]);
}
}
}
}
}

View File

@ -7,8 +7,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class RazorIRToken : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public string Content { get; set; }
@ -21,6 +36,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)

View File

@ -0,0 +1,133 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class ReadOnlyDiagnosticCollection : RazorDiagnosticCollection
{
public static readonly ReadOnlyDiagnosticCollection Instance = new ReadOnlyDiagnosticCollection();
private ReadOnlyDiagnosticCollection()
{
}
public override RazorDiagnostic this[int index]
{
get
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
throw null; // Unreachable
}
set
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
throw null; // Unreachable
}
}
public override int Count => 0;
public override bool IsReadOnly => true;
public override void Add(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
throw new NotSupportedException();
}
public override void Clear()
{
throw new NotSupportedException();
}
public override bool Contains(RazorDiagnostic item)
{
return false;
}
public override void CopyTo(RazorDiagnostic[] array, int arrayIndex)
{
if (array == null)
{
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
else if (array.Length - arrayIndex < Count)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
throw new NotSupportedException();
}
public override IEnumerator<RazorDiagnostic> GetEnumerator()
{
return Enumerable.Empty<RazorDiagnostic>().GetEnumerator();
}
public override int IndexOf(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
return -1;
}
public override void Insert(int index, RazorDiagnostic item)
{
if (index < 0 || index > Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
throw new NotSupportedException();
}
public override bool Remove(RazorDiagnostic item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
return false;
}
public override void RemoveAt(int index)
{
if (index < 0 || index >= Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
throw new NotSupportedException();
}
}
}

View File

@ -8,10 +8,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
internal sealed class SetPreallocatedTagHelperPropertyIRNode : ExtensionIRNode
{
public override RazorDiagnosticCollection Diagnostics => ReadOnlyDiagnosticCollection.Instance;
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => false;
public string VariableName { get; set; }
public string AttributeName { get; set; }

View File

@ -8,12 +8,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class SetTagHelperPropertyIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string TagHelperTypeName { get; set; }
public string PropertyName { get; set; }

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class TagHelperBodyIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public sealed class TagHelperIRNode : RazorIRNode
{
private ItemCollection _annotations;
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations
{
@ -22,10 +23,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string TagName { get; set; }
public TagMode TagMode { get; set; }

View File

@ -7,12 +7,29 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public sealed class UsingStatementIRNode : RazorIRNode
{
private RazorDiagnosticCollection _diagnostics;
public override ItemCollection Annotations => ReadOnlyItemCollection.Empty;
public override RazorDiagnosticCollection Diagnostics
{
get
{
if (_diagnostics == null)
{
_diagnostics = new DefaultDiagnosticCollection();
}
return _diagnostics;
}
}
public override RazorIRNodeCollection Children => ReadOnlyIRNodeCollection.Instance;
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0;
public string Content { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)

View File

@ -30,26 +30,6 @@ namespace Microsoft.AspNetCore.Razor.Language
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
public void Execute_ThrowsForMissingDependency_SyntaxTree()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
var irDocument = new DocumentIRNode();
codeDocument.SetIRDocument(irDocument);
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorCSharpLoweringPhase)}' phase requires a '{nameof(RazorSyntaxTree)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
public void Execute_ThrowsForMissingDependency_CodeTarget()
{
@ -76,13 +56,12 @@ namespace Microsoft.AspNetCore.Razor.Language
}
[Fact]
public void Execute_CollatesSyntaxDiagnosticsFromSourceDocument()
public void Execute_CollatesIRDocumentDiagnosticsFromSourceDocument()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.Create("<p class=@(");
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
var options = RazorCodeGenerationOptions.CreateDefault();
var irDocument = new DocumentIRNode()
{
@ -90,6 +69,10 @@ namespace Microsoft.AspNetCore.Razor.Language
Target = CodeTarget.CreateDefault(codeDocument, options),
Options = options,
};
var expectedDiagnostic = RazorDiagnostic.Create(
new RazorDiagnosticDescriptor("1234", () => "I am an error.", RazorDiagnosticSeverity.Error),
new SourceSpan("SomeFile.cshtml", 11, 0, 11, 1));
irDocument.Diagnostics.Add(expectedDiagnostic);
codeDocument.SetIRDocument(irDocument);
// Act
@ -98,49 +81,7 @@ namespace Microsoft.AspNetCore.Razor.Language
// Assert
var csharpDocument = codeDocument.GetCSharpDocument();
var diagnostic = Assert.Single(csharpDocument.Diagnostics);
Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.",
diagnostic.GetMessage());
}
[Fact]
public void Execute_CollatesSyntaxDiagnosticsFromImportDocuments()
{
// Arrange
var phase = new DefaultRazorCSharpLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
codeDocument.SetImportSyntaxTrees(new[]
{
RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("@ ")),
RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("<p @(")),
});
var options = RazorCodeGenerationOptions.CreateDefault();
var irDocument = new DocumentIRNode()
{
DocumentKind = "test",
Target = CodeTarget.CreateDefault(codeDocument, options),
Options = options,
};
codeDocument.SetIRDocument(irDocument);
// Act
phase.Execute(codeDocument);
// Assert
var csharpDocument = codeDocument.GetCSharpDocument();
Assert.Collection(csharpDocument.Diagnostics,
diagnostic =>
{
Assert.Equal(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.",
diagnostic.GetMessage());
},
diagnostic =>
{
Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.",
diagnostic.GetMessage());
});
Assert.Same(expectedDiagnostic, diagnostic);
}
}
}

View File

@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language
{
public class DefaultRazorIRLoweringPhaseTest
{
[Fact]
public void Execute_ThrowsForMissingDependency_SyntaxTree()
{
// Arrange
var phase = new DefaultRazorIRLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
// Act & Assert
ExceptionAssert.Throws<InvalidOperationException>(
() => phase.Execute(codeDocument),
$"The '{nameof(DefaultRazorIRLoweringPhase)}' phase requires a '{nameof(RazorSyntaxTree)}' " +
$"provided by the '{nameof(RazorCodeDocument)}'.");
}
[Fact]
public void Execute_CollatesSyntaxDiagnosticsFromSourceDocument()
{
// Arrange
var phase = new DefaultRazorIRLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.Create("<p class=@(");
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
phase.Execute(codeDocument);
// Assert
var irDocument = codeDocument.GetIRDocument();
var diagnostic = Assert.Single(irDocument.Diagnostics);
Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.",
diagnostic.GetMessage());
}
[Fact]
public void Execute_CollatesSyntaxDiagnosticsFromImportDocuments()
{
// Arrange
var phase = new DefaultRazorIRLoweringPhase();
var engine = RazorEngine.CreateEmpty(b => b.Phases.Add(phase));
var codeDocument = TestRazorCodeDocument.CreateEmpty();
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(codeDocument.Source));
codeDocument.SetImportSyntaxTrees(new[]
{
RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("@ ")),
RazorSyntaxTree.Parse(TestRazorSourceDocument.Create("<p @(")),
});
var options = RazorCodeGenerationOptions.CreateDefault();
// Act
phase.Execute(codeDocument);
// Assert
var irDocument = codeDocument.GetIRDocument();
Assert.Collection(irDocument.Diagnostics,
diagnostic =>
{
Assert.Equal(@"A space or line break was encountered after the ""@"" character. Only valid identifiers, keywords, comments, ""("" and ""{"" are valid at the start of a code block and they must occur immediately following ""@"" with no space in between.",
diagnostic.GetMessage());
},
diagnostic =>
{
Assert.Equal(@"The explicit expression block is missing a closing "")"" character. Make sure you have a matching "")"" character for all the ""("" characters within this block, and that none of the "")"" characters are being interpreted as markup.",
diagnostic.GetMessage());
});
}
}
}

View File

@ -210,10 +210,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public override ItemCollection Annotations { get; } = new DefaultItemCollection();
public override RazorDiagnosticCollection Diagnostics { get; } = new DefaultDiagnosticCollection();
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => Diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new NotImplementedException();

View File

@ -499,10 +499,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public override ItemCollection Annotations { get; } = new DefaultItemCollection();
public override RazorDiagnosticCollection Diagnostics => new DefaultDiagnosticCollection();
public override RazorIRNodeCollection Children { get; }
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => Diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new System.NotImplementedException();

View File

@ -126,10 +126,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public override ItemCollection Annotations { get; } = new DefaultItemCollection();
public override RazorDiagnosticCollection Diagnostics { get; } = new DefaultDiagnosticCollection();
public override RazorIRNodeCollection Children { get; } = new DefaultIRNodeCollection();
public override SourceSpan? Source { get; set; }
public override bool HasDiagnostics => Diagnostics.Count > 0;
public override void Accept(RazorIRNodeVisitor visitor)
{
((DerivedIRNodeWalker)visitor).VisitBasic(this);

View File

@ -4,6 +4,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
@ -188,24 +190,66 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{
if (node.Source != null)
{
var sourceRange = node.Source.Value;
_writer.Write("(");
_writer.Write(sourceRange.AbsoluteIndex);
_writer.Write(":");
_writer.Write(sourceRange.LineIndex);
_writer.Write(",");
_writer.Write(sourceRange.CharacterIndex);
_writer.Write(" [");
_writer.Write(sourceRange.Length);
_writer.Write("] ");
WriteSourceRange(node.Source.Value);
}
}
if (sourceRange.FilePath != null)
protected void WriteSourceRange(SourceSpan sourceRange)
{
_writer.Write("(");
_writer.Write(sourceRange.AbsoluteIndex);
_writer.Write(":");
_writer.Write(sourceRange.LineIndex);
_writer.Write(",");
_writer.Write(sourceRange.CharacterIndex);
_writer.Write(" [");
_writer.Write(sourceRange.Length);
_writer.Write("] ");
if (sourceRange.FilePath != null)
{
var fileName = sourceRange.FilePath.Substring(sourceRange.FilePath.LastIndexOf('/') + 1);
_writer.Write(fileName);
}
_writer.Write(")");
}
protected void WriteDiagnostics(RazorIRNode node)
{
if (node.HasDiagnostics)
{
_writer.Write("| ");
for (var i = 0; i < node.Diagnostics.Count; i++)
{
var fileName = sourceRange.FilePath.Substring(sourceRange.FilePath.LastIndexOf('/') + 1);
_writer.Write(fileName);
}
var diagnostic = node.Diagnostics[i];
_writer.Write("{");
WriteSourceRange(diagnostic.Span);
_writer.Write(": ");
_writer.Write(diagnostic.Severity);
_writer.Write(" ");
_writer.Write(diagnostic.Id);
_writer.Write(": ");
_writer.Write(")");
// Purposefully not writing out the entire message to ensure readable IR and because messages
// can span multiple lines. Not using string.GetHashCode because we can't have any collisions.
using (var md5 = MD5.Create())
{
var diagnosticMessage = diagnostic.GetMessage();
var messageBytes = Encoding.UTF8.GetBytes(diagnosticMessage);
var messageHash = md5.ComputeHash(messageBytes);
var stringHashBuilder = new StringBuilder();
for (var j = 0; j < messageHash.Length; j++)
{
stringHashBuilder.Append(messageHash[j].ToString("x2"));
}
var stringHash = stringHashBuilder.ToString();
_writer.Write(stringHash);
}
_writer.Write("} ");
}
}
}