# core/parsers/tool.scalastyle.parser.ts

> 78 lines of code and 26 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-parsers-tool-scalastyle-parser-ts
Source text: https://banes-lab.com/source/quality/core/parsers/tool.scalastyle.parser.ts.txt

Listed in [core/parsers](https://banes-lab.com/api/source/quality/core/parsers.md), after [core/parsers/tool.ruff.parser.ts](https://banes-lab.com/source/quality/core/parsers/tool.ruff.parser.ts.md) and before [core/parsers/tool.semgrep.parser.ts](https://banes-lab.com/source/quality/core/parsers/tool.semgrep.parser.ts.md).

## Definitions

- `attr` (lexical_declaration, line 31)
- `unescapeXml` (lexical_declaration, line 27)
- `errorFinding` (lexical_declaration, line 38)
- `stepFrom` (lexical_declaration, line 67)
- `fileStep` (lexical_declaration, line 50)
- `errorStep` (lexical_declaration, line 58)
- `parseScalastyleXml` (lexical_declaration, line 77, exported)
- `TOOL` (lexical_declaration, line 5)
- `FILE_MARKER` (lexical_declaration, line 6)
- `ERROR_MARKER` (lexical_declaration, line 7)
- `QUOTE` (lexical_declaration, line 8)
- `XML_ENTITIES` (lexical_declaration, line 9)
- `ScanState` (interface_declaration, line 17)
- `ScanStep` (interface_declaration, line 22)
- `key` (lexical_declaration, line 32)
- `from` (lexical_declaration, line 33)
- `start` (lexical_declaration, line 51)
- `end` (lexical_declaration, line 52)
- `tagEnd` (lexical_declaration, line 59)
- `stop` (lexical_declaration, line 60)
- `fileIdx` (lexical_declaration, line 68)
- `errIdx` (lexical_declaration, line 69)
- `takeFile` (lexical_declaration, line 73)
- `findings` (lexical_declaration, line 78, exported)
- `initial` (lexical_declaration, line 79, exported)
- `step` (lexical_declaration, line 80, exported)

## Contained in

- [core/parsers](https://banes-lab.com/anatomy/quality/folder-quality-core-parsers.md)

## Uses

- [core/factories/finding.factory.ts](https://banes-lab.com/source/quality/core/factories/finding.factory.ts.md)

## Linked from

- [core/factories](https://banes-lab.com/anatomy/quality/folder-quality-core-factories.md)
- [core/parsers](https://banes-lab.com/anatomy/quality/folder-quality-core-parsers.md)

## Source

```typescript
import type { Finding } from "#types/finding.types";
import { POSITION } from "#configuration/constants/tool.constants";
import { toolFinding } from "#core/factories/finding.factory";

const TOOL = "scalastyle";
const FILE_MARKER = '<file name="';
const ERROR_MARKER = "<error ";
const QUOTE = '"';
const XML_ENTITIES: readonly (readonly [string, string])[] = [
    ["&quot;", '"'],
    ["&lt;", "<"],
    ["&gt;", ">"],
    ["&apos;", "'"],
    ["&amp;", "&"],
];

interface ScanState {
    currentFile: string;
    cursor: number;
}

interface ScanStep {
    finding: Finding | null;
    state: ScanState;
}

const unescapeXml = function unescapeXml(value: string): string {
    return XML_ENTITIES.reduce((text, [entity, char]) => text.split(entity).join(char), value);
};

const attr = function attr(tag: string, name: string): string {
    const key = `${name}="`;
    const from = tag.indexOf(key);
    const end = from === -1 ? -1 : tag.indexOf(QUOTE, from + key.length);
    return end === -1 ? "" : unescapeXml(tag.slice(from + key.length, end));
};

const errorFinding = function errorFinding(tag: string, currentFile: string, ecosystem: string): Finding {
    return toolFinding({
        column: Number(attr(tag, "column")) || POSITION,
        ecosystem,
        file: currentFile,
        line: Number(attr(tag, "line")) || POSITION,
        message: attr(tag, "message"),
        ruleId: attr(tag, "source") || TOOL,
        tool: TOOL,
    });
};

const fileStep = function fileStep(xml: string, fileIdx: number, state: ScanState): ScanStep {
    const start = fileIdx + FILE_MARKER.length;
    const end = xml.indexOf(QUOTE, start);
    return end === -1
        ? { finding: null, state: { ...state, cursor: start } }
        : { finding: null, state: { currentFile: unescapeXml(xml.slice(start, end)), cursor: end + 1 } };
};

const errorStep = function errorStep(xml: string, errIdx: number, state: ScanState, ecosystem: string): ScanStep {
    const tagEnd = xml.indexOf(">", errIdx);
    const stop = tagEnd === -1 ? xml.length : tagEnd;
    return {
        finding: errorFinding(xml.slice(errIdx, stop), state.currentFile, ecosystem),
        state: { currentFile: state.currentFile, cursor: tagEnd === -1 ? xml.length : tagEnd + 1 },
    };
};

const stepFrom = function stepFrom(xml: string, state: ScanState, ecosystem: string): ScanStep | null {
    const fileIdx = xml.indexOf(FILE_MARKER, state.cursor);
    const errIdx = xml.indexOf(ERROR_MARKER, state.cursor);
    if (fileIdx === -1 && errIdx === -1) {
        return null;
    }
    const takeFile = fileIdx !== -1 && (errIdx === -1 || fileIdx < errIdx);
    return takeFile ? fileStep(xml, fileIdx, state) : errorStep(xml, errIdx, state, ecosystem);
};

export const parseScalastyleXml = function parseScalastyleXml(xml: string, ecosystem: string): Finding[] {
    const findings: Finding[] = [];
    const initial: ScanState = { currentFile: "", cursor: 0 };
    let step = stepFrom(xml, initial, ecosystem);
    while (step !== null) {
        if (step.finding !== null) {
            findings.push(step.finding);
        }
        step = stepFrom(xml, step.state, ecosystem);
    }
    return findings;
};
```
