diff --git a/.editorconfig b/.editorconfig index 62a533e468..d5b2badfd5 100644 --- a/.editorconfig +++ b/.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}] diff --git a/build/BuildTargets.targets b/build/BuildTargets.targets index 481dbf06b2..13b002d523 100644 --- a/build/BuildTargets.targets +++ b/build/BuildTargets.targets @@ -4,6 +4,7 @@ true true true + true diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index ee9d8c6dfe..2232ea35af 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -130,6 +130,7 @@ $(IntermediateOutputPath)/Avalonia/original.dll false false + false + DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)" + VerboseExceptions="$(AvaloniaXamlVerboseExceptions)" + AnalyzerConfigFiles="@(EditorConfigFiles)"> - - - + DisplayName="Debug XAML Compiler" + Description="Allow debug XAML compilation" + Category="Debug" /> + + + + diff --git a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs deleted file mode 100644 index a31c9a7516..0000000000 --- a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs +++ /dev/null @@ -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 - } -} diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index 3bd1ce5ce7..5287827652 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/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(); } diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs index b0b7e0cc3d..557e1e1fbe 100644 --- a/src/Avalonia.Build.Tasks/Extensions.cs +++ b/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); } } } diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 86c04132a0..6aa815a28b 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/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; } diff --git a/src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs b/src/Avalonia.Build.Tasks/XamlCompilerDiagnosticsFilter.cs new file mode 100644 index 0000000000..4314c7b724 --- /dev/null +++ b/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> _lazyEditorConfig; + + public XamlCompilerDiagnosticsFilter( + ITaskItem[]? analyzerConfigFiles) + { + _lazyEditorConfig = new Lazy>(() => 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 ParseEditorConfigFiles(ITaskItem[]? analyzerConfigFiles) + { + // Very naive EditorConfig parser, supporting minimal properties set via regex: + var severities = new Dictionary(); + 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; + } +} diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 3f68092b56..4a5b8b7ab2 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/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(); + 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 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; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index 939efc60d1..bb18dd5ee3 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index f3a5a9415c..480c74223f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/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(); + 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); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs index 339b720d10..80e1807ac3 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlArrayConstantAstNode.cs +++ b/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; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlDiagnosticCodes.cs new file mode 100644 index 0000000000..ddc5e1c2de --- /dev/null +++ b/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 + }; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 88a0039dba..723675f975 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/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 GroupTransformers { get; } - public void TransformGroup(IReadOnlyCollection documents, bool strict = true) + public void TransformGroup(IReadOnlyCollection 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) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs index 9fc6b5d517..d5b760aa84 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs +++ b/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; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index f9a121443e..b11086e74d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/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); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs index eeb5293325..37b1952c49 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs +++ b/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 documents, TransformerConfiguration configuration, bool strictMode = true) - : base(configuration, null, strictMode) + public AstGroupTransformationContext( + IReadOnlyCollection documents, + TransformerConfiguration configuration) + : base(configuration, null) { Documents = documents; } + public override string Document => CurrentDocument?.FileSource?.FilePath ?? "{unknown document}"; public IXamlDocumentResource CurrentDocument { get; set; } public IReadOnlyCollection 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) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index 28a48e5855..fe96b43d2f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/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(); @@ -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 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 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().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); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs index f135ed5ffb..61a12aa72d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs +++ b/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(); + 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().Single(); if (singleRootObject.Type != resourceDictionaryType) { - return context.ParseError( + return context.ReportTransformError( "MergeResourceInclude can only include another ResourceDictionary", propertyNode, node); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs index de2c0eab96..e530b39a65 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs +++ b/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); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs index 736e764aa7..b62066e916 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs +++ b/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; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs index 3edc458fd2..fa697972e9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs +++ b/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().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; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs index 51fe58d1c9..09b6779fa3 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs +++ b/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() .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), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index a24d4eb6e9..488fcef46c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/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().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().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; } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs index 4ab9594cd8..c019ca6853 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs +++ b/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)); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs index 13de455b96..06db71f3eb 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs +++ b/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(); 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() .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(); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs index 44b469c34e..067e01e671 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs +++ b/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() .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 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(); @@ -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; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index edddc5424a..5292aadab2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/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().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 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 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); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs index ebc6c01ba8..07a10f0875 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs +++ b/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); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 5a6fd8246d..8b5a893884 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/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().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() .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]); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlStyleValidatorTransformer.cs new file mode 100644 index 0000000000..87528649e6 --- /dev/null +++ b/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; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs deleted file mode 100644 index 0532287a67..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs +++ /dev/null @@ -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) - { - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 264361e743..5ae8a8f182 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/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, make sure to use typed properties", lineInfo); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 37a028d7b1..965d6cb98a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/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 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; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index aa2223dec1..b7ed273273 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit aa2223dec1e7c70679fdb73f9d364363a0285adb +Subproject commit b7ed273273949a5dd9f01e682ab97f61b43697ad diff --git a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs index 1cd48f87d7..90e607ddcc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs +++ b/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'. /// public bool DesignMode { get; set; } = false; + + /// + /// XAML diagnostics handler. + /// + /// + /// Defines if any diagnostic severity should be overriden. + /// Note, severity cannot be set lower than minimal for specific diagnostic. + /// + public XamlDiagnosticFunc? DiagnosticHandler { get; set; } + + /// + /// Delegate for property. + /// + public delegate RuntimeXamlDiagnosticSeverity XamlDiagnosticFunc(RuntimeXamlDiagnostic diagnostic); +} + +public enum RuntimeXamlDiagnosticSeverity +{ + None = 0, + + /// + /// Diagnostic is reported as a warning. + /// + Warning = 1, + + /// + /// 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. + /// + Error, + + /// + /// Diagnostic is reported as an fatal error. + /// Compilation process is stopped right after this error. + /// + Fatal +} + +public record RuntimeXamlDiagnostic( + string Id, + RuntimeXamlDiagnosticSeverity Severity, + string Title, + int? LineNumber, + int? LinePosition) +{ + public string? Document { get; init; } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index c8608451e5..ead954d641 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -22,6 +22,7 @@ + diff --git a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs index 71f34d173c..b9fbdb537d 100644 --- a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs +++ b/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 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 XamlRuntimeContext context, bool needContextLocal) => throw new NotSupportedException(); -} \ No newline at end of file +} diff --git a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs b/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs index 26ed20f982..a54fc7bf12 100644 --- a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs +++ b/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs @@ -14,7 +14,6 @@ public class MiniCompilerTests { private const string AvaloniaXaml = ""; private const string MiniClass = "namespace Example { public class Valid { public int Foo() => 21; } }"; - private const string MiniInvalidXaml = ""; private const string MiniValidXaml = ""; [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(() => 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)); -} \ No newline at end of file +} diff --git a/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml b/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml index 5e9d083be3..00eec9b969 100644 --- a/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml +++ b/tests/Avalonia.Generators.Tests/Views/xNamedControls.xml @@ -8,7 +8,8 @@ +