123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- using System.IO;
- using System.Linq;
- using UnityEditor;
- using Microsoft.CodeAnalysis;
- using UnityEditor.Compilation;
- using System.Collections.Generic;
- using Microsoft.CodeAnalysis.CSharp;
- using Microsoft.CodeAnalysis.CSharp.Syntax;
- namespace IntelligentProjectAnalyzer.Analyzer
- {
- /// <summary>
- /// A helper class that uses Roslyn to perform static analysis on C# scripts.
- /// </summary>
- public static class RoslynReferenceFinder
- {
- /// <summary>
- /// Finds references to a type or a method within that type.
- /// </summary>
- /// <param name="qualifier">A string that can be a class name, a full type name, or a full type name with a method.</param>
- /// <returns>A tuple containing the list of references, the GUID of the script where the type is defined, and the Type itself.</returns>
- public static (List<object> references, string foundScriptGuid, System.Type foundScriptType) FindReferences(string qualifier)
- {
- var (compilation, syntaxTrees) = GetProjectCompilation();
- var (typeSymbol, methodName) = ResolveQualifier(qualifier, compilation);
- if (typeSymbol == null)
- {
- return (new List<object>(), null, null);
- }
-
- var results = new List<object>();
- var classFullName = typeSymbol.ToDisplayString();
- foreach (var tree in syntaxTrees)
- {
- var semanticModel = compilation.GetSemanticModel(tree);
- var root = tree.GetRoot();
-
- var invocations = root.DescendantNodes().OfType<InvocationExpressionSyntax>();
- foreach (var invocation in invocations)
- {
- if (semanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol methodSymbol) continue;
- var isMatch = (methodName != null)
- ? methodSymbol.Name == methodName && methodSymbol.ContainingType.ToDisplayString() == classFullName
- : methodSymbol.ContainingType.ToDisplayString() == classFullName;
- if (!isMatch) continue;
- var location = tree.GetLineSpan(invocation.Span);
- results.Add(new
- {
- path = tree.FilePath,
- guid = AssetDatabase.AssetPathToGUID(tree.FilePath),
- line = location.StartLinePosition.Line + 1,
- invokedMethod = methodSymbol.Name,
- text = invocation.ToString()
- });
- }
- }
-
- var scriptPath = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.SyntaxTree.FilePath;
- var scriptGuid = !string.IsNullOrEmpty(scriptPath) ? AssetDatabase.AssetPathToGUID(scriptPath) : null;
- var scriptType = !string.IsNullOrEmpty(scriptGuid) ? AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath)?.GetClass() : null;
- return (results.Distinct().ToList(), scriptGuid, scriptType);
- }
- /// <summary>
- /// Intelligently parses the qualifier string to find the type symbol and an optional method name.
- /// </summary>
- private static (INamedTypeSymbol typeSymbol, string methodName) ResolveQualifier(string qualifier, CSharpCompilation compilation)
- {
- // First, try to resolve the whole string as a type.
- var typeSymbol = compilation.GetTypeByMetadataName(qualifier);
- if (typeSymbol != null)
- {
- return (typeSymbol, null); // Found a class/type, no method.
- }
- // If that fails, assume the last part is a method name.
- var lastDotIndex = qualifier.LastIndexOf('.');
- if (lastDotIndex == -1)
- {
- return (null, null); // Not a namespaced type or method.
- }
- var potentialTypeName = qualifier[..lastDotIndex];
- var potentialMethodName = qualifier[(lastDotIndex + 1)..];
- typeSymbol = compilation.GetTypeByMetadataName(potentialTypeName);
- if (typeSymbol == null) return (null, null); // Could not resolve.
- // Check if a method with that name actually exists on the type
- return typeSymbol.GetMembers(potentialMethodName).Any(m => m.Kind == SymbolKind.Method) ?
- (typeSymbol, potentialMethodName) : (null, null); // Could not resolve.
- }
- private static (CSharpCompilation, List<SyntaxTree>) GetProjectCompilation()
- {
- var sourceFiles = AssetDatabase.FindAssets("t:MonoScript")
- .Select(AssetDatabase.GUIDToAssetPath)
- .Where(p => p.StartsWith("Assets/"))
- .ToList();
-
- var syntaxTrees = sourceFiles.Select(path => CSharpSyntaxTree.ParseText(File.ReadAllText(path), path: path)).ToList();
- var references = new List<MetadataReference>();
- foreach (var assembly in CompilationPipeline.GetAssemblies())
- {
- references.AddRange(assembly.compiledAssemblyReferences
- .Select(p => MetadataReference.CreateFromFile(p)));
- }
-
- var compilation = CSharpCompilation.Create("ReferenceAnalysisAssembly", syntaxTrees, references);
- return (compilation, syntaxTrees);
- }
- }
- }
|