Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
Position.cs817 linesDownload Raw Back to parser
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Diagnostics.CodeAnalysis;5using System.Globalization;6using System.Text;7#if !STANDALONE8using System.Management.Automation.Internal;9#endif10 11namespace System.Management.Automation.Language12{13    #region Public Interfaces14 15    /// <summary>16    /// Represents a single point in a script.  The script may come from a file or interactive input.17    /// </summary>18#nullable enable19    public interface IScriptPosition20    {21        /// <summary>22        /// The name of the file, or if the script did not come from a file, then null.23        /// </summary>24        string? File { get; }25 26        /// <summary>27        /// The line number of the position, with the value 1 being the first line.28        /// </summary>29        int LineNumber { get; }30 31        /// <summary>32        /// The column number of the position, with the value 1 being the first column.33        /// </summary>34        int ColumnNumber { get; }35 36        /// <summary>37        /// The offset from the beginning of the script.38        /// </summary>39        int Offset { get; }40 41        /// <summary>42        /// The complete text of the line that this position is included on.43        /// </summary>44        string Line { get; }45 46        /// <summary>47        /// The complete script that this position is included in.48        /// </summary>49        string? GetFullScript();50    }51#nullable restore52 53    /// <summary>54    /// Represents the a span of text in a script.55    /// </summary>56#nullable enable57    public interface IScriptExtent58    {59        /// <summary>60        /// The filename the extent includes, or null if the extent is not included in any file.61        /// </summary>62        string? File { get; }63 64        /// <summary>65        /// The starting position of the extent.66        /// </summary>67        IScriptPosition StartScriptPosition { get; }68 69        /// <summary>70        /// The end position of the extent.  This position is actually 1 character past the end of the extent.71        /// </summary>72        IScriptPosition EndScriptPosition { get; }73 74        /// <summary>75        /// The line number at the beginning of the extent, with the value 1 being the first line.76        /// </summary>77        int StartLineNumber { get; }78 79        /// <summary>80        /// The column number at the beginning of the extent, with the value 1 being the first column.81        /// </summary>82        int StartColumnNumber { get; }83 84        /// <summary>85        /// The line number at the end of the extent, with the value 1 being the first line.86        /// </summary>87        int EndLineNumber { get; }88 89        /// <summary>90        /// The column number at the end of the extent, with the value 1 being the first column.91        /// </summary>92        int EndColumnNumber { get; }93 94        /// <summary>95        /// The script text that the extent includes.96        /// </summary>97        string Text { get; }98 99        /// <summary>100        /// The starting offset of the extent.101        /// </summary>102        int StartOffset { get; }103 104        /// <summary>105        /// The ending offset of the extent.106        /// </summary>107        int EndOffset { get; }108    }109#nullable restore110 111    /// <summary>112    /// A few utility functions for script positions.113    /// </summary>114    internal static class PositionUtilities115    {116        /// <summary>117        /// Return a unique position representing an empty or missing position.118        /// </summary>119        public static IScriptPosition EmptyPosition { get; } = new EmptyScriptPosition();120 121        /// <summary>122        /// Return a unique extent representing an empty or missing extent.123        /// </summary>124        public static IScriptExtent EmptyExtent { get; } = new EmptyScriptExtent();125 126        /// <summary>127        /// Return a message that looks like:128        ///129        ///     At {filename}:{line} char:{column}130        ///     + $x + @y131        ///     +    ~132        /// </summary>133        internal static string VerboseMessage(IScriptExtent position)134        {135            if (PositionUtilities.EmptyExtent.Equals(position))136            {137                return string.Empty;138            }139 140            string fileName = position.File;141            if (string.IsNullOrEmpty(fileName))142            {143                fileName = ParserStrings.TextForWordLine;144            }145 146            string sourceLine = position.StartScriptPosition.Line.TrimEnd();147 148            string message = string.Empty;149            if (!string.IsNullOrEmpty(sourceLine))150            {151                int spacesBeforeError = position.StartColumnNumber - 1;152                int errorLength = (position.StartLineNumber == position.EndLineNumber && position.EndColumnNumber <= sourceLine.Length + 1)153                                      ? position.EndColumnNumber - position.StartColumnNumber154                                      : sourceLine.Length - position.StartColumnNumber + 1;155 156                // Expand tabs before figuring out if we need to truncate the line157                if (sourceLine.Contains('\t'))158                {159                    var copyLine = new StringBuilder(sourceLine.Length * 2);160 161                    var beforeError = sourceLine.Substring(0, spacesBeforeError).Replace("\t", "    ");162                    var error = sourceLine.Substring(spacesBeforeError, errorLength).Replace("\t", "    ");163 164                    copyLine.Append(beforeError);165                    copyLine.Append(error);166                    copyLine.Append(sourceLine.Substring(spacesBeforeError + errorLength).Replace("\t", "    "));167 168                    spacesBeforeError = beforeError.Length;169                    errorLength = error.Length;170                    sourceLine = copyLine.ToString();171                }172 173                // Max width is 69 because:174                //   * sometimes PowerShell is opened with width 80175                //   * we always prepend "+ "176                //   * we sometimes prepend "... "177                //   * we sometimes append " ..."178                //   * wrapping kicks in if we hit the width exactly, so -1 to avoid that.179                const int maxLineLength = 69;180                bool needsPrefixDots = false;181                bool needsSuffixDots = false;182 183                int lineLength = sourceLine.Length;184                var sb = new StringBuilder(sourceLine.Length * 2 + 4);185                if (lineLength > maxLineLength)186                {187                    // Need to truncate - include as much of the error as we can, but with188                    // some preceding context if possible.189 190                    int totalPrefix = spacesBeforeError;191                    int prefix = Math.Min(totalPrefix, 12);192 193                    int totalSuffix = lineLength - errorLength - spacesBeforeError;194                    int suffix = Math.Min(totalSuffix, 8);195 196                    int candidateLength = prefix + errorLength + suffix;197                    if (candidateLength >= maxLineLength)198                    {199                        // Too long.  The suffix is truncated automatically by200                        // the Substring call, but we might need some of the201                        // squiggles removed as well.202                        if (prefix + errorLength >= maxLineLength)203                        {204                            errorLength = maxLineLength - prefix;205                        }206 207                        needsSuffixDots = true;208                    }209                    else210                    {211                        // We can shift prefix to suffix or vice versa to fill in212                        // more of the line.  Prefer shifting to prefix.213                        int prefixAvailable = totalPrefix - prefix;214                        if (prefixAvailable > 0)215                        {216                            prefix += Math.Min(prefixAvailable, maxLineLength - candidateLength);217                            candidateLength = prefix + errorLength + suffix;218                        }219 220                        if (candidateLength < maxLineLength && totalSuffix > 0)221                        {222                            suffix += Math.Min(totalSuffix, maxLineLength - candidateLength);223                        }224 225                        needsSuffixDots = (suffix < totalSuffix);226                    }227 228                    needsPrefixDots = (prefix < totalPrefix);229 230                    var startIndex = Math.Max(spacesBeforeError - prefix, 0);231                    sourceLine = sourceLine.Substring(startIndex, maxLineLength);232                    spacesBeforeError = Math.Min(spacesBeforeError, prefix);233                    errorLength = Math.Min(errorLength, maxLineLength - spacesBeforeError);234                }235 236                if (needsPrefixDots)237                {238                    sb.Append("\u2026 "); // Unicode ellipsis character239                }240 241                sb.Append(sourceLine);242 243                if (needsSuffixDots)244                {245                    sb.Append(" \u2026"); // Unicode ellipsis character246                }247 248                sb.Append(Environment.NewLine);249                sb.Append("+ ");250                sb.Append(' ', spacesBeforeError + (needsPrefixDots ? 2 : 0));251                // errorLength of 0 happens at EOF - always write out 1.252                sb.Append('~', errorLength > 0 ? errorLength : 1);253 254                message = sb.ToString();255            }256 257            return StringUtil.Format(258                ParserStrings.TextForPositionMessage,259                fileName,260                position.StartLineNumber,261                position.StartColumnNumber,262                message);263        }264 265        /// <summary>266        /// Return a message that looks like:267        ///     12+ $x + &lt;&lt;&lt;&lt; $b.268        /// </summary>269        internal static string BriefMessage(IScriptPosition position)270        {271            StringBuilder message = new StringBuilder(position.Line);272            if (position.ColumnNumber > message.Length)273            {274                message.Append(" <<<< ");275            }276            else277            {278                message.Insert(position.ColumnNumber - 1, " >>>> ");279            }280 281            return StringUtil.Format(ParserStrings.TraceScriptLineMessage, position.LineNumber, message.ToString());282        }283 284        internal static IScriptExtent NewScriptExtent(IScriptExtent start, IScriptExtent end)285        {286            if (start == end)287            {288                return start;289            }290 291            if (start == EmptyExtent)292            {293                return end;294            }295 296            if (end == EmptyExtent)297            {298                return start;299            }300 301            InternalScriptExtent startExtent = start as InternalScriptExtent;302            InternalScriptExtent endExtent = end as InternalScriptExtent;303            Diagnostics.Assert(startExtent != null && endExtent != null, "This function only handles internal and empty extents");304            Diagnostics.Assert(startExtent.PositionHelper == endExtent.PositionHelper, "Extents must be from same source");305 306            return new InternalScriptExtent(startExtent.PositionHelper, startExtent.StartOffset, endExtent.EndOffset);307        }308 309        internal static bool IsBefore(this IScriptExtent extentToTest, IScriptExtent startExtent)310        {311            if (extentToTest.EndLineNumber < startExtent.StartLineNumber)312            {313                return true;314            }315 316            if (extentToTest.EndLineNumber == startExtent.StartLineNumber)317            {318                return extentToTest.EndColumnNumber <= startExtent.StartColumnNumber;319            }320 321            return false;322        }323 324        internal static bool IsAfter(this IScriptExtent extentToTest, IScriptExtent endExtent)325        {326            if (extentToTest.StartLineNumber > endExtent.EndLineNumber)327            {328                return true;329            }330 331            if (extentToTest.StartLineNumber == endExtent.EndLineNumber)332            {333                return extentToTest.StartColumnNumber >= endExtent.EndColumnNumber;334            }335 336            return false;337        }338 339        internal static bool IsWithin(this IScriptExtent extentToTest, IScriptExtent extent)340        {341            return extentToTest.StartOffset >= extent.StartOffset && extentToTest.EndOffset <= extent.EndOffset;342        }343 344        internal static bool IsAfter(this IScriptExtent extent, int line, int column)345        {346            if (line < extent.StartLineNumber)347                return true;348 349            return (line == extent.StartLineNumber && column < extent.StartColumnNumber);350        }351 352        internal static bool ContainsLineAndColumn(this IScriptExtent extent, int line, int column)353        {354            if (extent.StartLineNumber == line)355            {356                if (column == 0)357                {358                    return true;359                }360 361                if (column >= extent.StartColumnNumber)362                {363                    if (extent.EndLineNumber != extent.StartLineNumber)364                    {365                        return true;366                    }367 368                    return (column < extent.EndColumnNumber);369                }370 371                return false;372            }373 374            if (extent.StartLineNumber > line)375                return false;376 377            if (line > extent.EndLineNumber)378                return false;379 380            if (extent.EndLineNumber == line)381                return column < extent.EndColumnNumber;382 383            return true;384        }385    }386 387    #endregion Public Interfaces388 389    #region Internal Position390 391    internal class PositionHelper392    {393        private int[] _lineStartMap;394 395        internal PositionHelper(string filename, string scriptText)396        {397            File = filename;398            ScriptText = scriptText;399        }400 401        internal string ScriptText { get; }402 403        internal int[] LineStartMap404        {405            set { _lineStartMap = value; }406        }407 408        public string File { get; }409 410        internal int LineFromOffset(int offset)411        {412            int line = Array.BinarySearch<int>(_lineStartMap, offset);413            if (line < 0)414            {415                line = ~line - 1;416            }417 418            return line + 1;419        }420 421        internal int ColumnFromOffset(int offset)422        {423            return offset - _lineStartMap[LineFromOffset(offset) - 1] + 1;424        }425 426        internal string Text(int line)427        {428            int start = _lineStartMap[line - 1];429            if (line < _lineStartMap.Length)430            {431                int length = _lineStartMap[line] - start;432                return ScriptText.Substring(start, length);433            }434 435            return ScriptText.Substring(start);436        }437    }438 439    internal sealed class InternalScriptPosition : IScriptPosition440    {441        private readonly PositionHelper _positionHelper;442 443        internal InternalScriptPosition(PositionHelper _positionHelper, int offset)444        {445            this._positionHelper = _positionHelper;446            Offset = offset;447        }448 449        public string File { get { return _positionHelper.File; } }450 451        public int LineNumber { get { return _positionHelper.LineFromOffset(Offset); } }452 453        public int ColumnNumber { get { return _positionHelper.ColumnFromOffset(Offset); } }454 455        public string Line { get { return _positionHelper.Text(LineNumber); } }456 457        public int Offset { get; }458 459        internal InternalScriptPosition CloneWithNewOffset(int offset)460        {461            return new InternalScriptPosition(_positionHelper, offset);462        }463 464        public string GetFullScript()465        {466            return _positionHelper.ScriptText;467        }468    }469 470    internal sealed class InternalScriptExtent : IScriptExtent471    {472        internal InternalScriptExtent(PositionHelper _positionHelper, int startOffset, int endOffset)473        {474            this.PositionHelper = _positionHelper;475            StartOffset = startOffset;476            EndOffset = endOffset;477        }478 479        public string File480        {481            get { return PositionHelper.File; }482        }483 484        public IScriptPosition StartScriptPosition485        {486            get { return new InternalScriptPosition(PositionHelper, StartOffset); }487        }488 489        public IScriptPosition EndScriptPosition490        {491            get { return new InternalScriptPosition(PositionHelper, EndOffset); }492        }493 494        public int StartLineNumber495        {496            get { return PositionHelper.LineFromOffset(StartOffset); }497        }498 499        public int StartColumnNumber500        {501            get { return PositionHelper.ColumnFromOffset(StartOffset); }502        }503 504        public int EndLineNumber505        {506            get { return PositionHelper.LineFromOffset(EndOffset); }507        }508 509        public int EndColumnNumber510        {511            get { return PositionHelper.ColumnFromOffset(EndOffset); }512        }513 514        public string Text515        {516            get517            {518                // StartOffset can be > the length for the EOF token.519                if (StartOffset > PositionHelper.ScriptText.Length)520                {521                    return string.Empty;522                }523 524                return PositionHelper.ScriptText.Substring(StartOffset, EndOffset - StartOffset);525            }526        }527 528        public override string ToString()529        {530            return Text;531        }532 533        internal PositionHelper PositionHelper { get; }534 535        public int StartOffset { get; }536 537        public int EndOffset { get; }538    }539 540    #endregion Internal Position541 542    #region "Empty" Positions543 544    internal sealed class EmptyScriptPosition : IScriptPosition545    {546        public string File { get { return null; } }547 548        public int LineNumber { get { return 0; } }549 550        public int ColumnNumber { get { return 0; } }551 552        public int Offset { get { return 0; } }553 554        public string Line { get { return string.Empty; } }555 556        public string GetFullScript() { return null; }557    }558 559    internal sealed class EmptyScriptExtent : IScriptExtent560    {561        public string File { get { return null; } }562 563        public IScriptPosition StartScriptPosition { get { return PositionUtilities.EmptyPosition; } }564 565        public IScriptPosition EndScriptPosition { get { return PositionUtilities.EmptyPosition; } }566 567        public int StartLineNumber { get { return 0; } }568 569        public int StartColumnNumber { get { return 0; } }570 571        public int EndLineNumber { get { return 0; } }572 573        public int EndColumnNumber { get { return 0; } }574 575        public int StartOffset { get { return 0; } }576 577        public int EndOffset { get { return 0; } }578 579        public string Text { get { return string.Empty; } }580 581        public override bool Equals(object obj)582        {583            if (!(obj is IScriptExtent otherPosition))584            {585                return false;586            }587 588            if ((string.IsNullOrEmpty(otherPosition.File)) &&589                (otherPosition.StartLineNumber == StartLineNumber) &&590                (otherPosition.StartColumnNumber == StartColumnNumber) &&591                (otherPosition.EndLineNumber == EndLineNumber) &&592                (otherPosition.EndColumnNumber == EndColumnNumber) &&593                (string.IsNullOrEmpty(otherPosition.Text)))594            {595                return true;596            }597            else598            {599                return false;600            }601        }602 603        public override int GetHashCode()604        {605            return base.GetHashCode();606        }607    }608 609    #endregion "Empty" Positions610 611    /// <summary>612    /// Represents a single point in a script.  The script may come from a file or interactive input.613    /// </summary>614    public sealed class ScriptPosition : IScriptPosition615    {616        private readonly string _fullScript;617 618        /// <summary>619        /// Creates a new script position, which represents a point in a script.620        /// </summary>621        /// <param name="scriptName">The name of the file, or if the script did not come from a file, then null.</param>622        /// <param name="scriptLineNumber">The line number of the position, with the value 1 being the first line.</param>623        /// <param name="offsetInLine">The column number of the position, with the value 1 being the first column.</param>624        /// <param name="line">The complete text of the line that this position is included on.</param>625        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")]626        public ScriptPosition(string scriptName, int scriptLineNumber, int offsetInLine, string line)627        {628            File = scriptName;629            LineNumber = scriptLineNumber;630            ColumnNumber = offsetInLine;631 632            if (string.IsNullOrEmpty(line))633            {634                Line = string.Empty;635            }636            else637            {638                Line = line;639            }640        }641 642        /// <summary>643        /// Creates a new script position, which represents a point in a script.644        /// </summary>645        /// <param name="scriptName">The name of the file, or if the script did not come from a file, then null.</param>646        /// <param name="scriptLineNumber">The line number of the position, with the value 1 being the first line.</param>647        /// <param name="offsetInLine">The column number of the position, with the value 1 being the first column.</param>648        /// <param name="line">The complete text of the line that this position is included on.</param>649        /// <param name="fullScript">The complete script text.  Optional, can be null.</param>650        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")]651        public ScriptPosition(652            string scriptName,653            int scriptLineNumber,654            int offsetInLine,655            string line,656            string fullScript)657            : this(scriptName, scriptLineNumber, offsetInLine, line)658        {659            _fullScript = fullScript;660        }661 662        /// <summary>663        /// The name of the file, or if the script did not come from a file, then null.664        /// </summary>665        public string File { get; }666 667        /// <summary>668        /// The line number of the position, with the value 1 being the first line.669        /// </summary>670        public int LineNumber { get; }671 672        /// <summary>673        /// The column number of the position, with the value 1 being the first column.674        /// </summary>675        public int ColumnNumber { get; }676 677        /// <summary>678        /// The offset from the beginning of the script, always return 0.679        /// </summary>680        public int Offset { get { return 0; } }681 682        /// <summary>683        /// The complete text of the line that this position is included on.684        /// </summary>685        public string Line { get; }686 687        /// <summary>688        /// The complete script that this position is included in.689        /// </summary>690        public string GetFullScript() { return _fullScript; }691    }692 693    /// <summary>694    /// A script extent used to customize the display of error location information.695    /// </summary>696    public sealed class ScriptExtent : IScriptExtent697    {698        private ScriptPosition _startPosition;699        private ScriptPosition _endPosition;700 701        private ScriptExtent()702        {703        }704 705        /// <summary>706        /// Creates a new ScriptExtent class.707        /// </summary>708        public ScriptExtent(ScriptPosition startPosition, ScriptPosition endPosition)709        {710            _startPosition = startPosition;711            _endPosition = endPosition;712        }713 714        /// <summary>715        /// The name of the file, or if the script did not come from a file, then null.716        /// </summary>717        public string File { get { return _startPosition.File; } }718 719        /// <summary>720        /// The starting position of the extent.721        /// </summary>722        public IScriptPosition StartScriptPosition { get { return _startPosition; } }723 724        /// <summary>725        /// The end position of the extent.  This position is actually 1 character past the end of the extent.726        /// </summary>727        public IScriptPosition EndScriptPosition { get { return _endPosition; } }728 729        /// <summary>730        /// The line number at the beginning of the extent, with the value 1 being the first line.731        /// </summary>732        public int StartLineNumber { get { return _startPosition.LineNumber; } }733 734        /// <summary>735        /// The column number at the beginning of the extent, with the value 1 being the first column.736        /// </summary>737        public int StartColumnNumber { get { return _startPosition.ColumnNumber; } }738 739        /// <summary>740        /// The line number at the end of the extent, with the value 1 being the first line.741        /// </summary>742        public int EndLineNumber { get { return _endPosition.LineNumber; } }743 744        /// <summary>745        /// The column number at the end of the extent, with the value 1 being the first column.746        /// </summary>747        public int EndColumnNumber { get { return _endPosition.ColumnNumber; } }748 749        /// <summary>750        /// The start offset (always returns 0)751        /// </summary>752        public int StartOffset { get { return 0; } }753 754        /// <summary>755        /// The end offset (always returns 0)756        /// </summary>757        public int EndOffset { get { return 0; } }758 759        /// <summary>760        /// The script text that the extent includes.761        /// </summary>762        public string Text763        {764            get765            {766                if (EndColumnNumber > 0)767                {768                    if (StartLineNumber == EndLineNumber)769                    {770                        return _startPosition.Line.Substring(_startPosition.ColumnNumber - 1,771                                                             _endPosition.ColumnNumber - _startPosition.ColumnNumber);772                    }773 774                    var start = _startPosition.Line.AsSpan(_startPosition.ColumnNumber);775                    var end = _endPosition.Line.AsSpan(0, _endPosition.ColumnNumber);776                    return string.Create(CultureInfo.InvariantCulture, $"{start}...{end}");777                }778                else779                {780                    return string.Empty;781                }782            }783        }784 785        internal void ToPSObjectForRemoting(PSObject dest)786        {787            RemotingEncoder.AddNoteProperty(dest, "ScriptExtent_File", () => File);788            RemotingEncoder.AddNoteProperty(dest, "ScriptExtent_StartLineNumber", () => StartLineNumber);789            RemotingEncoder.AddNoteProperty(dest, "ScriptExtent_StartColumnNumber", () => StartColumnNumber);790            RemotingEncoder.AddNoteProperty(dest, "ScriptExtent_EndLineNumber", () => EndLineNumber);791            RemotingEncoder.AddNoteProperty(dest, "ScriptExtent_EndColumnNumber", () => EndColumnNumber);792        }793 794        private void PopulateFromSerializedInfo(PSObject serializedScriptExtent)795        {796            string file = RemotingDecoder.GetPropertyValue<string>(serializedScriptExtent, "ScriptExtent_File");797            int startLineNumber = RemotingDecoder.GetPropertyValue<int>(serializedScriptExtent, "ScriptExtent_StartLineNumber");798            int startColumnNumber = RemotingDecoder.GetPropertyValue<int>(serializedScriptExtent, "ScriptExtent_StartColumnNumber");799            int endLineNumber = RemotingDecoder.GetPropertyValue<int>(serializedScriptExtent, "ScriptExtent_EndLineNumber");800            int endColumnNumber = RemotingDecoder.GetPropertyValue<int>(serializedScriptExtent, "ScriptExtent_EndColumnNumber");801 802            ScriptPosition startPosition = new ScriptPosition(file, startLineNumber, startColumnNumber, null);803            ScriptPosition endPosition = new ScriptPosition(file, endLineNumber, endColumnNumber, null);804 805            _startPosition = startPosition;806            _endPosition = endPosition;807        }808 809        internal static ScriptExtent FromPSObjectForRemoting(PSObject serializedScriptExtent)810        {811            ScriptExtent extent = new ScriptExtent();812            extent.PopulateFromSerializedInfo(serializedScriptExtent);813            return extent;814        }815    }816}817