//
// doc.cs: Support for XML documentation comment.
//
-// Author:
+// Authors:
// Atsushi Enomoto <atsushi@ximian.com>
+// Marek Safar (marek.safar@gmail.com>
//
-// Licensed under the terms of the GNU GPL
+// Dual licensed under the terms of the MIT X11 or GNU GPL
//
-// (C) 2004 Novell, Inc.
+// Copyright 2004 Novell, Inc.
+// Copyright 2011 Xamarin Inc
//
//
-#if ! BOOTSTRAP_WITH_OLDLIB
+
using System;
-using System.Collections;
-using System.Collections.Specialized;
+using System.Collections.Generic;
using System.IO;
-using System.Reflection;
-using System.Reflection.Emit;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Security.Permissions;
using System.Text;
using System.Xml;
+using System.Linq;
-using Mono.CompilerServices.SymbolWriter;
-
-namespace Mono.CSharp {
-
+namespace Mono.CSharp
+{
//
- // Support class for XML documentation.
+ // Implements XML documentation generation.
//
-#if NET_2_0
- static
-#else
- abstract
-#endif
- public class DocUtil
+ class DocumentationBuilder
{
-#if !NET_2_0
- private DocUtil () {}
-#endif
- // TypeContainer
-
//
- // Generates xml doc comments (if any), and if required,
- // handle warning report.
+ // Used to create element which helps well-formedness checking.
//
- internal static void GenerateTypeDocComment (TypeContainer t,
- DeclSpace ds)
- {
- GenerateDocComment (t, ds);
-
- if (t.DefaultStaticConstructor != null)
- t.DefaultStaticConstructor.GenerateDocComment (t);
+ readonly XmlDocument XmlDocumentation;
- if (t.InstanceConstructors != null)
- foreach (Constructor c in t.InstanceConstructors)
- c.GenerateDocComment (t);
+ readonly ModuleContainer module;
- if (t.Types != null)
- foreach (TypeContainer tc in t.Types)
- tc.GenerateDocComment (t);
-
- if (t.Delegates != null)
- foreach (Delegate de in t.Delegates)
- de.GenerateDocComment (t);
- if (t.Enums != null)
- foreach (Enum en in t.Enums)
- en.GenerateDocComment (t);
+ //
+ // The output for XML documentation.
+ //
+ XmlWriter XmlCommentOutput;
- if (t.Constants != null)
- foreach (Const c in t.Constants)
- c.GenerateDocComment (t);
+ static readonly string line_head = Environment.NewLine + " ";
- if (t.Fields != null)
- foreach (FieldBase f in t.Fields)
- f.GenerateDocComment (t);
+ //
+ // Stores XmlDocuments that are included in XML documentation.
+ // Keys are included filenames, values are XmlDocuments.
+ //
+ Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
- if (t.Events != null)
- foreach (Event e in t.Events)
- e.GenerateDocComment (t);
+ public DocumentationBuilder (ModuleContainer module)
+ {
+ this.module = module;
+ XmlDocumentation = new XmlDocument ();
+ XmlDocumentation.PreserveWhitespace = false;
+ }
- if (t.Indexers != null)
- foreach (Indexer ix in t.Indexers)
- ix.GenerateDocComment (t);
+ Report Report {
+ get {
+ return module.Compiler.Report;
+ }
+ }
- if (t.Properties != null)
- foreach (Property p in t.Properties)
- p.GenerateDocComment (t);
+ public MemberName ParsedName {
+ get; set;
+ }
- if (t.Methods != null)
- foreach (Method m in t.Methods)
- m.GenerateDocComment (t);
+ public List<DocumentationParameter> ParsedParameters {
+ get; set;
+ }
- if (t.Operators != null)
- foreach (Operator o in t.Operators)
- o.GenerateDocComment (t);
+ public TypeExpression ParsedBuiltinType {
+ get; set;
}
- // MemberCore
- private static readonly string lineHead =
- Environment.NewLine + " ";
+ public Operator.OpType? ParsedOperator {
+ get; set;
+ }
- private static XmlNode GetDocCommentNode (MemberCore mc,
- string name)
+ XmlNode GetDocCommentNode (MemberCore mc, string name)
{
// FIXME: It could be even optimizable as not
// to use XmlDocument. But anyways the nodes
// are not kept in memory.
- XmlDocument doc = RootContext.Documentation.XmlDocumentation;
+ XmlDocument doc = XmlDocumentation;
try {
XmlElement el = doc.CreateElement ("member");
el.SetAttribute ("name", name);
if (s.Length > 0)
split [j++] = s;
}
- el.InnerXml = lineHead + String.Join (
- lineHead, split, 0, j);
+ el.InnerXml = line_head + String.Join (
+ line_head, split, 0, j);
return el;
- } catch (XmlException ex) {
- Report.Warning (1570, 1, mc.Location, "XML comment on `{0}' has non-well-formed XML ({1})", name, ex.Message);
- XmlComment com = doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
- return com;
+ } catch (Exception ex) {
+ Report.Warning (1570, 1, mc.Location, "XML documentation comment on `{0}' is not well-formed XML markup ({1})",
+ mc.GetSignatureForError (), ex.Message);
+
+ return doc.CreateComment (String.Format ("FIXME: Invalid documentation markup was found for member {0}", name));
}
}
// Generates xml doc comments (if any), and if required,
// handle warning report.
//
- internal static void GenerateDocComment (MemberCore mc,
- DeclSpace ds)
+ internal void GenerateDocumentationForMember (MemberCore mc)
{
- if (mc.DocComment != null) {
- string name = mc.GetDocCommentName (ds);
-
- XmlNode n = GetDocCommentNode (mc, name);
-
- XmlElement el = n as XmlElement;
- if (el != null) {
- mc.OnGenerateDocComment (el);
-
- // FIXME: it could be done with XmlReader
- XmlNodeList nl = n.SelectNodes (".//include");
- if (nl.Count > 0) {
- // It could result in current node removal, so prepare another list to iterate.
- ArrayList al = new ArrayList (nl.Count);
- foreach (XmlNode inc in nl)
- al.Add (inc);
- foreach (XmlElement inc in al)
- if (!HandleInclude (mc, inc))
- inc.ParentNode.RemoveChild (inc);
- }
+ string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
- // FIXME: it could be done with XmlReader
- DeclSpace dsTarget = mc as DeclSpace;
- if (dsTarget == null)
- dsTarget = ds;
-
- foreach (XmlElement see in n.SelectNodes (".//see"))
- HandleSee (mc, dsTarget, see);
- foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
- HandleSeeAlso (mc, dsTarget, seealso);
- foreach (XmlElement see in n.SelectNodes (".//exception"))
- HandleException (mc, dsTarget, see);
+ XmlNode n = GetDocCommentNode (mc, name);
+
+ XmlElement el = n as XmlElement;
+ if (el != null) {
+ var pm = mc as IParametersMember;
+ if (pm != null) {
+ CheckParametersComments (mc, pm, el);
}
- n.WriteTo (RootContext.Documentation.XmlCommentOutput);
- }
- else if (mc.IsExposedFromAssembly ()) {
- Constructor c = mc as Constructor;
- if (c == null || !c.IsDefault ())
- Report.Warning (1591, 4, mc.Location,
- "Missing XML comment for publicly visible type or member `{0}'", mc.GetSignatureForError ());
+ // FIXME: it could be done with XmlReader
+ XmlNodeList nl = n.SelectNodes (".//include");
+ if (nl.Count > 0) {
+ // It could result in current node removal, so prepare another list to iterate.
+ var al = new List<XmlNode> (nl.Count);
+ foreach (XmlNode inc in nl)
+ al.Add (inc);
+ foreach (XmlElement inc in al)
+ if (!HandleInclude (mc, inc))
+ inc.ParentNode.RemoveChild (inc);
+ }
+
+ // FIXME: it could be done with XmlReader
+ var ds_target = mc as TypeContainer;
+ if (ds_target == null)
+ ds_target = mc.Parent;
+
+ foreach (XmlElement see in n.SelectNodes (".//see"))
+ HandleSee (mc, ds_target, see);
+ foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
+ HandleSeeAlso (mc, ds_target, seealso);
+ foreach (XmlElement see in n.SelectNodes (".//exception"))
+ HandleException (mc, ds_target, see);
+ foreach (XmlElement node in n.SelectNodes (".//typeparam"))
+ HandleTypeParam (mc, node);
+ foreach (XmlElement node in n.SelectNodes (".//typeparamref"))
+ HandleTypeParamRef (mc, node);
}
+
+ n.WriteTo (XmlCommentOutput);
}
//
// Processes "include" element. Check included file and
// embed the document content inside this documentation node.
//
- private static bool HandleInclude (MemberCore mc, XmlElement el)
+ bool HandleInclude (MemberCore mc, XmlElement el)
{
- bool keepIncludeNode = false;
+ bool keep_include_node = false;
string file = el.GetAttribute ("file");
string path = el.GetAttribute ("path");
if (file == "") {
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `file' attribute");
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
- keepIncludeNode = true;
+ keep_include_node = true;
}
else if (path.Length == 0) {
Report.Warning (1590, 1, mc.Location, "Invalid XML `include' element. Missing `path' attribute");
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Include tag is invalid "), el);
- keepIncludeNode = true;
+ keep_include_node = true;
}
else {
- XmlDocument doc = RootContext.Documentation.StoredDocuments [file] as XmlDocument;
- if (doc == null) {
+ XmlDocument doc;
+ if (!StoredDocuments.TryGetValue (file, out doc)) {
try {
doc = new XmlDocument ();
doc.Load (file);
- RootContext.Documentation.StoredDocuments.Add (file, doc);
+ StoredDocuments.Add (file, doc);
} catch (Exception) {
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (String.Format (" Badly formed XML in at comment file `{0}': cannot be included ", file)), el);
Report.Warning (1592, 1, mc.Location, "Badly formed XML in included comments file -- `{0}'", file);
if (nl.Count == 0) {
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" No matching elements were found for the include tag embedded here. "), el);
- keepIncludeNode = true;
+ keep_include_node = true;
}
foreach (XmlNode n in nl)
el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
}
}
}
- return keepIncludeNode;
+ return keep_include_node;
}
//
// Handles <see> elements.
//
- private static void HandleSee (MemberCore mc,
- DeclSpace ds, XmlElement see)
+ void HandleSee (MemberCore mc, TypeContainer ds, XmlElement see)
{
HandleXrefCommon (mc, ds, see);
}
//
// Handles <seealso> elements.
//
- private static void HandleSeeAlso (MemberCore mc,
- DeclSpace ds, XmlElement seealso)
+ void HandleSeeAlso (MemberCore mc, TypeContainer ds, XmlElement seealso)
{
HandleXrefCommon (mc, ds, seealso);
}
//
// Handles <exception> elements.
//
- private static void HandleException (MemberCore mc,
- DeclSpace ds, XmlElement seealso)
+ void HandleException (MemberCore mc, TypeContainer ds, XmlElement seealso)
{
HandleXrefCommon (mc, ds, seealso);
}
- static readonly char [] wsChars =
- new char [] {' ', '\t', '\n', '\r'};
-
//
- // returns a full runtime type name from a name which might
- // be C# specific type name.
+ // Handles <typeparam /> node
//
- private static Type FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref)
+ void HandleTypeParam (MemberCore mc, XmlElement node)
{
- bool isArray = false;
- string identifier = name;
- if (name [name.Length - 1] == ']') {
- string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
- if (tmp [tmp.Length - 1] == '[') {
- identifier = tmp.Substring (0, tmp.Length - 1).Trim (wsChars);
- isArray = true;
- }
- }
- Type t = FindDocumentedTypeNonArray (mc, identifier, ds, cref);
- if (t != null && isArray)
- t = Array.CreateInstance (t, 0).GetType ();
- return t;
- }
+ if (!node.HasAttribute ("name"))
+ return;
- private static Type FindDocumentedTypeNonArray (MemberCore mc,
- string identifier, DeclSpace ds, string cref)
- {
- switch (identifier) {
- case "int":
- return typeof (int);
- case "uint":
- return typeof (uint);
- case "short":
- return typeof (short);
- case "ushort":
- return typeof (ushort);
- case "long":
- return typeof (long);
- case "ulong":
- return typeof (ulong);
- case "float":
- return typeof (float);
- case "double":
- return typeof (double);
- case "char":
- return typeof (char);
- case "decimal":
- return typeof (decimal);
- case "byte":
- return typeof (byte);
- case "sbyte":
- return typeof (sbyte);
- case "object":
- return typeof (object);
- case "bool":
- return typeof (bool);
- case "string":
- return typeof (string);
- case "void":
- return typeof (void);
- }
- FullNamedExpression e = ds.LookupNamespaceOrType (identifier, mc.Location, false);
- if (e != null) {
- if (!(e is TypeExpr))
- return null;
- return e.Type;
+ string tp_name = node.GetAttribute ("name");
+ if (mc.CurrentTypeParameters != null) {
+ if (mc.CurrentTypeParameters.Find (tp_name) != null)
+ return;
}
- int index = identifier.LastIndexOf ('.');
- if (index < 0)
- return null;
- int warn;
- Type parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref);
- if (parent == null)
- return null;
- // no need to detect warning 419 here
- return FindDocumentedMember (mc, parent,
- identifier.Substring (index + 1),
- null, ds, out warn, cref, false, null).Member as Type;
- }
-
- private static MemberInfo [] empty_member_infos =
- new MemberInfo [0];
-
- private static MemberInfo [] FindMethodBase (Type type,
- BindingFlags bindingFlags, MethodSignature signature)
- {
- MemberList ml = TypeManager.FindMembers (
- type,
- MemberTypes.Constructor | MemberTypes.Method | MemberTypes.Property | MemberTypes.Custom,
- bindingFlags,
- MethodSignature.method_signature_filter,
- signature);
- if (ml == null)
- return empty_member_infos;
-
- return FilterOverridenMembersOut ((MemberInfo []) ml);
+
+ // TODO: CS1710, CS1712
+
+ mc.Compiler.Report.Warning (1711, 2, mc.Location,
+ "XML comment on `{0}' has a typeparam name `{1}' but there is no type parameter by that name",
+ mc.GetSignatureForError (), tp_name);
}
- static bool IsOverride (PropertyInfo deriv_prop, PropertyInfo base_prop)
+ //
+ // Handles <typeparamref /> node
+ //
+ void HandleTypeParamRef (MemberCore mc, XmlElement node)
{
- if (!Invocation.IsAncestralType (base_prop.DeclaringType, deriv_prop.DeclaringType))
- return false;
-
- Type [] deriv_pd = TypeManager.GetArgumentTypes (deriv_prop);
- Type [] base_pd = TypeManager.GetArgumentTypes (base_prop);
-
- if (deriv_pd.Length != base_pd.Length)
- return false;
-
- for (int j = 0; j < deriv_pd.Length; ++j) {
- if (deriv_pd [j] != base_pd [j])
- return false;
- Type ct = TypeManager.TypeToCoreType (deriv_pd [j]);
- Type bt = TypeManager.TypeToCoreType (base_pd [j]);
-
- if (ct != bt)
- return false;
- }
-
- return true;
- }
+ if (!node.HasAttribute ("name"))
+ return;
- private static MemberInfo [] FilterOverridenMembersOut (
- MemberInfo [] ml)
- {
- if (ml == null)
- return empty_member_infos;
-
- ArrayList al = new ArrayList (ml.Length);
- for (int i = 0; i < ml.Length; i++) {
- MethodBase mx = ml [i] as MethodBase;
- PropertyInfo px = ml [i] as PropertyInfo;
- if (mx != null || px != null) {
- bool overriden = false;
- for (int j = 0; j < ml.Length; j++) {
- if (j == i)
- continue;
- MethodBase my = ml [j] as MethodBase;
- if (mx != null && my != null &&
- Invocation.IsOverride (my, mx)) {
- overriden = true;
- break;
- }
- else if (mx != null)
- continue;
- PropertyInfo py = ml [j] as PropertyInfo;
- if (px != null && py != null &&
- IsOverride (py, px)) {
- overriden = true;
- break;
- }
- }
- if (overriden)
- continue;
+ string tp_name = node.GetAttribute ("name");
+ var member = mc;
+ do {
+ if (member.CurrentTypeParameters != null) {
+ if (member.CurrentTypeParameters.Find (tp_name) != null)
+ return;
}
- al.Add (ml [i]);
- }
- return al.ToArray (typeof (MemberInfo)) as MemberInfo [];
- }
-
- struct FoundMember
- {
- public static FoundMember Empty = new FoundMember (true);
- public bool IsEmpty;
- public readonly MemberInfo Member;
- public readonly Type Type;
+ member = member.Parent;
+ } while (member != null);
- public FoundMember (bool regardlessOfThisValueItsEmpty)
- {
- IsEmpty = true;
- Member = null;
- Type = null;
- }
-
- public FoundMember (Type foundType, MemberInfo member)
- {
- IsEmpty = false;
- Type = foundType;
- Member = member;
- }
- }
-
- //
- // Returns a MemberInfo that is referenced in XML documentation
- // (by "see" or "seealso" elements).
- //
- private static FoundMember FindDocumentedMember (MemberCore mc,
- Type type, string memberName, Type [] paramList,
- DeclSpace ds, out int warningType, string cref,
- bool warn419, string nameForError)
- {
- for (; type != null; type = type.DeclaringType) {
- MemberInfo mi = FindDocumentedMemberNoNest (
- mc, type, memberName, paramList, ds,
- out warningType, cref, warn419,
- nameForError);
- if (mi != null)
- return new FoundMember (type, mi);
- }
- warningType = 0;
- return FoundMember.Empty;
+ mc.Compiler.Report.Warning (1735, 2, mc.Location,
+ "XML comment on `{0}' has a typeparamref name `{1}' that could not be resolved",
+ mc.GetSignatureForError (), tp_name);
}
- private static MemberInfo FindDocumentedMemberNoNest (
- MemberCore mc, Type type, string memberName,
- Type [] paramList, DeclSpace ds, out int warningType,
- string cref, bool warn419, string nameForError)
+ FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
{
- warningType = 0;
- MemberInfo [] mis;
-
- if (paramList == null) {
- // search for fields/events etc.
- mis = TypeManager.MemberLookup (type, null,
- type, MemberTypes.All,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
- memberName, null);
- mis = FilterOverridenMembersOut (mis);
- if (mis == null || mis.Length == 0)
- return null;
- if (warn419 && IsAmbiguous (mis))
- Report419 (mc, nameForError, mis);
- return mis [0];
- }
+ if (mn.Left == null)
+ return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
- MethodSignature msig = new MethodSignature (memberName, null, paramList);
- mis = FindMethodBase (type,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
- msig);
+ var left = ResolveMemberName (context, mn.Left);
+ var ns = left as Namespace;
+ if (ns != null)
+ return ns.LookupTypeOrNamespace (context, mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
- if (warn419 && mis.Length > 0) {
- if (IsAmbiguous (mis))
- Report419 (mc, nameForError, mis);
- return mis [0];
- }
+ TypeExpr texpr = left as TypeExpr;
+ if (texpr != null) {
+ var found = MemberCache.FindNestedType (texpr.Type, ParsedName.Name, ParsedName.Arity);
+ if (found != null)
+ return new TypeExpression (found, Location.Null);
- // search for operators (whose parameters exactly
- // matches with the list) and possibly report CS1581.
- string oper = null;
- string returnTypeName = null;
- if (memberName.StartsWith ("implicit operator ")) {
- oper = "op_Implicit";
- returnTypeName = memberName.Substring (18).Trim (wsChars);
- }
- else if (memberName.StartsWith ("explicit operator ")) {
- oper = "op_Explicit";
- returnTypeName = memberName.Substring (18).Trim (wsChars);
- }
- else if (memberName.StartsWith ("operator ")) {
- oper = memberName.Substring (9).Trim (wsChars);
- switch (oper) {
- // either unary or binary
- case "+":
- oper = paramList.Length == 2 ?
- Binary.oper_names [(int) Binary.Operator.Addition] :
- Unary.oper_names [(int) Unary.Operator.UnaryPlus];
- break;
- case "-":
- oper = paramList.Length == 2 ?
- Binary.oper_names [(int) Binary.Operator.Subtraction] :
- Unary.oper_names [(int) Unary.Operator.UnaryNegation];
- break;
- // unary
- case "!":
- oper = Unary.oper_names [(int) Unary.Operator.LogicalNot]; break;
- case "~":
- oper = Unary.oper_names [(int) Unary.Operator.OnesComplement]; break;
-
- case "++":
- oper = "op_Increment"; break;
- case "--":
- oper = "op_Decrement"; break;
- case "true":
- oper = "op_True"; break;
- case "false":
- oper = "op_False"; break;
- // binary
- case "*":
- oper = Binary.oper_names [(int) Binary.Operator.Multiply]; break;
- case "/":
- oper = Binary.oper_names [(int) Binary.Operator.Division]; break;
- case "%":
- oper = Binary.oper_names [(int) Binary.Operator.Modulus]; break;
- case "&":
- oper = Binary.oper_names [(int) Binary.Operator.BitwiseAnd]; break;
- case "|":
- oper = Binary.oper_names [(int) Binary.Operator.BitwiseOr]; break;
- case "^":
- oper = Binary.oper_names [(int) Binary.Operator.ExclusiveOr]; break;
- case "<<":
- oper = Binary.oper_names [(int) Binary.Operator.LeftShift]; break;
- case ">>":
- oper = Binary.oper_names [(int) Binary.Operator.RightShift]; break;
- case "==":
- oper = Binary.oper_names [(int) Binary.Operator.Equality]; break;
- case "!=":
- oper = Binary.oper_names [(int) Binary.Operator.Inequality]; break;
- case "<":
- oper = Binary.oper_names [(int) Binary.Operator.LessThan]; break;
- case ">":
- oper = Binary.oper_names [(int) Binary.Operator.GreaterThan]; break;
- case "<=":
- oper = Binary.oper_names [(int) Binary.Operator.LessThanOrEqual]; break;
- case ">=":
- oper = Binary.oper_names [(int) Binary.Operator.GreaterThanOrEqual]; break;
- default:
- warningType = 1584;
- Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", paramList.Length == 2 ? "binary" : "unary");
- Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
- mc.GetSignatureForError (), cref);
- return null;
- }
- }
- // here we still don't consider return type (to
- // detect CS1581 or CS1002+CS1584).
- msig = new MethodSignature (oper, null, paramList);
-
- mis = FindMethodBase (type,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
- msig);
- if (mis.Length == 0)
- return null; // CS1574
- MemberInfo mi = mis [0];
- Type expected = mi is MethodInfo ?
- ((MethodInfo) mi).ReturnType :
- mi is PropertyInfo ?
- ((PropertyInfo) mi).PropertyType :
- null;
- if (returnTypeName != null) {
- Type returnType = FindDocumentedType (mc, returnTypeName, ds, cref);
- if (returnType == null || returnType != expected) {
- warningType = 1581;
- Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
- return null;
- }
+ return null;
}
- return mis [0];
- }
- private static bool IsAmbiguous (MemberInfo [] members)
- {
- if (members.Length < 2)
- return false;
- if (members.Length > 2)
- return true;
- if (members [0] is EventInfo && members [1] is FieldInfo)
- return false;
- if (members [1] is EventInfo && members [0] is FieldInfo)
- return false;
- return true;
+ return left;
}
//
- // Processes "see" or "seealso" elements.
- // Checks cref attribute.
+ // Processes "see" or "seealso" elements from cref attribute.
//
- private static void HandleXrefCommon (MemberCore mc,
- DeclSpace ds, XmlElement xref)
+ void HandleXrefCommon (MemberCore mc, TypeContainer ds, XmlElement xref)
{
- string cref = xref.GetAttribute ("cref").Trim (wsChars);
+ string cref = xref.GetAttribute ("cref");
// when, XmlReader, "if (cref == null)"
if (!xref.HasAttribute ("cref"))
return;
- if (cref.Length == 0)
- Report.Warning (1001, 1, mc.Location, "Identifier expected");
- // ... and continue until CS1584.
-
- string signature; // "x:" are stripped
- string name; // method invokation "(...)" are removed
- string parameters; // method parameter list
- // strip 'T:' 'M:' 'F:' 'P:' 'E:' etc.
- // Here, MS ignores its member kind. No idea why.
+ // Nothing to be resolved the reference is marked explicitly
if (cref.Length > 2 && cref [1] == ':')
- signature = cref.Substring (2).Trim (wsChars);
- else
- signature = cref;
-
- int parensPos = signature.IndexOf ('(');
- int bracePos = parensPos >= 0 ? -1 :
- signature.IndexOf ('[');
- if (parensPos > 0 && signature [signature.Length - 1] == ')') {
- name = signature.Substring (0, parensPos).Trim (wsChars);
- parameters = signature.Substring (parensPos + 1, signature.Length - parensPos - 2).Trim (wsChars);
- }
- else if (bracePos > 0 && signature [signature.Length - 1] == ']') {
- name = signature.Substring (0, bracePos).Trim (wsChars);
- parameters = signature.Substring (bracePos + 1, signature.Length - bracePos - 2).Trim (wsChars);
- }
- else {
- name = signature;
- parameters = null;
- }
- Normalize (mc, ref name);
-
- string identifier = GetBodyIdentifierFromName (name);
-
- // Check if identifier is valid.
- // This check is not necessary to mark as error, but
- // csc specially reports CS1584 for wrong identifiers.
- string [] nameElems = identifier.Split ('.');
- for (int i = 0; i < nameElems.Length; i++) {
- string nameElem = GetBodyIdentifierFromName (nameElems [i]);
- if (i > 0)
- Normalize (mc, ref nameElem);
- if (!Tokenizer.IsValidIdentifier (nameElem)
- && nameElem.IndexOf ("operator") < 0) {
- Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
- mc.GetSignatureForError (), cref);
- xref.SetAttribute ("cref", "!:" + signature);
- return;
- }
+ return;
+
+ // Additional symbols for < and > are allowed for easier XML typing
+ cref = cref.Replace ('{', '<').Replace ('}', '>');
+
+ var encoding = module.Compiler.Settings.Encoding;
+ var s = new MemoryStream (encoding.GetBytes (cref));
+ SeekableStreamReader seekable = new SeekableStreamReader (s, encoding);
+
+ var source_file = new CompilationSourceFile ("{documentation}", "", 1);
+ var doc_module = new ModuleContainer (module.Compiler);
+ doc_module.DocumentationBuilder = this;
+ source_file.NamespaceContainer = new NamespaceContainer (null, doc_module, null, source_file);
+
+ Report parse_report = new Report (new NullReportPrinter ());
+ var parser = new CSharpParser (seekable, source_file, parse_report);
+ ParsedParameters = null;
+ ParsedName = null;
+ ParsedBuiltinType = null;
+ ParsedOperator = null;
+ parser.Lexer.putback_char = Tokenizer.DocumentationXref;
+ parser.Lexer.parsing_generic_declaration_doc = true;
+ parser.parse ();
+ if (parse_report.Errors > 0) {
+ Report.Warning (1584, 1, mc.Location, "XML comment on `{0}' has syntactically incorrect cref attribute `{1}'",
+ mc.GetSignatureForError (), cref);
+
+ xref.SetAttribute ("cref", "!:" + cref);
+ return;
}
- // check if parameters are valid
- Type [] parameterTypes;
- if (parameters == null)
- parameterTypes = null;
- else if (parameters.Length == 0)
- parameterTypes = Type.EmptyTypes;
- else {
- string [] paramList = parameters.Split (',');
- ArrayList plist = new ArrayList ();
- for (int i = 0; i < paramList.Length; i++) {
- string paramTypeName = paramList [i].Trim (wsChars);
- Normalize (mc, ref paramTypeName);
- Type paramType = FindDocumentedType (mc, paramTypeName, ds, cref);
- if (paramType == null) {
- Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
- (i + 1).ToString (), cref);
- return;
+ MemberSpec member;
+ string prefix = null;
+ FullNamedExpression fne = null;
+
+ //
+ // Try built-in type first because we are using ParsedName as identifier of
+ // member names on built-in types
+ //
+ if (ParsedBuiltinType != null && (ParsedParameters == null || ParsedName != null)) {
+ member = ParsedBuiltinType.Type;
+ } else {
+ member = null;
+ }
+
+ if (ParsedName != null || ParsedOperator.HasValue) {
+ TypeSpec type = null;
+ string member_name = null;
+
+ if (member == null) {
+ if (ParsedOperator.HasValue) {
+ type = mc.CurrentType;
+ } else if (ParsedName.Left != null) {
+ fne = ResolveMemberName (mc, ParsedName.Left);
+ if (fne != null) {
+ var ns = fne as Namespace;
+ if (ns != null) {
+ fne = ns.LookupTypeOrNamespace (mc, ParsedName.Name, ParsedName.Arity, LookupMode.Probing, Location.Null);
+ if (fne != null) {
+ member = fne.Type;
+ }
+ } else {
+ type = fne.Type;
+ }
+ }
+ } else {
+ fne = ResolveMemberName (mc, ParsedName);
+ if (fne == null) {
+ type = mc.CurrentType;
+ } else if (ParsedParameters == null) {
+ member = fne.Type;
+ } else if (fne.Type.MemberDefinition == mc.CurrentType.MemberDefinition) {
+ member_name = Constructor.ConstructorName;
+ type = fne.Type;
+ }
}
- plist.Add (paramType);
+ } else {
+ type = (TypeSpec) member;
+ member = null;
}
- parameterTypes = plist.ToArray (typeof (Type)) as Type [];
- }
- Type type = FindDocumentedType (mc, name, ds, cref);
- if (type != null
- // delegate must not be referenced with args
- && (!type.IsSubclassOf (typeof (System.Delegate))
- || parameterTypes == null)) {
- string result = GetSignatureForDoc (type)
- + (bracePos < 0 ? String.Empty : signature.Substring (bracePos));
- xref.SetAttribute ("cref", "T:" + result);
- return; // a type
- }
+ if (ParsedParameters != null) {
+ var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
+ foreach (var pp in ParsedParameters) {
+ pp.Resolve (mc);
+ }
+ mc.Module.Compiler.Report.SetPrinter (old_printer);
+ }
- int period = name.LastIndexOf ('.');
- if (period > 0) {
- string typeName = name.Substring (0, period);
- string memberName = name.Substring (period + 1);
- Normalize (mc, ref memberName);
- type = FindDocumentedType (mc, typeName, ds, cref);
- int warnResult;
if (type != null) {
- FoundMember fm = FindDocumentedMember (mc, type, memberName, parameterTypes, ds, out warnResult, cref, true, name);
- if (warnResult > 0)
- return;
- if (!fm.IsEmpty) {
- MemberInfo mi = fm.Member;
- // we cannot use 'type' directly
- // to get its name, since mi
- // could be from DeclaringType
- // for nested types.
- xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + memberName + GetParametersFormatted (mi));
- return; // a member of a type
+ if (member_name == null)
+ member_name = ParsedOperator.HasValue ?
+ Operator.GetMetadataName (ParsedOperator.Value) : ParsedName.Name;
+
+ int parsed_param_count;
+ if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
+ parsed_param_count = ParsedParameters.Count - 1;
+ } else if (ParsedParameters != null) {
+ parsed_param_count = ParsedParameters.Count;
+ } else {
+ parsed_param_count = 0;
}
- }
- }
- else {
- int warnResult;
- FoundMember fm = FindDocumentedMember (mc, ds.TypeBuilder, name, parameterTypes, ds, out warnResult, cref, true, name);
- if (warnResult > 0)
- return;
- if (!fm.IsEmpty) {
- MemberInfo mi = fm.Member;
- // we cannot use 'type' directly
- // to get its name, since mi
- // could be from DeclaringType
- // for nested types.
- xref.SetAttribute ("cref", GetMemberDocHead (mi.MemberType) + GetSignatureForDoc (fm.Type) + "." + name + GetParametersFormatted (mi));
- return; // local member name
- }
- }
- // It still might be part of namespace name.
- Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
- if (ns != null) {
- xref.SetAttribute ("cref", "N:" + ns.FullName);
- return; // a namespace
- }
- if (RootNamespace.Global.IsNamespace (name)) {
- xref.SetAttribute ("cref", "N:" + name);
- return; // a namespace
- }
+ int parameters_match = -1;
+ do {
+ var members = MemberCache.FindMembers (type, member_name, true);
+ if (members != null) {
+ foreach (var m in members) {
+ if (ParsedName != null && m.Arity != ParsedName.Arity)
+ continue;
+
+ if (ParsedParameters != null) {
+ IParametersMember pm = m as IParametersMember;
+ if (pm == null)
+ continue;
+
+ if (m.Kind == MemberKind.Operator && !ParsedOperator.HasValue)
+ continue;
+
+ int i;
+ for (i = 0; i < parsed_param_count; ++i) {
+ var pparam = ParsedParameters[i];
+
+ if (i >= pm.Parameters.Count || pparam == null ||
+ pparam.TypeSpec != pm.Parameters.Types[i] ||
+ (pparam.Modifier & Parameter.Modifier.SignatureMask) != (pm.Parameters.FixedParameters[i].ModFlags & Parameter.Modifier.SignatureMask)) {
+
+ if (i > parameters_match) {
+ parameters_match = i;
+ }
+
+ i = -1;
+ break;
+ }
+ }
+
+ if (i < 0)
+ continue;
+
+ if (ParsedOperator == Operator.OpType.Explicit || ParsedOperator == Operator.OpType.Implicit) {
+ if (pm.MemberType != ParsedParameters[parsed_param_count].TypeSpec) {
+ parameters_match = parsed_param_count + 1;
+ continue;
+ }
+ } else {
+ if (parsed_param_count != pm.Parameters.Count)
+ continue;
+ }
+ }
+
+ if (member != null) {
+ Report.Warning (419, 3, mc.Location,
+ "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
+ cref, member.GetSignatureForError (), m.GetSignatureForError ());
+
+ break;
+ }
+
+ member = m;
+ }
+ }
- Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
- mc.GetSignatureForError (), cref);
+ // Continue with parent type for nested types
+ if (member == null) {
+ type = type.DeclaringType;
+ } else {
+ type = null;
+ }
+ } while (type != null);
- xref.SetAttribute ("cref", "!:" + name);
- }
+ if (member == null && parameters_match >= 0) {
+ for (int i = parameters_match; i < parsed_param_count; ++i) {
+ Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
+ (i + 1).ToString (), cref);
+ }
- static string GetParametersFormatted (MemberInfo mi)
- {
- MethodBase mb = mi as MethodBase;
- bool isSetter = false;
- PropertyInfo pi = mi as PropertyInfo;
- if (pi != null) {
- mb = pi.GetGetMethod ();
- if (mb == null) {
- isSetter = true;
- mb = pi.GetSetMethod ();
+ if (parameters_match == parsed_param_count + 1) {
+ Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
+ }
+ }
}
}
- if (mb == null)
- return String.Empty;
-
- ParameterData parameters = TypeManager.GetParameterData (mb);
- if (parameters == null || parameters.Count == 0)
- return String.Empty;
-
- StringBuilder sb = new StringBuilder ();
- sb.Append ('(');
- for (int i = 0; i < parameters.Count; i++) {
- if (isSetter && i + 1 == parameters.Count)
- break; // skip "value".
- if (i > 0)
- sb.Append (',');
- Type t = parameters.ParameterType (i);
- sb.Append (GetSignatureForDoc (t));
- }
- sb.Append (')');
- return sb.ToString ();
- }
- static string GetBodyIdentifierFromName (string name)
- {
- string identifier = name;
-
- if (name.Length > 0 && name [name.Length - 1] == ']') {
- string tmp = name.Substring (0, name.Length - 1).Trim (wsChars);
- int last = tmp.LastIndexOf ('[');
- if (last > 0)
- identifier = tmp.Substring (0, last).Trim (wsChars);
+ if (member == null) {
+ Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
+ mc.GetSignatureForError (), cref);
+ cref = "!:" + cref;
+ } else if (member == InternalType.Namespace) {
+ cref = "N:" + fne.GetSignatureForError ();
+ } else {
+ prefix = GetMemberDocHead (member);
+ cref = prefix + member.GetSignatureForDocumentation ();
}
- return identifier;
- }
-
- static void Report419 (MemberCore mc, string memberName, MemberInfo [] mis)
- {
- Report.Warning (419, 3, mc.Location,
- "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
- memberName,
- TypeManager.GetFullNameSignature (mis [0]),
- TypeManager.GetFullNameSignature (mis [1]));
+ xref.SetAttribute ("cref", cref);
}
//
// Get a prefix from member type for XML documentation (used
// to formalize cref target name).
//
- static string GetMemberDocHead (MemberTypes type)
+ static string GetMemberDocHead (MemberSpec type)
{
- switch (type) {
- case MemberTypes.Constructor:
- case MemberTypes.Method:
+ if (type is FieldSpec)
+ return "F:";
+ if (type is MethodSpec)
return "M:";
- case MemberTypes.Event:
+ if (type is EventSpec)
return "E:";
- case MemberTypes.Field:
- return "F:";
- case MemberTypes.NestedType:
- case MemberTypes.TypeInfo:
- return "T:";
- case MemberTypes.Property:
+ if (type is PropertySpec)
return "P:";
- }
- return "!:";
- }
-
- // MethodCore
-
- //
- // Returns a string that represents the signature for this
- // member which should be used in XML documentation.
- //
- public static string GetMethodDocCommentName (MemberCore mc, Parameters parameters, DeclSpace ds)
- {
- Parameter [] plist = parameters.FixedParameters;
- string paramSpec = String.Empty;
- if (plist != null) {
- StringBuilder psb = new StringBuilder ();
- foreach (Parameter p in plist) {
- psb.Append (psb.Length != 0 ? "," : "(");
- psb.Append (GetSignatureForDoc (p.ExternalType ()));
- }
- paramSpec = psb.ToString ();
- }
+ if (type is TypeSpec)
+ return "T:";
- if (paramSpec.Length > 0)
- paramSpec += ")";
-
- string name = mc is Constructor ? "#ctor" : mc.Name;
- string suffix = String.Empty;
- Operator op = mc as Operator;
- if (op != null) {
- switch (op.OperatorType) {
- case Operator.OpType.Implicit:
- case Operator.OpType.Explicit:
- suffix = "~" + GetSignatureForDoc (op.MethodBuilder.ReturnType);
- break;
- }
- }
- return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
- }
-
- static string GetSignatureForDoc (Type type)
- {
- return TypeManager.IsGenericParameter (type) ?
- "`" + TypeManager.GenericParameterPosition (type) :
- type.FullName.Replace ("+", ".").Replace ('&', '@');
+ throw new NotImplementedException (type.GetType ().ToString ());
}
//
// FIXME: with a few effort, it could be done with XmlReader,
// that means removal of DOM use.
//
- internal static void OnMethodGenerateDocComment (
- MethodCore mc, XmlElement el)
+ void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
{
- Hashtable paramTags = new Hashtable ();
+ HashSet<string> found_tags = null;
foreach (XmlElement pelem in el.SelectNodes ("param")) {
- int i;
string xname = pelem.GetAttribute ("name");
if (xname.Length == 0)
continue; // really? but MS looks doing so
- if (xname != "" && mc.Parameters.GetParameterByName (xname, out i) == null)
- Report.Warning (1572, 2, mc.Location, "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
- mc.GetSignatureForError (), xname);
- else if (paramTags [xname] != null)
- Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
- mc.GetSignatureForError (), xname);
- paramTags [xname] = xname;
- }
- Parameter [] plist = mc.Parameters.FixedParameters;
- foreach (Parameter p in plist) {
- if (paramTags.Count > 0 && paramTags [p.Name] == null)
- Report.Warning (1573, 4, mc.Location, "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
- p.Name, mc.GetSignatureForError ());
- }
- }
-
- private static void Normalize (MemberCore mc, ref string name)
- {
- if (name.Length > 0 && name [0] == '@')
- name = name.Substring (1);
- else if (name == "this")
- name = "Item";
- else if (Tokenizer.IsKeyword (name) && !IsTypeName (name))
- Report.Warning (1041, 1, mc.Location, "Identifier expected. `{0}' is a keyword", name);
- }
-
- private static bool IsTypeName (string name)
- {
- switch (name) {
- case "bool":
- case "byte":
- case "char":
- case "decimal":
- case "double":
- case "float":
- case "int":
- case "long":
- case "object":
- case "sbyte":
- case "short":
- case "string":
- case "uint":
- case "ulong":
- case "ushort":
- case "void":
- return true;
- }
- return false;
- }
- }
- //
- // Implements XML documentation generation.
- //
- public class Documentation
- {
- public Documentation (string xml_output_filename)
- {
- docfilename = xml_output_filename;
- XmlDocumentation = new XmlDocument ();
- XmlDocumentation.PreserveWhitespace = false;
- }
+ if (found_tags == null) {
+ found_tags = new HashSet<string> ();
+ }
- private string docfilename;
+ if (xname != "" && paramMember.Parameters.GetParameterIndexByName (xname) < 0) {
+ Report.Warning (1572, 2, member.Location,
+ "XML comment on `{0}' has a param tag for `{1}', but there is no parameter by that name",
+ member.GetSignatureForError (), xname);
+ continue;
+ }
- //
- // Used to create element which helps well-formedness checking.
- //
- public XmlDocument XmlDocumentation;
+ if (found_tags.Contains (xname)) {
+ Report.Warning (1571, 2, member.Location,
+ "XML comment on `{0}' has a duplicate param tag for `{1}'",
+ member.GetSignatureForError (), xname);
+ continue;
+ }
- //
- // The output for XML documentation.
- //
- public XmlWriter XmlCommentOutput;
+ found_tags.Add (xname);
+ }
- //
- // Stores XmlDocuments that are included in XML documentation.
- // Keys are included filenames, values are XmlDocuments.
- //
- public Hashtable StoredDocuments = new Hashtable ();
+ if (found_tags != null) {
+ foreach (Parameter p in paramMember.Parameters.FixedParameters) {
+ if (!found_tags.Contains (p.Name) && !(p is ArglistParameter))
+ Report.Warning (1573, 4, member.Location,
+ "Parameter `{0}' has no matching param tag in the XML comment for `{1}'",
+ p.Name, member.GetSignatureForError ());
+ }
+ }
+ }
//
// Outputs XML documentation comment from tokenized comments.
//
- public bool OutputDocComment (string asmfilename)
+ public bool OutputDocComment (string asmfilename, string xmlFileName)
{
XmlTextWriter w = null;
try {
- w = new XmlTextWriter (docfilename, null);
+ w = new XmlTextWriter (xmlFileName, null);
w.Indentation = 4;
w.Formatting = Formatting.Indented;
w.WriteStartDocument ();
w.WriteStartElement ("doc");
w.WriteStartElement ("assembly");
w.WriteStartElement ("name");
- w.WriteString (Path.ChangeExtension (asmfilename, null));
+ w.WriteString (Path.GetFileNameWithoutExtension (asmfilename));
w.WriteEndElement (); // name
w.WriteEndElement (); // assembly
w.WriteStartElement ("members");
XmlCommentOutput = w;
- GenerateDocComment ();
+ module.GenerateDocComment (this);
w.WriteFullEndElement (); // members
w.WriteEndElement ();
w.WriteWhitespace (Environment.NewLine);
w.WriteEndDocument ();
return true;
} catch (Exception ex) {
- Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", docfilename, ex.Message);
+ Report.Error (1569, "Error generating XML documentation file `{0}' (`{1}')", xmlFileName, ex.Message);
return false;
} finally {
if (w != null)
w.Close ();
}
}
+ }
- //
- // Fixes full type name of each documented types/members up.
- //
- public void GenerateDocComment ()
- {
- TypeContainer root = RootContext.ToplevelTypes;
- if (root.Interfaces != null)
- foreach (Interface i in root.Interfaces)
- DocUtil.GenerateTypeDocComment (i, null);
+ class DocumentationParameter
+ {
+ public readonly Parameter.Modifier Modifier;
+ public FullNamedExpression Type;
+ TypeSpec type;
- if (root.Types != null)
- foreach (TypeContainer tc in root.Types)
- DocUtil.GenerateTypeDocComment (tc, null);
+ public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
+ : this (type)
+ {
+ this.Modifier = modifier;
+ }
- if (root.Delegates != null)
- foreach (Delegate d in root.Delegates)
- DocUtil.GenerateDocComment (d, null);
+ public DocumentationParameter (FullNamedExpression type)
+ {
+ this.Type = type;
+ }
- if (root.Enums != null)
- foreach (Enum e in root.Enums)
- e.GenerateDocComment (null);
+ public TypeSpec TypeSpec {
+ get {
+ return type;
+ }
+ }
+ public void Resolve (IMemberContext context)
+ {
+ type = Type.ResolveAsType (context);
}
}
}
-#endif