From e3287ae672cf83c324f11298bbe8e234de013aed Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Thu, 8 Jun 2017 12:25:54 -0700 Subject: [PATCH] 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 --- .../DefaultRazorCSharpLoweringPhase.cs | 11 +- .../DefaultRazorIRLoweringPhase.cs | 22 ++- ...reallocatedTagHelperHtmlAttributeIRNode.cs | 4 + .../AddTagHelperHtmlAttributeIRNode.cs | 17 +++ .../CSharpCodeAttributeValueIRNode.cs | 17 +++ .../Intermediate/CSharpCodeIRNode.cs | 16 +++ .../CSharpExpressionAttributeValueIRNode.cs | 17 +++ .../Intermediate/CSharpExpressionIRNode.cs | 16 +++ .../Intermediate/ChecksumIRNode.cs | 17 +++ .../Intermediate/ClassDeclarationIRNode.cs | 16 +++ .../Intermediate/CreateTagHelperIRNode.cs | 17 +++ ...arePreallocatedTagHelperAttributeIRNode.cs | 4 + ...reallocatedTagHelperHtmlAttributeIRNode.cs | 4 + .../DeclareTagHelperFieldsIRNode.cs | 17 +++ .../DefaultDiagnosticCollection.cs | 128 +++++++++++++++++ .../Intermediate/DirectiveIRNode.cs | 16 +++ .../Intermediate/DirectiveTokenIRNode.cs | 17 +++ .../Intermediate/DocumentIRNode.cs | 16 +++ .../Intermediate/ExtensionIRNode.cs | 18 ++- .../Intermediate/FieldDeclarationIRNode.cs | 16 +++ .../Intermediate/HtmlAttributeIRNode.cs | 17 +++ .../Intermediate/HtmlAttributeValueIRNode.cs | 17 +++ .../Intermediate/HtmlContentIRNode.cs | 17 +++ .../Intermediate/MethodDeclarationIRNode.cs | 17 +++ .../NamespaceDeclarationIRNode.cs | 16 +++ .../Intermediate/PropertyDeclarationIRNode.cs | 16 +++ .../Intermediate/RazorDiagnosticCollection.cs | 40 ++++++ .../Intermediate/RazorIRNode.cs | 4 + .../Intermediate/RazorIRNodeExtensions.cs | 46 ++++++ .../Intermediate/RazorIRToken.cs | 17 +++ .../ReadOnlyDiagnosticCollection.cs | 133 ++++++++++++++++++ .../SetPreallocatedTagHelperPropertyIRNode.cs | 4 + .../SetTagHelperPropertyIRNode.cs | 17 +++ .../Intermediate/TagHelperBodyIRNode.cs | 17 +++ .../Intermediate/TagHelperIRNode.cs | 16 +++ .../Intermediate/UsingStatementIRNode.cs | 17 +++ .../DefaultRazorCSharpLoweringPhaseTest.cs | 71 +--------- .../DefaultRazorIRLoweringPhaseTest.cs | 85 +++++++++++ .../Intermediate/DefaultRazorIRBuilderTest.cs | 4 + .../Intermediate/RazorIRNodeReferenceTest.cs | 4 + .../Intermediate/RazorIRNodeWalkerTest.cs | 4 + .../IntegrationTests/RazorIRNodeWriter.cs | 74 ++++++++-- 42 files changed, 967 insertions(+), 92 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/DefaultDiagnosticCollection.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorDiagnosticCollection.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNodeExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/ReadOnlyDiagnosticCollection.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseTest.cs diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCSharpLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCSharpLoweringPhase.cs index c9c9271845..a3328cbb66 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCSharpLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorCSharpLoweringPhase.cs @@ -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(); - 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( diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs index 7e370449de..963428207c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIRLoweringPhase.cs @@ -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); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddPreallocatedTagHelperHtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddPreallocatedTagHelperHtmlAttributeIRNode.cs index 18596049ea..9b1671bfbd 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddPreallocatedTagHelperHtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddPreallocatedTagHelperHtmlAttributeIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs index a3a8b274db..9249e16855 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeAttributeValueIRNode.cs index 540fafb96c..49e1191b79 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeAttributeValueIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeAttributeValueIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeIRNode.cs index eb17780042..4fef6c8e0f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpCodeIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionAttributeValueIRNode.cs index 0368bd6d94..eeb6a859ad 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionAttributeValueIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionAttributeValueIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs index bccaf88d15..6883a039c6 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs index 92c2aba201..eaff0a88fa 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs index f6745bdaa3..60fcfc4cb1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs index 05e4181145..24784d5abb 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperAttributeIRNode.cs index 7d1aa90e6a..e1d2d9ec0b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperAttributeIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperHtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperHtmlAttributeIRNode.cs index 9c87931564..ddec9678a2 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperHtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclarePreallocatedTagHelperHtmlAttributeIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs index c776f73217..5be56ec2f3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs @@ -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 UsedTagHelperTypeNames { get; set; } = new HashSet(StringComparer.Ordinal); public override void Accept(RazorIRNodeVisitor visitor) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DefaultDiagnosticCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DefaultDiagnosticCollection.cs new file mode 100644 index 0000000000..be80087eb0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DefaultDiagnosticCollection.cs @@ -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 _inner = new List(); + + 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 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); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs index dbee2e7152..8835d9221d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs @@ -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 Tokens => Children.OfType(); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs index c55097ee6e..dd6a0e5020 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs index 4217de8f06..aa2e3be646 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs index 6e50018929..1ca010d844 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs @@ -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 node, RazorIRNodeVisitor visitor) + protected static void AcceptExtensionNode(TNode node, RazorIRNodeVisitor visitor) where TNode : ExtensionIRNode { var typedVisitor = visitor as IExtensionIRNodeVisitor; diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/FieldDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/FieldDeclarationIRNode.cs index 9e82492900..81a8cab7d3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/FieldDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/FieldDeclarationIRNode.cs @@ -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 Modifiers { get; set; } = new List(); 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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs index 8101bf6e1c..d1fc3a10d5 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs index a47aaca684..bfa6b98a4c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs index 68d368e29e..1f182a6d93 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/MethodDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/MethodDeclarationIRNode.cs index b2a02ad0c0..150daeac93 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/MethodDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/MethodDeclarationIRNode.cs @@ -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 Modifiers { get; set; } = new List(); @@ -44,5 +60,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate visitor.VisitMethodDeclaration(this); } + } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs index 940cb3474e..1613afccd1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/PropertyDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/PropertyDeclarationIRNode.cs index 9568689bcc..068f02f804 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/PropertyDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/PropertyDeclarationIRNode.cs @@ -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 Modifiers { get; set; } = new List(); 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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorDiagnosticCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorDiagnosticCollection.cs new file mode 100644 index 0000000000..369ca43b59 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorDiagnosticCollection.cs @@ -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 + { + 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 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(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs index b56bc4472a..1b32577bb4 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs @@ -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); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNodeExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNodeExtensions.cs new file mode 100644 index 0000000000..2483d53c53 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNodeExtensions.cs @@ -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 EmptyDiagnostics = Array.Empty(); + + public static IReadOnlyList GetAllDiagnostics(this RazorIRNode node) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + HashSet diagnostics = null; + + AddAllDiagnostics(node); + + return diagnostics?.ToList() ?? EmptyDiagnostics; + + void AddAllDiagnostics(RazorIRNode n) + { + if (n.HasDiagnostics) + { + if (diagnostics == null) + { + diagnostics = new HashSet(); + } + + diagnostics.UnionWith(n.Diagnostics); + } + + for (var i = 0; i < n.Children.Count; i++) + { + AddAllDiagnostics(n.Children[i]); + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs index 60ca7ff606..0111ed3b55 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ReadOnlyDiagnosticCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ReadOnlyDiagnosticCollection.cs new file mode 100644 index 0000000000..ab50f9be44 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ReadOnlyDiagnosticCollection.cs @@ -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 GetEnumerator() + { + return Enumerable.Empty().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(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs index c11d4109f3..870a61642c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetPreallocatedTagHelperPropertyIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs index f20067389e..f097f83f0d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperBodyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperBodyIRNode.cs index 4410886554..54c71b2f99 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperBodyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperBodyIRNode.cs @@ -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) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs index 8560f1f9dc..18026165d9 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs @@ -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; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs index 09f1573c12..11ae8f9991 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs @@ -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) diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorCSharpLoweringPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorCSharpLoweringPhaseTest.cs index 661a7add50..333c88fa44 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorCSharpLoweringPhaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorCSharpLoweringPhaseTest.cs @@ -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( - () => 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("

"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("

- { - 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); } } } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseTest.cs new file mode 100644 index 0000000000..ddb5cbe315 --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DefaultRazorIRLoweringPhaseTest.cs @@ -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( + () => 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("

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("

+ { + 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()); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs index 3595d3e15e..da45ff3f4c 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs @@ -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(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeReferenceTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeReferenceTest.cs index 997bca8ffa..1be15d9ebb 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeReferenceTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeReferenceTest.cs @@ -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(); diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs index 4691cf55e0..43af4991ed 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs @@ -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); diff --git a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/IntegrationTests/RazorIRNodeWriter.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/IntegrationTests/RazorIRNodeWriter.cs index 6387219a06..8f469cf650 100644 --- a/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/IntegrationTests/RazorIRNodeWriter.cs +++ b/test/Microsoft.AspNetCore.Razor.Test.Common/Langauge/IntegrationTests/RazorIRNodeWriter.cs @@ -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("} "); + } } }