This test simulates the conditions when a dune user write a toplevel module.

The module C is not exposed in the handwritten toplevel module.
The module A and B are exposed.
The module B depends on both B and C, the module C only depends on A.

  $ ocamlc -c -o main__.cmo main__.ml -bin-annot -w -49 -no-alias-deps -I .
  $ ocamlc -c -open Main__ -o main__A.cmo a.ml -bin-annot -I .
  $ ocamlc -c -open Main__ -o main__C.cmo c.ml -bin-annot -I .
  $ ocamlc -c -open Main__ -o main__B.cmo b.ml -bin-annot -I .
  $ ocamlc -c -open Main__ main.ml -bin-annot -I .

Collecting occurrences is done on implementation files. 

  $ odoc compile-impl -I . main__A.cmt --output-dir .
  $ odoc compile-impl -I . main__C.cmt --output-dir .
  $ odoc compile-impl -I . main__B.cmt --output-dir .
  $ odoc compile-impl -I . main__.cmt --output-dir .
  $ odoc compile-impl -I . main.cmt --output-dir .

We need the interface version to resolve the occurrences

  $ odoc compile -I . main__A.cmt
  $ odoc compile -I . main__C.cmt
  $ odoc compile -I . main__B.cmt
  $ odoc compile -I . main__.cmt
  $ odoc compile -I . main.cmt

Let's link the implementations

  $ odoc link -I . impl-main.odoc
  $ odoc link -I . impl-main__A.odoc
  $ odoc link -I . impl-main__B.odoc
  $ odoc link -I . impl-main__C.odoc
  $ odoc link -I . impl-main__.odoc

The count occurrences command outputs a marshalled hashtable, whose keys are
odoc identifiers, and whose values are integers corresponding to the number of
uses. We can later aggregate those hashtables, so we create the full hashtable,
and a hashtable for each compilation unit.

  $ mkdir main
  $ mkdir main__
  $ mkdir main__A
  $ mkdir main__B
  $ mkdir main__C

  $ mv impl-main.odocl main
  $ mv impl-main__.odocl main__
  $ mv impl-main__A.odocl main__A
  $ mv impl-main__B.odocl main__B
  $ mv impl-main__C.odocl main__C
  $ odoc count-occurrences main -o main.odoc-occurrences
  $ odoc count-occurrences main__ -o main__.odoc-occurrences
  $ odoc count-occurrences main__A -o main__A.odoc-occurrences
  $ odoc count-occurrences main__B -o main__B.odoc-occurrences
  $ odoc count-occurrences main__C -o main__C.odoc-occurrences

The occurrences_print executable, available only for testing, unmarshal the file
and prints the number of occurrences in a readable format.

Uses of A are: 2 times in b.ml, 1 time in c.ml, 1 time in main.ml
Uses of B are: 1 time in main.ml
Uses of C are not counted, since the canonical destination (Main.C, generated by dune) does not exist.
Uses of B.Z are not counted since they go to a hidden module.
Uses of values Y.x and Z.y (in b.ml) are not counted since they come from a "local" module.

  $ occurrences_print main.odoc-occurrences | sort
  Main was used directly 0 times and indirectly 2 times
  Main.A was used directly 1 times and indirectly 0 times
  Main.B was used directly 1 times and indirectly 0 times

  $ occurrences_print main__.odoc-occurrences | sort

A only uses "persistent" values: one it defines itself.
  $ occurrences_print main__A.odoc-occurrences | sort

"Aliased" values are not counted since they become persistent
  $ occurrences_print main__B.odoc-occurrences | sort
  Main was used directly 0 times and indirectly 7 times
  Main.A was used directly 2 times and indirectly 5 times
  Main.A.(||>) was used directly 1 times and indirectly 0 times
  Main.A.M was used directly 2 times and indirectly 0 times
  Main.A.t was used directly 1 times and indirectly 0 times
  Main.A.x was used directly 1 times and indirectly 0 times

"Aliased" values are not counted since they become persistent
  $ occurrences_print main__C.odoc-occurrences | sort
  Main was used directly 0 times and indirectly 2 times
  Main.A was used directly 1 times and indirectly 1 times
  Main.A.x was used directly 1 times and indirectly 0 times

Now we can merge all tables

  $ cat > files.map << EOF
  > main__A.odoc-occurrences
  > main__B.odoc-occurrences
  > main__C.odoc-occurrences
  > EOF
  $ odoc aggregate-occurrences main.odoc-occurrences main__.odoc-occurrences --file-list files.map -o aggregated.odoc-occurrences

  $ occurrences_print aggregated.odoc-occurrences | sort > all_merged
  $ cat all_merged
  Main was used directly 0 times and indirectly 11 times
  Main.A was used directly 4 times and indirectly 6 times
  Main.A.(||>) was used directly 1 times and indirectly 0 times
  Main.A.M was used directly 2 times and indirectly 0 times
  Main.A.t was used directly 1 times and indirectly 0 times
  Main.A.x was used directly 2 times and indirectly 0 times
  Main.B was used directly 1 times and indirectly 0 times

Compare with the one created directly with all occurrences:

  $ odoc count-occurrences . -o all.odoc-occurrences
  $ occurrences_print all.odoc-occurrences | sort > directly_all
  $ diff all_merged directly_all

We can also include hidden ids:

  $ odoc count-occurrences main__B -o b.odoc-occurrences --include-hidden
  $ occurrences_print b.odoc-occurrences | sort
  Main was used directly 0 times and indirectly 7 times
  Main.A was used directly 2 times and indirectly 5 times
  Main.A.(||>) was used directly 1 times and indirectly 0 times
  Main.A.M was used directly 2 times and indirectly 0 times
  Main.A.t was used directly 1 times and indirectly 0 times
  Main.A.x was used directly 1 times and indirectly 0 times

  $ odoc count-occurrences . -o all.odoc-occurrences --include-hidden
  $ occurrences_print all.odoc-occurrences | sort
  Main was used directly 0 times and indirectly 11 times
  Main.A was used directly 4 times and indirectly 6 times
  Main.A.(||>) was used directly 1 times and indirectly 0 times
  Main.A.M was used directly 2 times and indirectly 0 times
  Main.A.t was used directly 1 times and indirectly 0 times
  Main.A.x was used directly 2 times and indirectly 0 times
  Main.B was used directly 1 times and indirectly 0 times

We can use the generated table when generating the json output:

  $ odoc link -I . main.odoc

  $ odoc compile-index --json -o index.json --occurrences all.odoc-occurrences main.odocl

  $ cat index.json | jq sort | jq '.[]' -c
  {"id":[{"kind":"Root","name":"Main"}],"doc":"Handwritten top-level module","kind":{"kind":"Module"},"display":{"url":"Main/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>Handwritten top-level module</p></div></div>"},"occurrences":{"direct":0,"indirect":11}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"A"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/index.html#module-A","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">A</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":4,"indirect":6}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/index.html#module-B","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">B</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":1,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Module","name":"M"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/B/index.html#module-M","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.</span><span class=\"entry-name\">M</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Module","name":"Y"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/B/index.html#module-Y","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.</span><span class=\"entry-name\">Y</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Module","name":"Z"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/B/index.html#module-Z","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.</span><span class=\"entry-name\">Z</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Module","name":"Z"},{"kind":"Module","name":"Y"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"Main/B/Z/index.html#module-Y","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.Z.</span><span class=\"entry-name\">Y</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"A"},{"kind":"ModuleType","name":"M"}],"doc":"","kind":{"kind":"ModuleType"},"display":{"url":"Main/A/index.html#module-type-M","html":"<code class=\"entry-kind\">sig</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.A.</span><span class=\"entry-name\">M</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":2,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"ModuleType","name":"Y"}],"doc":"","kind":{"kind":"ModuleType"},"display":{"url":"Main/B/index.html#module-type-Y","html":"<code class=\"entry-kind\">sig</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.</span><span class=\"entry-name\">Y</span></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"A"},{"kind":"Type","name":"t"}],"doc":"","kind":{"kind":"TypeDecl","private":false,"manifest":"string","constraints":[]},"display":{"url":"Main/A/index.html#type-t","html":"<code class=\"entry-kind\">type</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.A.</span><span class=\"entry-name\">t</span><code class=\"entry-rhs\"> = string</code></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":1,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"A"},{"kind":"Value","name":"(||>)"}],"doc":"","kind":{"kind":"Value","type":"int -> int -> int"},"display":{"url":"Main/A/index.html#val-(||>)","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.A.</span><span class=\"entry-name\">(||&gt;)</span><code class=\"entry-rhs\"> : int -&gt; int -&gt; int</code></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":1,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"A"},{"kind":"Value","name":"x"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"Main/A/index.html#val-x","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.A.</span><span class=\"entry-name\">x</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":2,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Value","name":"y"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"Main/B/index.html#val-y","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.</span><span class=\"entry-name\">y</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}
  {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"B"},{"kind":"Module","name":"Z"},{"kind":"Value","name":"y"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"Main/B/Z/index.html#val-y","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.B.Z.</span><span class=\"entry-name\">y</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"},"occurrences":{"direct":0,"indirect":0}}

  $ cat index.json | jq sort | head -n 33
  [
    {
      "id": [
        {
          "kind": "Root",
          "name": "Main"
        }
      ],
      "doc": "Handwritten top-level module",
      "kind": {
        "kind": "Module"
      },
      "display": {
        "url": "Main/index.html",
        "html": "<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>Handwritten top-level module</p></div></div>"
      },
      "occurrences": {
        "direct": 0,
        "indirect": 11
      }
    },
    {
      "id": [
        {
          "kind": "Root",
          "name": "Main"
        },
        {
          "kind": "Module",
          "name": "A"
        }
      ],
      "doc": "",

  $ cat index.json | jq -r '.[] | "\(.id | map("\(.kind)-\(.name)") | join(".")), direct: \(.occurrences.direct), indirect: \(.occurrences.indirect)"' | sort
  Root-Main, direct: 0, indirect: 11
  Root-Main.Module-A, direct: 4, indirect: 6
  Root-Main.Module-A.ModuleType-M, direct: 2, indirect: 0
  Root-Main.Module-A.Type-t, direct: 1, indirect: 0
  Root-Main.Module-A.Value-(||>), direct: 1, indirect: 0
  Root-Main.Module-A.Value-x, direct: 2, indirect: 0
  Root-Main.Module-B, direct: 1, indirect: 0
  Root-Main.Module-B.Module-M, direct: 0, indirect: 0
  Root-Main.Module-B.Module-Y, direct: 0, indirect: 0
  Root-Main.Module-B.Module-Z, direct: 0, indirect: 0
  Root-Main.Module-B.Module-Z.Module-Y, direct: 0, indirect: 0
  Root-Main.Module-B.Module-Z.Value-y, direct: 0, indirect: 0
  Root-Main.Module-B.ModuleType-Y, direct: 0, indirect: 0
  Root-Main.Module-B.Value-y, direct: 0, indirect: 0
