All files / utils TargetResolver.ts

100% Statements 20/20
100% Branches 12/12
100% Functions 3/3
100% Lines 20/20

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 77 78 79 80 81 82 83 84 85 86 87 88                                                  1405x 1383x   22x               1416x 11x 11x 4x     7x 7x 6x     1410x                                     290x 290x 2x     288x 288x 4x 4x         1x     288x          
/**
 * ADR-049: the single place a target name becomes a set of capabilities.
 *
 * Two consumers ask different questions of the same answer. `CodeGenerator` asks
 * per file, at Stage 5, because the capabilities shape the code it emits.
 * `Transpiler` asks once per run, at Stage 4c, because MISRA C:2012 Rule 5.1 is
 * a whole-program property — an identifier budget has to be one number for the
 * whole build, not whichever file happened to generate last (#1307 review).
 *
 * Both go through here so the pragma is parsed once, in one way. Re-deriving the
 * target from source text at the second call site would have produced two
 * decisions that agree only while the corpus stays simple.
 */
 
import type * as Parser from "../transpiler/logic/parser/grammar/CNextParser";
import type ITargetCapabilities from "../transpiler/types/ITargetCapabilities";
import TARGET_CAPABILITIES from "../transpiler/constants/TARGET_CAPABILITIES";
import DEFAULT_TARGET from "../transpiler/constants/DEFAULT_TARGET";
 
class TargetResolver {
  /**
   * Capabilities for a named target, or undefined when the name is unknown.
   * Case-insensitive, matching `#pragma target` and `--target`.
   */
  static byName(name: string | undefined): ITargetCapabilities | undefined {
    if (!name) {
      return undefined;
    }
    return TARGET_CAPABILITIES[name.toLowerCase()];
  }
 
  /**
   * The target named by a file's `#pragma target`, or undefined when the file
   * declares none (or names one this transpiler does not know).
   */
  static fromPragma(tree: Parser.ProgramContext): string | undefined {
    for (const directive of tree.preprocessorDirective()) {
      const pragma = directive.pragmaDirective();
      if (!pragma) {
        continue;
      }
      // PRAGMA_TARGET captures "#pragma target <name>" as a single token.
      const match = /#\s*pragma\s+target\s+(\S+)/i.exec(pragma.getText());
      if (match) {
        return match[1].toLowerCase();
      }
    }
    return undefined;
  }
 
  /**
   * The capabilities a whole build must satisfy.
   *
   * An explicit `--target` names one target for every translation unit, so it
   * wins outright. Otherwise the files may each declare their own `#pragma
   * target`, and a whole-program identifier budget has to hold for all of them:
   * the narrowest budget wins, because an identifier pair that collides for the
   * strictest target in the build collides in that build.
   *
   * @param cliTarget The `--target` flag, if given
   * @param pragmaTargets Target names declared by the build's files
   */
  static forRun(
    cliTarget: string | undefined,
    pragmaTargets: ReadonlyArray<string>,
  ): ITargetCapabilities {
    const fromCli = TargetResolver.byName(cliTarget);
    if (fromCli) {
      return fromCli;
    }
 
    let narrowest = DEFAULT_TARGET;
    for (const name of pragmaTargets) {
      const candidate = TargetResolver.byName(name);
      if (
        candidate &&
        candidate.significantExternalIdentifierChars <
          narrowest.significantExternalIdentifierChars
      ) {
        narrowest = candidate;
      }
    }
    return narrowest;
  }
}
 
export default TargetResolver;