xpcom/analysis/type-printer.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/xpcom/analysis/type-printer.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,402 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +let dumpTypes = options['dump-types'].split(',');
     1.9 +
    1.10 +let interestingList = {};
    1.11 +let typelist = {};
    1.12 +
    1.13 +function interestingType(t)
    1.14 +{
    1.15 +  let name = t.name;
    1.16 +  
    1.17 +  if (dumpTypes.some(function(n) n == name)) {
    1.18 +    interestingList[name] = t;
    1.19 +    typelist[name] = t;
    1.20 +    return true;
    1.21 +  }
    1.22 +  
    1.23 +  for each (let base in t.bases) {
    1.24 +    if (base.access == 'public' && interestingType(base.type)) {
    1.25 +      typelist[name] = t;
    1.26 +      return true;
    1.27 +    }
    1.28 +  }
    1.29 +  
    1.30 +  return false;
    1.31 +}
    1.32 +
    1.33 +function addSubtype(t, subt)
    1.34 +{
    1.35 +  if (subt.typedef === undefined &&
    1.36 +      subt.kind === undefined)
    1.37 +    throw Error("Unexpected subtype: not class or typedef: " + subt);
    1.38 +
    1.39 +  if (t.subtypes === undefined)
    1.40 +    t.subtypes = [];
    1.41 +  
    1.42 +  t.subtypes.push(subt);
    1.43 +}
    1.44 +
    1.45 +function process_type(t)
    1.46 +{
    1.47 +  interestingType(t);
    1.48 +  
    1.49 +  for each (let base in t.bases)
    1.50 +    addSubtype(base.type, t);
    1.51 +}
    1.52 +
    1.53 +function process_decl(d)
    1.54 +{
    1.55 +  if (d.typedef !== undefined && d.memberOf)
    1.56 +    addSubtype(d.memberOf, d);
    1.57 +}
    1.58 +
    1.59 +function publicBases(t)
    1.60 +{
    1.61 +  yield t;
    1.62 +
    1.63 +  for each (let base in t.bases)
    1.64 +    if (base.access == "public")
    1.65 +      for each (let gbase in publicBases(base.type))
    1.66 +        yield gbase;
    1.67 +}
    1.68 +
    1.69 +function publicMembers(t)
    1.70 +{
    1.71 +  for each (let base in publicBases(t)) {
    1.72 +    for each (let member in base.members) {
    1.73 +      if (member.access === undefined)
    1.74 +        throw Error("Harumph: member without access? " + member);
    1.75 +
    1.76 +      if (member.access != "public")
    1.77 +        continue;
    1.78 +      
    1.79 +      yield member;
    1.80 +    }
    1.81 +  }
    1.82 +}
    1.83 +
    1.84 +/**
    1.85 + * Get the short name of a decl name. E.g. turn
    1.86 + * "MyNamespace::MyClass::Method(int j) const" into
    1.87 + * "Method"
    1.88 + */
    1.89 +function getShortName(decl)
    1.90 +{
    1.91 +  let name = decl.name;
    1.92 +  let lp = name.lastIndexOf('(');
    1.93 +  if (lp != -1)
    1.94 +    name = name.slice(0, lp);
    1.95 +  
    1.96 +  lp = name.lastIndexOf('::');
    1.97 +  if (lp != -1)
    1.98 +    name = name.slice(lp + 2);
    1.99 +
   1.100 +  return name;
   1.101 +}
   1.102 +
   1.103 +/**
   1.104 + * Remove functions in a base class which were overridden in a derived
   1.105 + * class.
   1.106 + *
   1.107 + * Although really, we should perhaps do this the other way around, or even
   1.108 + * group the two together, but that can come later.
   1.109 + */ 
   1.110 +function removeOverrides(members)
   1.111 +{
   1.112 +  let overrideMap = {};
   1.113 +  for (let i = members.length - 1; i >= 0; --i) {
   1.114 +    let m = members[i];
   1.115 +    if (!m.isFunction)
   1.116 +      continue;
   1.117 +
   1.118 +    let shortName = getShortName(m);
   1.119 +
   1.120 +    let overrides = overrideMap[shortName];
   1.121 +    if (overrides === undefined) {
   1.122 +      overrideMap[shortName] = [m];
   1.123 +      continue;
   1.124 +    }
   1.125 +
   1.126 +    let found = false;
   1.127 +    for each (let override in overrides) {
   1.128 +      if (signaturesMatch(override, m)) {
   1.129 +        // remove members[i], it was overridden
   1.130 +        members.splice(i, 1);
   1.131 +        found = true;
   1.132 +      }
   1.133 +    }
   1.134 +    if (found)
   1.135 +      continue;
   1.136 +         
   1.137 +    overrides.push(m);
   1.138 +  }
   1.139 +}
   1.140 +
   1.141 +/**
   1.142 + * Generates the starting position of lines within a file.
   1.143 + */
   1.144 +function getLineLocations(fdata)
   1.145 +{
   1.146 +  yield 0;
   1.147 +  
   1.148 +  let r = /\n/y;
   1.149 +  let pos = 0;
   1.150 +  let i = 1;
   1.151 +  for (;;) {
   1.152 +    pos = fdata.indexOf('\n', pos) + 1;
   1.153 +    if (pos == 0)
   1.154 +      break;
   1.155 +
   1.156 +    yield pos;
   1.157 +    i++;
   1.158 +  }
   1.159 +}
   1.160 +    
   1.161 +/**
   1.162 + * Find and return the doxygen comment immediately prior to the location
   1.163 + * object that was passed in.
   1.164 + * 
   1.165 + * @todo: parse doccomment data such as @param, @returns
   1.166 + * @todo: parse comments for markup
   1.167 + */
   1.168 +function getDocComment(loc)
   1.169 +{
   1.170 +  let fdata = read_file(loc.file);
   1.171 +  let linemap = [l for (l in getLineLocations(fdata))];
   1.172 +  
   1.173 +  if (loc.line >= linemap.length) {
   1.174 +    warning("Location larger than actual header: " + loc);
   1.175 +    return <></>;
   1.176 +  }
   1.177 +  
   1.178 +  let endpos = linemap[loc.line - 1] + loc.column - 1;
   1.179 +  let semipos = fdata.lastIndexOf(';', endpos);
   1.180 +  let bracepos = fdata.lastIndexOf('}', endpos);
   1.181 +  let searchslice = fdata.slice(Math.max(semipos, bracepos) + 1, endpos);
   1.182 +
   1.183 +  let m = searchslice.match(/\/\*\*[\s\S]*?\*\//gm);
   1.184 +  if (m === null)
   1.185 +    return <></>;
   1.186 +  
   1.187 +  let dc = m[m.length - 1].slice(3, -2);
   1.188 +  dc = dc.replace(/^\s*(\*+[ \t]*)?/gm, "");
   1.189 +
   1.190 +  return <pre class="doccomment">{dc}</pre>;
   1.191 +}
   1.192 +
   1.193 +function typeName(t)
   1.194 +{
   1.195 +  if (t.name !== undefined)
   1.196 +    return t.name;
   1.197 +
   1.198 +  if (t.isPointer)
   1.199 +    return "%s%s*".format(t.isConst ? "const " : "", typeName(t.type));
   1.200 +  
   1.201 +  if (t.isReference)
   1.202 +    return "%s%s&".format(t.isConst ? "const " : "", typeName(t.type));
   1.203 +
   1.204 +  return t.toString();
   1.205 +}
   1.206 +
   1.207 +function publicBaseList(t)
   1.208 +{
   1.209 +  let l = <ul/>;
   1.210 +  for each (let b in t.bases) {
   1.211 +    if (b.access == 'public')
   1.212 +      l.* += <li><a href={"/en/%s".format(b.type.name)}>{b.type.name}</a></li>;
   1.213 +  }
   1.214 +
   1.215 +  if (l.*.length() == 0)
   1.216 +    return <></>;
   1.217 +  
   1.218 +  return <>
   1.219 +    <h2>Base Classes</h2>
   1.220 +    {l}
   1.221 +  </>;
   1.222 +}
   1.223 +
   1.224 +/**
   1.225 + * Get a source-link for a given location.
   1.226 + */
   1.227 +function getLocLink(loc, text)
   1.228 +{
   1.229 +  return <a class="loc"
   1.230 +            href={"http://hg.mozilla.org/mozilla-central/file/%s/[LOC%s]#l%i".format(options.rev, loc.file, loc.line)}>{text}</a>;
   1.231 +}
   1.232 +
   1.233 +function dumpType(t)
   1.234 +{
   1.235 +  print("DUMP-TYPE(%s)".format(t.name));
   1.236 +
   1.237 +  let methodOverview = <tbody />;
   1.238 +  let methodList = <div/>;
   1.239 +  let memberList = <></>;
   1.240 +
   1.241 +  let shortNameMap = {};
   1.242 +
   1.243 +  let members = [m for (m in publicMembers(t))];
   1.244 +  
   1.245 +  removeOverrides(members);
   1.246 +
   1.247 +  for each (let m in members) {
   1.248 +    let qname = m.memberOf.name + '::';
   1.249 +
   1.250 +    // we don't inherit constructors from base classes
   1.251 +    if (m.isConstructor && m.memberOf !== t)
   1.252 +      continue;
   1.253 +    
   1.254 +    if (m.name.indexOf(qname) != 0)
   1.255 +      throw Error("Member name not qualified?");
   1.256 +    
   1.257 +    let name = m.name.slice(qname.length);
   1.258 +    
   1.259 +    if (name.indexOf('~') == 0)
   1.260 +      continue;
   1.261 +
   1.262 +    if (m.isFunction) {
   1.263 +      let innerList;
   1.264 +
   1.265 +      let shortName = getShortName(m);
   1.266 +      if (m.isConstructor)
   1.267 +        shortName = 'Constructors';
   1.268 +
   1.269 +      if (shortNameMap.hasOwnProperty(shortName)) {
   1.270 +        innerList = shortNameMap[shortName];
   1.271 +      }
   1.272 +      else {
   1.273 +        let overview = 
   1.274 +          <tr><td>
   1.275 +            <a href={'#%s'.format(escape(shortName))}>{shortName}</a>
   1.276 +          </td></tr>;
   1.277 +
   1.278 +        if (m.isConstructor)
   1.279 +          methodOverview.insertChildAfter(null, overview);
   1.280 +        else
   1.281 +          methodOverview.appendChild(overview);
   1.282 +        
   1.283 +        let shortMarkup =
   1.284 +          <div>
   1.285 +            <h3 id={shortName}>{shortName}</h3>
   1.286 +            <dl/>
   1.287 +          </div>;
   1.288 +
   1.289 +        
   1.290 +        if (m.isConstructor)
   1.291 +          methodList.insertChildAfter(null, shortMarkup);
   1.292 +        else
   1.293 +          methodList.appendChild(shortMarkup);
   1.294 +
   1.295 +        innerList = shortMarkup.dl;
   1.296 +        shortNameMap[shortName] = innerList;
   1.297 +      }
   1.298 +      
   1.299 +      let parameters = <ul/>;
   1.300 +      for each (p in m.parameters) {
   1.301 +        let name = p.name;
   1.302 +        if (name == 'this')
   1.303 +          continue;
   1.304 +        
   1.305 +        if (/^D_\d+$/.test(name))
   1.306 +          name = '<anonymous>';
   1.307 +        
   1.308 +        parameters.* += <li>{typeName(p.type)} {name}</li>;
   1.309 +      }
   1.310 +
   1.311 +      innerList.* +=
   1.312 +        <>
   1.313 +          <dt id={name} class="methodName">
   1.314 +            <code>{typeName(m.type.type)} {name}</code> - {getLocLink(m.loc, "source")}
   1.315 +          </dt>
   1.316 +          <dd>
   1.317 +            {getDocComment(m.loc)}
   1.318 +            {parameters.*.length() > 0 ?
   1.319 +             <>
   1.320 +               <h4>Parameters</h4>
   1.321 +               {parameters}
   1.322 +             </> : <></>}
   1.323 +          </dd>
   1.324 +        </>;
   1.325 +    }
   1.326 +    else {
   1.327 +      memberList += <li class="member">{name}</li>;
   1.328 +    }
   1.329 +  }
   1.330 +
   1.331 +  let r =
   1.332 +    <body>
   1.333 +      <p>{getLocLink(t.loc, "Class Declaration")}</p>
   1.334 +  
   1.335 +      {getDocComment(t.loc)}
   1.336 +
   1.337 +      {dumpTypes.some(function(n) n == t.name) ?
   1.338 +         <>
   1.339 +           [MAP{t.name}-graph.map]
   1.340 +           <img src={"/@api/deki/pages/=en%%252F%s/files/=%s-graph.png".format(t.name, t.name)} usemap="#classes" />
   1.341 +         </> : <></>
   1.342 +      }
   1.343 +  
   1.344 +      {methodOverview.*.length() > 0 ?
   1.345 +         <>
   1.346 +           <h2>Method Overview</h2>
   1.347 +           <table class="standard-table">{methodOverview}</table>
   1.348 +         </> :
   1.349 +         ""
   1.350 +      }
   1.351 +
   1.352 +      {publicBaseList(t)}
   1.353 +  
   1.354 +      <h2>Data Members</h2>
   1.355 +
   1.356 +      {memberList.*.length() > 0 ?
   1.357 +         memberList :
   1.358 +         <p><em>No public members.</em></p>
   1.359 +      }
   1.360 +
   1.361 +      <h2>Methods</h2>
   1.362 +  
   1.363 +      {methodList.*.length() > 0 ?
   1.364 +         methodList :
   1.365 +         <p><em>No public methods.</em></p>
   1.366 +      }
   1.367 +  
   1.368 +    </body>;
   1.369 +
   1.370 +  write_file(t.name + ".html", r.toXMLString());
   1.371 +}
   1.372 +
   1.373 +function graphType(t)
   1.374 +{
   1.375 +  print("GRAPH-TYPE(%s)".format(t.name));
   1.376 +
   1.377 +  let contents = "digraph classes {\n"
   1.378 +               + "  node [shape=rectangle fontsize=11]\n"
   1.379 +               + "  %s;\n".format(t.name);
   1.380 +
   1.381 +  function graphClass(c)
   1.382 +  {
   1.383 +    contents += '%s [URL="http://developer.mozilla.org/en/%s"]\n'.format(c.name, c.name);
   1.384 +    
   1.385 +    for each (let st in c.subtypes) {
   1.386 +      contents += "  %s -> %s;\n".format(c.name, st.name);
   1.387 +      graphClass(st);
   1.388 +    }
   1.389 +  }
   1.390 +
   1.391 +  graphClass(t);
   1.392 +  
   1.393 +  contents += "}\n";
   1.394 +  
   1.395 +  write_file(t.name + "-graph.gv", contents);
   1.396 +}
   1.397 +  
   1.398 +function input_end()
   1.399 +{
   1.400 +  for (let p in typelist)
   1.401 +    dumpType(typelist[p]);
   1.402 +
   1.403 +  for (let n in interestingList)
   1.404 +    graphType(interestingList[n]);
   1.405 +}

mercurial