# codemods/analyzers/increment.analyzer.ts

> 71 lines of code and 12 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-codemods-analyzers-increment-analyzer-ts
Source text: https://banes-lab.com/source/governance/codemods/analyzers/increment.analyzer.ts.txt

Listed in [codemods/analyzers](https://banes-lab.com/api/source/governance/codemods/analyzers.md), after [codemods/analyzers/identifier.analyzer.ts](https://banes-lab.com/source/governance/codemods/analyzers/identifier.analyzer.ts.md) and before [codemods/analyzers/literal.analyzer.ts](https://banes-lab.com/source/governance/codemods/analyzers/literal.analyzer.ts.md).

## Definitions

- `findingFor` (lexical_declaration, line 48)
- `visit` (lexical_declaration, line 71, exported)
- `positionReason` (lexical_declaration, line 31)
- `isUpdateExpression` (lexical_declaration, line 14)
- `discardsValue` (lexical_declaration, line 21)
- `operandReason` (lexical_declaration, line 38)
- `scanSourceFile` (lexical_declaration, line 69, exported)
- `STEP` (lexical_declaration, line 5)
- `OPERATORS` (lexical_declaration, line 7)
- `UpdateExpression` (type_alias_declaration, line 12)
- `replacementText` (lexical_declaration, line 65, exported)
- `out` (lexical_declaration, line 70, exported)

## Uses

- [codemods/selectors/program.selector.ts](https://banes-lab.com/source/governance/codemods/selectors/program.selector.ts.md)

## Source

```typescript
import { lineOf, relPath } from "../selectors/program.selector.ts";
import type { IncrementFinding } from "../../types/analyzer.types.ts";
import ts from "typescript";

const STEP = "1";

const OPERATORS: ReadonlyMap<ts.SyntaxKind, string> = new Map([
    [ts.SyntaxKind.PlusPlusToken, "+="],
    [ts.SyntaxKind.MinusMinusToken, "-="],
]);

type UpdateExpression = ts.PostfixUnaryExpression | ts.PrefixUnaryExpression;

const isUpdateExpression = function isUpdateExpression(node: ts.Node): node is UpdateExpression {
    if (!ts.isPostfixUnaryExpression(node) && !ts.isPrefixUnaryExpression(node)) {
        return false;
    }
    return OPERATORS.has(node.operator);
};

const discardsValue = function discardsValue(node: UpdateExpression, parent: ts.Node | null): boolean {
    if (!parent) {
        return false;
    }
    if (ts.isExpressionStatement(parent)) {
        return true;
    }
    return ts.isForStatement(parent) && parent.incrementor === node;
};

const positionReason = function positionReason(node: UpdateExpression, parent: ts.Node | null): string | null {
    if (discardsValue(node, parent)) {
        return null;
    }
    return "the increment's value is read, so '+= 1' would change what the expression evaluates to";
};

const operandReason = function operandReason(operand: ts.Expression): string | null {
    if (ts.isIdentifier(operand) || ts.isPropertyAccessExpression(operand)) {
        return null;
    }
    if (ts.isElementAccessExpression(operand)) {
        return "operand is an element access — rewriting would evaluate the index expression twice";
    }
    return "operand is not a simple reference";
};

const findingFor = function findingFor(
    sourceFile: ts.SourceFile,
    node: UpdateExpression,
    parent: ts.Node | null,
): IncrementFinding {
    return {
        end: node.getEnd(),
        file: relPath(sourceFile.fileName),
        fileName: sourceFile.fileName,
        line: lineOf(sourceFile, node),
        operand: node.operand.getText(sourceFile),
        operator: OPERATORS.get(node.operator) ?? "+=",
        reason: positionReason(node, parent) ?? operandReason(node.operand),
        start: node.getStart(sourceFile),
    };
};

export const replacementText = function replacementText(finding: IncrementFinding): string {
    return `${finding.operand} ${finding.operator} ${STEP}`;
};

export const scanSourceFile = function scanSourceFile(sourceFile: ts.SourceFile): IncrementFinding[] {
    const out: IncrementFinding[] = [];
    const visit = function visit(node: ts.Node, parent: ts.Node | null): void {
        if (isUpdateExpression(node)) {
            out.push(findingFor(sourceFile, node, parent));
        }
        ts.forEachChild(node, (child) => {
            visit(child, node);
        });
    };
    visit(sourceFile, null);
    return out;
};
```
