From 52f2bd64efcd2cb2384484cd95580da2a49afaf1 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 13 Jan 2015 10:43:41 -0800 Subject: [PATCH] Added ProjectFormatException and more detailed errors to DTH - ProjectFormatException has the project path, line and Column (1 based), where the exception ocurred in the project.json file. - Give more specific errors when they aren't just JsonReaderExceptions, but semantic exceptions within the project format itself (beyond just JSON). - Added Path, Line and Column to ErrorMessage in DTH #1052 --- .../ApplicationContext.cs | 9 +- .../Models/OutgoingMessages/ErrorMessage.cs | 3 + src/Microsoft.Framework.Runtime/Project.cs | 97 ++++++++++++++++--- .../ProjectFormatException.cs | 64 ++++++++++++ 4 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.Framework.Runtime/ProjectFormatException.cs diff --git a/src/Microsoft.Framework.DesignTimeHost/ApplicationContext.cs b/src/Microsoft.Framework.DesignTimeHost/ApplicationContext.cs index 63171d76c..a62e3a1fd 100644 --- a/src/Microsoft.Framework.DesignTimeHost/ApplicationContext.cs +++ b/src/Microsoft.Framework.DesignTimeHost/ApplicationContext.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Runtime.Versioning; using System.Threading; -using Microsoft.CodeAnalysis.Text; using Microsoft.Framework.DesignTimeHost.Models; using Microsoft.Framework.DesignTimeHost.Models.IncomingMessages; using Microsoft.Framework.DesignTimeHost.Models.OutgoingMessages; @@ -103,6 +102,14 @@ public void ProcessLoop(object state) Message = ex.Message }; + var projectFormatException = ex as ProjectFormatException; + if (projectFormatException != null) + { + error.Path = projectFormatException.Path; + error.Line = projectFormatException.Line; + error.Column = projectFormatException.Column; + } + var message = new Message { ContextId = Id, diff --git a/src/Microsoft.Framework.DesignTimeHost/Models/OutgoingMessages/ErrorMessage.cs b/src/Microsoft.Framework.DesignTimeHost/Models/OutgoingMessages/ErrorMessage.cs index 2b848833b..ae1e5ac0b 100644 --- a/src/Microsoft.Framework.DesignTimeHost/Models/OutgoingMessages/ErrorMessage.cs +++ b/src/Microsoft.Framework.DesignTimeHost/Models/OutgoingMessages/ErrorMessage.cs @@ -7,5 +7,8 @@ namespace Microsoft.Framework.DesignTimeHost.Models.OutgoingMessages public class ErrorMessage { public string Message { get; set; } + public string Path { get; set; } + public int Line { get; set; } + public int Column { get; set; } } } diff --git a/src/Microsoft.Framework.Runtime/Project.cs b/src/Microsoft.Framework.Runtime/Project.cs index dea06146e..25ec67a09 100644 --- a/src/Microsoft.Framework.Runtime/Project.cs +++ b/src/Microsoft.Framework.Runtime/Project.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using System.Runtime.Versioning; +using System.Text; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NuGet; @@ -253,21 +255,37 @@ public static bool TryGetProject(string path, out Project project) projectPath = Path.Combine(path, ProjectFileName); } - var json = File.ReadAllText(projectPath); - // Assume the directory name is the project name if none was specified var projectName = GetDirectoryName(path); + projectPath = Path.GetFullPath(projectPath); - project = GetProject(json, projectName, projectPath); + try + { + using (var stream = File.OpenRead(projectPath)) + { + project = GetProject(stream, projectName, projectPath); + } + } + catch (JsonReaderException ex) + { + throw ProjectFormatException.Create(ex, projectPath); + } return true; } public static Project GetProject(string json, string projectName, string projectPath) + { + var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)); + return GetProject(ms, projectName, projectPath); + } + + public static Project GetProject(Stream stream, string projectName, string projectPath) { var project = new Project(); - var rawProject = JObject.Parse(json); + var reader = new JsonTextReader(new StreamReader(stream)); + var rawProject = JObject.Load(reader); // Metadata properties var version = rawProject["version"]; @@ -275,11 +293,29 @@ public static Project GetProject(string json, string projectName, string project var tags = rawProject["tags"]; project.Name = projectName; - project.Version = version == null ? new SemanticVersion("1.0.0") : new SemanticVersion(version.Value()); + project.ProjectFilePath = Path.GetFullPath(projectPath); + + if (version == null) + { + project.Version = new SemanticVersion("1.0.0"); + } + else + { + try + { + project.Version = new SemanticVersion(version.Value()); + } + catch (Exception ex) + { + var lineInfo = (IJsonLineInfo)version; + + throw ProjectFormatException.Create(ex, version, project.ProjectFilePath); + } + } + project.Description = GetValue(rawProject, "description"); project.Authors = authors == null ? new string[] { } : authors.ToObject(); project.Dependencies = new List(); - project.ProjectFilePath = Path.GetFullPath(projectPath); project.WebRoot = GetValue(rawProject, "webroot"); project.EntryPoint = GetValue(rawProject, "entryPoint"); project.ProjectUrl = GetValue(rawProject, "projectUrl"); @@ -342,8 +378,10 @@ public static Project GetProject(string json, string projectName, string project } else { - throw new InvalidDataException(string.Format( - "The value of a script in {0} can only be a string or an array of strings", ProjectFileName)); + throw ProjectFormatException.Create( + string.Format("The value of a script in {0} can only be a string or an array of strings", ProjectFileName), + value, + project.ProjectFilePath); } } } @@ -357,6 +395,7 @@ public static Project GetProject(string json, string projectName, string project project.BuildTargetFrameworksAndConfigurations(rawProject); PopulateDependencies( + project.ProjectFilePath, project.Dependencies, rawProject, "dependencies", @@ -426,6 +465,7 @@ private static IEnumerable GetSourcesSplit(string sourceDescription) } private static void PopulateDependencies( + string projectPath, IList results, JObject settings, string propertyName, @@ -436,9 +476,13 @@ private static void PopulateDependencies( { foreach (var dependency in dependencies) { - if (String.IsNullOrEmpty(dependency.Key)) + if (string.IsNullOrEmpty(dependency.Key)) { - throw new InvalidDataException("Unable to resolve dependency ''."); + + throw ProjectFormatException.Create( + "Unable to resolve dependency ''.", + dependency.Value, + projectPath); } // Support @@ -447,8 +491,11 @@ private static void PopulateDependencies( // } var dependencyValue = dependency.Value; - string dependencyVersionValue = null; var dependencyTypeValue = LibraryDependencyType.Default; + + string dependencyVersionValue = null; + JToken dependencyVersionToken = dependencyValue; + if (dependencyValue.Type == JTokenType.String) { dependencyVersionValue = dependencyValue.Value(); @@ -457,7 +504,7 @@ private static void PopulateDependencies( { if (dependencyValue.Type == JTokenType.Object) { - var dependencyVersionToken = dependencyValue["version"]; + dependencyVersionToken = dependencyValue["version"]; if (dependencyVersionToken != null && dependencyVersionToken.Type == JTokenType.String) { dependencyVersionValue = dependencyVersionToken.Value(); @@ -472,9 +519,20 @@ private static void PopulateDependencies( } SemanticVersion dependencyVersion = null; - if (!String.IsNullOrEmpty(dependencyVersionValue)) + + if (!string.IsNullOrEmpty(dependencyVersionValue)) { - dependencyVersion = SemanticVersion.Parse(dependencyVersionValue); + try + { + dependencyVersion = SemanticVersion.Parse(dependencyVersionValue); + } + catch (Exception ex) + { + throw ProjectFormatException.Create( + ex, + dependencyVersionToken, + projectPath); + } } results.Add(new LibraryDependency( @@ -601,7 +659,14 @@ private void BuildTargetFrameworksAndConfigurations(JObject rawProject) { foreach (var framework in frameworks) { - BuildTargetFrameworkNode(framework); + try + { + BuildTargetFrameworkNode(framework); + } + catch (Exception ex) + { + throw ProjectFormatException.Create(ex, framework.Value, ProjectFilePath); + } } } } @@ -642,6 +707,7 @@ private bool BuildTargetFrameworkNode(KeyValuePair targetFramewo var properties = targetFramework.Value.Value(); PopulateDependencies( + ProjectFilePath, targetFrameworkInformation.Dependencies, properties, "dependencies", @@ -649,6 +715,7 @@ private bool BuildTargetFrameworkNode(KeyValuePair targetFramewo var frameworkAssemblies = new List(); PopulateDependencies( + ProjectFilePath, frameworkAssemblies, properties, "frameworkAssemblies", diff --git a/src/Microsoft.Framework.Runtime/ProjectFormatException.cs b/src/Microsoft.Framework.Runtime/ProjectFormatException.cs new file mode 100644 index 000000000..8b68d2936 --- /dev/null +++ b/src/Microsoft.Framework.Runtime/ProjectFormatException.cs @@ -0,0 +1,64 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Framework.Runtime +{ + public sealed class ProjectFormatException : Exception + { + public ProjectFormatException(string message) : + base(message) + { + } + + public ProjectFormatException(string message, Exception innerException) : + base(message, innerException) + { + + } + + public string Path { get; private set; } + public int Line { get; private set; } + public int Column { get; private set; } + + private ProjectFormatException WithLineInfo(IJsonLineInfo lineInfo) + { + Line = lineInfo.LineNumber; + Column = lineInfo.LinePosition; + + return this; + } + + public static ProjectFormatException Create(Exception exception, JToken value, string path) + { + var lineInfo = (IJsonLineInfo)value; + + return new ProjectFormatException(exception.Message, exception) + { + Path = path + } + .WithLineInfo(lineInfo); + } + + public static ProjectFormatException Create(string message, JToken value, string path) + { + var lineInfo = (IJsonLineInfo)value; + + return new ProjectFormatException(message) + { + Path = path + } + .WithLineInfo(lineInfo); + } + + internal static ProjectFormatException Create(JsonReaderException exception, string path) + { + return new ProjectFormatException(exception.Message, exception) + { + Path = path, + Column = exception.LineNumber, + Line = exception.LinePosition + }; + } + } +} \ No newline at end of file