Document only things that are marked with a doc comment including children and other references

This issue has been tracked since 2023-02-22.

Search terms

include exclude tag default option comment children reference filter

Question

Is it possible to have a custom tag to explicitly mark items and generate a documentation that output those items and any related types (children, reference, etc...)?

I found the issue #2028, which give me a good hint on how to do this. With the #2028 everything to include needs to be marked with the tag. I am trying to automate the inclusion of the types related to the one marked.

ex:

/**
 * @include
 */
interface IncludedInterface {
  parameters: SomeParameterRecord;
}

/**
 * should be included
 */
type SomeParameterRecord = Record<string, ParameterValue>;

/**
 * should be included
 */
type ParameterValue = BooleanParameter | AlignmentParameter;

/**
 * should be included
 */
interface BooleanParameter {
  value: boolean;
}

/**
 * should be included
 */
type AlignmentParameterValue = 'left' | 'center' | 'right';

/**
 * should be included
 */
interface AlignmentParameter {
  value: AlignmentParameterValue;
}

/**
 * should not be included
 */
interface NotIncludedInterface {[
   parameters: SomeParameterRecord;
   otherProperty: OtherPropertyInterface
}

/**
 * should not be included
 */
interface OtherPropertyInterface {
  key: string
}

Reflection.traverse give me some children but it seems to be not enough.
Any pointer to achieve would be greatly appreciated.

Gerrit0 wrote this answer on 2023-03-05

Started writing out how I'd do it... then realized it'd be easier to follow if I just did it, and would probably take less time

I added a few test cases:

/** @include */
function foo<TP extends Ext = Def>(prop: ParamRef): RetRef {
  throw 1;
}

/** yes */
interface ParamRef {}

/** yes */
interface RetRef {}

/** yes */
interface Ext {}

/** yes */
interface Def {}

/** @include */
declare function overloaded(): string;
declare function overloaded(x: string): NotIncluded;

declare function overloadedFullyRemoved(): string;
declare function overloadedFullyRemoved(x: string): NotIncluded;

interface NotIncluded {}

And here's the plugin:

// @ts-check
// CC0

const td = require("typedoc");

/** @param {td.Application} app */
exports.load = function (app) {
  // Optional: Automatically add `@include` as a modifier tag so that the user
  // of this plugin doesn't have to. Do this with a low priority so that if the
  // user sets the option anywhere, their choice will overwrite the default.
  app.options.addReader({
    name: "include-plugin",
    priority: 0,
    read(options) {
      options.setValue("modifierTags", [
        ...options.getValue("modifierTags"),
        "@include",
      ]);
    },
  });

  app.converter.on(
    td.Converter.EVENT_RESOLVE_BEGIN,
    /** @param {td.Context} context */
    (context) => {
      for (const refl of discoverReflectionsToRemove(context.project)) {
        context.project.removeReflection(refl);
      }
    }
  );
};

/** @param {td.ProjectReflection} project */
function discoverReflectionsToRemove(project) {
  /** @type {Set<td.Reflection>} */
  const toRemove = new Set();
  /** @type {Array<td.Reflection>} */
  const retain = [];
  /** @type {Array<td.DeclarationReflection>} */
  const signatureContainers = [];

  // First, separate by whether or not the reflection is tagged with @include
  for (const refl of Object.values(project.reflections)) {
    if (
      !(
        refl instanceof td.DeclarationReflection ||
        refl instanceof td.SignatureReflection
      )
    ) {
      continue;
    }

    // Never filter the project itself, or an entry point.
    if (refl.kindOf(td.ReflectionKind.Project | td.ReflectionKind.Module)) {
      continue;
    }

    // Handle these at the end, they don't have comments and should be removed if all signatures will be removed
    if (refl instanceof td.DeclarationReflection && refl.signatures) {
      signatureContainers.push(refl);
      continue;
    }

    if (refl.comment?.hasModifier("@include")) {
      retain.push(refl);
    } else {
      toRemove.add(refl);
    }
  }

  const visitor = td.makeRecursiveVisitor({
    reference(type) {
      if (type.reflection) {
        retain.push(type.reflection);
      }
    },
  });

  // Now, for anything we're keeping, loop through its children and type references
  // and remove them from the list of things to remove.
  let refl = retain.shift();
  while (refl) {
    toRemove.delete(refl);
    refl.comment?.removeModifier("@include");

    if (refl instanceof td.DeclarationReflection) {
      refl.type?.visit(visitor);
      retain.push(...(refl.children || []));
      retain.push(...(refl.typeParameters || []));
    } else if (refl instanceof td.SignatureReflection) {
      refl.type?.visit(visitor);
      retain.push(...(refl.parameters || []));
      retain.push(...(refl.typeParameters || []));
    } else if (refl instanceof td.ParameterReflection) {
      refl.type?.visit(visitor);
    } else if (refl instanceof td.TypeParameterReflection) {
      refl.type?.visit(visitor);
      refl.default?.visit(visitor);
    }

    refl = retain.shift();
  }

  // Now handle declarations which contain signatures, and remove them if
  // we're removing all the signatures.
  for (const refl of signatureContainers) {
    if (refl.signatures?.every((sig) => toRemove.has(sig))) {
      toRemove.add(refl);
    }
  }

  return toRemove;
}
yannick-bonnefond wrote this answer on 2023-03-05

Awesome!
that's perfect thanks so much @Gerrit0 !

More Details About Repo
Owner Name TypeStrong
Repo Name typedoc
Full Name TypeStrong/typedoc
Language TypeScript
Created Date 2014-05-24
Updated Date 2023-03-19
Star Count 6487
Watcher Count 68
Fork Count 639
Issue Count 48

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date