All files / TRANSPILE/1-Analyze DeclarationModifierAnalyzer.ts

100% Statements 13/13
100% Branches 4/4
100% Functions 5/5
100% Lines 12/12

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                                                    461x     461x     447x     447x     3x 3x               450x 4x 4x                         461x 461x 461x          
/**
 * ADR-049 declaration modifiers: E0889.
 *
 * #1322. One throw in `VariableModifierBuilder`, reported as `1:0` with its
 * position built into the message text.
 *
 * `atomic` already implies `volatile` -- it is `volatile` plus the guarantee
 * that a read or write cannot be torn -- so writing both says one of two
 * things, and the author has to be asked which. The rule is entirely
 * syntactic: two modifier tokens on one declaration, which is why it needs no
 * type, no frame and no symbols.
 *
 * Every declaration form is checked. Codegen's copy sat in the builder that
 * file-scope and block declarations share, so it saw both; a `for` header's
 * declaration and a parameter carry the same modifiers in the grammar and are
 * asked here too rather than being assumed unreachable.
 */
 
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
 
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ParserUtils from "../../utils/ParserUtils";
import IDeclarationModifierError from "./types/IDeclarationModifierError";
 
class DeclarationModifierListener extends CNextListener {
  private readonly found: IDeclarationModifierError[] = [];
 
  public errors(): IDeclarationModifierError[] {
    return this.found;
  }
 
  override enterVariableDeclaration = (
    ctx: Parser.VariableDeclarationContext,
  ): void => {
    this.check(ctx, ctx.atomicModifier(), ctx.volatileModifier());
  };
 
  override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
    this.check(ctx, ctx.atomicModifier(), ctx.volatileModifier());
  };
 
  private check(
    at: ParserRuleContext,
    atomic: Parser.AtomicModifierContext | null,
    volatileMod: Parser.VolatileModifierContext | null,
  ): void {
    if (atomic === null || volatileMod === null) return;
    const { line, column } = ParserUtils.getPosition(at);
    this.found.push({
      code: "E0889",
      line,
      column,
      message: "Cannot use both 'atomic' and 'volatile' on one declaration",
      helpText:
        "`atomic` already implies `volatile`, and adds that a read or write cannot be torn. Use `atomic` for a variable an ISR shares, or `volatile` alone for a hardware register or a delay loop (ADR-049).",
    });
  }
}
 
class DeclarationModifierAnalyzer {
  public analyze(tree: Parser.ProgramContext): IDeclarationModifierError[] {
    const listener = new DeclarationModifierListener();
    ParseTreeWalker.DEFAULT.walk(listener, tree);
    return listener.errors();
  }
}
 
export default DeclarationModifierAnalyzer;