Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved diagnostic rendering #42282

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5040e37
Improve diagnostic rendering for simple cases
RadCod3 Jan 30, 2024
2b8d0aa
Handling tab chars in user code
RadCod3 Jan 31, 2024
ff06eb0
Add caret underline for multi lined diagnostics
RadCod3 Feb 1, 2024
35c2d0b
Filter duplicate diagnostics
RadCod3 Feb 2, 2024
c9f3e6c
Change tree traversal approach
RadCod3 Feb 9, 2024
b302f00
Move classes to shell-cli to access jansi
RadCod3 Feb 9, 2024
ccbeb66
Truncate singleline diagnostics by terminal width
RadCod3 Feb 12, 2024
596c32f
Change tree traversal to direct line access
RadCod3 Feb 12, 2024
b7fa5b0
Handle extreme terminal window sizes
RadCod3 Feb 13, 2024
38954d2
Use existing syntaxtree, handle bal build projects
RadCod3 Feb 15, 2024
41c93e3
Bump jline version to access jansi from within it
RadCod3 Feb 16, 2024
4282517
Pass missing tokens through diagnosticproperties
RadCod3 Feb 21, 2024
88664d6
Clean up
RadCod3 Feb 21, 2024
2a70ade
Move back to ballerina-cli, show error code
RadCod3 Feb 27, 2024
8742f41
Annotate errors in module tests
RadCod3 Feb 27, 2024
66949c3
Fix padding before colon when single digit
RadCod3 Feb 27, 2024
dcc8dfa
Add license header and class comment
RadCod3 Feb 28, 2024
376e1dd
Fix checkstyle fail and add review suggestions
RadCod3 Feb 28, 2024
44dfb41
Update jline version for test compatibility
RadCod3 Mar 3, 2024
bdc5d5b
Move usage of jansi to AnnotateDiagnostics class
RadCod3 Mar 3, 2024
f3d32d8
Add option to disable color for tests
RadCod3 Mar 3, 2024
7e89bc3
Update ballerina-cli tests for new diagnostics
RadCod3 Mar 4, 2024
53d30d3
Fix to pass jballerina-integration-test
RadCod3 Mar 5, 2024
d20fec1
Update testerina-tests for new diagnostics
RadCod3 Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/ballerina-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ configurations {

dependencies {

implementation project(':ballerina-parser')
implementation project(':ballerina-lang')
implementation project(':ballerina-runtime')
implementation project(':ballerina-tools-api')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

package io.ballerina.cli.task;

import io.ballerina.cli.utils.AnnotateDiagnostics;
import io.ballerina.cli.utils.BuildTime;
import io.ballerina.projects.CodeGeneratorResult;
import io.ballerina.projects.CodeModifierResult;
import io.ballerina.projects.Document;
import io.ballerina.projects.JBallerinaBackend;
import io.ballerina.projects.JvmTarget;
import io.ballerina.projects.ModuleName;
import io.ballerina.projects.Package;
import io.ballerina.projects.PackageCompilation;
import io.ballerina.projects.PackageResolution;
import io.ballerina.projects.Project;
Expand All @@ -42,8 +46,12 @@
import org.wso2.ballerinalang.util.RepoUtils;

import java.io.PrintStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.ballerina.cli.launcher.LauncherUtils.createLauncherException;
Expand All @@ -56,6 +64,7 @@
* @since 2.0.0
*/
public class CompileTask implements Task {

private final transient PrintStream out;
private final transient PrintStream err;
private final boolean compileForBalPack;
Expand Down Expand Up @@ -208,14 +217,39 @@ public void execute(Project project) {
if (project.buildOptions().dumpBuildTime()) {
BuildTime.getInstance().codeGenDuration = System.currentTimeMillis() - start;
}
// HashSet to keep track of the diagnostics to avoid duplicate diagnostics
Set<String> diagnosticSet = new HashSet<>();
// HashMap for documents based on filename
Map<String, Document> documentMap = new HashMap<>();
int terminalWidth = AnnotateDiagnostics.getTerminalWidth();
boolean colorEnabled = terminalWidth != 0;

Package currentPackage = project.currentPackage();
currentPackage.moduleIds().forEach(moduleId -> {
currentPackage.module(moduleId).documentIds().forEach(documentId -> {
Document document = currentPackage.module(moduleId).document(documentId);
documentMap.put(getDocumentPath(document.module().moduleName(), document.name()), document);
});
currentPackage.module(moduleId).testDocumentIds().forEach(documentId -> {
Document document = currentPackage.module(moduleId).document(documentId);
documentMap.put(getDocumentPath(document.module().moduleName(), document.name()), document);
});
});
// Report package compilation and backend diagnostics
diagnostics.addAll(jBallerinaBackend.diagnosticResult().diagnostics(false));
diagnostics.forEach(d -> {
if (d.diagnosticInfo().code() == null || (!d.diagnosticInfo().code().equals(
ProjectDiagnosticErrorCode.BUILT_WITH_OLDER_SL_UPDATE_DISTRIBUTION.diagnosticId()) &&
!d.diagnosticInfo().code().startsWith(TOOL_DIAGNOSTIC_CODE_PREFIX))) {
err.println(d);
if (diagnosticSet.add(d.toString())) {
Document document = documentMap.get(d.location().lineRange().fileName());
if (document != null) {
err.println(AnnotateDiagnostics.renderDiagnostic(d, document,
terminalWidth == 0 ? 999 : terminalWidth, colorEnabled));
} else {
err.println(AnnotateDiagnostics.renderDiagnostic(d, colorEnabled));
}
}
}
});
// Report build tool execution diagnostics
Expand All @@ -239,6 +273,13 @@ public void execute(Project project) {
}
}

private String getDocumentPath(ModuleName moduleName, String documentName) {
if (moduleName.isDefaultModuleName()) {
return documentName;
}
return Paths.get("modules", moduleName.moduleNamePart(), documentName).toString();
}

private boolean isPackCmdForATemplatePkg(Project project) {
return compileForBalPack && project.currentPackage().manifest().template();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package io.ballerina.cli.utils;

import io.ballerina.compiler.internal.diagnostics.StringDiagnosticProperty;
import io.ballerina.projects.Document;
import io.ballerina.projects.internal.PackageDiagnostic;
import io.ballerina.tools.diagnostics.Diagnostic;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;
import io.ballerina.tools.diagnostics.Location;
import io.ballerina.tools.text.TextDocument;
import org.jline.jansi.Ansi;
import org.jline.terminal.TerminalBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static io.ballerina.cli.utils.DiagnosticAnnotation.NEW_LINE;
import static io.ballerina.cli.utils.DiagnosticAnnotation.SEVERITY_COLORS;
import static io.ballerina.cli.utils.DiagnosticAnnotation.getColoredString;

/**
* This class is used to generate diagnostic annotations from diagnostics.
*
* @since 2201.9.0
*/
public class AnnotateDiagnostics {

Check warning on line 44 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L44

Added line #L44 was not covered by tests

private static final String COMPILER_ERROR_PREFIX = "BCE";
private static final int SYNTAX_ERROR_CODE_THRESHOLD = 1000;
private static final int MISSING_TOKEN_KEYWORD_CODE_THRESHOLD = 400;
private static final int INVALID_TOKEN_CODE = 600;

public static Ansi renderDiagnostic(Diagnostic diagnostic, Document document, int terminalWidth,
boolean colorEnabled) {

String diagnosticCode = diagnostic.diagnosticInfo().code();
if (diagnostic instanceof PackageDiagnostic && diagnosticCode != null &&
diagnosticCode.startsWith(COMPILER_ERROR_PREFIX)) {
int diagnosticCodeNumber = Integer.parseInt(diagnosticCode.substring(3));
if (diagnosticCodeNumber < SYNTAX_ERROR_CODE_THRESHOLD) {
PackageDiagnostic packageDiagnostic = (PackageDiagnostic) diagnostic;
return Ansi.ansi()
.render(diagnosticToString(diagnostic, colorEnabled) + NEW_LINE + getSyntaxDiagnosticAnnotation(
document, packageDiagnostic, diagnosticCodeNumber, terminalWidth, colorEnabled));
}
}
DiagnosticAnnotation diagnosticAnnotation = getDiagnosticLineFromSyntaxAPI(
document, diagnostic.location(), diagnostic.diagnosticInfo().severity(), terminalWidth, colorEnabled);
return Ansi.ansi().render(diagnosticToString(diagnostic, colorEnabled) + NEW_LINE + diagnosticAnnotation);

}

public static int getTerminalWidth() {
try {
return TerminalBuilder.builder().dumb(true).build().getWidth();
} catch (IOException e) {
return 999;

Check warning on line 75 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L74-L75

Added lines #L74 - L75 were not covered by tests
}
}

public static Ansi renderDiagnostic(Diagnostic diagnostic, boolean colorEnabled) {
return Ansi.ansi().render(diagnosticToString(diagnostic, colorEnabled));
}

private static String diagnosticToString(Diagnostic diagnostic, boolean colorEnabled) {
DiagnosticSeverity severity = diagnostic.diagnosticInfo().severity();
String severityString = severity.toString();
String color = SEVERITY_COLORS.get(severity);
String message = diagnostic.toString().substring(severityString.length());
String code = diagnostic.diagnosticInfo().code();
boolean isMultiline = diagnostic.message().contains(NEW_LINE);
String formatString = getColoredString("%s", color, colorEnabled) + "%s" +
(code != null ? (isMultiline ? NEW_LINE + "(%s)" : " (%s)") : "");

return String.format(formatString, severityString, message, code != null ? code : "");
}

private static DiagnosticAnnotation getDiagnosticLineFromSyntaxAPI(Document document, Location location,
DiagnosticSeverity severity, int terminalWidth,
boolean colorEnabled) {
TextDocument textDocument = document.textDocument();
int startOffset = location.lineRange().startLine().offset();
int endOffset = location.lineRange().endLine().offset();
int startLine = location.lineRange().startLine().line();
int endLine = location.lineRange().endLine().line();
boolean isMultiline = startLine != endLine;
int length = isMultiline ? textDocument.line(startLine).length() - startOffset : endOffset - startOffset;

return new DiagnosticAnnotation(
getLines(textDocument, startLine, endLine),
startOffset,
length == 0 ? 1 : length,
isMultiline,
endOffset,
startLine + 1,
severity,
DiagnosticAnnotation.DiagnosticAnnotationType.REGULAR,
terminalWidth, colorEnabled);
}

private static DiagnosticAnnotation getSyntaxDiagnosticAnnotation(Document document,
PackageDiagnostic packageDiagnostic,
int diagnosticCode, int terminalWidth,
boolean colorEnabled) {
TextDocument textDocument = document.textDocument();
Location location = packageDiagnostic.location();
int startLine = location.lineRange().startLine().line();
int startOffset = location.lineRange().startLine().offset();
int padding = 0;
int endLine = location.lineRange().endLine().line();
int endOffset = location.lineRange().endLine().offset();
String color = SEVERITY_COLORS.get(DiagnosticSeverity.ERROR);

if (diagnosticCode < MISSING_TOKEN_KEYWORD_CODE_THRESHOLD) {
StringDiagnosticProperty strProperty = (StringDiagnosticProperty) packageDiagnostic.properties().get(0);
String lineString = textDocument.line(startLine).text();
String missingTokenString = getColoredString(strProperty.value(), color, colorEnabled);

Check warning on line 135 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L133-L135

Added lines #L133 - L135 were not covered by tests
if (startOffset < lineString.length() && lineString.charAt(startOffset) != ' ') {
missingTokenString = missingTokenString + " ";

Check warning on line 137 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L137

Added line #L137 was not covered by tests
}
if (startOffset > 0 && lineString.charAt(startOffset - 1) != ' ') {
missingTokenString = " " + missingTokenString;
padding++;

Check warning on line 141 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L140-L141

Added lines #L140 - L141 were not covered by tests
}

String lineWithMissingToken = lineString.substring(0, startOffset) + missingTokenString +
lineString.substring(startOffset);
List<String> lines = new ArrayList<>();
lines.add(lineWithMissingToken);
return new DiagnosticAnnotation(

Check warning on line 148 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L144-L148

Added lines #L144 - L148 were not covered by tests
lines,
padding + startOffset,
strProperty.value().length(),

Check warning on line 151 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L151

Added line #L151 was not covered by tests
false,
0,
startLine + 1,
DiagnosticSeverity.ERROR,
DiagnosticAnnotation.DiagnosticAnnotationType.MISSING,
terminalWidth, colorEnabled);
}

if (diagnosticCode == INVALID_TOKEN_CODE) {
List<String> lines = getLines(textDocument, startLine, endLine);
if (lines.size() > 1) {
String annotatedLine1 = lines.get(0).substring(0, startOffset) +
getColoredString(lines.get(0).substring(startOffset), color, colorEnabled);
String annotatedLine2 =
getColoredString(lines.get(lines.size() - 1).substring(0, endOffset), color, colorEnabled) +
lines.get(lines.size() - 1).substring(endOffset);
lines.set(0, annotatedLine1);
lines.set(lines.size() - 1, annotatedLine2);
return new DiagnosticAnnotation(

Check warning on line 170 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L163-L170

Added lines #L163 - L170 were not covered by tests
lines,
startOffset,
textDocument.line(startLine).length() - location.lineRange().startLine().offset(),

Check warning on line 173 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L173

Added line #L173 was not covered by tests
true,
endOffset,
startLine + 1,
DiagnosticSeverity.ERROR,
DiagnosticAnnotation.DiagnosticAnnotationType.INVALID,
terminalWidth, colorEnabled);
}
String line = lines.get(0);
String annotatedLine = line.substring(0, startOffset) +
getColoredString(line.substring(startOffset, endOffset), color, colorEnabled) +
line.substring(endOffset);
lines.set(0, annotatedLine);
return new DiagnosticAnnotation(
lines,
startOffset,
endOffset - startOffset,
false,
0,
startLine + 1,
DiagnosticSeverity.ERROR,
DiagnosticAnnotation.DiagnosticAnnotationType.INVALID,
terminalWidth, colorEnabled);
}
return getDiagnosticLineFromSyntaxAPI(document, location, DiagnosticSeverity.ERROR, terminalWidth,

Check warning on line 197 in cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java

View check run for this annotation

Codecov / codecov/patch

cli/ballerina-cli/src/main/java/io/ballerina/cli/utils/AnnotateDiagnostics.java#L197

Added line #L197 was not covered by tests
colorEnabled);
}

private static List<String> getLines(TextDocument textDocument, int start, int end) {
List<String> lines = new ArrayList<>();
for (int i = start; i <= end; i++) {
lines.add(textDocument.line(i).text());
}
return lines;
}

}
Loading
Loading