diff --git a/compiler/src/dmd/cli.d b/compiler/src/dmd/cli.d index 9d5afd5d8103..1a5f2f134ca2 100644 --- a/compiler/src/dmd/cli.d +++ b/compiler/src/dmd/cli.d @@ -904,7 +904,7 @@ dmd -cov -unittest myprog.d Option("vcolumns", "print character (column) numbers in diagnostics" ), - Option("verror-style=[digitalmars|gnu|sarif]", + Option("verror-style=[digitalmars|gnu|sarif|lsp]", "set the style for file/line number annotations on compiler messages", `Set the style for file/line number annotations on compiler messages, where: @@ -912,6 +912,7 @@ dmd -cov -unittest myprog.d $(DT digitalmars)$(DD 'file(line[,column]): message'. This is the default.) $(DT gnu)$(DD 'file:line[:column]: message', conforming to the GNU standard used by gcc and clang.) $(DT sarif)$(DD 'Generates JSON output conforming to the SARIF (Static Analysis Results Interchange Format) standard, useful for integration with tools like GitHub and other SARIF readers.') + $(DT lsp)$(DD 'Generates JSON output conforming to Language Server Protocol(LSP) standard.') )`, ), Option("verror-supplements=", diff --git a/compiler/src/dmd/errors.d b/compiler/src/dmd/errors.d index 3a0e8fb3ecfb..8c90668fbf55 100644 --- a/compiler/src/dmd/errors.d +++ b/compiler/src/dmd/errors.d @@ -47,6 +47,7 @@ struct Diagnostic SourceLoc loc; // The location in the source code where the diagnostic was generated (includes file, line, and column). string message; // The text of the diagnostic message, describing the issue. ErrorKind kind; // The type of diagnostic, indicating whether it is an error, warning, deprecation, etc. + bool supplemental; // true if supplemental message } __gshared Diagnostic[] diagnostics = []; @@ -98,11 +99,12 @@ class ErrorSinkCompiler : ErrorSink void plugSink() { // Exit if there are no collected diagnostics - if (!diagnostics.length) return; - + if (!diagnostics.length) return; // Generate the SARIF report with the current diagnostics - generateSarifReport(false); - + if(global.params.v.messageStyle == MessageStyle.lsp) + generateLSPArray(); + else + generateSarifReport(false); // Clear diagnostics after generating the report diagnostics.length = 0; } @@ -562,6 +564,11 @@ private extern(C++) void vreportDiagnostic(const SourceLoc loc, const(char)* for case ErrorKind.message: OutBuffer tmp; + if (global.params.v.messageStyle == MessageStyle.lsp) + { + addLSPDiagnostic(info.loc,format,ap,ErrorKind.message,info.supplemental); + return; + } writeSourceLoc(tmp, info.loc, Loc.showColumns, Loc.messageStyle); if (tmp.length) fprintf(stdout, "%s: ", tmp.extractChars()); @@ -611,7 +618,9 @@ private extern(C++) void vsupplementalDiagnostic(const SourceLoc loc, const(char info.headerColor = Classification.gagged; } else + { info.headerColor = Classification.error; + } printDiagnostic(format, ap, info); return; @@ -651,6 +660,11 @@ private extern(C++) void vsupplementalDiagnostic(const SourceLoc loc, const(char */ private void printDiagnostic(const(char)* format, va_list ap, ref DiagnosticContext info) { + if(global.params.v.messageStyle == MessageStyle.lsp) + { + addLSPDiagnostic(info.loc,format,ap,info.kind,info.supplemental); + return; + } const(char)* header; // title of error message if (info.supplemental) header = " "; @@ -799,6 +813,142 @@ unittest ); } +/** +Adds a LSP diagnostic entry to the diagnostics list. + +Formats a diagnostic message and appends it to the global diagnostics array, allowing errors, warnings, or other diagnostics to be captured in LSP format. + +Params: + loc = The location in the source code where the diagnostic was generated (includes file, line, and column). + format = The printf-style format string for the diagnostic message. + ap = The variadic argument list containing values to format into the diagnostic message. + kind = The type of diagnostic, indicating whether it is an error, warning, deprecation, etc. +*/ +private void addLSPDiagnostic(const SourceLoc loc, const(char)* format, va_list ap, ErrorKind kind, bool isSupplemental) nothrow +{ + char[2048] buffer; + int written = vsnprintf(buffer.ptr, buffer.length, format, ap); + + // Handle any truncation + string formattedMessage = cast(string) buffer[0 .. (written < 0 || written > buffer.length ? buffer.length : written)].dup; + + // Add the Diagnostic to the global diagnostics array + diagnostics ~= Diagnostic(loc, formattedMessage, kind, isSupplemental); +} + +/** + * Just print to stdout in LSP format, doesn't care about gagging. + * (format,ap) text within backticks gets syntax highlighted. + * Params: + * format = printf-style format specification + * ap = printf-style variadic arguments + * info = context of error + */ + +private void printLSPDiagnostic(ref OutBuffer tmp, Diagnostic diag) +{ + const(char)* severity = ""; // title of error message + if (diag.supplemental) + { + tmp.writestring("\"supplemental\":\""); + tmp.writestring(diag.message); + tmp.writestring("\""); + } + else + { + final switch (diag.kind) + { + case ErrorKind.error: severity = "Error"; break; + case ErrorKind.deprecation: severity = "Deprecation"; break; + case ErrorKind.warning: severity = "Warning"; break; + case ErrorKind.tip: severity = "Tip"; break; + case ErrorKind.message: assert(0); + } + + /// Print line number + tmp.writestringln("\"range\":{"); + ++tmp.level; + tmp.writestring("\"start\":{\"line\":"); + tmp.printf(`'%d',`,diag.loc.linnum); + tmp.writestring("\"character:\""); + tmp.printf(`'%d'`,diag.loc.charnum); + tmp.writestringln("},"); + tmp.writestring("\"end\":{\"line\":"); + tmp.printf(`'%d',`,diag.loc.linnum+1); + tmp.writestring("\"character:'0'\""); + tmp.writestringln("}"); + --tmp.level; + tmp.writestringln("}"); + + /// Print diagnostic severity (i.e. errors, warnings etc.) + tmp.writestring("\"severity\":\""); + tmp.writestring(severity); + tmp.writestringln("\","); + + /// Print filename + tmp.writestring("\"uri\":\""); + tmp.writestring(diag.loc.filename); + tmp.writestringln("\","); + + /// Print error message + tmp.writestring("\"description\":\""); + tmp.writestring(diag.message); + tmp.writestring("\""); + } +} + +/** + * Generate LSP report containing LSP objects in a diagnostic array + */ + +void generateLSPArray(bool flag = false) nothrow +{ + if(flag) + { + fputs("Diagnostics {}",stdout); + return; + } + OutBuffer tmp; + tmp.doindent = true; + tmp.spaces = true; + tmp.writestringln("Diagnostics ["); + ++tmp.level; + foreach(idx, diag; diagnostics) + { + if(!diag.supplemental) + { + tmp.writestringln("{"); + ++tmp.level; + } + printLSPDiagnostic(tmp,diag); + if(idx < diagnostics.length-1) + { + if(diagnostics[idx+1].supplemental) + { + tmp.writestringln(","); + } + else + { + tmp.writestringln(""); + --tmp.level; + tmp.writestringln("},"); + } + } + else + { + tmp.writestringln(""); + --tmp.level; + tmp.writestringln("}"); + } + } + --tmp.level; + tmp.writestring("]"); + const(char)* LSPOutput = tmp.extractChars(); + fputs(LSPOutput,stdout); + fflush(stdout); // ensure it gets written out in case of compiler aborts +} + + /** * The type of the fatal error handler * Returns: true if error handling is done, false to do exit(EXIT_FAILURE) diff --git a/compiler/src/dmd/errorsink.d b/compiler/src/dmd/errorsink.d index 5793ef1c0e5e..c4e9e36d0c2d 100644 --- a/compiler/src/dmd/errorsink.d +++ b/compiler/src/dmd/errorsink.d @@ -208,4 +208,5 @@ class ErrorSinkStderr : ErrorSink } void vdeprecationSupplemental(Loc loc, const(char)* format, va_list ap) { } -} + +} \ No newline at end of file diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index aaa10aad249c..f9ae6ecea01b 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -376,6 +376,7 @@ enum class MessageStyle : uint8_t digitalmars = 0u, gnu = 1u, sarif = 2u, + lsp = 3u, }; struct SourceLoc final diff --git a/compiler/src/dmd/globals.h b/compiler/src/dmd/globals.h index f1aa547d8920..a991b4231e1b 100644 --- a/compiler/src/dmd/globals.h +++ b/compiler/src/dmd/globals.h @@ -35,7 +35,8 @@ enum class MessageStyle : unsigned char { digitalmars, // file(line,column): message gnu, // file:line:column: message - sarif // JSON SARIF output, see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html + sarif, // JSON SARIF output, see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html + lsp // Output errors in JSON LSP format }; // The state of array bounds checking diff --git a/compiler/src/dmd/location.d b/compiler/src/dmd/location.d index daff06f3d2c6..74856ed03354 100644 --- a/compiler/src/dmd/location.d +++ b/compiler/src/dmd/location.d @@ -23,7 +23,8 @@ enum MessageStyle : ubyte { digitalmars, /// filename.d(line): message gnu, /// filename.d:line: message, see https://www.gnu.org/prep/standards/html_node/Errors.html - sarif /// JSON SARIF output, see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html + sarif, /// JSON SARIF output, see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html + lsp /// JSON output in Language Server protocol format } /** A source code location @@ -228,6 +229,8 @@ void writeSourceLoc(ref OutBuffer buf, case MessageStyle.sarif: // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html // No formatting needed here for SARIF break; + case MessageStyle.lsp: // lsp-style error messages + break; } } diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 74aadb482f69..040fc983dbff 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -185,6 +185,11 @@ private int tryMain(const(char)[][] argv, out Param params) { generateSarifReport(true); } + + if (global.errors == 0 && global.params.v.messageStyle == MessageStyle.lsp) + { + generateLSPArray(true); + } } target.setTargetBuildDefaults(); diff --git a/compiler/src/dmd/mars.d b/compiler/src/dmd/mars.d index 714d2af78717..79e011210b57 100644 --- a/compiler/src/dmd/mars.d +++ b/compiler/src/dmd/mars.d @@ -1114,8 +1114,11 @@ bool parseCommandLine(const ref Strings arguments, const size_t argc, out Param case "sarif": params.v.messageStyle = MessageStyle.sarif; break; + case "lsp": + params.v.messageStyle = MessageStyle.lsp; + break; default: - error("unknown error style '%.*s', must be 'digitalmars', 'gnu', or 'sarif'", cast(int) style.length, style.ptr); + error("unknown error style '%.*s', must be 'digitalmars', 'gnu', 'sarif' or 'lsp'", cast(int) style.length, style.ptr); } } else if (startsWith(p + 1, "target")) diff --git a/compiler/test/fail_compilation/diag_lsp.d b/compiler/test/fail_compilation/diag_lsp.d new file mode 100644 index 000000000000..af2384ba897c --- /dev/null +++ b/compiler/test/fail_compilation/diag_lsp.d @@ -0,0 +1,23 @@ +/* +REQUIRED_ARGS: -verror-style=lsp +TEST_OUTPUT: +--- +{ + "severity":"Error", + "uri":"fail_compilation/diag21979.d", + "line:":27, + "column":12, + "description":"return value `"hi"` of type `string` does not match return type `int`, and cannot be implicitly converted", +} +--- +*/ + +int num(int a) +{ + return "hi"; +} +void main() +{ + int ch = num(5); + writeln(ch); +} \ No newline at end of file