Browse Source

XAML warnings/diagnostics support (#13447)

* Add diagnostics support to the Avalonia.Build.Tasks

* HostApp and generators build fix

* Diagnostics support in Avalonia XAML

* Support multiple style selector errors at once

* Improve avalonia intrinsics error handling + add tests

* Add CompiledBindings multiple errors tests

* Fix name generator

* Make AvaloniaXamlIlDuplicateSettersChecker a warning

* Fix Style_Parser_Throws_For_Duplicate_Setter test

* Make XamlLoaderUnreachable respect warnings settings

* Add AvaloniaXamlIlStyleValidatorTransformer

* Throw more specific exceptions instead of XamlParseException

* Get rid of XamlXDiagnosticCode to simplify diagnostics code

* Simplify XAML exceptions by avoiding DiagnosticCode in them

* Simplify XamlCompilerDiagnosticsFilter

* Don't use AvaloniaXamlDiagnosticCodes in Avalonia.Generators

* Fix some error handlings in compiler task

* Update editor config for in-solution analysis

* Update XamlX

* Fix missing document path

* Avoid Description field usage

* Add AvaloniaXamlVerboseExceptions property and make exception formatting customizable

* Make Avalonia.NameGenerator not crash if there are XAML errors, members should still be generated

* Update tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

---------

Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com>
pull/13644/head
Max Katz 2 years ago
committed by GitHub
parent
commit
ea64505600
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .editorconfig
  2. 1
      build/BuildTargets.targets
  3. 5
      packages/Avalonia/AvaloniaBuildTasks.targets
  4. 18
      packages/Avalonia/AvaloniaRules.Project.xml
  5. 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  6. 14
      src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs
  7. 8
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  8. 109
      src/Avalonia.Build.Tasks/Extensions.cs
  9. 5
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  10. 70
      src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs
  11. 117
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  12. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  13. 30
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  14. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs
  15. 63
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs
  16. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  17. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  18. 56
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  19. 43
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
  20. 60
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
  21. 16
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
  22. 11
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  23. 24
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  24. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  25. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
  26. 22
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  27. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs
  28. 16
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
  29. 28
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  30. 42
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  31. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
  32. 21
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  33. 34
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs
  34. 26
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
  35. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  36. 27
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  37. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  38. 47
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  39. 1
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  40. 8
      src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
  41. 13
      tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
  42. 5
      tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
  43. 53
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  44. 134
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs
  45. 28
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
  46. 24
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  47. 36
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  48. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs

6
.editorconfig

@ -208,6 +208,12 @@ dotnet_diagnostic.AVA2001.severity = error
# Xaml files
[*.{xaml,axaml}]
indent_size = 2
# DuplicateSetterError
avalonia_xaml_diagnostic.AVLN2203.severity = error
# StyleInMergedDictionaries
avalonia_xaml_diagnostic.AVLN2204.severity = error
# Obsolete
avalonia_xaml_diagnostic.AVLN5001.severity = error
# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]

1
build/BuildTargets.targets

@ -4,6 +4,7 @@
<AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
<AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">true</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>

5
packages/Avalonia/AvaloniaBuildTasks.targets

@ -130,6 +130,7 @@
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
</PropertyGroup>
<WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@ -153,7 +154,9 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)">
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
AnalyzerConfigFiles="@(EditorConfigFiles)">
<Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
</CompileAvaloniaXamlTask>
<Exec

18
packages/Avalonia/AvaloniaRules.Project.xml

@ -18,11 +18,6 @@
Description="Enable/Disable XAML Compiling"
Category="Compile" />
<BoolProperty Name="AvaloniaXamlIlVerifyIl"
DisplayName="Verify IL"
Description="Enable/Disable Verify IL after XAML Compiling"
Category="Compile" />
<BoolProperty Name="AvaloniaUseCompiledBindingsByDefault"
DisplayName="Use CompiledBindings"
Description="Use compiled bindings by default"
@ -32,10 +27,15 @@
<!-- Debug -->
<BoolProperty Name="AvaloniaXamlIlDebuggerLaunch"
DisplayName="Debug XAML Compiler"
Description="Allow debug XAML compilation"
Category="Debug" />
DisplayName="Debug XAML Compiler"
Description="Allow debug XAML compilation"
Category="Debug" />
<BoolProperty Name="AvaloniaXamlVerboseExceptions"
DisplayName="Report verbose internal exceptions with stack traces"
Description="Also includes inner exceptions"
Category="Debug" />
<EnumProperty Name="AvaloniaXamlReportImportance"
DisplayName="XAML Report Importance"
Description="Provides levels of importance for XAML Compiler messages"

1
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -119,6 +119,7 @@
<Compile Include="..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
<Compile Include="..\Avalonia.Base\Utilities\SpanHelpers.cs" Link="Utilities\SpanHelpers.cs" />
<Compile Include="..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
<Compile Include="..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
<Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />

14
src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs

@ -1,14 +0,0 @@
namespace Avalonia.Build.Tasks
{
public enum BuildEngineErrorCode
{
InvalidXAML = 1,
DuplicateXClass = 2,
LegacyResmScheme = 3,
TransformError = 4,
EmitError = 4,
Loader = 5,
Unknown = 9999
}
}

8
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -56,7 +56,9 @@ namespace Avalonia.Build.Tasks
refInput, RefOutputPath,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch);
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
if (!res.Success)
{
WrittenFilePaths = writtenFilePaths.ToArray();
@ -121,6 +123,10 @@ namespace Avalonia.Build.Tasks
public bool DebuggerLaunch { get; set; }
public bool VerboseExceptions { get; set; }
public ITaskItem[] AnalyzerConfigFiles { get; set; }
[Output]
public string[] WrittenFilePaths { get; private set; } = Array.Empty<string>();
}

109
src/Avalonia.Build.Tasks/Extensions.cs

@ -1,41 +1,110 @@
using System;
using System.Text;
using System.Xml;
using Microsoft.Build.Framework;
using XamlX;
namespace Avalonia.Build.Tasks
namespace Avalonia.Build.Tasks;
internal static class Extensions
{
static class Extensions
public static void LogError(this IBuildEngine engine, string code, string file, Exception ex,
int? lineNumber = null, int? linePosition = null)
{
static string FormatErrorCode(BuildEngineErrorCode code) => FormattableString.Invariant($"AVLN:{(int)code:0000}");
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, Exception ex,
int lineNumber = 0, int linePosition = 0)
if (lineNumber is null && linePosition is null
&& ex is XmlException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
#if DEBUG
LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
#else
LogError(engine, code, file, ex.Message, lineNumber, linePosition);
LogError(engine, code, file, ex.Message, lineNumber, linePosition);
#endif
}
public static void LogDiagnostic(this IBuildEngine engine, XamlDiagnostic diagnostic)
{
var message = diagnostic.Title;
if (diagnostic.Severity == XamlDiagnosticSeverity.None)
{
// Skip.
}
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
int lineNumber = 0, int linePosition = 0)
else if (diagnostic.Severity == XamlDiagnosticSeverity.Warning)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
lineNumber, linePosition, lineNumber, linePosition, message,
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
message,
"", "Avalonia"));
}
public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
int lineNumber = 0, int linePosition = 0)
else
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
lineNumber, linePosition, lineNumber, linePosition, message,
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", diagnostic.Code, diagnostic.Document ?? "",
diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
diagnostic.LineNumber ?? 0, diagnostic.LinePosition ?? 0,
message,
"", "Avalonia"));
}
}
public static void LogError(this IBuildEngine engine, string code, string file, string message,
int? lineNumber = null, int? linePosition = null)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", code, file ?? "",
lineNumber ?? 0, linePosition ?? 0, lineNumber ?? 0, linePosition ?? 0,
message, "", "Avalonia"));
}
public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
public static void LogWarning(this IBuildEngine engine, string code, string file, string message,
int lineNumber = 0, int linePosition = 0)
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", code, file ?? "",
lineNumber, linePosition, lineNumber, linePosition, message,
"", "Avalonia"));
}
public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
{
engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
}
public static string FormatException(this Exception exception, bool verbose)
{
if (!verbose)
{
engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
return exception.Message;
}
var builder = new StringBuilder();
Process(exception);
return builder.ToString();
// Inspired by https://github.com/dotnet/msbuild/blob/e6409007d3a09255431eb28af01835ce1cd316b5/src/Shared/TaskLoggingHelper.cs#L909
void Process(Exception exception)
{
if (exception is AggregateException aggregateException)
{
foreach (Exception innerException in aggregateException.Flatten().InnerExceptions)
{
Process(innerException);
}
return;
}
do
{
builder.Append(exception.GetType().Name);
builder.Append(": ");
builder.AppendLine(exception.Message);
builder.AppendLine(exception.StackTrace);
exception = exception.InnerException;
} while (exception != null);
}
}
}

5
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Utilities;
using Microsoft.Build.Framework;
using SPath = System.IO.Path;
@ -100,7 +101,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
return false;
}
@ -109,7 +110,7 @@ namespace Avalonia.Build.Tasks
if (typeToXamlIndex.ContainsKey(info.XClass))
{
BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath,
BuildEngine.LogError(AvaloniaXamlDiagnosticCodes.DuplicateXClass, s.SystemPath,
$"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
return false;
}

70
src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using XamlX;
namespace Avalonia.Build.Tasks;
// With MSBuild, we don't need to read for TreatWarningsAsErrors/WarningsAsErrors/WarningsNotAsErrors/NoWarn properties.
// Just by reporting them with LogWarning MSBuild will do the rest for us.
// But we still need to read EditorConfig manually.
public class XamlCompilerDiagnosticsFilter
{
private static readonly Regex s_editorConfigRegex =
new("""avalonia_xaml_diagnostic\.([\w\d]+)\.severity\s*=\s*(\w*)""");
private readonly Lazy<Dictionary<string, string>> _lazyEditorConfig;
public XamlCompilerDiagnosticsFilter(
ITaskItem[]? analyzerConfigFiles)
{
_lazyEditorConfig = new Lazy<Dictionary<string, string>>(() => ParseEditorConfigFiles(analyzerConfigFiles));
}
internal XamlDiagnosticSeverity Handle(XamlDiagnostic diagnostic)
{
return Handle(diagnostic.Severity, diagnostic.Code);
}
internal XamlDiagnosticSeverity Handle(XamlDiagnosticSeverity currentSeverity, string diagnosticCode)
{
if (_lazyEditorConfig.Value.TryGetValue(diagnosticCode, out var severity))
{
return severity.ToLowerInvariant() switch
{
"default" => currentSeverity,
"error" => XamlDiagnosticSeverity.Error,
"warning" => XamlDiagnosticSeverity.Warning,
_ => XamlDiagnosticSeverity.None // "suggestion", "silent", "none"
};
}
return currentSeverity;
}
private Dictionary<string, string> ParseEditorConfigFiles(ITaskItem[]? analyzerConfigFiles)
{
// Very naive EditorConfig parser, supporting minimal properties set via regex:
var severities = new Dictionary<string, string>();
if (analyzerConfigFiles is not null)
{
foreach (var fileItem in analyzerConfigFiles)
{
if (File.Exists(fileItem.ItemSpec))
{
var fileContent = File.ReadAllText(fileItem.ItemSpec);
var matches = s_editorConfigRegex.Matches(fileContent);
foreach (Match match in matches)
{
severities[match.Groups[1].Value] = match.Groups[2].Value;
}
}
}
}
return severities;
}
}

117
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -44,21 +45,13 @@ namespace Avalonia.Build.Tasks
}
}
public static CompileResult Compile(IBuildEngine engine,
string input, string output,
string refInput, string refOutput,
string[] references, string projectDirectory,
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey,
bool skipXamlCompilation)
{
return Compile(engine, input, output, refInput, refOutput, references, projectDirectory, verifyIl, defaultCompileBindings, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false);
}
internal static CompileResult Compile(IBuildEngine engine,
string input, string output,
string refInput, string refOutput,
string[] references, string projectDirectory,
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch)
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions)
{
try
{
@ -70,7 +63,10 @@ namespace Avalonia.Build.Tasks
var refAsm = refTypeSystem?.TargetAssemblyDefinition;
if (!skipXamlCompilation)
{
var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, defaultCompileBindings, logImportance, debuggerLaunch);
var compileRes = CompileCore(
engine, typeSystem, projectDirectory, verifyIl,
defaultCompileBindings, logImportance, diagnosticsFilter,
debuggerLaunch, verboseExceptions);
if (compileRes == null)
return new CompileResult(true);
if (compileRes == false)
@ -99,7 +95,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception ex)
{
engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, "", ex);
return new CompileResult(false);
}
}
@ -107,8 +103,10 @@ namespace Avalonia.Build.Tasks
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
string projectDirectory, bool verifyIl,
bool defaultCompileBindings,
MessageImportance logImportance
, bool debuggerLaunch = false)
MessageImportance logImportance,
XamlCompilerDiagnosticsFilter diagnosticsFilter,
bool debuggerLaunch,
bool verboseExceptions)
{
if (debuggerLaunch)
{
@ -170,6 +168,20 @@ namespace Avalonia.Build.Tasks
asm.MainModule.Types.Add(trampolineBuilder);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var diagnostics = new List<XamlDiagnostic>();
var diagnosticsHandler = new XamlDiagnosticsHandler()
{
HandleDiagnostic = diagnostic =>
{
var newSeverity = diagnosticsFilter.Handle(diagnostic);
diagnostic = diagnostic with { Severity = newSeverity };
diagnostics.Add(diagnostic);
return newSeverity;
},
CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia,
ExceptionFormatter = ex => ex.FormatException(verboseExceptions)
};
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
@ -178,7 +190,8 @@ namespace Avalonia.Build.Tasks
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
new DeterministicIdGenerator());
new DeterministicIdGenerator(),
diagnosticsHandler);
var contextDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "XamlIlContext",
@ -259,6 +272,7 @@ namespace Avalonia.Build.Tasks
{
var typeDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!"+ group.Name,
TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object);
var transformFailed = false;
typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor)
{
@ -277,6 +291,7 @@ namespace Avalonia.Build.Tasks
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlParser.Parse(xaml);
parsed.Document = res.FilePath;
var initialRoot = (XamlAstObjectNode)parsed.Root;
@ -338,8 +353,7 @@ namespace Avalonia.Build.Tasks
new XamlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}
compiler.Transform(parsed);
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
@ -376,29 +390,28 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
return false;
transformFailed = true;
engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, res.FilePath, e);
}
}
try
{
compiler.TransformGroup(parsedXamlDocuments);
if (!transformFailed)
{
compiler.TransformGroup(parsedXamlDocuments);
}
}
catch (XamlDocumentParseException e)
catch (Exception e)
{
engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
transformFailed = true;
engine.LogError(AvaloniaXamlDiagnosticCodes.TransformError, "", e);
}
catch (XamlParseException e)
var hasAnyError = ReportDiagnostics(engine, diagnostics) || transformFailed;
if (hasAnyError)
{
engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
return false;
}
foreach (var document in parsedXamlDocuments)
@ -605,21 +618,23 @@ namespace Avalonia.Build.Tasks
}
else
{
engine.LogWarning(BuildEngineErrorCode.Loader, "",
$"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found");
var diagnostic = new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable,
diagnosticsFilter.Handle(
XamlDiagnosticSeverity.Warning,
AvaloniaXamlDiagnosticCodes.XamlLoaderUnreachable),
$"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found")
{
Document = document.FileSource?.FilePath
};
engine.LogDiagnostic(diagnostic);
}
}
}
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
engine.LogError(AvaloniaXamlDiagnosticCodes.EmitError, res.FilePath, e);
return false;
}
res.Remove();
@ -636,7 +651,6 @@ namespace Avalonia.Build.Tasks
}
}
return true;
}
@ -652,6 +666,21 @@ namespace Avalonia.Build.Tasks
return true;
}
static bool ReportDiagnostics(IBuildEngine engine, IReadOnlyCollection<XamlDiagnostic> diagnostics)
{
var hasAnyError = diagnostics.Any(d => d.Severity >= XamlDiagnosticSeverity.Error);
const int maxErrorsPerDocument = 100;
foreach (var diagnostic in diagnostics
.GroupBy(d => d.Document)
.SelectMany(d => d.Take(maxErrorsPerDocument)))
{
engine.LogDiagnostic(diagnostic);
}
return hasAnyError;
}
static bool? CompileCoreForRefAssembly(
IBuildEngine engine, CecilTypeSystem sourceTypeSystem, CecilTypeSystem refTypeSystem)
{
@ -693,9 +722,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception e)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", "",
0, 0, 0, 0,
e.Message, "", "Avalonia"));
engine.LogError(AvaloniaXamlDiagnosticCodes.Unknown, e.Message, e);
return false;
}

1
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@ -13,6 +13,7 @@
<ItemGroup>
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
<Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />

30
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -209,11 +209,34 @@ namespace Avalonia.Markup.Xaml.XamlIl
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
var diagnostics = new List<XamlDiagnostic>();
var diagnosticsHandler = new XamlDiagnosticsHandler()
{
HandleDiagnostic = (diagnostic) =>
{
var runtimeDiagnostic = new RuntimeXamlDiagnostic(diagnostic.Code.ToString(),
(RuntimeXamlDiagnosticSeverity)diagnostic.Severity,
diagnostic.Title, diagnostic.LineNumber, diagnostic.LinePosition)
{
Document = diagnostic.Document
};
var newSeverity =
(XamlDiagnosticSeverity?)configuration.DiagnosticHandler?.Invoke(runtimeDiagnostic) ??
diagnostic.Severity;
diagnostic = diagnostic with { Severity = newSeverity };
diagnostics.Add(diagnostic);
return newSeverity;
},
CodeMappings = AvaloniaXamlDiagnosticCodes.XamlXDiagnosticCodeToAvalonia
};
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder)),
null,
diagnosticsHandler),
_sreEmitMappings,
_sreContextType)
{
@ -236,8 +259,9 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
}
var parsed = compiler.Parse(xaml, overrideType);
parsed.Document = "runtimexaml:" + parsedDocuments.Count;
compiler.Transform(parsed);
var xamlName = GetSafeUriIdentifier(document.BaseUri)
@ -263,6 +287,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.TransformGroup(parsedDocuments);
diagnostics.ThrowExceptionIfAnyError();
var createdTypes = parsedDocuments.Select(document =>
{
compiler.Compile(document.XamlDocument, document.TypeBuilderProvider, document.Uri, document.FileSource);

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs

@ -22,14 +22,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes
_values = values;
Type = new XamlAstClrTypeReference(lineInfo, arrayType, false);
foreach (var element in values)
{
if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
{
throw new XamlParseException("x:Array element is not assignable to the array element type!", lineInfo);
}
}
}
public IXamlAstTypeReference Type { get; }

63
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs

@ -0,0 +1,63 @@
using System;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
internal static class AvaloniaXamlDiagnosticCodes
{
public const string Unknown = "AVLN9999";
// XML/XAML parsing errors 1000-1999.
public const string ParseError = "AVLN1000";
public const string InvalidXAML = "AVLN1001";
// XAML transform errors 2000-2999.
public const string TransformError = "AVLN2000";
public const string DuplicateXClass = "AVLN2002";
public const string TypeSystemError = "AVLN2003";
public const string AvaloniaIntrinsicsError = "AVLN2005";
public const string BindingsError = "AVLN2100";
public const string DataContextResolvingError = "AVLN2101";
public const string StyleTransformError = "AVLN2200";
public const string SelectorsTransformError = "AVLN2201";
public const string PropertyPathError = "AVLN2202";
public const string DuplicateSetterError = "AVLN2203";
public const string StyleInMergedDictionaries = "AVLN2204";
// XAML emit errors 3000-3999.
public const string EmitError = "AVLN3000";
public const string XamlLoaderUnreachable = "AVLN3001";
// Generator specific errors 4000-4999.
public const string NameGeneratorError = "AVLN4001";
// Reserved 5000-9998
public const string Obsolete = "AVLN5001";
internal static string XamlXDiagnosticCodeToAvalonia(object xamlException)
{
return xamlException switch
{
XamlXWellKnownDiagnosticCodes wellKnownDiagnosticCodes => wellKnownDiagnosticCodes switch
{
XamlXWellKnownDiagnosticCodes.Obsolete => Obsolete,
_ => throw new ArgumentOutOfRangeException()
},
XamlDataContextException => DataContextResolvingError,
XamlBindingsTransformException => BindingsError,
XamlPropertyPathException => PropertyPathError,
XamlStyleTransformException => StyleTransformError,
XamlSelectorsTransformException => SelectorsTransformError,
XamlTransformException => TransformError,
XamlTypeSystemException => TypeSystemError,
XamlLoadException => EmitError,
XamlParseException => ParseError,
_ => Unknown
};
}
}

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -57,6 +57,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlStyleValidatorTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
@ -126,9 +127,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public List<IXamlAstGroupTransformer> GroupTransformers { get; }
public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents, bool strict = true)
public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents)
{
var ctx = new AstGroupTransformationContext(documents, _configuration, strict);
var ctx = new AstGroupTransformationContext(documents, _configuration);
foreach (var transformer in GroupTransformers)
{
foreach (var doc in documents)
@ -166,8 +167,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new XamlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
false) :
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed),
(XamlAstXmlTypeReference)rootObject.Type);
if (overrideRootType != null)

5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@ -17,8 +17,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
XamlIlTrampolineBuilder trampolineBuilder,
IXamlIdentifierGenerator identifierGenerator = null)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
IXamlIdentifierGenerator identifierGenerator,
XamlDiagnosticsHandler diagnosticsHandler)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator, diagnosticsHandler)
{
ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter;

56
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -6,6 +6,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Media;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
@ -16,6 +17,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result)
{
bool ReturnOnParseError(string title, out IXamlAstValueNode result)
{
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.AvaloniaIntrinsicsError,
XamlDiagnosticSeverity.Error,
title,
node)
{
// Only one instance when we can lower Error to a Warning
MinSeverity = XamlDiagnosticSeverity.Warning
});
result = null;
return false;
}
if (type.FullName == "System.TimeSpan")
{
var tsText = text.Trim();
@ -24,11 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
// // shorthand seconds format (ie. "0.25")
if (!tsText.Contains(":") && double.TryParse(tsText,
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture, out var seconds))
NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture, out var seconds))
timeSpan = TimeSpan.FromSeconds(seconds);
else
throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node);
{
return ReturnOnParseError($"Unable to parse {text} as a time span", out result);
}
}
result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@ -56,7 +74,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a thickness", out result);
}
}
@ -73,7 +91,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a point", out result);
}
}
@ -90,7 +108,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a vector", out result);
}
}
@ -107,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a size", out result);
}
}
@ -124,7 +142,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a matrix", out result);
}
}
@ -141,7 +159,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a corner radius", out result);
}
}
@ -149,7 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (!Color.TryParse(text, out Color color))
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a color", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a color", out result);
}
result = new XamlStaticOrTargetedReturnMethodCallNode(node,
@ -179,7 +197,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a relative point", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a relative point", out result);
}
}
@ -195,7 +213,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
}
}
@ -218,7 +236,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
catch
{
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node);
return ReturnOnParseError($"Unable to parse \"{text}\" as a grid length", out result);
}
}
@ -295,7 +313,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
{
throw new XamlX.XamlLoadException($"Unable to parse text {uriText} as a {kind} uri.", node);
return ReturnOnParseError($"Unable to parse text \"{uriText}\" as a {kind} uri", out result);
}
result = new XamlAstNewClrObjectNode(node
, new(node, types.Uri, false)
@ -341,7 +359,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
else
{
throw new XamlX.XamlLoadException($"Invalid PointsList.", node);
return ReturnOnParseError($"Unable to parse text \"{text}\" as a Points list", out result);
}
}
else
@ -388,6 +406,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
nodes[index] = itemNode;
}
foreach (var element in nodes)
{
if (!elementType.IsAssignableFrom(element.Type.GetClrType()))
{
return ReturnOnParseError($"x:Array element {element.Type.GetClrType().Name} is not assignable to the array element type {elementType.Name}", out result);
}
}
if (types.AvaloniaList.MakeGenericType(elementType).IsAssignableFrom(type))
{
result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, type, elementType, nodes);

43
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs

@ -9,55 +9,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
internal class AstGroupTransformationContext : AstTransformationContext
{
public AstGroupTransformationContext(IReadOnlyCollection<IXamlDocumentResource> documents, TransformerConfiguration configuration, bool strictMode = true)
: base(configuration, null, strictMode)
public AstGroupTransformationContext(
IReadOnlyCollection<IXamlDocumentResource> documents,
TransformerConfiguration configuration)
: base(configuration, null)
{
Documents = documents;
}
public override string Document => CurrentDocument?.FileSource?.FilePath ?? "{unknown document}";
public IXamlDocumentResource CurrentDocument { get; set; }
public IReadOnlyCollection<IXamlDocumentResource> Documents { get; }
public new IXamlAstNode ParseError(string message, IXamlAstNode node) =>
Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node));
public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) =>
Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender));
class Visitor : IXamlAstVisitor
class Visitor : ContextXamlAstVisitor
{
private readonly AstGroupTransformationContext _context;
private readonly IXamlAstGroupTransformer _transformer;
public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer)
public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) : base(context)
{
_context = context;
_transformer = transformer;
}
public IXamlAstNode Visit(IXamlAstNode node)
{
#if Xaml_DEBUG
return _transformer.Transform(_context, node);
#else
try
{
return _transformer.Transform(_context, node);
}
catch (Exception e) when (!(e is XmlException))
{
throw new XamlDocumentParseException(
_context.CurrentDocument?.FileSource?.FilePath,
"Internal compiler error while transforming node " + node + ":\n" + e,
node);
}
#endif
}
public void Push(IXamlAstNode node) => _context.PushParent(node);
public override string GetTransformerInfo() => _transformer.GetType().Name;
public void Pop() => _context.PopParent();
public override IXamlAstNode VisitCore(AstTransformationContext context, IXamlAstNode node) =>
_transformer.Transform((AstGroupTransformationContext)context, node);
}
public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer)

60
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@ -38,8 +38,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
{
throw new XamlDocumentParseException(context.CurrentDocument,
$"Invalid \"{nodeTypeName}\" node initialization.", valueNode);
throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
}
var additionalProperties = new List<IXamlAstManipulationNode>();
@ -56,8 +55,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
}
else
{
throw new XamlDocumentParseException(context.CurrentDocument,
context.ReportTransformError(
$"Source property must be set on the \"{nodeTypeName}\" node.", valueNode);
return node;
}
}
@ -89,22 +89,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
}
return context.ParseError(
context.ReportTransformError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.",
sourceUriNode, node);
sourceUriNode);
return node;
}
// If resource wasn't found in the current assembly, search in the others.
if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
{
return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node);
context.ReportTransformError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode);
return node;
}
var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
if (avaResType is null)
{
return context.ParseError(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node);
context.ReportTransformError(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode);
return node;
}
var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
@ -118,20 +121,22 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly, additionalProperties);
}
return context.ParseError(
context.ReportTransformError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly. Make sure this file exists and is public.",
sourceUriNode, node);
sourceUriNode);
return node;
}
private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li,
private static IXamlAstNode FromType(AstGroupTransformationContext context, IXamlType type, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
IEnumerable<IXamlAstManipulationNode> manipulationNodes)
{
if (!expectedLoadedType.IsAssignableFrom(type))
{
return context.ParseError(
context.ReportTransformError(
$"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
li, fallbackNode);
li);
return fallbackNode;
}
IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
@ -141,15 +146,16 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
return new NewObjectTransformer().Transform(context, newObjNode);
}
private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li,
private static IXamlAstNode FromMethod(AstGroupTransformationContext context, IXamlMethod method, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly,
IEnumerable<IXamlAstManipulationNode> manipulationNodes)
{
if (!expectedLoadedType.IsAssignableFrom(method.ReturnType))
{
return context.ParseError(
context.ReportTransformError(
$"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
li, fallbackNode);
li);
return fallbackNode;
}
var sp = context.Configuration.TypeMappings.ServiceProvider;
@ -164,6 +170,13 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty,
bool strictSourceValueType)
{
void OnInvalidSource(IXamlAstNode node) =>
context.ReportDiagnostic(
AvaloniaXamlDiagnosticCodes.TransformError,
strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
node);
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
@ -172,16 +185,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
// Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
var anyPropValue = sourceProperty.Values.FirstOrDefault();
if (strictSourceValueType)
{
context.Error(anyPropValue,
new XamlDocumentParseException(context.CurrentDocument,
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", anyPropValue));
}
else
{
// TODO: make it a compiler warning
}
OnInvalidSource(anyPropValue);
return (null, anyPropValue);
}
@ -193,9 +197,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
}
else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
{
context.Error(sourceUriNode,
new XamlDocumentParseException(context.CurrentDocument,
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", sourceUriNode));
OnInvalidSource(sourceUriNode);
return (null, sourceUriNode);
}

16
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
using XamlX.TypeSystem;
@ -22,6 +23,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude;
var mergeSourceNodes = new List<XamlPropertyAssignmentNode>();
var shouldExit = false; // if any manipulation node has an error, we should stop processing further.
foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray())
{
void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode)
@ -39,14 +41,16 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
}
else
{
throw new XamlDocumentParseException(context.CurrentDocument,
shouldExit = true;
context.ReportTransformError(
"Invalid MergeResourceInclude node found. Make sure that Source property is set.",
valueNode);
}
}
else if (mergeSourceNodes.Any())
{
throw new XamlDocumentParseException(context.CurrentDocument,
shouldExit = true;
context.ReportTransformError(
"MergeResourceInclude should always be included last when mixing with other dictionaries inside of the ResourceDictionary.MergedDictionaries.",
valueNode);
}
@ -66,7 +70,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
}
}
if (!mergeSourceNodes.Any())
if (shouldExit || !mergeSourceNodes.Any())
{
return node;
}
@ -78,7 +82,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true);
if (originalAssetPath is null)
{
return context.ParseError(
return context.ReportTransformError(
$"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
}
@ -86,7 +90,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase));
if (targetDocument?.XamlDocument.Root is not XamlValueWithManipulationNode targetDocumentRoot)
{
return context.ParseError(
return context.ReportTransformError(
$"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node);
}
@ -94,7 +98,7 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
.Children.OfType<XamlObjectInitializationNode>().Single();
if (singleRootObject.Type != resourceDictionaryType)
{
return context.ParseError(
return context.ReportTransformError(
"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node);
}

11
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs

@ -1,3 +1,4 @@
using System;
using System.Linq;
using XamlX;
using XamlX.Ast;
@ -8,6 +9,14 @@ using XamlParseException = XamlX.XamlParseException;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XamlBindingsTransformException : XamlTransformException
{
public XamlBindingsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
: base(message, lineInfo, innerException)
{
}
}
class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
{
public bool CompileBindingsByDefault { get; set; }
@ -32,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!(directive.Values[0] is XamlAstTextNode text
&& bool.TryParse(text.Text, out var compileBindings)))
{
throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
throw new XamlBindingsTransformException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
}
obj.Children.Remove(directive);

24
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@ -104,12 +104,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else if (elementNameProperty != null)
{
throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
throw new XamlBindingsTransformException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
}
if (sourceProperty != null && convertedNode != null)
{
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
if (GetRelativeSourceObjectFromAssignment(
@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (convertedNode != null)
{
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
throw new XamlBindingsTransformException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
var modeProperty = relativeSourceObject.Children
@ -159,14 +159,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
true).GetClrType(),
XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
null => null,
_ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
_ => throw new XamlBindingsTransformException($"Unsupported node for AncestorType property", relativeSourceObject)
};
if (ancestorType is null)
{
if (treeType == "Visual")
{
throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
throw new XamlBindingsTransformException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
}
else if (treeType == "Logical")
{
@ -180,7 +180,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (ancestorType is null)
{
throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
throw new XamlBindingsTransformException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
}
}
}
@ -203,7 +203,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
throw new XamlBindingsTransformException($"Unknown tree type '{treeType}'.", binding);
}
}
else if (mode == "DataContext")
@ -221,20 +221,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
if (contentTemplateNode is null)
{
throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
throw new XamlBindingsTransformException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
}
var parentType = contentTemplateNode.TargetType.GetClrType();
if (parentType is null)
{
throw new XamlParseException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
throw new XamlBindingsTransformException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
}
convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
}
else
{
throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
throw new XamlBindingsTransformException($"Unknown RelativeSource mode '{mode}'.", binding);
}
}
@ -265,7 +265,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
}
relativeSourceObject = (XamlAstObjectNode)me.Value;
@ -276,7 +276,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
{
throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
throw new XamlBindingsTransformException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
}
relativeSourceObject = on;

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -116,7 +116,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
throw new XamlBindingsTransformException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
}
return parentDataContextNode.DataContextType;

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs

@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var targetTypeNode = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
throw new XamlParseException("ControlTheme must have a TargetType.", node);
throw new XamlTransformException("ControlTheme must have a TargetType.", node);
IXamlType targetType;
@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (targetTypeNode.Values[0] is XamlAstTextNode text)
targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
else
throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode);
throw new XamlTransformException("Could not determine TargetType for ControlTheme.", targetTypeNode);
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(targetTypeNode, targetType, false),

22
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -10,6 +10,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XamlDataContextException : XamlTransformException
{
public XamlDataContextException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
: base(message, lineInfo, innerException)
{
}
}
class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -43,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else
{
throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
throw new XamlDataContextException("x:DataType should be set to a type name.", directive.Values[0]);
}
}
}
@ -136,7 +144,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentItemsDataContext != null)
{
itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, () => parentItemsDataContext.DataContextType, parentObject.Type.GetClrType());
itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context,
parentItemsBinding, () => parentItemsDataContext.DataContextType,
parentObject.Type.GetClrType());
}
}
}
@ -179,13 +189,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
throw new XamlDataContextException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
}
return parentDataContextNode.DataContextType;
};
var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver, on.Type.GetClrType());
var bindingResultType =
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver,
on.Type.GetClrType());
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
}
@ -222,6 +234,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
}
public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element. Please set x:DataType on the Binding or parent.", Value);
public override IXamlType DataContextType => XamlPseudoType.Unknown;
}
}

5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs

@ -36,7 +36,10 @@ class AvaloniaXamlIlDuplicateSettersChecker : IXamlAstTransformer
{
if (!index.Add(property))
{
throw new XamlParseException($"Duplicate setter encountered for property '{property}'", node);
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.DuplicateSetterError,
XamlDiagnosticSeverity.Warning,
$"Duplicate setter encountered for property '{property}'", node));
}
}

16
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs

@ -38,7 +38,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
{
if (objectNode.Arguments.Count > 1)
{
throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode);
throw new XamlTransformException("Options MarkupExtensions allow only single argument", objectNode);
}
defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
@ -68,20 +68,20 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
?? Array.Empty<string>();
if (options.Length == 0)
{
throw new XamlParseException("On.Options string must be set", onObj);
throw new XamlTransformException("On.Options string must be set", onObj);
}
var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
if (content is null)
{
throw new XamlParseException("On content object must be set", onObj);
throw new XamlTransformException("On content object must be set", onObj);
}
var propertiesSet = options
.Select(o => type.GetAllProperties()
.FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
?? throw new XamlTransformException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
.ToArray();
foreach (var propertySet in propertiesSet)
{
@ -102,7 +102,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
if (defaultValue is null && !values.Any())
{
throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode);
throw new XamlTransformException("Options markup extension requires at least one option to be set", objectNode);
}
return new OptionsMarkupExtensionNode(
@ -125,7 +125,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
var option = optAttr.Parameters.Single();
if (option is null)
{
throw new XamlParseException("MarkupExtension option must not be null", li);
throw new XamlTransformException("MarkupExtension option must not be null", li);
}
var optionAsString = option.ToString();
@ -173,7 +173,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
}
}
throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
throw new XamlTransformException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
}
return false;
@ -198,7 +198,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor
if (values.Count > 1)
{
throw new XamlParseException("Options markup extension supports only a singular value", line);
throw new XamlTransformException("Options markup extension supports only a singular value", line);
}
return values.Single();

28
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs

@ -12,6 +12,14 @@ using XamlX.IL;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XamlPropertyPathException : XamlTransformException
{
public XamlPropertyPathException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
: base(message, lineInfo, innerException)
{
}
}
class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -26,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault();
if(parentScope == null)
throw new XamlX.XamlParseException("No target type scope found for property path", text);
throw new XamlPropertyPathException("No target type scope found for property path", text);
if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
throw new XamlPropertyPathException("PropertyPath is currently only valid for styles", pv);
IEnumerable<PropertyPathGrammar.ISyntax> parsed;
@ -38,7 +46,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
throw new XamlPropertyPathException("Unable to parse PropertyPath: " + e.Message, text, innerException: e);
}
var elements = new List<IXamlIlPropertyPathElementNode>();
@ -59,7 +67,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
void HandleProperty(string name, string typeNamespace, string typeName)
{
if(!expectProperty || currentType == null)
throw new XamlX.XamlParseException("Unexpected property node", text);
throw new XamlPropertyPathException("Unexpected property node", text);
var propertySearchType =
typeName != null ? GetType(typeNamespace, typeName) : currentType;
@ -80,7 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
if (prop == null)
throw new XamlX.XamlParseException(
throw new XamlPropertyPathException(
$"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
text);
@ -95,7 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (ge is PropertyPathGrammar.ChildTraversalSyntax)
{
if (!expectTraversal)
throw new XamlX.XamlParseException("Unexpected child traversal .", text);
throw new XamlPropertyPathException("Unexpected child traversal .", text);
elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
expectTraversal = expectCast = false;
expectProperty = true;
@ -103,7 +111,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
{
if(!expectCast)
throw new XamlX.XamlParseException("Unexpected cast node", text);
throw new XamlPropertyPathException("Unexpected cast node", text);
currentType = GetType(ets.TypeNamespace, ets.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
expectProperty = false;
@ -112,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
{
if(!expectCast)
throw new XamlX.XamlParseException("Unexpected cast node", text);
throw new XamlPropertyPathException("Unexpected cast node", text);
//TODO: Check if cast can be done
currentType = GetType(cts.TypeNamespace, cts.TypeName);
elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
@ -128,12 +136,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
}
else
throw new XamlX.XamlParseException("Unexpected node " + ge, text);
throw new XamlPropertyPathException("Unexpected node " + ge, text);
}
var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
if (propertyPathNode.Type == null)
throw new XamlX.XamlParseException("Unexpected end of the property path", text);
throw new XamlPropertyPathException("Unexpected end of the property path", text);
pv.Values[0] = propertyPathNode;
}

42
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -13,8 +13,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
internal class XamlSelectorsTransformException : XamlTransformException
{
public XamlSelectorsTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
: base(message, lineInfo, innerException)
{
}
}
class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -30,14 +36,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
if (pn.Values.Count != 1)
throw new XamlParseException("Selector property should should have exactly one value", node);
throw new XamlSelectorsTransformException("Selector property should should have exactly one value",
node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("Selector property should be a text node", node);
throw new XamlSelectorsTransformException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
@ -69,18 +76,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var type = result?.TargetType;
if (type == null)
throw new XamlParseException("Property selectors must be applied to a type.", node);
throw new XamlTransformException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
throw new XamlTransformException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlParseException(
throw new XamlTransformException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
@ -92,13 +99,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var targetType = result?.TargetType;
if (targetType == null)
{
throw new XamlParseException("Attached Property selectors must be applied to a type.",node);
throw new XamlTransformException("Attached Property selectors must be applied to a type.",node);
}
var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type;
if (attachedPropertyOwnerType is null)
{
throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
throw new XamlTransformException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node);
}
var attachedPropertyName = attachedProperty.Property + "Property";
@ -112,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (targetPropertyField is null)
{
throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
throw new XamlTransformException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node);
}
var targetPropertyType = XamlIlAvaloniaPropertyHelper
@ -121,7 +128,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
targetPropertyType, out var typedValue))
throw new XamlParseException(
throw new XamlTransformException(
$"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",
node);
@ -156,12 +163,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentTargetType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (parentTargetType is null)
throw new XamlParseException($"Cannot find parent style for nested selector.", node);
throw new XamlTransformException($"Cannot find parent style for nested selector.", node);
result = new XamlIlNestingSelector(result, parentTargetType.TargetType.GetClrType());
break;
default:
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
throw new XamlTransformException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
@ -180,11 +187,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
throw new XamlParseException("Unable to parse selector: " + e.Message, node);
throw new XamlSelectorsTransformException("Unable to parse selector: " + e.Message, node, e);
}
var selector = Create(parsed, (p, n)
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true)
?? new XamlAstClrTypeReference(node, XamlPseudoType.Unknown, false));
pn.Values[0] = selector;
var templateType = GetLastTemplateTypeFromSelector(selector);
@ -402,7 +410,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlLoadException(
throw new XamlX.XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@ -486,7 +494,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
throw new XamlLoadException("Invalid selector count", this);
throw new XamlX.XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs

@ -25,7 +25,7 @@ internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTrans
if (type is null)
{
throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
throw new XamlTransformException("Unable to resolve SetterTargetType type", typeDirective);
}
return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}

21
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using XamlX;
@ -9,6 +10,14 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XamlStyleTransformException : XamlTransformException
{
public XamlStyleTransformException(string message, IXamlLineInfo lineInfo, Exception innerException = null)
: base(message, lineInfo, innerException)
{
}
}
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@ -27,13 +36,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (styleParent != null)
{
targetType = styleParent.TargetType.GetClrType()
?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
?? throw new XamlStyleTransformException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
lineInfo = on;
}
if (targetType == null)
{
throw new XamlParseException("Could not determine target type of Setter", node);
throw new XamlStyleTransformException("Could not determine target type of Setter", node);
}
IXamlType propType = null;
@ -43,7 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node);
throw new XamlStyleTransformException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
@ -55,12 +64,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var propertyPath = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
if (propertyPath == null)
throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
throw new XamlStyleTransformException("Setter without a property or property path is not valid", node);
if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
&& ppn.PropertyType != null)
propType = ppn.PropertyType;
else
throw new XamlX.XamlParseException("Unable to get the property path property type", node);
throw new XamlStyleTransformException("Unable to get the property path property type", node);
}
var valueProperty = on.Children
@ -69,7 +78,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
throw new XamlParseException(
throw new XamlStyleTransformException(
$"Unable to convert property value to {propType.GetFqn()}",
valueProperty.Values[0]);

34
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs

@ -0,0 +1,34 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlStyleValidatorTransformer : IXamlAstTransformer
{
// See https://github.com/AvaloniaUI/Avalonia/issues/7461
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlAstObjectNode on
&& context.GetAvaloniaTypes().IStyle.IsAssignableFrom(on.Type.GetClrType())))
return node;
if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode propertyValueNode
&& propertyValueNode.Property.GetClrProperty() is { } clrProperty
&& clrProperty.Name == "MergedDictionaries"
&& clrProperty.DeclaringType == context.GetAvaloniaTypes().ResourceDictionary)
{
var nodeName = on.Type.GetClrType().Name;
context.ReportDiagnostic(new XamlDiagnostic(
AvaloniaXamlDiagnosticCodes.StyleInMergedDictionaries,
XamlDiagnosticSeverity.Warning,
// Keep it single line, as MSBuild splits multiline warnings into two warnings.
$"Including {nodeName} as part of MergedDictionaries will ignore any nested styles." +
$"Instead, you can add {nodeName} to the Styles collection on the same control or application.",
node));
}
return node;
}
}

26
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs

@ -1,26 +0,0 @@
using XamlX;
using XamlX.Ast;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
internal class XamlDocumentParseException : XamlParseException
{
public string FilePath { get; }
public XamlDocumentParseException(string path, XamlParseException parseException)
: base(parseException.Message, parseException.LineNumber, parseException.LinePosition)
{
FilePath = path;
}
public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo)
: base(message, lineInfo.Line, lineInfo.Position)
{
FilePath = path;
}
public XamlDocumentParseException(IXamlDocumentResource document, string message, IXamlLineInfo lineInfo)
: this(document.FileSource?.FilePath, message, lineInfo)
{
}
}

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -83,7 +83,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
throw new XamlX.XamlParseException(
throw new XamlX.XamlTransformException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlX.XamlParseException(
throw new XamlX.XamlTransformException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);

27
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -87,8 +87,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return transformed;
}
var lastElement =
transformed.Elements[transformed.Elements.Count - 1];
var lastElement = transformed.Elements.LastOrDefault();
if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
{
@ -158,7 +157,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
break;
}
throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
throw new XamlX.XamlTransformException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
}
case BindingExpressionGrammar.PropertyNameNode propName:
{
@ -182,7 +181,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
else
{
throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
throw new XamlX.XamlTransformException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
break;
}
@ -207,7 +206,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
if (property is null)
{
throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
throw new XamlX.XamlTransformException($"The type '${targetType}' does not have an indexer.", lineInfo);
}
IEnumerable<IXamlType> parameters = property.IndexerParameters;
@ -219,7 +218,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
param, out var converted))
throw new XamlX.XamlParseException(
throw new XamlX.XamlTransformException(
$"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
textNode);
@ -267,7 +266,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (ancestorType is null)
{
throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
}
nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
@ -294,7 +293,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (elementType is null)
{
throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
throw new XamlX.XamlTransformException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
}
nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
break;
@ -306,7 +305,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (castType is null)
{
throw new XamlX.XamlParseException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
throw new XamlX.XamlTransformException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
}
nodes.Add(new TypeCastPathElementNode(castType));
@ -785,7 +784,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
if (!int.TryParse(item, out var index))
{
throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
throw new XamlX.XamlTransformException($"Unable to convert '{item}' to an integer.", lineInfo);
}
_values.Add(index);
}
@ -866,10 +865,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Elements = elements;
}
public IXamlType BindingResultType
=> _transformElements.Count > 0
? _transformElements[0].Type
: Elements[Elements.Count - 1].Type;
public IXamlType BindingResultType =>
_transformElements.FirstOrDefault()?.Type
?? Elements.LastOrDefault()?.Type
?? XamlPseudoType.Unknown;
public IXamlAstTypeReference Type { get; }

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit aa2223dec1e7c70679fdb73f9d364363a0285adb
Subproject commit b7ed273273949a5dd9f01e682ab97f61b43697ad

47
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@ -1,3 +1,4 @@
using System;
using System.Reflection;
namespace Avalonia.Markup.Xaml;
@ -20,4 +21,50 @@ public class RuntimeXamlLoaderConfiguration
/// Default is 'false'.
/// </summary>
public bool DesignMode { get; set; } = false;
/// <summary>
/// XAML diagnostics handler.
/// </summary>
/// <returns>
/// Defines if any diagnostic severity should be overriden.
/// Note, severity cannot be set lower than minimal for specific diagnostic.
/// </returns>
public XamlDiagnosticFunc? DiagnosticHandler { get; set; }
/// <summary>
/// Delegate for <see cref="RuntimeXamlLoaderConfiguration.DiagnosticHandler"/> property.
/// </summary>
public delegate RuntimeXamlDiagnosticSeverity XamlDiagnosticFunc(RuntimeXamlDiagnostic diagnostic);
}
public enum RuntimeXamlDiagnosticSeverity
{
None = 0,
/// <summary>
/// Diagnostic is reported as a warning.
/// </summary>
Warning = 1,
/// <summary>
/// Diagnostic is reported as an error.
/// Compilation process is continued until the end of the parsing and transforming stage, throwing an aggregated exception of all errors.
/// </summary>
Error,
/// <summary>
/// Diagnostic is reported as an fatal error.
/// Compilation process is stopped right after this error.
/// </summary>
Fatal
}
public record RuntimeXamlDiagnostic(
string Id,
RuntimeXamlDiagnosticSeverity Severity,
string Title,
int? LineNumber,
int? LinePosition)
{
public string? Document { get; init; }
}

1
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -22,6 +22,7 @@
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" />
<Compile Include="..\..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\..\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="TrimmingAttributes.cs" Visible="False" />
<Compile Include="..\..\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.22045.20230930" />

8
src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using XamlX;
using XamlX.Compiler;
using XamlX.Emit;
using XamlX.Transform;
@ -19,10 +20,13 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
foreach (var additionalType in additionalTypes)
mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
var diagnosticsHandler = new XamlDiagnosticsHandler();
var configuration = new TransformerConfiguration(
typeSystem,
typeSystem.Assemblies.First(),
mappings);
mappings,
diagnosticsHandler: diagnosticsHandler);
return new MiniCompiler(configuration);
}
@ -47,4 +51,4 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
XamlRuntimeContext<object, IXamlEmitResult> context,
bool needContextLocal) =>
throw new NotSupportedException();
}
}

13
tests/Avalonia.Generators.Tests/MiniCompilerTests.cs

@ -14,7 +14,6 @@ public class MiniCompilerTests
{
private const string AvaloniaXaml = "<TextBlock xmlns='clr-namespace:Avalonia.Controls;assembly=Avalonia' />";
private const string MiniClass = "namespace Example { public class Valid { public int Foo() => 21; } }";
private const string MiniInvalidXaml = "<Invalid xmlns='clr-namespace:Example;assembly=Example' />";
private const string MiniValidXaml = "<Valid xmlns='clr-namespace:Example;assembly=Example' />";
[Fact]
@ -27,16 +26,6 @@ public class MiniCompilerTests
Assert.NotNull(xaml.Root);
}
[Fact]
public void Should_Throw_When_Unable_To_Resolve_Types_From_Simple_Invalid_Markup()
{
var xaml = XDocumentXamlParser.Parse(MiniInvalidXaml);
var compilation = CreateBasicCompilation(MiniClass);
var compiler = MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation));
Assert.Throws<XamlParseException>(() => compiler.Transform(xaml));
}
[Fact]
public void Should_Resolve_Types_From_Simple_Avalonia_Markup()
{
@ -56,4 +45,4 @@ public class MiniCompilerTests
.AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location))
.AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
}
}

5
tests/Avalonia.Generators.Tests/Views/xNamedControls.xml

@ -8,7 +8,8 @@
<TextBox x:Name="PasswordTextBox"
Watermark="Password input"
UseFloatingWatermark="True" />
<!-- Name generator should still generate members, even if XAML is invalid -->
<Button x:Name="SignUpButton"
Content="Sign up" />
Content="{x:Static NonExistent.Member}" />
</StackPanel>
</Window>
</Window>

53
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -8,6 +8,7 @@ using System.Linq;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Xml;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
@ -496,7 +497,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</Window.DataTemplates>
<ContentControl Name='target' Content='{CompiledBinding}' />
</Window>";
ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@ -514,7 +515,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
</ContentControl>
</Window>";
ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
[Fact]
public void ReportsMultipleErrorsOnDataContextAndBindingPathErrors()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<ContentControl Content='{CompiledBinding NoDataContext}'
Tag='{CompiledBinding NonExistentProp, DataType=local:TestDataContext}'
Height='{CompiledBinding invalid.}' />
</Window>";
var ex = Assert.Throws<AggregateException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.Collection(
ex.InnerExceptions,
inner => Assert.IsAssignableFrom<XmlException>(inner),
inner => Assert.IsAssignableFrom<XmlException>(inner),
inner => Assert.IsAssignableFrom<XmlException>(inner));
}
}
@ -663,7 +686,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</ItemsControl.DataTemplates>
</ItemsControl>
</Window>";
ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@ -1148,7 +1171,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:CompileBindings='true'>
<TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
</Window>";
ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@ -1227,7 +1250,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
x:DataType='local:TestDataContext'
x:CompileBindings='notabool'>
</Window>";
ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
@ -1843,25 +1866,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
, textBlock.GetValue(TextBlock.TextProperty));
}
}
static void Throws(string type, Action cb)
{
try
{
cb();
}
catch (Exception e) when (e.GetType().Name == type)
{
return;
}
throw new Exception("Expected " + type);
}
static void ThrowsXamlParseException(Action cb) => Throws("XamlParseException", cb);
static void ThrowsXamlTransformException(Action cb) => Throws("XamlTransformException", cb);
static void PerformClick(Button button)
{
button.RaiseEvent(new KeyEventArgs

134
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AvaloniaIntrinsicsTests.cs

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Styling;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
public class AvaloniaIntrinsicsTests : XamlTestBase
{
[Fact]
public void All_Intrinsics_Are_Parsed_And_Set()
{
var xaml = @"<local:TestIntrinsicsControl
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
TimeSpanProperty='00:10:10'
ThicknessProperty='1 1 1 1'
PointProperty='15, 15'
VectorProperty='16.6, 16.6'
SizeProperty='20, 20'
MatrixProperty='1 0 0 1 0 0'
CornerRadiusProperty='4'
ColorProperty='#44ff11'
RelativePointProperty='50%, 50%'
GridLengthProperty='10*'
IBrushProperty='#44ff11'
TextTrimmingProperty='CharacterEllipsis'
TextDecorationCollectionProperty='Strikethrough'
WindowTransparencyLevelProperty='AcrylicBlur'
UriProperty='https://avaloniaui.net/'
ThemeVariantProperty='Dark'
PointsProperty='1, 1, 2, 2' />";
var target = AvaloniaRuntimeXamlLoader.Parse<TestIntrinsicsControl>(xaml);
Assert.NotNull(target);
Assert.Equal(new TimeSpan(0, 10, 10), target.TimeSpanProperty);
Assert.Equal(new Thickness(1), target.ThicknessProperty);
Assert.Equal(new Thickness(1), target.ThicknessProperty);
Assert.Equal(new Point(15, 15), target.PointProperty);
Assert.Equal(new Vector(16.6, 16.6), target.VectorProperty);
Assert.Equal(new Size(20, 20), target.SizeProperty);
Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), target.MatrixProperty);
Assert.Equal(new CornerRadius(4), target.CornerRadiusProperty);
Assert.Equal(Color.Parse("#44ff11"), target.ColorProperty);
Assert.Equal(new RelativePoint(0.5, 0.5, RelativeUnit.Relative), target.RelativePointProperty);
Assert.Equal(new GridLength(10, GridUnitType.Star), target.GridLengthProperty);
Assert.Equal(new ImmutableSolidColorBrush(Color.Parse("#44ff11")), target.IBrushProperty);
Assert.Equal(TextTrimming.CharacterEllipsis, target.TextTrimmingProperty);
Assert.Equal(TextDecorations.Strikethrough, target.TextDecorationCollectionProperty);
Assert.Equal(WindowTransparencyLevel.AcrylicBlur, target.WindowTransparencyLevelProperty);
Assert.Equal(new Uri("https://avaloniaui.net/"), target.UriProperty);
Assert.Equal(ThemeVariant.Dark, target.ThemeVariantProperty);
Assert.Equal(new[] { new Point(1, 1), new Point(2, 2) }, target.PointsProperty);
}
[Fact]
public void All_Intrinsics_Report_Errors_If_Failed()
{
var xaml = @"<local:TestIntrinsicsControl
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'
TimeSpanProperty='00:00:10,1'
ThicknessProperty='1 1 1'
PointProperty='15% 15%'
VectorProperty='16.6. 16.6'
SizeProperty='20%, 20%'
MatrixProperty='1 0 1 0 0'
CornerRadiusProperty='4 1 4'
ColorProperty='#44ff1'
RelativePointProperty='50, 50%'
GridLengthProperty='10%'
PointsProperty='1, 1, 2' />";
// TODO: double check why we don't throw error on other supported types. Should it be warnings?
var diagnostics = new List<RuntimeXamlDiagnostic>();
Assert.Throws<AggregateException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
new RuntimeXamlLoaderConfiguration
{
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
}));
Assert.Collection(
diagnostics,
d => AssertDiagnostic(d, "time span"),
d => AssertDiagnostic(d, "thickness"),
d => AssertDiagnostic(d, "point"),
d => AssertDiagnostic(d, "vector"),
d => AssertDiagnostic(d, "size"),
d => AssertDiagnostic(d, "matrix"),
d => AssertDiagnostic(d, "corner radius"),
d => AssertDiagnostic(d, "color"),
d => AssertDiagnostic(d, "relative point"),
d => AssertDiagnostic(d, "grid length"),
d => AssertDiagnostic(d, "points list"),
// Compiler attempts to parse PointsList twice - as a list and as a point.
d => AssertDiagnostic(d, "point"));
void AssertDiagnostic(RuntimeXamlDiagnostic runtimeXamlDiagnostic, string contains)
{
Assert.Equal(RuntimeXamlDiagnosticSeverity.Error, runtimeXamlDiagnostic.Severity);
Assert.Contains(contains, runtimeXamlDiagnostic.Title, StringComparison.OrdinalIgnoreCase);
}
}
}
public class TestIntrinsicsControl : Control
{
public TimeSpan TimeSpanProperty { get; set; }
// public FontFamily FontFamilyProperty { get; set; }
public Thickness ThicknessProperty { get; set; }
public Point PointProperty { get; set; }
public Vector VectorProperty { get; set; }
public Size SizeProperty { get; set; }
public Matrix MatrixProperty { get; set; }
public CornerRadius CornerRadiusProperty { get; set; }
public Color ColorProperty { get; set; }
public RelativePoint RelativePointProperty { get; set; }
public GridLength GridLengthProperty { get; set; }
public IBrush IBrushProperty { get; set; }
public TextTrimming TextTrimmingProperty { get; set; }
public TextDecorationCollection TextDecorationCollectionProperty { get; set; }
public WindowTransparencyLevel WindowTransparencyLevelProperty { get; set; }
public Uri UriProperty { get; set; }
public ThemeVariant ThemeVariantProperty { get; set; }
public Points PointsProperty { get; set; }
}

28
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@ -277,6 +277,34 @@ public class StyleIncludeTests
Assert.IsType<SimpleTheme>(control.Styles[0]);
Assert.IsType<SimpleTheme>(control.Styles[1]);
}
[Fact]
public void Style_Inside_Resources_Should_Produce_Warning()
{
var diagnostics = new List<RuntimeXamlDiagnostic>();
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:themes='clr-namespace:Avalonia.Themes.Simple;assembly=Avalonia.Themes.Simple'>
<ContentControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<themes:SimpleTheme />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>"), new RuntimeXamlLoaderConfiguration
{
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
});
Assert.IsAssignableFrom<IStyle>(((ResourceDictionary)control.Resources)!.MergedDictionaries[0]);
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
}
[Fact]
public void StyleInclude_From_CodeBehind_Resolves_Compiled()

24
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -631,5 +631,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
[Fact]
public void Multiple_Errors_Are_Reported()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='5' />
<Style Selector='NonExistentType' />
<Style Selector='Border:normal' />
<Style Selector='Border+invalid' />
</Window.Styles>
</Window>";
var ex = Assert.Throws<AggregateException>(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.Collection(
ex.InnerExceptions,
inner => Assert.IsAssignableFrom<XmlException>(inner),
inner => Assert.IsAssignableFrom<XmlException>(inner),
inner => Assert.IsAssignableFrom<XmlException>(inner));
}
}
}
}

36
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -327,6 +327,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
[Fact]
public void Style_Parser_Throws_For_Duplicate_Setter()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@ -340,13 +342,27 @@ namespace Avalonia.Markup.Xaml.UnitTests
</Window.Styles>
<TextBlock/>
</Window>";
AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
var diagnostics = new List<RuntimeXamlDiagnostic>();
// We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
Assert.Throws<InvalidOperationException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = typeof(XamlIlTests).Assembly,
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
}));
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
}
[Fact]
public void Control_Theme_Parser_Throws_For_Duplicate_Setter()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
@ -361,8 +377,20 @@ namespace Avalonia.Markup.Xaml.UnitTests
<u:TestTemplatedControl Theme='{StaticResource MyTheme}'/>
</Window>";
AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true),
e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'"));
var diagnostics = new List<RuntimeXamlDiagnostic>();
// We still have a runtime check in the StyleInstance class, but in this test we only care about compile warnings.
Assert.Throws<InvalidOperationException>(() => AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = typeof(XamlIlTests).Assembly,
DiagnosticHandler = diagnostic =>
{
diagnostics.Add(diagnostic);
return diagnostic.Severity;
}
}));
var warning = Assert.Single(diagnostics);
Assert.Equal(RuntimeXamlDiagnosticSeverity.Warning, warning.Severity);
Assert.StartsWith("Duplicate setter encountered for property 'Height'", warning.Title);
}
}

1
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlTestHelpers.cs

@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
if(e is XmlException)
return;
throw new Exception("Expected to throw xaml exception", e);
}
throw new Exception("Expected to throw xaml exception");
}

Loading…
Cancel
Save