static void RevaluateMethodInheritanceAdornments(InheritanceTracker @this) { Contract.Requires(@this != null); var workingSnapshot = @this._textViewTracker.TextView.TextSnapshot; //Save the current snapshot so it doesn't change while you work, we assume that the snapshot is the same as the source file. //Check if model is ready var parseTree = @this._textViewTracker.LatestSourceFile == null ? null : @this._textViewTracker.LatestSourceFile.GetParseTree(); if (parseTree == null || !parseTree.IsModelReady()) { @this._textViewTracker.IsLatestSourceFileStale = true; Utilities.Delay(() => ContractsPackageAccessor.Current.AskForNewVSModel(), DelayOnVSModelFailedBeforeTryingAgain); return; } //Collect all the methods in this text view var methodCollector = new MethodCollector(workingSnapshot); methodCollector.Visit(parseTree.RootNode); var methods = methodCollector.GetMethods(); //Calculate which methods are new var newKeys = new List <object>(methods.Keys.Where((k) => !@this._methodKeys.Contains(k))); ContractsPackageAccessor.Current.Logger.WriteToLog(String.Format("Found {0} new methods.", newKeys.Count)); //Update our method keys @this._methodKeys.Clear(); @this._methodKeys.AddAll(methods.Keys); ContractsPackageAccessor.Current.QueueWorkItem(() => { if (@this._textViewTracker.TextView.IsClosed) { return; } var adornmentKeys = new List <object>(@this._adornmentManager.Adornments.Keys); foreach (var key in adornmentKeys) { var keyAsString = key as string; if (keyAsString == null) { ContractsPackageAccessor.Current.Logger.WriteToLog("Unexpected: A key in the AdornmentManager wasn't a string! key: " + key.ToString()); continue; } if (!@this._methodKeys.Contains(key) && keyAsString.EndsWith(MethodCollector.MethodTagSuffix)) { @this._adornmentManager.RemoveAdornment(key); ContractsPackageAccessor.Current.Logger.WriteToLog("Removing obsolete method adornment with tag: " + keyAsString); } } }, () => @this._textViewTracker.TextView.IsClosed || @this._textViewTracker.TextView.HasAggregateFocus); //Create placeholder adornments for our new methods and queue them for contract lookup ContractsPackageAccessor.Current.QueueWorkItem(() => { foreach (var key in newKeys) { MethodDeclarationNode method; if (methods.TryGetValue(key, out method)) { ContractsPackageAccessor.Current.Logger.WriteToLog("Creating placeholder adornment and enqueueing for future contract lookup for: " + key.ToString()); #region Create placeholder adornment //We add the placeholder adornment here because our workingSnapshot corresponds most closely to the syntactic model's text var snapshotSpan = new SnapshotSpan(method.GetSpan().Convert(workingSnapshot).Start, 1); var trackingSpan = workingSnapshot.CreateTrackingSpan(snapshotSpan.Span, SpanTrackingMode.EdgeExclusive); var ops = AdornmentOptionsHelper.GetAdornmentOptions(ContractsPackageAccessor.Current.VSOptionsPage); @this._adornmentManager.AddAdornment(new InheritanceContractAdornment(trackingSpan, @this._textViewTracker.VSTextProperties, ContractsPackageAccessor.Current.Logger, @this._adornmentManager.QueueRefreshLineTransformer, ops), key); #endregion @this._methodsNeedingContractLookup.Enqueue(new KeyValuePair <object, MethodDeclarationNode>(key, method)); } } }); //Most likely we've changed something (and this is a pretty cheap call), so let's ask for a refresh Utilities.Delay(() => ContractsPackageAccessor.Current.QueueWorkItem(@this._adornmentManager.QueueRefreshLineTransformer), DelayAfterMembersRevalutation); //Ask for the new VS model so we can look up contracts Utilities.Delay(() => ContractsPackageAccessor.Current.QueueWorkItem(ContractsPackageAccessor.Current.AskForNewVSModel), DelayAfterMembersRevalutation); }
static void RevaluatePropertyInheritanceAdornments(InheritanceTracker @this) { Contract.Requires(@this != null); var workingSnapshot = @this._textViewTracker.TextView.TextSnapshot; //Save the current snapshot so it doesn't change while you work, we assume that the snapshot is the same as the source file. //Check if model is ready var parseTree = @this._textViewTracker.LatestSourceFile == null ? null : @this._textViewTracker.LatestSourceFile.GetParseTree(); if (parseTree == null || !parseTree.IsModelReady()) { @this._textViewTracker.IsLatestSourceFileStale = true; Utilities.Delay(() => ContractsPackageAccessor.Current.AskForNewVSModel(), DelayOnVSModelFailedBeforeTryingAgain); return; } //Collect all the properties in this text view var propertyCollector = new PropertyCollector(workingSnapshot); propertyCollector.Visit(parseTree.RootNode); var properties = propertyCollector.GetProperties(); //Get our property keys var keys = new List <object>(); var newTuples = new List <Tuple <object, object> >(); foreach (var tuple in properties.Keys) { if (tuple.Item1 != null) { keys.Add(tuple.Item1); } if (tuple.Item2 != null) { keys.Add(tuple.Item2); } if (!(@this._propertyKeys.Contains(tuple.Item1) && @this._propertyKeys.Contains(tuple.Item1))) { newTuples.Add(tuple); } } ContractsPackageAccessor.Current.Logger.WriteToLog(String.Format("Found {0} new properties.", newTuples.Count)); //Update our property keys @this._propertyKeys.Clear(); @this._propertyKeys.AddAll(keys); ContractsPackageAccessor.Current.QueueWorkItem(() => { if (@this._textViewTracker.TextView.IsClosed) { return; } var adornmentKeys = new List <object>(@this._adornmentManager.Adornments.Keys); foreach (var key in adornmentKeys) { var keyAsString = key as string; if (keyAsString == null) { ContractsPackageAccessor.Current.Logger.WriteToLog("Unexpected: A key in the AdornmentManager wasn't a string! key: " + key.ToString()); continue; } if (!@this._propertyKeys.Contains(key) && keyAsString.EndsWith(PropertyCollector.PropertyTagSuffix)) { @this._adornmentManager.RemoveAdornment(key); ContractsPackageAccessor.Current.Logger.WriteToLog("Removing obsolete property adornment with tag: " + keyAsString); } } }, () => @this._textViewTracker.TextView.IsClosed || @this._textViewTracker.TextView.HasAggregateFocus); //Create placeholder adornments for our new properties and queue them for contract lookup ContractsPackageAccessor.Current.QueueWorkItem(() => { foreach (var tuple in newTuples) { PropertyDeclarationNode property; if (properties.TryGetValue(tuple, out property)) { if (tuple.Item1 != null && tuple.Item2 != null && property.GetAccessorDeclaration.GetSpan().Start.Line == property.SetAccessorDeclaration.GetSpan().Start.Line) { // set and get on same line, don't add adornment ContractsPackageAccessor.Current.Logger.WriteToLog("Skipping adornments for " + property.GetName(workingSnapshot) + " because get and set are on same line"); } else { ContractsPackageAccessor.Current.Logger.WriteToLog("Creating placeholder adornment and enqueueing for future contract lookup for: " + property.GetName(workingSnapshot)); if (tuple.Item1 != null) { #region Create getter placeholder adornment ContractsPackageAccessor.Current.Logger.WriteToLog(String.Format("\t(Creating getter placeholder with tag {0})", tuple.Item1)); //We add the placeholder adornment here because our workingSnapshot corresponds most closely to the syntactic model's text var snapshotSpan = new SnapshotSpan(property.GetAccessorDeclaration.GetSpan().Convert(workingSnapshot).Start, 1); var trackingSpan = workingSnapshot.CreateTrackingSpan(snapshotSpan.Span, SpanTrackingMode.EdgeExclusive); var ops = AdornmentOptionsHelper.GetAdornmentOptions(ContractsPackageAccessor.Current.VSOptionsPage); @this._adornmentManager.AddAdornment(new InheritanceContractAdornment(trackingSpan, @this._textViewTracker.VSTextProperties, ContractsPackageAccessor.Current.Logger, @this._adornmentManager.QueueRefreshLineTransformer, ops), tuple.Item1); #endregion } if (tuple.Item2 != null) { #region Create setter placeholder adornment ContractsPackageAccessor.Current.Logger.WriteToLog(String.Format("\t(Creating setter placeholder with tag {0})", tuple.Item2)); //We add the placeholder adornment here because our workingSnapshot corresponds most closely to the syntactic model's text var snapshotSpan = new SnapshotSpan(property.SetAccessorDeclaration.GetSpan().Convert(workingSnapshot).Start, 1); var trackingSpan = workingSnapshot.CreateTrackingSpan(snapshotSpan.Span, SpanTrackingMode.EdgeExclusive); var ops = AdornmentOptionsHelper.GetAdornmentOptions(ContractsPackageAccessor.Current.VSOptionsPage); @this._adornmentManager.AddAdornment(new InheritanceContractAdornment(trackingSpan, @this._textViewTracker.VSTextProperties, ContractsPackageAccessor.Current.Logger, @this._adornmentManager.QueueRefreshLineTransformer, ops), tuple.Item2); #endregion } @this._propertiesNeedingContractLookup.Enqueue(new KeyValuePair <Tuple <object, object>, PropertyDeclarationNode>(tuple, property)); } } } }); //Most likely we've changed something (and this is a pretty cheap call), so let's ask for a refresh Utilities.Delay(() => ContractsPackageAccessor.Current.QueueWorkItem(@this._adornmentManager.QueueRefreshLineTransformer, () => @this._textViewTracker.TextView.HasAggregateFocus), DelayAfterMembersRevalutation); //Ask for the new VS model so we can look up contracts Utilities.Delay(() => ContractsPackageAccessor.Current.QueueWorkItem(ContractsPackageAccessor.Current.AskForNewVSModel, () => @this._textViewTracker.TextView.HasAggregateFocus), DelayAfterMembersRevalutation); }
void DoWork() { string assemblyName; Version assemblyVersion; string assemblyLocation; string typeName; int genericParameterCount; GetNecessaryInfo(out assemblyName, out assemblyVersion, out assemblyLocation, out typeName, out genericParameterCount); #region Create host var host = this._projectTracker.Host; if (host == null) { VSServiceProvider.Current.Logger.WriteToLog("Couldn't create host."); return; } #endregion #region Find and load assembly IAssembly iAssembly = null; if (assemblyName.Equals(host.CoreAssemblySymbolicIdentity.Name.Value, StringComparison.OrdinalIgnoreCase) && assemblyVersion.Equals(host.CoreAssemblySymbolicIdentity.Version)) { iAssembly = host.FindAssembly(host.CoreAssemblySymbolicIdentity); } else { var references = _projectTracker.References; VSLangProj.Reference reference = null; var assemblyNameWithoutExtension = Path.GetFileNameWithoutExtension(assemblyName); for (int i = 1; i <= references.Count; i++)//TODO: Unify this code. This process of looking up a reference from a name is also done in ContractsProvider.TryGetAssemblyReference { var tempRef = references.Item(i); string refName = tempRef.Name; if (refName.Equals(assemblyNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { reference = tempRef; break; } } if (reference != null) { IName iName = host.NameTable.GetNameFor(Path.GetFileNameWithoutExtension(reference.Path)); string culture = reference.Culture; Version version = new Version(reference.MajorVersion, reference.MinorVersion, reference.BuildNumber, reference.RevisionNumber); string location = reference.Path; var tempRef2 = new Microsoft.Cci.Immutable.AssemblyReference(host, new AssemblyIdentity(iName, culture, version, Enumerable <byte> .Empty, location)); iAssembly = host.LoadAssembly(tempRef2.AssemblyIdentity); } else { VSServiceProvider.Current.Logger.WriteToLog("Couldn't find reference for metadata file."); return; } } if (iAssembly == null || iAssembly == Dummy.Assembly) { VSServiceProvider.Current.Logger.WriteToLog("Couldn't get assembly for metadata file."); return; } #endregion #region Get contracts provider var contractsProvider = host.GetContractExtractor(iAssembly.UnitIdentity); if (contractsProvider == null) { VSServiceProvider.Current.Logger.WriteToLog("Couldn't get contracts provider."); return; } #endregion #region Collect contracts var type = UnitHelper.FindType(host.NameTable, iAssembly, typeName, genericParameterCount); if (type == null || type is Dummy) { VSServiceProvider.Current.Logger.WriteToLog("Couldn't find metadata type '" + typeName + "' in assembly."); return; } //This dictionaries will map the method/property signature to the contracts for the method/property var methodsToContracts = new Dictionary <string, IMethodContract>(type.Methods.Count()); var gettersToContracts = new Dictionary <string, IMethodContract>(); var settersToContracts = new Dictionary <string, IMethodContract>(); //Set the formatting options for property getters and setters var propertySignatureFormattingOptions = NameFormattingOptions.OmitContainingNamespace | // NameFormattingOptions.ReturnType | NameFormattingOptions.TypeParameters | NameFormattingOptions.UseTypeKeywords | NameFormattingOptions.OmitContainingType; //NameFormattingOptions.Visibility; //Set the formating options for methods var methodSignatureFormattingOptions = NameFormattingOptions.OmitContainingNamespace | NameFormattingOptions.ReturnType | NameFormattingOptions.ParameterName | NameFormattingOptions.ParameterModifiers | NameFormattingOptions.TypeParameters | NameFormattingOptions.UseTypeKeywords | NameFormattingOptions.OmitContainingType | //NameFormattingOptions.Modifiers | NameFormattingOptions.Signature //NameFormattingOptions.Visibility ; //var sourceEmitterOutput = new SourceEmitterOutputString();//TODO: Use source emitter for all my printing? Instead of the whole NameFormattingOptions ordeal. //var csSourceEmitter = new SourceEmitter(sourceEmitterOutput); foreach (var method in type.Methods) { var methodContract = ContractHelper.GetMethodContractForIncludingInheritedContracts(host, method); if (methodContract != null && methodContract != ContractDummy.MethodContract) { if (IsGetter(method)) { if (method.ParameterCount > 0) //We have an indexer! { var indexerSignature = PrintIndexer(method, true); gettersToContracts.Add(indexerSignature, methodContract); } else { var getterSignature = MemberHelper.GetMemberSignature(method, propertySignatureFormattingOptions);//Example: "XmlSchemaSet Schemas.get" getterSignature = getterSignature.Substring(0, getterSignature.LastIndexOf('.')); gettersToContracts.Add(getterSignature, methodContract); } } else if (IsSetter(method)) { if (method.ParameterCount > 1) //We have an indexer! { var indexerSignature = PrintIndexer(method, false); settersToContracts.Add(indexerSignature, methodContract); } else { var setterSignature = MemberHelper.GetMemberSignature(method, propertySignatureFormattingOptions); setterSignature = setterSignature.Substring(0, setterSignature.LastIndexOf('.')); settersToContracts.Add(setterSignature, methodContract); } } else { //#region Print method, stolen from CSharpSourceEmitter //csSourceEmitter.PrintMethodDefinitionVisibility(method); //csSourceEmitter.PrintMethodDefinitionModifiers(method); //bool conversion = csSourceEmitter.IsConversionOperator(method); //if (!conversion) { // csSourceEmitter.PrintMethodDefinitionReturnType(method); // if (!method.IsConstructor && !csSourceEmitter.IsDestructor(method)) // csSourceEmitter.PrintToken(CSharpToken.Space); //} //csSourceEmitter.PrintMethodDefinitionName(method); //if (conversion) // csSourceEmitter.PrintMethodDefinitionReturnType(method); //if (method.IsGeneric) { // csSourceEmitter.Traverse(method.GenericParameters); //} //csSourceEmitter.Traverse(method.Parameters); //#endregion //var methodSignature = sourceEmitterOutput.Data; //sourceEmitterOutput.ClearData(); var methodSignature = MemberHelper.GetMemberSignature(method, methodSignatureFormattingOptions);//Example: "XmlAttribute CreateAttribute(string name)" methodsToContracts.Add(methodSignature, methodContract); } } } #endregion var hasMethodContracts = methodsToContracts.Count > 0; var hasPropertyContracts = gettersToContracts.Count > 0 || settersToContracts.Count > 0; if (!hasMethodContracts && !hasPropertyContracts) { VSServiceProvider.Current.Logger.WriteToLog("No contracts found."); return; } int propertyCounter = 0; //Counts the number of adornments added for property contracts int methodCounter = 0; //Counts the number of adornments added for method contracts foreach (var line in _textView.TextSnapshot.Lines) { var lineText = line.GetText(); //Skip lines with comments //This assumes that no method/property decelerations in metadata files will have comments in them if (lineText.Contains("//")) { continue; } // bail out on nested types var typeNameMatch = Regex.Match(lineText, @"(class|struct|interface|enum) (\w+)"); if (typeNameMatch.Success) { if (typeNameMatch.Groups[2].Value == type.Name.Value) { continue; } break; } if (hasPropertyContracts && lineText.Contains('{') && (lineText.Contains(" get; ") || lineText.Contains(" set; ")) && lineText.Contains('}')) //Check if line is a property decleration { #region Add property contracts //Parse the property decleration to get a signature we can compare to our contract dictionaries //Example of a property decleration: " public int Build { get; }" int endOfSig = lineText.IndexOf('{') - 1; int startOfSig = endOfSig - 1; bool hasHitSpace = false; bool isInPropertyParameter = false; for (int i = startOfSig; i > 0; i--) { char c = lineText[i]; if (c == ']') { isInPropertyParameter = true; } else if (c == '[') { isInPropertyParameter = false; } else if (hasHitSpace && c == ' ') { startOfSig = i + 1; break; } else if (!isInPropertyParameter && c == ' ') { startOfSig = i + 1; break; // MAF: ignore return type of properties. //hasHitSpace = true; } } var propertySignature = lineText.Substring(startOfSig, endOfSig - startOfSig); //Example: "int Build" IMethodContract getterContract = null; IMethodContract setterContract = null; if (gettersToContracts.TryGetValue(propertySignature, out getterContract) | // yes eager evaluation!!! settersToContracts.TryGetValue(propertySignature, out setterContract)) { var tag = propertySignature.GetHashCode();//We use this to uniquely identify the particular method/property decleration //Find this first non-whitespace character. This is were we'll place the adornment. int firstNonWhitespace = 0; for (int i = 0; i < lineText.Length; i++) { char c = lineText[i]; if (c != ' ') { firstNonWhitespace = i; break; } } var span = _textView.TextSnapshot.CreateTrackingSpan(line.Start + firstNonWhitespace, propertySignature.Length, SpanTrackingMode.EdgeExclusive); _vsTextProperties.LineHeight = _textView.LineHeight; var ops = AdornmentOptionsHelper.GetAdornmentOptions(VSServiceProvider.Current.VSOptionsPage); var adornment = new MetadataContractAdornment(span, _vsTextProperties, VSServiceProvider.Current.Logger, _adornmentManager.QueueRefreshLineTransformer, ops); adornment.SetContracts(getterContract, setterContract, null /* "Contracts from " + typeName + "." */); _adornmentManager.AddAdornment(adornment, tag); propertyCounter++; } #endregion continue; } if (hasMethodContracts && lineText.Contains('(') && lineText.Contains(')') && lineText.Contains(';')) //Check if line is a method decleration { #region Add method contracts //Parse the method decleration to get a signature we can compare to our contract dictionaries //Example of a method decleration: " public static Version Parse(string input);" int endOfSig = lineText.LastIndexOf(')') + 1; //int startOfSig = !Char.IsWhiteSpace(lineText[0]) ? 0 : lineText.IndexOf(lineText.First(c => !Char.IsWhiteSpace(c))); //int startOfSig = lineText.IndexOf('('); //bool hitSpace = false; //for (int i = startOfSig; i > 0; i--) { // char c = lineText[i]; // if (c == ' ' && hitSpace) { // startOfSig = i + 1; // break; // } else if (c == ' ') { // hitSpace = true; // } //} var methodSignature = lineText.Substring(0, endOfSig); //Example: "Version Parse(string input)" // remove modifiers methodSignature = Regex.Replace(methodSignature, modifierFilter, ""); methodSignature = methodSignature.Trim(); if (methodSignature.StartsWith(type.Name.Value + "(")) { methodSignature = "void .ctor" + methodSignature.Substring(type.Name.Value.Length); } IMethodContract methodContract; if (methodsToContracts.TryGetValue(methodSignature, out methodContract)) { var tag = methodSignature.GetHashCode();//We use this to uniquely identify the particular method/property decleration //Find this first non-whitespace character. This is were we'll place the adornment. int firstNonWhitespace = 0; for (int i = 0; i < lineText.Length; i++) { char c = lineText[i]; if (c != ' ') { firstNonWhitespace = i; break; } } var span = _textView.TextSnapshot.CreateTrackingSpan(line.Start + firstNonWhitespace, methodSignature.Length, SpanTrackingMode.EdgeExclusive); _vsTextProperties.LineHeight = _textView.LineHeight; var ops = AdornmentOptionsHelper.GetAdornmentOptions(VSServiceProvider.Current.VSOptionsPage); var adornment = new MetadataContractAdornment(span, _vsTextProperties, VSServiceProvider.Current.Logger, _adornmentManager.QueueRefreshLineTransformer, ops); adornment.SetContracts(methodContract, "Contracts from " + typeName + "."); _adornmentManager.AddAdornment(adornment, tag); //if (methodContract.IsPure) { // var purityAdornment = new PurityAdornment(_vsTextProperties, adornment); // //_adornmentManager.AddAdornment(purityAdornment, null); //} methodCounter++; } #endregion continue; } } #region Add button to collapse all contracts if (propertyCounter > 0 || methodCounter > 0) { var button = new Button(); button.Content = "Hide all contracts"; button.Click += OnCollapseAllClick; button.Cursor = Cursors.Hand; var collapseAllAdornment = new StaticAdornment(false, 10d, true, 10d, button); _adornmentManager.AddStaticAdornment(collapseAllAdornment); } #endregion }