// Copyright (c) 2010-2013 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; using System.Threading; using ICSharpCode.NRefactory.CSharp.Completion; using System.Collections.ObjectModel; namespace ICSharpCode.NRefactory.CSharp.Analysis { /// /// C# Semantic highlighter. /// public abstract class SemanticHighlightingVisitor : DepthFirstAstVisitor { protected CancellationToken cancellationToken = default (CancellationToken); protected TColor defaultTextColor; protected TColor referenceTypeColor; protected TColor valueTypeColor; protected TColor interfaceTypeColor; protected TColor enumerationTypeColor; protected TColor typeParameterTypeColor; protected TColor delegateTypeColor; protected TColor methodCallColor; protected TColor methodDeclarationColor; protected TColor eventDeclarationColor; protected TColor eventAccessColor; protected TColor propertyDeclarationColor; protected TColor propertyAccessColor; protected TColor fieldDeclarationColor; protected TColor fieldAccessColor; protected TColor variableDeclarationColor; protected TColor variableAccessColor; protected TColor parameterDeclarationColor; protected TColor parameterAccessColor; protected TColor valueKeywordColor; protected TColor externAliasKeywordColor; protected TColor varKeywordTypeColor; /// /// 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 TColor stringFormatItemColor; protected TColor syntaxErrorColor; protected TextLocation regionStart; protected TextLocation regionEnd; protected CSharpAstResolver resolver; protected bool isInAccessorContainingValueParameter; 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.IsError) { Colorize(identifier, syntaxErrorColor); return; } if (rr is TypeResolveResult) { if (blockDepth > 0 && identifier.Name == "var" && rr.Type.Kind != TypeKind.Null && rr.Type.Name != "var" ) { Colorize(identifier, varKeywordTypeColor); return; } TColor color; if (TryGetTypeHighlighting (rr.Type.Kind, out color)) { Colorize(identifier, color); } return; } var mrr = rr as MemberResolveResult; if (mrr != null) { TColor color; if (TryGetMemberColor (mrr.Member, out color)) { Colorize(identifier, color); return; } } if (rr is MethodGroupResolveResult) { Colorize (identifier, methodCallColor); return; } var localResult = rr as LocalResolveResult; if (localResult != null) { if (localResult.Variable is IParameter) { Colorize (identifier, parameterAccessColor); } else { Colorize (identifier, variableAccessColor); } } 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) { cancellationToken.ThrowIfCancellationRequested(); 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) { cancellationToken.ThrowIfCancellationRequested(); 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() // "alias" is handled in VisitExternAliasDeclaration() } public override void VisitSimpleType(SimpleType simpleType) { var identifierToken = simpleType.IdentifierToken; VisitChildrenUntil(simpleType, identifierToken); Colorize(identifierToken, resolver.Resolve(simpleType, cancellationToken)); VisitChildrenAfter(simpleType, identifierToken); } public override void VisitMemberType(MemberType memberType) { var memberNameToken = memberType.MemberNameToken; VisitChildrenUntil(memberType, memberNameToken); Colorize(memberNameToken, resolver.Resolve(memberType, cancellationToken)); VisitChildrenAfter(memberType, memberNameToken); } public override void VisitIdentifierExpression(IdentifierExpression identifierExpression) { var identifier = identifierExpression.IdentifierToken; VisitChildrenUntil(identifierExpression, identifier); if (isInAccessorContainingValueParameter && identifierExpression.Identifier == "value") { Colorize(identifier, valueKeywordColor); } else { Colorize(identifier, resolver.Resolve(identifierExpression, cancellationToken)); } VisitChildrenAfter(identifierExpression, identifier); } public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) { var memberNameToken = memberReferenceExpression.MemberNameToken; VisitChildrenUntil(memberReferenceExpression, memberNameToken); ResolveResult rr = resolver.Resolve(memberReferenceExpression, cancellationToken); Colorize(memberNameToken, rr); VisitChildrenAfter(memberReferenceExpression, memberNameToken); } void HighlightStringFormatItems(PrimitiveExpression expr) { if (!(expr.Value is string)) return; int line = expr.StartLocation.Line; int col = expr.StartLocation.Column; TextLocation start = TextLocation.Empty; for (int i = 0; i < expr.LiteralValue.Length; i++) { char ch = expr.LiteralValue [i]; if (NewLine.GetDelimiterType(ch, i + 1 < expr.LiteralValue.Length ? expr.LiteralValue [i + 1] : '\0') != UnicodeNewline.Unknown) { line++; col = 1; continue; } if (ch == '{' && start.IsEmpty) { char next = i + 1 < expr.LiteralValue.Length ? expr.LiteralValue [i + 1] : '\0'; if (next == '{') { i++; col += 2; continue; } start = new TextLocation(line, col); } if (ch == '}' &&!start.IsEmpty) { Colorize(start, new TextLocation(line, col + 1), stringFormatItemColor); start = TextLocation.Empty; } col++; } } 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, cancellationToken) as CSharpInvocationResolveResult; if (invocationRR != null) { if (invocationExpression.Parent is ExpressionStatement && (IsInactiveConditionalMethod(invocationRR.Member) || IsEmptyPartialMethod(invocationRR.Member))) { // mark the whole invocation statement as inactive code Colorize(invocationExpression.Parent, inactiveCodeColor); return; } Expression fmtArgumets; IList args; if (invocationRR.Arguments.Count > 1 && FormatStringHelper.TryGetFormattingParameters(invocationRR, invocationExpression, out fmtArgumets, out args, null)) { var expr = invocationExpression.Arguments.First() as PrimitiveExpression; if (expr != null) HighlightStringFormatItems(expr); } } 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, cancellationToken); Colorize(identifier, targetRR); } VisitChildrenAfter(target, identifier); VisitChildrenAfter(invocationExpression, target); } else { VisitChildren(invocationExpression); } } #region IsInactiveConditional helper methods bool IsInactiveConditionalMethod(IParameterizedMember member) { if (member.SymbolKind != SymbolKind.Method || member.ReturnType.Kind != TypeKind.Void) return false; foreach (var baseMember in InheritanceHelper.GetBaseMembers(member, false)) { if (IsInactiveConditional (baseMember.Attributes)) return true; } return IsInactiveConditional(member.Attributes); } static bool IsEmptyPartialMethod(IParameterizedMember member) { if (member.SymbolKind != SymbolKind.Method || member.ReturnType.Kind != TypeKind.Void) return false; var method = (IMethod)member; return method.IsPartial && !method.HasBody; } 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 VisitExternAliasDeclaration (ExternAliasDeclaration externAliasDeclaration) { var aliasToken = externAliasDeclaration.AliasToken; VisitChildrenUntil(externAliasDeclaration, aliasToken); Colorize (aliasToken, externAliasKeywordColor); VisitChildrenAfter(externAliasDeclaration, aliasToken); } public override void VisitAccessor(Accessor accessor) { isInAccessorContainingValueParameter = accessor.Role != PropertyDeclaration.GetterRole; try { VisitChildren(accessor); } finally { isInAccessorContainingValueParameter = false; } } bool CheckInterfaceImplementation (EntityDeclaration entityDeclaration) { var result = resolver.Resolve (entityDeclaration, cancellationToken) as MemberResolveResult; if (result == null) return false; if (result.Member.ImplementedInterfaceMembers.Count == 0) { Colorize (entityDeclaration.NameToken, syntaxErrorColor); return false; } return true; } public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration) { var nameToken = methodDeclaration.NameToken; VisitChildrenUntil(methodDeclaration, nameToken); if (!methodDeclaration.PrivateImplementationType.IsNull) { if (!CheckInterfaceImplementation (methodDeclaration)) { VisitChildrenAfter(methodDeclaration, nameToken); return; } } Colorize(nameToken, methodDeclarationColor); VisitChildrenAfter(methodDeclaration, nameToken); } public override void VisitParameterDeclaration(ParameterDeclaration parameterDeclaration) { var nameToken = parameterDeclaration.NameToken; VisitChildrenUntil(parameterDeclaration, nameToken); Colorize(nameToken, parameterDeclarationColor); VisitChildrenAfter(parameterDeclaration, nameToken); } public override void VisitEventDeclaration(EventDeclaration eventDeclaration) { var nameToken = eventDeclaration.NameToken; VisitChildrenUntil(eventDeclaration, nameToken); Colorize(nameToken, eventDeclarationColor); VisitChildrenAfter(eventDeclaration, nameToken); } public override void VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration) { var nameToken = eventDeclaration.NameToken; VisitChildrenUntil(eventDeclaration, nameToken); if (!eventDeclaration.PrivateImplementationType.IsNull) { if (!CheckInterfaceImplementation (eventDeclaration)) { VisitChildrenAfter(eventDeclaration, nameToken); return; } } Colorize(nameToken, eventDeclarationColor); VisitChildrenAfter(eventDeclaration, nameToken); } public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) { var nameToken = propertyDeclaration.NameToken; VisitChildrenUntil(propertyDeclaration, nameToken); if (!propertyDeclaration.PrivateImplementationType.IsNull) { if (!CheckInterfaceImplementation (propertyDeclaration)) { VisitChildrenAfter(propertyDeclaration, nameToken); return; } } Colorize(nameToken, propertyDeclarationColor); VisitChildrenAfter(propertyDeclaration, nameToken); } public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration) { base.VisitIndexerDeclaration(indexerDeclaration); if (!indexerDeclaration.PrivateImplementationType.IsNull) { CheckInterfaceImplementation (indexerDeclaration); } } public override void VisitFieldDeclaration(FieldDeclaration fieldDeclaration) { fieldDeclaration.ReturnType.AcceptVisitor (this); foreach (var init in fieldDeclaration.Variables) { Colorize (init.NameToken, fieldDeclarationColor); init.Initializer.AcceptVisitor (this); } } public override void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration) { fixedFieldDeclaration.ReturnType.AcceptVisitor (this); foreach (var init in fixedFieldDeclaration.Variables) { Colorize (init.NameToken, fieldDeclarationColor); init.CountExpression.AcceptVisitor (this); } } 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) { TColor color; if (TryGetTypeHighlighting (currentTypeDef.Kind, out color)) Colorize(nameToken, color); } VisitChildrenAfter(constructorDeclaration, nameToken); } bool TryGetMemberColor(IMember member, out TColor color) { switch (member.SymbolKind) { case SymbolKind.Field: color = fieldAccessColor; return true; case SymbolKind.Property: color = propertyAccessColor; return true; case SymbolKind.Event: color = eventAccessColor; return true; case SymbolKind.Method: color = methodCallColor; return true; case SymbolKind.Constructor: case SymbolKind.Destructor: return TryGetTypeHighlighting (member.DeclaringType.Kind, out color); default: color = default (TColor); return false; } } TColor GetTypeHighlighting (ClassType classType) { switch (classType) { case ClassType.Class: return referenceTypeColor; case ClassType.Struct: return valueTypeColor; case ClassType.Interface: return interfaceTypeColor; case ClassType.Enum: return enumerationTypeColor; default: throw new InvalidOperationException ("Unknown class type :" + classType); } } bool TryGetTypeHighlighting (TypeKind kind, out TColor color) { switch (kind) { case TypeKind.Class: color = referenceTypeColor; return true; case TypeKind.Struct: color = valueTypeColor; return true; case TypeKind.Interface: color = interfaceTypeColor; return true; case TypeKind.Enum: color = enumerationTypeColor; return true; case TypeKind.TypeParameter: color = typeParameterTypeColor; return true; case TypeKind.Delegate: color = delegateTypeColor; return true; case TypeKind.Unknown: case TypeKind.Null: color = syntaxErrorColor; return true; default: color = default (TColor); return false; } } public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) { var nameToken = typeDeclaration.NameToken; VisitChildrenUntil(typeDeclaration, nameToken); Colorize(nameToken, GetTypeHighlighting (typeDeclaration.ClassType)); 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, typeParameterTypeColor); /*isValueType ? valueTypeColor : referenceTypeColor*/ VisitChildrenAfter(typeParameterDeclaration, nameToken); } public override void VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration) { var nameToken = delegateDeclaration.NameToken; VisitChildrenUntil(delegateDeclaration, nameToken); Colorize(nameToken, delegateTypeColor); VisitChildrenAfter(delegateDeclaration, nameToken); } public override void VisitVariableInitializer(VariableInitializer variableInitializer) { var nameToken = variableInitializer.NameToken; VisitChildrenUntil(variableInitializer, nameToken); if (variableInitializer.Parent is FieldDeclaration) { Colorize(nameToken, fieldDeclarationColor); } else if (variableInitializer.Parent is EventDeclaration) { Colorize(nameToken, eventDeclarationColor); } else { Colorize(nameToken, variableDeclarationColor); } VisitChildrenAfter(variableInitializer, nameToken); } public override void VisitComment(Comment comment) { if (comment.CommentType == CommentType.InactiveCode) { Colorize(comment, inactiveCodeColor); } } public override void VisitPreProcessorDirective(PreProcessorDirective preProcessorDirective) { } public override void VisitAttribute(ICSharpCode.NRefactory.CSharp.Attribute attribute) { ITypeDefinition attrDef = resolver.Resolve(attribute.Type, cancellationToken).Type.GetDefinition(); if (attrDef != null && IsInactiveConditional(attrDef.Attributes)) { Colorize(attribute, inactiveCodeColor); } else { VisitChildren(attribute); } } public override void VisitArrayInitializerExpression (ArrayInitializerExpression arrayInitializerExpression) { foreach (var a in arrayInitializerExpression.Elements) { var namedElement = a as NamedExpression; if (namedElement != null) { var result = resolver.Resolve (namedElement, cancellationToken); if (result.IsError) Colorize (namedElement.NameToken, syntaxErrorColor); namedElement.Expression.AcceptVisitor (this); } else { a.AcceptVisitor (this); } } } int blockDepth; public override void VisitBlockStatement(BlockStatement blockStatement) { blockDepth++; base.VisitBlockStatement(blockStatement); blockDepth--; } } }