From 384c2a3f11ad9394aea617e7e0948886da703815 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 30 Jun 2019 20:48:48 -0700 Subject: [PATCH 1/3] Move format logic to new data structure --- Engine/EditableText.cs | 2 +- Engine/Formatter.cs | 63 +++++------- Engine/Generic/CorrectionExtent.cs | 40 ++++++++ Engine/ScriptAnalyzer.cs | 86 +++++++++++++--- Engine/text.cs | 159 +++++++++++++++++++++++++++++ 5 files changed, 302 insertions(+), 48 deletions(-) create mode 100644 Engine/text.cs diff --git a/Engine/EditableText.cs b/Engine/EditableText.cs index 22e072fd0..555dd02de 100644 --- a/Engine/EditableText.cs +++ b/Engine/EditableText.cs @@ -61,7 +61,7 @@ public EditableText ApplyEdit(TextEdit textEdit) { ValidateTextEdit(textEdit); - var editLines = textEdit.Lines; + string[] editLines = textEdit.Lines; // Get the first fragment of the first line string firstLineFragment = diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index d86127f2f..1f55c21a1 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -3,6 +3,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Management.Automation; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer @@ -12,6 +13,16 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// public class Formatter { + private static readonly IEnumerable s_formattingRulesInOrder = new [] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" + }; + /// /// Format a powershell script. /// @@ -27,45 +38,21 @@ public static string Format( TCmdlet cmdlet) where TCmdlet : PSCmdlet, IOutputWriter { // todo implement notnull attribute for such a check - ValidateNotNull(scriptDefinition, "scriptDefinition"); - ValidateNotNull(settings, "settings"); - ValidateNotNull(cmdlet, "cmdlet"); + ValidateNotNull(scriptDefinition, nameof(scriptDefinition)); + ValidateNotNull(settings, nameof(settings)); + ValidateNotNull(cmdlet, nameof(cmdlet)); Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); Helper.Instance.Initialize(); - var ruleOrder = new string[] - { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement", - "PSUseCorrectCasing" - }; - - var text = new EditableText(scriptDefinition); - foreach (var rule in ruleOrder) - { - if (!settings.RuleArguments.ContainsKey(rule)) - { - continue; - } - - var currentSettings = GetCurrentSettings(settings, rule); - ScriptAnalyzer.Instance.UpdateSettings(currentSettings); - ScriptAnalyzer.Instance.Initialize(cmdlet, null, null, null, null, true, false); + Settings currentSettings = GetCurrentSettings(settings); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); + ScriptAnalyzer.Instance.Initialize(cmdlet, includeDefaultRules: true); - Range updatedRange; - bool fixesWereApplied; - text = ScriptAnalyzer.Instance.Fix(text, range, out updatedRange, out fixesWereApplied); - range = updatedRange; - } - - return text.ToString(); + return ScriptAnalyzer.Instance.Fix(scriptDefinition, range); } - private static void ValidateNotNull(T obj, string name) + private static void ValidateNotNull(object obj, string name) { if (obj == null) { @@ -73,12 +60,18 @@ private static void ValidateNotNull(T obj, string name) } } - private static Settings GetCurrentSettings(Settings settings, string rule) + private static Settings GetCurrentSettings(Settings settings) { + var ruleSettings = new Hashtable(); + foreach (string rule in s_formattingRulesInOrder) + { + ruleSettings[rule] = new Hashtable(settings.RuleArguments[rule]); + } + return new Settings(new Hashtable() { - {"IncludeRules", new string[] {rule}}, - {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } + { "IncludeRules", s_formattingRulesInOrder }, + { "Rules", ruleSettings } }); } } diff --git a/Engine/Generic/CorrectionExtent.cs b/Engine/Generic/CorrectionExtent.cs index caad49cdb..979e92c0a 100644 --- a/Engine/Generic/CorrectionExtent.cs +++ b/Engine/Generic/CorrectionExtent.cs @@ -105,4 +105,44 @@ public CorrectionExtent( } } + + internal struct CorrectionComparer : IComparer, IEqualityComparer + { + public int Compare(CorrectionExtent x, CorrectionExtent y) + { + if (x.StartLineNumber > y.StartLineNumber) + { + return 1; + } + + if (x.StartLineNumber < y.StartLineNumber) + { + return -1; + } + + if (x.StartColumnNumber > y.StartColumnNumber) + { + return 1; + } + + if (x.StartColumnNumber < y.StartColumnNumber) + { + return -1; + } + + return 0; + } + + public bool Equals(CorrectionExtent x, CorrectionExtent y) + { + return Compare(x, y) == 0; + } + + public int GetHashCode(CorrectionExtent obj) + { + return obj != null + ? obj.GetHashCode() + : 0; + } + } } diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 18fc06bf8..3a37346f2 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -29,6 +29,8 @@ public sealed class ScriptAnalyzer { #region Private members + private readonly CorrectionComparer s_correctionComparer = new CorrectionComparer(); + private IOutputWriter outputWriter; private Dictionary settings; private readonly Regex s_aboutHelpRegex = new Regex("^about_.*help\\.txt$", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -1574,22 +1576,24 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange, throw new ArgumentNullException(nameof(text)); } - var isRangeNull = range == null; + bool isRangeNull = range == null; if (!isRangeNull && !text.IsValidRange(range)) { this.outputWriter.ThrowTerminatingError(new ErrorRecord( - new ArgumentException("Invalid Range", nameof(range)), - "FIX_ERROR", - ErrorCategory.InvalidArgument, - range)); + new ArgumentException( + "Invalid Range", + nameof(range)), + "FIX_ERROR", + ErrorCategory.InvalidArgument, + range)); } range = isRangeNull ? null : SnapToEdges(text, range); - var previousLineCount = text.LineCount; - var previousUnusedCorrections = 0; + int previousLineCount = text.LineCount; + int previousUnusedCorrections = 0; do { - var records = AnalyzeScriptDefinition(text.ToString()); + IEnumerable records = AnalyzeScriptDefinition(text.ToString()); var corrections = records .Select(r => r.SuggestedCorrections) .Where(sc => sc != null && sc.Any()) @@ -1598,9 +1602,8 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange, .ToList(); this.outputWriter.WriteVerbose($"Found {corrections.Count} violations."); - int unusedCorrections; - Fix(text, corrections, out unusedCorrections); - var numberOfFixedViolatons = corrections.Count - unusedCorrections; + Fix(text, corrections, out int unusedCorrections); + int numberOfFixedViolatons = corrections.Count - unusedCorrections; fixesWereApplied = numberOfFixedViolatons > 0; this.outputWriter.WriteVerbose($"Fixed {numberOfFixedViolatons} violations."); @@ -1616,7 +1619,7 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange, } previousUnusedCorrections = unusedCorrections; - var lineCount = text.LineCount; + int lineCount = text.LineCount; if (!isRangeNull && lineCount != previousLineCount) { range = new Range( @@ -1632,6 +1635,65 @@ public EditableText Fix(EditableText text, Range range, out Range updatedRange, return text; } + internal string Fix(string scriptContent, Range fixRange) + { + if (scriptContent == null) + { + throw new ArgumentNullException(nameof(scriptContent)); + } + + var scriptText = new TextDocumentBuilder(scriptContent); + + if (fixRange != null) + { + var fixTextRange = new TextRange( + new TextPosition(fixRange.Start.Line, fixRange.Start.Column), + new TextPosition(fixRange.End.Line, fixRange.End.Column)); + + if (!scriptText.IsValidRange(fixTextRange)) + { + this.outputWriter.ThrowTerminatingError(new ErrorRecord( + new ArgumentException( + "Invalid Range", + nameof(fixRange)), + "FIX_ERROR", + ErrorCategory.InvalidArgument, + fixRange)); + } + } + + var uniqueCorrections = new HashSet(s_correctionComparer); + foreach (DiagnosticRecord record in AnalyzeScriptDefinition(scriptText.ToString())) + { + if (record.SuggestedCorrections == null + || !record.SuggestedCorrections.Any()) + { + continue; + } + + CorrectionExtent correction = record.SuggestedCorrections.First(); + + if (fixRange != null + && (fixRange.Start < correction.Start || fixRange.End > correction.End)) + { + continue; + } + + uniqueCorrections.Add(correction); + } + + var corrections = new List(uniqueCorrections); + corrections.Sort(s_correctionComparer); + + this.outputWriter.WriteVerbose($"Found {corrections.Count} violations."); + + scriptText.ApplyCorrections(corrections); + + this.outputWriter.WriteVerbose($"Fixed {corrections.Count} violations."); + + return scriptText.ToString(); + } + private static Encoding GetFileEncoding(string path) { using (var stream = new FileStream(path, FileMode.Open)) diff --git a/Engine/text.cs b/Engine/text.cs new file mode 100644 index 000000000..c825e2490 --- /dev/null +++ b/Engine/text.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + internal struct TextPosition + { + public TextPosition(int line, int column) + { + Line = line; + Column = column; + } + + public int Line { get; } + + public int Column { get; } + } + + internal struct TextRange + { + public TextRange(TextPosition start, TextPosition end) + { + Start = start; + End = end; + } + + public TextPosition Start { get; } + + public TextPosition End { get; } + } + + internal class TextDocumentBuilder + { + private class CharBuffer + { + private char[] _charArray; + + private int _validLength; + + public CharBuffer() + { + _charArray = new char[128]; + _validLength = 0; + } + + public void CopyFrom(string content, int startIndex, int length) + { + while (length > _charArray.Length) + { + _charArray = new char[_charArray.Length * 2]; + } + + content.CopyTo(startIndex, _charArray, 0, length); + _validLength = length; + } + + public void CopyTo(StringBuilder buffer) + { + buffer.Append(_charArray, 0, _validLength); + } + } + + private readonly static char s_newlineStart = Environment.NewLine[0]; + + private readonly CharBuffer _spanBuffer; + + private string _content; + + public TextDocumentBuilder(string content) + { + _content = content; + _spanBuffer = new CharBuffer(); + } + + public override string ToString() + { + return _content; + } + + public TextRange GetValidColumnIndexRange(TextRange textRange) + { + return new TextRange( + new TextPosition(textRange.Start.Line - 1, Math.Min(1, textRange.Start.Column - 1)), + new TextPosition(textRange.End.Line - 1, Math.Max(textRange.End.Column - 1, GetLastColumnLength()))); + } + + public bool IsValidRange(TextRange range) + { + return range.Start.Line <= range.End.Line + && range.End.Line <= GetLineCount() + 1 + && range.Start.Column <= GetColumnLength(range.Start.Line) + 1 + && range.End.Column <= GetColumnLength(range.End.Line) + 1; + } + + public void ApplyCorrections(IReadOnlyList corrections) + { + var newContent = new StringBuilder(_content.Length); + var effectiveOldPosition = new TextPosition(0, 0); + int currentIndex = 0; + + foreach (CorrectionExtent correction in corrections) + { + var correctionStartPosition = new TextPosition(correction.StartLineNumber - 1, correction.StartColumnNumber - 1); + ReadNextSpan(ref currentIndex, effectiveOldPosition, correctionStartPosition); + _spanBuffer.CopyTo(newContent); + newContent.Append(correction.Text); + currentIndex += correction.Text.Length; + effectiveOldPosition = new TextPosition(correction.EndLineNumber - 1, correction.EndColumnNumber - 1); + } + ReadToEnd(currentIndex); + _spanBuffer.CopyTo(newContent); + _content = newContent.ToString(); + } + + private void ReadNextSpan(ref int index, TextPosition effectiveOldPosition, TextPosition correctionStartPosition) + { + int linesToRead = correctionStartPosition.Line - effectiveOldPosition.Line; + int nextIndex = _content.IndexOf(Environment.NewLine, index, linesToRead) + correctionStartPosition.Column; + _spanBuffer.CopyFrom(_content, index, nextIndex - index); + index = nextIndex; + } + + private void ReadToEnd(int currentIndex) + { + _spanBuffer.CopyFrom(_content, currentIndex, _content.Length - currentIndex); + } + + private int GetColumnLength(int lineNumber) + { + int lineIndex = GetLineIndex(lineNumber); + return _content.IndexOf(Environment.NewLine, lineIndex); + } + + private int GetLastColumnLength() + { + return _content.Length - _content.LastIndexOf(Environment.NewLine); + } + + private int GetLineCount() + { + int lineCount = 0; + foreach (char c in _content) + { + if (c == '\n') { lineCount++; } + } + return lineCount; + } + + private int GetLineIndex(int lineNumber) + { + return _content.IndexOf(Environment.NewLine, 0, lineNumber); + } + } +} \ No newline at end of file From 165321275b2c41caf6251a03d99d8b184a220f08 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 1 Jul 2019 19:29:32 -0700 Subject: [PATCH 2/3] Fix string logic --- Engine/Formatter.cs | 34 ++++++++++++- Engine/Generic/CorrectionExtent.cs | 10 ++++ Engine/ScriptAnalyzer.cs | 79 +++++++++++++++++++++++------- Engine/text.cs | 74 +++++++++++++++++++++++----- 4 files changed, 166 insertions(+), 31 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 1f55c21a1..7ddc4402c 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Management.Automation; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { @@ -49,7 +50,20 @@ public static string Format( ScriptAnalyzer.Instance.UpdateSettings(currentSettings); ScriptAnalyzer.Instance.Initialize(cmdlet, includeDefaultRules: true); - return ScriptAnalyzer.Instance.Fix(scriptDefinition, range); + try + { + return ScriptAnalyzer.Instance.Fix(scriptDefinition, range); + } + catch (FormattingException e) + { + cmdlet.ThrowTerminatingError( + new ErrorRecord( + e, + "FIX_ERROR", + ErrorCategory.InvalidOperation, + e.Corrections)); + return null; + } } private static void ValidateNotNull(object obj, string name) @@ -65,7 +79,10 @@ private static Settings GetCurrentSettings(Settings settings) var ruleSettings = new Hashtable(); foreach (string rule in s_formattingRulesInOrder) { - ruleSettings[rule] = new Hashtable(settings.RuleArguments[rule]); + if (settings.RuleArguments.TryGetValue(rule, out Dictionary ruleConfiguration)) + { + ruleSettings[rule] = ruleConfiguration; + } } return new Settings(new Hashtable() @@ -75,4 +92,17 @@ private static Settings GetCurrentSettings(Settings settings) }); } } + + public class FormattingException : Exception + { + public FormattingException( + string message, + IReadOnlyList corrections) + : base(message) + { + Corrections = corrections; + } + + public IReadOnlyList Corrections { get; } + } } diff --git a/Engine/Generic/CorrectionExtent.cs b/Engine/Generic/CorrectionExtent.cs index 979e92c0a..09486a059 100644 --- a/Engine/Generic/CorrectionExtent.cs +++ b/Engine/Generic/CorrectionExtent.cs @@ -130,6 +130,16 @@ public int Compare(CorrectionExtent x, CorrectionExtent y) return -1; } + if (x.Text.Length > y.Text.Length) + { + return 1; + } + + if (x.Text.Length < y.Text.Length) + { + return -1; + } + return 0; } diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 3a37346f2..995a28852 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -1647,8 +1647,8 @@ internal string Fix(string scriptContent, Range fixRange) if (fixRange != null) { var fixTextRange = new TextRange( - new TextPosition(fixRange.Start.Line, fixRange.Start.Column), - new TextPosition(fixRange.End.Line, fixRange.End.Column)); + new TextPosition(fixRange.Start.Line - 1, fixRange.Start.Column - 1), + new TextPosition(fixRange.End.Line - 1, fixRange.End.Column - 1)); if (!scriptText.IsValidRange(fixTextRange)) { @@ -1662,34 +1662,77 @@ internal string Fix(string scriptContent, Range fixRange) } } - var uniqueCorrections = new HashSet(s_correctionComparer); - foreach (DiagnosticRecord record in AnalyzeScriptDefinition(scriptText.ToString())) + int unappliedCorrectionCount = -1; + int previousUnappliedCorrections = -1; + int fixCount = 0; + do { - if (record.SuggestedCorrections == null - || !record.SuggestedCorrections.Any()) + previousUnappliedCorrections = unappliedCorrectionCount; + unappliedCorrectionCount = 0; + + // First filter out diagnostics with no corrections, + // or ones not applied in the valid range + var possiblyOverlappingCorrections = new List(); + foreach (DiagnosticRecord record in AnalyzeScriptDefinition(scriptText.ToString())) { - continue; + if (record.SuggestedCorrections == null + || !record.SuggestedCorrections.Any()) + { + continue; + } + + CorrectionExtent correction = record.SuggestedCorrections.First(); + + if (fixRange != null + && !(correction.Start >= fixRange.Start && correction.End <= fixRange.End)) + { + continue; + } + + possiblyOverlappingCorrections.Add(correction); } - CorrectionExtent correction = record.SuggestedCorrections.First(); + // We now need the list to be sorted for a second pass + possiblyOverlappingCorrections.Sort(s_correctionComparer); - if (fixRange != null - && (fixRange.Start < correction.Start || fixRange.End > correction.End)) + // Remove corrections that lie within the range of a predecessor. + // The sorting function we use + CorrectionExtent previousCorrection = null; + var corrections = new List(possiblyOverlappingCorrections.Count); + var unappliedCorrections = new List(); + foreach (CorrectionExtent correction in possiblyOverlappingCorrections) { - continue; + if (previousCorrection != null + && (correction.Start >= previousCorrection.Start && correction.Start <= previousCorrection.End + || correction.End >= previousCorrection.Start && correction.End <= previousCorrection.End)) + { + unappliedCorrectionCount++; + continue; + } + + corrections.Add(correction); + previousCorrection = correction; } - uniqueCorrections.Add(correction); - } + if (unappliedCorrectionCount > 0 + && unappliedCorrectionCount == previousUnappliedCorrections) + { + throw new FormattingException( + "Unable to apply all fixes to script", + unappliedCorrections); + } - var corrections = new List(uniqueCorrections); - corrections.Sort(s_correctionComparer); + this.outputWriter.WriteVerbose($"Found {corrections.Count} violations."); - this.outputWriter.WriteVerbose($"Found {corrections.Count} violations."); + if (corrections.Count > 0) + { + fixCount += corrections.Count; + scriptText.ApplyCorrections(corrections); + } - scriptText.ApplyCorrections(corrections); + } while (unappliedCorrectionCount > 0); - this.outputWriter.WriteVerbose($"Fixed {corrections.Count} violations."); + this.outputWriter.WriteVerbose($"Fixed {fixCount} violations."); return scriptText.ToString(); } diff --git a/Engine/text.cs b/Engine/text.cs index c825e2490..a096bd08a 100644 --- a/Engine/text.cs +++ b/Engine/text.cs @@ -50,9 +50,15 @@ public CharBuffer() public void CopyFrom(string content, int startIndex, int length) { - while (length > _charArray.Length) + if (length > _charArray.Length) { - _charArray = new char[_charArray.Length * 2]; + int newArrayLength = _charArray.Length; + do + { + newArrayLength *= 2; + } while (length > newArrayLength); + + _charArray = new char[newArrayLength]; } content.CopyTo(startIndex, _charArray, 0, length); @@ -106,28 +112,69 @@ public void ApplyCorrections(IReadOnlyList corrections) foreach (CorrectionExtent correction in corrections) { var correctionStartPosition = new TextPosition(correction.StartLineNumber - 1, correction.StartColumnNumber - 1); - ReadNextSpan(ref currentIndex, effectiveOldPosition, correctionStartPosition); - _spanBuffer.CopyTo(newContent); + CopyNextSpan(ref currentIndex, newContent, effectiveOldPosition, correctionStartPosition); newContent.Append(correction.Text); - currentIndex += correction.Text.Length; + currentIndex += GetContentReplacedLength( + currentIndex, + correctionStartPosition, + new TextPosition(correction.EndLineNumber - 1, correction.EndColumnNumber - 1)); effectiveOldPosition = new TextPosition(correction.EndLineNumber - 1, correction.EndColumnNumber - 1); } - ReadToEnd(currentIndex); - _spanBuffer.CopyTo(newContent); + CopyToEnd(currentIndex, newContent); _content = newContent.ToString(); } - private void ReadNextSpan(ref int index, TextPosition effectiveOldPosition, TextPosition correctionStartPosition) + private int GetContentReplacedLength(int startIndex, TextPosition startPosition, TextPosition endPosition) + { + int linesToRead = endPosition.Line - startPosition.Line; + int index = startIndex; + + if (linesToRead == 0) + { + return endPosition.Column - startPosition.Column; + } + + for (int i = 0; i < linesToRead; i++) + { + index = _content.IndexOf(Environment.NewLine, index) + Environment.NewLine.Length; + } + return index - startIndex + endPosition.Column; + } + + private void CopyNextSpan( + ref int index, + StringBuilder destinationBuffer, + TextPosition effectiveOldPosition, + TextPosition correctionStartPosition) { + // Seek from the current index to the start of the next correction + int nextIndex = index; int linesToRead = correctionStartPosition.Line - effectiveOldPosition.Line; - int nextIndex = _content.IndexOf(Environment.NewLine, index, linesToRead) + correctionStartPosition.Column; + if (linesToRead == 0) + { + nextIndex += correctionStartPosition.Column - effectiveOldPosition.Column; + } + else + { + for (int i = 0; i < linesToRead; i++) + { + nextIndex = _content.IndexOf(Environment.NewLine, nextIndex) + Environment.NewLine.Length; + } + nextIndex += correctionStartPosition.Column; + } + + // Copy the characters over _spanBuffer.CopyFrom(_content, index, nextIndex - index); + _spanBuffer.CopyTo(destinationBuffer); + + // Update the index index = nextIndex; } - private void ReadToEnd(int currentIndex) + private void CopyToEnd(int currentIndex, StringBuilder destinationBuffer) { _spanBuffer.CopyFrom(_content, currentIndex, _content.Length - currentIndex); + _spanBuffer.CopyTo(destinationBuffer); } private int GetColumnLength(int lineNumber) @@ -153,7 +200,12 @@ private int GetLineCount() private int GetLineIndex(int lineNumber) { - return _content.IndexOf(Environment.NewLine, 0, lineNumber); + int index = 0; + for (int i = 0; i < lineNumber; i++) + { + index = _content.IndexOf(Environment.NewLine, index); + } + return index; } } } \ No newline at end of file From 0693d9ce601df8bb8ea55b983f4bddd775745275 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Tue, 2 Jul 2019 21:13:01 -0700 Subject: [PATCH 3/3] Fix issues with cross-plat newlines --- Engine/text.cs | 32 +++++++++++++++---- .../Query/RuntimeData.cs | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Engine/text.cs b/Engine/text.cs index a096bd08a..e76c0c936 100644 --- a/Engine/text.cs +++ b/Engine/text.cs @@ -71,7 +71,11 @@ public void CopyTo(StringBuilder buffer) } } - private readonly static char s_newlineStart = Environment.NewLine[0]; + private static readonly char[] s_newlineStartChars = new [] + { + '\r', + '\n' + }; private readonly CharBuffer _spanBuffer; @@ -136,7 +140,7 @@ private int GetContentReplacedLength(int startIndex, TextPosition startPosition, for (int i = 0; i < linesToRead; i++) { - index = _content.IndexOf(Environment.NewLine, index) + Environment.NewLine.Length; + ScanToNextLine(ref index); } return index - startIndex + endPosition.Column; } @@ -158,7 +162,7 @@ private void CopyNextSpan( { for (int i = 0; i < linesToRead; i++) { - nextIndex = _content.IndexOf(Environment.NewLine, nextIndex) + Environment.NewLine.Length; + ScanToNextLine(ref nextIndex); } nextIndex += correctionStartPosition.Column; } @@ -171,6 +175,22 @@ private void CopyNextSpan( index = nextIndex; } + private void ScanToNextLine(ref int index) + { + index = _content.IndexOfAny(s_newlineStartChars, index); + + char c = _content[index]; + if (c == '\n') + { + index++; + return; + } + + // Must be looking at "\r\n" + index += 2; + return; + } + private void CopyToEnd(int currentIndex, StringBuilder destinationBuffer) { _spanBuffer.CopyFrom(_content, currentIndex, _content.Length - currentIndex); @@ -180,12 +200,12 @@ private void CopyToEnd(int currentIndex, StringBuilder destinationBuffer) private int GetColumnLength(int lineNumber) { int lineIndex = GetLineIndex(lineNumber); - return _content.IndexOf(Environment.NewLine, lineIndex); + return _content.IndexOfAny(s_newlineStartChars, lineIndex); } private int GetLastColumnLength() { - return _content.Length - _content.LastIndexOf(Environment.NewLine); + return _content.Length - _content.LastIndexOfAny(s_newlineStartChars); } private int GetLineCount() @@ -203,7 +223,7 @@ private int GetLineIndex(int lineNumber) int index = 0; for (int i = 0; i < lineNumber; i++) { - index = _content.IndexOf(Environment.NewLine, index); + ScanToNextLine(ref index); } return index; } diff --git a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/RuntimeData.cs b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/RuntimeData.cs index 98db0616c..2fcf4c693 100644 --- a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/RuntimeData.cs +++ b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/RuntimeData.cs @@ -128,7 +128,7 @@ private static IReadOnlyDictionary> CreateAli IReadOnlyDictionary> modules, IReadOnlyDictionary> commands) { - var aliasTable = new Dictionary>(); + var aliasTable = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> module in modules) { foreach (KeyValuePair moduleVersion in module.Value)