/// <summary> /// A symbol requires a documentation comment if it was explicitly declared and /// will be visible outside the current assembly (ignoring InternalsVisibleTo). /// Exception: accessors do not require doc comments. /// </summary> private static bool RequiresDocumentationComment(Symbol symbol) { Debug.Assert((object)symbol != null); if (symbol.IsImplicitlyDeclared || symbol.IsAccessor()) { return false; } while ((object)symbol != null) { switch (symbol.DeclaredAccessibility) { case Accessibility.Public: case Accessibility.Protected: case Accessibility.ProtectedOrInternal: symbol = symbol.ContainingType; break; default: return false; } } return true; }
/// <summary> /// Compile documentation comments on the symbol and write them to the stream if one is provided. /// </summary> public override void DefaultVisit(Symbol symbol) { _cancellationToken.ThrowIfCancellationRequested(); if (symbol.IsImplicitlyDeclared || symbol.IsAccessor()) { return; } if (_filterTree != null && !symbol.IsDefinedInSourceTree(_filterTree, _filterSpanWithinTree)) { return; } bool isPartialMethodDefinitionPart = symbol.IsPartialDefinition(); // CONSIDER: ignore this if isForSingleSymbol? if (isPartialMethodDefinitionPart) { MethodSymbol implementationPart = ((MethodSymbol)symbol).PartialImplementationPart; if ((object)implementationPart != null) { Visit(implementationPart); } } DocumentationMode maxDocumentationMode; ImmutableArray<DocumentationCommentTriviaSyntax> docCommentNodes; if (!TryGetDocumentationCommentNodes(symbol, out maxDocumentationMode, out docCommentNodes)) { // If the XML in any of the doc comments is invalid, skip all further processing (for this symbol) and // just write a comment saying that info was lost for this symbol. string message = ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED, CultureInfo.CurrentUICulture); WriteLine(string.Format(CultureInfo.CurrentUICulture, message, symbol.GetDocumentationCommentId())); return; } // If there are no doc comments, then no further work is required (other than to report a diagnostic if one is required). if (docCommentNodes.IsEmpty) { if (maxDocumentationMode >= DocumentationMode.Diagnose && RequiresDocumentationComment(symbol)) { // Report the error at a location in the tree that was parsing doc comments. Location location = GetLocationInTreeReportingDocumentationCommentDiagnostics(symbol); if (location != null) { _diagnostics.Add(ErrorCode.WRN_MissingXMLComment, location, symbol); } } return; } _cancellationToken.ThrowIfCancellationRequested(); bool reportParameterOrTypeParameterDiagnostics = GetLocationInTreeReportingDocumentationCommentDiagnostics(symbol) != null; string withUnprocessedIncludes; bool haveParseError; HashSet<TypeParameterSymbol> documentedTypeParameters; HashSet<ParameterSymbol> documentedParameters; ImmutableArray<CSharpSyntaxNode> includeElementNodes; if (!TryProcessDocumentationCommentTriviaNodes( symbol, isPartialMethodDefinitionPart, docCommentNodes, reportParameterOrTypeParameterDiagnostics, out withUnprocessedIncludes, out haveParseError, out documentedTypeParameters, out documentedParameters, out includeElementNodes)) { return; } if (haveParseError) { // If the XML in any of the doc comments is invalid, skip all further processing (for this symbol) and // just write a comment saying that info was lost for this symbol. string message = ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED, CultureInfo.CurrentUICulture); WriteLine(string.Format(CultureInfo.CurrentUICulture, message, symbol.GetDocumentationCommentId())); return; } // If there are no include elements, then there's nothing to expand. if (!includeElementNodes.IsDefaultOrEmpty) { _cancellationToken.ThrowIfCancellationRequested(); // NOTE: we are expanding include elements AFTER formatting the comment, since the included text is pure // XML, not XML mixed with documentation comment trivia (e.g. ///). If we expanded them before formatting, // the formatting engine would have trouble determining what prefix to remove from each line. TextWriter expanderWriter = isPartialMethodDefinitionPart ? null : _writer; // Don't actually write partial method definition parts. IncludeElementExpander.ProcessIncludes(withUnprocessedIncludes, symbol, includeElementNodes, _compilation, ref documentedParameters, ref documentedTypeParameters, ref _includedFileCache, expanderWriter, _diagnostics, _cancellationToken); } else if (_writer != null && !isPartialMethodDefinitionPart) { // CONSIDER: The output would look a little different if we ran the XDocument through an XmlWriter. In particular, // formatting inside tags (e.g. <__tag___attr__=__"value"__>) would be normalized. Whitespace in elements would // (or should) not be affected. If we decide that this difference matters, we can run the XDocument through an XmlWriter. // Otherwise, just writing out the string saves a bunch of processing and does a better job of preserving whitespace. Write(withUnprocessedIncludes); } if (reportParameterOrTypeParameterDiagnostics) { _cancellationToken.ThrowIfCancellationRequested(); if (documentedParameters != null) { foreach (ParameterSymbol parameter in GetParameters(symbol)) { if (!documentedParameters.Contains(parameter)) { Location location = parameter.Locations[0]; Debug.Assert(location.SourceTree.ReportDocumentationCommentDiagnostics()); //Should be the same tree as for the symbol. // NOTE: parameter name, since the parameter would be displayed as just its type. _diagnostics.Add(ErrorCode.WRN_MissingParamTag, location, parameter.Name, symbol); } } } if (documentedTypeParameters != null) { foreach (TypeParameterSymbol typeParameter in GetTypeParameters(symbol)) { if (!documentedTypeParameters.Contains(typeParameter)) { Location location = typeParameter.Locations[0]; Debug.Assert(location.SourceTree.ReportDocumentationCommentDiagnostics()); //Should be the same tree as for the symbol. _diagnostics.Add(ErrorCode.WRN_MissingTypeParamTag, location, typeParameter, symbol); } } } } }
/// <returns>False if no further checks are required (because they would be cascading).</returns> private bool VisitTypeOrMember(Symbol symbol, Compliance compliance) { SymbolKind symbolKind = symbol.Kind; System.Diagnostics.Debug.Assert( symbolKind == SymbolKind.NamedType || symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property || symbolKind == SymbolKind.Event || symbolKind == SymbolKind.Method); System.Diagnostics.Debug.Assert(!symbol.IsAccessor()); if (!CheckForDeclarationWithoutAssemblyDeclaration(symbol, compliance)) { return false; // Don't cascade from this failure. } bool isCompliant = IsTrue(compliance); bool isAccessibleOutsideAssembly = IsAccessibleOutsideAssembly(symbol); if (isAccessibleOutsideAssembly) { if (isCompliant) { CheckName(symbol); CheckForCompliantWithinNonCompliant(symbol); CheckReturnTypeCompliance(symbol); if (symbol.Kind == SymbolKind.NamedType) { CheckMemberDistinctness((NamedTypeSymbol)symbol); } } else if (GetDeclaredOrInheritedCompliance(symbol.ContainingAssembly) == Compliance.DeclaredTrue && IsTrue(GetInheritedCompliance(symbol))) { CheckForNonCompliantAbstractMember(symbol); } } else if (IsDeclared(compliance)) { this.AddDiagnostic(ErrorCode.WRN_CLS_MeaninglessOnPrivateType, symbol.Locations[0], symbol); return false; // Don't cascade from this failure. } if (isCompliant) { // Independent of accessibility. CheckForAttributeWithArrayArgument(symbol); } // These checks are independent of accessibility and compliance. if (symbolKind == SymbolKind.NamedType) { NamedTypeSymbol type = (NamedTypeSymbol)symbol; if (type.TypeKind == TypeKind.Delegate) { MethodSymbol method = type.DelegateInvokeMethod; CheckForMeaninglessOnParameter(method.Parameters); CheckForMeaninglessOnReturn(method); } } else if (symbolKind == SymbolKind.Method) { MethodSymbol method = (MethodSymbol)symbol; CheckForMeaninglessOnParameter(method.Parameters); CheckForMeaninglessOnReturn(method); } else if (symbolKind == SymbolKind.Property) { PropertySymbol property = (PropertySymbol)symbol; CheckForMeaninglessOnParameter(property.Parameters); } // All checks that apply to inaccessible symbols are performed by this method. return isAccessibleOutsideAssembly; }
/// <remarks> /// NOTE: Dev11 behavior - First, it ignores arity, /// which seems like a good way to disambiguate symbols (in particular, /// CLS Rule 43 says that the name includes backtick-arity). Second, it /// does not consider two members with identical names (i.e. not differing /// in case) to collide. /// </remarks> private void CheckSymbolDistinctness(Symbol symbol, string symbolName, MultiDictionary<string, Symbol>.ValueSet sameNameSymbols) { Debug.Assert(sameNameSymbols.Count > 0); Debug.Assert(symbol.Name == symbolName); bool isMethodOrProperty = symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.Property; foreach (Symbol other in sameNameSymbols) { if (other.Name != symbolName && !(isMethodOrProperty && other.Kind == symbol.Kind)) { // TODO: Shouldn't we somehow reference the conflicting member? Dev11 doesn't. this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.Locations[0], symbol); return; } } if (!isMethodOrProperty) { return; } foreach (Symbol other in sameNameSymbols) { // Note: not checking accessor signatures, but checking accessor names. ErrorCode code; if (symbol.Kind == other.Kind && !symbol.IsAccessor() && !other.IsAccessor() && TryGetCollisionErrorCode(symbol, other, out code)) { this.AddDiagnostic(code, symbol.Locations[0], symbol); return; } else if (other.Name != symbolName) { // TODO: Shouldn't we somehow reference the conflicting member? Dev11 doesn't. this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.Locations[0], symbol); return; } } }
/// <remarks> /// NOTE: Dev11 behavior - First, it ignores arity, /// which seems like a good way to disambiguate symbols (in particular, /// CLS Rule 43 says that the name includes backtick-arity). Second, it /// does not consider two members with identical names (i.e. not differing /// in case) to collide. /// </remarks> private void CheckSymbolDistinctness(Symbol symbol, IEnumerable<Symbol> sameNameSymbols) { System.Diagnostics.Debug.Assert(sameNameSymbols != null); System.Diagnostics.Debug.Assert(System.Linq.Enumerable.Any(sameNameSymbols)); bool isMethodOrProperty = symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.Property; foreach (Symbol other in sameNameSymbols) { if (other.Name != symbol.Name && !(isMethodOrProperty && other.Kind == symbol.Kind)) { // TODO: Shouldn't we somehow reference the conflicting member? Dev11 doesn't. this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.Locations[0], symbol); return; } } if (!isMethodOrProperty) { return; } foreach (Symbol other in sameNameSymbols) { // Note: not checking accessor signatures, but checking accessor names. ErrorCode code; if (symbol.Kind == other.Kind && !symbol.IsAccessor() && !other.IsAccessor() && TryGetCollisionErrorCode(symbol, other, out code)) { this.AddDiagnostic(code, symbol.Locations[0], symbol); return; } else if (symbol.Name != other.Name) { // TODO: Shouldn't we somehow reference the conflicting member? Dev11 doesn't. this.AddDiagnostic(ErrorCode.WRN_CLS_BadIdentifierCase, symbol.Locations[0], symbol); return; } } }