Example #1
0
        private static TestResult CheckAssemblyFileVersion(this List <string> fileLines, TestResult finalResult, string assemblyInfoPath, string documentationLink)
        {
            string currentAssemblyVersion = Query.FullCurrentAssemblyFileVersion();

            string searchLine = $"[assembly: AssemblyFileVersion(\"";

            string foundLine = fileLines.Where(x => x.StartsWith(searchLine)).FirstOrDefault();

            if (string.IsNullOrEmpty(foundLine))
            {
                return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error($"Assembly File Version should be set to {currentAssemblyVersion}", Create.Location(assemblyInfoPath, Create.LineSpan(1, 1)), documentationLink)
                })));
            }
            else
            {
                string allowedLine = $"{searchLine}{Query.FullCurrentAssemblyFileVersion()}\")]";
                int    line        = fileLines.IndexOf(foundLine) + 1;
                if (!foundLine.Contains(allowedLine))
                {
                    return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Assembly File Version should be set to {currentAssemblyVersion}", Create.Location(assemblyInfoPath, Create.LineSpan(line, line)), documentationLink)
                    })));
                }
            }

            return(finalResult);
        }
Example #2
0
        private static TestResult CheckPostBuild(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink)
        {
            string postBuildShouldContain = "";
            string searchLine             = "";

            if (csProject.IsOldStyle)
            {
                postBuildShouldContain = "xcopy \"$(TargetDir)$(TargetFileName)\" \"C:\\ProgramData\\BHoM\\Assemblies\" /Y";
                searchLine             = "<PostBuildEvent";
            }
            else
            {
                postBuildShouldContain = "&quot;$(TargetDir)$(TargetFileName)&quot; &quot;C:\\ProgramData\\BHoM\\Assemblies&quot; /Y";
                searchLine             = "<Exec Command=\"xcopy";
            }

            if (!csProject.PostBuildEvent.Any(x => x.Contains(postBuildShouldContain)))
            {
                postBuildShouldContain = "xcopy \"$(TargetDir)$(TargetFileName)\"  \"C:\\ProgramData\\BHoM\\Assemblies\" /Y"; //Check again with a double spacing
                if (!csProject.PostBuildEvent.Any(x => x.Contains(postBuildShouldContain)))
                {
                    postBuildShouldContain = "&quot;$(TargetDir)$(TargetFileName)&quot;  &quot;C:\\ProgramData\\BHoM\\Assemblies&quot; /Y"; //Check again with a double spacing
                    if (!csProject.PostBuildEvent.Any(x => x.Contains(postBuildShouldContain)))
                    {
                        int lineNumber = fileLines.IndexOf(fileLines.Where(x => x.Contains(searchLine)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                        return(finalResult.Merge(Create.TestResult(TestStatus.Warning, new List <Error> {
                            Create.Error($"Post Build event should be correctly set to copy the compiled DLL to the BHoM Assemblies folder", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink, TestStatus.Warning)
                        })));
                    }
                }
            }

            return(finalResult);
        }
Example #3
0
        private static TestResult CheckAssemblyDescription(this List <string> fileLines, TestResult finalResult, string assemblyInfoPath, string documentationLink, string descriptionUrl)
        {
            string searchLine = $"[assembly: AssemblyDescription(\"";

            string foundLine = fileLines.Where(x => x.StartsWith(searchLine)).FirstOrDefault();

            if (string.IsNullOrEmpty(foundLine))
            {
                return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error($"Assembly Description should contain the URL to the GitHub organisation which owns the repository", Create.Location(assemblyInfoPath, Create.LineSpan(1, 1)), documentationLink)
                })));
            }
            else
            {
                string allowedLine = $"{searchLine}{descriptionUrl}\")]";
                int    line        = fileLines.IndexOf(foundLine) + 1;
                if (!foundLine.Contains(allowedLine))
                {
                    return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Assembly Description should contain the URL to the GitHub organisation which owns the repository", Create.Location(assemblyInfoPath, Create.LineSpan(line, line)), documentationLink)
                    })));
                }
            }

            return(finalResult);
        }
Example #4
0
        private static TestResult CheckNETTarget(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink)
        {
            List <string> acceptableNETTargets = new List <string> {
                "v4.7.2", "net472", "netstandard2.0", "net5.0", "net6.0"
            };
            bool atLeastOneCorrect = false;

            foreach (string target in csProject.TargetNETVersions)
            {
                if (!string.IsNullOrEmpty(target) && !acceptableNETTargets.Contains(target))
                {
                    string fullXMLText = $"<TargetFramework>{target}</TargetFramework>";
                    int    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(fullXMLText)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Warning, new List <Error> {
                        Create.Error($"Target frameworks for BHoM projects should either be .Net Framework 4.7.2, .Net Standard 2.0, or .Net 5.0.", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink, TestStatus.Warning)
                    }));
                }
                else
                {
                    atLeastOneCorrect = true;
                }
            }

            if (!atLeastOneCorrect)
            {
                finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error($"At least one of the Target frameworks for BHoM projects must either be .Net Framework 4.7.2, .Net Standard 2.0, or .Net 5.0.", Create.Location(csProjFilePath, Create.LineSpan(1, 1)), documentationLink)
                }));
            }

            return(finalResult);
        }
Example #5
0
        private static TestResult CheckAssemblyDescription(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink, string descriptionUrl)
        {
            if (csProject.AssemblyDescription.ToLower() != descriptionUrl.ToLower())
            {
                string fullXMLText = $"<Description>{csProject.AssemblyDescription}</Description>";
                int    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(fullXMLText)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error($"Assembly Description should contain the URL to the GitHub organisation which owns the repository", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                })));
            }

            return(finalResult);
        }
Example #6
0
        private static TestResult CheckAssembyFileVersion(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink)
        {
            string currentlyAssemblyFileVersion = Query.FullCurrentAssemblyFileVersion();

            if (csProject.AssemblyFileVersion.ToLower() != currentlyAssemblyFileVersion.ToLower())
            {
                string fullXMLText = $"<FileVersion>{csProject.AssemblyFileVersion}</FileVersion>";
                int    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(fullXMLText)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                return(finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error($"Assembly File Version should be set to {currentlyAssemblyFileVersion}", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                })));
            }

            return(finalResult);
        }
Example #7
0
        public static TestResult CheckProjectFile(this string csProjFilePath, string assemblyDescriptionOrg = null)
        {
            if ((Path.GetExtension(csProjFilePath) != ".csproj"))
            {
                return(Create.TestResult(TestStatus.Pass));
            }

            if (!File.Exists(csProjFilePath))
            {
                return(Create.TestResult(TestStatus.Pass));
            }

            TestResult finalResult       = Create.TestResult(TestStatus.Pass);
            string     documentationLink = "Project-References-and-Build-Paths";

            List <string> fileLines = ReadFileContents(csProjFilePath);

            ProjectFile csProject = GetProjectFile(fileLines);

            if (csProject == null)
            {
                return(finalResult);
            }

            finalResult = CheckNETTarget(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);
            finalResult = CheckOutputPath(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);
            finalResult = CheckReferences(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);
            finalResult = CheckPostBuild(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);

            if (csProject.IsOldStyle)
            {
                finalResult = finalResult.Merge(Create.TestResult(TestStatus.Warning, new List <Error> {
                    Create.Error($"CSProject files should be in the new format as used by core BHoM projects. Upgrading the file is possible for .Net Framework 4.7.2 projects as well. Please speak to a member of the DevOps team for assistance with this.", Create.Location(csProjFilePath, Create.LineSpan(1, 1)), documentationLink, TestStatus.Warning)
                }));
            }
            else
            {
                finalResult = CheckAssemblyVersion(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);
                finalResult = CheckAssembyFileVersion(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink);

                if (!string.IsNullOrEmpty(assemblyDescriptionOrg))
                {
                    finalResult = CheckAssemblyDescription(csProject, new List <string>(fileLines), finalResult, csProjFilePath, documentationLink, assemblyDescriptionOrg);
                }
            }

            return(finalResult);
        }
Example #8
0
        public static TestResult Check(this MethodInfo method, SyntaxNode node, string checkType = null)
        {
            TestResult finalResult = Create.TestResult(TestStatus.Pass);

            if (method == null || node == null)
            {
                return(finalResult);
            }

            string path = node.SyntaxTree.FilePath;

            if (Path.GetFileName(path) == "AssemblyInfo.cs")
            {
                return(finalResult);
            }

            Type type = node.GetType();

            if (method.GetParameters()[0].ParameterType.IsAssignableFrom(type) &&
                !(typeof(MemberDeclarationSyntax).IsAssignableFrom(node.GetType()) &&
                  ((MemberDeclarationSyntax)node).IsDeprecated()) &&
                method.GetCustomAttributes <ConditionAttribute>().All(condition => condition.IPasses(node)) &&
                (checkType != null && method.GetCustomAttribute <ComplianceTypeAttribute>()?.ComplianceType == checkType))
            {
                Func <object[], object> fn = method.ToFunc();
                Span result = fn(new object[] { node }) as Span;
                if (result != null)
                {
                    string message       = method.GetCustomAttribute <MessageAttribute>()?.Message ?? "";
                    string documentation = method.GetCustomAttribute <MessageAttribute>()?.DocumentationLink ?? "";

                    TestStatus errLevel = method.GetCustomAttribute <ErrorLevelAttribute>()?.Level ?? TestStatus.Error;
                    finalResult = finalResult.Merge(Create.TestResult(
                                                        errLevel == TestStatus.Error ? TestStatus.Error : TestStatus.Warning,
                                                        new List <Error> {
                        Create.Error(message, Create.Location(path, result.ToLineSpan(node.SyntaxTree.GetRoot().ToFullString())), documentation, errLevel, method.Name)
                    }));
                }
            }

            return(finalResult.Merge(method.Check(node.ChildNodes(), checkType)));
        }
Example #9
0
        private static TestResult CheckOutputPath(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink)
        {
            List <string> acceptableOutputPaths = new List <string>()
            {
                "..\\Build\\"
            };

            foreach (string outputPath in csProject.OutputPaths)
            {
                if (!string.IsNullOrEmpty(outputPath) && !acceptableOutputPaths.Contains(outputPath))
                {
                    string fullXMLText = $"<OutputPath>{outputPath}</OutputPath>";
                    int    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(fullXMLText)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Output path for all build configurations should be set to '..\\Build\\'", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                    }));
                }
            }

            return(finalResult);
        }
Example #10
0
        public static TestResult CheckProjectStructure(this string projectDirectory)
        {
            TestResult finalResult = Create.TestResult(TestStatus.Pass);

            string documentationLink = "Project-References-and-Build-Paths";

            List <string> exceptionalRepos = new List <string>
            {
                "BHoM",
                "BHoM_Engine",
                "BHoM_UI",
                "BHoM_Adapter",
            };

            string[] directoryParts = projectDirectory.Split('\\');
            string   toolkit        = directoryParts.Last();

            string[] toolkitParts = toolkit.Split('_');
            if (toolkitParts.Length == 1 && !exceptionalRepos.Contains(toolkit))
            {
                finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error("Project not a valid project name. Project should end in '_Toolkit'", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                }));
            }
            else if (toolkitParts.Length == 2 && toolkitParts[1] != "Toolkit")
            {
                finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                    Create.Error("Project not a valid project name. Project should end in '_Toolkit'", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                }));
            }
            else if (toolkitParts.Length > 1)
            {
                string[] subFolders      = Directory.GetDirectories(projectDirectory);
                bool     containsEngine  = true;
                bool     containsAdapter = true;
                bool     containsObject  = true;

                if (subFolders.Where(x => x.EndsWith("_Engine")).Count() > 0 && subFolders.Where(x => x.EndsWith("_Engine") && (x.Split('\\').Last()) == toolkitParts[0] + "_Engine").FirstOrDefault() == null)
                {
                    containsEngine = false;
                }
                if (subFolders.Where(x => x.EndsWith("_Adapter")).Count() > 0 && subFolders.Where(x => x.EndsWith("_Adapter") && (x.Split('\\').Last()) == toolkitParts[0] + "_Adapter").FirstOrDefault() == null)
                {
                    containsAdapter = false;
                }
                if (subFolders.Where(x => x.EndsWith("_oM")).Count() > 0 && subFolders.Where(x => x.EndsWith("_oM") && (x.Split('\\').Last()) == toolkitParts[0] + "_oM").FirstOrDefault() == null)
                {
                    containsObject = false;
                }

                if (!containsObject)
                {
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Warning, new List <Error> {
                        Create.Error($"If the project requires an oM, the project should be titled '{toolkitParts[0]}_oM'", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                    }));
                }
                if (!containsAdapter)
                {
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Warning, new List <Error> {
                        Create.Error($"If the project requires an Adapter, the project should be titled '{toolkitParts[0]}_Adapter'", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                    }));
                }
                if (!containsEngine)
                {
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"If the project requires an Engine, the project should be titled '{toolkitParts[0]}_Engine'", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                    }));
                }

                List <string> allowedEngineFolders = new List <string>
                {
                    "Compute",
                    "Convert",
                    "Create",
                    "Modify",
                    "Query",
                    "Properties",
                    "obj",
                    "bin",
                };
                if (containsEngine)
                {
                    try
                    {
                        string[] engineFolders = Directory.GetDirectories(projectDirectory + "\\" + toolkitParts[0] + "_Engine");
                        if (engineFolders.Length > allowedEngineFolders.Count)
                        {
                            finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                                Create.Error($"The Engine project should only contain Compute, Convert, Create, Modify, and Query sub-folders", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                            }));
                        }
                        else
                        {
                            foreach (string st in engineFolders)
                            {
                                string[] pr = st.Split('\\');
                                if (!allowedEngineFolders.Contains(pr.Last()))
                                {
                                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                                        Create.Error($"{st} is not a valid Engine sub folder", Create.Location(projectDirectory, Create.LineSpan(1, 1)), documentationLink)
                                    }));
                                }
                            }
                        }
                    }
                    catch { }
                }
            }

            return(finalResult);
        }
Example #11
0
        public static TestResult HasValidCopyright(this SyntaxTriviaList leadingTrivia, int year = -1, string filePath = "")
        {
            if (leadingTrivia == null)
            {
                return(Create.TestResult(TestStatus.Pass));
            }

            bool checkAllYears = false;

            if (year == -1)
            {
                checkAllYears = true;
                year          = 2018; //Start
            }

            string documentationLink = "HasValidCopyright";

            int maxYear = DateTime.Now.Year; //Max

            string copyrightStatement = $@"/*
 * This file is part of the Buildings and Habitats object Model (BHoM)
 * Copyright (c) 2015 - {year}, the respective contributors. All rights reserved.
 *
 * Each contributor holds copyright over their respective contributions.
 * The project versioning (Git) records all such contribution source information.
 *                                           
 *                                                                              
 * The BHoM is free software: you can redistribute it and/or modify         
 * it under the terms of the GNU Lesser General Public License as published by  
 * the Free Software Foundation, either version 3.0 of the License, or          
 * (at your option) any later version.                                          
 *                                                                              
 * The BHoM is distributed in the hope that it will be useful,              
 * but WITHOUT ANY WARRANTY; without even the implied warranty of               
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                 
 * GNU Lesser General Public License for more details.                          
 *                                                                            
 * You should have received a copy of the GNU Lesser General Public License     
 * along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.      
 */";

            string l = leadingTrivia.ToString();

            l = l.Replace('\r', ' ');
            copyrightStatement = copyrightStatement.Replace('\r', ' ');

            string[] split          = l.Split('\n');
            string[] copyrightSplit = copyrightStatement.Split('\n');

            if (split.Length < copyrightSplit.Length)
            {
                Error e = Create.Error("Copyright message is not accurate at line " + 1, Create.Location(filePath, Create.LineSpan(1, 2)), documentationLink, TestStatus.Error);
                return(Create.TestResult(TestStatus.Error, new List <Error> {
                    e
                }));
            }

            if (!checkAllYears)
            {
                for (int x = 0; x < copyrightSplit.Length; x++)
                {
                    if (split[x].TrimEnd() != copyrightSplit[x].TrimEnd())
                    {
                        Error e = Create.Error("Copyright message is not accurate at line " + (x + 1), Create.Location(filePath, Create.LineSpan(x + 1, x + 2)), documentationLink, TestStatus.Error);
                        return(Create.TestResult(TestStatus.Error, new List <Error> {
                            e
                        }));
                    }
                }
            }
            else
            {
                List <int> availableYears = new List <int>();
                for (int x = 2018; x <= maxYear; x++)
                {
                    availableYears.Add(x);
                }

                for (int x = 0; x < copyrightSplit.Length; x++)
                {
                    if (x == 2)
                    {
                        continue;         //Skip the year line
                    }
                    if (split[x].TrimEnd() != copyrightSplit[x].TrimEnd())
                    {
                        Error e = Create.Error("Copyright message is not accurate at line " + (x + 1), Create.Location(filePath, Create.LineSpan(x + 1, x + 2)), documentationLink, TestStatus.Error);
                        return(Create.TestResult(TestStatus.Error, new List <Error> {
                            e
                        }));
                    }
                }

                bool validOnOneYear = false;
                foreach (int a in availableYears)
                {
                    copyrightSplit[2] = $" * Copyright (c) 2015 - {a}, the respective contributors. All rights reserved.";
                    if (split[2].TrimEnd() == copyrightSplit[2].TrimEnd())
                    {
                        validOnOneYear = true;
                    }
                }

                if (!validOnOneYear)
                {
                    Error e = Create.Error("Copyright message is not accurate at line 3", Create.Location(filePath, Create.LineSpan(3, 4)), documentationLink, TestStatus.Error);
                    return(Create.TestResult(TestStatus.Error, new List <Error> {
                        e
                    }));
                }
            }

            return(Create.TestResult(TestStatus.Pass));
        }
Example #12
0
        private static TestResult CheckReferences(this ProjectFile csProject, List <string> fileLines, TestResult finalResult, string csProjFilePath, string documentationLink)
        {
            foreach (AssemblyReference reference in csProject.References)
            {
                string includeName = reference.Name.ToLower();
                if (includeName != "bhom" && !includeName.Contains("_om") && !includeName.Contains("_engine") && !includeName.Contains("_adapter") && !includeName.Contains("_ui"))
                {
                    continue; //Not a BHoM DLL so no point worrying
                }
                string includeNameXMLStart = $"<Reference Include=\"{reference.Name}";
                int    lineNumber          = -1;
                if (!string.IsNullOrEmpty(reference.Version) || !string.IsNullOrEmpty(reference.Culture) || !string.IsNullOrEmpty(reference.ProcessorArchitecture))
                {
                    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(includeNameXMLStart)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error("Project references for BHoM DLLs should not include Version, Culture, or Processor Architecture", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                    }));
                }

                string hintPath    = @"C:\ProgramData\BHoM\Assemblies\" + reference.Name + ".dll";
                string hintPathXML = $"<HintPath>{reference.HintPath}</HintPath>";
                if (reference.HintPath != hintPath)
                {
                    lineNumber  = fileLines.IndexOf(fileLines.Where(x => x.Contains(hintPathXML)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans
                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Project reference for '{reference.Name}' should be set to '{hintPath}'", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                    }));
                }

                if (reference.CopyLocal)
                {
                    lineNumber = fileLines.IndexOf(fileLines.Where(x => x.Contains(hintPathXML)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans - searching from hintPathXML to then make sure we get the right line number related to this copy local issue
                    while (!fileLines[lineNumber].Contains("<Private>true</Private>"))
                    {
                        lineNumber++;
                        if (lineNumber >= fileLines.Count)
                        {
                            lineNumber = fileLines.IndexOf(fileLines.Where(x => x.Contains(hintPathXML)).FirstOrDefault()) + 1; //return to this line as the copy local isn't set at all
                            break;                                                                                              //To avoid infinite loop
                        }
                    }

                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Project reference for '{reference.Name}' should be set to not copy local", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                    }));
                }

                if (reference.SpecificVersion)
                {
                    lineNumber = fileLines.IndexOf(fileLines.Where(x => x.Contains(hintPathXML)).FirstOrDefault()) + 1; //+1 because index is 0 based but line numbers start at 1 for the spans - searching from hintPathXML to then make sure we get the right line number related to this copy local issue
                    while (!fileLines[lineNumber].Contains("<SpecificVersion>true</SpecificVersion>"))
                    {
                        lineNumber++;
                        if (lineNumber >= fileLines.Count)
                        {
                            lineNumber = fileLines.IndexOf(fileLines.Where(x => x.Contains(hintPathXML)).FirstOrDefault()) + 1; //return to this line as the specific version isn't set at all
                            break;                                                                                              //To avoid infinite loop
                        }
                    }

                    finalResult = finalResult.Merge(Create.TestResult(TestStatus.Error, new List <Error> {
                        Create.Error($"Project reference for '{reference.Name}' should be set to not be set to a specific version", Create.Location(csProjFilePath, Create.LineSpan(lineNumber, lineNumber)), documentationLink)
                    }));
                }
            }

            return(finalResult);
        }