public IEnumerable<Violation> Apply(TestCase testCase) { // Store the framework so that we don't have to pass it around everywhere. _framework = testCase.Framework; var calledAssertingMethods = testCase.GetCalledAssertingMethods(); var tracker = new MethodValueTracker(testCase.TestMethod); var whitelistedFields = FindWhitelistedFields(testCase.TestMethod.DeclaringType); // For each asserting method with >= 1 parameters: foreach (var cm in calledAssertingMethods.Where(cm => cm.MethodDefinition.HasParameters)) { var method = cm.MethodDefinition; //TODO: if the method is a helper, we need to "unfold" the helper // to get to the real asserting methods, and this will require us // to join value-generation graphs across method calls... var paramPurposes = _framework.GetParameterPurposes(method); if (paramPurposes == null) continue; // unknown method, rule does not apply foreach (var valueGraph in tracker.ValueGraphs) { IList<MethodValueTracker.Value> consumedValues = tracker.GetConsumedValues(valueGraph, cm.Instruction).ToList(); if (consumedValues.Count == 0) continue; // not part of value graph // Build a list of arguments with the details we need to know if the rule applies. var arguments = method.Parameters .Select((p, index) => new ArgumentDetails { Method = method, Index = index, Purpose = paramPurposes[index], ConsumedValue = consumedValues[index] }).ToList(); // Handle cases like Assert.IsTrue(x == 5) by expanding arguments ExpandIfSingleTruthCheckingMethod(method, ref arguments); // We're only interested in arguments that represent expectations! var interestingArguments = arguments.Where(a => IsPerhapsExpectation(a.Purpose)).ToList(); // This might happen with for example Assert.Fail("some reason"). if (interestingArguments.Count == 0) continue; // Add in the "forbidden producer", if any, for each argument. A forbidden producer is an // instruction that generates a value externally, such as a call. interestingArguments = interestingArguments.Select( a => { a.ForbiddenProducer = FirstForbiddenProducer(valueGraph, a.ConsumedValue, whitelistedFields); return a; }).ToList(); // If there is at least one locally produced argument, the rule doesn't apply. if (interestingArguments.Any(IsLocallyProduced)) continue; if (interestingArguments.All(a => a.Purpose == ParameterPurpose.ExpectedOrActual)) { // Since we don't know exactly which parameter that represents the expectation, we // just generate a single violation. yield return new Violation(this, testCase, interestingArguments[0].ConsumedValue.Consumer, CreateViolationMessageForUncertainCase(interestingArguments[0])); continue; } foreach (var a in interestingArguments.Where(IsExternallyProduced)) { // Generate a violation at the location of the forbidden producer! yield return new Violation(this, testCase, a.ForbiddenProducer, CreateViolationMessage(a)); } } } }