From 509ca5080c20833ad18e627013d6452cb2233bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Fri, 8 Dec 2023 19:02:25 +0100 Subject: [PATCH] Better error message for wrong assembly name in directives We now try to find the type without the assembly, if it gets resolved we let the user know that a different assembly has the correct type. --- .../Resolved/DirectiveCompilationService.cs | 32 +++++++++++++++++-- .../ViewModelDirectiveTest.cs | 11 +++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs index b602c75505..614e55690d 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/DirectiveCompilationService.cs @@ -6,6 +6,9 @@ using DotVVM.Framework.Compilation.Binding; using System.Collections.Immutable; using DotVVM.Framework.Configuration; +using System.Security.Cryptography; +using System.Diagnostics; +using FastExpressionCompiler; namespace DotVVM.Framework.Compilation.ControlTree.Resolved { @@ -26,7 +29,8 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, { if (CompileDirectiveExpression(directive, nameSyntax, imports) is not StaticClassIdentifierExpression expression) { - directive.AddError($"Could not resolve type '{nameSyntax.ToDisplayString()}'."); + var help = GetMissingTypeHelp(nameSyntax, imports); + directive.AddError($"Could not resolve type '{nameSyntax.ToDisplayString()}'.{(help is null ? "" : " " + help)}"); return null; } else return new ResolvedTypeDescriptor(expression.Type); @@ -94,7 +98,29 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, return null; } - private Expression? CompileDirectiveExpression(DothtmlDirectiveNode directive, BindingParserNode expressionSyntax, ImmutableList imports) + private string? GetMissingTypeHelp(BindingParserNode syntax, ImmutableList imports) + { + try + { + // wrong assembly name: + if (syntax is AssemblyQualifiedNameBindingParserNode assemblyQualifiedName) + { + var noAssemblyResolve = CompileDirectiveExpression(null, assemblyQualifiedName.TypeName, imports); + if (noAssemblyResolve is StaticClassIdentifierExpression expression) + { + return $"Did you mean the '{expression.Type.Assembly.GetName().Name}' assembly? Note that assembly name is optional."; + } + } + return null; + } + catch + { + Debug.Fail("This should not happen"); + return null; // just fancy error handling, ignore errors + } + } + + private Expression? CompileDirectiveExpression(DothtmlDirectiveNode? directive, BindingParserNode expressionSyntax, ImmutableList imports) { TypeRegistry registry; if (expressionSyntax is TypeOrFunctionReferenceBindingParserNode typeOrFunction) @@ -122,7 +148,7 @@ public DirectiveCompilationService(CompiledAssemblyCache compiledAssemblyCache, } catch (Exception ex) { - directive.AddError($"{expressionSyntax.ToDisplayString()} is not a valid type or namespace: {ex.Message}"); + directive?.AddError($"{expressionSyntax.ToDisplayString()} is not a valid type or namespace: {ex.Message}"); return null; } } diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs index 4176eb21b9..d107cf8094 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/ViewModelDirectiveTest.cs @@ -99,5 +99,16 @@ public void ResolvedTree_ViewModel_DirectiveIdentifier_CaseInsensitivity(string Assert.IsFalse(root.Directives.Any(d => d.Value.Any(dd => dd.DothtmlNode.HasNodeErrors))); Assert.AreEqual(typeof(string), root.DataContextTypeStack.DataContextType); } + + [TestMethod] + public void ResolvedTree_ViewModel_AssemblyNotFound_Error() + { + var root = ParseSource($"@viewModel DotVVM.Framework.Controls.IGridViewDataSet, DotVVM.Framework"); + + var directive = root.Directives["viewModel"].Single().DothtmlNode; + Assert.IsTrue(directive.HasNodeErrors); + StringAssert.Contains(directive.NodeErrors.Single(), "'DotVVM.Core'"); + } + } }