diff --git a/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs b/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs new file mode 100644 index 0000000000..5dee09f2f0 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Analysis/SemanticHighlightingVisitor.cs @@ -0,0 +1,367 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Analysis +{ + /// + /// C# Semantic highlighter. + /// + public abstract class SemanticHighlightingVisitor : DepthFirstAstVisitor + { + protected TColor defaultTextColor; + protected TColor referenceTypeColor; + protected TColor valueTypeColor; + protected TColor methodCallColor; + protected TColor fieldAccessColor; + protected TColor valueKeywordColor; + + /// + /// Used for 'in' modifiers on type parameters. + /// + /// + /// 'in' may have a different color when used with 'foreach'. + /// 'out' is not colored by semantic highlighting, as syntax highlighting can already detect it as a parameter modifier. + /// + protected TColor parameterModifierColor; + + /// + /// Used for inactive code (excluded by preprocessor or ConditionalAttribute) + /// + protected TColor inactiveCodeColor; + + protected TextLocation regionStart; + protected TextLocation regionEnd; + + protected CSharpAstResolver resolver; + bool isInAccessor; + + protected abstract void Colorize(TextLocation start, TextLocation end, TColor color); + + #region Colorize helper methods + protected void Colorize(Identifier identifier, ResolveResult rr) + { + if (identifier.IsNull) + return; + if (rr is TypeResolveResult) { + if (rr.Type.IsReferenceType == false) + Colorize(identifier, valueTypeColor); + else + Colorize(identifier, referenceTypeColor); + return; + } + MemberResolveResult mrr = rr as MemberResolveResult; + if (mrr != null) { + if (mrr.Member is IField) { + Colorize(identifier, fieldAccessColor); + return; + } + } + VisitIdentifier(identifier); // un-colorize contextual keywords + } + + protected void Colorize(AstNode node, TColor color) + { + if (node.IsNull) + return; + Colorize(node.StartLocation, node.EndLocation, color); + } + #endregion + + protected override void VisitChildren(AstNode node) + { + for (var child = node.FirstChild; child != null; child = child.NextSibling) { + if (child.StartLocation < regionEnd && child.EndLocation > regionStart) + child.AcceptVisitor(this); + } + } + + /// + /// Visit all children of node until (but excluding) end. + /// If end is a null node, nothing will be visited. + /// + protected void VisitChildrenUntil(AstNode node, AstNode end) + { + if (end.IsNull) + return; + Debug.Assert(node == end.Parent); + for (var child = node.FirstChild; child != end; child = child.NextSibling) { + if (child.StartLocation < regionEnd && child.EndLocation > regionStart) + child.AcceptVisitor(this); + } + } + + /// + /// Visit all children of node after (excluding) start. + /// If start is a null node, all children will be visited. + /// + protected void VisitChildrenAfter(AstNode node, AstNode start) + { + Debug.Assert(start.IsNull || start.Parent == node); + for (var child = (start.IsNull ? node.FirstChild : start.NextSibling); child != null; child = child.NextSibling) { + if (child.StartLocation < regionEnd && child.EndLocation > regionStart) + child.AcceptVisitor(this); + } + } + + public override void VisitIdentifier(Identifier identifier) + { + switch (identifier.Name) { + case "add": + case "async": + case "await": + case "get": + case "partial": + case "remove": + case "set": + case "where": + case "yield": + case "from": + case "select": + case "group": + case "into": + case "orderby": + case "join": + case "let": + case "on": + case "equals": + case "by": + case "ascending": + case "descending": + case "dynamic": + case "var": + // Reset color of contextual keyword to default if it's used as an identifier. + // Note that this method does not get called when 'var' or 'dynamic' is used as a type, + // because types get highlighted with valueTypeColor/referenceTypeColor instead. + Colorize(identifier, defaultTextColor); + break; + case "global": + // Reset color of 'global' keyword to default unless its used as part of 'global::'. + MemberType parentMemberType = identifier.Parent as MemberType; + if (parentMemberType == null || !parentMemberType.IsDoubleColon) + Colorize(identifier, defaultTextColor); + break; + } + // "value" is handled in VisitIdentifierExpression() + } + + public override void VisitSimpleType(SimpleType simpleType) + { + var identifierToken = simpleType.IdentifierToken; + VisitChildrenUntil(simpleType, identifierToken); + Colorize(identifierToken, resolver.Resolve(simpleType)); + VisitChildrenAfter(simpleType, identifierToken); + } + + public override void VisitMemberType(MemberType memberType) + { + var memberNameToken = memberType.MemberNameToken; + VisitChildrenUntil(memberType, memberNameToken); + Colorize(memberNameToken, resolver.Resolve(memberType)); + VisitChildrenAfter(memberType, memberNameToken); + } + + public override void VisitIdentifierExpression(IdentifierExpression identifierExpression) + { + var identifier = identifierExpression.IdentifierToken; + VisitChildrenUntil(identifierExpression, identifier); + if (isInAccessor && identifierExpression.Identifier == "value") { + Colorize(identifier, valueKeywordColor); + } else { + Colorize(identifier, resolver.Resolve(identifierExpression)); + } + VisitChildrenAfter(identifierExpression, identifier); + } + + public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) + { + var memberNameToken = memberReferenceExpression.MemberNameToken; + VisitChildrenUntil(memberReferenceExpression, memberNameToken); + ResolveResult rr = resolver.Resolve(memberReferenceExpression); + Colorize(memberNameToken, rr); + VisitChildren(memberReferenceExpression); + VisitChildrenAfter(memberReferenceExpression, memberNameToken); + } + + public override void VisitInvocationExpression(InvocationExpression invocationExpression) + { + Expression target = invocationExpression.Target; + if (target is IdentifierExpression || target is MemberReferenceExpression || target is PointerReferenceExpression) { + var invocationRR = resolver.Resolve(invocationExpression) as CSharpInvocationResolveResult; + if (invocationRR != null && IsInactiveConditionalMethod(invocationRR.Member)) { + // mark the whole invocation expression as inactive code + Colorize(invocationExpression, inactiveCodeColor); + return; + } + + VisitChildrenUntil(invocationExpression, target); + + // highlight the method call + var identifier = target.GetChildByRole(Roles.Identifier); + VisitChildrenUntil(target, identifier); + if (invocationRR != null && !invocationRR.IsDelegateInvocation) { + Colorize(identifier, methodCallColor); + } else { + ResolveResult targetRR = resolver.Resolve(target); + Colorize(identifier, targetRR); + } + VisitChildrenAfter(target, identifier); + VisitChildrenAfter(invocationExpression, target); + } else { + VisitChildren(invocationExpression); + } + } + + #region IsInactiveConditional helper methods + bool IsInactiveConditionalMethod(IParameterizedMember member) + { + if (member.EntityType != EntityType.Method || member.ReturnType.Kind != TypeKind.Void) + return false; + while (member.IsOverride) + member = (IParameterizedMember)InheritanceHelper.GetBaseMember(member); + return IsInactiveConditional(member.Attributes); + } + + bool IsInactiveConditional(IList attributes) + { + bool hasConditionalAttribute = false; + foreach (var attr in attributes) { + if (attr.AttributeType.Name == "ConditionalAttribute" && attr.AttributeType.Namespace == "System.Diagnostics" && attr.PositionalArguments.Count == 1) { + string symbol = attr.PositionalArguments[0].ConstantValue as string; + if (symbol != null) { + hasConditionalAttribute = true; + var cu = this.resolver.RootNode as SyntaxTree; + if (cu != null) { + if (cu.ConditionalSymbols.Contains(symbol)) + return false; // conditional is active + } + } + } + } + return hasConditionalAttribute; + } + #endregion + + public override void VisitAccessor(Accessor accessor) + { + isInAccessor = true; + try { + VisitChildren(accessor); + } finally { + isInAccessor = false; + } + } + + public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) + { + var nameToken = methodDeclaration.NameToken; + VisitChildrenUntil(methodDeclaration, nameToken); + Colorize(nameToken, methodCallColor); + VisitChildrenAfter(methodDeclaration, nameToken); + } + + public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) + { + HandleConstructorOrDestructor(constructorDeclaration); + } + + public override void VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration) + { + HandleConstructorOrDestructor(destructorDeclaration); + } + + void HandleConstructorOrDestructor(AstNode constructorDeclaration) + { + Identifier nameToken = constructorDeclaration.GetChildByRole(Roles.Identifier); + VisitChildrenUntil(constructorDeclaration, nameToken); + var currentTypeDef = resolver.GetResolverStateBefore(constructorDeclaration).CurrentTypeDefinition; + if (currentTypeDef != null && nameToken.Name == currentTypeDef.Name) { + if (currentTypeDef.IsReferenceType == true) + Colorize(nameToken, referenceTypeColor); + else if (currentTypeDef.IsReferenceType == false) + Colorize(nameToken, valueTypeColor); + } + VisitChildrenAfter(constructorDeclaration, nameToken); + } + + public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) + { + var nameToken = typeDeclaration.NameToken; + VisitChildrenUntil(typeDeclaration, nameToken); + if (typeDeclaration.ClassType == ClassType.Enum || typeDeclaration.ClassType == ClassType.Struct) + Colorize(nameToken, valueTypeColor); + else + Colorize(nameToken, referenceTypeColor); + VisitChildrenAfter(typeDeclaration, nameToken); + } + + public override void VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration) + { + if (typeParameterDeclaration.Variance == VarianceModifier.Contravariant) + Colorize(typeParameterDeclaration.VarianceToken, parameterModifierColor); + + bool isValueType = false; + if (typeParameterDeclaration.Parent != null) { + foreach (var constraint in typeParameterDeclaration.Parent.GetChildrenByRole(Roles.Constraint)) { + if (constraint.TypeParameter.Identifier == typeParameterDeclaration.Name) { + isValueType = constraint.BaseTypes.OfType().Any(p => p.Keyword == "struct"); + } + } + } + var nameToken = typeParameterDeclaration.NameToken; + VisitChildrenUntil(typeParameterDeclaration, nameToken); + Colorize(nameToken, isValueType ? valueTypeColor : referenceTypeColor); + VisitChildrenAfter(typeParameterDeclaration, nameToken); + } + + public override void VisitVariableInitializer(VariableInitializer variableInitializer) + { + if (variableInitializer.Parent is FieldDeclaration) { + VisitChildrenUntil(variableInitializer, variableInitializer.NameToken); + Colorize(variableInitializer.NameToken, fieldAccessColor); + VisitChildrenAfter(variableInitializer, variableInitializer.NameToken); + } else { + VisitChildren(variableInitializer); + } + } + + public override void VisitComment(Comment comment) + { + if (comment.CommentType == CommentType.InactiveCode) { + Colorize(comment, inactiveCodeColor); + } + } + + public override void VisitAttribute(ICSharpCode.NRefactory.CSharp.Attribute attribute) + { + ITypeDefinition attrDef = resolver.Resolve(attribute.Type).Type.GetDefinition(); + if (attrDef != null && IsInactiveConditional(attrDef.Attributes)) { + Colorize(attribute, inactiveCodeColor); + } else { + VisitChildren(attribute); + } + } + } +} diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 63cdd74394..2fedc8bca6 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -88,6 +88,7 @@ +