private static void GetDocumentContext(ProjectAnalysisResult result, int textCursorOffset, ReadOnlyDocument doc)
        {
            #region Debugging Aid

            // Resolve content around text cursor
#if false
            int numberOfCharactersAroundCursorToResolve = 20;
            int firstCharOffset = textCursorOffset - numberOfCharactersAroundCursorToResolve / 2;
            int lastCharOffset  = textCursorOffset + numberOfCharactersAroundCursorToResolve / 2;
#else
            int firstCharOffset = textCursorOffset - 40;
            int lastCharOffset  = textCursorOffset + 20;
#endif

            // shift window to the "right"
            if (firstCharOffset < 0)
            {
                lastCharOffset -= firstCharOffset;
                firstCharOffset = 0;
            }

            // shift window to the "left"
            if (lastCharOffset > doc.TextLength)
            {
                firstCharOffset -= (lastCharOffset - doc.TextLength);
                lastCharOffset   = doc.TextLength;

                // compensate, in case "left" side of window is in the void
                if (firstCharOffset < 0)
                {
                    firstCharOffset = 0;
                }
            }

            //if (doc.TextLength < firstCharOffset + numberOfCharactersAroundCursorToResolve)
            //    numberOfCharactersAroundCursorToResolve = doc.TextLength - firstCharOffset;
            //string surroundingText = doc.GetText(firstCharOffset, numberOfCharactersAroundCursorToResolve);
            //Debug.WriteLine("Text around cursor: [{0}]", surroundingText);

            result.CompletionContextBefore = doc.GetText(firstCharOffset, textCursorOffset - firstCharOffset);
            result.CompletionContextAfter  = doc.GetText(textCursorOffset, lastCharOffset - textCursorOffset);

            Debug.WriteLine("Completion Context: \"{0}\" <cursor> \"{1}\"", result.CompletionContextBefore, result.CompletionContextAfter);

            #endregion
        }
		void RenameAllReferences(List<SearchResultMatch> references)
		{
			SearchResultMatch firstResult = references.First();
			var document = new ReadOnlyDocument(SD.FileService.GetFileContent(firstResult.FileName));
			string name = document.GetText(firstResult.StartOffset, firstResult.Length);
			string newName = GetNewName(name);
			if (ShouldRenameReference(newName, name)) {
				ApplyRenames(references, newName);
			}
		}
        void RenameAllReferences(List <SearchResultMatch> references)
        {
            SearchResultMatch firstResult = references.First();
            var    document = new ReadOnlyDocument(SD.FileService.GetFileContent(firstResult.FileName));
            string name     = document.GetText(firstResult.StartOffset, firstResult.Length);
            string newName  = GetNewName(name);

            if (ShouldRenameReference(newName, name))
            {
                ApplyRenames(references, newName);
            }
        }
Esempio n. 4
0
        void CheckWhitespace(AstNode startNode, TextLocation whitespaceStart, AstNode endNode, TextLocation whitespaceEnd)
        {
            if (whitespaceStart == whitespaceEnd || startNode == endNode)
            {
                return;
            }
            int    start     = currentDocument.GetOffset(whitespaceStart.Line, whitespaceStart.Column);
            int    end       = currentDocument.GetOffset(whitespaceEnd.Line, whitespaceEnd.Column);
            string text      = currentDocument.GetText(start, end - start);
            bool   assertion = string.IsNullOrWhiteSpace(text);

            if (!assertion)
            {
                if (startNode.Parent != endNode.Parent)
                {
                    PrintNode(startNode.Parent);
                }
                PrintNode(endNode.Parent);
            }
            Assert.IsTrue(assertion, "Expected whitespace between " + startNode.GetType() + ":" + whitespaceStart + " and " + endNode.GetType() + ":" + whitespaceEnd
                          + ", but got '" + text + "' (in " + currentFileName + " parent:" + startNode.Parent.GetType() + ")");
        }
        //// todo: eliminate this method later
        //private static void AugmentProjectModelWithAlgorithmBase(ProjectModel model)
        //{
        //    model.Children.Add(QcAlgorithmFileModel.Value);
        //}

        public static ProjectAnalysisResult RunFullProjectAnalysis(ProjectAnalysisRequest projectAnalysisRequest)
        {
            Stopwatch sw = Stopwatch.StartNew();

            ProjectAnalysisResult projectAnalysisResult = new ProjectAnalysisResult();
            ProjectModel          projectModel          = projectAnalysisRequest.ProjectModel;

            // todo: Ask Jared why QCAlgorithm is a partial class and why it's not included in "common"

#if false
            // ************************************************************************************
            // NOTE:  In order to get this project building cleanly, with minimal dependencies
            // before checking into Github, I've removed all hard QuantConnect dependencies,
            // including the QCAlgorithm.cs embedded resource and the assembly references to
            // QuantConnect.Algorithm.Interface

            // Augment the project model with the QCAgorithm base class
            // projectModel.Children.Add(QcAlgorithmFileModel.Value);

            // ************************************************************************************
#endif

            // Set up the project (if not already done)
            if (projectModel.ProjectContent == null)
            {
                projectModel.ProjectContent = new CSharpProjectContent();
                projectModel.ProjectContent = projectModel.ProjectContent.AddAssemblyReferences(QCReferences.Value);
            }

            // For each new file, we need to integrate it into the project content
            var fileModelsInProject = projectModel.GetFileDescendants().ToArray();
            foreach (var fileModelInProject in fileModelsInProject)
            {
                IntegrateFileModel(projectModel, fileModelInProject);
            }

            // We can return now if no code completion was requested
            if (projectAnalysisRequest.CodeCompletionParameters == null)
            {
                projectAnalysisResult.TimeElapsed = sw.Elapsed;
                return(projectAnalysisResult);
            }

            // Now it's time to give attention specifically to the matter of resolving the code completion
            // options.  This, of course, requires a deeper analysis of the specified file...

            var codeCompletionParams = projectAnalysisRequest.CodeCompletionParameters;

            // Locate the file in the project
            ProjectFileModel fileModel = projectModel.FindFile(codeCompletionParams.FileId);
            if (fileModel == null)
            {
                throw new Exception("Specified file does not exist in this project");
            }


            // Create a TypeSystem.ICompilation that allows resolving within the project.
            var compilation = projectModel.ProjectContent.CreateCompilation();

            #region Resolve text cursor/caret location

            // The text cursor position is crucial to creating a properly-scoped type resolution context
            // so as to get relevant code completion suggestions.
            int              textCursorOffset   = 0;
            TextLocation     textCursorLocation = new TextLocation(1, 1);
            ReadOnlyDocument doc = new ReadOnlyDocument(fileModel.Content);
            try
            {
                // if line and column aren't set, we'll assume that the cursor offset/index is set
                if (codeCompletionParams.Line == 0 && codeCompletionParams.Column == 0)
                {
                    textCursorOffset = codeCompletionParams.Offset;
                    if (textCursorOffset < 0)
                    {
                        textCursorOffset = 0;
                    }
                    textCursorLocation = doc.GetLocation(textCursorOffset);
                }
                // if either line or column are invalid (i.e. <= 0), then we'll use offset 0 instead
                else if (codeCompletionParams.Line <= 0 || codeCompletionParams.Column <= 0)
                {
                    textCursorOffset = 0;
                }
                else
                {
                    textCursorLocation          = new TextLocation(codeCompletionParams.Line, codeCompletionParams.Column);
                    textCursorOffset            = doc.GetOffset(textCursorLocation);
                    codeCompletionParams.Offset = textCursorOffset;
                }
            }
            catch (Exception)
            {
                textCursorOffset   = 0;
                textCursorLocation = new TextLocation(1, 1);
            }
            finally
            {
                projectAnalysisResult.Line   = textCursorLocation.Line;
                projectAnalysisResult.Column = textCursorLocation.Column;
                projectAnalysisResult.Offset = textCursorOffset;
            }

            #endregion

            #region Create and Refine the type resolution context as much as possible based upon the cursor position

            var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly);
            // Constrain the resolve context by using scope
            typeResolveContext = typeResolveContext
                                 .WithUsingScope(fileModel.UnresolvedFile.GetUsingScope(textCursorLocation)
                                                 .Resolve(compilation));

            var curDef = fileModel.UnresolvedFile.GetInnermostTypeDefinition(textCursorLocation);
            if (curDef != null)
            {
                var resolvedDef = curDef.Resolve(typeResolveContext).GetDefinition();
                typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef);
                var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= textCursorLocation && textCursorLocation < m.BodyRegion.End);
                if (curMember != null)
                {
                    typeResolveContext = typeResolveContext.WithCurrentMember(curMember);
                }
            }

            #endregion

            // The purpose of the rest of these steps is a little fuzzy in my mind...
            // I'm still trying to understand them fully and if/why they're all needed.
            // It seems there is some redundancy here...

            var completionContext = new DefaultCompletionContextProvider(doc, fileModel.UnresolvedFile);

            #region Add Preprocessor Symbols??
            completionContext.AddSymbol("TEST");
            foreach (var sym in fileModel.SyntaxTree.ConditionalSymbols)
            {
                completionContext.AddSymbol(sym);
            }
            #endregion

            var completionDataFactory = new CodeCompletionDataFactory(new CSharpResolver(typeResolveContext));

            var completionEngine = new CSharpCompletionEngine(doc, completionContext, completionDataFactory, projectModel.ProjectContent, typeResolveContext);
            completionEngine.EolMarker        = Environment.NewLine;
            completionEngine.FormattingPolicy = FormattingOptionsFactory.CreateMono();
            projectModel.CompletionEngine     = completionEngine;

            // Attach contextual info to analysis result
            GetDocumentContext(projectAnalysisResult, textCursorOffset, doc);

            // Finally, generate completion data!
            var completionOptions = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray();

            projectAnalysisResult.CompletionOptions       = completionOptions.OrderBy(x => x.CompletionText).ToArray();
            projectAnalysisResult.AutoCompleteEmptyMatch  = completionEngine.AutoCompleteEmptyMatch;
            projectAnalysisResult.AutoSelect              = completionEngine.AutoSelect;
            projectAnalysisResult.DefaultCompletionString = completionEngine.DefaultCompletionString;

            int startPos, wordLength;
            if (completionEngine.TryGetCompletionWord(textCursorOffset, out startPos, out wordLength))
            {
                //Debug.WriteLine("TryGetCompletionWord :: startpos:{0}  wordlength:{1}", startPos, wordLength);
                string completionWord = projectAnalysisResult.CompletionWord = doc.GetText(startPos, wordLength);

                if (!string.IsNullOrWhiteSpace(completionWord))
                {
                    var bestMatch = projectAnalysisResult.CompletionOptions
                                    .FirstOrDefault(x => x.CompletionText.CompareTo(completionWord) >= 0);
                    projectAnalysisResult.BestMatchToCompletionWord = bestMatch;
                    //if (bestMatch != null)
                    //projectAnalysisResult.BestMatchToCompletionWord = bestMatch.CompletionText;
                }
            }

            projectAnalysisResult.TimeElapsed = sw.Elapsed;

            return(projectAnalysisResult);
        }
Esempio n. 6
0
        public static ProjectAnalysisResult RunFullProjectAnalysis(ProjectAnalysisRequest projectAnalysisRequest) // ProjectModel projectModel, int fileId, int line, int column)
        {
            Stopwatch sw = Stopwatch.StartNew();

            ProjectAnalysisResult projectAnalysisResult = new ProjectAnalysisResult();
            ProjectModel          projectModel          = projectAnalysisRequest.ProjectModel;


            // Set up the project (if not already done)
            if (projectModel.ProjectContent == null)
            {
                projectModel.ProjectContent = new CSharpProjectContent();
                projectModel.ProjectContent = projectModel.ProjectContent.AddAssemblyReferences(QCReferences.Value);
            }

            // For each new file, we need to integrate it into the project content
            var fileModelsInProject = projectModel.GetFileDescendants().ToArray();

            foreach (var fileModelInProject in fileModelsInProject)
            {
                IntegrateFileModel(projectModel, fileModelInProject);
            }

            // We can return now if no code completion was requested
            if (projectAnalysisRequest.CodeCompletionParameters == null)
            {
                projectAnalysisResult.TimeElapsed = sw.Elapsed;
                return(projectAnalysisResult);
            }

            // Now it's time to give attention specifically to the matter of resolving the code completion
            // options.  This, of course, requires a deeper analysis of the specified file...

            var codeCompletionParams = projectAnalysisRequest.CodeCompletionParameters;

            // Locate the file in the project
            ProjectFileModel fileModel = projectModel.FindFile(codeCompletionParams.FileId);

            if (fileModel == null)
            {
                throw new Exception("Specified file does not exist in this project");
            }


            // Create a TypeSystem.ICompilation that allows resolving within the project.
            var compilation = projectModel.ProjectContent.CreateCompilation();

            #region Resolve text cursor/caret location

            // The text cursor position is crucial to creating a properly-scoped type resolution context
            // so as to get relevant code completion suggestions.
            int              textCursorOffset;
            TextLocation     textCursorLocation;
            ReadOnlyDocument doc = new ReadOnlyDocument(fileModel.Content);
            if (codeCompletionParams.Line == 0 && codeCompletionParams.Column == 0)
            {
                textCursorOffset            = codeCompletionParams.Offset;
                textCursorLocation          = doc.GetLocation(textCursorOffset);
                codeCompletionParams.Line   = textCursorLocation.Line;
                codeCompletionParams.Column = textCursorLocation.Column;
            }
            else
            {
                textCursorLocation          = new TextLocation(codeCompletionParams.Line, codeCompletionParams.Column);
                textCursorOffset            = doc.GetOffset(textCursorLocation);
                codeCompletionParams.Offset = textCursorOffset;
            }

            #endregion

            #region Create and Refine the type resolution context as much as possible based upon the cursor position

            var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly);
            // Constrain the resolve context by using scope
            typeResolveContext = typeResolveContext
                                 .WithUsingScope(fileModel.UnresolvedFile.GetUsingScope(textCursorLocation)
                                                 .Resolve(compilation));

            var curDef = fileModel.UnresolvedFile.GetInnermostTypeDefinition(textCursorLocation);
            if (curDef != null)
            {
                var resolvedDef = curDef.Resolve(typeResolveContext).GetDefinition();
                typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef);
                var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= textCursorLocation && textCursorLocation < m.BodyRegion.End);
                if (curMember != null)
                {
                    typeResolveContext = typeResolveContext.WithCurrentMember(curMember);
                }
            }

            #endregion

            // The purpose of the rest of these steps is a little fuzzy in my mind...
            // I'm still trying to understand them fully and if/why they're all needed.
            // It seems there is some redundancy here...

            var completionContext = new DefaultCompletionContextProvider(doc, fileModel.UnresolvedFile);

            #region Add Preprocessor Symbols??
            completionContext.AddSymbol("TEST");
            foreach (var sym in fileModel.SyntaxTree.ConditionalSymbols)
            {
                completionContext.AddSymbol(sym);
            }
            #endregion

            var completionDataFactory = new TestCompletionDataFactory(new CSharpResolver(typeResolveContext));

            var completionEngine = new CSharpCompletionEngine(doc, completionContext, completionDataFactory, projectModel.ProjectContent, typeResolveContext);
            completionEngine.EolMarker        = Environment.NewLine;
            completionEngine.FormattingPolicy = FormattingOptionsFactory.CreateMono();
            projectModel.CompletionEngine     = completionEngine;

            #region Debugging Aid
            // Resolve content around text cursor
            int numberOfCharactersAroundCursorToResolve = 20;
            int firstCharOffset = textCursorOffset - numberOfCharactersAroundCursorToResolve / 2;
            if (firstCharOffset < 0)
            {
                firstCharOffset = 0;
            }
            if (doc.TextLength < firstCharOffset + numberOfCharactersAroundCursorToResolve)
            {
                numberOfCharactersAroundCursorToResolve = doc.TextLength - firstCharOffset;
            }
            string surroundingText = doc.GetText(firstCharOffset, numberOfCharactersAroundCursorToResolve);
            Debug.WriteLine("Text around cursor: [{0}]", surroundingText);
            #endregion

            // Finally, generate completion data!
            var completionOptions = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray();
            projectAnalysisResult.CompletionOptions       = completionEngine.GetCompletionData(textCursorOffset, projectAnalysisRequest.CodeCompletionParameters.CtrlSpace).ToArray();
            projectAnalysisResult.AutoCompleteEmptyMatch  = completionEngine.AutoCompleteEmptyMatch;
            projectAnalysisResult.AutoSelect              = completionEngine.AutoSelect;
            projectAnalysisResult.DefaultCompletionString = completionEngine.DefaultCompletionString;

            int startPos, wordLength;
            if (completionEngine.TryGetCompletionWord(textCursorOffset, out startPos, out wordLength))
            {
                Debug.WriteLine("TryGetCompletionWord :: startpos:{0}  wordlength:{1}", startPos, wordLength);
                projectAnalysisResult.CompletionWord = doc.GetText(startPos, wordLength);
            }

            projectAnalysisResult.TimeElapsed = sw.Elapsed;

            return(projectAnalysisResult);
        }
        /// <summary>
        /// Gets the project resource from the specified expression.
        /// </summary>
        public ProjectResourceInfo GetProjectResource(CodePropertyReferenceExpression propRef)
        {
            CodeTypeReferenceExpression typeRef = propRef.TargetObject as CodeTypeReferenceExpression;

            if (typeRef == null)
            {
                LoggingService.Info("Target of possible project resources property reference is not a type reference, but " + propRef.TargetObject.ToString() + ".");
                return(null);
            }

            ICompilation compilation = SD.ParserService.GetCompilation(project);

            // Get the (generated) class where the resource is defined.
            var resourceClass = compilation.FindType(new FullTypeName(typeRef.Type.BaseType)).GetDefinition();

            if (resourceClass == null)
            {
                throw new InvalidOperationException("Could not find class for project resources: '" + typeRef.Type.BaseType + "'.");
            }

            if (resourceClass.Region.IsEmpty)
            {
                return(null);
            }

            // Make sure the class we have found is a generated resource class.
            if (!IsGeneratedResourceClass(resourceClass))
            {
                return(null);
            }

            // Get the name of the resource file based on the file that contains the generated class.
            string resourceFileName = Path.GetFileNameWithoutExtension(resourceClass.Region.FileName);

            if (resourceFileName.EndsWith("Designer", StringComparison.OrdinalIgnoreCase))
            {
                resourceFileName = Path.GetFileNameWithoutExtension(resourceFileName);
            }
            resourceFileName = Path.Combine(Path.GetDirectoryName(resourceClass.Region.FileName), resourceFileName);
            if (File.Exists(resourceFileName + ".resources"))
            {
                resourceFileName = resourceFileName + ".resources";
            }
            else if (File.Exists(resourceFileName + ".resx"))
            {
                resourceFileName = resourceFileName + ".resx";
            }
            else
            {
                throw new FileNotFoundException("Could not find the resource file for type '" + resourceClass.FullName + "'. Tried these file names: '" + resourceFileName + ".(resources|resx)'.");
            }

            // Get the property for the resource.
            IProperty prop = resourceClass.Properties.SingleOrDefault(p => p.Name == propRef.PropertyName);

            if (prop == null)
            {
                throw new InvalidOperationException("Property '" + propRef.PropertyName + "' not found in type '" + resourceClass.FullName + "'.");
            }

            if (!prop.CanGet)
            {
                throw new InvalidOperationException("Property '" + propRef.PropertyName + "' in type '" + resourceClass.FullName + "' does not have a getter.");
            }

            // Get the code of the resource class and
            // extract the resource key from the property.
            // This is necessary because the key may differ from the property name
            // if special characters are used.
            // It would be better if we could use a real code parser for this, but
            // that is not possible without getting dependent on the programming language.

            IDocument doc = new ReadOnlyDocument(SD.FileService.GetFileContent(resourceClass.Region.FileName));

            int startOffset = doc.PositionToOffset(prop.Getter.BodyRegion.BeginLine, prop.Getter.BodyRegion.BeginColumn);
            int endOffset   = doc.PositionToOffset(prop.Getter.BodyRegion.EndLine, prop.Getter.BodyRegion.EndColumn);

            string code = doc.GetText(startOffset, endOffset - startOffset + 1);

            int index = code.IndexOf("ResourceManager", StringComparison.Ordinal);

            if (index == -1)
            {
                throw new InvalidOperationException("No reference to ResourceManager found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
            }

            index = code.IndexOf("Get", index, StringComparison.Ordinal);
            if (index == -1)
            {
                throw new InvalidOperationException("No call to Get... found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
            }

            index = code.IndexOf(this.StringLiteralDelimiter, index + 1, StringComparison.Ordinal);
            if (index == -1)
            {
                throw new InvalidOperationException("No string delimiter ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
            }
            index += this.StringLiteralDelimiter.Length;

            int endIndex = code.LastIndexOf(this.StringLiteralDelimiter, StringComparison.Ordinal);

            if (endIndex == -1)
            {
                throw new InvalidOperationException("No string terminator ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
            }

            string resourceKey = code.Substring(index, endIndex - index);

            LoggingService.Debug("-> Decoded resource: In: " + resourceFileName + ". Key: " + resourceKey);

            return(new ProjectResourceInfo(new FileName(resourceFileName), resourceKey));
        }
		/// <summary>
		/// Gets the project resource from the specified expression.
		/// </summary>
		public ProjectResourceInfo GetProjectResource(CodePropertyReferenceExpression propRef)
		{
			CodeTypeReferenceExpression typeRef = propRef.TargetObject as CodeTypeReferenceExpression;
			if (typeRef == null) {
				LoggingService.Info("Target of possible project resources property reference is not a type reference, but " + propRef.TargetObject.ToString() + ".");
				return null;
			}
			
			ICompilation compilation = SD.ParserService.GetCompilation(project);
			
			// Get the (generated) class where the resource is defined.
			var resourceClass = compilation.FindType(new FullTypeName(typeRef.Type.BaseType)).GetDefinition();
			if (resourceClass == null) {
				throw new InvalidOperationException("Could not find class for project resources: '" + typeRef.Type.BaseType + "'.");
			}
			
			if (resourceClass.Region.IsEmpty)
				return null;
			
			// Make sure the class we have found is a generated resource class.
			if (!IsGeneratedResourceClass(resourceClass)) {
				return null;
			}
			
			// Get the name of the resource file based on the file that contains the generated class.
			string resourceFileName = Path.GetFileNameWithoutExtension(resourceClass.Region.FileName);
			if (resourceFileName.EndsWith("Designer", StringComparison.OrdinalIgnoreCase)) {
				resourceFileName = Path.GetFileNameWithoutExtension(resourceFileName);
			}
			resourceFileName = Path.Combine(Path.GetDirectoryName(resourceClass.Region.FileName), resourceFileName);
			if (File.Exists(resourceFileName + ".resources")) {
				resourceFileName = resourceFileName + ".resources";
			} else if (File.Exists(resourceFileName + ".resx")) {
				resourceFileName = resourceFileName + ".resx";
			} else {
				throw new FileNotFoundException("Could not find the resource file for type '" + resourceClass.FullName + "'. Tried these file names: '" + resourceFileName + ".(resources|resx)'.");
			}
			
			// Get the property for the resource.
			IProperty prop = resourceClass.Properties.SingleOrDefault(p => p.Name == propRef.PropertyName);
			if (prop == null) {
				throw new InvalidOperationException("Property '" + propRef.PropertyName + "' not found in type '" + resourceClass.FullName + "'.");
			}
			
			if (!prop.CanGet) {
				throw new InvalidOperationException("Property '" + propRef.PropertyName + "' in type '" + resourceClass.FullName + "' does not have a getter.");
			}
			
			// Get the code of the resource class and
			// extract the resource key from the property.
			// This is necessary because the key may differ from the property name
			// if special characters are used.
			// It would be better if we could use a real code parser for this, but
			// that is not possible without getting dependent on the programming language.
			
			IDocument doc = new ReadOnlyDocument(SD.FileService.GetFileContent(resourceClass.Region.FileName));
			
			int startOffset = doc.PositionToOffset(prop.Getter.BodyRegion.BeginLine, prop.Getter.BodyRegion.BeginColumn);
			int endOffset   = doc.PositionToOffset(prop.Getter.BodyRegion.EndLine, prop.Getter.BodyRegion.EndColumn);
			
			string code = doc.GetText(startOffset, endOffset - startOffset + 1);
			
			int index = code.IndexOf("ResourceManager", StringComparison.Ordinal);
			if (index == -1) {
				throw new InvalidOperationException("No reference to ResourceManager found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
			}
			
			index = code.IndexOf("Get", index, StringComparison.Ordinal);
			if (index == -1) {
				throw new InvalidOperationException("No call to Get... found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
			}
			
			index = code.IndexOf(this.StringLiteralDelimiter, index + 1, StringComparison.Ordinal);
			if (index == -1) {
				throw new InvalidOperationException("No string delimiter ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
			}
			index += this.StringLiteralDelimiter.Length;
			
			int endIndex = code.LastIndexOf(this.StringLiteralDelimiter, StringComparison.Ordinal);
			if (endIndex == -1) {
				throw new InvalidOperationException("No string terminator ('" + this.StringLiteralDelimiter + "') found in property getter of '" + prop.FullName + "'. Code: '" + code + "'");
			}
			
			string resourceKey = code.Substring(index, endIndex - index);
			LoggingService.Debug("-> Decoded resource: In: " + resourceFileName + ". Key: " + resourceKey);
			
			return new ProjectResourceInfo(new FileName(resourceFileName), resourceKey);
		}