//
// doc.cs: Support for XML documentation comment.
//
-// Author:
+// Authors:
// Atsushi Enomoto <atsushi@ximian.com>
+// Marek Safar (marek.safar@gmail.com>
//
// Dual licensed under the terms of the MIT X11 or GNU GPL
//
// Copyright 2004 Novell, Inc.
+// Copyright 2011 Xamarin Inc
//
//
using System.Xml;
using System.Linq;
-
-namespace Mono.CSharp {
-
+namespace Mono.CSharp
+{
//
- // Support class for XML documentation.
+ // Implements XML documentation generation.
//
- static class DocUtil
+ class DocumentationBuilder
{
- // 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, Report Report)
- {
- GenerateDocComment (t, ds, Report);
+ readonly XmlDocument XmlDocumentation;
- if (t.DefaultStaticConstructor != null)
- t.DefaultStaticConstructor.GenerateDocComment (t);
+ readonly ModuleContainer module;
+ readonly ModuleContainer doc_module;
- if (t.InstanceConstructors != null)
- foreach (Constructor c in t.InstanceConstructors)
- c.GenerateDocComment (t);
+ //
+ // The output for XML documentation.
+ //
+ XmlWriter XmlCommentOutput;
- if (t.Types != null)
- foreach (TypeContainer tc in t.Types)
- tc.GenerateDocComment (t);
+ static readonly string line_head = Environment.NewLine + " ";
- if (t.Constants != null)
- foreach (Const c in t.Constants)
- c.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.Fields != null)
- foreach (FieldBase f in t.Fields)
- f.GenerateDocComment (t);
+ ParserSession session;
- if (t.Events != null)
- foreach (Event e in t.Events)
- e.GenerateDocComment (t);
+ public DocumentationBuilder (ModuleContainer module)
+ {
+ doc_module = new ModuleContainer (module.Compiler);
+ doc_module.DocumentationBuilder = this;
- if (t.Indexers != null)
- foreach (Indexer ix in t.Indexers)
- ix.GenerateDocComment (t);
+ this.module = module;
+ XmlDocumentation = new XmlDocument ();
+ XmlDocumentation.PreserveWhitespace = false;
+ }
- if (t.Properties != null)
- foreach (Property p in t.Properties)
- p.GenerateDocComment (t);
+ Report Report {
+ get {
+ return module.Compiler.Report;
+ }
+ }
- if (t.Methods != null)
- foreach (MethodOrOperator m in t.Methods)
- m.GenerateDocComment (t);
+ public MemberName ParsedName {
+ get; set;
+ }
- if (t.Operators != null)
- foreach (Operator o in t.Operators)
- o.GenerateDocComment (t);
+ public List<DocumentationParameter> ParsedParameters {
+ get; set;
}
- // MemberCore
- private static readonly string line_head =
- Environment.NewLine + " ";
+ public TypeExpression ParsedBuiltinType {
+ get; set;
+ }
- static XmlNode GetDocCommentNode (MemberCore mc, string name, Report Report)
+ public Operator.OpType? ParsedOperator {
+ get; set;
+ }
+
+ 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 = mc.Compiler.Settings.Documentation.XmlDocumentation;
+ XmlDocument doc = XmlDocumentation;
try {
XmlElement el = doc.CreateElement ("member");
el.SetAttribute ("name", name);
// Generates xml doc comments (if any), and if required,
// handle warning report.
//
- internal static void GenerateDocComment (MemberCore mc,
- DeclSpace ds, Report Report)
+ internal void GenerateDocumentationForMember (MemberCore mc)
{
- if (mc.DocComment != null) {
- string name = mc.GetDocCommentName (ds);
-
- XmlNode n = GetDocCommentNode (mc, name, Report);
-
- 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.
- var al = new List<XmlNode> (nl.Count);
- foreach (XmlNode inc in nl)
- al.Add (inc);
- foreach (XmlElement inc in al)
- if (!HandleInclude (mc, inc, Report))
- inc.ParentNode.RemoveChild (inc);
- }
+ string name = mc.DocCommentHeader + mc.GetSignatureForDocumentation ();
- // FIXME: it could be done with XmlReader
- DeclSpace ds_target = mc as DeclSpace;
- if (ds_target == null)
- ds_target = ds;
-
- foreach (XmlElement see in n.SelectNodes (".//see"))
- HandleSee (mc, ds_target, see, Report);
- foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
- HandleSeeAlso (mc, ds_target, seealso ,Report);
- foreach (XmlElement see in n.SelectNodes (".//exception"))
- HandleException (mc, ds_target, see, Report);
+ 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 (mc.Compiler.Settings.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
+
+ foreach (XmlElement see in n.SelectNodes (".//see"))
+ HandleSee (mc, see);
+ foreach (XmlElement seealso in n.SelectNodes (".//seealso"))
+ HandleSeeAlso (mc, seealso);
+ foreach (XmlElement see in n.SelectNodes (".//exception"))
+ HandleException (mc, 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, Report Report)
+ bool HandleInclude (MemberCore mc, XmlElement el)
{
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);
keep_include_node = true;
- }
- else if (path.Length == 0) {
+ } 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);
keep_include_node = true;
- }
- else {
+ } else {
XmlDocument doc;
- if (!mc.Compiler.Settings.Documentation.StoredDocuments.TryGetValue (file, out doc)) {
+ Exception exception = null;
+ var full_path = Path.Combine (Path.GetDirectoryName (mc.Location.NameFullPath), file);
+
+ if (!StoredDocuments.TryGetValue (full_path, out doc)) {
try {
doc = new XmlDocument ();
- doc.Load (file);
- mc.Compiler.Settings.Documentation.StoredDocuments.Add (file, doc);
- } catch (Exception) {
+ doc.Load (full_path);
+ StoredDocuments.Add (full_path, doc);
+ } catch (Exception e) {
+ exception = e;
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 (doc != null) {
try {
XmlNodeList nl = doc.SelectNodes (path);
foreach (XmlNode n in nl)
el.ParentNode.InsertBefore (el.OwnerDocument.ImportNode (n, true), el);
} catch (Exception ex) {
+ exception = ex;
el.ParentNode.InsertBefore (el.OwnerDocument.CreateComment (" Failed to insert some or all of included XML "), el);
- Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}' ({2})", path, file, ex.Message);
}
}
+
+ if (exception != null) {
+ Report.Warning (1589, 1, mc.Location, "Unable to include XML fragment `{0}' of file `{1}'. {2}",
+ path, file, exception.Message);
+ }
}
+
return keep_include_node;
}
//
// Handles <see> elements.
//
- private static void HandleSee (MemberCore mc,
- DeclSpace ds, XmlElement see, Report r)
+ void HandleSee (MemberCore mc, XmlElement see)
{
- HandleXrefCommon (mc, ds, see, r);
+ HandleXrefCommon (mc, see);
}
//
// Handles <seealso> elements.
//
- private static void HandleSeeAlso (MemberCore mc,
- DeclSpace ds, XmlElement seealso, Report r)
+ void HandleSeeAlso (MemberCore mc, XmlElement seealso)
{
- HandleXrefCommon (mc, ds, seealso, r);
+ HandleXrefCommon (mc, seealso);
}
//
// Handles <exception> elements.
//
- private static void HandleException (MemberCore mc,
- DeclSpace ds, XmlElement seealso, Report r)
+ void HandleException (MemberCore mc, XmlElement seealso)
{
- HandleXrefCommon (mc, ds, seealso, r);
+ HandleXrefCommon (mc, 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 TypeSpec FindDocumentedType (MemberCore mc, string name, DeclSpace ds, string cref, Report r)
- {
- bool is_array = 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);
- is_array = true;
- }
- }
- TypeSpec t = FindDocumentedTypeNonArray (mc, identifier, ds, cref, r);
- if (t != null && is_array)
- t = ArrayContainer.MakeType (mc.Module, t);
- return t;
- }
-
- private static TypeSpec FindDocumentedTypeNonArray (MemberCore mc,
- string identifier, DeclSpace ds, string cref, Report r)
+ static void HandleTypeParam (MemberCore mc, XmlElement node)
{
- var types = mc.Module.Compiler.BuiltinTypes;
- switch (identifier) {
- case "int":
- return types.Int;
- case "uint":
- return types.UInt;
- case "short":
- return types.Short;
- case "ushort":
- return types.UShort;
- case "long":
- return types.Long;
- case "ulong":
- return types.ULong;
- case "float":
- return types.Float;
- case "double":
- return types.Double;
- case "char":
- return types.Char;
- case "decimal":
- return types.Decimal;
- case "byte":
- return types.Byte;
- case "sbyte":
- return types.SByte;
- case "object":
- return types.Object;
- case "bool":
- return types.Bool;
- case "string":
- return types.String;
- case "void":
- return types.Void;
- }
- FullNamedExpression e = ds.LookupNamespaceOrType (identifier, 0, mc.Location, false);
- if (e != null) {
- if (!(e is TypeExpr))
- return null;
- return e.Type;
- }
- int index = identifier.LastIndexOf ('.');
- if (index < 0)
- return null;
+ if (!node.HasAttribute ("name"))
+ return;
- var nsName = identifier.Substring (0, index);
- var typeName = identifier.Substring (index + 1);
- Namespace ns = ds.NamespaceEntry.NS.GetNamespace (nsName, false);
- ns = ns ?? mc.Module.GlobalRootNamespace.GetNamespace(nsName, false);
- if (ns != null) {
- var te = ns.LookupType(mc, typeName, 0, true, mc.Location);
- if(te != null)
- return te.Type;
+ string tp_name = node.GetAttribute ("name");
+ if (mc.CurrentTypeParameters != null) {
+ if (mc.CurrentTypeParameters.Find (tp_name) != null)
+ return;
}
-
- int warn;
- TypeSpec parent = FindDocumentedType (mc, identifier.Substring (0, index), ds, cref, r);
- if (parent == null)
- return null;
- // no need to detect warning 419 here
- var ts = FindDocumentedMember (mc, parent,
- identifier.Substring (index + 1),
- null, ds, out warn, cref, false, null, r) as TypeSpec;
- if (ts != null)
- return ts;
- return null;
+
+ // 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);
}
//
- // Returns a MemberInfo that is referenced in XML documentation
- // (by "see" or "seealso" elements).
+ // Handles <typeparamref /> node
//
- private static MemberSpec FindDocumentedMember (MemberCore mc,
- TypeSpec type, string member_name, AParametersCollection param_list,
- DeclSpace ds, out int warning_type, string cref,
- bool warn419, string name_for_error, Report r)
+ static void HandleTypeParamRef (MemberCore mc, XmlElement node)
{
-// for (; type != null; type = type.DeclaringType) {
- var mi = FindDocumentedMemberNoNest (
- mc, type, member_name, param_list, ds,
- out warning_type, cref, warn419,
- name_for_error, r);
- if (mi != null)
- return mi; // new FoundMember (type, mi);
-// }
- warning_type = 0;
- return null;
- }
+ if (!node.HasAttribute ("name"))
+ return;
- private static MemberSpec FindDocumentedMemberNoNest (
- MemberCore mc, TypeSpec type, string member_name,
- AParametersCollection param_list, DeclSpace ds, out int warning_type,
- string cref, bool warn419, string name_for_error, Report Report)
- {
- warning_type = 0;
-// var filter = new MemberFilter (member_name, 0, MemberKind.All, param_list, null);
- IList<MemberSpec> found = null;
- while (type != null && found == null) {
- found = MemberCache.FindMembers (type, member_name, false);
- type = type.DeclaringType;
- }
+ string tp_name = node.GetAttribute ("name");
+ var member = mc;
+ do {
+ if (member.CurrentTypeParameters != null) {
+ if (member.CurrentTypeParameters.Find (tp_name) != null)
+ return;
+ }
- if (found == null)
- return null;
+ member = member.Parent;
+ } while (member != null);
- if (warn419 && found.Count > 1) {
- Report419 (mc, name_for_error, found.ToArray (), Report);
- }
+ 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);
+ }
- return found [0];
-
-/*
- if (param_list == null) {
- // search for fields/events etc.
- mis = TypeManager.MemberLookup (type, null,
- type, MemberKind.All,
- BindingRestriction.None,
- member_name, null);
- mis = FilterOverridenMembersOut (mis);
- if (mis == null || mis.Length == 0)
- return null;
- if (warn419 && IsAmbiguous (mis))
- Report419 (mc, name_for_error, mis, Report);
- return mis [0];
- }
+ FullNamedExpression ResolveMemberName (IMemberContext context, MemberName mn)
+ {
+ if (mn.Left == null)
+ return context.LookupNamespaceOrType (mn.Name, mn.Arity, LookupMode.Probing, Location.Null);
- MethodSignature msig = new MethodSignature (member_name, null, param_list);
- 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, name_for_error, mis, Report);
- 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 return_type_name = null;
- if (member_name.StartsWith ("implicit operator ")) {
- Operator.GetMetadataName (Operator.OpType.Implicit);
- return_type_name = member_name.Substring (18).Trim (wsChars);
- }
- else if (member_name.StartsWith ("explicit operator ")) {
- oper = Operator.GetMetadataName (Operator.OpType.Explicit);
- return_type_name = member_name.Substring (18).Trim (wsChars);
- }
- else if (member_name.StartsWith ("operator ")) {
- oper = member_name.Substring (9).Trim (wsChars);
- switch (oper) {
- // either unary or binary
- case "+":
- oper = param_list.Length == 2 ?
- Operator.GetMetadataName (Operator.OpType.Addition) :
- Operator.GetMetadataName (Operator.OpType.UnaryPlus);
- break;
- case "-":
- oper = param_list.Length == 2 ?
- Operator.GetMetadataName (Operator.OpType.Subtraction) :
- Operator.GetMetadataName (Operator.OpType.UnaryNegation);
- break;
- default:
- oper = Operator.GetMetadataName (oper);
- if (oper != null)
- break;
-
- warning_type = 1584;
- Report.Warning (1020, 1, mc.Location, "Overloadable {0} operator is expected", param_list.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, param_list);
-
- mis = FindMethodBase (type,
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance,
- msig);
- if (mis.Length == 0)
- return null; // CS1574
- var mi = mis [0];
- TypeSpec expected = mi is MethodSpec ?
- ((MethodSpec) mi).ReturnType :
- mi is PropertySpec ?
- ((PropertySpec) mi).PropertyType :
- null;
- if (return_type_name != null) {
- TypeSpec returnType = FindDocumentedType (mc, return_type_name, ds, cref, Report);
- if (returnType == null || returnType != expected) {
- warning_type = 1581;
- Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
- return null;
- }
+ return null;
}
- return mis [0];
-*/
+
+ 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, Report Report)
+ void HandleXrefCommon (MemberCore mc, 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
-
- // When it found '?:' ('T:' 'M:' 'F:' 'P:' 'E:' etc.),
- // MS ignores not only its member kind, but also
- // the entire syntax correctness. Nor it also does
- // type fullname resolution i.e. "T:List(int)" is kept
- // as T:List(int), not
- // T:System.Collections.Generic.List<System.Int32>
+
+ // Nothing to be resolved the reference is marked explicitly
if (cref.Length > 2 && cref [1] == ':')
return;
- else
- signature = cref;
-
- // Also note that without "T:" any generic type
- // indication fails.
-
- int parens_pos = signature.IndexOf ('(');
- int brace_pos = parens_pos >= 0 ? -1 :
- signature.IndexOf ('[');
- if (parens_pos > 0 && signature [signature.Length - 1] == ')') {
- name = signature.Substring (0, parens_pos).Trim (wsChars);
- parameters = signature.Substring (parens_pos + 1, signature.Length - parens_pos - 2).Trim (wsChars);
- }
- else if (brace_pos > 0 && signature [signature.Length - 1] == ']') {
- name = signature.Substring (0, brace_pos).Trim (wsChars);
- parameters = signature.Substring (brace_pos + 1, signature.Length - brace_pos - 2).Trim (wsChars);
- }
- else {
- name = signature;
- parameters = null;
- }
- Normalize (mc, ref name, Report);
-
- 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 [] name_elems = identifier.Split ('.');
- for (int i = 0; i < name_elems.Length; i++) {
- string nameElem = GetBodyIdentifierFromName (name_elems [i]);
- if (i > 0)
- Normalize (mc, ref nameElem, Report);
- 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;
- }
- }
- // check if parameters are valid
- AParametersCollection parameter_types;
- if (parameters == null)
- parameter_types = null;
- else if (parameters.Length == 0)
- parameter_types = ParametersCompiled.EmptyReadOnlyParameters;
- else {
- string [] param_list = parameters.Split (',');
- var plist = new List<TypeSpec> ();
- for (int i = 0; i < param_list.Length; i++) {
- string param_type_name = param_list [i].Trim (wsChars);
- Normalize (mc, ref param_type_name, Report);
- TypeSpec param_type = FindDocumentedType (mc, param_type_name, ds, cref, Report);
- if (param_type == null) {
- Report.Warning (1580, 1, mc.Location, "Invalid type for parameter `{0}' in XML comment cref attribute `{1}'",
- (i + 1).ToString (), cref);
- return;
- }
- plist.Add (param_type);
- }
+ // Additional symbols for < and > are allowed for easier XML typing
+ cref = cref.Replace ('{', '<').Replace ('}', '>');
- parameter_types = ParametersCompiled.CreateFullyResolved (plist.ToArray ());
- }
+ var encoding = module.Compiler.Settings.Encoding;
+ var s = new MemoryStream (encoding.GetBytes (cref));
+
+ var source_file = new CompilationSourceFile (doc_module, mc.Location.SourceFile);
+ var report = new Report (doc_module.Compiler, new NullReportPrinter ());
+
+ if (session == null)
+ session = new ParserSession {
+ UseJayGlobalArrays = true
+ };
+
+ SeekableStreamReader seekable = new SeekableStreamReader (s, encoding, session.StreamReaderBuffer);
- TypeSpec type = FindDocumentedType (mc, name, ds, cref, Report);
- if (type != null
- // delegate must not be referenced with args
- && (!type.IsDelegate
- || parameter_types == null)) {
- string result = GetSignatureForDoc (type)
- + (brace_pos < 0 ? String.Empty : signature.Substring (brace_pos));
- xref.SetAttribute ("cref", "T:" + result);
- return; // a type
+ var parser = new CSharpParser (seekable, source_file, report, session);
+ ParsedParameters = null;
+ ParsedName = null;
+ ParsedBuiltinType = null;
+ ParsedOperator = null;
+ parser.Lexer.putback_char = Tokenizer.DocumentationXref;
+ parser.Lexer.parsing_generic_declaration_doc = true;
+ parser.parse ();
+ if (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;
}
- int period = name.LastIndexOf ('.');
- if (period > 0) {
- string typeName = name.Substring (0, period);
- string member_name = name.Substring (period + 1);
- string lookup_name = member_name == "this" ? MemberCache.IndexerNameAlias : member_name;
- Normalize (mc, ref lookup_name, Report);
- Normalize (mc, ref member_name, Report);
- type = FindDocumentedType (mc, typeName, ds, cref, Report);
- int warn_result;
- if (type != null) {
- var mi = FindDocumentedMember (mc, type, lookup_name, parameter_types, ds, out warn_result, cref, true, name, Report);
- if (warn_result > 0)
- return;
- if (mi != null) {
- // we cannot use 'type' directly
- // to get its name, since mi
- // could be from DeclaringType
- // for nested types.
- xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + member_name + GetParametersFormatted (mi));
- return; // a member of a type
+ 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;
+ }
}
+ } else {
+ type = (TypeSpec) member;
+ member = null;
}
- } else {
- int warn_result;
- var mi = FindDocumentedMember (mc, ds.PartialContainer.Definition, name, parameter_types, ds, out warn_result, cref, true, name, Report);
- if (warn_result > 0)
- return;
- if (mi != null) {
- // we cannot use 'type' directly
- // to get its name, since mi
- // could be from DeclaringType
- // for nested types.
- xref.SetAttribute ("cref", GetMemberDocHead (mi) + GetSignatureForDoc (mi.DeclaringType) + "." + name + GetParametersFormatted (mi));
- return; // local member name
- }
- }
+ if (ParsedParameters != null) {
+ var old_printer = mc.Module.Compiler.Report.SetPrinter (new NullReportPrinter ());
+ try {
+ var context = new DocumentationMemberContext (mc, ParsedName ?? MemberName.Null);
- // It still might be part of namespace name.
- Namespace ns = ds.NamespaceEntry.NS.GetNamespace (name, false);
- if (ns != null) {
- xref.SetAttribute ("cref", "N:" + ns.GetSignatureForError ());
- return; // a namespace
- }
- if (mc.Module.GlobalRootNamespace.IsNamespace (name)) {
- xref.SetAttribute ("cref", "N:" + name);
- return; // a namespace
- }
+ foreach (var pp in ParsedParameters) {
+ pp.Resolve (context);
+ }
+ } finally {
+ mc.Module.Compiler.Report.SetPrinter (old_printer);
+ }
+ }
- Report.Warning (1574, 1, mc.Location, "XML comment on `{0}' has cref attribute `{1}' that could not be resolved",
- mc.GetSignatureForError (), cref);
+ if (type != null) {
+ 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;
+ }
- xref.SetAttribute ("cref", "!:" + name);
- }
+ 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;
+
+ var pm_params = pm.Parameters;
+
+ int i;
+ for (i = 0; i < parsed_param_count; ++i) {
+ var pparam = ParsedParameters[i];
+
+ if (i >= pm_params.Count || pparam == null || pparam.TypeSpec == null ||
+ !TypeSpecComparer.Override.IsEqual (pparam.TypeSpec, pm_params.Types[i]) ||
+ (pparam.Modifier & Parameter.Modifier.RefOutMask) != (pm_params.FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask)) {
+
+ 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_params.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;
+ }
+ }
- static string GetParametersFormatted (MemberSpec mi)
- {
- var pm = mi as IParametersMember;
- if (pm == null || pm.Parameters.IsEmpty)
- return string.Empty;
-
- AParametersCollection parameters = pm.Parameters;
-/*
- if (parameters == null || parameters.Count == 0)
- return String.Empty;
-*/
- StringBuilder sb = new StringBuilder ();
- sb.Append ('(');
- for (int i = 0; i < parameters.Count; i++) {
-// if (is_setter && i + 1 == parameters.Count)
-// break; // skip "value".
- if (i > 0)
- sb.Append (',');
- TypeSpec t = parameters.Types [i];
- sb.Append (GetSignatureForDoc (t));
- }
- sb.Append (')');
- return sb.ToString ();
- }
+ // Continue with parent type for nested types
+ if (member == null) {
+ type = type.DeclaringType;
+ } else {
+ type = null;
+ }
+ } while (type != null);
- static string GetBodyIdentifierFromName (string name)
- {
- string identifier = 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);
+ }
- 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 (parameters_match == parsed_param_count + 1) {
+ Report.Warning (1581, 1, mc.Location, "Invalid return type in XML comment cref attribute `{0}'", cref);
+ }
+ }
+ }
}
- return identifier;
- }
+ 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 ();
+ }
- static void Report419 (MemberCore mc, string member_name, MemberSpec [] mis, Report Report)
- {
- Report.Warning (419, 3, mc.Location,
- "Ambiguous reference in cref attribute `{0}'. Assuming `{1}' but other overloads including `{2}' have also matched",
- member_name,
- TypeManager.GetFullNameSignature (mis [0]),
- TypeManager.GetFullNameSignature (mis [1]));
+ xref.SetAttribute ("cref", cref);
}
//
if (type is TypeSpec)
return "T:";
- 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, ParametersCompiled parameters, DeclSpace ds)
- {
- IParameterData [] plist = parameters.FixedParameters;
- string paramSpec = String.Empty;
- if (plist != null) {
- StringBuilder psb = new StringBuilder ();
- int i = 0;
- foreach (Parameter p in plist) {
- psb.Append (psb.Length != 0 ? "," : "(");
- psb.Append (GetSignatureForDoc (parameters.Types [i++]));
- if ((p.ModFlags & Parameter.Modifier.ISBYREF) != 0)
- psb.Append ('@');
- }
- paramSpec = psb.ToString ();
- }
-
- if (paramSpec.Length > 0)
- paramSpec += ")";
-
- string name = mc.Name;
- if (mc is Constructor)
- name = "#ctor";
- else if (mc is InterfaceMemberBase) {
- var imb = (InterfaceMemberBase) mc;
- name = imb.GetFullName (imb.ShortName);
- }
- name = name.Replace ('.', '#');
-
- if (mc.MemberName.TypeArguments != null && mc.MemberName.TypeArguments.Count > 0)
- name += "``" + mc.MemberName.CountTypeArguments;
-
- 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.ReturnType);
- break;
- }
- }
- return String.Concat (mc.DocCommentHeader, ds.Name, ".", name, paramSpec, suffix);
- }
-
- static string GetSignatureForDoc (TypeSpec type)
- {
- var tp = type as TypeParameterSpec;
- if (tp != null) {
- int c = 0;
- type = type.DeclaringType;
- while (type != null && type.DeclaringType != null) {
- type = type.DeclaringType;
- c += type.MemberDefinition.TypeParametersCount;
- }
- var prefix = tp.IsMethodOwned ? "``" : "`";
- return prefix + (c + tp.DeclaredPosition);
- }
-
- var pp = type as PointerContainer;
- if (pp != null)
- return GetSignatureForDoc (pp.Element) + "*";
-
- ArrayContainer ap = type as ArrayContainer;
- if (ap != null)
- return GetSignatureForDoc (ap.Element) +
- ArrayContainer.GetPostfixSignature (ap.Rank);
-
- if (TypeManager.IsGenericType (type)) {
- string g = type.MemberDefinition.Namespace;
- if (g != null && g.Length > 0)
- g += '.';
- int idx = type.Name.LastIndexOf ('`');
- g += (idx < 0 ? type.Name : type.Name.Substring (0, idx)) + '{';
- int argpos = 0;
- foreach (TypeSpec t in TypeManager.GetTypeArguments (type))
- g += (argpos++ > 0 ? "," : String.Empty) + GetSignatureForDoc (t);
- g += '}';
- return g;
- }
-
- string name = type.GetMetaInfo ().FullName != null ? type.GetMetaInfo ().FullName : type.Name;
- return name.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, Report Report)
+ void CheckParametersComments (MemberCore member, IParametersMember paramMember, XmlElement el)
{
- var paramTags = new Dictionary<string, string> ();
+ HashSet<string> found_tags = null;
foreach (XmlElement pelem in el.SelectNodes ("param")) {
string xname = pelem.GetAttribute ("name");
if (xname.Length == 0)
continue; // really? but MS looks doing so
- if (xname != "" && mc.ParameterInfo.GetParameterIndexByName (xname) < 0)
- 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.ContainsKey (xname))
- Report.Warning (1571, 2, mc.Location, "XML comment on `{0}' has a duplicate param tag for `{1}'",
- mc.GetSignatureForError (), xname);
- paramTags [xname] = xname;
- }
- IParameterData [] plist = mc.ParameterInfo.FixedParameters;
- foreach (Parameter p in plist) {
- if (paramTags.Count > 0 && !paramTags.ContainsKey (p.Name))
- 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, Report Report)
- {
- 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 Dictionary<string, XmlDocument> StoredDocuments = new Dictionary<string, XmlDocument> ();
+ 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, ModuleContainer module)
+ 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);
+ module.GenerateDocComment (this);
w.WriteFullEndElement (); // members
w.WriteEndElement ();
w.WriteWhitespace (Environment.NewLine);
w.WriteEndDocument ();
return true;
} catch (Exception ex) {
- module.Compiler.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 ();
}
}
+ }
+
+ //
+ // Type lookup of documentation references uses context of type where
+ // the reference is used but type parameters from cref value
+ //
+ sealed class DocumentationMemberContext : IMemberContext
+ {
+ readonly MemberCore host;
+ MemberName contextName;
+
+ public DocumentationMemberContext (MemberCore host, MemberName contextName)
+ {
+ this.host = host;
+ this.contextName = contextName;
+ }
+
+ public TypeSpec CurrentType {
+ get {
+ return host.CurrentType;
+ }
+ }
- //
- // Fixes full type name of each documented types/members up.
- //
- void GenerateDocComment (ModuleContainer module)
+ public TypeParameters CurrentTypeParameters {
+ get {
+ return contextName.TypeParameters;
+ }
+ }
+
+ public MemberCore CurrentMemberDefinition {
+ get {
+ return host.CurrentMemberDefinition;
+ }
+ }
+
+ public bool IsObsolete {
+ get {
+ return false;
+ }
+ }
+
+ public bool IsUnsafe {
+ get {
+ return host.IsStatic;
+ }
+ }
+
+ public bool IsStatic {
+ get {
+ return host.IsStatic;
+ }
+ }
+
+ public ModuleContainer Module {
+ get {
+ return host.Module;
+ }
+ }
+
+ public string GetSignatureForError ()
+ {
+ return host.GetSignatureForError ();
+ }
+
+ public ExtensionMethodCandidates LookupExtensionMethod (TypeSpec extensionType, string name, int arity)
+ {
+ return null;
+ }
+
+ public FullNamedExpression LookupNamespaceOrType (string name, int arity, LookupMode mode, Location loc)
+ {
+ if (arity == 0) {
+ var tp = CurrentTypeParameters;
+ if (tp != null) {
+ for (int i = 0; i < tp.Count; ++i) {
+ var t = tp[i];
+ if (t.Name == name) {
+ t.Type.DeclaredPosition = i;
+ return new TypeParameterExpr (t, loc);
+ }
+ }
+ }
+ }
+
+ return host.Parent.LookupNamespaceOrType (name, arity, mode, loc);
+ }
+
+ public FullNamedExpression LookupNamespaceAlias (string name)
+ {
+ throw new NotImplementedException ();
+ }
+ }
+
+ class DocumentationParameter
+ {
+ public readonly Parameter.Modifier Modifier;
+ public FullNamedExpression Type;
+ TypeSpec type;
+
+ public DocumentationParameter (Parameter.Modifier modifier, FullNamedExpression type)
+ : this (type)
+ {
+ this.Modifier = modifier;
+ }
+
+ public DocumentationParameter (FullNamedExpression type)
+ {
+ this.Type = type;
+ }
+
+ public TypeSpec TypeSpec {
+ get {
+ return type;
+ }
+ }
+
+ public void Resolve (IMemberContext context)
{
- if (module.Types != null)
- foreach (TypeContainer tc in module.Types)
- DocUtil.GenerateTypeDocComment (tc, null, module.Compiler.Report);
+ type = Type.ResolveAsType (context);
}
}
}