See More

using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NETMetaCoder.Abstractions; using NETMetaCoder.SyntaxEnvelope; namespace NETMetaCoder { ///

/// This type encapsulates the logic for building a , by scanning the syntax tree of a /// compilation unit. /// public static class SyntaxScanner { /// /// Constructs a new instance. /// /// /// /// public static SyntaxEnvelope.SyntaxEnvelope ScanSyntaxTree(SyntaxTree tree, IEnumerable attributeNames) => new SyntaxScannerWithContext(tree, attributeNames).ScanSyntaxTree(); private sealed class SyntaxScannerWithContext : CSharpSyntaxWalker { private readonly SyntaxTree _tree; private readonly IEnumerable _attributeNames; private readonly SyntaxEnvelope.SyntaxEnvelope _syntaxEnvelope = new SyntaxEnvelope.SyntaxEnvelope(); private ushort _nodeIndex; private readonly Stack _namespaceSyntaxEnvelopeStack = new Stack(); private readonly Stack _classOrStructSyntaxEnvelopeStack = new Stack(); public SyntaxScannerWithContext(SyntaxTree tree, IEnumerable attributeNames) { _tree = tree; _attributeNames = attributeNames; } public SyntaxEnvelope.SyntaxEnvelope ScanSyntaxTree() { if (_tree.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) { throw new NETMetaCoderException("The syntax tree has errors and no scanning will take place."); } Visit(_tree.GetCompilationUnitRoot()); _syntaxEnvelope.Prune(); return _syntaxEnvelope; } public override void VisitUsingDirective(UsingDirectiveSyntax node) => _syntaxEnvelope.AddUsingDirectiveSyntax(node); public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) { _nodeIndex++; UpdateNamespaceSyntaxEnvelopeStack(node); foreach (var childNode in node.ChildNodes()) { Visit(childNode); } } public override void VisitClassDeclaration(ClassDeclarationSyntax node) { _nodeIndex++; UpdateClassOrStructSyntaxEnvelopeStack(node); foreach (var childNode in node.ChildNodes()) { Visit(childNode); } } public override void VisitStructDeclaration(StructDeclarationSyntax node) { _nodeIndex++; UpdateClassOrStructSyntaxEnvelopeStack(node); foreach (var childNode in node.ChildNodes()) { Visit(childNode); } } public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { _nodeIndex++; AttributeSyntax methodObsoletion = null; var attributeNamesFound = node.FindAttributes(_attributeNames, (_, referenceAttributeName) => { _syntaxEnvelope.AddAttributeNameFound(referenceAttributeName); }, attributeSyntax => { if (attributeSyntax.IsMethodObsoletionAttribute()) { methodObsoletion = attributeSyntax; } }); if (attributeNamesFound.Any()) { UpdateClassOrStructSyntaxEnvelopeStackHead(node, attributeNamesFound.ToImmutableHashSet(), methodObsoletion); } } private static bool IsSameNode(NamespaceDeclarationSyntax a, NamespaceDeclarationSyntax b) { if (a == null && b == null) { return true; } var canBeTheSameNode = a != null && b != null && a.Name.ToString() == b.Name.ToString(); if (canBeTheSameNode) { if (a.Parent is CompilationUnitSyntax && b.Parent is CompilationUnitSyntax) { return true; } if (a.Parent is NamespaceDeclarationSyntax aa && b.Parent is NamespaceDeclarationSyntax bb) { return IsSameNode(aa, bb); } } return false; } private static bool IsSameNode(TypeDeclarationSyntax a, TypeDeclarationSyntax b) { if (a == null && b == null) { return true; } if (a is ClassDeclarationSyntax && !(b is ClassDeclarationSyntax) || a is StructDeclarationSyntax && !(b is StructDeclarationSyntax)) { return false; } var canBeTheSameNode = a != null && b != null && a.Identifier.ToString() == b.Identifier.ToString(); if (canBeTheSameNode) { if (a.Parent is ClassDeclarationSyntax ac && b.Parent is ClassDeclarationSyntax bc) { return IsSameNode(ac, bc); } if (a.Parent is StructDeclarationSyntax @as && b.Parent is StructDeclarationSyntax bs) { return IsSameNode(@as, bs); } if (a.Parent is NamespaceDeclarationSyntax an && b.Parent is NamespaceDeclarationSyntax bn) { return IsSameNode(an, bn); } if (a.Parent is CompilationUnitSyntax && b.Parent is CompilationUnitSyntax) { return true; } } return false; } private void UpdateNamespaceSyntaxEnvelopeStack(NamespaceDeclarationSyntax node) { // A new namespace means that the current class or struct nesting level must be abandoned. _classOrStructSyntaxEnvelopeStack.Clear(); { // While no common parent can be found, pop the namespace syntax stack, while (_namespaceSyntaxEnvelopeStack.Any()) { var namespaceSyntaxEnvelope = _namespaceSyntaxEnvelopeStack.Peek(); // if the current namespace syntax node is the same as the new syntax node's parent, then we // have found the node under which we need to place the new syntax node. if (node.Parent is NamespaceDeclarationSyntax namespaceSyntaxParent && IsSameNode(namespaceSyntaxParent, namespaceSyntaxEnvelope.NamespaceDeclarationSyntax)) { break; } _namespaceSyntaxEnvelopeStack.Pop(); } } { NamespaceSyntaxEnvelope newNamespaceSyntaxEnvelope; // If we have any namespace syntax nodes left in the stack, if (_namespaceSyntaxEnvelopeStack.Any()) { var namespaceSyntaxEnvelope = _namespaceSyntaxEnvelopeStack.Peek(); // we place the new syntax node under it. newNamespaceSyntaxEnvelope = namespaceSyntaxEnvelope.AddNamespaceSyntax(node, _nodeIndex); } // Else, else { // we place the new syntax node under the compilation unit's root. newNamespaceSyntaxEnvelope = _syntaxEnvelope.AddNamespaceSyntax(node, _nodeIndex); } _namespaceSyntaxEnvelopeStack.Push(newNamespaceSyntaxEnvelope); } } private void UpdateClassOrStructSyntaxEnvelopeStack(TypeDeclarationSyntax node) { { var foundParentClassOrStructSyntax = false; // While no common parent can be found, pop the class syntax stack, while (_classOrStructSyntaxEnvelopeStack.Any()) { var classOrStructSyntaxEnvelope = _classOrStructSyntaxEnvelopeStack.Peek(); // if the current class or struct syntax node is the same as the new syntax node's parent, then // we have found the node under which we need to place the new syntax node. if ((node.Parent is ClassDeclarationSyntax classSyntaxParent && IsSameNode(classSyntaxParent, classOrStructSyntaxEnvelope.ClassDeclarationSyntax)) || (node.Parent is StructDeclarationSyntax structSyntaxParent && IsSameNode(structSyntaxParent, classOrStructSyntaxEnvelope.StructDeclarationSyntax))) { foundParentClassOrStructSyntax = true; break; } _classOrStructSyntaxEnvelopeStack.Pop(); } if (!foundParentClassOrStructSyntax) { // While no common parent can be found, pop the namespace syntax stack, while (_namespaceSyntaxEnvelopeStack.Any()) { var namespaceSyntaxEnvelope = _namespaceSyntaxEnvelopeStack.Peek(); // if the current namespace syntax node is the same as the new syntax node's parent, then we // have found the node under which we need to place the new syntax node. if (node.Parent is NamespaceDeclarationSyntax namespaceSyntaxParent && IsSameNode(namespaceSyntaxParent, namespaceSyntaxEnvelope.NamespaceDeclarationSyntax)) { break; } _namespaceSyntaxEnvelopeStack.Pop(); } } } { ClassOrStructSyntaxEnvelope newClassOrStructSyntaxEnvelope; // If we have any class syntax nodes left in the stack, if (_classOrStructSyntaxEnvelopeStack.Any()) { var classOrStructSyntaxEnvelope = _classOrStructSyntaxEnvelopeStack.Peek(); // we place the new syntax node under it. if (node is ClassDeclarationSyntax classDeclarationSyntax) { newClassOrStructSyntaxEnvelope = classOrStructSyntaxEnvelope.AddClassSyntax(classDeclarationSyntax, _nodeIndex); } else if (node is StructDeclarationSyntax structDeclarationSyntax) { newClassOrStructSyntaxEnvelope = classOrStructSyntaxEnvelope.AddStructSyntax(structDeclarationSyntax, _nodeIndex); } else { throw new ArgumentException( $"The syntax node must be either of type {nameof(ClassDeclarationSyntax)}, or " + $"{nameof(StructDeclarationSyntax)}."); } } // Else, if we have any namespace syntax nodes left in the stack, else if (_namespaceSyntaxEnvelopeStack.Any()) { var namespaceSyntaxEnvelope = _namespaceSyntaxEnvelopeStack.Peek(); // we place the new syntax node under it. if (node is ClassDeclarationSyntax classDeclarationSyntax) { newClassOrStructSyntaxEnvelope = namespaceSyntaxEnvelope.AddClassSyntax(classDeclarationSyntax, _nodeIndex); } else if (node is StructDeclarationSyntax structDeclarationSyntax) { newClassOrStructSyntaxEnvelope = namespaceSyntaxEnvelope.AddStructSyntax(structDeclarationSyntax, _nodeIndex); } else { throw new ArgumentException( $"The syntax node must be either of type {nameof(ClassDeclarationSyntax)}, or " + $"{nameof(StructDeclarationSyntax)}."); } } // Else, else { // we place the new syntax node under the compilation unit's root. if (node is ClassDeclarationSyntax classDeclarationSyntax) { newClassOrStructSyntaxEnvelope = _syntaxEnvelope.AddClassSyntax(classDeclarationSyntax, _nodeIndex); } else if (node is StructDeclarationSyntax structDeclarationSyntax) { newClassOrStructSyntaxEnvelope = _syntaxEnvelope.AddStructSyntax(structDeclarationSyntax, _nodeIndex); } else { throw new ArgumentException( $"The syntax node must be either of type {nameof(ClassDeclarationSyntax)}, or " + $"{nameof(StructDeclarationSyntax)}."); } } _classOrStructSyntaxEnvelopeStack.Push(newClassOrStructSyntaxEnvelope); } } private void UpdateClassOrStructSyntaxEnvelopeStackHead(MethodDeclarationSyntax node, ImmutableHashSet attributeNamesFound, AttributeSyntax methodObsoletion) { // While no common parent can be found, pop the class or struct syntax stack, while (_classOrStructSyntaxEnvelopeStack.Any()) { var classOrStructSyntaxEnvelope = _classOrStructSyntaxEnvelopeStack.Peek(); // if the current class or struct syntax node is the same as the new syntax node's parent, then we // have found the node under which we need to place the new syntax node. if ((node.Parent is ClassDeclarationSyntax classSyntaxParent && IsSameNode(classSyntaxParent, classOrStructSyntaxEnvelope.ClassDeclarationSyntax)) || (node.Parent is StructDeclarationSyntax structSyntaxParent && IsSameNode(structSyntaxParent, classOrStructSyntaxEnvelope.StructDeclarationSyntax))) { break; } _classOrStructSyntaxEnvelopeStack.Pop(); } // This call should never fail because we check for error diagnostics before we begin and syntactically, // it is not possible to have a method outside a class or struct. _classOrStructSyntaxEnvelopeStack.Peek() .AddMethodSyntax(node, _nodeIndex, attributeNamesFound, methodObsoletion); } } } }