Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactor compilation diagnostics, add them to compilation status page #1762

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,12 @@ protected virtual void ResolveRootContent(DothtmlRootNode root, IAbstractControl
}
catch (DotvvmCompilationException ex)
{
if (ex.Tokens == null)
if (ex.Tokens is null)
{
ex.Tokens = node.Tokens;
ex.ColumnNumber = node.Tokens.First().ColumnNumber;
ex.LineNumber = node.Tokens.First().LineNumber;
var oldLoc = ex.CompilationError.Location;
ex.CompilationError = ex.CompilationError with {
Location = new(oldLoc.FileName, oldLoc.MarkupFile, node.Tokens)
};
}
if (!LogError(ex, node))
throw;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,22 @@ public DefaultControlBuilderFactory(DotvvmConfiguration configuration, IMarkupFi
var compilationService = configuration.ServiceProvider.GetService<IDotvvmViewCompilationService>();
void editCompilationException(DotvvmCompilationException ex)
{
if (ex.FileName == null)
if (ex.FileName is null || ex.FileName == file.FullPath || ex.FileName == file.FileName)
{
ex.FileName = file.FullPath;
ex.SetFile(file.FullPath, file);
}
else if (!Path.IsPathRooted(ex.FileName))
else if (ex.MarkupFile is null)
{
ex.FileName = Path.Combine(
file.FullPath.Remove(file.FullPath.Length - file.FileName.Length),
ex.FileName);
// try to load the markup file of this error
try
{
var exceptionFile = GetMarkupFile(ex.FileName);
ex.SetFile(exceptionFile.file.FullPath, exceptionFile.file);
}
catch { }
}
}

try
{
var sw = ValueStopwatch.StartNew();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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
{

public interface IDiagnosticsCompilationTracer
{
Handle CompilationStarted(string file, string sourceCode);
abstract class Handle
{
public virtual void Parsed(List<DothtmlToken> tokens, DothtmlRootNode syntaxTree) { }
public virtual void Resolved(ResolvedTreeRoot tree, ControlBuilderDescriptor descriptor) { }
public virtual void AfterVisitor(ResolvedControlTreeVisitor visitor, ResolvedTreeRoot tree) { }
public virtual void CompilationDiagnostic(DotvvmCompilationDiagnostic diagnostic, string? contextLine) { }
public virtual void Failed(Exception exception) { }
}
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();
}
}
}
}
37 changes: 37 additions & 0 deletions src/Framework/Framework/Compilation/DotHtmlFileInfo.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using DotVVM.Framework.Binding.Properties;

namespace DotVVM.Framework.Compilation
{
Expand All @@ -9,6 +10,9 @@ public sealed class DotHtmlFileInfo
public CompilationState Status { get; internal set; }
public string? Exception { get; internal set; }

public ImmutableArray<CompilationDiagnosticViewModel> Errors { get; internal set; } = ImmutableArray<CompilationDiagnosticViewModel>.Empty;
public ImmutableArray<CompilationDiagnosticViewModel> Warnings { get; internal set; } = ImmutableArray<CompilationDiagnosticViewModel>.Empty;

/// <summary>Gets or sets the virtual path to the view.</summary>
public string VirtualPath { get; }

Expand Down Expand Up @@ -46,5 +50,38 @@ private static bool IsDothtmlFile(string virtualPath)
virtualPath.IndexOf(".dotlayout", StringComparison.OrdinalIgnoreCase) > -1
);
}

public sealed record CompilationDiagnosticViewModel(
DiagnosticSeverity Severity,
string Message,
string? FileName,
int? LineNumber,
int? ColumnNumber,
string? SourceLine,
int? HighlightLength
)
{
public string? SourceLine { get; set; } = SourceLine;
public string? SourceLinePrefix => SourceLine?.Remove(ColumnNumber ?? 0);
public string? SourceLineHighlight =>
HighlightLength is {} len ? SourceLine?.Substring(ColumnNumber ?? 0, len)
: SourceLine?.Substring(ColumnNumber ?? 0);
public string? SourceLineSuffix =>
(ColumnNumber + HighlightLength) is int startIndex ? SourceLine?.Substring(startIndex) : null;


public CompilationDiagnosticViewModel(DotvvmCompilationDiagnostic diagnostic, string? contextLine)
: this(
diagnostic.Severity,
diagnostic.Message,
diagnostic.Location.FileName,
diagnostic.Location.LineNumber,
diagnostic.Location.ColumnNumber,
contextLine,
diagnostic.Location.LineErrorLength
)
{
}
}
}
}
189 changes: 189 additions & 0 deletions src/Framework/Framework/Compilation/DotvvmCompilationDiagnostic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
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 IEnumerable<TokenBase>? Tokens { get; init; }
exyi marked this conversation as resolved.
Show resolved Hide resolved
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)
{
if (tokens is {})
{
tokens = tokens.ToArray();
lineNumber ??= tokens.FirstOrDefault()?.LineNumber;
columnNumber ??= tokens.FirstOrDefault()?.ColumnNumber;
lineErrorLength ??= tokens.Where(t => t.LineNumber == lineNumber).Select(t => (int?)(t.ColumnNumber + t.Length)).LastOrDefault() - columnNumber;
}

this.MarkupFile = markupFile;
this.FileName = fileName ?? markupFile?.FileName;
this.Tokens = tokens;
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 is null && LineNumber is null && ColumnNumber is null;

public string[] AffectedSpans
{
get
{
if (Tokens is null || !Tokens.Any())
return Array.Empty<string>();
var ts = Tokens.ToArray();
var r = new List<string> { ts[0].Text };
for (int i = 1; i < ts.Length; i++)
{
if (ts[i].StartPosition == ts[i - 1].EndPosition)
r[r.Count - 1] += ts[i].Text;
else
r.Add(ts[i].Text);
}
return r.ToArray();
}
}

public (int start, int end)[] AffectedRanges
{
get
{
if (Tokens is null || !Tokens.Any())
return Array.Empty<(int, int)>();
var ts = Tokens.ToArray();
var r = new (int start, int end)[ts.Length];
r[0] = (ts[0].StartPosition, ts[0].EndPosition);
int ri = 0;
for (int i = 1; i < ts.Length; i++)
{
if (ts[i].StartPosition == ts[i - 1].EndPosition)
r[i].end = ts[i].EndPosition;
else
{
ri += 1;
r[ri] = (ts[i].StartPosition, ts[i].EndPosition);
}
}
return r.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
);
}
}
Loading
Loading