All files / TRANSPILE/2-Plan DeclarationPlan.ts

100% Statements 4/4
100% Branches 4/4
100% Functions 3/3
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76                                                                                          1233x                                           1240x 1842x   1240x          
/**
 * 2.2 Plan -- decides what this file's declarations look like, before any is
 * rendered.
 *
 * The companion to `EmissionPlan`, which decides includes and helpers from
 * facts the declarations raise WHILE rendering. Both answers here depend on
 * nothing that rendering produces, so they are settled first and Render reads
 * them instead of interpreting the state they came from.
 *
 * ## It takes the shape of the file, not the file
 *
 * `callbackTypedefsPrecede` is stated over a list of KINDS because that is all
 * the decision needs. #1317 records that 86 modules outside `logic/parser/`
 * import ANTLR contexts with no gate confining them, and a pass outside the
 * parse layer reaching for the tree to answer a question about ORDER would be
 * one more. So Render says what the file looks like and Plan says where the
 * block goes; neither needs the other's inputs.
 *
 * ## Why this absorbed `DeclarationOrder` rather than calling it
 *
 * The order decision lived in its own module, and adding a plan that called it
 * made a THIRD file reasoning about declaration kinds -- which
 * `render-decides-nothing.test.ts` caught immediately, asserting there are
 * exactly two: the decider and the classifier that produces the kinds. The
 * guard was right. ยง1 gives 2.2 Plan "declarations and order" as one
 * responsibility, and splitting the decider from the plan that carries its
 * answer is two modules for one job -- so the decider is the plan, and the
 * guard still counts two files rather than being widened to three.
 */
 
import type IDeclarationPlan from "../../types/IDeclarationPlan";
import type TDeclarationKind from "../../types/TDeclarationKind";
import HeaderOwnership from "./HeaderOwnership";
 
class DeclarationPlan {
  /**
   * Decide this file's declaration emission.
   *
   * @param kinds every declaration in the file, in source order
   * @param selfIncludeAdded whether this file emitted `#include "own.h"`
   */
  static build(
    kinds: readonly TDeclarationKind[],
    selfIncludeAdded: boolean,
  ): IDeclarationPlan {
    return Object.freeze({
      headerOwnsTypeDefinitions:
        HeaderOwnership.ownsDeclarations(selfIncludeAdded),
      callbackTypedefsPrecede: DeclarationPlan.callbackTypedefsPrecede(kinds),
    });
  }
 
  /**
   * The index of the declaration the callback typedef block precedes, or null
   * to place it last.
   *
   * ADR-029 typedefs must come after the type declarations they may name and
   * before the first function that may use one (#1212). A file with no function
   * and no scope has nothing for them to precede, and they go at the end --
   * where they are still after every type, which is the half of the rule that
   * still applies.
   *
   * @param kinds every declaration in the file, in source order
   */
  static callbackTypedefsPrecede(
    kinds: readonly TDeclarationKind[],
  ): number | null {
    const at = kinds.findIndex(
      (kind) => kind === "function" || kind === "scope",
    );
    return at === -1 ? null : at;
  }
}
 
export default DeclarationPlan;