From e04caab11fb8f2cac4d575ef1e352221bd421586 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 18 Jul 2020 14:57:08 -0700 Subject: Separate "format" from "convert" Closes wixtoolset/issues#6215 --- src/WixToolset.Converters/ConvertCommand.cs | 269 ++------------------- .../ConverterExtensionCommandLine.cs | 11 +- src/WixToolset.Converters/FixupCommandBase.cs | 267 ++++++++++++++++++++ src/WixToolset.Converters/FormatCommand.cs | 60 +++++ src/WixToolset.Converters/WixConverter.cs | 179 ++++++++------ .../WixToolsetTest.Converters/ConverterFixture.cs | 104 -------- .../ConverterIntegrationFixture.cs | 10 +- .../WixToolsetTest.Converters/FormatFixture.cs | 117 +++++++++ 8 files changed, 589 insertions(+), 428 deletions(-) create mode 100644 src/WixToolset.Converters/FixupCommandBase.cs create mode 100644 src/WixToolset.Converters/FormatCommand.cs create mode 100644 src/test/WixToolsetTest.Converters/FormatFixture.cs (limited to 'src') diff --git a/src/WixToolset.Converters/ConvertCommand.cs b/src/WixToolset.Converters/ConvertCommand.cs index 51e7b997..50ca7249 100644 --- a/src/WixToolset.Converters/ConvertCommand.cs +++ b/src/WixToolset.Converters/ConvertCommand.cs @@ -3,283 +3,58 @@ namespace WixToolset.Converters { using System; - using System.Collections.Generic; - using System.IO; using System.Threading; using System.Threading.Tasks; - using System.Xml; - using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; - internal class ConvertCommand : ICommandLineCommand + internal class ConvertCommand : FixupCommandBase { - private const string SettingsFileDefault = "wixcop.settings.xml"; + private const string SettingsFileDefault = "wix.convert.settings.xml"; public ConvertCommand(IWixToolsetServiceProvider serviceProvider) { this.Messaging = serviceProvider.GetService(); - - this.IndentationAmount = 4; // default indentation amount - this.ErrorsAsWarnings = new HashSet(); - this.ExemptFiles = new HashSet(); - this.IgnoreErrors = new HashSet(); - this.SearchPatternResults = new HashSet(); - this.SearchPatterns = new List(); } private IMessaging Messaging { get; } - public bool ShowLogo { get; private set; } - - public bool StopParsing { get; private set; } - - private bool ShowHelp { get; set; } - - private HashSet ErrorsAsWarnings { get; } - - private HashSet ExemptFiles { get; } - - private bool FixErrors { get; set; } - - private int IndentationAmount { get; set; } - - private HashSet IgnoreErrors { get; } - - private HashSet SearchPatternResults { get; } - - private List SearchPatterns { get; } - - private string SettingsFile1 { get; set; } - - private string SettingsFile2 { get; set; } - - private bool SubDirectories { get; set; } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - if (!parser.IsSwitch(argument)) - { - this.SearchPatterns.Add(argument); - return true; - } - - var parameter = argument.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - this.ShowHelp = true; - this.ShowLogo = true; - this.StopParsing = true; - return true; - - case "f": - this.FixErrors = true; - return true; - - case "nologo": - this.ShowLogo = false; - return true; - - case "s": - this.SubDirectories = true; - return true; - - default: // other parameters - if (parameter.StartsWith("set1", StringComparison.Ordinal)) - { - this.SettingsFile1 = parameter.Substring(4); - return true; - } - else if (parameter.StartsWith("set2", StringComparison.Ordinal)) - { - this.SettingsFile2 = parameter.Substring(4); - return true; - } - else if (parameter.StartsWith("indent:", StringComparison.Ordinal)) - { - try - { - this.IndentationAmount = Convert.ToInt32(parameter.Substring(7)); - } - catch - { - parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}"; - } - return true; - } - - return false; - } - } - - public Task ExecuteAsync(CancellationToken cancellationToken) + public override Task ExecuteAsync(CancellationToken cancellationToken) { if (this.ShowHelp) { DisplayHelp(); - return Task.FromResult(1); - } - - // parse the settings if any were specified - if (null != this.SettingsFile1 || null != this.SettingsFile2) - { - this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2); - } - else - { - if (File.Exists(ConvertCommand.SettingsFileDefault)) - { - this.ParseSettingsFiles(ConvertCommand.SettingsFileDefault, null); - } + return Task.FromResult(-1); } var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors); - var errors = this.InspectSubDirectories(converter, Path.GetFullPath("."), cancellationToken); + this.ParseSettings(SettingsFileDefault); + + var errors = base.Inspect(Inspector, cancellationToken); - foreach (var searchPattern in this.SearchPatterns) + return Task.FromResult(errors); + + int Inspector(string file, bool fix) { - if (!this.SearchPatternResults.Contains(searchPattern)) - { - Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern); - errors++; - } + return converter.ConvertFile(file, fix); } - - return Task.FromResult(errors != 0 ? 2 : 0); } private static void DisplayHelp() { - Console.WriteLine(" usage: wix.exe convert sourceFile [sourceFile ...]"); Console.WriteLine(); - Console.WriteLine(" -f fix errors automatically for writable files"); - Console.WriteLine(" -nologo suppress displaying the logo information"); - Console.WriteLine(" -s search for matching files in current dir and subdirs"); - Console.WriteLine(" -set1 primary settings file"); - Console.WriteLine(" -set2 secondary settings file (overrides primary)"); - Console.WriteLine(" -indent: indentation multiple (overrides default of 4)"); - Console.WriteLine(" -? this help information"); + Console.WriteLine("Usage: wix convert [options] sourceFile [sourceFile ...]"); Console.WriteLine(); - Console.WriteLine(" sourceFile may use wildcards like *.wxs"); - } - - /// - /// Get the files that match a search path pattern. - /// - /// The base directory at which to begin the search. - /// The search path pattern. - /// The files matching the pattern. - private static string[] GetFiles(string baseDir, string searchPath) - { - // convert alternate directory separators to the standard one - var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); - string[] files = null; - - try - { - if (0 > lastSeparator) - { - files = Directory.GetFiles(baseDir, filePath); - } - else // found directory separator - { - var searchPattern = filePath.Substring(lastSeparator + 1); - - files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern); - } - } - catch (DirectoryNotFoundException) - { - // don't let this function throw the DirectoryNotFoundException. (this exception - // occurs for non-existant directories and invalid characters in the searchPattern) - } - - return files; - } - - /// - /// Inspect sub-directories. - /// - /// The directory whose sub-directories will be inspected. - /// The number of errors that were found. - private int InspectSubDirectories(WixConverter converter, string directory, CancellationToken cancellationToken) - { - var errors = 0; - - foreach (var searchPattern in this.SearchPatterns) - { - foreach (var sourceFilePath in GetFiles(directory, searchPattern)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var file = new FileInfo(sourceFilePath); - - if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant())) - { - this.SearchPatternResults.Add(searchPattern); - errors += converter.ConvertFile(file.FullName, this.FixErrors); - } - } - } - - if (this.SubDirectories) - { - foreach (var childDirectoryPath in Directory.GetDirectories(directory)) - { - errors += this.InspectSubDirectories(converter, childDirectoryPath, cancellationToken); - } - } - - return errors; - } - - /// - /// Parse the primary and secondary settings files. - /// - /// The primary settings file. - /// The secondary settings file. - private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2) - { - if (null == localSettingsFile1 && null != localSettingsFile2) - { - throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2)); - } - - var settingsFile = localSettingsFile1; - while (null != settingsFile) - { - var doc = new XmlDocument(); - doc.Load(settingsFile); - - // get the types of tests that will have their errors displayed as warnings - var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test"); - foreach (XmlElement test in testsIgnoredElements) - { - var key = test.GetAttribute("Id"); - this.IgnoreErrors.Add(key); - } - - // get the types of tests that will have their errors displayed as warnings - var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test"); - foreach (XmlElement test in testsAsWarningsElements) - { - var key = test.GetAttribute("Id"); - this.ErrorsAsWarnings.Add(key); - } - - // get the exempt files - var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File"); - foreach (XmlElement file in localExemptFiles) - { - var key = file.GetAttribute("Name").ToUpperInvariant(); - this.ExemptFiles.Add(key); - } - - settingsFile = localSettingsFile2; - localSettingsFile2 = null; - } + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" --nologo Suppress displaying the logo information."); + Console.WriteLine(" -n|--dry-run Only display errors, do not update files."); + Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs."); + Console.WriteLine(" -set1 Primary settings file."); + Console.WriteLine(" -set2 Secondary settings file (overrides primary)."); + Console.WriteLine(" -indent: Indentation multiple (overrides default of 4)."); + Console.WriteLine(); + Console.WriteLine(" sourceFile may use wildcards like *.wxs"); } } } diff --git a/src/WixToolset.Converters/ConverterExtensionCommandLine.cs b/src/WixToolset.Converters/ConverterExtensionCommandLine.cs index ed4b613e..d78b89cc 100644 --- a/src/WixToolset.Converters/ConverterExtensionCommandLine.cs +++ b/src/WixToolset.Converters/ConverterExtensionCommandLine.cs @@ -21,8 +21,11 @@ namespace WixToolset.Converters private IWixToolsetServiceProvider ServiceProvider { get; } - // TODO: Do something with CommandLineSwitches - public override IEnumerable CommandLineSwitches => base.CommandLineSwitches; + public override IEnumerable CommandLineSwitches => new ExtensionCommandLineSwitch[] + { + new ExtensionCommandLineSwitch { Switch = "convert", Description = "Convert v3 source code to v4 source code." }, + new ExtensionCommandLineSwitch { Switch = "format", Description = "Ensures consistent formatting of source code." }, + }; public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) { @@ -32,6 +35,10 @@ namespace WixToolset.Converters { command = new ConvertCommand(this.ServiceProvider); } + else if ("format".Equals(argument, StringComparison.OrdinalIgnoreCase)) + { + command = new FormatCommand(this.ServiceProvider); + } return command != null; } diff --git a/src/WixToolset.Converters/FixupCommandBase.cs b/src/WixToolset.Converters/FixupCommandBase.cs new file mode 100644 index 00000000..0f58fbdb --- /dev/null +++ b/src/WixToolset.Converters/FixupCommandBase.cs @@ -0,0 +1,267 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Converters +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal abstract class FixupCommandBase : ICommandLineCommand + { + protected FixupCommandBase() + { + this.IndentationAmount = 4; // default indentation amount + this.ErrorsAsWarnings = new HashSet(); + this.ExemptFiles = new HashSet(); + this.IgnoreErrors = new HashSet(); + this.SearchPatternResults = new HashSet(); + this.SearchPatterns = new List(); + } + + public bool ShowLogo { get; private set; } + + public bool StopParsing { get; private set; } + + protected bool ShowHelp { get; set; } + + protected bool DryRun { get; set; } + + protected HashSet ErrorsAsWarnings { get; } + + protected HashSet IgnoreErrors { get; } + + protected HashSet ExemptFiles { get; } + + protected int IndentationAmount { get; set; } + + protected bool Recurse { get; set; } + + private HashSet SearchPatternResults { get; } + + private List SearchPatterns { get; } + + private string SettingsFile1 { get; set; } + + private string SettingsFile2 { get; set; } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (!parser.IsSwitch(argument)) + { + this.SearchPatterns.Add(argument); + return true; + } + + var parameter = argument.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "-help": + this.ShowHelp = true; + this.ShowLogo = true; + this.StopParsing = true; + return true; + + case "n": + case "--dry-run": + this.DryRun = true; + return true; + + case "nologo": + case "-nologo": + this.ShowLogo = false; + return true; + + case "s": + case "r": + case "-recurse": + this.Recurse = true; + return true; + + default: // other parameters + if (parameter.StartsWith("set1", StringComparison.Ordinal)) + { + this.SettingsFile1 = parameter.Substring(4); + return true; + } + else if (parameter.StartsWith("set2", StringComparison.Ordinal)) + { + this.SettingsFile2 = parameter.Substring(4); + return true; + } + else if (parameter.StartsWith("indent:", StringComparison.Ordinal)) + { + try + { + this.IndentationAmount = Convert.ToInt32(parameter.Substring(7)); + } + catch + { + parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}"; + } + return true; + } + + return false; + } + } + + public abstract Task ExecuteAsync(CancellationToken cancellationToken); + + protected void ParseSettings(string defaultSettingsFile) + { + // parse the settings if any were specified + if (null != this.SettingsFile1 || null != this.SettingsFile2) + { + this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2); + } + else + { + if (File.Exists(defaultSettingsFile)) + { + this.ParseSettingsFiles(defaultSettingsFile, null); + } + } + } + + protected int Inspect(Func inspector, CancellationToken cancellationToken) + { + var errors = this.InspectSubDirectories(inspector, Path.GetFullPath("."), cancellationToken); + + foreach (var searchPattern in this.SearchPatterns) + { + if (!this.SearchPatternResults.Contains(searchPattern)) + { + Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern); + errors++; + } + } + + return errors; + } + + /// + /// Inspect sub-directories. + /// + /// The directory whose sub-directories will be inspected. + /// The number of errors that were found. + private int InspectSubDirectories(Func inspector, string directory, CancellationToken cancellationToken) + { + var errors = 0; + + foreach (var searchPattern in this.SearchPatterns) + { + foreach (var sourceFilePath in GetFiles(directory, searchPattern)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var file = new FileInfo(sourceFilePath); + + if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant())) + { + this.SearchPatternResults.Add(searchPattern); + errors += inspector(file.FullName, !this.DryRun); + } + } + } + + if (this.Recurse) + { + foreach (var childDirectoryPath in Directory.GetDirectories(directory)) + { + errors += this.InspectSubDirectories(inspector, childDirectoryPath, cancellationToken); + } + } + + return errors; + } + + /// + /// Parse the primary and secondary settings files. + /// + /// The primary settings file. + /// The secondary settings file. + private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2) + { + if (null == localSettingsFile1 && null != localSettingsFile2) + { + throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2)); + } + + var settingsFile = localSettingsFile1; + while (null != settingsFile) + { + var doc = new XmlDocument(); + doc.Load(settingsFile); + + // get the types of tests that will have their errors displayed as warnings + var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test"); + foreach (XmlElement test in testsIgnoredElements) + { + var key = test.GetAttribute("Id"); + this.IgnoreErrors.Add(key); + } + + // get the types of tests that will have their errors displayed as warnings + var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test"); + foreach (XmlElement test in testsAsWarningsElements) + { + var key = test.GetAttribute("Id"); + this.ErrorsAsWarnings.Add(key); + } + + // get the exempt files + var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File"); + foreach (XmlElement file in localExemptFiles) + { + var key = file.GetAttribute("Name").ToUpperInvariant(); + this.ExemptFiles.Add(key); + } + + settingsFile = localSettingsFile2; + localSettingsFile2 = null; + } + } + + /// + /// Get the files that match a search path pattern. + /// + /// The base directory at which to begin the search. + /// The search path pattern. + /// The files matching the pattern. + private static string[] GetFiles(string baseDir, string searchPath) + { + // convert alternate directory separators to the standard one + var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); + string[] files = null; + + try + { + if (0 > lastSeparator) + { + files = Directory.GetFiles(baseDir, filePath); + } + else // found directory separator + { + var searchPattern = filePath.Substring(lastSeparator + 1); + + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern); + } + } + catch (DirectoryNotFoundException) + { + // don't let this function throw the DirectoryNotFoundException. (this exception + // occurs for non-existant directories and invalid characters in the searchPattern) + } + + return files; + } + } +} diff --git a/src/WixToolset.Converters/FormatCommand.cs b/src/WixToolset.Converters/FormatCommand.cs new file mode 100644 index 00000000..e9965df3 --- /dev/null +++ b/src/WixToolset.Converters/FormatCommand.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Converters +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility.Services; + + internal class FormatCommand : FixupCommandBase + { + private const string SettingsFileDefault = "wix.format.settings.xml"; + + public FormatCommand(IWixToolsetServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public override Task ExecuteAsync(CancellationToken cancellationToken) + { + if (this.ShowHelp) + { + DisplayHelp(); + return Task.FromResult(-1); + } + + var converter = new WixConverter(this.Messaging, this.IndentationAmount, this.ErrorsAsWarnings, this.IgnoreErrors); + + this.ParseSettings(SettingsFileDefault); + + var errors = base.Inspect(Inspector, cancellationToken); + + return Task.FromResult(errors); + + int Inspector(string file, bool fix) + { + return converter.FormatFile(file, fix); + } + } + + private static void DisplayHelp() + { + Console.WriteLine(); + Console.WriteLine("Usage: wix format [options] sourceFile [sourceFile ...]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" --nologo Suppress displaying the logo information."); + Console.WriteLine(" -n|--dry-run Only display errors, do not update files."); + Console.WriteLine(" -r|--recurse Search for matching files in current dir and subdirs."); + Console.WriteLine(" -set1 Primary settings file."); + Console.WriteLine(" -set2 Secondary settings file (overrides primary)."); + Console.WriteLine(" -indent: Indentation multiple (overrides default of 4)."); + Console.WriteLine(); + Console.WriteLine(" sourceFile may use wildcards like *.wxs"); + } + } +} diff --git a/src/WixToolset.Converters/WixConverter.cs b/src/WixToolset.Converters/WixConverter.cs index a05c7f58..bfeed03e 100644 --- a/src/WixToolset.Converters/WixConverter.cs +++ b/src/WixToolset.Converters/WixConverter.cs @@ -19,6 +19,12 @@ namespace WixToolset.Converters /// public class WixConverter { + private enum ConvertOperation + { + Convert, + Format, + } + private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled); private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters @@ -107,18 +113,6 @@ namespace WixToolset.Converters { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" }, }; - private readonly static SortedSet Wix3Namespaces = new SortedSet - { - "http://schemas.microsoft.com/wix/2006/wi", - "http://schemas.microsoft.com/wix/2006/localization", - }; - - private readonly static SortedSet Wix4Namespaces = new SortedSet - { - "http://wixtoolset.org/schemas/v4/wxs", - "http://wixtoolset.org/schemas/v4/wxl", - }; - private readonly Dictionary> ConvertElementMapping; /// @@ -193,6 +187,8 @@ namespace WixToolset.Converters private int IndentationAmount { get; set; } + private ConvertOperation Operation { get; set; } + private string SourceFile { get; set; } private int SourceVersion { get; set; } @@ -205,22 +201,11 @@ namespace WixToolset.Converters /// The number of errors found. public int ConvertFile(string sourceFile, bool saveConvertedFile) { - XDocument document; - - // Set the instance info. - this.Errors = 0; - this.SourceFile = sourceFile; - this.SourceVersion = 0; + var document = this.OpenSourceFile(sourceFile); - try + if (document is null) { - document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); - } - catch (XmlException e) - { - this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message); - - return this.Errors; + return 1; } this.ConvertDocument(document); @@ -228,17 +213,7 @@ namespace WixToolset.Converters // Fix errors if requested and necessary. if (saveConvertedFile && 0 < this.Errors) { - try - { - using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true })) - { - document.Save(writer); - } - } - catch (UnauthorizedAccessException) - { - this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file."); - } + this.SaveDocument(document); } return this.Errors; @@ -251,13 +226,68 @@ namespace WixToolset.Converters /// The number of errors found. public int ConvertDocument(XDocument document) { + // Reset the instance info. this.Errors = 0; this.SourceVersion = 0; + this.Operation = ConvertOperation.Convert; + + // Remove the declaration. + if (null != document.Declaration) + { + if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line.")) + { + document.Declaration = null; + } + } + + TrimLeadingText(document); + + // Start converting the nodes at the top. + this.ConvertNodes(document.Nodes(), 0); + + return this.Errors; + } - var declaration = document.Declaration; + /// + /// Format a file. + /// + /// The file to format. + /// Option to save the format errors that are found. + /// The number of errors found. + public int FormatFile(string sourceFile, bool saveConvertedFile) + { + var document = this.OpenSourceFile(sourceFile); + + if (document is null) + { + return 1; + } + + this.FormatDocument(document); + + // Fix errors if requested and necessary. + if (saveConvertedFile && 0 < this.Errors) + { + this.SaveDocument(document); + } + + return this.Errors; + } + + /// + /// Format a document. + /// + /// The document to format. + /// The number of errors found. + public int FormatDocument(XDocument document) + { + // Reset the instance info. + this.Errors = 0; + this.SourceVersion = 0; + this.Operation = ConvertOperation.Format; // Remove the declaration. - if (null != declaration) + if (null != document.Declaration) { if (this.OnError(ConverterTestType.DeclarationPresent, null, "This file contains an XML declaration on the first line.")) { @@ -273,6 +303,37 @@ namespace WixToolset.Converters return this.Errors; } + private XDocument OpenSourceFile(string sourceFile) + { + this.SourceFile = sourceFile; + + try + { + return XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + } + catch (XmlException e) + { + this.OnError(ConverterTestType.XmlException, null, "The xml is invalid. Detail: '{0}'", e.Message); + } + + return null; + } + + private void SaveDocument(XDocument document) + { + try + { + using (var writer = XmlWriter.Create(this.SourceFile, new XmlWriterSettings { OmitXmlDeclaration = true })) + { + document.Save(writer); + } + } + catch (UnauthorizedAccessException) + { + this.OnError(ConverterTestType.UnauthorizedAccessException, null, "Could not write to file."); + } + } + private void ConvertNodes(IEnumerable nodes, int level) { // Note we operate on a copy of the node list since we may @@ -901,7 +962,10 @@ namespace WixToolset.Converters /// Returns true indicating that action should be taken on this error, and false if it should be ignored. private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) { - if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error + // Ignore the error if explicitly ignored or outside the range of the current operation. + if (this.IgnoreErrors.Contains(converterTestType) || + (this.Operation == ConvertOperation.Convert && converterTestType < ConverterTestType.DeclarationPresent) || + (this.Operation == ConvertOperation.Format && converterTestType > ConverterTestType.DeclarationPresent)) { return false; } @@ -909,7 +973,7 @@ namespace WixToolset.Converters // Increase the error count. this.Errors++; - var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); + var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wix.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); var warning = this.ErrorsAsWarnings.Contains(converterTestType); var display = String.Format(CultureInfo.CurrentCulture, message, args); @@ -1049,40 +1113,20 @@ namespace WixToolset.Converters /// UnauthorizedAccessException, - /// - /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. - /// - DeclarationEncodingWrong, - - /// - /// Displayed when the XML declaration is missing from the source file. - /// - DeclarationMissing, - - /// - /// Displayed when the whitespace preceding a CDATA node is wrong. - /// - WhitespacePrecedingCDATAWrong, - /// /// Displayed when the whitespace preceding a node is wrong. /// WhitespacePrecedingNodeWrong, /// - /// Displayed when an element is not empty as it should be. - /// - NotEmptyElement, - - /// - /// Displayed when the whitespace following a CDATA node is wrong. + /// Displayed when the whitespace preceding an end element is wrong. /// - WhitespaceFollowingCDATAWrong, + WhitespacePrecedingEndElementWrong, /// - /// Displayed when the whitespace preceding an end element is wrong. + /// Displayed when the XML declaration is present in the source file. /// - WhitespacePrecedingEndElementWrong, + DeclarationPresent, /// /// Displayed when the xmlns attribute is missing from the document element. @@ -1154,11 +1198,6 @@ namespace WixToolset.Converters /// AutoGuidUnnecessary, - /// - /// Displayed when the XML declaration is present in the source file. - /// - DeclarationPresent, - /// /// The Feature Absent attribute renamed to AllowAbsent. /// diff --git a/src/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/test/WixToolsetTest.Converters/ConverterFixture.cs index 6e2ad2c5..cf89ba7e 100644 --- a/src/test/WixToolsetTest.Converters/ConverterFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConverterFixture.cs @@ -39,110 +39,6 @@ namespace WixToolsetTest.Converters Assert.Equal(expected, actual); } - [Fact] - public void CanFixWhitespace() - { - var parse = String.Join(Environment.NewLine, - "", - "", - " ", - " ", - " ", - " ", - ""); - - var expected = String.Join(Environment.NewLine, - "", - " ", - " ", - " ", - ""); - - var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); - - var messaging = new MockMessaging(); - var converter = new WixConverter(messaging, 4, null, null); - - var errors = converter.ConvertDocument(document); - - var actual = UnformattedDocumentString(document); - - Assert.Equal(expected, actual); - Assert.Equal(5, errors); - } - - [Fact] - public void CanPreserveNewLines() - { - var parse = String.Join(Environment.NewLine, - "", - "", - " ", - "", - " ", - "", - " ", - ""); - - var expected = String.Join(Environment.NewLine, - "", - " ", - "", - " ", - "", - " ", - ""); - - var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); - - var messaging = new MockMessaging(); - var converter = new WixConverter(messaging, 4, null, null); - - var conversions = converter.ConvertDocument(document); - - var actual = UnformattedDocumentString(document); - - Assert.Equal(expected, actual); - Assert.Equal(4, conversions); - } - - [Fact] - public void CanConvertWithNewLineAtEndOfFile() - { - var parse = String.Join(Environment.NewLine, - "", - " ", - "", - " ", - "", - " ", - "", - ""); - - var expected = String.Join(Environment.NewLine, - "", - " ", - "", - " ", - "", - " ", - "", - ""); - - var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); - - var messaging = new MockMessaging(); - var converter = new WixConverter(messaging, 4, null, null); - - var conversions = converter.ConvertDocument(document); - - var actual = UnformattedDocumentString(document); - - Assert.Equal(expected, actual); - Assert.Equal(3, conversions); - } - [Fact] public void CanConvertMainNamespace() { diff --git a/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs b/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs index 5eaeb985..79cc3f69 100644 --- a/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs +++ b/src/test/WixToolsetTest.Converters/ConverterIntegrationFixture.cs @@ -84,7 +84,7 @@ namespace WixToolsetTest.Converters var settingsFile = Path.Combine(folder, "wixcop.settings.xml"); var result = RunConversion(targetFile, settingsFile: settingsFile); - Assert.Equal(2, result.ExitCode); + Assert.Equal(7, result.ExitCode); var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n"); var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n"); @@ -108,7 +108,7 @@ namespace WixToolsetTest.Converters File.Copy(Path.Combine(folder, beforeFileName), Path.Combine(baseFolder, beforeFileName)); var result = RunConversion(targetFile); - Assert.Equal(2, result.ExitCode); + Assert.Equal(10, result.ExitCode); var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n"); var actual = File.ReadAllText(targetFile).Replace("\r\n", "\n"); @@ -133,7 +133,7 @@ namespace WixToolsetTest.Converters var result = RunConversion(targetFile); - Assert.Equal(2, result.ExitCode); + Assert.Equal(10, result.ExitCode); Assert.Single(result.Messages.Where(message => message.ToString().EndsWith("(QtExecCmdTimeoutAmbiguous)"))); var expected = File.ReadAllText(Path.Combine(folder, afterFileName)).Replace("\r\n", "\n"); @@ -142,7 +142,7 @@ namespace WixToolsetTest.Converters // still fails because QtExecCmdTimeoutAmbiguous is unfixable var result2 = RunConversion(targetFile); - Assert.Equal(2, result2.ExitCode); + Assert.Equal(1, result2.ExitCode); } } @@ -153,7 +153,7 @@ namespace WixToolsetTest.Converters var exitCode = WixRunner.Execute(new[] { "convert", - fixErrors ? "-f" : null, + fixErrors ? null : "--dry-run", String.IsNullOrEmpty(settingsFile) ? null : "-set1" + settingsFile, targetFile }, serviceProvider, out var messages); diff --git a/src/test/WixToolsetTest.Converters/FormatFixture.cs b/src/test/WixToolsetTest.Converters/FormatFixture.cs new file mode 100644 index 00000000..739fba66 --- /dev/null +++ b/src/test/WixToolsetTest.Converters/FormatFixture.cs @@ -0,0 +1,117 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolsetTest.Converters +{ + using System; + using System.Xml.Linq; + using WixToolset.Converters; + using WixToolsetTest.Converters.Mocks; + using Xunit; + + public class FormatFixture : BaseConverterFixture + { + [Fact] + public void CanFixWhitespace() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + " ", + " ", + " ", + ""); + + var expected = String.Join(Environment.NewLine, + "", + " ", + " ", + " ", + ""); + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 4, null, null); + + var errors = converter.FormatDocument(document); + + var actual = UnformattedDocumentString(document); + + Assert.Equal(expected, actual); + Assert.Equal(5, errors); + } + + [Fact] + public void CanPreserveNewLines() + { + var parse = String.Join(Environment.NewLine, + "", + "", + " ", + "", + " ", + "", + " ", + ""); + + var expected = String.Join(Environment.NewLine, + "", + " ", + "", + " ", + "", + " ", + ""); + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 4, null, null); + + var conversions = converter.FormatDocument(document); + + var actual = UnformattedDocumentString(document); + + Assert.Equal(expected, actual); + Assert.Equal(4, conversions); + } + + [Fact] + public void CanFormatWithNewLineAtEndOfFile() + { + var parse = String.Join(Environment.NewLine, + "", + " ", + "", + " ", + "", + " ", + "", + ""); + + var expected = String.Join(Environment.NewLine, + "", + " ", + "", + " ", + "", + " ", + "", + ""); + + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); + + var messaging = new MockMessaging(); + var converter = new WixConverter(messaging, 4, null, null); + + var conversions = converter.FormatDocument(document); + + var actual = UnformattedDocumentString(document); + + Assert.Equal(expected, actual); + Assert.Equal(3, conversions); + } + } +} -- cgit v1.2.3-55-g6feb