Click here to Skip to main content
15,895,799 members
Articles / Programming Languages / MSIL

Assembly Manipulation and C# / VB.NET Code Injection

Rate me:
Please Sign up or sign in to vote.
4.95/5 (200 votes)
26 Apr 2013MIT5 min read 927K   25K   486  
Reflexil is an assembly editor and runs as a plug-in for Reflector or JustDecompile. Reflexil is able to manipulate IL code and save the modified assemblies to disk. Reflexil also supports "on-the-fly" C#/VB.NET code injection.
// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
//     <version>$Revision: 3675 $</version>
// </file>

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.PrettyPrinter;
using NR = ICSharpCode.NRefactory;

namespace ICSharpCode.SharpDevelop.Dom.Refactoring
{
	public class NRefactoryRefactoringProvider : RefactoringProvider
	{
		public static readonly NRefactoryRefactoringProvider NRefactoryCSharpProviderInstance = new NRefactoryRefactoringProvider(NR.SupportedLanguage.CSharp);
		public static readonly NRefactoryRefactoringProvider NRefactoryVBNetProviderInstance = new NRefactoryRefactoringProvider(NR.SupportedLanguage.VBNet);
		
		NR.SupportedLanguage language;
		
		private NRefactoryRefactoringProvider(NR.SupportedLanguage language)
		{
			this.language = language;
		}
		
		public override bool IsEnabledForFile(string fileName)
		{
			string extension = Path.GetExtension(fileName);
			if (extension.Equals(".cs", StringComparison.InvariantCultureIgnoreCase))
				return language == NR.SupportedLanguage.CSharp;
			else if (extension.Equals(".vb", StringComparison.InvariantCultureIgnoreCase))
				return language == NR.SupportedLanguage.VBNet;
			else
				return false;
		}
		
		static void ShowSourceCodeErrors(IDomProgressMonitor progressMonitor, string errors)
		{
			if (progressMonitor != null)
				progressMonitor.ShowingDialog = true;
			HostCallback.ShowMessage("${res:SharpDevelop.Refactoring.CannotPerformOperationBecauseOfSyntaxErrors}\n" + errors);
			if (progressMonitor != null)
				progressMonitor.ShowingDialog = false;
		}
		
		NR.IParser ParseFile(IDomProgressMonitor progressMonitor, string fileContent)
		{
			NR.IParser parser = NR.ParserFactory.CreateParser(language, new StringReader(fileContent));
			parser.Parse();
			if (parser.Errors.Count > 0) {
				ShowSourceCodeErrors(progressMonitor, parser.Errors.ErrorOutput);
				parser.Dispose();
				return null;
			} else {
				return parser;
			}
		}
		
		IOutputAstVisitor GetOutputVisitor() {
			switch (language) {
				case NR.SupportedLanguage.CSharp:
					return new CSharpOutputVisitor();
				case NR.SupportedLanguage.VBNet:
					return new VBNetOutputVisitor();
				default:
					throw new NotSupportedException();
			}
		}
		
		string CommentToken {
			get {
				switch (language) {
					case NR.SupportedLanguage.CSharp:
						return "//";
					case NR.SupportedLanguage.VBNet:
						return "'";
					default:
						throw new NotSupportedException();
				}
			}
		}

		#region ExtractInterface
		public override bool SupportsExtractInterface {
			get {
				return true;
			}
		}
		
		private class ExtractInterfaceVisitor : NR.Visitors.AbstractAstVisitor
		{
			string newInterfaceName;
			string sourceClassName;
			string sourceNamespace;
			Dictionary<string, IMember> membersToInclude;
			
			public ExtractInterfaceVisitor(string newInterfaceName,
			                               string sourceNamespace,
			                               string sourceClassName,
			                               IList<IMember> chosenMembers) {
				this.newInterfaceName = newInterfaceName;
				this.sourceNamespace = sourceNamespace;
				this.sourceClassName = sourceClassName;
				
				// store the chosen members in a dictionary for easy lookup
				membersToInclude = new Dictionary<string, IMember>();
				foreach(IMember m in chosenMembers) {
					membersToInclude.Add(m.Name, m);
				}
			}

			public override object VisitCompilationUnit(CompilationUnit compilationUnit, object data)
			{
				// strip out any usings & extract our TypeReference from the NameSpace
				// we walk backwards so that deletions don't affect the iteration
				NamespaceDeclaration ns;
				TypeDeclaration td;
				object child;
				object nsChild;
				for(int i = compilationUnit.Children.Count-1; i>=0; i--) {
					child = compilationUnit.Children[i];
					if (child is UsingDeclaration) {
						// we don't want our usings here...
						compilationUnit.Children.RemoveAt(i);
					}
					else if (child is NamespaceDeclaration) {
						ns = (NamespaceDeclaration)child;
						if (ns.Name != this.sourceNamespace) {
							// we're not interested in this namespace...
							compilationUnit.Children.RemoveAt(i);
						} else {
							
							// this NamespaceDeclaration presumably contains our source class
							// walk its children backwards to that removing them won't break the iteration
							for(int j = ns.Children.Count-1; j>=0; j--) {
								nsChild = ns.Children[j];
								if (nsChild is TypeDeclaration) {
									td = (TypeDeclaration)nsChild;
									
									if (td.Name == this.sourceClassName) {
										// keep it, and substitute it for the current NamespaceDeclaration
										compilationUnit.Children[i] = td;
									} else {
										// it's not the class we're extracting from
										ns.Children.RemoveAt(j);
									}
								} else {
									// it's not even a class... (e.g. using, etc)
									ns.Children.RemoveAt(j);
								}
							}
						}
					} else {
						// we don't actually want to throw an exception here just because we havn't forseen the node type...
						//throw new NotSupportedException("trimming "+compilationUnit.Children[i].ToString()+" is not supported.");
					}
				}
				return base.VisitCompilationUnit(compilationUnit, data);
			}
			
			public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
			{
				// rewrite the type declaration to an interface
				typeDeclaration.Attributes.Clear();
				typeDeclaration.BaseTypes.Clear();
				
				typeDeclaration.Type = NR.Ast.ClassType.Interface;
				typeDeclaration.Name = newInterfaceName;
				
				// remove those children who are not explicitly listed in our 'membersToInclude' dictionary
				// we walk backwards so that deletions don't affect the iteration
				bool keepIt;
				MethodDeclaration method;
				PropertyDeclaration property;
				object child;
				for (int i = typeDeclaration.Children.Count-1; i >= 0; i--) {
					keepIt = false;
					child = typeDeclaration.Children[i];
					if (child is MethodDeclaration) {
						method = (MethodDeclaration)child;
						if (membersToInclude.ContainsKey(method.Name)
						    && ((method.Modifier & Modifiers.Static) == Modifiers.None)) {
							keepIt = true;
						}
					} else if (child is PropertyDeclaration) {
						property = (PropertyDeclaration)child;
						if (membersToInclude.ContainsKey(property.Name)
						    && ((property.Modifier & Modifiers.Static) == Modifiers.None)) {
							keepIt = true;
						}
					}
					
					if (!keepIt) {
						typeDeclaration.Children.RemoveAt(i);
					}
				}

				// must call the base method to ensure that this type's children get visited
				return base.VisitTypeDeclaration(typeDeclaration, data);
			}
			
			public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
			{
				// strip out the public modifier...
				methodDeclaration.Modifier = NR.Ast.Modifiers.None;
				
				// ...and the method body
				methodDeclaration.Body = BlockStatement.Null;

				return null;
			}
			
			public override object VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data)
			{
				// strip out the public modifiers...
				propertyDeclaration.Modifier = NR.Ast.Modifiers.None;

				// ... and the body of any get block...
				if (propertyDeclaration.HasGetRegion) {
					propertyDeclaration.GetRegion.Block = BlockStatement.Null;
				}

				// ... and the body of any set block...
				if (propertyDeclaration.HasSetRegion) {
					propertyDeclaration.SetRegion.Block = BlockStatement.Null;
				}
				
				return null;
			}
		}
		
		public override string GenerateInterfaceForClass(string newInterfaceName,
		                                                 IList<IMember> membersToKeep,
		                                                 bool preserveComments,
		                                                 string sourceNamespace,
		                                                 string sourceClassName,
		                                                 string existingCode
		                                                )
		{
			string codeForNewInterface = "<insert code for '"+newInterfaceName+"' here>";
			NR.IParser parser = ParseFile(null, existingCode);
			if (parser == null) {
				return null;
			}
			// use a custom IAstVisitor to strip our class out of this file,
			// rewrite it as our desired interface, and strip out every
			// member except those we want to keep in our new interface.
			ExtractInterfaceVisitor extractInterfaceVisitor = new ExtractInterfaceVisitor(newInterfaceName,
			                                                                              sourceNamespace,
			                                                                              sourceClassName,
			                                                                              membersToKeep);
			parser.CompilationUnit.AcceptVisitor(extractInterfaceVisitor, null);

			// now use an output visitor for the appropriate language (based on
			// extension of the existing code file) to format the new interface.
			IOutputAstVisitor output = GetOutputVisitor();
			if (preserveComments) {
				// run the output visitor with the specials inserter to insert comments
				// NOTE: *all* comments will be preserved, even for code that has been
				//       removed to create the interface...
				// TODO: is it worth enhancing the SpecialsNodeInserter to attach comments directly to code so they can be filtered based on what is preserved after a transformation?
				using (SpecialNodesInserter.Install(parser.Lexer.SpecialTracker.RetrieveSpecials(), output)) {
					parser.CompilationUnit.AcceptVisitor(output, null);
				}
			} else {
				// run the output visitor without the specials inserter
				parser.CompilationUnit.AcceptVisitor(output, null);
			}
			parser.Dispose();
			
			if (output.Errors.Count == 0) {
				// get the output
				codeForNewInterface = output.Text;
			} else {
				// dump errors into the new interface file...
				codeForNewInterface = String.Format("{0} {1}",
				                                    this.CommentToken, output.Errors.ErrorOutput);
			}

			// wrap the new code in the same comments/usings/namespace as the the original class file.
			string newFileContent = CreateNewFileLikeExisting(existingCode, codeForNewInterface);

			return newFileContent;
		}
		
		private class AddTypeToBaseTypesVisitor : NR.Visitors.AbstractAstVisitor
		{
			private TypeReference typeReference;
			
			public AddTypeToBaseTypesVisitor(string newTypeName)
			{
				this.typeReference = new TypeReference(newTypeName);
			}
			
			public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
			{
				// test the Type string property explicitly (rather than .BaseTypes.Contains())
				// to ensure that a matching type name is enough to prevent adding a second
				// reference.
				bool exists = false;
				foreach(TypeReference type in typeDeclaration.BaseTypes) {
					if (type.Type == this.typeReference.Type) {
						exists = true;
						break;
					}
				}
				if (!exists) {
					typeDeclaration.BaseTypes.Add(this.typeReference);
				}
				return base.VisitTypeDeclaration(typeDeclaration, data);
			}
		}
		
		public override string AddBaseTypeToClass(string existingCode, string newInterfaceName)
		{
			string newCode = existingCode;
			NR.IParser parser = ParseFile(null, existingCode);
			if (parser == null) {
				return null;
			}

			AddTypeToBaseTypesVisitor addTypeToBaseTypesVisitor = new AddTypeToBaseTypesVisitor(newInterfaceName);

			parser.CompilationUnit.AcceptVisitor(addTypeToBaseTypesVisitor, null);

			// now use an output visitor for the appropriate language (based on
			// extension of the existing code file) to format the new interface.
			IOutputAstVisitor output = GetOutputVisitor();
			
			// run the output visitor with the specials inserter to insert comments
			using (SpecialNodesInserter.Install(parser.Lexer.SpecialTracker.RetrieveSpecials(), output)) {
				parser.CompilationUnit.AcceptVisitor(output, null);
			}

			parser.Dispose();
			
			if (output.Errors.Count > 0) {
				ShowSourceCodeErrors(null, output.Errors.ErrorOutput);
				return null;
			}
			
			return output.Text;
		}
		#endregion
		
		#region FindUnusedUsingDeclarations
		protected class PossibleTypeReference
		{
			public string Name;
			public int TypeParameterCount;
			public IMethod ExtensionMethod;
			
			public PossibleTypeReference(string name)
			{
				this.Name = name;
			}
			
			public PossibleTypeReference(IdentifierExpression identifierExpression)
			{
				this.Name = identifierExpression.Identifier;
				this.TypeParameterCount = identifierExpression.TypeArguments.Count;
			}
			
			public PossibleTypeReference(TypeReference tr)
			{
				this.Name = tr.Type;
				this.TypeParameterCount = tr.GenericTypes.Count;
			}
			
			public PossibleTypeReference(IMethod extensionMethod)
			{
				this.ExtensionMethod = extensionMethod;
			}
			
			public override int GetHashCode()
			{
				int hashCode = 0;
				unchecked {
					if (Name != null) hashCode += 1000000007 * Name.GetHashCode();
					hashCode += 1000000009 * TypeParameterCount.GetHashCode();
					if (ExtensionMethod != null) hashCode += 1000000021 * ExtensionMethod.GetHashCode();
				}
				return hashCode;
			}
			
			public override bool Equals(object obj)
			{
				PossibleTypeReference other = obj as PossibleTypeReference;
				if (other == null) return false;
				return this.Name == other.Name && this.TypeParameterCount == other.TypeParameterCount && object.Equals(this.ExtensionMethod, other.ExtensionMethod);
			}
		}
		
		private class FindPossibleTypeReferencesVisitor : NR.Visitors.AbstractAstVisitor
		{
			internal HashSet<PossibleTypeReference> list = new HashSet<PossibleTypeReference>();
			NRefactoryResolver.NRefactoryResolver resolver;
			ParseInformation parseInformation;
			
			public FindPossibleTypeReferencesVisitor(ParseInformation parseInformation)
			{
				if (parseInformation != null) {
					this.parseInformation = parseInformation;
					resolver = new NRefactoryResolver.NRefactoryResolver(parseInformation.MostRecentCompilationUnit.ProjectContent.Language);
				}
			}
			
			public override object VisitIdentifierExpression(IdentifierExpression identifierExpression, object data)
			{
				list.Add(new PossibleTypeReference(identifierExpression));
				return base.VisitIdentifierExpression(identifierExpression, data);
			}
			
			public override object VisitTypeReference(TypeReference typeReference, object data)
			{
				if (!typeReference.IsGlobal) {
					list.Add(new PossibleTypeReference(typeReference));
				}
				return base.VisitTypeReference(typeReference, data);
			}
			
			public override object VisitAttribute(ICSharpCode.NRefactory.Ast.Attribute attribute, object data)
			{
				list.Add(new PossibleTypeReference(attribute.Name));
				list.Add(new PossibleTypeReference(attribute.Name + "Attribute"));
				return base.VisitAttribute(attribute, data);
			}
			
			public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data)
			{
				base.VisitInvocationExpression(invocationExpression, data);
				// don't use uninitialized resolver
				if (resolver.ProjectContent == null)
					return null;
				if (invocationExpression.TargetObject is MemberReferenceExpression) {
					MemberResolveResult mrr = resolver.ResolveInternal(invocationExpression, ExpressionContext.Default) as MemberResolveResult;
					if (mrr != null) {
						IMethod method = mrr.ResolvedMember as IMethod;
						if (method != null && method.IsExtensionMethod) {
							list.Add(new PossibleTypeReference(method));
						}
					}
				}
				return null;
			}
			
			public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
			{
				// Initialize resolver for method:
				if (!methodDeclaration.Body.IsNull && resolver != null) {
					if (resolver.Initialize(parseInformation, methodDeclaration.Body.StartLocation.Y, methodDeclaration.Body.StartLocation.X)) {
						resolver.RunLookupTableVisitor(methodDeclaration);
					}
				}
				return base.VisitMethodDeclaration(methodDeclaration, data);
			}
			
			public override object VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data)
			{
				if (resolver != null) {
					if (resolver.Initialize(parseInformation, propertyDeclaration.BodyStart.Y, propertyDeclaration.BodyStart.X)) {
						resolver.RunLookupTableVisitor(propertyDeclaration);
					}
				}
				return base.VisitPropertyDeclaration(propertyDeclaration, data);
			}
			
			public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data)
			{
				if (resolver != null) {
					resolver.Initialize(parseInformation, fieldDeclaration.StartLocation.X, fieldDeclaration.StartLocation.Y);
				}
				return base.VisitFieldDeclaration(fieldDeclaration, data);
			}
		}
		
		protected virtual HashSet<PossibleTypeReference> FindPossibleTypeReferences(IDomProgressMonitor progressMonitor, string fileContent, ParseInformation parseInfo)
		{
			NR.IParser parser = ParseFile(progressMonitor, fileContent);
			if (parser == null) {
				return null;
			} else {
				FindPossibleTypeReferencesVisitor visitor = new FindPossibleTypeReferencesVisitor(parseInfo);
				parser.CompilationUnit.AcceptVisitor(visitor, null);
				parser.Dispose();
				return visitor.list;
			}
		}
		
		public override bool SupportsFindUnusedUsingDeclarations {
			get {
				return true;
			}
		}
		
		public override IList<IUsing> FindUnusedUsingDeclarations(IDomProgressMonitor progressMonitor, string fileName, string fileContent, ICompilationUnit cu)
		{
			IClass @class = cu.Classes.Count == 0 ? null : cu.Classes[0];
			
			HashSet<PossibleTypeReference> references = FindPossibleTypeReferences(progressMonitor, fileContent, new ParseInformation(cu));
			if (references == null) return new IUsing[0];
			
			HashSet<IUsing> usedUsings = new HashSet<IUsing>();
			foreach (PossibleTypeReference tr in references) {
				if (tr.ExtensionMethod != null) {
					// the invocation of an extension method can implicitly use a using
					StringComparer nameComparer = cu.ProjectContent.Language.NameComparer;
					// go through all usings in all nested child scopes
					foreach (IUsing import in cu.GetAllUsings()) {
						foreach (string i in import.Usings) {
							if (nameComparer.Equals(tr.ExtensionMethod.DeclaringType.Namespace, i)) {
								usedUsings.Add(import);
							}
						}
					}
				} else {
					// normal possible type reference
					SearchTypeRequest request = new SearchTypeRequest(tr.Name, tr.TypeParameterCount, @class, cu, 1, 1);
					SearchTypeResult response = cu.ProjectContent.SearchType(request);
					if (response.UsedUsing != null) {
						usedUsings.Add(response.UsedUsing);
					}
				}
			}
			
			List<IUsing> unusedUsings = new List<IUsing>();
			foreach (IUsing import in cu.GetAllUsings()) {
				if (!usedUsings.Contains(import)) {
					if (import.HasAliases) {
						foreach (string key in import.Aliases.Keys) {
							if (references.Contains(new PossibleTypeReference(key)))
								goto checkNextImport;
						}
					}
					unusedUsings.Add(import); // this using is unused
				}
				checkNextImport:;
			}
			return unusedUsings;
		}
		#endregion
		
		#region CreateNewFileLikeExisting
		public override bool SupportsCreateNewFileLikeExisting {
			get {
				return true;
			}
		}
		
		public override string CreateNewFileLikeExisting(string existingFileContent, string codeForNewType)
		{
			NR.IParser parser = ParseFile(null, existingFileContent);
			if (parser == null) {
				return null;
			}
			RemoveTypesVisitor visitor = new RemoveTypesVisitor();
			parser.CompilationUnit.AcceptVisitor(visitor, null);
			List<NR.ISpecial> comments = new List<NR.ISpecial>();
			foreach (NR.ISpecial c in parser.Lexer.SpecialTracker.CurrentSpecials) {
				if (c.StartPosition.Y <= visitor.includeCommentsUpToLine
				    || c.StartPosition.Y > visitor.includeCommentsAfterLine)
				{
					comments.Add(c);
				}
			}
			IOutputAstVisitor outputVisitor = (language==NR.SupportedLanguage.CSharp) ? new CSharpOutputVisitor() : (IOutputAstVisitor)new VBNetOutputVisitor();
			using (SpecialNodesInserter.Install(comments, outputVisitor)) {
				parser.CompilationUnit.AcceptVisitor(outputVisitor, null);
			}
			string expectedText;
			if (language==NR.SupportedLanguage.CSharp)
				expectedText = "using " + RemoveTypesVisitor.DummyIdentifier + ";";
			else
				expectedText = "Imports " + RemoveTypesVisitor.DummyIdentifier;
			using (StringWriter w = new StringWriter()) {
				using (StringReader r1 = new StringReader(outputVisitor.Text)) {
					string line;
					while ((line = r1.ReadLine()) != null) {
						string trimLine = line.TrimStart();
						if (trimLine == expectedText) {
							string indentation = line.Substring(0, line.Length - trimLine.Length);
							using (StringReader r2 = new StringReader(codeForNewType)) {
								while ((line = r2.ReadLine()) != null) {
									w.Write(indentation);
									w.WriteLine(line);
								}
							}
						} else {
							w.WriteLine(line);
						}
					}
				}
				if (visitor.firstType) {
					w.WriteLine(codeForNewType);
				}
				return w.ToString();
			}
		}
		
		private class RemoveTypesVisitor : NR.Visitors.AbstractAstTransformer
		{
			internal const string DummyIdentifier = "DummyNamespace!InsertionPos";
			
			internal int includeCommentsUpToLine;
			internal int includeCommentsAfterLine = int.MaxValue;
			
			internal bool firstType = true;
			
			public override object VisitUsingDeclaration(UsingDeclaration usingDeclaration, object data)
			{
				if (firstType) {
					includeCommentsUpToLine = usingDeclaration.EndLocation.Y;
				}
				return null;
			}
			
			public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data)
			{
				includeCommentsAfterLine = namespaceDeclaration.EndLocation.Y;
				if (firstType) {
					includeCommentsUpToLine = namespaceDeclaration.StartLocation.Y;
					return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				} else {
					RemoveCurrentNode();
					return null;
				}
			}
			
			public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
			{
				if (typeDeclaration.EndLocation.Y > includeCommentsAfterLine)
					includeCommentsAfterLine = typeDeclaration.EndLocation.Y;
				if (firstType) {
					firstType = false;
					ReplaceCurrentNode(new UsingDeclaration(DummyIdentifier));
				} else {
					RemoveCurrentNode();
				}
				return null;
			}
		}
		#endregion
		
		#region ExtractCodeForType
		public override bool SupportsGetFullCodeRangeForType {
			get {
				return true;
			}
		}
		
		public override DomRegion GetFullCodeRangeForType(string fileContent, IClass type)
		{
			NR.Parser.ILexer lexer = NR.ParserFactory.CreateLexer(language, new StringReader(fileContent));
			// use the lexer to determine last token position before type start
			// and next token position after type end
			Stack<NR.Location> stack = new Stack<NR.Location>();
			NR.Location lastPos = NR.Location.Empty;
			NR.Parser.Token t = lexer.NextToken();
			bool csharp = language == NR.SupportedLanguage.CSharp;
			int eof = csharp ? NR.Parser.CSharp.Tokens.EOF : NR.Parser.VB.Tokens.EOF;
			int attribStart = csharp ? NR.Parser.CSharp.Tokens.OpenSquareBracket : NR.Parser.VB.Tokens.LessThan;
			int attribEnd = csharp ? NR.Parser.CSharp.Tokens.CloseSquareBracket : NR.Parser.VB.Tokens.GreaterThan;
			
			while (t.kind != eof) {
				if (t.kind == attribStart)
					stack.Push(lastPos);
				if (t.EndLocation.Y >= type.Region.BeginLine)
					break;
				lastPos = t.EndLocation;
				if (t.kind == attribEnd && stack.Count > 0)
					lastPos = stack.Pop();
				t = lexer.NextToken();
			}
			
			stack = null;
			
			// Skip until end of type
			while (t.kind != eof) {
				if (t.EndLocation.Y > type.BodyRegion.EndLine)
					break;
				t = lexer.NextToken();
			}
			
			int lastLineBefore = lastPos.IsEmpty ? 0 : lastPos.Y;
			int firstLineAfter = t.EndLocation.IsEmpty ? int.MaxValue : t.EndLocation.Y;
			
			lexer.Dispose();
			lexer = null;
			
			StringReader myReader = new StringReader(fileContent);
			
			string line;
			string mainLine;
			int resultBeginLine = lastLineBefore + 1;
			int resultEndLine = firstLineAfter - 1;
			int lineNumber = 0;
			int largestEmptyLineCount = 0;
			int emptyLinesInRow = 0;
			while ((line = myReader.ReadLine()) != null) {
				lineNumber++;
				if (lineNumber <= lastLineBefore)
					continue;
				if (lineNumber < type.Region.BeginLine) {
					string trimLine = line.TrimStart();
					if (trimLine.Length == 0) {
						if (++emptyLinesInRow > largestEmptyLineCount) {
							largestEmptyLineCount = emptyLinesInRow;
							resultBeginLine = lineNumber + 1;
						}
					} else {
						emptyLinesInRow = 0;
						if (IsEndDirective(trimLine)) {
							largestEmptyLineCount = 0;
							resultBeginLine = lineNumber + 1;
						}
					}
				} else if (lineNumber == type.Region.BeginLine) {
					mainLine = line;
				}
				// Region.BeginLine could be BodyRegion.EndLine
				if (lineNumber == type.BodyRegion.EndLine) {
					largestEmptyLineCount = 0;
					emptyLinesInRow = 0;
					resultEndLine = lineNumber;
				} else if (lineNumber > type.BodyRegion.EndLine) {
					if (lineNumber >= firstLineAfter)
						break;
					string trimLine = line.TrimStart();
					if (trimLine.Length == 0) {
						if (++emptyLinesInRow > largestEmptyLineCount) {
							largestEmptyLineCount = emptyLinesInRow;
							resultEndLine = lineNumber - emptyLinesInRow;
						}
					} else {
						emptyLinesInRow = 0;
						if (IsStartDirective(trimLine)) {
							break;
						}
					}
				}
			}
			
			myReader.Dispose();
			return new DomRegion(resultBeginLine, 0, resultEndLine, int.MaxValue);
		}
		
		static bool IsEndDirective(string trimLine)
		{
			return trimLine.StartsWith("#endregion") || trimLine.StartsWith("#endif");
		}
		
		static bool IsStartDirective(string trimLine)
		{
			return trimLine.StartsWith("#region") || trimLine.StartsWith("#if");
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior) Microsoft
United States United States
Sebastien Lebreton is a Software Engineer at Microsoft.

He is particularly interested in optimization, reverse engineering and distributed objects technologies.

Comments and Discussions