• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #10687

pending completion
#10687

push

web-flow
Print diagnostic related information (#867)

* Fix tab issue when printing diagnostics

* include related info in printed diagnostics

* Fix "....and X more" loop

5587 of 6814 branches covered (81.99%)

Branch coverage included in aggregate %.

18 of 18 new or added lines in 2 files covered. (100.0%)

8499 of 9184 relevant lines covered (92.54%)

1619.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.59
/src/diagnosticUtils.ts
1
import type { Chalk } from 'chalk';
2
import chalk from 'chalk';
1✔
3
import type { BsConfig } from './BsConfig';
4
import { DiagnosticSeverity } from 'vscode-languageserver';
1✔
5
import type { BsDiagnostic } from '.';
6
import type { Range } from 'vscode-languageserver';
7

8
/**
9
 * Prepare print diagnostic formatting options
10
 */
11
export function getPrintDiagnosticOptions(options: BsConfig) {
1✔
12
    let cwd = options?.cwd ? options.cwd : process.cwd();
53!
13

14
    let emitFullPaths = options?.emitFullPaths === true;
53!
15

16
    let diagnosticLevel = options?.diagnosticLevel || 'warn';
53!
17

18
    let diagnosticSeverityMap = {} as Record<string, DiagnosticSeverity>;
53✔
19
    diagnosticSeverityMap.info = DiagnosticSeverity.Information;
53✔
20
    diagnosticSeverityMap.hint = DiagnosticSeverity.Hint;
53✔
21
    diagnosticSeverityMap.warn = DiagnosticSeverity.Warning;
53✔
22
    diagnosticSeverityMap.error = DiagnosticSeverity.Error;
53✔
23

24
    let severityLevel = diagnosticSeverityMap[diagnosticLevel] || DiagnosticSeverity.Warning;
53✔
25
    let order = [DiagnosticSeverity.Information, DiagnosticSeverity.Hint, DiagnosticSeverity.Warning, DiagnosticSeverity.Error];
53✔
26
    let includeDiagnostic = order.slice(order.indexOf(severityLevel)).reduce((acc, value) => {
53✔
27
        acc[value] = true;
110✔
28
        return acc;
110✔
29
    }, {});
30

31
    let typeColor = {} as Record<string, Chalk>;
53✔
32
    typeColor[DiagnosticSeverity.Information] = chalk.blue;
53✔
33
    typeColor[DiagnosticSeverity.Hint] = chalk.green;
53✔
34
    typeColor[DiagnosticSeverity.Warning] = chalk.yellow;
53✔
35
    typeColor[DiagnosticSeverity.Error] = chalk.red;
53✔
36

37
    let severityTextMap = {};
53✔
38
    severityTextMap[DiagnosticSeverity.Information] = 'info';
53✔
39
    severityTextMap[DiagnosticSeverity.Hint] = 'hint';
53✔
40
    severityTextMap[DiagnosticSeverity.Warning] = 'warning';
53✔
41
    severityTextMap[DiagnosticSeverity.Error] = 'error';
53✔
42

43
    return {
53✔
44
        cwd: cwd,
45
        emitFullPaths: emitFullPaths,
46
        severityLevel: severityLevel,
47
        includeDiagnostic: includeDiagnostic,
48
        typeColor: typeColor,
49
        severityTextMap: severityTextMap
50
    };
51
}
52

53
/**
54
 * Format output of one diagnostic
55
 */
56
export function printDiagnostic(
1✔
57
    options: ReturnType<typeof getPrintDiagnosticOptions>,
58
    severity: DiagnosticSeverity,
59
    filePath: string,
60
    lines: string[],
61
    diagnostic: BsDiagnostic,
62
    relatedInformation?: Array<{ range: Range; filePath: string; message: string }>
63
) {
64
    let { includeDiagnostic, severityTextMap, typeColor } = options;
7✔
65

66
    if (!includeDiagnostic[severity]) {
7!
67
        return;
×
68
    }
69

70
    let severityText = severityTextMap[severity];
7✔
71

72
    console.log('');
7✔
73
    console.log(
7✔
74
        chalk.cyan(filePath ?? '<unknown file>') +
21✔
75
        ':' +
76
        chalk.yellow(
77
            diagnostic.range
78
                ? (diagnostic.range.start.line + 1) + ':' + (diagnostic.range.start.character + 1)
7✔
79
                : 'line?:col?'
80
        ) +
81
        ' - ' +
82
        typeColor[severity](severityText) +
83
        ' ' +
84
        chalk.grey('BS' + diagnostic.code) +
85
        ': ' +
86
        chalk.white(diagnostic.message)
87
    );
88
    console.log('');
7✔
89

90
    //Get the line referenced by the diagnostic. if we couldn't find a line,
91
    // default to an empty string so it doesn't crash the error printing below
92
    let diagnosticLine = lines[diagnostic.range?.start?.line ?? -1] ?? '';
7✔
93
    console.log(
7✔
94
        getDiagnosticLine(diagnostic, diagnosticLine, typeColor[severity])
95
    );
96

97
    //print related information if present (only first few rows)
98
    const relatedInfoList = relatedInformation ?? [];
7✔
99
    let indent = '    ';
7✔
100
    for (let i = 0; i < relatedInfoList.length; i++) {
7✔
101
        let relatedInfo = relatedInfoList[i];
×
102
        //only show the first 5 relatedInfo links
103
        if (i < 5) {
×
104
            console.log('');
×
105
            console.log(
×
106
                indent,
107
                chalk.cyan(relatedInfo.filePath ?? '<unknown file>') +
×
108
                ':' +
109
                chalk.yellow(
110
                    relatedInfo.range
111
                        ? (relatedInfo.range.start.line + 1) + ':' + (relatedInfo.range.start.character + 1)
×
112
                        : 'line?:col?'
113
                )
114
            );
115
            console.log(indent, relatedInfo.message);
×
116
        } else {
117
            console.log('\n', indent, `...and ${relatedInfoList.length - i + 1} more`);
×
118
            break;
×
119
        }
120
    }
121
    console.log('');
7✔
122
}
123

124
export function getDiagnosticLine(diagnostic: BsDiagnostic, diagnosticLine: string, colorFunction: Chalk) {
1✔
125
    let result = '';
13✔
126

127
    //only print the line information if we have some
128
    if (diagnostic.range && diagnosticLine) {
13✔
129
        const lineNumberText = chalk.bgWhite(' ' + chalk.black((diagnostic.range.start.line + 1).toString()) + ' ') + ' ';
9✔
130
        const blankLineNumberText = chalk.bgWhite(' ' + chalk.white(' '.repeat((diagnostic.range.start.line + 1).toString().length)) + ' ') + ' ';
9✔
131

132
        //remove tabs in favor of spaces to make diagnostic printing more consistent
133
        let leadingText = diagnosticLine.slice(0, diagnostic.range.start.character);
9✔
134
        let leadingTextNormalized = leadingText.replace(/\t/g, '    ');
9✔
135
        let actualText = diagnosticLine.slice(diagnostic.range.start.character, diagnostic.range.end.character);
9✔
136
        let actualTextNormalized = actualText.replace(/\t/g, '    ');
9✔
137
        let startIndex = leadingTextNormalized.length;
9✔
138
        let endIndex = leadingTextNormalized.length + actualTextNormalized.length;
9✔
139

140
        let diagnosticLineNormalized = diagnosticLine.replace(/\t/g, '    ');
9✔
141

142
        const squigglyText = getDiagnosticSquigglyText(diagnosticLineNormalized, startIndex, endIndex);
9✔
143
        result +=
9✔
144
            lineNumberText + diagnosticLineNormalized + '\n' +
145
            blankLineNumberText + colorFunction(squigglyText);
146
    }
147
    return result;
13✔
148
}
149

150
/**
151
 * Given a diagnostic, compute the range for the squiggly
152
 */
153
export function getDiagnosticSquigglyText(line: string, startCharacter: number, endCharacter: number) {
1✔
154
    let squiggle: string;
155
    //fill the entire line
156
    if (
21✔
157
        //there is no range
158
        typeof startCharacter !== 'number' || typeof endCharacter !== 'number' ||
96✔
159
        //there is no line
160
        !line ||
161
        //both positions point to same location
162
        startCharacter === endCharacter ||
163
        //the diagnostic starts after the end of the line
164
        startCharacter >= line.length
165
    ) {
166
        squiggle = ''.padStart(line?.length ?? 0, '~');
4✔
167
    } else {
168

169
        let endIndex = Math.max(endCharacter, line.length);
17✔
170
        endIndex = endIndex > 0 ? endIndex : 0;
17!
171
        if (line?.length < endIndex) {
17!
172
            endIndex = line.length;
3✔
173
        }
174

175
        let leadingWhitespaceLength = startCharacter;
17✔
176
        let squiggleLength: number;
177
        if (endCharacter === Number.MAX_VALUE) {
17✔
178
            squiggleLength = line.length - leadingWhitespaceLength;
1✔
179
        } else {
180
            squiggleLength = endCharacter - startCharacter;
16✔
181
        }
182
        let trailingWhitespaceLength = endIndex - endCharacter;
17✔
183

184
        //opening whitespace
185
        squiggle =
17✔
186
            ''.padStart(leadingWhitespaceLength, ' ') +
187
            //squiggle
188
            ''.padStart(squiggleLength, '~') +
189
            //trailing whitespace
190
            ''.padStart(trailingWhitespaceLength, ' ');
191

192
        //trim the end of the squiggle so it doesn't go longer than the end of the line
193
        if (squiggle.length > endIndex) {
17✔
194
            squiggle = squiggle.slice(0, endIndex);
2✔
195
        }
196
    }
197
    return squiggle;
21✔
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc