-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1762 from riganti/compilation-page-warnings
Refactor compilation diagnostics, add them to compilation status page
- Loading branch information
Showing
43 changed files
with
1,118 additions
and
310 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
src/Framework/Framework/Compilation/DiagnosticsCompilationTracer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using DotVVM.Framework.Compilation.ControlTree.Resolved; | ||
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser; | ||
using DotVVM.Framework.Compilation.Parser.Dothtml.Tokenizer; | ||
using DotVVM.Framework.Compilation.ViewCompiler; | ||
|
||
namespace DotVVM.Framework.Compilation | ||
{ | ||
/// <summary> Instrumets DotVVM view compilation, traced events are defined in <see cref="Handle" />. | ||
/// The tracers are found using IServiceProvider, to register your tracer, add it to DI with <c>service.AddSingleton<IDiagnosticsCompilationTracer, MyTracer>()</c> </summary> | ||
public interface IDiagnosticsCompilationTracer | ||
{ | ||
Handle CompilationStarted(string file, string sourceCode); | ||
/// <summary> Traces compilation of a single file, created in the <see cref="CompilationStarted(string, string)"/> method. Note that the class can also implement <see cref="IDisposable" />. </summary> | ||
abstract class Handle | ||
{ | ||
/// <summary> Called after the DotHTML file is parsed and syntax tree is created. Called even when there are errors. </summary> | ||
public virtual void Parsed(List<DothtmlToken> tokens, DothtmlRootNode syntaxTree) { } | ||
/// <summary> Called after the entire tree has resolved types - controls have assigned type, attributes have assigned DotvvmProperty, bindings are compiled, ... </summary> | ||
public virtual void Resolved(ResolvedTreeRoot tree, ControlBuilderDescriptor descriptor) { } | ||
/// <summary> After initial resolving, the tree is post-processed using a number of visitors (<see cref="DataContextPropertyAssigningVisitor"/>, <see cref="Styles.StylingVisitor" />, <see cref="LiteralOptimizationVisitor" />, ...). After each visitor processing, this method is called. </summary> | ||
public virtual void AfterVisitor(ResolvedControlTreeVisitor visitor, ResolvedTreeRoot tree) { } | ||
/// <summary> For each compilation diagnostic (warning/error), this method is called. </summary> | ||
/// <param name="contextLine"> The line of code where the error occured. </param> | ||
public virtual void CompilationDiagnostic(DotvvmCompilationDiagnostic diagnostic, string? contextLine) { } | ||
/// <summary> Called if the compilation fails for any reason. Normally, <paramref name="exception"/> will be of type <see cref="DotvvmCompilationDiagnostic" /> </summary> | ||
public virtual void Failed(Exception exception) { } | ||
} | ||
/// <summary> Singleton tracing handle which does nothing. </summary> | ||
sealed class NopHandle: Handle | ||
{ | ||
private NopHandle() { } | ||
public static readonly NopHandle Instance = new NopHandle(); | ||
} | ||
} | ||
|
||
public sealed class CompositeDiagnosticsCompilationTracer : IDiagnosticsCompilationTracer | ||
{ | ||
readonly IDiagnosticsCompilationTracer[] tracers; | ||
|
||
public CompositeDiagnosticsCompilationTracer(IEnumerable<IDiagnosticsCompilationTracer> tracers) | ||
{ | ||
this.tracers = tracers.ToArray(); | ||
} | ||
|
||
public IDiagnosticsCompilationTracer.Handle CompilationStarted(string file, string sourceCode) | ||
{ | ||
var handles = this.tracers | ||
.Select(t => t.CompilationStarted(file, sourceCode)) | ||
.Where(t => t != IDiagnosticsCompilationTracer.NopHandle.Instance) | ||
.ToArray(); | ||
|
||
|
||
return handles.Length switch { | ||
0 => IDiagnosticsCompilationTracer.NopHandle.Instance, | ||
1 => handles[0], | ||
_ => new Handle(handles) | ||
}; | ||
} | ||
|
||
sealed class Handle : IDiagnosticsCompilationTracer.Handle, IDisposable | ||
{ | ||
private IDiagnosticsCompilationTracer.Handle[] handles; | ||
|
||
public Handle(IDiagnosticsCompilationTracer.Handle[] handles) | ||
{ | ||
this.handles = handles; | ||
} | ||
|
||
public override void AfterVisitor(ResolvedControlTreeVisitor visitor, ResolvedTreeRoot tree) | ||
{ | ||
foreach (var h in handles) | ||
h.AfterVisitor(visitor, tree); | ||
} | ||
public override void CompilationDiagnostic(DotvvmCompilationDiagnostic warning, string? contextLine) | ||
{ | ||
foreach (var h in handles) | ||
h.CompilationDiagnostic(warning, contextLine); | ||
} | ||
|
||
|
||
public override void Failed(Exception exception) | ||
{ | ||
foreach (var h in handles) | ||
h.Failed(exception); | ||
} | ||
public override void Parsed(List<DothtmlToken> tokens, DothtmlRootNode syntaxTree) | ||
{ | ||
foreach (var h in handles) | ||
h.Parsed(tokens, syntaxTree); | ||
} | ||
public override void Resolved(ResolvedTreeRoot tree, ControlBuilderDescriptor descriptor) | ||
{ | ||
foreach (var h in handles) | ||
h.Resolved(tree, descriptor); | ||
} | ||
public void Dispose() | ||
{ | ||
foreach (var h in handles) | ||
(h as IDisposable)?.Dispose(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
188 changes: 188 additions & 0 deletions
188
src/Framework/Framework/Compilation/DotvvmCompilationDiagnostic.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using DotVVM.Framework.Binding.Properties; | ||
using DotVVM.Framework.Compilation.Parser; | ||
using DotVVM.Framework.Hosting; | ||
using System.Linq; | ||
using DotVVM.Framework.Compilation.Parser.Dothtml.Parser; | ||
using System; | ||
using DotVVM.Framework.Compilation.ControlTree.Resolved; | ||
using DotVVM.Framework.Binding; | ||
using Newtonsoft.Json; | ||
using DotVVM.Framework.Binding.Expressions; | ||
|
||
namespace DotVVM.Framework.Compilation | ||
{ | ||
/// <summary> Represents a dothtml compilation error or a warning, along with its location. </summary> | ||
public record DotvvmCompilationDiagnostic: IEquatable<DotvvmCompilationDiagnostic> | ||
{ | ||
public DotvvmCompilationDiagnostic( | ||
string message, | ||
DiagnosticSeverity severity, | ||
DotvvmCompilationSourceLocation? location, | ||
IEnumerable<DotvvmCompilationDiagnostic>? notes = null, | ||
Exception? innerException = null) | ||
{ | ||
Message = message; | ||
Severity = severity; | ||
Location = location ?? DotvvmCompilationSourceLocation.Unknown; | ||
Notes = notes?.ToImmutableArray() ?? ImmutableArray<DotvvmCompilationDiagnostic>.Empty; | ||
InnerException = innerException; | ||
} | ||
|
||
public string Message { get; init; } | ||
public Exception? InnerException { get; init; } | ||
public DiagnosticSeverity Severity { get; init; } | ||
public DotvvmCompilationSourceLocation Location { get; init; } | ||
public ImmutableArray<DotvvmCompilationDiagnostic> Notes { get; init; } | ||
/// <summary> Errors with lower number are preferred when selecting the primary fault to the user. When equal, errors are sorted based on the location. 0 is default for semantic errors, 100 for parser errors and 200 for tokenizer errors. </summary> | ||
public int Priority { get; init; } | ||
|
||
public bool IsError => Severity == DiagnosticSeverity.Error; | ||
public bool IsWarning => Severity == DiagnosticSeverity.Warning; | ||
|
||
public override string ToString() => | ||
$"{Severity}: {Message}\n at {Location?.ToString() ?? "unknown location"}"; | ||
} | ||
|
||
public sealed record DotvvmCompilationSourceLocation | ||
{ | ||
public string? FileName { get; init; } | ||
[JsonIgnore] | ||
public MarkupFile? MarkupFile { get; init; } | ||
[JsonIgnore] | ||
public ImmutableArray<TokenBase> Tokens { get; init; } | ||
public int? LineNumber { get; init; } | ||
public int? ColumnNumber { get; init; } | ||
public int LineErrorLength { get; init; } | ||
[JsonIgnore] | ||
public DothtmlNode? RelatedSyntaxNode { get; init; } | ||
[JsonIgnore] | ||
public ResolvedTreeNode? RelatedResolvedNode { get; init; } | ||
public DotvvmProperty? RelatedProperty { get; init; } | ||
public IBinding? RelatedBinding { get; init; } | ||
|
||
public Type? RelatedControlType => this.RelatedResolvedNode?.GetAncestors(true).OfType<ResolvedControl>().FirstOrDefault()?.Metadata.Type; | ||
|
||
public DotvvmCompilationSourceLocation( | ||
string? fileName, | ||
MarkupFile? markupFile, | ||
IEnumerable<TokenBase>? tokens, | ||
int? lineNumber = null, | ||
int? columnNumber = null, | ||
int? lineErrorLength = null) | ||
{ | ||
this.Tokens = tokens?.ToImmutableArray() ?? ImmutableArray<TokenBase>.Empty; | ||
if (this.Tokens.Length > 0) | ||
{ | ||
lineNumber ??= this.Tokens[0].LineNumber; | ||
columnNumber ??= this.Tokens[0].ColumnNumber; | ||
lineErrorLength ??= this.Tokens.Where(t => t.LineNumber == lineNumber).Select(t => (int?)(t.ColumnNumber + t.Length)).LastOrDefault() - columnNumber; | ||
} | ||
|
||
this.MarkupFile = markupFile; | ||
this.FileName = fileName ?? markupFile?.FileName; | ||
this.LineNumber = lineNumber; | ||
this.ColumnNumber = columnNumber; | ||
this.LineErrorLength = lineErrorLength ?? 0; | ||
} | ||
|
||
public DotvvmCompilationSourceLocation( | ||
IEnumerable<TokenBase> tokens): this(fileName: null, null, tokens) { } | ||
public DotvvmCompilationSourceLocation( | ||
DothtmlNode syntaxNode, IEnumerable<TokenBase>? tokens = null) | ||
: this(fileName: null, null, tokens ?? syntaxNode?.Tokens) | ||
{ | ||
RelatedSyntaxNode = syntaxNode; | ||
} | ||
public DotvvmCompilationSourceLocation( | ||
ResolvedTreeNode resolvedNode, DothtmlNode? syntaxNode = null, IEnumerable<TokenBase>? tokens = null) | ||
: this( | ||
syntaxNode ?? resolvedNode.GetAncestors(true).FirstOrDefault(n => n.DothtmlNode is {})?.DothtmlNode!, | ||
tokens | ||
) | ||
{ | ||
RelatedResolvedNode = resolvedNode; | ||
if (resolvedNode.GetAncestors().OfType<ResolvedPropertySetter>().FirstOrDefault() is {} property) | ||
RelatedProperty = property.Property; | ||
} | ||
|
||
public static readonly DotvvmCompilationSourceLocation Unknown = new(fileName: null, null, null); | ||
public bool IsUnknown => FileName is null && MarkupFile is null && Tokens.IsEmpty && LineNumber is null && ColumnNumber is null; | ||
|
||
/// <summary> Text of the affected tokens. Consecutive tokens are concatenated - usually, this returns a single element array. </summary> | ||
public string[] AffectedSpans | ||
{ | ||
get | ||
{ | ||
if (Tokens.IsEmpty) | ||
return Array.Empty<string>(); | ||
var spans = new List<string> { Tokens[0].Text }; | ||
for (int i = 1; i < Tokens.Length; i++) | ||
{ | ||
if (Tokens[i].StartPosition == Tokens[i - 1].EndPosition) | ||
spans[spans.Count - 1] += Tokens[i].Text; | ||
else | ||
spans.Add(Tokens[i].Text); | ||
} | ||
return spans.ToArray(); | ||
} | ||
} | ||
|
||
/// <summary> Ranges of the affected tokens (in UTF-16 codepoint positions). Consecutive rangess are merged - usually, this returns a single element array. </summary> | ||
public (int start, int end)[] AffectedRanges | ||
{ | ||
get | ||
{ | ||
if (Tokens.IsEmpty) | ||
return Array.Empty<(int, int)>(); | ||
var ranges = new (int start, int end)[Tokens.Length]; | ||
ranges[0] = (Tokens[0].StartPosition, Tokens[0].EndPosition); | ||
int ri = 0; | ||
for (int i = 1; i < Tokens.Length; i++) | ||
{ | ||
if (Tokens[i].StartPosition == Tokens[i - 1].EndPosition) | ||
ranges[i].end = Tokens[i].EndPosition; | ||
else | ||
{ | ||
ri += 1; | ||
ranges[ri] = (Tokens[i].StartPosition, Tokens[i].EndPosition); | ||
} | ||
} | ||
return ranges.AsSpan(0, ri + 1).ToArray(); | ||
} | ||
} | ||
|
||
public int? EndLineNumber => Tokens.LastOrDefault()?.LineNumber ?? LineNumber; | ||
public int? EndColumnNumber => (Tokens.LastOrDefault()?.ColumnNumber + Tokens.LastOrDefault()?.Length) ?? ColumnNumber; | ||
|
||
public override string ToString() | ||
{ | ||
if (IsUnknown) | ||
return "Unknown location"; | ||
else if (FileName is {} && LineNumber is {}) | ||
{ | ||
// MSBuild-style file location | ||
return $"{FileName}({LineNumber}{(ColumnNumber is {} ? "," + ColumnNumber : "")})"; | ||
} | ||
else | ||
{ | ||
// only position, plus add the affected spans | ||
var location = | ||
LineNumber is {} && ColumnNumber is {} ? $"{LineNumber},{ColumnNumber}: " : | ||
LineNumber is {} ? $"{LineNumber}: " : | ||
""; | ||
return $"{location}{string.Join("; ", AffectedSpans)}"; | ||
} | ||
} | ||
|
||
public DotvvmLocationInfo ToRuntimeLocation() => | ||
new DotvvmLocationInfo( | ||
this.FileName, | ||
this.AffectedRanges, | ||
this.LineNumber, | ||
this.RelatedControlType, | ||
this.RelatedProperty | ||
); | ||
} | ||
} |
Oops, something went wrong.