# core/converters/comment.converter.ts

> 85 lines of code and 25 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-converters-comment-converter-ts
Source text: https://banes-lab.com/source/quality/core/converters/comment.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/quality/core/converters.md), after [core/converters/canon.converter.ts](https://banes-lab.com/source/quality/core/converters/canon.converter.ts.md) and before [core/converters/concern.converter.ts](https://banes-lab.com/source/quality/core/converters/concern.converter.ts.md).

## Definitions

- `lineStartOf` (lexical_declaration, line 5)
- `removeSpans` (lexical_declaration, line 38)
- `trimLeadingWs` (lexical_declaration, line 9)
- `stripBounds` (lexical_declaration, line 25)
- `isLicenseComment` (lexical_declaration, line 53)
- `commentSpans` (lexical_declaration, line 66)
- `removalBounds` (lexical_declaration, line 17)
- `stripComments` (lexical_declaration, line 96, exported)
- `isDirectiveComment` (lexical_declaration, line 58)
- `isOwnLine` (lexical_declaration, line 62)
- `end` (lexical_declaration, line 19)
- `out` (lexical_declaration, line 26)
- `prev` (lexical_declaration, line 27)
- `bounds` (lexical_declaration, line 29)
- `removable` (lexical_declaration, line 39)
- `stripCommentPunctuation` (lexical_declaration, line 45)
- `i` (lexical_declaration, line 46)
- `inner` (lexical_declaration, line 54)
- `root` (lexical_declaration, line 67)
- `start` (lexical_declaration, line 72)
- `lineNumber` (lexical_declaration, line 83)
- `extractComments` (lexical_declaration, line 87, exported)
- `spans` (lexical_declaration, line 88, exported)
- `comments` (lexical_declaration, line 89, exported)
- `stripped` (lexical_declaration, line 92, exported)

## Contained in

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

## Linked from

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

## Source

```typescript
import { COMMENT_PUNCTUATION, LICENSE_MARKERS } from "#configuration/constants/comment.constants";
import type { CommentGrammar, CommentSpan, ExtractResult, StripResult } from "#types/comment.types";
import { commentNodes, parseCodeSync } from "@govlab/code-parse";

const lineStartOf = function lineStartOf(text: string, pos: number): number {
    return text.lastIndexOf("\n", pos - 1) + 1;
};

const trimLeadingWs = function trimLeadingWs(text: string, pos: number): number {
    let i = pos;
    while (i > 0 && (text[i - 1] === " " || text[i - 1] === "\t")) {
        i -= 1;
    }
    return i;
};

const removalBounds = function removalBounds(text: string, span: CommentSpan): { start: number; end: number } {
    if (span.ownLine) {
        const end = span.end < text.length && text[span.end] === "\n" ? span.end + 1 : span.end;
        return { end, start: lineStartOf(text, span.start) };
    }
    return { end: span.end, start: trimLeadingWs(text, span.start) };
};

const stripBounds = function stripBounds(text: string, removable: CommentSpan[]): string {
    let out = "";
    let prev = 0;
    for (const span of removable) {
        const bounds = removalBounds(text, span);
        if (bounds.start >= prev) {
            out += text.slice(prev, bounds.start);
            prev = bounds.end;
        }
    }
    return out + text.slice(prev);
};

const removeSpans = function removeSpans(text: string, spans: CommentSpan[]): StripResult {
    const removable = spans.filter((span) => !span.directive);
    return removable.length === 0
        ? { changed: false, content: text }
        : { changed: true, content: stripBounds(text, removable) };
};

const stripCommentPunctuation = function stripCommentPunctuation(text: string): string {
    let i = 0;
    while (i < text.length && COMMENT_PUNCTUATION.has(text[i] ?? "")) {
        i += 1;
    }
    return text.slice(i);
};

const isLicenseComment = function isLicenseComment(commentText: string): boolean {
    const inner = stripCommentPunctuation(commentText);
    return LICENSE_MARKERS.some((marker) => inner.startsWith(marker));
};

const isDirectiveComment = function isDirectiveComment(commentText: string, prefixes: readonly string[]): boolean {
    return prefixes.some((prefix) => commentText.startsWith(prefix)) || isLicenseComment(commentText);
};

const isOwnLine = function isOwnLine(text: string, start: number): boolean {
    return text.slice(lineStartOf(text, start), start).trim() === "";
};

const commentSpans = function commentSpans(content: string, grammar: CommentGrammar): CommentSpan[] {
    const root = parseCodeSync(content, grammar.lang);
    if (root === null) {
        return [];
    }
    const spans = commentNodes(root).map((node): CommentSpan => {
        const start = node.startIndex ?? 0;
        return {
            directive: isDirectiveComment(node.text ?? "", grammar.directivePrefixes),
            end: node.endIndex ?? start,
            ownLine: isOwnLine(content, start),
            start,
        };
    });
    return spans.toSorted((a, b) => a.start - b.start);
};

const lineNumber = function lineNumber(text: string, pos: number): number {
    return text.slice(0, Math.min(pos, text.length)).split("\n").length;
};

export const extractComments = function extractComments(content: string, grammar: CommentGrammar): ExtractResult {
    const spans = commentSpans(content, grammar);
    const comments = spans
        .filter((span) => !span.directive)
        .map((span) => ({ line: lineNumber(content, span.start), text: content.slice(span.start, span.end) }));
    const stripped = removeSpans(content, spans);
    return { changed: stripped.changed, comments, content: stripped.content };
};

export const stripComments = function stripComments(content: string, grammar: CommentGrammar): StripResult {
    return removeSpans(content, commentSpans(content, grammar));
};
```
