# test.rules/codemods/predicates/binding.predicate.test.ts

> 52 lines of code and 0 definitions.

Tree: Codebase Testing
Language: typescript
Canonical: https://banes-lab.com/anatomy/testing#file-testing-test-rules-codemods-predicates-binding-predicate-test-ts
Source text: https://banes-lab.com/source/testing/test.rules/codemods/predicates/binding.predicate.test.ts.txt

Listed in [test.rules/codemods/predicates](https://banes-lab.com/api/source/testing/test.rules/codemods/predicates.md).

## Contained in

- [test.rules/codemods/predicates](https://banes-lab.com/anatomy/testing/folder-testing-test-rules-codemods-predicates.md)

## Source

```typescript
import { describe, expect, it } from "vitest";
import type { BindingContext } from "@ssot/govlab/types/analyzer.types.ts";
import { sep } from "node:path";
import ts from "typescript";
import { unsafeReason } from "@ssot/govlab/codemods/predicates/binding.predicate.ts";

const FILE = `${process.cwd().split(sep).join("/")}/probe.ts`;

const contextFor = function contextFor(code: string): { ctx: BindingContext; method: ts.MethodDeclaration } {
    const source = ts.createSourceFile(FILE, code, ts.ScriptTarget.Latest, true);
    const host: ts.CompilerHost = {
        fileExists: (name) => name === FILE,
        getCanonicalFileName: (name) => name,
        getCurrentDirectory: () => process.cwd(),
        getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
        getNewLine: () => "\n",
        getSourceFile: (name) => (name === FILE ? source : undefined),
        readFile: (name) => (name === FILE ? code : undefined),
        useCaseSensitiveFileNames: () => true,
        writeFile: () => {},
    };
    const program = ts.createProgram({ host, options: {}, rootNames: [FILE] });
    const resolved = program.getSourceFile(FILE) ?? source;
    const classNode = resolved.statements.findLast((statement) => ts.isClassDeclaration(statement));
    if (classNode === undefined || !ts.isClassDeclaration(classNode)) {
        throw new Error("fixture declares no class");
    }
    const method = classNode.members.find((member) => ts.isMethodDeclaration(member));
    if (method === undefined || !ts.isMethodDeclaration(method)) {
        throw new Error("fixture declares no method");
    }
    return {
        ctx: { checker: program.getTypeChecker(), classNode, overrides: new Map(), sourceFile: resolved },
        method,
    };
};

describe("unsafeReason", () => {
    it("clears a plain method with a body", () => {
        const { ctx, method } = contextFor("class Job { run() {} }");
        expect(unsafeReason(ctx, method, "run")).toBeNull();
    });

    it("names the shape that blocks binding", () => {
        const generator = contextFor("class Job { *run() {} }");
        expect(unsafeReason(generator.ctx, generator.method, "run")).toBe("generator");
        const isStatic = contextFor("class Job { static run() {} }");
        expect(unsafeReason(isStatic.ctx, isStatic.method, "run")).toBe("static, abstract or declared member");
    });

    it("refuses a member that calls super or is read by an earlier field initializer", () => {
        const withSuper = contextFor("class Base { run() {} }\nclass Job extends Base { run() { super.run(); } }");
        expect(unsafeReason(withSuper.ctx, withSuper.method, "run")).toBe("member calls super");
        const early = contextFor("class Job { early = this.run(); run() {} }");
        expect(unsafeReason(early.ctx, early.method, "run")).toBe("read by an earlier field initializer");
    });
});
```
