Beispiel #1
0
        private void DoUpdate()
        {
            // It would be good to do all of this work on a background thread but we can't:
            //      _classifier.GetClassificationSpans() should only be called on the UI thread because some classifiers assume they are called from the UI thread.
            //      Raising the TagsChanged event from the taggers needs to happen on the UI thread (because some consumers might assume it is being raised on the UI thread).
            //
            // Updating the snapshot for the factory and calling the sink can happen on any thread but those operations are so fast that there is no point.
            if ((!_isDisposed) && (_dirtySpans.Count > 0))
            {
                var line = _dirtySpans[0].Start.GetContainingLine();

                if (line.Length > 0)
                {
                    var oldSecurityErrors = this.Factory.CurrentSnapshot;
                    var newSecurityErrors = new DevSkimErrorsSnapshot(this.FilePath, oldSecurityErrors.VersionNumber + 1);

                    // Go through the existing errors. If they are on the line we are currently parsing then
                    // copy them to oldLineErrors, otherwise they go to the new errors.
                    var oldLineErrors = new List <DevSkimError>();
                    foreach (var error in oldSecurityErrors.Errors)
                    {
                        Debug.Assert(error.NextIndex == -1);

                        if (line.Extent.Contains(error.Span))
                        {
                            error.NextIndex = -1;
                            oldLineErrors.Add(error);                           // Do not clone old error here ... we'll do that later there is no change.
                        }
                        else
                        {
                            error.NextIndex = newSecurityErrors.Errors.Count;
                            newSecurityErrors.Errors.Add(DevSkimError.Clone(error));   // We must clone the old error here.
                        }
                    }

                    int  expectedErrorCount = newSecurityErrors.Errors.Count + oldLineErrors.Count;
                    bool anyNewErrors       = false;


                    // provide whole line to processor so it has complete overview of the scanned code
                    string text = line.GetText();

                    //var classifications = _classifier.GetClassificationSpans(line.Extent);
                    //foreach (var classification in classifications)
                    //{
                    //if (classification.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Comment) || classification.ClassificationType.IsOfType("xml doc comment - text"))
                    // classification.Span.GetText();

                    int index = 0;
                    while (index < text.Length)
                    {
                        MatchResult result = RuleProcessor.IsMatch(text, index, line.Snapshot.ContentType.TypeName);

                        int errorStart = result.Location;
                        if (errorStart < 0)
                        {
                            break;
                        }

                        int errorLength = result.Length;
                        if (errorLength > 0)    // Ignore any single character error.
                        {
                            //var newSpan = new SnapshotSpan(classification.Span.Start + errorStart, errorLength);
                            var newSpan = new SnapshotSpan(line.Start + errorStart, errorLength);

                            var oldError = oldLineErrors.Find((e) => e.Span == newSpan);

                            if (oldError != null)
                            {
                                // There was a security error at the same span as the old one so we should be able to just reuse it.
                                oldError.NextIndex = newSecurityErrors.Errors.Count;
                                newSecurityErrors.Errors.Add(DevSkimError.Clone(oldError));    // Don't clone the old error yet
                            }
                            else
                            {
                                newSecurityErrors.Errors.Add(new DevSkimError(newSpan, result.Rule));
                                anyNewErrors = true;
                            }

                            index = errorStart + errorLength;
                        }
                        else
                        {
                            // How can you have a error with a length of 0? Handle it gracefully in any case.
                            index = errorStart + 1;
                        }
                    }
                    //endforeach}

                    // This does a deep comparison so we will only do the update if the a different set of errors was discovered compared to what we had previously.
                    // If there were any new errors or if we didn't see all the expected errors then there is a change and we need to update the security errors.
                    if (anyNewErrors || (newSecurityErrors.Errors.Count != expectedErrorCount))
                    {
                        this.UpdateSecurityErrors(newSecurityErrors);
                    }
                    else
                    {
                        // There were no changes so we don't need to update our snapshot.
                        // We have, however, dirtied the old errors by setting their NextIndex property on the assumption that we would be updating the errors.
                        // Revert all those changes.
                        foreach (var error in oldSecurityErrors.Errors)
                        {
                            error.NextIndex = -1;
                        }
                    }
                }

                // NormalizedSnapshotSpanCollection.Difference doesn't quite do what we need here. If I have {[5,5), [10,20)} and subtract {[5, 15)} and do a ...Difference, I
                // end up with {[5,5), [15,20)} (the zero length span at the beginning isn't getting removed). A zero-length span at the end wouldn't be removed but, in this case,
                // that is the desired behavior (the zero length span at the end could be a change at the beginning of the next line, which we'd want to keep).
                var newDirtySpans = new List <Span>(_dirtySpans.Count + 1);
                var extent        = line.ExtentIncludingLineBreak;

                for (int i = 0; (i < _dirtySpans.Count); ++i)
                {
                    Span s = _dirtySpans[i];
                    if ((s.End < extent.Start) || (s.Start >= extent.End))          // Intentionally asymmetric
                    {
                        newDirtySpans.Add(s);
                    }
                    else
                    {
                        if (s.Start < extent.Start)
                        {
                            newDirtySpans.Add(Span.FromBounds(s.Start, extent.Start));
                        }

                        if ((s.End >= extent.End) && (extent.End < line.Snapshot.Length))
                        {
                            newDirtySpans.Add(Span.FromBounds(extent.End, s.End));  //This could add a zero length span (which is by design since we want to ensure we security check the next line).
                        }
                    }
                }

                _dirtySpans = new NormalizedSnapshotSpanCollection(line.Snapshot, newDirtySpans);

                if (_dirtySpans.Count == 0)
                {
                    // We've cleaned up all the dirty spans.
                    _isUpdating = false;
                }
                else
                {
                    // Still more work to do.
                    _uiThreadDispatcher.BeginInvoke(new Action(() => this.DoUpdate()), DispatcherPriority.Background);
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Test given text for issues
        /// </summary>
        /// <param name="text">Source code</param>
        /// <param name="contenttype">Visual Studio content type</param>
        /// <returns>MatchRecord with infomartion of identified issue</returns>
        private MatchResult FindMatch(string text, string contenttype)
        {
            // Get rules for the given content type
            IEnumerable <Rule> rules  = GetRulesForContentType(contenttype);
            MatchResult        result = new MatchResult()
            {
                Success = false
            };

            // Go through each rule
            foreach (Rule r in rules)
            {
                // Go through each matching pattern of the rule
                foreach (PatternRecord p in r.Patterns)
                {
                    // Type == Substring
                    if (p.Type == PatternType.Substring)
                    {
                        result.Location = text.ToLower().IndexOf(p.Pattern.ToLower());
                        result.Length   = p.Pattern.Length;
                        if (result.Location > -1)
                        {
                            result.Success = true;
                            result.Rule    = r;
                            break; // from pattern loop
                        }
                    }
                    // Type == Regex
                    else if (p.Type == PatternType.Regex)
                    {
                        RegexOptions reopt = RegexOptions.None;
                        if (p.Modifiers != null)
                        {
                            reopt |= (p.Modifiers.Contains("ignorecase")) ? RegexOptions.IgnoreCase : RegexOptions.None;
                            reopt |= (p.Modifiers.Contains("multiline")) ? RegexOptions.Multiline : RegexOptions.None;
                        }

                        Regex patRegx = new Regex(p.Pattern, reopt);
                        Match m       = patRegx.Match(text);
                        if (m.Success)
                        {
                            result.Success  = true;
                            result.Rule     = r;
                            result.Location = m.Index;
                            result.Length   = m.Length;
                            break; // from pattern loop
                        }
                    }
                }

                // We got matching rule. Let's see if we have a supression on the line
                if (result.Success)
                {
                    Suppressor supp = new Suppressor(_textLine, contenttype);
                    // If rule is being suppressed then clear the MatchResult
                    if (supp.IsRuleSuppressed(result.Rule.Id))
                    {
                        result = new MatchResult();
                    }
                    // Otherwise break out of the loop as we found an issue.
                    // So, no need to scan for more.
                    else
                    {
                        break;
                    }
                }
            }

            return(result);
        }