[ 
https://issues.apache.org/jira/browse/GROOVY-12312?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12312:
-------------------------------
    Description: 
Groovy renders every diagnostic across four lines, in a shape that no common 
tool can parse. There is no way to ask for anything terser or more structured.

h2. The current format

{noformat}
X.groovy: 1: The method 'void m()' has invalid modifier final.
 @ line 1, column 15.
   interface X { final void m() }
                 ^
{noformat}

Three problems, beyond sheer size:

* *The location is split across two lines and partly repeated.* The line number 
appears twice, and the column only in the second line, so neither line alone 
identifies the position.
* *There is no severity token.* Nothing in the text says {{error}} or 
{{warning}}; severity is implied by which section of the output the message 
falls under.
* *The shape matches no established convention.* It is neither the gcc/javac 
{{file:line: severity: message}} form nor anything else editors and CI systems 
already understand, so consumers are left regex-guessing. Note that Groovy's 
{{X.groovy: 1: ...}} (spaces around the number, no severity) is close enough to 
javac's {{X.java:1: error: ...}} to invite a parser that then quietly 
mismatches.

Size, measured on a source with 14 static type checking errors: *73 lines / 
3581 bytes*, roughly 256 bytes per diagnostic. For a human reading a terminal 
the snippet and caret earn their place. For a build log, a CI annotation, an 
editor problem matcher, or an LLM-based tool that already has the file open, 
they are pure overhead.

h2. Proposal

Add an error format setting, selecting how diagnostics are rendered:

* {{--error-format=<format>}} on the {{groovyc}} command line
* {{errorFormat}} on {{CompilerConfiguration}}, so compiler configuration 
scripts (and therefore Gradle, via {{groovyOptions.configurationScript}}) can 
reach it
* a matching attribute on the Ant {{<groovyc>}} task

Two formats to begin with, with {{full}} the default so existing output is 
unchanged:

|| Format || Output ||
| {{full}} | the current four-line form, unchanged |
| {{short}} | {{X.groovy:1:15: error: The method 'void m()' has invalid 
modifier final.}} |

A named setting rather than a boolean leaves room for a {{json}} format later 
without adding a second option.

h2. On naming

Deliberately *not* {{-Xdiags:compact}}, despite the surface similarity to 
javac. javac's {{-Xdiags:\{compact,verbose\}}} selects how much explanation is 
given for method-resolution failures, and *both* modes still print the source 
line and caret:

{noformat}
# javac -Xdiags:compact (the default)
Dg.java:3: error: incompatible types: int cannot be converted to String
    void go() { m(1); }
                  ^
{noformat}

Reusing that name would mislead anyone who knows javac. javac's actual 
machine-readable switch is {{-XDrawDiagnostics}}, an internal option for its 
own test harness and not a user-facing precedent.

{{--error-format}} follows rustc, whose {{--error-format=short}} emits exactly 
the one-line form proposed here (and whose {{json}} value is the room-to-grow 
precedent). TypeScript's {{--pretty false}} is the same idea under a different 
name. The kebab-case spelling matches groovyc's recent options 
({{--enable-preview}}, {{--type-checked}}, {{--compile-static}}).

h2. Implementation notes

Rendering runs through {{ErrorCollector.write(PrintWriter, Janitor)}}, which 
delegates to {{Message.write(PrintWriter, Janitor)}} per message. For syntax 
errors, {{SyntaxErrorMessage.write}} prints {{name + ": " + line + ": " + 
getCause().getMessage()}} followed by the snippet from 
{{SourceUnit.getSample(line, column, janitor)}}, which appends the caret.

The pieces for the short form are already separately available, so it composes 
without string surgery:

* {{SyntaxException.getOriginalMessage()}} returns the bare text, while 
{{getMessage()}} is what appends {{" @ line N, column M."}}
* {{getStartLine()}} / {{getStartColumn()}} give the position
* {{SourceUnit.getName()}} gives the file

The other {{Message}} subclasses ({{SimpleMessage}}, {{LocatedMessage}}, 
{{ExceptionMessage}}, {{WarningMessage}}) each need a short rendering too; 
those without a position should degrade to {{file: error: message}} rather than 
inventing one.

h2. Open questions

* Should warnings follow the same format? They share the {{Message.write}} 
path, so the natural answer is yes.
* Should {{json}} be included from the start, or only the format name reserved?
* Should the default ever change? Keeping {{full}} indefinitely is the 
conservative choice; a tool that wants terse output can ask for it.


> Add a compact, machine-readable diagnostic output format to groovyc
> -------------------------------------------------------------------
>
>                 Key: GROOVY-12312
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12312
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Priority: Major
>
> Groovy renders every diagnostic across four lines, in a shape that no common 
> tool can parse. There is no way to ask for anything terser or more structured.
> h2. The current format
> {noformat}
> X.groovy: 1: The method 'void m()' has invalid modifier final.
>  @ line 1, column 15.
>    interface X { final void m() }
>                  ^
> {noformat}
> Three problems, beyond sheer size:
> * *The location is split across two lines and partly repeated.* The line 
> number appears twice, and the column only in the second line, so neither line 
> alone identifies the position.
> * *There is no severity token.* Nothing in the text says {{error}} or 
> {{warning}}; severity is implied by which section of the output the message 
> falls under.
> * *The shape matches no established convention.* It is neither the gcc/javac 
> {{file:line: severity: message}} form nor anything else editors and CI 
> systems already understand, so consumers are left regex-guessing. Note that 
> Groovy's {{X.groovy: 1: ...}} (spaces around the number, no severity) is 
> close enough to javac's {{X.java:1: error: ...}} to invite a parser that then 
> quietly mismatches.
> Size, measured on a source with 14 static type checking errors: *73 lines / 
> 3581 bytes*, roughly 256 bytes per diagnostic. For a human reading a terminal 
> the snippet and caret earn their place. For a build log, a CI annotation, an 
> editor problem matcher, or an LLM-based tool that already has the file open, 
> they are pure overhead.
> h2. Proposal
> Add an error format setting, selecting how diagnostics are rendered:
> * {{--error-format=<format>}} on the {{groovyc}} command line
> * {{errorFormat}} on {{CompilerConfiguration}}, so compiler configuration 
> scripts (and therefore Gradle, via {{groovyOptions.configurationScript}}) can 
> reach it
> * a matching attribute on the Ant {{<groovyc>}} task
> Two formats to begin with, with {{full}} the default so existing output is 
> unchanged:
> || Format || Output ||
> | {{full}} | the current four-line form, unchanged |
> | {{short}} | {{X.groovy:1:15: error: The method 'void m()' has invalid 
> modifier final.}} |
> A named setting rather than a boolean leaves room for a {{json}} format later 
> without adding a second option.
> h2. On naming
> Deliberately *not* {{-Xdiags:compact}}, despite the surface similarity to 
> javac. javac's {{-Xdiags:\{compact,verbose\}}} selects how much explanation 
> is given for method-resolution failures, and *both* modes still print the 
> source line and caret:
> {noformat}
> # javac -Xdiags:compact (the default)
> Dg.java:3: error: incompatible types: int cannot be converted to String
>     void go() { m(1); }
>                   ^
> {noformat}
> Reusing that name would mislead anyone who knows javac. javac's actual 
> machine-readable switch is {{-XDrawDiagnostics}}, an internal option for its 
> own test harness and not a user-facing precedent.
> {{--error-format}} follows rustc, whose {{--error-format=short}} emits 
> exactly the one-line form proposed here (and whose {{json}} value is the 
> room-to-grow precedent). TypeScript's {{--pretty false}} is the same idea 
> under a different name. The kebab-case spelling matches groovyc's recent 
> options ({{--enable-preview}}, {{--type-checked}}, {{--compile-static}}).
> h2. Implementation notes
> Rendering runs through {{ErrorCollector.write(PrintWriter, Janitor)}}, which 
> delegates to {{Message.write(PrintWriter, Janitor)}} per message. For syntax 
> errors, {{SyntaxErrorMessage.write}} prints {{name + ": " + line + ": " + 
> getCause().getMessage()}} followed by the snippet from 
> {{SourceUnit.getSample(line, column, janitor)}}, which appends the caret.
> The pieces for the short form are already separately available, so it 
> composes without string surgery:
> * {{SyntaxException.getOriginalMessage()}} returns the bare text, while 
> {{getMessage()}} is what appends {{" @ line N, column M."}}
> * {{getStartLine()}} / {{getStartColumn()}} give the position
> * {{SourceUnit.getName()}} gives the file
> The other {{Message}} subclasses ({{SimpleMessage}}, {{LocatedMessage}}, 
> {{ExceptionMessage}}, {{WarningMessage}}) each need a short rendering too; 
> those without a position should degrade to {{file: error: message}} rather 
> than inventing one.
> h2. Open questions
> * Should warnings follow the same format? They share the {{Message.write}} 
> path, so the natural answer is yes.
> * Should {{json}} be included from the start, or only the format name 
> reserved?
> * Should the default ever change? Keeping {{full}} indefinitely is the 
> conservative choice; a tool that wants terse output can ask for it.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to